public override bool Save() { bool hasDuplicate = CheckDuplicate(); bool userAnswer = true; if (hasDuplicate) { userAnswer = MessageDialogWorks.RunQuestionDialog( "Контрагент с данным ИНН уже существует. Сохранить?"); } if (userAnswer) { Entity.UoW = UoW; var valid = new QSValidator <Counterparty>(UoWGeneric.Root); if (valid.RunDlgIfNotValid((Gtk.Window) this.Toplevel)) { return(false); } logger.Info("Сохраняем контрагента..."); phonesView.SaveChanges(); emailsView.SaveChanges(); UoWGeneric.Save(); logger.Info("Ok."); return(true); } return(false); }
public static void CheckBanksUpdate(bool forceUpdate) { if (!forceUpdate) { DateTime lastModified = new DateTime(); if (MainSupport.BaseParameters.All.ContainsKey("last_banks_update")) { lastModified = DateTime.Parse(MainSupport.BaseParameters.All["last_banks_update"]); } int withoutUpdate = (int)DateTime.Now.Subtract(lastModified).TotalDays; if (withoutUpdate < UpdatePeriod) { return; } var runUpdate = MessageDialogWorks.RunQuestionDialog( lastModified == default(DateTime) ? "Справочник банков никогда не обновлялся. Обновить?" : NumberToTextRus.FormatCase(withoutUpdate, "Cправочник банков обновлялся\n{0} день назад. Обновить?", "Cправочник банков обновлялся\n{0} дня назад. Обновить?", "Cправочник банков обновлялся\n{0} дней назад. Обновить?")); if (!runUpdate) { return; } } BanksUpdateWindow updateWindow = new BanksUpdateWindow(); updateWindow.Show(); }
protected void OnButtonCreateBorderClicked(object sender, EventArgs e) { if (!creatingNewBorder) { creatingNewBorder = true; newBorderVertice = new List <PointLatLng>(); } else { if (MessageDialogWorks.RunQuestionDialog("Завершить задание границ района?")) { if (MessageDialogWorks.RunQuestionDialog("Сохранить новые границы района?")) { var closingPoint = newBorderVertice[0]; newBorderVertice.Add(closingPoint); currentBorderVertice = newBorderVertice; currentDistrict.DistrictBorder = gf.CreatePolygon(GetCoordinatesFromPoints()); } creatingNewBorder = false; ShowBorders(); ShowBorderVertice(currentBorderVertice); } } ControlsAccessibility(); }
protected void OnButtonCancelClicked(object sender, EventArgs e) { if (MessageDialogWorks.RunQuestionDialog("Выйти без сохранения?")) { this.Destroy(); } }
void EntryBuilding_Changed(object sender, EventArgs e) { if (entryBuilding.OsmCompletion.HasValue) { Entity.FoundOnOsm = entryBuilding.OsmCompletion.Value; decimal?latitude, longitude; entryBuilding.GetCoordinates(out longitude, out latitude); if (!Entity.ManualCoordinates || (Entity.ManualCoordinates && MessageDialogWorks.RunQuestionDialog("Координаты были установлены вручную, заменить их на коордитаты адреса?"))) { Entity.Latitude = latitude; Entity.Longitude = longitude; Entity.ManualCoordinates = false; } } if (entryBuilding.OsmHouse != null && !String.IsNullOrWhiteSpace(entryBuilding.OsmHouse.Name)) { labelHouseName.Visible = true; labelHouseName.LabelProp = entryBuilding.OsmHouse.Name; } else { labelHouseName.Visible = false; } }
void RunAgreementCreateDialog(CounterpartyContract contract) { ITdiTab dlg; string paymentTypeString = ""; switch (UoWGeneric.Root.Payment) { case PaymentType.cash: paymentTypeString = "наличной"; break; case PaymentType.cashless: paymentTypeString = "безналичной"; break; case PaymentType.barter: paymentTypeString = "бартерной"; break; } string question = "Отсутствует доп. соглашение сервиса с клиентом в договоре для " + paymentTypeString + " формы оплаты. Создать?"; if (MessageDialogWorks.RunQuestionDialog(question)) { dlg = new RepairAgreementDlg(contract); (dlg as IAgreementSaved).AgreementSaved += (sender, e) => { UoWGeneric.Root.InitialOrder?.CreateOrderAgreementDocument(e.Agreement); }; TabParent.AddSlaveTab(this, dlg); } }
void SelectTemplate_ObjectSelected(object sender, OrmReferenceObjectSectedEventArgs e) { if (DocumentUoW.Root.Items.Count > 0) { if (MessageDialogWorks.RunQuestionDialog("Текущий список будет очищен. Продолжить?")) { DocumentUoW.Root.ObservableItems.Clear(); } else { return; } } var template = DocumentUoW.GetById <RegradingOfGoodsTemplate>((e.Subject as RegradingOfGoodsTemplate).Id); foreach (var item in template.Items) { DocumentUoW.Root.AddItem(new RegradingOfGoodsDocumentItem() { NomenclatureNew = item.NomenclatureNew, NomenclatureOld = item.NomenclatureOld }); } LoadStock(); }
protected void OnButtonFillWarehouseItemsClicked(object sender, EventArgs e) { if (DocumentUoW.Root.Items.Count > 0) { if (!MessageDialogWorks.RunQuestionDialog("Список будет очищен. Продолжить?")) { return; } } DocumentUoW.Root.FillFromRouteList(DocumentUoW, false); if (DocumentUoW.Root.Items.Any(i => i.Nomenclature.Warehouse == null)) { string str = ""; foreach (var nomenclarure in DocumentUoW.Root.Items.Where(i => i.Nomenclature.Warehouse == null)) { str = string.Join("\n", nomenclarure.Nomenclature.Name); } MessageDialogWorks.RunErrorWithSecondaryTextDialog("В МЛ есть номенклатура не привязанная к складу.", str); } DocumentUoW.Root.FillFromRouteList(DocumentUoW, true); DocumentUoW.Root.UpdateAlreadyLoaded(DocumentUoW); if (DocumentUoW.Root.Warehouse != null) { DocumentUoW.Root.UpdateStockAmount(DocumentUoW); UpdateAmounts(); } }
public static void CheckBanksUpdate(bool forceUpdate) { if (!forceUpdate) { dynamic parameters = new ParametersService(QSMain.ConnectionDB); DateTime lastModified = parameters.last_banks_update ?? default(DateTime); int withoutUpdate = (int)DateTime.Now.Subtract(lastModified).TotalDays; if (withoutUpdate < UpdatePeriod) { return; } var runUpdate = MessageDialogWorks.RunQuestionDialog( lastModified == default(DateTime) ? "Справочник банков никогда не обновлялся. Обновить?" : NumberToTextRus.FormatCase(withoutUpdate, "Cправочник банков обновлялся\n{0} день назад. Обновить?", "Cправочник банков обновлялся\n{0} дня назад. Обновить?", "Cправочник банков обновлялся\n{0} дней назад. Обновить?")); if (!runUpdate) { return; } } BanksUpdateWindow updateWindow = new BanksUpdateWindow(); updateWindow.Show(); }
protected void OnButtonAddClicked(object sender, EventArgs e) { if (!String.IsNullOrWhiteSpace(textComment.Buffer.Text) || MessageDialogWorks.RunQuestionDialog("Вы не заполнили комментарий. Продолжить?")) { ServiceClaimStatus newStatus = (ServiceClaimStatus)(enumStatusEditable.SelectedItem ?? UoWGeneric.Root.Status); UoWGeneric.Root.AddHistoryRecord(newStatus, textComment.Buffer.Text, _employeeRepository); } }
protected void OnButtonRefreshClicked(object sender, EventArgs e) { bool hasChanges = items.Count(item => item.HasChanged) > 0; if (!hasChanges || MessageDialogWorks.RunQuestionDialog("Вы действительно хотите обновить список заказов? Внесенные изменения будут утрачены.")) { UoWGeneric.Session.Refresh(Entity); UpdateNodes(); } }
protected void OnBtnEditDistrictClicked(object sender, EventArgs e) { if (currentDistrict.Id == 0 && MessageDialogWorks.RunQuestionDialog("Для продолжения необходимо сохранить район, сохранить и продолжить?")) { uow.Save(currentDistrict); uow.Commit(); } TabParent.OpenTab( DialogHelper.GenerateDialogHashName <ScheduleRestrictedDistrict>(currentDistrict.Id), () => new ScheduleRestrictedDistrictDlg(currentDistrict) ); }
protected void OnYentryrefWarehouseBeforeChangeByUser(object sender, EntryReferenceBeforeChangeEventArgs e) { if (Entity.Warehouse != null && Entity.Items.Count > 0) { if (MessageDialogWorks.RunQuestionDialog("При изменении склада табличная часть документа будет очищена. Продолжить?")) { Entity.ObservableItems.Clear(); } else { e.CanChange = false; } } }
void Templatewidget_BeforeOpen(object sender, EventArgs e) { if (UoW.HasChanges) { if (MessageDialogWorks.RunQuestionDialog("Необходимо сохранить документ перед открытием печатной формы, сохранить?")) { UoWGeneric.Save(); RefreshParserRootObject(); } else { templatewidget.CanOpenDocument = false; } } }
public override bool Save() { if (Entity.Status == RouteListStatus.EnRoute && items.All(x => x.Status != RouteListItemStatus.EnRoute)) { if (MessageDialogWorks.RunQuestionDialog("В маршрутном листе не осталось адресов со статусом в 'В пути'. Завершить маршрут?")) { Entity.CompleteRoute(); } } UoWGeneric.Save(); var changedList = items.Where(item => item.ChangedDeliverySchedule || item.HasChanged).ToList(); if (changedList.Count == 0) { return(true); } var currentEmployee = EmployeeRepository.GetEmployeeForCurrentUser(UoWGeneric); if (currentEmployee == null) { MessageDialogWorks.RunInfoDialog("Ваш пользователь не привязан к сотруднику, уведомления об изменениях в маршрутном листе не будут отправлены водителю."); return(true); } foreach (var item in changedList) { if (item.HasChanged) { getChatService() .SendOrderStatusNotificationToDriver( currentEmployee.Id, item.RouteListItem.Id ); } if (item.ChangedDeliverySchedule) { getChatService().SendDeliveryScheduleNotificationToDriver( currentEmployee.Id, item.RouteListItem.Id ); } } return(true); }
void dlg_EntitySaved(object sender, EntitySavedEventArgs e) { if (e.Entity != null && mode == OrmReferenceMode.Select) { if (!MessageDialogWorks.RunQuestionDialog("Выбрать созданный объект и вернуться к предыдущему диалогу?")) { return; } RepresentationSelectResult res = new RepresentationSelectResult(DomainHelper.GetId(e.Entity), e.Entity); RepresentationSelectResult[] selected = { res }; logger.Debug("Выбрано {0} id:({1})", objectType, String.Join(",", selected.Select(x => x.EntityId))); ObjectSelected(this, new ReferenceRepresentationSelectedEventArgs(selected)); Application.Invoke(delegate { OnCloseTab(CloseSource.Self); }); } }
public override bool Save() { var valid = new QSValidator <Employee> (UoWGeneric.Root); if (valid.RunDlgIfNotValid((Gtk.Window) this.Toplevel)) { return(false); } if (Entity.User != null) { var associatedEmployees = Repository.Company.EmployeeRepository.GetEmployeesForUser(UoW, Entity.User.Id); if (associatedEmployees.Any(e => e.Id != Entity.Id)) { string mes = String.Format("Пользователь {0} уже связан с сотрудником {1}, при привязке этого сотрудника к пользователю, старая связь будет удалена. Продолжить?", Entity.User.Name, String.Join(", ", associatedEmployees.Select(e => e.ShortName)) ); if (MessageDialogWorks.RunQuestionDialog(mes)) { foreach (var ae in associatedEmployees.Where(e => e.Id != Entity.Id)) { ae.User = null; UoWGeneric.Save(ae); } } else { return(false); } } } phonesView.SaveChanges(); logger.Info("Сохраняем сотрудника..."); try { UoWGeneric.Save(); } catch (Exception ex) { logger.Error(ex, "Не удалось записать сотрудника."); QSProjectsLib.QSMain.ErrorMessage((Gtk.Window) this.Toplevel, ex); return(false); } logger.Info("Ok"); return(true); }
public override bool Save() { if (Entity.Status > RouteListStatus.OnClosing) { if (Entity.FuelOperationHaveDiscrepancy()) { if (!MessageDialogWorks.RunQuestionDialog("Был изменен водитель или автомобиль, при сохранении МЛ баланс по топливу изменится с учетом этих изменений. Продолжить сохранение?")) { return(false); } } Entity.UpdateFuelOperation(); } UoWGeneric.Save(); return(true); }
void SelectDrivers_ObjectSelected(object sender, OrmReferenceObjectSectedEventArgs e) { var addDrivers = e.GetEntities <Employee>().ToList(); logger.Info("Получаем авто для водителей..."); MainClass.progressBarWin.ProgressStart(2); var onlyNew = addDrivers.Where(x => driversAtDay.All(y => y.Employee.Id != x.Id)).ToList(); var allCars = CarRepository.GetCarsbyDrivers(uow, onlyNew.Select(x => x.Id).ToArray()); MainClass.progressBarWin.ProgressAdd(); foreach (var driver in addDrivers) { if (driversAtDay.Any(x => x.Employee.Id == driver.Id)) { logger.Warn("Водитель {0} уже добавлен. Пропускаем...", driver.ShortName); continue; } var atwork = new AtWorkDriver(driver, DialogAtDate, allCars.FirstOrDefault(x => x.Driver.Id == driver.Id) ); if (driver.DefaultForwarder != null) { var forwarder = ForwardersAtDay.FirstOrDefault(x => x.Employee.Id == driver.DefaultForwarder.Id); if (forwarder == null) { if (MessageDialogWorks.RunQuestionDialog($"Водитель {driver.ShortName} обычно ездить с экспедитором {driver.DefaultForwarder.ShortName}. Он отсутствует в списке экспедиторов. Добавить его в список?")) { forwarder = new AtWorkForwarder(driver.DefaultForwarder, DialogAtDate); observableForwardersAtDay.Add(forwarder); } } if (forwarder != null && DriversAtDay.All(x => x.WithForwarder != forwarder)) { atwork.WithForwarder = forwarder; } } driversAtDay.Add(atwork); } MainClass.progressBarWin.ProgressAdd(); DriversAtDay = driversAtDay.OrderBy(x => x.Employee.ShortName).ToList(); logger.Info("Ок"); MainClass.progressBarWin.ProgressClose(); }
void RunContractCreateDialog() { ITdiTab dlg; string paymentTypeString = ""; switch (UoWGeneric.Root.Payment) { case PaymentType.cash: paymentTypeString = "наличной"; break; case PaymentType.cashless: paymentTypeString = "безналичной"; break; case PaymentType.barter: paymentTypeString = "бартерной"; break; } string question = "Отсутствует договор с клиентом для " + paymentTypeString + " формы оплаты. Создать?"; if (MessageDialogWorks.RunQuestionDialog(question)) { Organization organization = null; throw new NotImplementedException(); dlg = new CounterpartyContractDlg(UoWGeneric.Root.Counterparty, organization); (dlg as IContractSaved).ContractSaved += (sender, e) => { if (UoWGeneric.Root.InitialOrder != null) { UoWGeneric.Root.InitialOrder.ObservableOrderDocuments.Add(new OrderContract { Order = UoWGeneric.Root.InitialOrder, AttachedToOrder = UoWGeneric.Root.InitialOrder, Contract = e.Contract }); } }; TabParent.AddSlaveTab(this, dlg); } }
void MapWidget_ButtonReleaseEvent(object o, Gtk.ButtonReleaseEventArgs args) { if (args.Event.Button == 1) { addressMoving = false; var newPoint = MapWidget.FromLocalToLatLng((int)args.Event.X, (int)args.Event.Y); if (!Entity.ManualCoordinates && Entity.FoundOnOsm) { if (!MessageDialogWorks.RunQuestionDialog("Координаты сейчас установлены по адресу. Вы уверены что хотите установить новые координаты?")) { UpdateAddressOnMap(); return; } } Entity.ManualCoordinates = true; Entity.Latitude = (decimal)newPoint.Lat; Entity.Longitude = (decimal)newPoint.Lng; } }
public override bool Save() { if (!Entity.СoordinatesExist) { if (!MessageDialogWorks.RunQuestionDialog("Адрес не найден на карте, вы точно хотите его сохранить?")) { return(false); } } var valid = new QSValidator <DeliveryPoint> (UoWGeneric.Root); if (valid.RunDlgIfNotValid((Gtk.Window) this.Toplevel)) { return(false); } UoWGeneric.Save(); return(true); }
public override bool Save() { var valid = new QSValidation.QSValidator <CarLoadDocument> (UoWGeneric.Root); if (valid.RunDlgIfNotValid((Gtk.Window) this.Toplevel)) { return(false); } Entity.LastEditor = Repository.EmployeeRepository.GetEmployeeForCurrentUser(UoW); Entity.LastEditedTime = DateTime.Now; if (Entity.LastEditor == null) { MessageDialogWorks.RunErrorDialog("Ваш пользователь не привязан к действующему сотруднику, вы не можете изменять складские документы, так как некого указывать в качестве кладовщика."); return(false); } if (Entity.Items.Any(x => x.Amount == 0)) { if (MessageDialogWorks.RunQuestionDialog("В списке есть нулевые позиции. Убрать нулевые позиции перед сохранением?")) { Entity.ClearItemsFromZero(); } } Entity.UpdateOperations(UoW); logger.Info("Сохраняем погрузочный талон..."); UoWGeneric.Save(); logger.Info("Меняем статус маршрутного листа..."); if (Entity.RouteList.ShipIfCan(UoW)) { MessageDialogWorks.RunInfoDialog("Маршрутный лист отгружен полностью."); } UoW.Save(Entity.RouteList); UoW.Commit(); logger.Info("Ok."); return(true); }
protected void OnButtonFillAllItemsClicked(object sender, EventArgs e) { if (DocumentUoW.Root.Items.Count > 0) { if (!MessageDialogWorks.RunQuestionDialog("Список будет очищен. Продолжить?")) { return; } } DocumentUoW.Root.FillFromRouteList(DocumentUoW, false); var items = DocumentUoW.Root.Items; string errorNomenclatures = string.Empty; foreach (var item in items) { if (item.Nomenclature.Unit == null) { errorNomenclatures += string.Format("{0} код {1}{2}", item.Nomenclature.Name, item.Nomenclature.Id, Environment.NewLine); } } if (!string.IsNullOrEmpty(errorNomenclatures)) { errorNomenclatures = "Не указаны единицы измерения для следующих номенклатур:" + Environment.NewLine + errorNomenclatures; MessageDialogWorks.RunErrorDialog(errorNomenclatures); DocumentUoW.Root.Items.Clear(); return; } DocumentUoW.Root.UpdateAlreadyLoaded(DocumentUoW); if (DocumentUoW.Root.Warehouse != null) { DocumentUoW.Root.UpdateStockAmount(DocumentUoW); UpdateAmounts(); } }
protected void OnButtonChangeToEmployeeClicked(object sender, EventArgs e) { if (UoW.HasChanges || Entity.Id == 0) { if (!MessageDialogWorks.RunQuestionDialog("Для продолжения необходимо сохранить изменения, сохранить и продолжить?")) { return; } if (Save()) { OnEntitySaved(true); } else { return; } } var employeeUow = UnitOfWorkFactory.CreateWithNewRoot <Employee>(); Personnel.ChangeTraineeToEmployee(employeeUow, Entity); TabParent.OpenTab(OrmMain.GenerateDialogHashName <Employee>(Entity.Id), () => new EmployeeDlg(employeeUow)); this.OnCloseTab(false); }
/// <summary> /// Метод создаем маршруты на день основываясь на данных всесенных в поля <c>Routes</c>, <c>Orders</c>, /// <c>Drivers</c> и <c>Forwarders</c>. /// </summary> public void CreateRoutes() { WarningMessages.Clear(); ProposedRoutes.Clear(); //Очищаем сразу, так как можем выйти из метода ранее. logger.Info("Подготавливаем заказы..."); PerformanceHelper.StartMeasurement($"Строим оптимальные маршруты"); MainClass.progressBarWin.ProgressStart(4); /// Создаем список поездок всех водителей. Тут перебираем всех водителей с машинами /// и создаем поездки для них, в зависимости от выбранного режима работы. var trips = Drivers.Where(x => x.Car != null) .OrderBy(x => x.PriorityAtDay) .SelectMany(x => x.DaySchedule != null ? x.DaySchedule.Shifts.Select(s => new PossibleTrip(x, s)) : new[] { new PossibleTrip(x, 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.Any(x => x.Id == address.Order.Id)) { Orders.Add(address.Order); } } } var possibleRoutes = trips.ToArray(); if (possibleRoutes.Length == 0) { AddWarning("Для построения маршрутов, нет водителей."); return; } TestCars(possibleRoutes); var areas = UoW.GetAll <LogisticsArea>().ToList(); List <LogisticsArea> unusedDistricts = new List <LogisticsArea>(); 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 aria = areas.Find(x => x.Geometry.Contains(point)); if (aria != null) { var oldRoute = Routes.FirstOrDefault(r => r.Addresses.Any(a => a.Order.Id == order.Id)); if (oldRoute != null) { calculatedOrders.Add(new CalculatedOrder(order, aria, false, oldRoute)); } else if (possibleRoutes.SelectMany(x => x.Districts).Any(x => x.District.Id == aria.Id)) { calculatedOrders.Add(new CalculatedOrder(order, aria)); } else if (!unusedDistricts.Contains(aria)) { unusedDistricts.Add(aria); } } } Nodes = calculatedOrders.ToArray(); if (unusedDistricts.Count > 0) { AddWarning("Районы без водителей: {0}", String.Join(", ", unusedDistricts.Select(x => x.Name))); } /// Создаем калькулятор расчета расстояний. Он сразу запрашивает уже имеющиеся расстояния из кеша /// и в фоновом режиме начинает считать недостающую матрицу. distanceCalculator = new ExtDistanceCalculator(DistanceProvider.Osrm, Nodes.Select(x => x.Order.DeliveryPoint).ToArray(), DebugBuffer); MainClass.progressBarWin.ProgressAdd(); 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> - Максимальное время ожидания водителя. То есть водитель закончил разгрузку следующий /// адрес в маршруте у него не должен быть позже чем на 3 часа ожидания. int horizon = 24 * 3600; int maxWaitTime = 3 * 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.Car.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); /// Добавляем фиксированный штраф за приоритет водителя. routing.SetFixedCostOfVehicle((possibleRoutes[ix].DriverPriority - 1) * DriverPriorityPenalty, 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].Car.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.OptimizationStep = 100; var solver = routing.solver(); PerformanceHelper.AddTimePoint(logger, $"Настроили оптимизацию"); MainClass.progressBarWin.ProgressAdd(); logger.Info("Закрываем модель..."); if (WarningMessages.Count > 0 && !MessageDialogWorks.RunQuestionDialog("При построении транспортной модели обнаружены следующие проблемы:\n{0}\nПродолжить?", String.Join("\n", WarningMessages.Select(x => "⚠ " + x)))) { 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, OrdersProgress, DebugBuffer, lastSolution)); PerformanceHelper.AddTimePoint(logger, $"Закрыли модель"); logger.Info("Поиск решения..."); MainClass.progressBarWin.ProgressAdd(); Assignment solution = routing.SolveWithParameters(search_parameters); PerformanceHelper.AddTimePoint(logger, $"Получили решение."); logger.Info("Готово. Заполняем."); MainClass.progressBarWin.ProgressAdd(); #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 MainClass.progressBarWin.ProgressAdd(); if (ProposedRoutes.Count > 0) { logger.Info($"Предложено {ProposedRoutes.Count} маршрутов."); } 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))) ); } }
protected void OnButtonFindGapClicked(object sender, EventArgs e) { trackOnGapOverlay.Clear(); tracksDistance.RemoveAll(x => x.Id == "MissingTrack"); string message = "Найдены разрывы в треке:"; double replacedDistance = 0; TrackPoint lastPoint = null; foreach (var point in track.TrackPoints) { if (lastPoint == null) { lastPoint = point; continue; } var distance = GMapProviders.EmptyProvider.Projection.GetDistance( new PointLatLng(lastPoint.Latitude, lastPoint.Longitude), new PointLatLng(point.Latitude, point.Longitude)); if (distance > 0.5) { logger.Info("Найден разрыв в треке расстоянием в {0}", distance); message += String.Format("\n* разрыв c {1:t} по {2:t} — {0:N1} км.", distance, lastPoint.TimeStamp, point.TimeStamp ); replacedDistance += distance; var addressesByCompletion = routeList.Addresses .Where(x => x.Status == RouteListItemStatus.Completed) .OrderBy(x => x.StatusLastUpdate) .ToList(); RouteListItem addressBeforeGap = addressesByCompletion.LastOrDefault(x => x.StatusLastUpdate < lastPoint.TimeStamp.AddMinutes(2)); RouteListItem addressAfterGap = addressesByCompletion.FirstOrDefault(x => x.StatusLastUpdate > point.TimeStamp.AddMinutes(-2)); var beforeIndex = addressBeforeGap == null ? -1 : addressesByCompletion.IndexOf(addressBeforeGap); var afterIndex = addressAfterGap == null ? addressesByCompletion.Count : addressesByCompletion.IndexOf(addressAfterGap); var routePoints = new List <PointOnEarth>(); routePoints.Add(new PointOnEarth(lastPoint.Latitude, lastPoint.Longitude)); if (afterIndex - beforeIndex > 1) { var throughAddress = addressesByCompletion.GetRange(beforeIndex + 1, afterIndex - beforeIndex - 1); logger.Info("В разрыве найдены выполенные адреса порядковый(е) номер(а) {0}", String.Join(", ", throughAddress.Select(x => x.IndexInRoute))); routePoints.AddRange( throughAddress.Where(x => x.Order?.DeliveryPoint?.Latitude != null && x.Order?.DeliveryPoint?.Longitude != null) .Select(x => new PointOnEarth(x.Order.DeliveryPoint.Latitude.Value, x.Order.DeliveryPoint.Longitude.Value))); message += $" c выполненными адресами({beforeIndex + 2}-{afterIndex}) п/п в МЛ " + String.Join(", ", throughAddress.Select(x => x.IndexInRoute)); } routePoints.Add(new PointOnEarth(point.Latitude, point.Longitude)); var missedTrack = SputnikMain.GetRoute(routePoints, false, true); if (missedTrack == null) { MessageDialogWorks.RunErrorDialog("Не удалось получить ответ от сервиса \"Спутник\""); return; } if (missedTrack.Status != 0) { MessageDialogWorks.RunErrorDialog("Cервис \"Спутник\" сообщил об ошибке {0}: {1}", missedTrack.Status, missedTrack.StatusMessageRus); return; } var decodedPoints = Polyline.DecodePolyline(missedTrack.RouteGeometry); var points = decodedPoints.Select(p => new PointLatLng(p.Latitude * 0.1, p.Longitude * 0.1)).ToList(); var route = new GMapRoute(points, "MissedRoute"); route.Stroke = new System.Drawing.Pen(System.Drawing.Color.DarkMagenta); route.Stroke.Width = 4; route.Stroke.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot; trackOnGapOverlay.Routes.Add(route); } lastPoint = point; } if (trackOnGapOverlay.Routes.Count > 0) { var distanceLayout = MakeDistanceLayout(trackOnGapOverlay.Routes.ToArray()); distanceLayout.Id = "MissingTrack"; tracksDistance.Add(distanceLayout); var oldDistance = track.DistanceEdited ? trackRoute.Distance : track.Distance; var missedRouteDistance = trackOnGapOverlay.Routes.Sum(x => x.Distance); var newDistance = oldDistance - replacedDistance + missedRouteDistance; var diffDistance = newDistance - oldDistance; message += $"\n Старая длинна трека:{oldDistance:N1} км." + $"\n Новая длинна трека: {newDistance:N1} км.(+{diffDistance:N1})" + "\n Сохранить изменения длинны трека?"; if (MessageDialogWorks.RunQuestionDialog(message)) { track.Distance = newDistance; track.DistanceEdited = true; UoW.Save(track); UoW.Commit(); UpdateDistanceLabel(); } } else { MessageDialogWorks.RunInfoDialog("Разрывов в треке не найдено."); } }