public FastDeliveryVerificationViewModel(FastDeliveryAvailabilityHistory fastDeliveryAvailabilityHistory)
        {
            var order         = fastDeliveryAvailabilityHistory.Order;
            var deliveryPoint = fastDeliveryAvailabilityHistory.DeliveryPoint;

            DetailsTitle = $"Детализация по заказу №{order?.Id ?? 0}, адрес: {deliveryPoint?.ShortAddress}";

            FastDeliveryAvailabilityHistory = fastDeliveryAvailabilityHistory ?? throw new ArgumentNullException(nameof(fastDeliveryAvailabilityHistory));;

            var fastDeliveryHistoryConverter = new FastDeliveryHistoryConverter();

            Nodes = fastDeliveryHistoryConverter.ConvertAvailabilityHistoryItemsToVerificationDetailsNodes(fastDeliveryAvailabilityHistory.Items);

            Message = Nodes.Any(x => x.IsValidRLToFastDelivery)
                                ? "Есть доступные водители для быстрой доставки"
                                : "Нет доступных водителей для быстрой доставки";

            if (fastDeliveryAvailabilityHistory.AdditionalInformation != null)
            {
                Message += string.Join("\n", fastDeliveryAvailabilityHistory.AdditionalInformation);
            }
        }
        public FastDeliveryAvailabilityHistory GetRouteListsForFastDelivery(
            IUnitOfWork uow,
            double latitude,
            double longitude,
            bool isGetClosestByRoute,
            IDeliveryRulesParametersProvider deliveryRulesParametersProvider,
            IEnumerable <NomenclatureAmountNode> nomenclatureNodes,
            Order fastDeliveryOrder = null)
        {
            var maxDistanceToTrackPoint      = deliveryRulesParametersProvider.MaxDistanceToLatestTrackPointKm;
            var driverGoodWeightLiftPerHand  = deliveryRulesParametersProvider.DriverGoodWeightLiftPerHandInKg;
            var maxFastOrdersPerSpecificTime = deliveryRulesParametersProvider.MaxFastOrdersPerSpecificTime;

            var maxTimeForFastDeliveryTimespan = deliveryRulesParametersProvider.MaxTimeForFastDelivery;

            //Переводим всё в минуты
            var trackPointTimeOffset           = (int)deliveryRulesParametersProvider.MaxTimeOffsetForLatestTrackPoint.TotalMinutes;
            var maxTimeForFastDelivery         = (int)maxTimeForFastDeliveryTimespan.TotalMinutes;
            var minTimeForNewOrder             = (int)deliveryRulesParametersProvider.MinTimeForNewFastDeliveryOrder.TotalMinutes;
            var driverUnloadTime               = (int)deliveryRulesParametersProvider.DriverUnloadTime.TotalMinutes;
            var specificTimeForFastOrdersCount = (int)deliveryRulesParametersProvider.SpecificTimeForMaxFastOrdersCount.TotalMinutes;

            var fastDeliveryAvailabilityHistory = new FastDeliveryAvailabilityHistory
            {
                IsGetClosestByRoute = isGetClosestByRoute,
                Order = fastDeliveryOrder,
                MaxDistanceToLatestTrackPointKm = maxDistanceToTrackPoint,
                DriverGoodWeightLiftPerHandInKg = driverGoodWeightLiftPerHand,
                MaxFastOrdersPerSpecificTime    = maxFastOrdersPerSpecificTime,
                MaxTimeForFastDelivery          = maxTimeForFastDeliveryTimespan,
                MinTimeForNewFastDeliveryOrder  = deliveryRulesParametersProvider.MinTimeForNewFastDeliveryOrder,
                DriverUnloadTime = deliveryRulesParametersProvider.DriverUnloadTime,
                SpecificTimeForMaxFastOrdersCount = deliveryRulesParametersProvider.SpecificTimeForMaxFastOrdersCount,
            };

            var order = fastDeliveryAvailabilityHistory.Order;

            if (order != null)
            {
                fastDeliveryAvailabilityHistory.Order         = order.Id == 0 ? null : order;
                fastDeliveryAvailabilityHistory.Author        = order.Author;
                fastDeliveryAvailabilityHistory.DeliveryPoint = order.DeliveryPoint;
                fastDeliveryAvailabilityHistory.District      = order.DeliveryPoint.District;
                fastDeliveryAvailabilityHistory.Counterparty  = order.Client;
            }

            var fastDeliveryHistoryConverter = new FastDeliveryHistoryConverter();

            if (nomenclatureNodes != null)
            {
                fastDeliveryAvailabilityHistory.OrderItemsHistory =
                    fastDeliveryHistoryConverter.ConvertNomenclatureAmountNodesToOrderItemsHistory(nomenclatureNodes, fastDeliveryAvailabilityHistory);
            }

            var distributions = uow.GetAll <AdditionalLoadingNomenclatureDistribution>();

            fastDeliveryAvailabilityHistory.NomenclatureDistributionHistoryItems =
                fastDeliveryHistoryConverter.ConvertNomenclatureDistributionToDistributionHistory(distributions, fastDeliveryAvailabilityHistory);

            var district = GetDistrict(uow, (decimal)latitude, (decimal)longitude);

            if (district?.TariffZone == null || !district.TariffZone.IsFastDeliveryAvailableAtCurrentTime)
            {
                fastDeliveryAvailabilityHistory.AdditionalInformation =
                    new List <string> {
                    "Не найден район, у района отсутствует тарифная зона, либо недоступна экспресс-доставка в текущее время."
                };

                return(fastDeliveryAvailabilityHistory);
            }

            var neededNomenclatures = nomenclatureNodes.ToDictionary(x => x.NomenclatureId, x => x.Amount);

            Track      t       = null;
            TrackPoint tp      = null;
            RouteList  rl      = null;
            TrackPoint tpInner = null;
            FastDeliveryVerificationDetailsNode result = null;
            Employee e = null;

            RouteListItem       rla           = null;
            RouteListItem       rlaTransfered = null;
            Order               o             = null;
            OrderItem           oi            = null;
            OrderEquipment      oe            = null;
            CarLoadDocument     scld          = null;
            CarLoadDocumentItem scldi         = null;
            CountUnclosedFastDeliveryAddressesNode countUnclosedFastDeliveryAddressesAlias = null;

            RouteListNomenclatureAmount ordersAmountAlias        = null;
            RouteListNomenclatureAmount loadDocumentsAmountAlias = null;

            var lastTimeTrackQuery = QueryOver.Of(() => tpInner)
                                     .Where(() => tpInner.Track.Id == t.Id)
                                     .Select(Projections.Max(() => tpInner.TimeStamp));

            //МЛ только в пути и с погруженным запасом
            var routeListNodes = uow.Session.QueryOver(() => rl)
                                 .JoinEntityAlias(() => t, () => t.RouteList.Id == rl.Id)
                                 .Inner.JoinAlias(() => t.TrackPoints, () => tp)
                                 .Inner.JoinAlias(() => rl.Driver, () => e)
                                 .WithSubquery.WhereProperty(() => tp.TimeStamp).Eq(lastTimeTrackQuery)
                                 .And(() => rl.Status == RouteListStatus.EnRoute)
                                 .And(() => rl.AdditionalLoadingDocument.Id != null) // только с погруженным запасом
                                 .SelectList(list => list
                                             .Select(() => tp.TimeStamp).WithAlias(() => result.TimeStamp)
                                             .Select(() => tp.Latitude).WithAlias(() => result.Latitude)
                                             .Select(() => tp.Longitude).WithAlias(() => result.Longitude)
                                             .Select(Projections.Entity(() => rl)).WithAlias(() => result.RouteList))
                                 .TransformUsing(Transformers.AliasToBean <FastDeliveryVerificationDetailsNode>())
                                 .List <FastDeliveryVerificationDetailsNode>();

            //Последняя координата в указанном радиусе
            foreach (var node in routeListNodes)
            {
                var distance      = DistanceHelper.GetDistanceKm(node.Latitude, node.Longitude, latitude, longitude);
                var deliveryPoint = new PointOnEarth(latitude, longitude);
                var proposedRoute = OsrmClientFactory.Instance
                                    .GetRoute(new List <PointOnEarth> {
                    new PointOnEarth(node.Latitude, node.Longitude), deliveryPoint
                }, false, GeometryOverview.False, _globalSettings.ExcludeToll)?.Routes?
                                    .FirstOrDefault();

                node.DistanceByLineToClient.ParameterValue = (decimal)distance;
                node.DistanceByRoadToClient.ParameterValue = decimal.Round((decimal)(proposedRoute?.TotalDistance ?? int.MaxValue) / 1000, 2);
                if (distance < maxDistanceToTrackPoint)
                {
                    node.DistanceByLineToClient.IsValidParameter = node.DistanceByRoadToClient.IsValidParameter = true;
                }
                else
                {
                    node.DistanceByLineToClient.IsValidParameter = node.DistanceByRoadToClient.IsValidParameter = false;
                    node.IsValidRLToFastDelivery = false;
                }

                //Выставляем время последней координаты

                var timeSpan = DateTime.Now - node.TimeStamp;
                node.LastCoordinateTime.ParameterValue = timeSpan.TotalHours > 838 ? new TimeSpan(838, 0, 0) : timeSpan;

                if (node.LastCoordinateTime.ParameterValue.TotalMinutes <= trackPointTimeOffset)
                {
                    node.LastCoordinateTime.IsValidParameter = true;
                }
                else
                {
                    node.LastCoordinateTime.IsValidParameter = false;
                    node.IsValidRLToFastDelivery             = false;
                }
            }

            routeListNodes = routeListNodes
                             .OrderBy(x => isGetClosestByRoute ? x.DistanceByRoadToClient.ParameterValue : x.DistanceByLineToClient.ParameterValue)
                             .ToList();

            //Не более определённого кол-ва заказов с быстрой доставкой в определённый промежуток времени
            var addressCountSubquery = QueryOver.Of(() => rla)
                                       .Inner.JoinAlias(() => rla.Order, () => o)
                                       .Where(() => rla.RouteList.Id == rl.Id)
                                       .And(() => rla.Status == RouteListItemStatus.EnRoute)
                                       .And(() => o.IsFastDelivery)
                                       .And(Restrictions.GtProperty(
                                                Projections.Property(() => rla.CreationDate),
                                                Projections.SqlFunction(
                                                    new SQLFunctionTemplate(NHibernateUtil.DateTime,
                                                                            $"TIMESTAMPADD(MINUTE, -{specificTimeForFastOrdersCount}, CURRENT_TIMESTAMP)"),
                                                    NHibernateUtil.DateTime)))
                                       .Select(Projections.Count(() => rla.Id));

            var routeListsWithCountUnclosedFastDeliveries = uow.Session.QueryOver(() => rl)
                                                            .WhereRestrictionOn(() => rl.Id).IsInG(routeListNodes.Select(x => x.RouteList.Id))
                                                            .SelectList(list => list
                                                                        .Select(() => rl.Id).WithAlias(() => countUnclosedFastDeliveryAddressesAlias.RouteListId)
                                                                        .SelectSubQuery(addressCountSubquery).WithAlias(() => countUnclosedFastDeliveryAddressesAlias.UnclosedFastDeliveryAddresses))
                                                            .TransformUsing(Transformers.AliasToBean <CountUnclosedFastDeliveryAddressesNode>())
                                                            .List <CountUnclosedFastDeliveryAddressesNode>();

            var rlsWithCountUnclosedFastDeliveries =
                routeListsWithCountUnclosedFastDeliveries.ToDictionary(x => x.RouteListId, x => x.UnclosedFastDeliveryAddresses);

            foreach (var node in routeListNodes)
            {
                var countUnclosedFastDeliveryAddresses = rlsWithCountUnclosedFastDeliveries[node.RouteList.Id];
                node.UnClosedFastDeliveries.ParameterValue = countUnclosedFastDeliveryAddresses;
                if (countUnclosedFastDeliveryAddresses < maxFastOrdersPerSpecificTime)
                {
                    node.UnClosedFastDeliveries.IsValidParameter = true;
                }
                else
                {
                    node.UnClosedFastDeliveries.IsValidParameter = false;
                    node.IsValidRLToFastDelivery = false;
                }
            }

            //Время доставки следующего (текущего) заказа позволяет взять быструю доставку
            foreach (var routeListNode in routeListNodes)
            {
                RouteListItem latestAddress = null;

                var orderedEnRouteAddresses = routeListNode.RouteList.Addresses
                                              .Where(x => x.Status == RouteListItemStatus.EnRoute).OrderBy(x => x.IndexInRoute).ToList();

                var orderedCompletedAddresses = routeListNode.RouteList.Addresses
                                                .Where(x => x.Status == RouteListItemStatus.Completed).OrderBy(x => x.IndexInRoute).ToList();

                var latestCompletedAddress = orderedCompletedAddresses.OrderByDescending(x => x.StatusLastUpdate).FirstOrDefault();

                if (latestCompletedAddress != null)
                {
                    latestAddress = orderedEnRouteAddresses.FirstOrDefault(x => x.IndexInRoute > latestCompletedAddress.IndexInRoute);
                }
                if (latestAddress == null)
                {
                    latestAddress = orderedEnRouteAddresses.FirstOrDefault();
                }

                if (latestAddress != null)
                {
                    var neededTime1 = maxTimeForFastDelivery - latestAddress.Order.DeliveryPoint.MinutesToUnload;
                    if (neededTime1 < minTimeForNewOrder)
                    {
                        routeListNode.RemainingTimeForShipmentNewOrder.ParameterValue   = new TimeSpan(0, neededTime1, 0);
                        routeListNode.RemainingTimeForShipmentNewOrder.IsValidParameter = false;
                        routeListNode.IsValidRLToFastDelivery = false;
                        continue;
                    }

                    var water19Count = latestAddress.Order.OrderItems
                                       .Where(x => x.Nomenclature.TareVolume == TareVolume.Vol19L && x.Nomenclature.Category == NomenclatureCategory.water)
                                       .Sum(x => x.Count);

                    var orderItemsSummaryWeight = latestAddress.Order.OrderItems
                                                  .Where(x => x.Nomenclature.TareVolume != TareVolume.Vol19L || x.Nomenclature.Category != NomenclatureCategory.water)
                                                  .Sum(x => x.Nomenclature.Weight * x.Count);

                    var orderEquipmentsSummaryWeight = latestAddress.Order.OrderEquipments
                                                       .Where(x => x.Direction == Direction.Deliver)
                                                       .Sum(x => x.Nomenclature.Weight * x.Count);

                    var goodsSummaryWeight = orderItemsSummaryWeight + orderEquipmentsSummaryWeight;

                    //Время выгрузки след. заказа:
                    //(Суммарный вес прочих товаров / кол-во кг, которое водитель может унести в одной руке + кол-во 19л) / 2 руки * время выгрузки в 2 руках 2 бутылей или товара
                    var unloadTime  = (goodsSummaryWeight / driverGoodWeightLiftPerHand + water19Count) / 2 * driverUnloadTime;
                    var neededTime2 = maxTimeForFastDelivery - (int)unloadTime;

                    if (neededTime2 < minTimeForNewOrder)
                    {
                        routeListNode.RemainingTimeForShipmentNewOrder.ParameterValue   = new TimeSpan(0, neededTime2, 0);
                        routeListNode.RemainingTimeForShipmentNewOrder.IsValidParameter = false;
                        routeListNode.IsValidRLToFastDelivery = false;
                    }
                    else
                    {
                        routeListNode.RemainingTimeForShipmentNewOrder.ParameterValue   = new TimeSpan(0, neededTime2, 0);
                        routeListNode.RemainingTimeForShipmentNewOrder.IsValidParameter = true;
                    }
                }
                else
                {
                    routeListNode.RemainingTimeForShipmentNewOrder.ParameterValue   = maxTimeForFastDeliveryTimespan;
                    routeListNode.RemainingTimeForShipmentNewOrder.IsValidParameter = true;
                }
            }

            var rlIds = routeListNodes.Select(x => x.RouteList.Id).ToArray();

            //OrderItems
            var orderItemsToDeliver = uow.Session.QueryOver <RouteListItem>(() => rla)
                                      .Inner.JoinAlias(() => rla.Order, () => o)
                                      .Inner.JoinAlias(() => o.OrderItems, () => oi)
                                      .Left.JoinAlias(() => rla.TransferedTo, () => rlaTransfered)
                                      .WhereRestrictionOn(() => rla.RouteList.Id).IsIn(rlIds)
                                      .WhereRestrictionOn(() => oi.Nomenclature.Id).IsIn(neededNomenclatures.Keys)
                                      .Where(() =>
                                             //не отменённые и не недовозы
                                             rla.Status != RouteListItemStatus.Canceled && rla.Status != RouteListItemStatus.Overdue
                                             // и не перенесённые к водителю; либо перенесённые с погрузкой; либо перенесённые и это экспресс-доставка (всегда без погрузки)
                                             && (!rla.WasTransfered || rla.NeedToReload || o.IsFastDelivery)
                                             // и не перенесённые от водителя; либо перенесённые и не нужна погрузка и не экспресс-доставка (остатки по экспресс-доставке не переносятся)
                                             && (rla.Status != RouteListItemStatus.Transfered || (!rlaTransfered.NeedToReload && !o.IsFastDelivery)))
                                      .SelectList(list => list
                                                  .SelectGroup(() => rla.RouteList.Id).WithAlias(() => ordersAmountAlias.RouteListId)
                                                  .SelectGroup(() => oi.Nomenclature.Id).WithAlias(() => ordersAmountAlias.NomenclatureId)
                                                  .SelectSum(() => oi.Count).WithAlias(() => ordersAmountAlias.Amount))
                                      .TransformUsing(Transformers.AliasToBean <RouteListNomenclatureAmount>())
                                      .Future <RouteListNomenclatureAmount>();

            //OrderEquipments
            var orderEquipmentsToDeliver = uow.Session.QueryOver <RouteListItem>(() => rla)
                                           .Inner.JoinAlias(() => rla.Order, () => o)
                                           .Inner.JoinAlias(() => o.OrderEquipments, () => oe)
                                           .Left.JoinAlias(() => rla.TransferedTo, () => rlaTransfered)
                                           .WhereRestrictionOn(() => rla.RouteList.Id).IsIn(rlIds)
                                           .WhereRestrictionOn(() => oe.Nomenclature.Id).IsIn(neededNomenclatures.Keys)
                                           .Where(() =>
                                                  //не отменённые и не недовозы
                                                  rla.Status != RouteListItemStatus.Canceled && rla.Status != RouteListItemStatus.Overdue
                                                  // и не перенесённые к водителю; либо перенесённые с погрузкой; либо перенесённые и это экспресс-доставка (всегда без погрузки)
                                                  && (!rla.WasTransfered || rla.NeedToReload || o.IsFastDelivery)
                                                  // и не перенесённые от водителя; либо перенесённые и не нужна погрузка и не экспресс-доставка (остатки по экспресс-доставке не переносятся)
                                                  && (rla.Status != RouteListItemStatus.Transfered || (!rlaTransfered.NeedToReload && !o.IsFastDelivery)))
                                           .And(() => oe.Direction == Direction.Deliver)
                                           .SelectList(list => list
                                                       .SelectGroup(() => rla.RouteList.Id).WithAlias(() => ordersAmountAlias.RouteListId)
                                                       .SelectGroup(() => oe.Nomenclature.Id).WithAlias(() => ordersAmountAlias.NomenclatureId)
                                                       .Select(Projections.Sum(Projections.Cast(NHibernateUtil.Decimal, Projections.Property(() => oe.Count)))
                                                               ).WithAlias(() => ordersAmountAlias.Amount))
                                           .TransformUsing(Transformers.AliasToBean <RouteListNomenclatureAmount>())
                                           .Future <RouteListNomenclatureAmount>();

            //CarLoadDocuments
            var allLoaded = uow.Session.QueryOver <CarLoadDocument>(() => scld)
                            .Inner.JoinAlias(() => scld.Items, () => scldi)
                            .WhereRestrictionOn(() => scld.RouteList.Id).IsIn(rlIds)
                            .WhereRestrictionOn(() => scldi.Nomenclature.Id).IsIn(neededNomenclatures.Keys)
                            .SelectList(list => list
                                        .SelectGroup(() => scld.RouteList.Id).WithAlias(() => loadDocumentsAmountAlias.RouteListId)
                                        .SelectGroup(() => scldi.Nomenclature.Id).WithAlias(() => loadDocumentsAmountAlias.NomenclatureId)
                                        .SelectSum(() => scldi.Amount).WithAlias(() => loadDocumentsAmountAlias.Amount))
                            .TransformUsing(Transformers.AliasToBean <RouteListNomenclatureAmount>())
                            .Future <RouteListNomenclatureAmount>();

            var allToDeliver = orderItemsToDeliver
                               .Union(orderEquipmentsToDeliver)
                               .GroupBy(x => new { x.RouteListId, x.NomenclatureId })
                               .Select(group => new RouteListNomenclatureAmount
            {
                RouteListId    = group.Key.RouteListId,
                NomenclatureId = group.Key.NomenclatureId,
                Amount         = group.Sum(x => x.Amount)
            })
                               .ToList();

            //Выбираем МЛ, в котором хватает запаса номенклатур на поступивший быстрый заказ
            foreach (var routeListNode in routeListNodes)
            {
                var toDeliverForRL = allToDeliver.Where(x => x.RouteListId == routeListNode.RouteList.Id).ToList();
                var loadedForRL    = allLoaded.Where(x => x.RouteListId == routeListNode.RouteList.Id).ToList();

                foreach (var need in neededNomenclatures)
                {
                    var toDeliver = toDeliverForRL.FirstOrDefault(x => x.NomenclatureId == need.Key)?.Amount ?? 0;
                    var loaded    = loadedForRL.FirstOrDefault(x => x.NomenclatureId == need.Key)?.Amount ?? 0;

                    var onBoard = loaded - toDeliver;
                    if (onBoard < need.Value)
                    {
                        routeListNode.IsGoodsEnough.ParameterValue   = false;
                        routeListNode.IsGoodsEnough.IsValidParameter = false;
                        routeListNode.IsValidRLToFastDelivery        = false;
                        break;
                    }
                }
            }

            if (routeListNodes != null)
            {
                fastDeliveryAvailabilityHistory.Items = fastDeliveryHistoryConverter
                                                        .ConvertVerificationDetailsNodesToAvailabilityHistoryItems(routeListNodes, fastDeliveryAvailabilityHistory);
            }

            return(fastDeliveryAvailabilityHistory);
        }