Ver código fonte

Started update on JobPlanner

Kenric Nugteren 1 mês atrás
pai
commit
1903797185

+ 2 - 0
prs.classes/Entities/Job/JobStageLink.cs

@@ -11,6 +11,8 @@ namespace Comal.Classes
         [TextBoxEditor(Editable = Editable.Hidden)]
         public string Name { get; set; }
 
+        public JobLink Job { get; set; }
+
         [DateEditor(Editable = Editable.Hidden)]
         public DateTime StartDate { get; set; }
 

+ 7 - 1
prs.desktop/Panels/JobPlanner/JobResourcePlanner.xaml

@@ -207,7 +207,13 @@
                                         AppointmentEditorOpening="Schedule_AppointmentEditorOpening"
                                         SchedulerContextMenuOpening="Schedule_OnSchedulerContextMenuOpening"
                                         AppointmentDropping="Schedule_OnAppointmentDropping"
-                                        QueryAppointments="Schedule_OnQueryAppointments">
+                                        QueryAppointments="Schedule_OnQueryAppointments"
+                                        EnableToolTip="True">
+                    <Syncfusion:SfScheduler.ToolTipTemplate>
+                        <DataTemplate>
+                            <TextBlock Text="{Binding ToolTipContent}"/>
+                        </DataTemplate>
+                    </Syncfusion:SfScheduler.ToolTipTemplate>
                     <Syncfusion:SfScheduler.CellContextMenu>
                         <ContextMenu x:Name="CellContextMenu" />
                     </Syncfusion:SfScheduler.CellContextMenu>

+ 182 - 62
prs.desktop/Panels/JobPlanner/JobResourcePlanner.xaml.cs

@@ -29,6 +29,7 @@ using Syncfusion.Data.Extensions;
 using NPOI.OpenXmlFormats.Spreadsheet;
 using Syncfusion.UI.Xaml.Scheduler;
 using Color = System.Drawing.Color;
+using System.Diagnostics.CodeAnalysis;
 
 namespace PRSDesktop;
 
@@ -87,7 +88,7 @@ public partial class JobResourcePlanner : UserControl
 
     private EmployeeResourceModel[] _emps = new EmployeeResourceModel[] { };
 
-    private List<AssignmentModel> _assignments = new List<AssignmentModel>();
+    private List<Assignment> _assignments = new List<Assignment>();
 
     private DateTime[] _dates = Array.Empty<DateTime>();
     private double[] _totals = Array.Empty<double>();
@@ -278,24 +279,56 @@ public partial class JobResourcePlanner : UserControl
     {
         using (new WaitCursor())
         {
-            
-            var jobids = _jobs.Select(x => x.ID).ToArray();
-            var empids = _emps.Select(x => x.ID).ToArray();
-            var actids = _activities.Select(x => x.ID).ToArray();
+            var empids = _emps.ToArray(x => x.ID);
+            var jobIDs = _jobs.ToArray(x => x.ID);
 
             DateTime fromDate = DateTime.Today;
             DateTime toDate = DateTime.Today.AddMonths(Properties.MonthsToView);
-            
-            MultiQuery query = new MultiQuery();
 
-            query.Add<Assignment>(
-                Filter<Assignment>.Where(x =>x.Employee.ID).InList(empids)
-                    .And(x=>x.Date).IsGreaterThanOrEqualTo(fromDate)
-                    .And(x=>x.Date).IsLessThanOrEqualTo(toDate),
-                AssignmentModel.Columns
-            );
-            query.Query();
-            _assignments = query.Get<Assignment>().Rows.Select(r => new AssignmentModel(r)).ToList();
+            var query = Client.QueryMultiple(
+                new KeyedQueryDef<Assignment>(
+                    Filter<Assignment>.Where(x => x.Employee.ID).InList(empids)
+                        .And(x => x.Date).IsGreaterThanOrEqualTo(fromDate)
+                        .And(x => x.Date).IsLessThanOrEqualTo(toDate)
+                        .Or(Filter<Assignment>.Where(x => x.JobStage.Job.ID).InList(jobIDs)),
+                    Columns.None<Assignment>()
+                        .Add(x => x.Date)
+                        .Add(x => x.Booked.Start)
+                        .Add(x => x.Booked.Finish)
+                        .Add(x => x.Booked.Duration)
+                        .Add(x => x.Job.ID)
+                        .Add(x => x.JobStage.ID)
+                        .Add(x => x.Employee.ID)),
+                new KeyedQueryDef<Job>(
+                    Filter<Job>.Where(x => x.ID).InList(_jobs.ToArray(x => x.ID)),
+                    Columns.None<Job>()
+                        .Add(x => x.ID)
+                        .Add(x => x.WorkingDays)),
+                new KeyedQueryDef<CompanyInformation>(
+                    Filter.All<CompanyInformation>(),
+                    Columns.None<CompanyInformation>()
+                        .Add(x => x.WorkingDays),
+                    range: CoreRange.Database(1)),
+                new KeyedQueryDef<JobStage>(
+                    Filter<JobStage>.Where(x => x.Job.ID).InList(jobIDs),
+                    Columns.None<JobStage>()
+                        .Add(x => x.ID)
+                        .Add(x => x.Job.ID)
+                        .Add(x => x.StartDate)
+                        .Add(x => x.EndDate)
+                        .Add(x => x.TotalHours)));
+
+            _assignments = query.GetList<Assignment>();
+
+            var jobWorkingHours = query.GetObjects<Job>()
+                .ToDictionary(x => x.ID, x =>
+                {
+                    return WorkingDayHours.Deserialize(x.WorkingDays ?? "");
+                });
+            var jobStages = query
+                .GetObjects<JobStage>()
+                .GroupByDictionary(x => x.Job.ID);
+            var defaultWorkingHours = WorkingDayHours.Deserialize(query.GetObjects<CompanyInformation>().FirstOrDefault()?.WorkingDays ?? "");
 
             var data = new DataTable();
 
@@ -310,6 +343,8 @@ public partial class JobResourcePlanner : UserControl
             var available = new double[dates.Count];
             var totals = new double[dates.Count];
 
+            var required = new decimal[dates.Count, _jobs.Length];
+
             foreach (var (dateIdx, date) in dates.WithIndex())
             {
                 double avail = 0.0F;
@@ -334,15 +369,15 @@ public partial class JobResourcePlanner : UserControl
                 var values = new List<object?> { date };
                 
                 var anyjobstoday = _assignments.Where(x => (x.Date.Date == date.Date)); 
-                avail -= anyjobstoday.Aggregate<AssignmentModel, double>(0F, (value, model) => value + model.BookedDuration.TotalHours);
+                avail -= anyjobstoday.Aggregate<Assignment, double>(0F, (value, model) => value + model.Booked.Duration.TotalHours);
 
                 foreach (var (jobIdx, job) in _jobs.WithIndex())
                 {
-                    var thisjobtoday = _assignments.Where(x => (x.Date.Date == date.Date) && (x.JobID == job.ID));
+                    var thisjobtoday = _assignments.Where(x => (x.Date.Date == date.Date) && (x.Job.ID == job.ID));
                     if (thisjobtoday.Any())
                     {
-                        var assigned = thisjobtoday.Aggregate<AssignmentModel, double>(0F,
-                            (value, model) => value + model.BookedDuration.TotalHours);
+                        var assigned = thisjobtoday.Aggregate<Assignment, double>(0F,
+                            (value, model) => value + model.Booked.Duration.TotalHours);
                         dataValues[dateIdx, jobIdx] = assigned / Properties.HoursPerDay;
                     }
                     else
@@ -352,6 +387,86 @@ public partial class JobResourcePlanner : UserControl
                 available[dateIdx] = avail / Properties.HoursPerDay;
                 totals[dateIdx] = total / Properties.HoursPerDay;
             }
+
+            foreach(var (jobID, stages) in jobStages)
+            {
+                var workingHours = jobWorkingHours.GetValueOrDefault(jobID) ?? defaultWorkingHours;
+                foreach(var stage in stages)
+                {
+                    var totalRequiredHoursRemaining = stage.TotalHours;
+                    var stageAssignments = _assignments.Where(x => x.JobStage.ID == stage.ID)
+                        .GroupByDictionary(x => x.Date.Date);
+                    foreach(var assignment in stageAssignments.SelectMany(x => x.Value))
+                    {
+                        if(assignment.Date.Date < DateTime.Today)
+                        {
+                            totalRequiredHoursRemaining -= assignment.Booked.Duration.TotalHoursDecimal();
+                        }
+                    }
+
+                    var remainingDates = new HashSet<DateTime>();
+                    for(var date = DateTime.Today; date <= stage.EndDate.Date; date = date.AddDays(1))
+                    {
+                        if (workingHours[(int)date.DayOfWeek].IsWorkingDay)
+                        {
+                            remainingDates.Add(date);
+                        }
+                    }
+                    var dayTotals = remainingDates.ToDictionary(
+                        x => x,
+                        x =>
+                        {
+                            if (stageAssignments.TryGetValue(x, out var dayAssignments))
+                            {
+                                return dayAssignments.Sum(x => x.Booked.Duration.TotalHoursDecimal());
+                            }
+                            else
+                            {
+                                return 0;
+                            }
+                        });
+
+                    while (totalRequiredHoursRemaining > 0 && remainingDates.Count > 0)
+                    {
+                        var averageHoursPerDay = totalRequiredHoursRemaining / remainingDates.Count;
+
+                        var tempRemainingDates = new HashSet<DateTime>();
+                        var changed = false;
+                        foreach(var date in remainingDates)
+                        {
+                            var dayTotal = dayTotals.GetValueOrDefault(date);
+                            if(dayTotal >= averageHoursPerDay)
+                            {
+                                totalRequiredHoursRemaining -= dayTotal;
+                                changed = true;
+                            }
+                            else
+                            {
+                                tempRemainingDates.Add(date);
+                            }
+                        }
+
+                        remainingDates = tempRemainingDates;
+                        if (!changed) break;
+                    }
+
+                    var hoursPerDay =
+                        remainingDates.Count > 0 ? Math.Max(0, totalRequiredHoursRemaining) / remainingDates.Count
+                        : 0;
+                    foreach(var date in remainingDates)
+                    {
+                        var dateIdx = dates.IndexOf(date);
+                        if (dateIdx == -1) continue;
+
+                        var jobEntry = _jobs.WithIndex().FirstOrDefault(x => x.Value.ID == jobID);
+                        if (jobEntry.Value is null) continue;
+                        var jobIdx = jobEntry.Key;
+
+                        required[dateIdx, jobIdx] += hoursPerDay - dayTotals.GetValueOrDefault(date);
+                    }
+                }
+            }
+
             _totals = totals;
             _available = available;
 
@@ -813,8 +928,8 @@ public partial class JobResourcePlanner : UserControl
                 AdjustStandardLeave(date, ref rostered);
                 AdjustLeaveRequests(date, emp, ref rostered);
 
-                var assignments = _assignments.Where(x => (x.Date == date) && (x.EmployeeID == emp.ID));
-                var assigned = assignments.Aggregate(TimeSpan.Zero, (time, assign) => time += assign.BookedDuration);
+                var assignments = _assignments.Where(x => (x.Date == date) && (x.Employee.ID == emp.ID));
+                var assigned = assignments.Aggregate(TimeSpan.Zero, (time, assign) => time += assign.Booked.Duration);
 
                 if (rostered > assigned)
                     GetEmployee(emp.ID, availableemployees).Time += rostered.Subtract(assigned);
@@ -827,8 +942,8 @@ public partial class JobResourcePlanner : UserControl
     private void LoadAssignedEmployees(Guid[] jobIDs, DateTime[] dates)
     {
         List<JobPlannerEmployee> assignedemployees = new List<JobPlannerEmployee>();
-        foreach (var assignment in _assignments.Where(x => dates.Contains(x.Date) && jobIDs.Contains(x.JobID)))
-            GetEmployee(assignment.EmployeeID, assignedemployees).Time += assignment.BookedDuration;
+        foreach (var assignment in _assignments.Where(x => dates.Contains(x.Date) && jobIDs.Contains(x.Job.ID)))
+            GetEmployee(assignment.Employee.ID, assignedemployees).Time += assignment.Booked.Duration;
         AssignedEmployees.Items = assignedemployees.OrderBy(x=>x.Name).ToList();
         AssignedEmployees.Refresh(false, true);
     }
@@ -959,11 +1074,11 @@ public partial class JobResourcePlanner : UserControl
             );
         }
 
-        bool IsAssigned(AssignmentModel[] assignments, TimeSpan start, TimeSpan finish)
+        bool IsAssigned(Assignment[] assignments, TimeSpan start, TimeSpan finish)
         {
             foreach (var assignment in assignments)
             {
-                if ((assignment.BookedStart <= start) && (assignment.BookedFinish >= finish))
+                if ((assignment.Booked.Start <= start) && (assignment.Booked.Finish >= finish))
                     return true;
             }
             return false;                
@@ -971,11 +1086,9 @@ public partial class JobResourcePlanner : UserControl
         
         if (ExtractSelection(out var jobIDs, out var dates))
         {
-
             if (dataGrid.ItemsSource is DataTable table)
             {
-
-                List<Assignment> updates = new List<Assignment>();
+                var newAssignments = new List<Assignment>();
                 foreach (var available in availables)
                 {
                     foreach (var date in dates)
@@ -996,9 +1109,9 @@ public partial class JobResourcePlanner : UserControl
                             if (GetLeaveRequestTimes(date, emp, out TimeSpan leaverequeststart, out TimeSpan leaverequestfinish))
                                 CheckEdges(leaverequeststart, leaverequestfinish);
                             
-                            var assignments = _assignments.Where(x => (x.Date == date) && (x.EmployeeID == emp.ID)).ToArray();
+                            var assignments = _assignments.Where(x => (x.Date == date) && (x.Employee.ID == emp.ID)).ToArray();
                             foreach (var assignment in assignments)
-                                CheckEdges(assignment.BookedStart, assignment.BookedFinish);
+                                CheckEdges(assignment.Booked.Start, assignment.Booked.Finish);
                             edges.Sort();
 
                             var adjustment = new double[jobIDs.Length];
@@ -1014,7 +1127,7 @@ public partial class JobResourcePlanner : UserControl
                                 {
                                     foreach(var (idx, jobid) in jobIDs.WithIndex())
                                     {
-                                        Assignment assignment = new Assignment();
+                                        var assignment = new Assignment();
                                         assignment.Activity.ID = Properties.ActivityType;
                                         assignment.Employee.ID = emp.ID;
                                         assignment.Date = date;
@@ -1022,7 +1135,7 @@ public partial class JobResourcePlanner : UserControl
                                         assignment.Booked.Start = start;
                                         assignment.Booked.Finish = finish;
                                         assignment.Booked.Duration = finish - start;
-                                        updates.Add(assignment);
+                                        newAssignments.Add(assignment);
                                         adjustment[idx] += assignment.Booked.Duration.TotalHours;
                                     }
                                 }
@@ -1087,16 +1200,12 @@ public partial class JobResourcePlanner : UserControl
                     }
                 }
 
-                if (updates.Any())
+                if (newAssignments.Count != 0)
                 {
                     using (new WaitCursor())
                     {
-                        new Client<Assignment>().Save(updates, "Assigned by Job Planner");
-                        
-                        CoreTable temp = new CoreTable();
-                        temp.LoadColumns(typeof(Assignment));
-                        temp.LoadRows(updates);
-                        _assignments.AddRange(temp.Rows.Select(r => new AssignmentModel(r)));
+                        Client.Save(newAssignments, "Assigned by Job Planner");
+                        _assignments.AddRange(newAssignments);
                         
                         AssignedEmployees.Refresh(false, true);
                         AvailableEmployees.Refresh(false, true);
@@ -1117,11 +1226,11 @@ public partial class JobResourcePlanner : UserControl
                     var dateIdx = _dates.IndexOf(date);
 
                     var emptimes = _assignments.Where(x =>
-                        jobIDs.Contains(x.JobID)
+                        jobIDs.Contains(x.Job.ID)
                         && (x.Date == date)
-                        && employees.Any(e => e.ID == x.EmployeeID)
+                        && employees.Any(e => e.ID == x.Employee.ID)
                     ).ToArray();
-                    var emptime = emptimes.Aggregate(TimeSpan.Zero, (time, ass) => time += ass.BookedDuration);
+                    var emptime = emptimes.Aggregate(TimeSpan.Zero, (time, ass) => time += ass.Booked.Duration);
                     if(Properties.DisplayMode == JobPlannerDisplayMode.JobColumns)
                     {
                         System.Data.DataRow row = table.Rows[dateIdx];
@@ -1173,16 +1282,15 @@ public partial class JobResourcePlanner : UserControl
             }
             
             var assignments = _assignments.Where(x =>
-                jobIDs.Contains(x.JobID)
+                jobIDs.Contains(x.Job.ID)
                 && dates.Contains(x.Date)
-                && employees.Any(e => e.ID == x.EmployeeID)
+                && employees.Any(e => e.ID == x.Employee.ID)
             ).ToArray();
-            if (assignments.Any())
+            if (assignments.Length != 0)
             {
                 using (new WaitCursor())
                 {
-                    var deletes = assignments.Select(x => new Assignment() { ID = x.ID }).ToArray();
-                    new Client<Assignment>().Delete(deletes, "Deleted from Job Planner");
+                    Client.Delete(assignments, "Deleted from Job Planner");
                     
                     var removes = AssignedEmployees.Items.Where(x => employees.Any(e => e.ID == x.ID)).ToArray();
                     foreach (var remove in removes)
@@ -1275,37 +1383,49 @@ public partial class JobResourcePlanner : UserControl
             .Add(x=>x.SupervisionHours)
             .Add(x=>x.Tradespersons)
             .Add(x=>x.TradesHours);
-        
-        var stages =  Client.Query<JobStage>(
-            Filter<JobStage>.Where(x => x.StartDate).IsLessThanOrEqualTo(e.VisibleDateRange.ActualEndDate)
-                .And(x => x.EndDate).IsGreaterThanOrEqualTo(e.VisibleDateRange.ActualStartDate),
-            cols
-        ).ToObjects<JobStage>()
-           .ToArray();
-        
-       foreach (var stage in stages)
-           CreateAppointment(stage);
+        var filter = Filter<JobStage>.Where(x => x.StartDate).IsLessThanOrEqualTo(e.VisibleDateRange.ActualEndDate)
+            .And(x => x.EndDate).IsGreaterThanOrEqualTo(e.VisibleDateRange.ActualStartDate);
+
+        var data = Client.QueryMultiple(
+            new KeyedQueryDef<JobStage>(filter, cols),
+            new KeyedQueryDef<Assignment>(
+                Filter<Assignment>.Where(x => x.JobStage.ID).InQuery(filter, x => x.ID),
+                Columns.None<Assignment>()
+                    .Add(x => x.JobStage.ID)
+                    .Add(x => x.Employee.Name)));
+        var stages = data.GetArray<JobStage>();
+        var assignments = data.GetObjects<Assignment>()
+            .GroupBy(x => x.JobStage.ID)
+            .ToDictionary(x => x.Key, x => x.Select(x => x.Employee.Name).Distinct().ToList());
+
+        foreach (var stage in stages)
+            CreateAppointment(stage, assignments.GetValueOrDefault(stage.ID));
        Schedule.ItemsSource = appointments;
     }
 
-    private void CreateAppointment(JobStage stage)
+    private void CreateAppointment(JobStage stage, List<string>? employees)
     {
-        var model = new StageModel(stage);
+        var model = new StageModel(stage, employees ?? []);
         appointments.Add(model);
     }
     
     private class StageModel : ScheduleAppointment
     {
+        public List<string> Employees { get; }
+
+        public string ToolTipContent => $"Employees:\n{string.Join('\n', Employees.Select(x => $"- {x}"))}";
 
-        public StageModel(JobStage stage)
+        public StageModel(JobStage stage, List<string> employees)
         {
             Stage = stage;
+            Employees = employees;
         }
         
         private JobStage _stage;
         public JobStage Stage
         {
             get => _stage;
+            [MemberNotNull(nameof(_stage))]
             set
             {
                 _stage = value;
@@ -1360,7 +1480,7 @@ public partial class JobResourcePlanner : UserControl
         stage.Type.CopyFrom(type);
         if (new JobStagesGrid().EditItems([stage]))
         {
-            CreateAppointment(stage);
+            CreateAppointment(stage, null);
             _copiedModel = null;
         }
         if (!types?.Any(x => x.ID == stage.Type.ID) == false)
@@ -1408,7 +1528,7 @@ public partial class JobResourcePlanner : UserControl
         newstage.EndDate = newstage.StartDate + span;
         
         new Client<JobStage>().Save(newstage,"Created from Project Planner");
-        CreateAppointment(newstage);
+        CreateAppointment(newstage, null);
         _copiedModel = null;
     }