AssignmentStore.cs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using Comal.Classes;
  4. using InABox.Core;
  5. using System;
  6. using ExCSS;
  7. namespace Comal.Stores
  8. {
  9. internal class AssignmentStore : BaseStore<Assignment>
  10. {
  11. private void CheckActivityForms(Assignment assignment)
  12. {
  13. if (!assignment.ActivityLink.HasOriginalValue(x => x.ID))
  14. {
  15. return;
  16. }
  17. List<AssignmentForm> assignmentForms;
  18. if(assignment.ID != Guid.Empty)
  19. {
  20. assignmentForms = Provider.Query(
  21. Filter<AssignmentForm>.Where(x => x.Parent.ID).IsEqualTo(assignment.ID),
  22. Columns.None<AssignmentForm>().Add(x => x.ID, x => x.FormData)
  23. ).Rows.Select(x => x.ToObject<AssignmentForm>()).ToList();
  24. }
  25. else
  26. {
  27. assignmentForms = new();
  28. }
  29. var toDelete = assignmentForms.Where(x => string.IsNullOrWhiteSpace(x.FormData));
  30. Provider.Delete(toDelete, UserID);
  31. var activityForms = Provider.Query(
  32. Filter<ActivityForm>.Where(x => x.Activity.ID).IsEqualTo(assignment.ActivityLink.ID)
  33. .And(x => x.Form.AppliesTo).IsEqualTo(nameof(Assignment)));
  34. var newForms = new List<AssignmentForm>();
  35. foreach (var row in activityForms.Rows)
  36. {
  37. var formID = row.Get<ActivityForm, Guid>(x => x.Form.ID);
  38. if (!(assignmentForms.Any(x => x.Form.ID == formID) && !toDelete.Any(x => x.Form.ID == formID)))
  39. {
  40. var assignmentForm = new AssignmentForm();
  41. assignmentForm.Form.ID = formID;
  42. assignmentForm.Parent.ID = assignment.ID;
  43. newForms.Add(assignmentForm);
  44. }
  45. }
  46. Provider.Save(newForms);
  47. }
  48. protected override void AfterSave(Assignment entity)
  49. {
  50. base.AfterSave(entity);
  51. CheckActivityForms(entity);
  52. CheckAssignmentCosts(entity);
  53. }
  54. private void CheckAssignmentCosts(Assignment entity)
  55. {
  56. if(!entity.Actual.IsChanged()
  57. && !entity.Booked.IsChanged()
  58. && !entity.HasOriginalValue(x => x.EmployeeLink.ID)
  59. && !entity.HasOriginalValue(x => x.Date))
  60. {
  61. // No change we care about.
  62. return;
  63. }
  64. var changedEmployee = entity.TryGetOriginalValue(x => x.EmployeeLink.ID, out var employeeID);
  65. if (!changedEmployee)
  66. {
  67. employeeID = entity.EmployeeLink.ID;
  68. }
  69. var changedDate = entity.TryGetOriginalValue(x => x.Date, out var date);
  70. if (!changedDate)
  71. {
  72. date = entity.Date;
  73. }
  74. if(changedEmployee || changedDate)
  75. {
  76. CheckAssignmentCosts(date, employeeID);
  77. }
  78. CheckAssignmentCosts(entity.Date, entity.EmployeeLink.ID);
  79. }
  80. private void CheckAssignmentCosts(DateTime date, Guid employeeID)
  81. {
  82. if (employeeID == Guid.Empty) return;
  83. var assignments = Provider.Query(
  84. Filter<Assignment>.Where(x => x.Date).IsEqualTo(date)
  85. .And(x => x.EmployeeLink.ID).IsEqualTo(employeeID),
  86. Columns.Required<Assignment>()
  87. .Add(x => x.ID)
  88. .Add(x => x.Cost)
  89. .Add(x => x.Actual.Start)
  90. .Add(x => x.Actual.Finish)
  91. .Add(x => x.Actual.Duration)
  92. .Add(x => x.Booked.Start)
  93. .Add(x => x.Booked.Finish)
  94. .Add(x => x.Booked.Duration))
  95. .ToArray<Assignment>();
  96. var employee = Provider.Query(
  97. Filter<Employee>.Where(x => x.ID).IsEqualTo(employeeID),
  98. Columns.None<Employee>()
  99. .Add(x => x.RosterStart)
  100. .Add(x => x.HourlyRate))
  101. .ToObjects<Employee>().FirstOrDefault();
  102. if (employee is null) return;
  103. var rosterItems = Provider.Query(
  104. Filter<EmployeeRosterItem>.Where(x => x.Employee.ID).IsEqualTo(employeeID),
  105. Columns.None<EmployeeRosterItem>()
  106. .Add(x => x.Overtime.ID),
  107. new SortOrder<EmployeeRosterItem>(x => x.Day))
  108. .ToArray<EmployeeRosterItem>();
  109. var overtimeID = RosterUtils.GetRoster(rosterItems, employee.RosterStart, date)?.Overtime.ID ?? Guid.Empty;
  110. if(overtimeID == Guid.Empty) return;
  111. var overtime = Provider.Query(
  112. Filter<OvertimeInterval>.Where(x => x.Overtime.ID).IsEqualTo(overtimeID),
  113. Columns.None<OvertimeInterval>()
  114. .Add(x => x.Interval)
  115. .Add(x => x.IntervalType)
  116. .Add(x => x.Multiplier)
  117. .Add(x => x.IsPaid),
  118. new SortOrder<OvertimeInterval>(x => x.Sequence))
  119. .ToArray<OvertimeInterval>();
  120. // We need to sort the assignments, because both the chopping algorithm and the overtime algorithm requires it.
  121. assignments.SortBy(x => x.EffectiveStartTime());
  122. // Do assignment choppage.
  123. var lastEnd = TimeSpan.Zero;
  124. var durations = assignments.ToArray(assignment =>
  125. {
  126. var start = assignment.EffectiveStartTime();
  127. var finish = assignment.EffectiveFinishTime();
  128. // We know this check is enough to check overlap, since the assignments are in increasing order of their start time.
  129. if (lastEnd <= start)
  130. {
  131. // This assignment does not overlap any previous assignments.
  132. lastEnd = assignment.EffectiveFinishTime();
  133. return finish - start;
  134. }
  135. else if(finish >= lastEnd)
  136. {
  137. // This assignment ends after the assignment that we are overlapping.
  138. var duration = finish - lastEnd;
  139. lastEnd = finish;
  140. return duration;
  141. }
  142. else
  143. {
  144. // This assignment is entirely contained within the assignment being overlapped.
  145. return TimeSpan.Zero;
  146. }
  147. });
  148. var totalHours = new double[assignments.Length];
  149. OvertimeUtils.EvaluateOvertime(Enumerable.Range(0, assignments.Length), overtime, i => durations[i], (i, interval, duration) =>
  150. {
  151. if(interval is not null)
  152. {
  153. var multiplier = interval.IsPaid ? interval.Multiplier : 0;
  154. }
  155. if (interval?.IsPaid != false)
  156. {
  157. totalHours[i] += duration.TotalHours * (interval?.Multiplier ?? 1);
  158. }
  159. });
  160. foreach(var (assignment, hours) in assignments.Zip(totalHours))
  161. {
  162. assignment.Cost = hours * employee.HourlyRate;
  163. }
  164. Provider.Save(assignments.Where(x => x.IsChanged()));
  165. }
  166. }
  167. }