OvertimeUtils.cs 4.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. using InABox.Core;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Text;
  5. namespace Comal.Classes
  6. {
  7. public static class OvertimeUtils
  8. {
  9. public delegate void EvaluateOvertimeDelegate<TBlock>(TBlock block, OvertimeInterval? interval, TimeSpan duration);
  10. /// <summary>
  11. /// Evaluate the overtime intervals given, using <paramref name="blocks"/> as the list of blocks that have been worked for this day.
  12. /// <paramref name="durationSelector"/> is used to get the duration of each <typeparamref name="TBlock"/>, and
  13. /// <paramref name="evaluateBlock"/> is called for every distinct block that needs evaluating. So if a given <typeparamref name="TBlock"/>
  14. /// overlaps with multiple overtime intervals, <paramref name="evaluateBlock"/> will be called for each interval, with the length of time overlapped
  15. /// with that interval provided.
  16. /// </summary>
  17. /// <remarks>
  18. /// 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
  19. /// is used up and there is no interval of type <see cref="OvertimeIntervalType.RemainingTime"/>; this is not allowed, so it shouldn't happen. Nevertheless,
  20. /// the case should be accounted for in case of bad data.
  21. /// </remarks>
  22. public static void EvaluateOvertime<TBlock>(
  23. IEnumerable<TBlock> blocks,
  24. OvertimeInterval[] overtimeIntervals,
  25. Func<TBlock, TimeSpan> durationSelector,
  26. EvaluateOvertimeDelegate<TBlock> evaluateBlock
  27. )
  28. {
  29. var curOvertimeIdx = 0;
  30. OvertimeInterval? GetOvertimeInterval() => curOvertimeIdx < overtimeIntervals.Length ? overtimeIntervals[curOvertimeIdx] : null;
  31. var curInterval = GetOvertimeInterval()?.Interval ?? TimeSpan.Zero;
  32. foreach (var block in blocks)
  33. {
  34. var duration = durationSelector(block);
  35. while (duration > TimeSpan.Zero)
  36. {
  37. var interval = GetOvertimeInterval();
  38. if (interval != null)
  39. {
  40. switch (interval.IntervalType)
  41. {
  42. case OvertimeIntervalType.Interval:
  43. if (duration >= curInterval)
  44. {
  45. // In this case, the block is more than the rest of
  46. // the current interval, so we use up all the remaining interval
  47. // time, and then move to the next interval.
  48. evaluateBlock(block, interval, curInterval);
  49. duration -= curInterval;
  50. ++curOvertimeIdx;
  51. curInterval = GetOvertimeInterval()?.Interval ?? TimeSpan.Zero;
  52. }
  53. else
  54. {
  55. evaluateBlock(block, interval, duration);
  56. // Otherwise, we use up the entire block, and decrease the interval by the duration remaining.
  57. curInterval -= duration;
  58. duration = TimeSpan.Zero;
  59. }
  60. break;
  61. case OvertimeIntervalType.RemainingTime:
  62. // In this case, the interval is unchanged.
  63. evaluateBlock(block, interval, duration);
  64. duration = TimeSpan.Zero;
  65. break;
  66. default:
  67. throw new NotImplementedException($"Not implemented Overtime interval type {interval.IntervalType}");
  68. }
  69. }
  70. else
  71. {
  72. // If there is no overtime interval, then we use up the rest of the time on
  73. // the block with a blank PayrollID. Theoretically, this shouldn't happen,
  74. // since the "RemainingTime" interval is required.
  75. Logger.Send(LogType.Error, "", $"Error with overtime intervals; no interval of type RemainingTime");
  76. evaluateBlock(block, null, duration);
  77. duration = TimeSpan.Zero;
  78. }
  79. }
  80. }
  81. }
  82. }
  83. }