private static void StartMainWindow( string loginDialogName, IApplicationConfigurator applicationConfigurator, IParametersProvider parametersProvider) { //Настрока удаления Configure.ConfigureDeletion(); PerformanceHelper.AddTimePoint(logger, "Закончена настройка удаления"); //Настройка сервисов if (parametersProvider.ContainsParameter("instant_sms_enabled_database") && parametersProvider.ContainsParameter("sms_service_address")) { if (parametersProvider.GetParameterValue("instant_sms_enabled_database") == loginDialogName) { InstantSmsServiceSetting.Init(parametersProvider.GetParameterValue("sms_service_address")); } } if (parametersProvider.ContainsParameter("sms_payment_send_enabled_database") && parametersProvider.ContainsParameter("sms_payment_send_service_address")) { if (parametersProvider.GetParameterValue("sms_payment_send_enabled_database") == loginDialogName) { SmsPaymentServiceSetting.Init(parametersProvider.GetParameterValue("sms_payment_send_service_address")); } } DriverApiParametersProvider.InitializeNotifications(parametersProvider, loginDialogName); CreateTempDir(); //Запускаем программу MainWin = new MainWindow(passwordValidator, applicationConfigurator); MainWin.Title += $" (БД: {loginDialogName})"; QSMain.ErrorDlgParrent = MainWin; MainWin.Show(); }
public RouteListClosingDlg(int routeListId) { this.Build(); PerformanceHelper.StartMeasurement(); UoWGeneric = UnitOfWorkFactory.CreateForRoot <RouteList>(routeListId); this.HasChanges = true; TabName = String.Format("Закрытие маршрутного листа №{0}", Entity.Id); PerformanceHelper.AddTimePoint("Создан UoW"); ConfigureDlg(); }
private static void StartMainWindow(string loginDialogName) { //Настрока удаления Configure.ConfigureDeletion(); PerformanceHelper.AddTimePoint(logger, "Закончена настройка удаления"); //Настройка сервисов if (ParametersProvider.Instance.ContainsParameter("email_send_enabled_database") && ParametersProvider.Instance.ContainsParameter("email_service_address")) { if (ParametersProvider.Instance.GetParameterValue("email_send_enabled_database") == loginDialogName) { EmailServiceSetting.Init(ParametersProvider.Instance.GetParameterValue("email_service_address")); } } if (ParametersProvider.Instance.ContainsParameter("instant_sms_enabled_database") && ParametersProvider.Instance.ContainsParameter("sms_service_address")) { if (ParametersProvider.Instance.GetParameterValue("instant_sms_enabled_database") == loginDialogName) { InstantSmsServiceSetting.Init(ParametersProvider.Instance.GetParameterValue("sms_service_address")); } } if (ParametersProvider.Instance.ContainsParameter("sms_payment_send_enabled_database") && ParametersProvider.Instance.ContainsParameter("sms_payment_send_service_address")) { if (ParametersProvider.Instance.GetParameterValue("sms_payment_send_enabled_database") == loginDialogName) { SmsPaymentServiceSetting.Init(ParametersProvider.Instance.GetParameterValue("sms_payment_send_service_address")); } } CreateTempDir(); //Запускаем программу MainWin = new MainWindow(); MainWin.Title += $" (БД: {loginDialogName})"; QSMain.ErrorDlgParrent = MainWin; MainWin.Show(); }
public static void Main(string[] args) { CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture("ru-RU"); Application.Init(); QSMain.GuiThread = System.Threading.Thread.CurrentThread; applicationInfo = new ApplicationVersionInfo(); #region Первоначальная настройка обработки ошибок SingletonErrorReporter.Initialize(ReportWorker.GetReportService(), applicationInfo, new LogService(), null, false, null); var errorMessageModelFactoryWithoutUserService = new DefaultErrorMessageModelFactory(SingletonErrorReporter.Instance, null, null); var exceptionHandler = new DefaultUnhandledExceptionHandler(errorMessageModelFactoryWithoutUserService, applicationInfo); exceptionHandler.SubscribeToUnhandledExceptions(); exceptionHandler.GuiThread = System.Threading.Thread.CurrentThread; #endregion //FIXME Удалить после того как будет удалена зависимость от библиотеки QSProjectLib QSMain.ProjectPermission = new System.Collections.Generic.Dictionary <string, UserPermission>(); CreateProjectParam(); ConfigureViewModelWidgetResolver(); ConfigureJournalColumnsConfigs(); QSMain.SetupFromArgs(args); QS.Project.Search.GtkUI.SearchView.QueryDelay = 1500; Gtk.Settings.Default.SetLongProperty("gtk-button-images", 1, ""); // Создаем окно входа Login LoginDialog = new Login(); LoginDialog.Logo = Gdk.Pixbuf.LoadFromResource("Vodovoz.icons.logo.png"); LoginDialog.SetDefaultNames("Vodovoz"); LoginDialog.DefaultLogin = "******"; LoginDialog.DefaultServer = "sql.vod.qsolution.ru"; LoginDialog.UpdateFromGConf(); ResponseType LoginResult; LoginResult = (ResponseType)LoginDialog.Run(); if (LoginResult == ResponseType.DeleteEvent || LoginResult == ResponseType.Cancel) { return; } LoginDialog.Destroy(); PerformanceHelper.StartMeasurement("Замер запуска приложения"); GetPermissionsSettings(); //Настройка базы CreateBaseConfig(); PerformanceHelper.AddTimePoint(logger, "Закончена настройка базы"); VodovozGtkServicesConfig.CreateVodovozDefaultServices(); ParametersProvider.Instance.RefreshParameters(); #region Настройка обработки ошибок c параметрами из базы и сервисами var baseParameters = new BaseParametersProvider(); SingletonErrorReporter.Initialize( ReportWorker.GetReportService(), applicationInfo, new LogService(), LoginDialog.BaseName, LoginDialog.BaseName == baseParameters.GetDefaultBaseForErrorSend(), baseParameters.GetRowCountForErrorLog() ); var errorMessageModelFactoryWithUserService = new DefaultErrorMessageModelFactory(SingletonErrorReporter.Instance, ServicesConfig.UserService, UnitOfWorkFactory.GetDefaultFactory); exceptionHandler.InteractiveService = ServicesConfig.InteractiveService; exceptionHandler.ErrorMessageModelFactory = errorMessageModelFactoryWithUserService; //Настройка обычных обработчиков ошибок. exceptionHandler.CustomErrorHandlers.Add(CommonErrorHandlers.MySqlException1055OnlyFullGroupBy); exceptionHandler.CustomErrorHandlers.Add(CommonErrorHandlers.MySqlException1366IncorrectStringValue); exceptionHandler.CustomErrorHandlers.Add(CommonErrorHandlers.NHibernateFlushAfterException); exceptionHandler.CustomErrorHandlers.Add(ErrorHandlers.NHibernateStaleObjectStateExceptionHandler); exceptionHandler.CustomErrorHandlers.Add(ErrorHandlers.MySqlExceptionConnectionTimeoutHandler); exceptionHandler.CustomErrorHandlers.Add(ErrorHandlers.MySqlExceptionAuthHandler); #endregion //Настройка карты GMapProvider.UserAgent = String.Format("{0}/{1} used GMap.Net/{2} ({3})", applicationInfo.ProductName, applicationInfo.Version.VersionToShortString(), Assembly.GetAssembly(typeof(GMapProvider)).GetName().Version.VersionToShortString(), Environment.OSVersion.VersionString ); GMapProvider.Language = GMap.NET.LanguageType.Russian; PerformanceHelper.AddTimePoint(logger, "Закончена настройка карты."); DatePicker.CalendarFontSize = 16; DateRangePicker.CalendarFontSize = 16; OsmWorker.ServiceHost = "osm.vod.qsolution.ru"; OsmWorker.ServicePort = 7073; QS.Osm.Osrm.OsrmMain.ServerUrl = "http://osrm.vod.qsolution.ru:5000"; PerformanceHelper.StartPointsGroup("Главное окно"); var baseVersionChecker = new CheckBaseVersion(applicationInfo, new ParametersService(QS.Project.DB.Connection.ConnectionDB)); if (baseVersionChecker.Check()) { ServicesConfig.CommonServices.InteractiveService.ShowMessage(ImportanceLevel.Warning, baseVersionChecker.TextMessage, "Несовпадение версии"); return; } QSMain.CheckServer(null); // Проверяем настройки сервера PerformanceHelper.AddTimePoint("Закончена загрузка параметров базы и проверка версии."); AutofacClassConfig(); PerformanceHelper.AddTimePoint("Закончена настройка AutoFac."); if (QSMain.User.Login == "root") { string Message = "Вы зашли в программу под администратором базы данных. У вас есть только возможность создавать других пользователей."; MessageDialog md = new MessageDialog(null, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, Message); md.Run(); md.Destroy(); UsersDialog usersDlg = new UsersDialog(ServicesConfig.InteractiveService); usersDlg.Show(); usersDlg.Run(); usersDlg.Destroy(); return; } else { if (ChangePassword(LoginDialog.BaseName) && CanLogin()) { StartMainWindow(LoginDialog.BaseName); } else { return; } } PerformanceHelper.EndPointsGroup(); PerformanceHelper.AddTimePoint(logger, "Закончен старт SAAS. Конец загрузки."); QSSaaS.Session.StartSessionRefresh(); PerformanceHelper.AddTimePoint(logger, "Закончен старт SAAS. Конец загрузки."); PerformanceHelper.Main.PrintAllPoints(logger); Application.Run(); QSSaaS.Session.StopSessionRefresh(); ClearTempDir(); }
// <summary> // Получаем предложение по оптимальному расположению адресов в указанном маршруте. // Рачет идет с учетом окон доставки. Но естественно без любых ограничений по весу и прочему. // </summary> // <returns>Предолженый маршрут</returns> // <param name="route">Первоначальный маршрутный лист, чтобы взять адреса.</param> public ProposedRoute RebuidOneRoute(RouteList route) { var trip = new PossibleTrip(route); logger.Info("Подготавливаем заказы..."); PerformanceHelper.StartMeasurement($"Строим маршрут"); List <CalculatedOrder> calculatedOrders = new List <CalculatedOrder>(); foreach (var address in route.Addresses) { if (address.Order.DeliveryPoint.Longitude == null || address.Order.DeliveryPoint.Latitude == null) { continue; } calculatedOrders.Add(new CalculatedOrder(address.Order, null)); } Nodes = calculatedOrders.ToArray(); distanceCalculator = new ExtDistanceCalculator(DistanceProvider.Osrm, Nodes.Select(x => x.Order.DeliveryPoint).ToArray(), StatisticsTxtAction); PerformanceHelper.AddTimePoint(logger, $"Подготовка заказов"); logger.Info("Создаем модель..."); RoutingModel routing = new RoutingModel(Nodes.Length + 1, 1, 0); int horizon = 24 * 3600; routing.AddDimension(new CallbackTime(Nodes, trip, distanceCalculator), 3 * 3600, horizon, false, "Time"); var time_dimension = routing.GetDimensionOrDie("Time"); var cumulTimeOnEnd = routing.CumulVar(routing.End(0), "Time"); var cumulTimeOnBegin = routing.CumulVar(routing.Start(0), "Time"); if (route.Shift != null) { var shift = route.Shift; cumulTimeOnEnd.SetMax((long)shift.EndTime.TotalSeconds); cumulTimeOnBegin.SetMin((long)shift.StartTime.TotalSeconds); } routing.SetArcCostEvaluatorOfVehicle(new CallbackDistance(Nodes, distanceCalculator), 0); for (int ix = 0; ix < Nodes.Length; ix++) { var startWindow = Nodes[ix].Order.DeliverySchedule.From.TotalSeconds; var endWindow = Nodes[ix].Order.DeliverySchedule.To.TotalSeconds - trip.Driver.TimeCorrection(Nodes[ix].Order.CalculateTimeOnPoint(route.Forwarder != null)); if (endWindow < startWindow) { logger.Warn("Время разгрузки на точке, не помещается в диапазон времени доставки. {0}-{1}", Nodes[ix].Order.DeliverySchedule.From, Nodes[ix].Order.DeliverySchedule.To); endWindow = startWindow; } time_dimension.CumulVar(ix + 1).SetRange((long)startWindow, (long)endWindow); routing.AddDisjunction(new int[] { ix + 1 }, MaxDistanceAddressPenalty); } RoutingSearchParameters search_parameters = RoutingModel.DefaultSearchParameters(); search_parameters.FirstSolutionStrategy = FirstSolutionStrategy.Types.Value.ParallelCheapestInsertion; search_parameters.TimeLimitMs = MaxTimeSeconds * 1000; //search_parameters.FingerprintArcCostEvaluators = true; //search_parameters.OptimizationStep = 100; var solver = routing.solver(); PerformanceHelper.AddTimePoint(logger, $"Настроили оптимизацию"); logger.Info("Закрываем модель..."); logger.Info("Рассчет расстояний между точками..."); routing.CloseModelWithParameters(search_parameters); distanceCalculator.FlushCache(); var lastSolution = solver.MakeLastSolutionCollector(); lastSolution.AddObjective(routing.CostVar()); routing.AddSearchMonitor(lastSolution); routing.AddSearchMonitor(new CallbackMonitor(solver, StatisticsTxtAction, lastSolution)); PerformanceHelper.AddTimePoint(logger, $"Закрыли модель"); logger.Info("Поиск решения..."); Assignment solution = routing.SolveWithParameters(search_parameters); PerformanceHelper.AddTimePoint(logger, $"Получили решение."); logger.Info("Готово. Заполняем."); Console.WriteLine("Status = {0}", routing.Status()); ProposedRoute proposedRoute = null; if (solution != null) { // Solution cost. Console.WriteLine("Cost = {0}", solution.ObjectiveValue()); time_dimension = routing.GetDimensionOrDie("Time"); int route_number = 0; proposedRoute = new ProposedRoute(null); long first_node = routing.Start(route_number); long second_node = solution.Value(routing.NextVar(first_node)); // Пропускаем первый узел, так как это наша база. proposedRoute.RouteCost = routing.GetCost(first_node, second_node, route_number); while (!routing.IsEnd(second_node)) { var time_var = time_dimension.CumulVar(second_node); var rPoint = new ProposedRoutePoint( TimeSpan.FromSeconds(solution.Min(time_var)), TimeSpan.FromSeconds(solution.Max(time_var)), Nodes[second_node - 1].Order ); rPoint.DebugMaxMin = string.Format("\n({0},{1})[{3}-{4}]-{2}", new DateTime().AddSeconds(solution.Min(time_var)).ToShortTimeString(), new DateTime().AddSeconds(solution.Max(time_var)).ToShortTimeString(), second_node, rPoint.Order.DeliverySchedule.From.ToString("hh\\:mm"), rPoint.Order.DeliverySchedule.To.ToString("hh\\:mm") ); proposedRoute.Orders.Add(rPoint); first_node = second_node; second_node = solution.Value(routing.NextVar(first_node)); proposedRoute.RouteCost += routing.GetCost(first_node, second_node, route_number); } } PerformanceHelper.Main.PrintAllPoints(logger); if (distanceCalculator.ErrorWays.Count > 0) { logger.Debug("Ошибок получения расстояний {0}", distanceCalculator.ErrorWays.Count); var uniqueFrom = distanceCalculator.ErrorWays.Select(x => x.FromHash).Distinct().ToList(); var uniqueTo = distanceCalculator.ErrorWays.Select(x => x.ToHash).Distinct().ToList(); logger.Debug("Уникальных точек: отправки = {0}, прибытия = {1}", uniqueFrom.Count, uniqueTo.Count); logger.Debug("Проблемные точки отправки:\n{0}", string.Join("; ", distanceCalculator.ErrorWays .GroupBy(x => x.FromHash) .Where(x => x.Count() > (uniqueTo.Count / 2)) .Select(x => CachedDistance.GetText(x.Key))) ); logger.Debug("Проблемные точки прибытия:\n{0}", string.Join("; ", distanceCalculator.ErrorWays .GroupBy(x => x.ToHash) .Where(x => x.Count() > (uniqueFrom.Count / 2)) .Select(x => CachedDistance.GetText(x.Key))) ); } distanceCalculator.Dispose(); return(proposedRoute); }
// <summary> // Метод создаем маршруты на день основываясь на данных всесенных в поля <c>Routes</c>, <c>Orders</c>, // <c>Drivers</c> и <c>Forwarders</c>. // </summary> public void CreateRoutes(TimeSpan drvStartTime, TimeSpan drvEndTime) { WarningMessages.Clear(); ProposedRoutes.Clear(); //Очищаем сразу, так как можем выйти из метода ранее. logger.Info("Подготавливаем заказы..."); PerformanceHelper.StartMeasurement($"Строим оптимальные маршруты"); // Создаем список поездок всех водителей. Тут перебираем всех водителей с машинами // и создаем поездки для них, в зависимости от выбранного режима работы. var trips = Drivers.Where(x => x.Car != null) .OrderBy(x => x.PriorityAtDay) .SelectMany(drv => drv.DaySchedule != null ? drv.DaySchedule.Shifts.Where(s => s.StartTime >= drvStartTime && s.StartTime < drvEndTime) .Select(shift => new PossibleTrip(drv, shift)) : new[] { new PossibleTrip(drv, null) } ) .ToList(); // Стыкуем уже созданные маршрутные листы с возможными поездками, на основании водителя и смены. // Если уже созданный маршрут не найден в поездках, то создаем поездку для него. foreach (var existRoute in Routes) { var trip = trips.FirstOrDefault(x => x.Driver == existRoute.Driver && x.Shift == existRoute.Shift); if (trip != null) { trip.OldRoute = existRoute; } else { trips.Add(new PossibleTrip(existRoute)); } //Проверяем все ли заказы из МЛ присутствуют в списке заказов. Если их нет. Добавляем. foreach (var address in existRoute.Addresses) { if (Orders.All(x => x.Id != address.Order.Id)) { Orders.Add(address.Order); } } } var possibleRoutes = trips.ToArray(); if (!possibleRoutes.Any()) { AddWarning("Для построения маршрутов, нет водителей."); return; } TestCars(possibleRoutes); var areas = UoW.GetAll <District>().Where(x => x.DistrictsSet.Status == DistrictsSetStatus.Active).ToList(); List <District> unusedDistricts = new List <District>(); List <CalculatedOrder> calculatedOrders = new List <CalculatedOrder>(); // Перебираем все заказы, исключаем те которые без координат, определяем для каждого заказа район // на основании координат. И создавая экземпляр <c>CalculatedOrder</c>, происходит подсчет сумарной // информации о заказе. Всего бутылей, вес и прочее. foreach (var order in Orders) { if (order.DeliveryPoint.Longitude == null || order.DeliveryPoint.Latitude == null) { continue; } var point = new Point((double)order.DeliveryPoint.Latitude.Value, (double)order.DeliveryPoint.Longitude.Value); var area = areas.Find(x => x.DistrictBorder.Contains(point)); if (area != null) { var oldRoute = Routes.FirstOrDefault(r => r.Addresses.Any(a => a.Order.Id == order.Id)); if (oldRoute != null) { calculatedOrders.Add(new CalculatedOrder(order, area, false, oldRoute)); } else if (possibleRoutes.SelectMany(x => x.Districts).Any(x => x.District.Id == area.Id)) { var cOrder = new CalculatedOrder(order, area); //if(possibleRoutes.Any(r => r.GeographicGroup.Id == cOrder.ShippingBase.Id))//убрать, если в автоформировании должны учавствовать заказы из всех частей города вне зависимости от того какие части города выбраны в диалоге calculatedOrders.Add(cOrder); } else if (!unusedDistricts.Contains(area)) { unusedDistricts.Add(area); } } } Nodes = calculatedOrders.ToArray(); if (unusedDistricts.Any()) { AddWarning("Районы без водителей: {0}", string.Join(", ", unusedDistricts.Select(x => x.DistrictName))); } // Создаем калькулятор расчета расстояний. Он сразу запрашивает уже имеющиеся расстояния из кеша // и в фоновом режиме начинает считать недостающую матрицу. distanceCalculator = new ExtDistanceCalculator(DistanceProvider.Osrm, Nodes.Select(x => x.Order.DeliveryPoint).ToArray(), StatisticsTxtAction); logger.Info("Развозка по {0} районам.", calculatedOrders.Select(x => x.District).Distinct().Count()); PerformanceHelper.AddTimePoint(logger, $"Подготовка заказов"); // Пред запуском оптимизации мы должны создать модель и внести в нее все необходимые данные. logger.Info("Создаем модель..."); RoutingModel routing = new RoutingModel(Nodes.Length + 1, possibleRoutes.Length, 0); // Создаем измерение со временем на маршруте. // <c>horizon</c> - ограничивает максимально допустимое значение диапазона, чтобы не уйти за границы суток; // <c>maxWaitTime</c> - Максимальное время ожидания водителя. То есть водитель закончил разгрузку следующий // адрес в маршруте у него не должен быть позже чем на 4 часа ожидания. int horizon = 24 * 3600; int maxWaitTime = 4 * 3600; var timeEvaluators = possibleRoutes.Select(x => new CallbackTime(Nodes, x, distanceCalculator)).ToArray(); routing.AddDimensionWithVehicleTransits(timeEvaluators, maxWaitTime, horizon, false, "Time"); var time_dimension = routing.GetDimensionOrDie("Time"); // Ниже заполняем все измерения для учета бутылей, веса, адресов, объема. var bottlesCapacity = possibleRoutes.Select(x => (long)x.Car.MaxBottles).ToArray(); routing.AddDimensionWithVehicleCapacity(new CallbackBottles(Nodes), 0, bottlesCapacity, true, "Bottles"); var weightCapacity = possibleRoutes.Select(x => (long)x.Car.MaxWeight).ToArray(); routing.AddDimensionWithVehicleCapacity(new CallbackWeight(Nodes), 0, weightCapacity, true, "Weight"); var volumeCapacity = possibleRoutes.Select(x => (long)(x.Car.MaxVolume * 1000)).ToArray(); routing.AddDimensionWithVehicleCapacity(new CallbackVolume(Nodes), 0, volumeCapacity, true, "Volume"); var addressCapacity = possibleRoutes.Select(x => (long)(x.Driver.MaxRouteAddresses)).ToArray(); routing.AddDimensionWithVehicleCapacity(new CallbackAddressCount(Nodes.Length), 0, addressCapacity, true, "AddressCount"); var bottlesDimension = routing.GetDimensionOrDie("Bottles"); var addressDimension = routing.GetDimensionOrDie("AddressCount"); for (int ix = 0; ix < possibleRoutes.Length; ix++) { // Устанавливаем функцию получения стоимости маршрута. routing.SetArcCostEvaluatorOfVehicle(new CallbackDistanceDistrict(Nodes, possibleRoutes[ix], distanceCalculator), ix); // Добавляем фиксированный штраф за принадлежность водителя. if (possibleRoutes[ix].Driver.DriverType.HasValue) { routing.SetFixedCostOfVehicle(((int)possibleRoutes[ix].Driver.DriverType) * DriverPriorityPenalty, ix); } else { routing.SetFixedCostOfVehicle(DriverPriorityPenalty * 3, ix); } var cumulTimeOnEnd = routing.CumulVar(routing.End(ix), "Time"); var cumulTimeOnBegin = routing.CumulVar(routing.Start(ix), "Time"); // Устанавливаем минимальные(мягкие) границы для измерений. При значениях меньше минимальных, маршрут все таки принимается, // но вносятся некоторые штрафные очки на последнюю точку маршрута. //bottlesDimension.SetEndCumulVarSoftLowerBound(ix, possibleRoutes[ix].Car.MinBottles, MinBottlesInRoutePenalty); //addressDimension.SetEndCumulVarSoftLowerBound(ix, possibleRoutes[ix].Driver.MinRouteAddresses, MinAddressesInRoutePenalty); // Устанавливаем диапазон времени для движения по маршруту в зависимости от выбраной смены, // день, вечер и с учетом досрочного завершения водителем работы. if (possibleRoutes[ix].Shift != null) { var shift = possibleRoutes[ix].Shift; var endTime = possibleRoutes[ix].EarlyEnd.HasValue ? Math.Min(shift.EndTime.TotalSeconds, possibleRoutes[ix].EarlyEnd.Value.TotalSeconds) : shift.EndTime.TotalSeconds; cumulTimeOnEnd.SetMax((long)endTime); cumulTimeOnBegin.SetMin((long)shift.StartTime.TotalSeconds); } else if (possibleRoutes[ix].EarlyEnd.HasValue) //Устанавливаем время окончания рабочего дня у водителя. { cumulTimeOnEnd.SetMax((long)possibleRoutes[ix].EarlyEnd.Value.TotalSeconds); } } for (int ix = 0; ix < Nodes.Length; ix++) { // Проставляем на каждый адрес окно времени приезда. var startWindow = Nodes[ix].Order.DeliverySchedule.From.TotalSeconds; var endWindow = Nodes[ix].Order.DeliverySchedule.To.TotalSeconds - Nodes[ix].Order.CalculateTimeOnPoint(false); //FIXME Внимание здесь задаем время без экспедитора и без учета скорости водителя. Это не правильно, но другого варианта я придумать не смог. if (endWindow < startWindow) { AddWarning("Время разгрузки на {2}, не помещается в диапазон времени доставки. {0}-{1}", Nodes[ix].Order.DeliverySchedule.From, Nodes[ix].Order.DeliverySchedule.To, Nodes[ix].Order.DeliveryPoint.ShortAddress); endWindow = startWindow; } time_dimension.CumulVar(ix + 1).SetRange((long)startWindow, (long)endWindow); // Добавляем абсолютно все заказы в дизюкцию. Если бы заказы небыли вдобавлены в отдельные дизьюкции // то при не возможность доставить хоть один заказ. Все решение бы считаль не верным. Добавление каждого заказа // в отдельную дизьюкцию, позволяет механизму не вести какой то и заказов, и все таки формировать решение с недовезенными // заказами. Дизьюкция работает так. Он говорит, если хотя бы один заказ в этой группе(дизьюкции) доставлен, // то все хорошо, иначе штраф. Так как у нас в кадой дизьюкции по одному заказу. Мы получаем опциональную доставку каждого заказа. routing.AddDisjunction(new int[] { ix + 1 }, MaxDistanceAddressPenalty); } logger.Debug("Nodes.Length = {0}", Nodes.Length); logger.Debug("routing.Nodes() = {0}", routing.Nodes()); logger.Debug("GetNumberOfDisjunctions = {0}", routing.GetNumberOfDisjunctions()); RoutingSearchParameters search_parameters = RoutingModel.DefaultSearchParameters(); // Setting first solution heuristic (cheapest addition). // Указывается стратегия первоначального заполнения. Опытным путем было вычислено, что именно при // стратегиях вставки маршруты получаются с набором точек более близких к друг другу. То есть в большей // степени облачком. Что воспринималось человеком как более отпимальное. В отличии от большенства других // стратегий в которых маршруты, формируюся скорее по лентами ведущими через все обезжаемые раоны. То есть водители // чаще имели пересечения маршутов. search_parameters.FirstSolutionStrategy = FirstSolutionStrategy.Types.Value.ParallelCheapestInsertion; search_parameters.TimeLimitMs = MaxTimeSeconds * 1000; // Отключаем внутреннего кеширования расчитанных значений. Опытным путем было проверено, что включение этого значения. // Значительно(на несколько секунд) увеличивает время закрытия модели и сокращает иногда не значительно время расчета оптимизаций. // И в принцепе становится целесообразно только на количествах заказов 300-400. При количестве заказов менее 200 // влючение отпечатков значений. Не уменьшало, а увеличивало общее время расчета. А при большом количестве заказов // время расчета уменьшалось не значительно. //search_parameters.FingerprintArcCostEvaluators = false; search_parameters.FingerprintArcCostEvaluators = true; //search_parameters.OptimizationStep = 100; var solver = routing.solver(); PerformanceHelper.AddTimePoint(logger, $"Настроили оптимизацию"); logger.Info("Закрываем модель..."); if ( WarningMessages.Any() && !interactiveService.Question( string.Join("\n", WarningMessages.Select(x => "⚠ " + x)), "При построении транспортной модели обнаружены следующие проблемы:\n{0}\nПродолжить?" ) ) { distanceCalculator.Canceled = true; distanceCalculator.Dispose(); return; } logger.Info("Рассчет расстояний между точками..."); routing.CloseModelWithParameters(search_parameters); #if DEBUG PrintMatrixCount(distanceCalculator.matrixcount); #endif //Записывем возможно не схраненый кеш в базу. distanceCalculator.FlushCache(); //Попытка хоть как то ослеживать что происходит в момент построения. Возможно не очень правильная. //Пришлось создавать 2 монитора. var lastSolution = solver.MakeLastSolutionCollector(); lastSolution.AddObjective(routing.CostVar()); routing.AddSearchMonitor(lastSolution); routing.AddSearchMonitor(new CallbackMonitor(solver, StatisticsTxtAction, lastSolution)); PerformanceHelper.AddTimePoint(logger, $"Закрыли модель"); logger.Info("Поиск решения..."); Assignment solution = routing.SolveWithParameters(search_parameters); PerformanceHelper.AddTimePoint(logger, $"Получили решение."); logger.Info("Готово. Заполняем."); #if DEBUG PrintMatrixCount(distanceCalculator.matrixcount); #endif Console.WriteLine("Status = {0}", routing.Status()); if (solution != null) { // Solution cost. Console.WriteLine("Cost = {0}", solution.ObjectiveValue()); time_dimension = routing.GetDimensionOrDie("Time"); //Читаем полученные маршруты. for (int route_number = 0; route_number < routing.Vehicles(); route_number++) { var route = new ProposedRoute(possibleRoutes[route_number]); long first_node = routing.Start(route_number); long second_node = solution.Value(routing.NextVar(first_node)); // Пропускаем первый узел, так как это наша база. route.RouteCost = routing.GetCost(first_node, second_node, route_number); while (!routing.IsEnd(second_node)) { var time_var = time_dimension.CumulVar(second_node); var rPoint = new ProposedRoutePoint( TimeSpan.FromSeconds(solution.Min(time_var)), TimeSpan.FromSeconds(solution.Max(time_var)), Nodes[second_node - 1].Order ); rPoint.DebugMaxMin = string.Format("\n({0},{1})[{3}-{4}]-{2} Cost:{5}", new DateTime().AddSeconds(solution.Min(time_var)).ToShortTimeString(), new DateTime().AddSeconds(solution.Max(time_var)).ToShortTimeString(), second_node, rPoint.Order.DeliverySchedule.From.ToString("hh\\:mm"), rPoint.Order.DeliverySchedule.To.ToString("hh\\:mm"), routing.GetCost(first_node, second_node, route_number) ); route.Orders.Add(rPoint); first_node = second_node; second_node = solution.Value(routing.NextVar(first_node)); route.RouteCost += routing.GetCost(first_node, second_node, route_number); } if (route.Orders.Count > 0) { ProposedRoutes.Add(route); logger.Debug("Маршрут {0}: {1}", route.Trip.Driver.ShortName, string.Join(" -> ", route.Orders.Select(x => x.DebugMaxMin)) ); } else { logger.Debug("Маршрут {0}: пустой", route.Trip.Driver.ShortName); } } } #if DEBUG logger.Debug("SGoToBase:{0}", string.Join(", ", CallbackDistanceDistrict.SGoToBase.Select(x => $"{x.Key.Driver.ShortName}={x.Value}"))); logger.Debug("SFromExistPenality:{0}", string.Join(", ", CallbackDistanceDistrict.SFromExistPenality.Select(x => $"{x.Key.Driver.ShortName}={x.Value}"))); logger.Debug("SUnlikeDistrictPenality:{0}", string.Join(", ", CallbackDistanceDistrict.SUnlikeDistrictPenality.Select(x => $"{x.Key.Driver.ShortName}={x.Value}"))); logger.Debug("SLargusPenality:{0}", string.Join(", ", CallbackDistanceDistrict.SLargusPenality.Select(x => $"{x.Key.Driver.ShortName}={x.Value}"))); #endif if (ProposedRoutes.Count > 0) { logger.Info($"Предложено {ProposedRoutes.Count} маршрутов."); } PerformanceHelper.Main.PrintAllPoints(logger); if (distanceCalculator.ErrorWays.Any()) { logger.Debug("Ошибок получения расстояний {0}", distanceCalculator.ErrorWays.Count); var uniqueFrom = distanceCalculator.ErrorWays.Select(x => x.FromHash).Distinct().ToList(); var uniqueTo = distanceCalculator.ErrorWays.Select(x => x.ToHash).Distinct().ToList(); logger.Debug("Уникальных точек: отправки = {0}, прибытия = {1}", uniqueFrom.Count, uniqueTo.Count); logger.Debug("Проблемные точки отправки:\n{0}", string.Join("; ", distanceCalculator.ErrorWays .GroupBy(x => x.FromHash) .Where(x => x.Count() > (uniqueTo.Count / 2)) .Select(x => CachedDistance.GetText(x.Key))) ); logger.Debug("Проблемные точки прибытия:\n{0}", string.Join("; ", distanceCalculator.ErrorWays .GroupBy(x => x.ToHash) .Where(x => x.Count() > (uniqueFrom.Count / 2)) .Select(x => CachedDistance.GetText(x.Key))) ); } }
private void ConfigureDlg() { Entity.ObservableFuelDocuments.ElementAdded += ObservableFuelDocuments_ElementAdded; Entity.ObservableFuelDocuments.ElementRemoved += ObservableFuelDocuments_ElementRemoved; referenceCar.SubjectType = typeof(Car); referenceCar.ItemsQuery = CarRepository.ActiveCarsQuery(); referenceCar.Binding.AddBinding(Entity, rl => rl.Car, widget => widget.Subject).InitializeFromSource(); referenceCar.Sensitive = editing; var filterDriver = new EmployeeFilter(UoW); filterDriver.SetAndRefilterAtOnce( x => x.RestrictCategory = EmployeeCategory.driver, x => x.ShowFired = false ); referenceDriver.RepresentationModel = new EmployeesVM(filterDriver); referenceDriver.Binding.AddBinding(Entity, rl => rl.Driver, widget => widget.Subject).InitializeFromSource(); referenceDriver.Sensitive = editing; previousForwarder = Entity.Forwarder; var filterForwarder = new EmployeeFilter(UoW); filterForwarder.SetAndRefilterAtOnce( x => x.RestrictCategory = EmployeeCategory.forwarder, x => x.ShowFired = false ); referenceForwarder.RepresentationModel = new EmployeesVM(filterForwarder); referenceForwarder.Binding.AddBinding(Entity, rl => rl.Forwarder, widget => widget.Subject).InitializeFromSource(); referenceForwarder.Sensitive = editing; referenceForwarder.Changed += ReferenceForwarder_Changed; var filterLogistican = new EmployeeFilter(UoW); filterLogistican.SetAndRefilterAtOnce(x => x.ShowFired = false); referenceLogistican.RepresentationModel = new EmployeesVM(filterLogistican); referenceLogistican.Binding.AddBinding(Entity, rl => rl.Logistican, widget => widget.Subject).InitializeFromSource(); referenceLogistican.Sensitive = editing; speccomboShift.ItemsList = DeliveryShiftRepository.ActiveShifts(UoW); speccomboShift.Binding.AddBinding(Entity, rl => rl.Shift, widget => widget.SelectedItem).InitializeFromSource(); speccomboShift.Sensitive = editing; yspinActualDistance.Binding.AddBinding(Entity, rl => rl.ActualDistance, widget => widget.ValueAsDecimal).InitializeFromSource(); yspinActualDistance.Sensitive = editing; datePickerDate.Binding.AddBinding(Entity, rl => rl.Date, widget => widget.Date).InitializeFromSource(); datePickerDate.Sensitive = editing; ycheckConfirmDifferences.Binding.AddBinding(Entity, e => e.DifferencesConfirmed, w => w.Active).InitializeFromSource(); ycheckConfirmDifferences.Sensitive = editing && Entity.Status == RouteListStatus.OnClosing; decimal unclosedAdvanceMoney = AccountableDebtsRepository.EmloyeeDebt(UoW, Entity.Driver); ylabelUnclosedAdvancesMoney.LabelProp = String.Format(unclosedAdvanceMoney > 0m ? "<span foreground='red'><b>Долг: {0}</b></span>" : "", unclosedAdvanceMoney); ytextClosingComment.Binding.AddBinding(Entity, e => e.ClosingComment, w => w.Buffer.Text).InitializeFromSource(); ytextClosingComment.Sensitive = editing; labelOrderEarly.Text = "Сдано ранее:" + GetCashOrder().ToString(); spinCashOrder.Value = 0; advanceSpinbutton.Value = 0; advanceSpinbutton.Visible = false; ycheckNormalWage.Binding.AddSource(Entity) .AddFuncBinding(x => x.Driver.WageCalcType == WageCalculationType.normal && x.Car.IsCompanyHavings, w => w.Visible) .AddBinding(x => x.NormalWage, w => w.Active) .InitializeFromSource(); ycheckNormalWage.Sensitive = editing && QSMain.User.Permissions["change_driver_wage"]; PerformanceHelper.AddTimePoint("Создан диалог"); //Предварительная загрузка объектов для ускорения. /* Vodovoz.Domain.Orders.Order orderAlias = null; * var clients = UoW.Session.QueryOver<RouteListItem>() * .Where(rli => rli.RouteList.Id == Entity.Id) * .JoinAlias(rli => rli.Order, () => orderAlias) * .Fetch(rli => rli.Order.Client).Eager * .List(); * //.Select(Projections. a => orderAlias.Client).Future(); * //.List<Counterparty>(); */ PerformanceHelper.AddTimePoint("Предварительная загрузка"); routeListAddressesView.UoW = UoW; routeListAddressesView.RouteList = Entity; foreach (RouteListItem item in routeListAddressesView.Items) { item.Order.ObservableOrderItems.ElementChanged += ObservableOrderItems_ElementChanged; item.Order.ObservableOrderItems.ElementAdded += ObservableOrderItems_ElementAdded; item.Order.ObservableOrderItems.ElementRemoved += ObservableOrderItems_ElementRemoved; item.Order.ObservableOrderEquipments.ElementChanged += ObservableOrderItems_ElementChanged; item.Order.ObservableOrderEquipments.ElementAdded += ObservableOrderItems_ElementAdded; item.Order.ObservableOrderEquipments.ElementRemoved += ObservableOrderItems_ElementRemoved; item.Order.ObservableOrderDepositItems.ElementChanged += ObservableOrderItems_ElementChanged; item.Order.ObservableOrderDepositItems.ElementAdded += ObservableOrderItems_ElementAdded; item.Order.ObservableOrderDepositItems.ElementRemoved += ObservableOrderItems_ElementRemoved; } routeListAddressesView.Items.ElementChanged += OnRouteListItemChanged; routeListAddressesView.OnClosingItemActivated += OnRouteListItemActivated; routeListAddressesView.IsEditing = editing; routeListAddressesView.ColumsVisibility = !ycheckHideCells.Active; ycheckHideCells.Sensitive = editing; PerformanceHelper.AddTimePoint("заполнили список адресов"); ReloadReturnedToWarehouse(); var returnableOrderItems = routeListAddressesView.Items .Where(address => address.IsDelivered()) .SelectMany(address => address.Order.OrderItems) .Where(orderItem => !orderItem.Nomenclature.IsSerial) .Where(orderItem => Nomenclature.GetCategoriesForShipment().Any(nom => nom == orderItem.Nomenclature.Category)); foreach (var item in returnableOrderItems) { if (allReturnsToWarehouse.All(r => r.NomenclatureId != item.Nomenclature.Id)) { allReturnsToWarehouse.Add(new RouteListRepository.ReturnsNode { Name = item.Nomenclature.Name, Trackable = item.Nomenclature.IsSerial, NomenclatureId = item.Nomenclature.Id, Amount = 0 }); } } PerformanceHelper.AddTimePoint("Получили возврат на склад"); //FIXME Убрать из этого места первоначальное заполнение. Сейчас оно вызывается при переводе статуса на сдачу. После того как не нормально не переведенных в закрытие маршрутников, тут заполение можно убрать. if (!Entity.ClosingFilled) { Entity.FirstFillClosing(); } PerformanceHelper.AddTimePoint("Закончено первоначальное заполнение"); hbox6.Remove(vboxHidenPanel); rightsidepanel1.Panel = vboxHidenPanel; rightsidepanel1.IsHided = true; expander1.Expanded = false; routelistdiscrepancyview.FindDiscrepancies(Entity.Addresses, allReturnsToWarehouse); routelistdiscrepancyview.FineChanged += Routelistdiscrepancyview_FineChanged; routelistdiscrepancyview.Sensitive = editing; PerformanceHelper.AddTimePoint("Заполнили расхождения"); buttonAddFuelDocument.Sensitive = Entity.Car?.FuelType?.Cost != null && Entity.Driver != null && editing; buttonDeleteFuelDocument.Sensitive = Entity.Car?.FuelType?.Cost != null && Entity.Driver != null && editing; ytreeviewFuelDocuments.ItemsDataSource = Entity.ObservableFuelDocuments; ytreeviewFuelDocuments.Reorderable = true; Entity.ObservableFuelDocuments.ListChanged += ObservableFuelDocuments_ListChanged; UpdateFuelDocumentsColumns(); enummenuRLActions.ItemsEnum = typeof(RouteListActions); enummenuRLActions.EnumItemClicked += EnummenuRLActions_EnumItemClicked; enummenuRLActions.Sensitive = editing; CheckWage(); LoadDataFromFine(); OnItemsUpdated(); PerformanceHelper.AddTimePoint("Загрузка штрафов"); GetFuelInfo(); UpdateFuelInfo(); PerformanceHelper.AddTimePoint("Загрузка бензина"); PerformanceHelper.Main.PrintAllPoints(logger); //Подписки на обновления OrmMain.GetObjectDescription <CarUnloadDocument>().ObjectUpdatedGeneric += OnCalUnloadUpdated; enumPrint.ItemsEnum = typeof(RouteListPrintDocuments); enumPrint.EnumItemClicked += (sender, e) => PrintSelectedDocument((RouteListPrintDocuments)e.ItemEnum); ylabelRecalculatedMileage.Binding.AddFuncBinding(Entity, e => e.RecalculatedDistance.HasValue ? $" {e.RecalculatedDistance} км" : "", w => w.LabelProp).InitializeFromSource(); checkSendToMileageCheck.Binding.AddBinding(Entity, x => x.MileageCheck, w => w.Active).InitializeFromSource(); Entity.PropertyChanged += Entity_PropertyChanged; //FIXME костыли, необходимо избавится от этого кода когда решим проблему с сессиями и flush nhibernate HasChanges = true; UoW.CanCheckIfDirty = false; }