Kenric Nugteren пре 4 недеља
родитељ
комит
a333f140cc

+ 8 - 1
prs.classes/Utilities/OvertimeUtils.cs

@@ -1,4 +1,5 @@
-using System;
+using InABox.Core;
+using System;
 using System.Collections.Generic;
 using System.Text;
 
@@ -15,6 +16,11 @@ namespace Comal.Classes
         /// overlaps with multiple overtime intervals, <paramref name="evaluateBlock"/> will be called for each interval, with the length of time overlapped
         /// with that interval provided.
         /// </summary>
+        /// <remarks>
+        /// The interval passed to <paramref name="evaluateBlock"/> <i>should</i> never be <see langword="null"/>; it is only <see langword="null"/> if all the overtime
+        /// is used up and there is no interval of type <see cref="OvertimeIntervalType.RemainingTime"/>; this is not allowed, so it shouldn't happen. Nevertheless,
+        /// the case should be accounted for in case of bad data.
+        /// </remarks>
         public static void EvaluateOvertime<TBlock>(
             IEnumerable<TBlock> blocks,
             OvertimeInterval[] overtimeIntervals,
@@ -69,6 +75,7 @@ namespace Comal.Classes
                         // If there is no overtime interval, then we use up the rest of the time on
                         // the block with a blank PayrollID. Theoretically, this shouldn't happen,
                         // since the "RemainingTime" interval is required.
+                        Logger.Send(LogType.Error, "", $"Error with overtime intervals; no interval of type RemainingTime");
                         evaluateBlock(block, null, duration);
                         duration = TimeSpan.Zero;
                     }

+ 1 - 1
prs.desktop/Dashboards/Common/DigitalFormsDashboard.xaml.cs

@@ -1326,7 +1326,7 @@ public partial class DigitalFormsDashboard : UserControl,
         }
         if (CustomFilter is not null)
         {
-            filter.And(CustomFilter);
+            (filter as IFilter).And(CustomFilter);
         }
 
         var columns = Columns.None<T>().Add(x => x.ID)

+ 57 - 8
prs.stores/AssignmentStore.cs

@@ -3,6 +3,7 @@ using System.Linq;
 using Comal.Classes;
 using InABox.Core;
 using System;
+using ExCSS;
 
 namespace Comal.Stores
 {
@@ -61,6 +62,15 @@ namespace Comal.Stores
 
         private void CheckAssignmentCosts(Assignment entity)
         {
+            if(!entity.Actual.IsChanged()
+                && !entity.Booked.IsChanged()
+                && !entity.HasOriginalValue(x => x.EmployeeLink.ID)
+                && !entity.HasOriginalValue(x => x.Date))
+            {
+                // No change we care about.
+                return;
+            }
+
             var changedEmployee = entity.TryGetOriginalValue(x => x.EmployeeLink.ID, out var employeeID);
             if (!changedEmployee)
             {
@@ -87,8 +97,9 @@ namespace Comal.Stores
             var assignments = Provider.Query(
                 Filter<Assignment>.Where(x => x.Date).IsEqualTo(date)
                     .And(x => x.EmployeeLink.ID).IsEqualTo(employeeID),
-                Columns.None<Assignment>()
+                Columns.Required<Assignment>()
                     .Add(x => x.ID)
+                    .Add(x => x.Cost)
                     .Add(x => x.Actual.Start)
                     .Add(x => x.Actual.Finish)
                     .Add(x => x.Actual.Duration)
@@ -96,6 +107,7 @@ namespace Comal.Stores
                     .Add(x => x.Booked.Finish)
                     .Add(x => x.Booked.Duration))
                 .ToArray<Assignment>();
+
             var employee = Provider.Query(
                 Filter<Employee>.Where(x => x.ID).IsEqualTo(employeeID),
                 Columns.None<Employee>()
@@ -110,6 +122,7 @@ namespace Comal.Stores
                     .Add(x => x.Overtime.ID),
                 new SortOrder<EmployeeRosterItem>(x => x.Day))
                 .ToArray<EmployeeRosterItem>();
+
             var overtimeID = RosterUtils.GetRoster(rosterItems, employee.RosterStart, date)?.Overtime.ID ?? Guid.Empty;
             if(overtimeID == Guid.Empty) return;
 
@@ -118,21 +131,57 @@ namespace Comal.Stores
                 Columns.None<OvertimeInterval>()
                     .Add(x => x.Interval)
                     .Add(x => x.IntervalType)
-                    .Add(x => x.Multiplier),
+                    .Add(x => x.Multiplier)
+                    .Add(x => x.IsPaid),
                 new SortOrder<OvertimeInterval>(x => x.Sequence))
                 .ToArray<OvertimeInterval>();
 
+            // We need to sort the assignments, because both the chopping algorithm and the overtime algorithm requires it.
             assignments.SortBy(x => x.EffectiveStartTime());
-            var totalHours = new Dictionary<Assignment, double>();
-            OvertimeUtils.EvaluateOvertime(assignments, overtime, x => x.EffectiveFinishTime() - x.EffectiveStartTime(), (assignment, interval, duration) =>
+
+            // Do assignment choppage.
+            var lastEnd = TimeSpan.Zero;
+            var durations = assignments.ToArray(assignment =>
+            {
+                var start = assignment.EffectiveStartTime();
+                var finish = assignment.EffectiveFinishTime();
+                // We know this check is enough to check overlap, since the assignments are in increasing order of their start time.
+                if (lastEnd <= start)
+                {
+                    // This assignment does not overlap any previous assignments.
+                    lastEnd = assignment.EffectiveFinishTime();
+                    return finish - start;
+                }
+                else if(finish >= lastEnd)
+                {
+                    // This assignment ends after the assignment that we are overlapping.
+                    var duration = finish - lastEnd;
+                    lastEnd = finish;
+                    return duration;
+                }
+                else
+                {
+                    // This assignment is entirely contained within the assignment being overlapped.
+                    return TimeSpan.Zero;
+                }
+            });
+
+            var totalHours = new double[assignments.Length];
+            OvertimeUtils.EvaluateOvertime(Enumerable.Range(0, assignments.Length), overtime, i => durations[i], (i, interval, duration) =>
             {
-                totalHours[assignment] = totalHours.GetValueOrAdd(assignment)
-                    + duration.TotalHours * (interval?.Multiplier ?? 1);
+                if(interval is not null)
+                {
+                    var multiplier = interval.IsPaid ? interval.Multiplier : 0;
+                }
+                if (interval?.IsPaid != false)
+                {
+                    totalHours[i] += duration.TotalHours * (interval?.Multiplier ?? 1);
+                }
             });
 
-            foreach(var assignment in assignments)
+            foreach(var (assignment, hours) in assignments.Zip(totalHours))
             {
-                assignment.Cost = totalHours.GetValueOrDefault(assignment) * employee.HourlyRate;
+                assignment.Cost = hours * employee.HourlyRate;
             }
 
             Provider.Save(assignments.Where(x => x.IsChanged()));