Пример #1
0
        public virtual decimal GetEmployeeHoursFromCalendar(int?employeeID)
        {
            decimal    hours    = 40;
            var        select   = new PXSelect <EPEmployee, Where <EPEmployee.bAccountID, Equal <Required <EPEmployee.bAccountID> > > >(graph);
            EPEmployee employee = select.Select(employeeID);

            if (employee != null && employee.CalendarID != null)
            {
                CSCalendar calendar = PXSelect <CSCalendar, Where <CSCalendar.calendarID, Equal <Required <CSCalendar.calendarID> > > > .Select(graph, employee.CalendarID);

                if (calendar != null)
                {
                    hours  = 0;
                    hours += CalendarHelper.GetHoursWorkedOnDay(calendar, DayOfWeek.Monday);
                    hours += CalendarHelper.GetHoursWorkedOnDay(calendar, DayOfWeek.Tuesday);
                    hours += CalendarHelper.GetHoursWorkedOnDay(calendar, DayOfWeek.Wednesday);
                    hours += CalendarHelper.GetHoursWorkedOnDay(calendar, DayOfWeek.Thursday);
                    hours += CalendarHelper.GetHoursWorkedOnDay(calendar, DayOfWeek.Friday);
                    hours += CalendarHelper.GetHoursWorkedOnDay(calendar, DayOfWeek.Saturday);
                    hours += CalendarHelper.GetHoursWorkedOnDay(calendar, DayOfWeek.Sunday);
                }
            }

            return(hours);
        }
Пример #2
0
        private static void EnsureUnpaidTimeValid(CSCalendar calendar, DayOfWeek dayOfWeek)
        {
            CalendarDayInfo calendarDayInfo = new CalendarDayInfo(calendar, dayOfWeek);

            if (calendarDayInfo.UnpaidTime == null)
            {
                if (calendarDayInfo.WorkDay == true)
                {
                    SetUnpaidTime(calendar, dayOfWeek, 0);
                }
                return;
            }

            double maxPossibleBreak = calendarDayInfo.GetMaxPossibleUnpaidBreak();

            if (maxPossibleBreak > MinutesInDay)
            {
                maxPossibleBreak = MinutesInDay;
            }
            if (maxPossibleBreak < 0)
            {
                maxPossibleBreak = 0;
            }

            if (calendarDayInfo.UnpaidTime < 0 || calendarDayInfo.UnpaidTime > maxPossibleBreak)
            {
                SetUnpaidTime(calendar, dayOfWeek, (int)maxPossibleBreak);
            }
        }
Пример #3
0
 private static void SetUnpaidTime(CSCalendar calendar, DayOfWeek dayOfWeek, int?unpaidTime)
 {
     if (dayOfWeek == DayOfWeek.Sunday)
     {
         calendar.SunUnpaidTime = unpaidTime;
     }
     else if (dayOfWeek == DayOfWeek.Monday)
     {
         calendar.MonUnpaidTime = unpaidTime;
     }
     else if (dayOfWeek == DayOfWeek.Tuesday)
     {
         calendar.TueUnpaidTime = unpaidTime;
     }
     else if (dayOfWeek == DayOfWeek.Wednesday)
     {
         calendar.WedUnpaidTime = unpaidTime;
     }
     else if (dayOfWeek == DayOfWeek.Thursday)
     {
         calendar.ThuUnpaidTime = unpaidTime;
     }
     else if (dayOfWeek == DayOfWeek.Friday)
     {
         calendar.FriUnpaidTime = unpaidTime;
     }
     else if (dayOfWeek == DayOfWeek.Saturday)
     {
         calendar.SatUnpaidTime = unpaidTime;
     }
 }
Пример #4
0
            }                                                        //Unpaid time in minutes

            public CalendarDayInfo(CSCalendar c, DayOfWeek dayOfWeek)
            {
                if (dayOfWeek == DayOfWeek.Sunday)
                {
                    SetDayInfo(c.SunWorkDay, c.SunStartTime, c.SunEndTime, c.SunGoodsMoves, c.SunUnpaidTime);
                }
                else if (dayOfWeek == DayOfWeek.Monday)
                {
                    SetDayInfo(c.MonWorkDay, c.MonStartTime, c.MonEndTime, c.MonGoodsMoves, c.MonUnpaidTime);
                }
                else if (dayOfWeek == DayOfWeek.Tuesday)
                {
                    SetDayInfo(c.TueWorkDay, c.TueStartTime, c.TueEndTime, c.TueGoodsMoves, c.TueUnpaidTime);
                }
                else if (dayOfWeek == DayOfWeek.Wednesday)
                {
                    SetDayInfo(c.WedWorkDay, c.WedStartTime, c.WedEndTime, c.WedGoodsMoves, c.WedUnpaidTime);
                }
                else if (dayOfWeek == DayOfWeek.Thursday)
                {
                    SetDayInfo(c.ThuWorkDay, c.ThuStartTime, c.ThuEndTime, c.ThuGoodsMoves, c.ThuUnpaidTime);
                }
                else if (dayOfWeek == DayOfWeek.Friday)
                {
                    SetDayInfo(c.FriWorkDay, c.FriStartTime, c.FriEndTime, c.FriGoodsMoves, c.FriUnpaidTime);
                }
                else if (dayOfWeek == DayOfWeek.Saturday)
                {
                    SetDayInfo(c.SatWorkDay, c.SatStartTime, c.SatEndTime, c.SatGoodsMoves, c.SatUnpaidTime);
                }
            }
Пример #5
0
        public static bool IsWorkDay(PXGraph graph, string CalendarID, DateTime Date)
        {
            PXResult <CSCalendar, CSCalendarExceptions> result =
                (PXResult <CSCalendar, CSCalendarExceptions>)
                PXSelectJoin <
                    CSCalendar
                    , LeftJoin <CSCalendarExceptions
                                , On <CSCalendarExceptions.calendarID, Equal <CSCalendar.calendarID>, And <CSCalendarExceptions.date, Equal <Required <CSCalendarExceptions.date> > > >
                                >
                    , Where <CSCalendar.calendarID, Equal <Required <CSCalendar.calendarID> > >
                    > .SelectWindowed(graph, 0, 1, Date, CalendarID);

            if (result != null)
            {
                CSCalendar           calendar = result;
                CSCalendarExceptions exc      = result;
                if (exc.Date != null)
                {
                    return(exc.WorkDay == true);
                }
                else
                {
                    return(calendar.IsWorkDay(Date));
                }
            }
            return(true);
        }
Пример #6
0
        public static void EnsureUnpaidTimeValid(CSCalendar calendar)
        {
            if (calendar == null)
            {
                return;
            }

            EnsureUnpaidTimeValid(calendar, DayOfWeek.Sunday);
            EnsureUnpaidTimeValid(calendar, DayOfWeek.Monday);
            EnsureUnpaidTimeValid(calendar, DayOfWeek.Tuesday);
            EnsureUnpaidTimeValid(calendar, DayOfWeek.Wednesday);
            EnsureUnpaidTimeValid(calendar, DayOfWeek.Thursday);
            EnsureUnpaidTimeValid(calendar, DayOfWeek.Friday);
            EnsureUnpaidTimeValid(calendar, DayOfWeek.Saturday);
        }
Пример #7
0
        public static void CalculateStartEndTime(PXGraph graph, string calendarID, DateTime date, out DateTime startDate, out DateTime endDate)
        {
            CSCalendar calendar = PXSelect <CSCalendar> .Search <CSCalendar.calendarID>(graph, calendarID);

            if (calendar == null)
            {
                throw new InvalidOperationException(Messages.FailedToSelectCalenderId);
            }

            CSCalendarExceptions cse = PXSelect <CSCalendarExceptions> .
                                       Search <CSCalendarExceptions.calendarID, CSCalendarExceptions.date>(graph, calendarID, date);

            CalendarHelper helper = new CalendarHelper(calendar, cse, date);

            helper.CalculateStartEndTime(out startDate, out endDate);
        }
Пример #8
0
        public static double GetMaxPossibleUnpaidBreak(CSCalendar calendar, DayOfWeek dayOfWeek)
        {
            CalendarDayInfo calendarDayInfo = new CalendarDayInfo(calendar, dayOfWeek);

            return(calendarDayInfo.GetMaxPossibleUnpaidBreak());
        }
Пример #9
0
        public static decimal GetHoursWorkedOnDay(CSCalendar calendar, DayOfWeek dayOfWeek)
        {
            CalendarDayInfo calendarDayInfo = new CalendarDayInfo(calendar, dayOfWeek);

            return(calendarDayInfo.GetHoursWorkedOnDay());
        }
Пример #10
0
        public virtual TimeWindow GetWorkingTimeWindow(CSCalendar csCalendarRow, DateTime?date)
        {
            if (csCalendarRow.MonStartTime == null && csCalendarRow.MonStartTime.HasValue == false)
            {
                return(null);
            }

            TimeWindow workingTime = new TimeWindow();

            workingTime.startTimeSec = 0;
            workingTime.stopTimeSec  = 0;

            switch (date.Value.DayOfWeek)
            {
            case DayOfWeek.Sunday:
                if (csCalendarRow.SunStartTime != null && csCalendarRow.SunStartTime.HasValue == true)
                {
                    workingTime.startTimeSec = (int)csCalendarRow.SunStartTime.Value.TimeOfDay.TotalSeconds;
                }
                if (csCalendarRow.SunEndTime != null && csCalendarRow.SunEndTime.HasValue == true)
                {
                    workingTime.stopTimeSec = (int)csCalendarRow.SunEndTime.Value.TimeOfDay.TotalSeconds;
                }
                break;

            case DayOfWeek.Monday:
                if (csCalendarRow.MonStartTime != null && csCalendarRow.MonStartTime.HasValue == true)
                {
                    workingTime.startTimeSec = (int)csCalendarRow.MonStartTime.Value.TimeOfDay.TotalSeconds;
                }
                if (csCalendarRow.MonEndTime != null && csCalendarRow.MonEndTime.HasValue == true)
                {
                    workingTime.stopTimeSec = (int)csCalendarRow.MonEndTime.Value.TimeOfDay.TotalSeconds;
                }
                break;

            case DayOfWeek.Tuesday:
                if (csCalendarRow.TueStartTime != null && csCalendarRow.TueStartTime.HasValue == true)
                {
                    workingTime.startTimeSec = (int)csCalendarRow.TueStartTime.Value.TimeOfDay.TotalSeconds;
                }
                if (csCalendarRow.TueEndTime != null && csCalendarRow.TueEndTime.HasValue == true)
                {
                    workingTime.stopTimeSec = (int)csCalendarRow.TueEndTime.Value.TimeOfDay.TotalSeconds;
                }
                break;

            case DayOfWeek.Wednesday:
                if (csCalendarRow.WedStartTime != null && csCalendarRow.WedStartTime.HasValue == true)
                {
                    workingTime.startTimeSec = (int)csCalendarRow.WedStartTime.Value.TimeOfDay.TotalSeconds;
                }
                if (csCalendarRow.WedEndTime != null && csCalendarRow.WedEndTime.HasValue == true)
                {
                    workingTime.stopTimeSec = (int)csCalendarRow.MonEndTime.Value.TimeOfDay.TotalSeconds;
                }
                break;

            case DayOfWeek.Thursday:
                if (csCalendarRow.ThuStartTime != null && csCalendarRow.ThuStartTime.HasValue == true)
                {
                    workingTime.startTimeSec = (int)csCalendarRow.ThuStartTime.Value.TimeOfDay.TotalSeconds;
                }
                if (csCalendarRow.ThuEndTime != null && csCalendarRow.ThuEndTime.HasValue == true)
                {
                    workingTime.stopTimeSec = (int)csCalendarRow.ThuEndTime.Value.TimeOfDay.TotalSeconds;
                }
                break;

            case DayOfWeek.Friday:
                if (csCalendarRow.FriStartTime != null && csCalendarRow.FriStartTime.HasValue == true)
                {
                    workingTime.startTimeSec = (int)csCalendarRow.FriStartTime.Value.TimeOfDay.TotalSeconds;
                }
                if (csCalendarRow.FriEndTime != null && csCalendarRow.FriEndTime.HasValue == true)
                {
                    workingTime.stopTimeSec = (int)csCalendarRow.FriEndTime.Value.TimeOfDay.TotalSeconds;
                }
                break;

            case DayOfWeek.Saturday:
                if (csCalendarRow.SatStartTime != null && csCalendarRow.SatStartTime.HasValue == true)
                {
                    workingTime.startTimeSec = (int)csCalendarRow.SatStartTime.Value.TimeOfDay.TotalSeconds;
                }
                if (csCalendarRow.SatEndTime != null && csCalendarRow.SatEndTime.HasValue == true)
                {
                    workingTime.stopTimeSec = (int)csCalendarRow.SatEndTime.Value.TimeOfDay.TotalSeconds;
                }
                break;

            default:
                break;
            }

            return(workingTime.startTimeSec == workingTime.stopTimeSec ? null : workingTime);
        }
Пример #11
0
        public virtual void OptimizeRoutes(RoutesOptimizationProcess graph, FSAppointmentFilter filter, List <FSAppointmentFSServiceOrder> list, PXResultset <FSAppointmentStaffMember, CSCalendar> staffSelected)
        {
            RouteOptimizerClient client = new RouteOptimizerClient();

            SingleDayOptimizationInput requestBody = new SingleDayOptimizationInput();

            List <FSAppointment> processList = new List <FSAppointment>();

            FSSetup fsSetupRow = graph.SetupRecord.Current;

            requestBody.balanced = true;

            requestBody.vehicles  = new List <Vehicle>();
            requestBody.waypoints = new List <Waypoint>();

            string address = string.Empty;

            if (staffSelected != null && staffSelected.Count == 0)
            {
                throw new PXException(PXMessages.LocalizeFormatNoPrefix(TX.Error.SELECT_AT_LEAST_ONE_STAFF_MEMBER));
            }

            //Origin end Route location
            FSAddress fsAddressRow = PXSelectJoin <FSAddress,
                                                   InnerJoin <FSBranchLocation,
                                                              On <FSBranchLocation.branchLocationAddressID, Equal <FSAddress.addressID> > >,
                                                   Where <
                                                       FSBranchLocation.branchLocationID, Equal <Required <FSBranchLocation.branchLocationID> > > >
                                     .Select(graph, list[0].BranchLocationID);

            address = SharedFunctions.GetAddressForGeolocation(fsAddressRow.PostalCode,
                                                               fsAddressRow.AddressLine1,
                                                               fsAddressRow.AddressLine2,
                                                               fsAddressRow.City,
                                                               fsAddressRow.State,
                                                               fsAddressRow.CountryID);

            GLocation[] results = Geocoder.Geocode(address, fsSetupRow.MapApiKey);

            if (results.Length == 0)
            {
                throw new PXException(PXMessages.LocalizeFormatNoPrefix(TX.Error.MAPS_FAILED_REVERSE_ADRESS, TX.TableName.BRANCH_LOCATION));
            }

            CSCalendar csVendorCalendarRow = PXSelect <CSCalendar,
                                                       Where <CSCalendar.calendarID, Equal <Required <CSCalendar.calendarID> > > >
                                             .Select(graph, fsSetupRow.CalendarID);

            //Driver Logic
            foreach (PXResult <FSAppointmentStaffMember, CSCalendar> result in staffSelected)
            {
                FSAppointmentStaffMember staffRow = (FSAppointmentStaffMember)result;
                CSCalendar csCalendarRow          = (CSCalendar)result;

                Vehicle vehicleRow = new Vehicle()
                {
                    name   = staffRow.BAccountID.ToString(),
                    origin = new RouteLocation()
                    {
                        latitude = results[0].LatLng.Latitude, longitude = results[0].LatLng.Longitude
                    },
                    destination = new RouteLocation()
                    {
                        latitude = results[0].LatLng.Latitude, longitude = results[0].LatLng.Longitude
                    },
                    tags = new List <string>()
                    {
                        staffRow.BAccountID.ToString()
                    }
                };

                TimeWindow working    = graph.GetWorkingTimeWindow(staffRow.EmployeeSDEnabled == true ? csCalendarRow : csVendorCalendarRow, filter.StartDate);
                Break      lunchBreak = graph.GetBreakWindow(fsSetupRow);

                if (lunchBreak != null)
                {
                    vehicleRow.breaks = new List <Break>()
                    {
                        lunchBreak
                    };
                }

                if (working != null)
                {
                    vehicleRow.timeWindow = working;
                    requestBody.vehicles.Add(vehicleRow);
                }
            }

            if (requestBody.vehicles.Count == 0)
            {
                for (int i = 0; i < list.Count; i++)
                {
                    FSAppointment fsAppointmentRow = list[i];
                    UpdateAppointmentHeader(fsAppointmentRow, ID.Status_ROOptimization.NOT_ABLE);
                    graph.Appointments.Update(fsAppointmentRow);
                    PXProcessing <FSAppointmentFSServiceOrder> .SetError(i, PXMessages.LocalizeFormatNoPrefix(TX.Error.APPOINTMENT_COULD_NOT_BE_REACH_SERVICED_NO_DRIVER_AVAILABLE));
                }

                if (graph.Appointments.Cache.IsDirty == true)
                {
                    graph.Appointments.Cache.Persist(PXDBOperation.Update);
                }

                return;
            }

            //Existing Appointment Logic
            if (filter.Type == ID.Type_ROOptimization.UNASSIGNED_APP && staffSelected.Count() > 0)
            {
                List <object> args = new List <object>();

                BqlCommand fsAppointmentList = new Select2 <FSAppointment,
                                                            InnerJoin <FSServiceOrder,
                                                                       On <FSServiceOrder.sOID, Equal <FSAppointment.sOID> >,
                                                                       InnerJoin <FSAddress,
                                                                                  On <FSAddress.addressID, Equal <FSServiceOrder.serviceOrderAddressID> > > > >();

                if (filter.BranchID != null)
                {
                    fsAppointmentList = fsAppointmentList.WhereAnd(typeof(Where <FSServiceOrder.branchID, Equal <Required <FSServiceOrder.branchID> > >));
                    args.Add(filter.BranchID);
                }

                if (filter.BranchLocationID != null)
                {
                    fsAppointmentList = fsAppointmentList.WhereAnd(typeof(Where <FSServiceOrder.branchLocationID, Equal <Required <FSServiceOrder.branchLocationID> > >));
                    args.Add(filter.BranchLocationID);
                }

                if (filter.StartDate != null)
                {
                    fsAppointmentList = fsAppointmentList.WhereAnd(typeof(Where <FSAppointment.scheduledDateTimeBegin, GreaterEqual <Required <FSAppointment.scheduledDateTimeBegin> > >));
                    args.Add(filter.StartDateWithTime);
                }

                if (filter.EndDateWithTime != null)
                {
                    fsAppointmentList = fsAppointmentList.WhereAnd(typeof(Where <FSAppointment.scheduledDateTimeEnd, LessEqual <Required <FSAppointment.scheduledDateTimeEnd> > >));
                    args.Add(filter.EndDateWithTime);
                }

                if (staffSelected != null && staffSelected.Count() > 0)
                {
                    fsAppointmentList = fsAppointmentList.WhereAnd(typeof(Where <FSAppointment.primaryDriver, In <Required <FSAppointment.primaryDriver> > >));

                    int[] staffResult = StaffMemberFilter.Select()
                                        .RowCast <FSAppointmentStaffMember>()
                                        .Where(_ => _.Selected == true)
                                        .Select(_ => _.BAccountID)
                                        .Cast <int>()
                                        .ToArray();

                    args.Add(staffResult);
                }

                PXView appointmentView = new PXView(graph, true, fsAppointmentList);

                var fsAppointmentSet = appointmentView.SelectMulti(args.ToArray());

                foreach (PXResult <FSAppointment, FSServiceOrder, FSAddress> row in fsAppointmentSet)
                {
                    FSAppointment fsAppointmentRow = (FSAppointment)row;
                    fsAddressRow = (FSAddress)row;

                    address = SharedFunctions.GetAddressForGeolocation(
                        fsAddressRow.PostalCode,
                        fsAddressRow.AddressLine1,
                        fsAddressRow.AddressLine2,
                        fsAddressRow.City,
                        fsAddressRow.State,
                        fsAddressRow.CountryID);


                    Waypoint wp = GetWaypointFromAppointment(fsSetupRow, fsAppointmentRow, address);

                    if (wp != null)
                    {
                        requestBody.waypoints.Add(wp);

                        processList.Add(fsAppointmentRow);
                    }
                    else
                    {
                        UpdateAppointmentHeader(fsAppointmentRow, ID.Status_ROOptimization.ADDRESS_ERROR);
                        graph.Appointments.Update(fsAppointmentRow);
                    }
                }
            }

            //Appointment Logic
            for (int i = list.Count - 1; i >= 0; i--)
            {
                bool addressError = false;

                try
                {
                    address = SharedFunctions.GetAddressForGeolocation(
                        list[i].PostalCode,
                        list[i].AddressLine1,
                        list[i].AddressLine2,
                        list[i].City,
                        list[i].State,
                        list[i].CountryID);

                    Waypoint wp = GetWaypointFromAppointment(fsSetupRow, list[i], address);

                    if (wp != null)
                    {
                        requestBody.waypoints.Add(wp);
                        processList.Add(list[i]);
                    }
                    else
                    {
                        addressError = true;
                    }
                }
                catch
                {
                    addressError = true;
                }

                if (addressError == true)
                {
                    addressError = false;

                    FSAppointment fsAppointmentRow = list[i];
                    UpdateAppointmentHeader(fsAppointmentRow, ID.Status_ROOptimization.ADDRESS_ERROR);
                    graph.Appointments.Update(fsAppointmentRow);
                    list.RemoveAt(i);

                    PXProcessing <FSAppointmentFSServiceOrder> .SetError(i, PXMessages.LocalizeFormatNoPrefix(TX.Error.MAPS_FAILED_REVERSE_ADRESS, TX.TableName.APPOINTMENT));
                }
            }

            if (graph.Appointments.Cache.IsDirty == true)
            {
                graph.Appointments.Cache.Persist(PXDBOperation.Update);
            }

            try
            {
                SingleDayOptimizationOutput responseObject = client.getSingleDayOptimization(fsSetupRow.ROWWApiEndPoint, fsSetupRow.ROWWLicensekey, requestBody);

                AppointmentEntry graphAppointmentEntry = PXGraph.CreateInstance <AppointmentEntry>();

                for (int i = 0; i < responseObject.routes.Count; i++)
                {
                    int assignedstaffID;
                    int.TryParse(responseObject.routes[i].vehicle.name, out assignedstaffID);
                    bool changeFlag = false;

                    for (int j = 1; j < responseObject.routes[i].steps.Count - 1; j++)
                    {
                        RouteOtimizer.RouteStep currentAppointment = responseObject.routes[i].steps[j];
                        int appointmentID;
                        int.TryParse(currentAppointment.waypoint.name, out appointmentID);

                        FSAppointment fsAppointmentListRow = processList.Find(x => x.AppointmentID == appointmentID);

                        var newBegin = convertSecToTime(currentAppointment.serviceStartTimeSec, fsAppointmentListRow.ScheduledDateTimeBegin);
                        var newEnd   = newBegin.AddSeconds(currentAppointment.departureTimeSec - currentAppointment.arrivalTimeSec);

                        UpdateAppointmentHeader(fsAppointmentListRow, ID.Status_ROOptimization.OPTIMIZED, j, null, newBegin, newEnd);

                        if (fsAppointmentListRow.PrimaryDriver != null)
                        {
                            graph.Appointments.Update(fsAppointmentListRow);
                            changeFlag = true;
                        }
                        else
                        {
                            fsAppointmentListRow.PrimaryDriver = assignedstaffID;

                            FSAppointment fsAppointmentRow = graphAppointmentEntry.AppointmentRecords.Current = graphAppointmentEntry.AppointmentRecords.Search <FSAppointment.refNbr>(
                                fsAppointmentListRow.RefNbr, fsAppointmentListRow.SrvOrdType);

                            UpdateAppointmentHeader(fsAppointmentRow, ID.Status_ROOptimization.OPTIMIZED, j, assignedstaffID, newBegin, newEnd);

                            graphAppointmentEntry.AppointmentRecords.Update(fsAppointmentRow);

                            FSAppointmentEmployee fsAppointmentEmployeeRow_New = new FSAppointmentEmployee()
                            {
                                AppointmentID = fsAppointmentRow.AppointmentID,
                                EmployeeID    = assignedstaffID
                            };

                            fsAppointmentEmployeeRow_New = graphAppointmentEntry.AppointmentServiceEmployees.Insert(fsAppointmentEmployeeRow_New);

                            graphAppointmentEntry.Save.Press();
                        }
                    }

                    if (changeFlag == true)
                    {
                        graph.Appointments.Cache.Persist(PXDBOperation.Update);
                    }
                }

                foreach (OutputWaypoint wp in responseObject.unreachableWaypoints.Concat(responseObject.unreachedWaypoints).GroupBy(p => p.name).Select(g => g.First()).ToList())
                {
                    int appointmentID;
                    int.TryParse(wp.name, out appointmentID);

                    FSAppointment fsAppointmentRow = list.Find(x => x.AppointmentID == appointmentID);
                    if (fsAppointmentRow != null)
                    {
                        for (int i = 0; i < list.Count; i++)
                        {
                            if (fsAppointmentRow.AppointmentID == list[i].AppointmentID)
                            {
                                UpdateAppointmentHeader(fsAppointmentRow, ID.Status_ROOptimization.NOT_ABLE);
                                graph.Appointments.Update(fsAppointmentRow);

                                PXProcessing <FSAppointmentFSServiceOrder> .SetError(i, PXMessages.LocalizeFormatNoPrefix(TX.Error.APPOINTMENT_COULD_NOT_BE_REACH_SERVICED));
                            }
                        }
                    }
                }

                graph.Appointments.Cache.Persist(PXDBOperation.Update);
            }
            catch (PXException e)
            {
                for (int i = 0; i < list.Count; i++)
                {
                    FSAppointment fsAppointmentRow = list[i];
                    UpdateAppointmentHeader(fsAppointmentRow, ID.Status_ROOptimization.NOT_ABLE);
                    graph.Appointments.Update(fsAppointmentRow);

                    PXProcessing <FSAppointmentFSServiceOrder> .SetError(i, PXMessages.LocalizeFormatNoPrefix(e.Message));
                }

                graph.Appointments.Cache.Persist(PXDBOperation.Update);
            }
        }