@lilaquadrat/studio 12.1.0 → 12.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/lib/models.d.ts +8 -1
  2. package/lib/models.js +8 -1
  3. package/lib/models.js.map +1 -1
  4. package/lib/services.d.ts +8 -1
  5. package/lib/services.js +8 -1
  6. package/lib/services.js.map +1 -1
  7. package/lib/src/Immutable.class.js +10 -4
  8. package/lib/src/Immutable.class.js.map +1 -1
  9. package/lib/src/aggegations/structures.aggregation.d.ts +6 -8
  10. package/lib/src/aggegations/structures.aggregation.js +3 -2
  11. package/lib/src/aggegations/structures.aggregation.js.map +1 -1
  12. package/lib/src/classes/paymentProviders/PaymentProviderFactory.class.d.ts +4 -0
  13. package/lib/src/classes/paymentProviders/PaymentProviderFactory.class.js +8 -0
  14. package/lib/src/classes/paymentProviders/PaymentProviderFactory.class.js.map +1 -0
  15. package/lib/src/classes/paymentProviders/StripePaymentProvider.class.d.ts +9 -0
  16. package/lib/src/classes/paymentProviders/StripePaymentProvider.class.js +49 -0
  17. package/lib/src/classes/paymentProviders/StripePaymentProvider.class.js.map +1 -0
  18. package/lib/src/classes/shiftPlanningModel.class.d.ts +6 -0
  19. package/lib/src/classes/shiftPlanningModel.class.js +26 -0
  20. package/lib/src/classes/shiftPlanningModel.class.js.map +1 -0
  21. package/lib/src/helpers/buildProductName.d.ts +7 -0
  22. package/lib/src/helpers/buildProductName.js +20 -0
  23. package/lib/src/helpers/buildProductName.js.map +1 -0
  24. package/lib/src/helpers/shiftPlanAllocation.d.ts +53 -0
  25. package/lib/src/helpers/shiftPlanAllocation.js +110 -0
  26. package/lib/src/helpers/shiftPlanAllocation.js.map +1 -0
  27. package/lib/src/helpers/shiftPlanCoverage.d.ts +2 -0
  28. package/lib/src/helpers/shiftPlanCoverage.js +4 -0
  29. package/lib/src/helpers/shiftPlanCoverage.js.map +1 -0
  30. package/lib/src/helpers/shiftPlanningReports.d.ts +7 -0
  31. package/lib/src/helpers/shiftPlanningReports.js +135 -0
  32. package/lib/src/helpers/shiftPlanningReports.js.map +1 -0
  33. package/lib/src/helpers/shiftPlanningSchemas.d.ts +143 -0
  34. package/lib/src/helpers/shiftPlanningSchemas.js +87 -0
  35. package/lib/src/helpers/shiftPlanningSchemas.js.map +1 -0
  36. package/lib/src/helpers/shiftPlanningTime.d.ts +24 -0
  37. package/lib/src/helpers/shiftPlanningTime.js +215 -0
  38. package/lib/src/helpers/shiftPlanningTime.js.map +1 -0
  39. package/lib/src/helpers/shiftPlanningValidation.d.ts +32 -0
  40. package/lib/src/helpers/shiftPlanningValidation.js +69 -0
  41. package/lib/src/helpers/shiftPlanningValidation.js.map +1 -0
  42. package/lib/src/helpers/structureMatch.d.ts +15 -0
  43. package/lib/src/helpers/structureMatch.js +72 -0
  44. package/lib/src/helpers/structureMatch.js.map +1 -0
  45. package/lib/src/interfaces/ListPlanningParticipant.d.ts +17 -0
  46. package/lib/src/interfaces/ListPlanningParticipant.js +2 -0
  47. package/lib/src/interfaces/ListPlanningParticipant.js.map +1 -0
  48. package/lib/src/interfaces/PaymentProviderBase.d.ts +14 -0
  49. package/lib/src/interfaces/PaymentProviderBase.js +2 -0
  50. package/lib/src/interfaces/PaymentProviderBase.js.map +1 -0
  51. package/lib/src/interfaces/PlanCoveragePreparation.d.ts +17 -0
  52. package/lib/src/interfaces/PlanCoveragePreparation.js +2 -0
  53. package/lib/src/interfaces/PlanCoveragePreparation.js.map +1 -0
  54. package/lib/src/interfaces/PlanningParticipantsResult.d.ts +12 -0
  55. package/lib/src/interfaces/PlanningParticipantsResult.js +2 -0
  56. package/lib/src/interfaces/PlanningParticipantsResult.js.map +1 -0
  57. package/lib/src/interfaces/ShiftAutofillResult.d.ts +6 -0
  58. package/lib/src/interfaces/ShiftAutofillResult.js +2 -0
  59. package/lib/src/interfaces/ShiftAutofillResult.js.map +1 -0
  60. package/lib/src/interfaces/ShiftReportContextOptions.d.ts +9 -0
  61. package/lib/src/interfaces/ShiftReportContextOptions.js +2 -0
  62. package/lib/src/interfaces/ShiftReportContextOptions.js.map +1 -0
  63. package/lib/src/models/customers.model.js +10 -0
  64. package/lib/src/models/customers.model.js.map +1 -1
  65. package/lib/src/models/employeeAbsence.model.d.ts +4 -0
  66. package/lib/src/models/employeeAbsence.model.js +11 -0
  67. package/lib/src/models/employeeAbsence.model.js.map +1 -0
  68. package/lib/src/models/historySeen.model.d.ts +4 -0
  69. package/lib/src/models/historySeen.model.js +30 -0
  70. package/lib/src/models/historySeen.model.js.map +1 -0
  71. package/lib/src/models/lists.model.js +2 -1
  72. package/lib/src/models/lists.model.js.map +1 -1
  73. package/lib/src/models/listsParticipants.model.js +11 -0
  74. package/lib/src/models/listsParticipants.model.js.map +1 -1
  75. package/lib/src/models/permissions.model.js +28 -0
  76. package/lib/src/models/permissions.model.js.map +1 -1
  77. package/lib/src/models/shift.model.d.ts +4 -0
  78. package/lib/src/models/shift.model.js +17 -0
  79. package/lib/src/models/shift.model.js.map +1 -0
  80. package/lib/src/models/shiftPlan.model.d.ts +4 -0
  81. package/lib/src/models/shiftPlan.model.js +14 -0
  82. package/lib/src/models/shiftPlan.model.js.map +1 -0
  83. package/lib/src/models/shiftPlanDays.model.d.ts +4 -0
  84. package/lib/src/models/shiftPlanDays.model.js +15 -0
  85. package/lib/src/models/shiftPlanDays.model.js.map +1 -0
  86. package/lib/src/models/shiftPlanningSettings.model.d.ts +4 -0
  87. package/lib/src/models/shiftPlanningSettings.model.js +10 -0
  88. package/lib/src/models/shiftPlanningSettings.model.js.map +1 -0
  89. package/lib/src/models/workAreas.model.d.ts +4 -0
  90. package/lib/src/models/workAreas.model.js +12 -0
  91. package/lib/src/models/workAreas.model.js.map +1 -0
  92. package/lib/src/services/customers.service.d.ts +12 -1
  93. package/lib/src/services/customers.service.js +27 -0
  94. package/lib/src/services/customers.service.js.map +1 -1
  95. package/lib/src/services/employeeAbsences.service.d.ts +18 -0
  96. package/lib/src/services/employeeAbsences.service.js +57 -0
  97. package/lib/src/services/employeeAbsences.service.js.map +1 -0
  98. package/lib/src/services/history.service.d.ts +15 -0
  99. package/lib/src/services/history.service.js +36 -0
  100. package/lib/src/services/history.service.js.map +1 -1
  101. package/lib/src/services/historySeen.service.d.ts +32 -0
  102. package/lib/src/services/historySeen.service.js +66 -0
  103. package/lib/src/services/historySeen.service.js.map +1 -0
  104. package/lib/src/services/listParticipants.service.d.ts +180 -12
  105. package/lib/src/services/listParticipants.service.js +426 -6
  106. package/lib/src/services/listParticipants.service.js.map +1 -1
  107. package/lib/src/services/lists.service.d.ts +9 -0
  108. package/lib/src/services/lists.service.js +51 -0
  109. package/lib/src/services/lists.service.js.map +1 -1
  110. package/lib/src/services/paymentProviders.service.d.ts +5 -0
  111. package/lib/src/services/paymentProviders.service.js +10 -0
  112. package/lib/src/services/paymentProviders.service.js.map +1 -1
  113. package/lib/src/services/shiftPlanDays.service.d.ts +22 -0
  114. package/lib/src/services/shiftPlanDays.service.js +80 -0
  115. package/lib/src/services/shiftPlanDays.service.js.map +1 -0
  116. package/lib/src/services/shiftPlanningSettings.service.d.ts +11 -0
  117. package/lib/src/services/shiftPlanningSettings.service.js +27 -0
  118. package/lib/src/services/shiftPlanningSettings.service.js.map +1 -0
  119. package/lib/src/services/shiftPlans.service.d.ts +24 -0
  120. package/lib/src/services/shiftPlans.service.js +139 -0
  121. package/lib/src/services/shiftPlans.service.js.map +1 -0
  122. package/lib/src/services/shifts.service.d.ts +46 -0
  123. package/lib/src/services/shifts.service.js +405 -0
  124. package/lib/src/services/shifts.service.js.map +1 -0
  125. package/lib/src/services/structures.service.d.ts +2 -0
  126. package/lib/src/services/structures.service.js +7 -0
  127. package/lib/src/services/structures.service.js.map +1 -1
  128. package/lib/src/services/workAreas.service.d.ts +18 -0
  129. package/lib/src/services/workAreas.service.js +67 -0
  130. package/lib/src/services/workAreas.service.js.map +1 -0
  131. package/lib/tests/buildProductName.spec.d.ts +1 -0
  132. package/lib/tests/buildProductName.spec.js +27 -0
  133. package/lib/tests/buildProductName.spec.js.map +1 -0
  134. package/lib/tests/customersServiceStructures.spec.d.ts +1 -0
  135. package/lib/tests/customersServiceStructures.spec.js +146 -0
  136. package/lib/tests/customersServiceStructures.spec.js.map +1 -0
  137. package/lib/tests/historySeenService.spec.d.ts +1 -0
  138. package/lib/tests/historySeenService.spec.js +159 -0
  139. package/lib/tests/historySeenService.spec.js.map +1 -0
  140. package/lib/tests/listParticipantsPlanning.spec.d.ts +1 -0
  141. package/lib/tests/listParticipantsPlanning.spec.js +479 -0
  142. package/lib/tests/listParticipantsPlanning.spec.js.map +1 -0
  143. package/lib/tests/listParticipantsServiceStructures.spec.d.ts +1 -0
  144. package/lib/tests/listParticipantsServiceStructures.spec.js +176 -0
  145. package/lib/tests/listParticipantsServiceStructures.spec.js.map +1 -0
  146. package/lib/tests/listsServiceCreateExternalProducts.spec.d.ts +1 -0
  147. package/lib/tests/listsServiceCreateExternalProducts.spec.js +91 -0
  148. package/lib/tests/listsServiceCreateExternalProducts.spec.js.map +1 -0
  149. package/lib/tests/paymentProviders.spec.d.ts +1 -0
  150. package/lib/tests/paymentProviders.spec.js +119 -0
  151. package/lib/tests/paymentProviders.spec.js.map +1 -0
  152. package/lib/tests/paymentProvidersServiceGetProduct.spec.d.ts +1 -0
  153. package/lib/tests/paymentProvidersServiceGetProduct.spec.js +33 -0
  154. package/lib/tests/paymentProvidersServiceGetProduct.spec.js.map +1 -0
  155. package/lib/tests/shiftPlanCoverage.spec.d.ts +1 -0
  156. package/lib/tests/shiftPlanCoverage.spec.js +189 -0
  157. package/lib/tests/shiftPlanCoverage.spec.js.map +1 -0
  158. package/lib/tests/shiftPlanning.spec.d.ts +1 -0
  159. package/lib/tests/shiftPlanning.spec.js +1162 -0
  160. package/lib/tests/shiftPlanning.spec.js.map +1 -0
  161. package/lib/tests/shiftPlanningTime.spec.d.ts +1 -0
  162. package/lib/tests/shiftPlanningTime.spec.js +90 -0
  163. package/lib/tests/shiftPlanningTime.spec.js.map +1 -0
  164. package/lib/tests/structureMatch.spec.d.ts +1 -0
  165. package/lib/tests/structureMatch.spec.js +106 -0
  166. package/lib/tests/structureMatch.spec.js.map +1 -0
  167. package/package.json +2 -2
@@ -0,0 +1,7 @@
1
+ import type { WithId } from 'mongodb';
2
+ import type { Shift, ShiftResponse, ShiftReportContext } from '@lilaquadrat/interfaces';
3
+ export declare const unavailableParticipantStates: Set<string>;
4
+ export declare const scheduleWindowMilliseconds: number;
5
+ export declare const boundedPlanningInstant: (timestamp: number) => string;
6
+ /** This calculation has no persistence side effects, so reads and prospective writes use the same rules. */
7
+ export declare function reportShift(shift: WithId<Shift>, context: ShiftReportContext, includeWorkingTime?: boolean, evidence?: Map<string, number>): ShiftResponse;
@@ -0,0 +1,135 @@
1
+ import { breakViolation, calculateWorkingTime, effectivePolicy, minuteMilliseconds, previousDate, workedDates, workedMinutesByDate } from './shiftPlanningTime.js';
2
+ import { violationIdentity } from './shiftPlanningValidation.js';
3
+ export const unavailableParticipantStates = new Set(['blocked', 'unchecked', 'notuseful', 'canceled']);
4
+ // 366 configurable days + seven-day shifts + two days for timezone/DST boundary differences.
5
+ export const scheduleWindowMilliseconds = 375 * 24 * 60 * minuteMilliseconds;
6
+ const firstInstant = Date.parse('0000-01-01T00:00:00Z');
7
+ const lastInstant = Date.parse('9999-12-31T23:59:59.999Z');
8
+ export const boundedPlanningInstant = (timestamp) => new Date(Math.max(firstInstant, Math.min(lastInstant, timestamp))).toISOString();
9
+ function participantUnavailable(shift, assignment, context) {
10
+ const participant = context.participants.find((entry) => entry._id.equals(assignment.participant));
11
+ return !participant || participant.project !== shift.project || !shift.list || !participant.list.equals(shift.list)
12
+ || !participant.user.equals(assignment.customer) || unavailableParticipantStates.has(participant.state)
13
+ || !context.customers.some((entry) => entry._id.equals(assignment.customer));
14
+ }
15
+ function restViolations(shift, policy, otherShifts, add) {
16
+ if (!policy.minimumRest.enabled)
17
+ return;
18
+ // Overlaps are separate attention items; their intervals must not become an implicit assignment prohibition.
19
+ const previous = otherShifts.filter((entry) => entry.end <= shift.start).sort((left, right) => right.end.localeCompare(left.end))[0];
20
+ if (!previous)
21
+ return;
22
+ const restingMinutes = (Date.parse(shift.start) - Date.parse(previous.end)) / minuteMilliseconds;
23
+ if (restingMinutes < policy.minimumRest.minutes) {
24
+ add('rest', { relatedShift: previous._id.toHexString() }, policy.minimumRest.minutes - restingMinutes);
25
+ }
26
+ }
27
+ function dailyHoursSeverity(shift, assignment, timeZone, policy, otherShifts) {
28
+ if (!policy.maximumDailyWorkMinutes.enabled)
29
+ return 0;
30
+ const totals = new Map();
31
+ for (const scheduled of [shift, ...otherShifts]) {
32
+ const assigned = scheduled.assignments.find((entry) => entry.customer.equals(assignment.customer));
33
+ if (!assigned)
34
+ continue;
35
+ for (const [date, minutes] of workedMinutesByDate(scheduled, assigned, timeZone)) {
36
+ totals.set(date, (totals.get(date) ?? 0) + minutes);
37
+ }
38
+ }
39
+ let severity = 0;
40
+ // The candidate's own dates decide which local days are charged, so a shift around midnight is attributed to the days it works.
41
+ for (const [date, minutes] of workedMinutesByDate(shift, assignment, timeZone)) {
42
+ severity = Math.max(severity, (totals.get(date) ?? 0) - policy.maximumDailyWorkMinutes.minutes);
43
+ }
44
+ return severity;
45
+ }
46
+ function consecutiveDaySeverity(shift, assignment, timeZone, policy, otherShifts) {
47
+ if (!policy.maximumConsecutiveWorkingDays.enabled)
48
+ return 0;
49
+ // Each evaluated plan chooses its local calendar; stored shift.day cannot describe another plan's overnight work.
50
+ const allDays = new Set();
51
+ for (const scheduled of [shift, ...otherShifts]) {
52
+ const assigned = scheduled.assignments.find((entry) => entry.customer.equals(assignment.customer));
53
+ workedDates(scheduled, assigned, timeZone).forEach((day) => allDays.add(day));
54
+ }
55
+ let severity = 0;
56
+ for (const day of workedDates(shift, assignment, timeZone)) {
57
+ let count = 1;
58
+ let cursor = previousDate(day);
59
+ while (allDays.has(cursor)) {
60
+ count += 1;
61
+ cursor = previousDate(cursor);
62
+ }
63
+ severity = Math.max(severity, count - policy.maximumConsecutiveWorkingDays.days);
64
+ }
65
+ return severity;
66
+ }
67
+ /** This calculation has no persistence side effects, so reads and prospective writes use the same rules. */
68
+ export function reportShift(shift, context, includeWorkingTime = true, evidence) {
69
+ const response = { ...shift, participants: shift.assignments.map((assignment) => {
70
+ const customer = context.customers.find((entry) => entry._id.equals(assignment.customer));
71
+ const detail = context.participantDetails.find((entry) => entry._id?.equals(assignment.participant));
72
+ // Carry the participant's own answers and both model definitions so the shift's conditions can be scored.
73
+ const answers = {};
74
+ if (detail)
75
+ for (const [key, value] of Object.entries(detail))
76
+ if (key.startsWith('s-'))
77
+ answers[key] = value;
78
+ return {
79
+ ...answers,
80
+ participant: assignment.participant,
81
+ customer: assignment.customer,
82
+ name: customer ? [customer.name, customer.prename].filter(Boolean).join(', ') : '',
83
+ email: customer?.email ?? '',
84
+ ...(detail?.structures ? { structures: detail.structures } : {}),
85
+ ...(detail?.user ? { user: detail.user } : {}),
86
+ };
87
+ }), unavailableParticipantIds: [], violations: [], workingTime: [] };
88
+ const plan = context.plans.find((entry) => entry._id.equals(shift.plan));
89
+ const policy = plan ? effectivePolicy(context.settings, plan.settingsOverrides) : null;
90
+ for (const assignment of shift.assignments) {
91
+ const customer = assignment.customer.toHexString();
92
+ const add = (rule, references = {}, severity = 1) => {
93
+ const violation = { customer, shift: shift._id.toHexString(), rule, ...references };
94
+ response.violations.push(violation);
95
+ evidence?.set(violationIdentity(violation), severity);
96
+ };
97
+ if (participantUnavailable(shift, assignment, context))
98
+ response.unavailableParticipantIds.push(assignment.participant.toHexString());
99
+ const otherShifts = context.schedules.filter((entry) => !entry._id.equals(shift._id)
100
+ && entry.assignments.some((other) => other.customer.equals(assignment.customer)));
101
+ for (const other of otherShifts) {
102
+ // Severity is the shared minutes, so the evidence comparison can tell an increased overlap from a reduced one.
103
+ if (other.start < shift.end && other.end > shift.start) {
104
+ const overlapMinutes = (Math.min(Date.parse(shift.end), Date.parse(other.end)) - Math.max(Date.parse(shift.start), Date.parse(other.start))) / minuteMilliseconds;
105
+ add('overlap', { relatedShift: other._id.toHexString() }, Math.max(1, Math.round(overlapMinutes)));
106
+ }
107
+ }
108
+ if (!policy || !plan) {
109
+ add('settingsMissing');
110
+ continue;
111
+ }
112
+ if (includeWorkingTime)
113
+ response.workingTime.push(calculateWorkingTime(shift, assignment, plan.timeZone, policy));
114
+ if (breakViolation(shift, assignment, policy))
115
+ add('breaks');
116
+ if (policy.absenceEnforcement) {
117
+ for (const absence of context.absences) {
118
+ if (absence.customer.equals(assignment.customer) && absence.start < shift.end && absence.end > shift.start) {
119
+ // IDs explain attention without disclosing a sickness/vacation reason to another department.
120
+ add('absence', { absence: absence._id.toHexString() });
121
+ }
122
+ }
123
+ }
124
+ // The upcoming shift's plan owns the required incoming rest, regardless of which shift was edited.
125
+ restViolations(shift, policy, otherShifts, add);
126
+ const consecutiveSeverity = consecutiveDaySeverity(shift, assignment, plan.timeZone, policy, otherShifts);
127
+ if (consecutiveSeverity > 0)
128
+ add('consecutiveDays', {}, consecutiveSeverity);
129
+ const dailySeverity = dailyHoursSeverity(shift, assignment, plan.timeZone, policy, otherShifts);
130
+ if (dailySeverity > 0)
131
+ add('dailyHours', {}, dailySeverity);
132
+ }
133
+ return response;
134
+ }
135
+ //# sourceMappingURL=shiftPlanningReports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shiftPlanningReports.js","sourceRoot":"","sources":["../../../src/helpers/shiftPlanningReports.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,143 @@
1
+ import type { SchemaObject } from 'ajv';
2
+ export declare const objectSchema: (properties: Record<string, SchemaObject>, required?: string[]) => SchemaObject;
3
+ export declare const objectIdSchema: SchemaObject;
4
+ export declare const instantSchema: SchemaObject;
5
+ /** `enabled` is omitted by clients when false, so it must never be required; omission means false. */
6
+ export declare const policyProperties: {
7
+ daytime: SchemaObject;
8
+ nighttime: SchemaObject;
9
+ maximumConsecutiveWorkingDays: SchemaObject;
10
+ maximumDailyWorkMinutes: SchemaObject;
11
+ minimumRest: SchemaObject;
12
+ breaks: SchemaObject;
13
+ absenceEnforcement: {
14
+ type: string;
15
+ };
16
+ };
17
+ export declare const policySchema: SchemaObject;
18
+ export declare const structureConditionProperties: {
19
+ structure: SchemaObject;
20
+ constraint: {
21
+ enum: string[];
22
+ };
23
+ grade: {
24
+ enum: string[];
25
+ };
26
+ operator: {
27
+ enum: string[];
28
+ };
29
+ value: {
30
+ anyOf: ({
31
+ type: string;
32
+ maxLength: number;
33
+ } | {
34
+ type: string;
35
+ maxLength?: undefined;
36
+ })[];
37
+ };
38
+ };
39
+ export declare const structureConditionSchema: SchemaObject;
40
+ export declare const areaProperties: {
41
+ id: SchemaObject;
42
+ name: SchemaObject;
43
+ description: SchemaObject;
44
+ location: SchemaObject;
45
+ address: SchemaObject;
46
+ structures: SchemaObject;
47
+ };
48
+ export declare const planningDateSchema: SchemaObject;
49
+ export declare const dayAreaProperties: {
50
+ area: SchemaObject;
51
+ };
52
+ export declare const dayProperties: {
53
+ date: SchemaObject;
54
+ name: SchemaObject;
55
+ areas: {
56
+ type: string;
57
+ maxItems: number;
58
+ items: SchemaObject;
59
+ };
60
+ };
61
+ export declare const planWorkAreaProperties: {
62
+ area: SchemaObject;
63
+ categoryIds: {
64
+ type: string;
65
+ maxItems: number;
66
+ items: SchemaObject;
67
+ };
68
+ };
69
+ export declare const workAreaCategoriesSchema: SchemaObject;
70
+ /** Same states the list participant views offer; `unchecked` is stored but not offered by the UI. */
71
+ export declare const participantStates: string[];
72
+ export declare const participantStateSchema: SchemaObject;
73
+ export declare const workHoursSchema: SchemaObject;
74
+ export declare const planProperties: {
75
+ name: SchemaObject;
76
+ timeZone: SchemaObject;
77
+ settingsOverrides: SchemaObject;
78
+ workAreaCategories: SchemaObject;
79
+ defaultParticipantState: {
80
+ anyOf: SchemaObject[];
81
+ };
82
+ workHours: SchemaObject;
83
+ };
84
+ export declare const planInputProperties: {
85
+ list: SchemaObject;
86
+ name: SchemaObject;
87
+ timeZone: SchemaObject;
88
+ settingsOverrides: SchemaObject;
89
+ workAreaCategories: SchemaObject;
90
+ defaultParticipantState: {
91
+ anyOf: SchemaObject[];
92
+ };
93
+ workHours: SchemaObject;
94
+ };
95
+ export declare const scopeProperties: {
96
+ company: SchemaObject;
97
+ project: SchemaObject;
98
+ list: SchemaObject;
99
+ };
100
+ export declare const shiftProperties: {
101
+ area: SchemaObject;
102
+ day: SchemaObject;
103
+ title: SchemaObject;
104
+ start: SchemaObject;
105
+ end: SchemaObject;
106
+ };
107
+ export declare const shiftStaffingProperties: {
108
+ structures: SchemaObject;
109
+ requiredPeople: SchemaObject;
110
+ matchingPeople: SchemaObject;
111
+ };
112
+ export declare const breakSchema: SchemaObject;
113
+ export declare const assignmentProperties: {
114
+ participant: SchemaObject;
115
+ breaks: {
116
+ type: string;
117
+ maxItems: number;
118
+ items: SchemaObject;
119
+ };
120
+ };
121
+ export declare const assignmentInputSchema: {
122
+ type: string;
123
+ maxItems: number;
124
+ items: SchemaObject;
125
+ };
126
+ export declare const assignmentSchema: {
127
+ type: string;
128
+ maxItems: number;
129
+ items: SchemaObject;
130
+ };
131
+ export declare const absenceProperties: {
132
+ customer: SchemaObject;
133
+ type: {
134
+ enum: string[];
135
+ };
136
+ start: SchemaObject;
137
+ end: SchemaObject;
138
+ };
139
+ export declare const filterProperties: {
140
+ from: SchemaObject;
141
+ to: SchemaObject;
142
+ site: SchemaObject;
143
+ };
@@ -0,0 +1,87 @@
1
+ export const objectSchema = (properties, required = Object.keys(properties)) => ({
2
+ type: 'object', properties, required, additionalProperties: false,
3
+ });
4
+ const text = (maxLength, minLength = 0) => ({ type: 'string', minLength, maxLength });
5
+ const integer = (minimum, maximum) => ({ type: 'integer', minimum, maximum });
6
+ export const objectIdSchema = { shiftPlanningObjectId: true };
7
+ export const instantSchema = {
8
+ type: 'string', format: 'date-time', maxLength: 40,
9
+ pattern: '^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{1,3})?(?:Z|[+-]\\d{2}:\\d{2})$',
10
+ };
11
+ const clockSchema = { type: 'string', pattern: '^(?:[01]\\d|2[0-3]):[0-5]\\d$' };
12
+ const clockPeriod = objectSchema({ start: clockSchema, end: clockSchema });
13
+ /** `enabled` is omitted by clients when false, so it must never be required; omission means false. */
14
+ export const policyProperties = {
15
+ daytime: clockPeriod,
16
+ nighttime: clockPeriod,
17
+ maximumConsecutiveWorkingDays: objectSchema({ enabled: { type: 'boolean' }, days: integer(1, 366) }, ['days']),
18
+ maximumDailyWorkMinutes: objectSchema({ enabled: { type: 'boolean' }, minutes: integer(1, 1440) }, ['minutes']),
19
+ minimumRest: objectSchema({ enabled: { type: 'boolean' }, minutes: integer(0, 10080) }, []),
20
+ breaks: objectSchema({
21
+ enabled: { type: 'boolean' }, maximumContinuousWorkMinutes: integer(1, 10080),
22
+ minimumQualifyingBreakMinutes: integer(1, 1440),
23
+ requirements: { type: 'array', maxItems: 10, items: objectSchema({
24
+ afterWorkMinutes: integer(1, 10080), minimumTotalBreakMinutes: integer(1, 1440),
25
+ }) },
26
+ }, ['maximumContinuousWorkMinutes', 'minimumQualifyingBreakMinutes', 'requirements']),
27
+ absenceEnforcement: { type: 'boolean' },
28
+ };
29
+ export const policySchema = objectSchema(policyProperties, Object.keys(policyProperties).filter((key) => key !== 'absenceEnforcement'));
30
+ const addressProperties = Object.fromEntries(['street', 'streetNumber', 'zipcode', 'city', 'country_name', 'addressAddition'].map((key) => [key, text(250)]));
31
+ addressProperties.country = { type: 'string', pattern: '^[A-Za-z]{2}$' };
32
+ addressProperties.osm_id = { type: 'number' };
33
+ export const structureConditionProperties = {
34
+ structure: text(24, 1),
35
+ constraint: { enum: ['required', 'forbidden'] },
36
+ grade: { enum: ['very-positive', 'positive', 'negative', 'very-negative'] },
37
+ operator: { enum: ['lt', 'lte', 'eq', 'gt', 'gte'] },
38
+ value: { anyOf: [{ type: 'string', maxLength: 250 }, { type: 'number' }, { type: 'boolean' }] },
39
+ };
40
+ export const structureConditionSchema = objectSchema(structureConditionProperties, ['structure']);
41
+ const structuresSchema = { type: 'array', maxItems: 100, items: structureConditionSchema };
42
+ export const areaProperties = {
43
+ id: text(100, 1), name: text(100, 1), description: text(2500), location: text(250), address: objectSchema(addressProperties, []),
44
+ structures: structuresSchema,
45
+ };
46
+ export const planningDateSchema = { type: 'string', format: 'date', pattern: '^\\d{4}-\\d{2}-\\d{2}$' };
47
+ export const dayAreaProperties = { area: objectIdSchema };
48
+ export const dayProperties = {
49
+ date: planningDateSchema,
50
+ name: text(100, 1),
51
+ areas: { type: 'array', maxItems: 100, items: objectSchema(dayAreaProperties, ['area']) },
52
+ };
53
+ export const planWorkAreaProperties = { area: objectIdSchema, categoryIds: { type: 'array', maxItems: 10, items: objectIdSchema } };
54
+ export const workAreaCategoriesSchema = { type: 'array', maxItems: 200, items: objectSchema(planWorkAreaProperties, ['area']) };
55
+ /** Same states the list participant views offer; `unchecked` is stored but not offered by the UI. */
56
+ export const participantStates = ['unchecked', 'paid', 'notuseful', 'confirmed', 'enlisted', 'reserved', 'waiting', 'blocked', 'finalized', 'canceled'];
57
+ export const participantStateSchema = { enum: participantStates };
58
+ export const workHoursSchema = objectSchema({ start: integer(0, 23), end: integer(1, 24) });
59
+ export const planProperties = {
60
+ name: text(100, 1), timeZone: text(100, 1),
61
+ settingsOverrides: objectSchema(policyProperties, []),
62
+ workAreaCategories: workAreaCategoriesSchema,
63
+ defaultParticipantState: { anyOf: [participantStateSchema, { type: 'null' }] },
64
+ workHours: workHoursSchema,
65
+ };
66
+ export const planInputProperties = { ...planProperties, list: objectIdSchema };
67
+ export const scopeProperties = { company: text(200, 1), project: text(200, 1), list: objectIdSchema };
68
+ export const shiftProperties = {
69
+ area: objectIdSchema,
70
+ day: objectIdSchema,
71
+ title: text(100, 1), start: instantSchema, end: instantSchema,
72
+ };
73
+ export const shiftStaffingProperties = {
74
+ structures: structuresSchema,
75
+ requiredPeople: integer(0, 1000),
76
+ matchingPeople: integer(0, 1000),
77
+ };
78
+ export const breakSchema = objectSchema({ start: instantSchema, end: instantSchema });
79
+ export const assignmentProperties = {
80
+ participant: objectIdSchema,
81
+ breaks: { type: 'array', maxItems: 20, items: breakSchema },
82
+ };
83
+ export const assignmentInputSchema = { type: 'array', maxItems: 100, items: objectSchema(assignmentProperties, ['participant']) };
84
+ export const assignmentSchema = { type: 'array', maxItems: 100, items: objectSchema({ ...assignmentProperties, customer: objectIdSchema }) };
85
+ export const absenceProperties = { customer: objectIdSchema, type: { enum: ['vacation', 'sickness'] }, start: instantSchema, end: instantSchema };
86
+ export const filterProperties = { from: instantSchema, to: instantSchema, site: integer(1, Number.MAX_SAFE_INTEGER) };
87
+ //# sourceMappingURL=shiftPlanningSchemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shiftPlanningSchemas.js","sourceRoot":"","sources":["../../../src/helpers/shiftPlanningSchemas.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { Shift, ShiftAssignment, ShiftPlanningPolicy, ShiftPlanningPolicyOverrides, ShiftWorkingTime } from '@lilaquadrat/interfaces';
2
+ export declare const minuteMilliseconds = 60000;
3
+ export declare const maximumShiftMilliseconds: number;
4
+ export declare function planningValidation(path: string, message: string): never;
5
+ export declare function normalizeInstant(value: string): string;
6
+ export declare function normalizeDate(value: string): string;
7
+ export declare function interval(start: string, end: string): {
8
+ start: string;
9
+ end: string;
10
+ };
11
+ export declare function localFormatter(timeZone: string): Intl.DateTimeFormat;
12
+ export declare function localDate(instant: string, timeZone: string): string;
13
+ export declare function previousDate(date: string): string;
14
+ export declare function validatePolicy(policy?: ShiftPlanningPolicyOverrides): void;
15
+ export declare function effectivePolicy(company: ShiftPlanningPolicy | undefined, overrides: ShiftPlanningPolicyOverrides): ShiftPlanningPolicy | null;
16
+ export declare function normalizeBreaks(shift: Pick<Shift, 'start' | 'end'>, assignment: ShiftAssignment): ShiftAssignment;
17
+ export declare function workSegments(shift: Pick<Shift, 'start' | 'end'>, assignment: ShiftAssignment): Array<[number, number]>;
18
+ /** Local clock boundaries classify work, while UTC elapsed minutes prevent DST from inventing or losing time. */
19
+ export declare function calculateWorkingTime(shift: Shift, assignment: ShiftAssignment, timeZone: string, policy: ShiftPlanningPolicy): ShiftWorkingTime;
20
+ export declare function workedDates(shift: Shift, assignment: ShiftAssignment, timeZone: string): Set<string>;
21
+ /** Break-excluded work minutes keyed by local calendar date; UTC stepping keeps DST from inventing or losing time. */
22
+ export declare function workedMinutesByDate(shift: Shift, assignment: ShiftAssignment, timeZone: string): Map<string, number>;
23
+ /** Short interruptions reduce net work, but cannot satisfy break rights or reset uninterrupted working time. */
24
+ export declare function breakViolation(shift: Shift, assignment: ShiftAssignment, policy: ShiftPlanningPolicy): boolean;
@@ -0,0 +1,215 @@
1
+ import ImmutableError from '../classes/errors/Immutable.error.js';
2
+ export const minuteMilliseconds = 60000;
3
+ export const maximumShiftMilliseconds = 7 * 24 * 60 * minuteMilliseconds;
4
+ // Candidate trials revisit the same instants; bounds keep reuse independent of process lifetime.
5
+ const formatters = new Map();
6
+ const partsByFormatter = new WeakMap();
7
+ const maximumFormatters = 16;
8
+ const maximumLocalParts = 4096;
9
+ export function planningValidation(path, message) {
10
+ throw new ImmutableError('VALIDATION_FAILED', [{
11
+ instancePath: path, schemaPath: '#/shiftPlanning', keyword: 'shiftPlanning', params: {}, message,
12
+ }]);
13
+ }
14
+ export function normalizeInstant(value) {
15
+ const instant = Date.parse(value);
16
+ if (!Number.isFinite(instant))
17
+ planningValidation('', 'must be a valid instant');
18
+ const normalized = new Date(instant).toISOString();
19
+ if (!/^\d{4}-/.test(normalized))
20
+ planningValidation('', 'must normalize to a four-digit ISO year');
21
+ return normalized;
22
+ }
23
+ export function normalizeDate(value) {
24
+ const instant = new Date(`${value}T00:00:00Z`);
25
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || Number.isNaN(instant.getTime())
26
+ || instant.toISOString().slice(0, 10) !== value)
27
+ planningValidation('/date', 'must be a valid date');
28
+ return value;
29
+ }
30
+ export function interval(start, end) {
31
+ const result = { start: normalizeInstant(start), end: normalizeInstant(end) };
32
+ if (result.end <= result.start)
33
+ planningValidation('/end', 'must be after start');
34
+ return result;
35
+ }
36
+ export function localFormatter(timeZone) {
37
+ const cached = formatters.get(timeZone);
38
+ if (cached)
39
+ return cached;
40
+ try {
41
+ const formatter = new Intl.DateTimeFormat('en-GB', {
42
+ timeZone, year: 'numeric', month: '2-digit', day: '2-digit',
43
+ hour: '2-digit', minute: '2-digit', hourCycle: 'h23',
44
+ });
45
+ if (formatters.size >= maximumFormatters)
46
+ formatters.delete(formatters.keys().next().value);
47
+ formatters.set(timeZone, formatter);
48
+ return formatter;
49
+ }
50
+ catch {
51
+ return planningValidation('/timeZone', 'must be a valid IANA time zone');
52
+ }
53
+ }
54
+ function localParts(formatter, instant) {
55
+ let cache = partsByFormatter.get(formatter);
56
+ if (!cache) {
57
+ cache = new Map();
58
+ partsByFormatter.set(formatter, cache);
59
+ }
60
+ const cached = cache.get(instant);
61
+ if (cached)
62
+ return cached;
63
+ const parts = Object.fromEntries(formatter.formatToParts(instant).map((part) => [part.type, part.value]));
64
+ const result = { date: `${parts.year.padStart(4, '0')}-${parts.month}-${parts.day}`, clock: `${parts.hour}:${parts.minute}` };
65
+ if (cache.size >= maximumLocalParts)
66
+ cache.delete(cache.keys().next().value);
67
+ cache.set(instant, result);
68
+ return result;
69
+ }
70
+ export function localDate(instant, timeZone) {
71
+ return localParts(localFormatter(timeZone), Date.parse(instant)).date;
72
+ }
73
+ export function previousDate(date) {
74
+ const instant = new Date(`${date}T12:00:00Z`);
75
+ instant.setUTCDate(instant.getUTCDate() - 1);
76
+ return instant.toISOString().slice(0, 10);
77
+ }
78
+ export function validatePolicy(policy = {}) {
79
+ if (policy.daytime && policy.nighttime) {
80
+ if (policy.daytime.start === policy.daytime.end
81
+ || policy.daytime.start !== policy.nighttime.end || policy.daytime.end !== policy.nighttime.start) {
82
+ planningValidation('/settings', 'daytime and nighttime must partition the day');
83
+ }
84
+ }
85
+ if (policy.breaks) {
86
+ if (policy.breaks.enabled && !policy.breaks.requirements.length) {
87
+ planningValidation('/settings/breaks/requirements', 'enabled break rules require thresholds');
88
+ }
89
+ let previousWork = 0;
90
+ let previousBreak = 0;
91
+ for (const requirement of policy.breaks.requirements) {
92
+ if (requirement.afterWorkMinutes <= previousWork || requirement.minimumTotalBreakMinutes < previousBreak) {
93
+ planningValidation('/settings/breaks/requirements', 'thresholds must increase and required breaks must not decrease');
94
+ }
95
+ previousWork = requirement.afterWorkMinutes;
96
+ previousBreak = requirement.minimumTotalBreakMinutes;
97
+ }
98
+ }
99
+ }
100
+ export function effectivePolicy(company, overrides) {
101
+ // Whole groups replace defaults, including false; null signals setup/attention rather than silently disabling rules.
102
+ const result = { ...company, ...overrides };
103
+ if (!result.daytime || !result.nighttime || !result.maximumConsecutiveWorkingDays
104
+ || !result.maximumDailyWorkMinutes || !result.minimumRest || !result.breaks)
105
+ return null;
106
+ // Two individually valid groups may be incompatible after inheritance.
107
+ try {
108
+ validatePolicy(result);
109
+ }
110
+ catch {
111
+ return null;
112
+ }
113
+ // Booleans are omitted when false, so a missing flag is false.
114
+ return { ...result, absenceEnforcement: result.absenceEnforcement === true };
115
+ }
116
+ export function normalizeBreaks(shift, assignment) {
117
+ let previousEnd = shift.start;
118
+ const breaks = assignment.breaks.map((entry) => {
119
+ const normalized = interval(entry.start, entry.end);
120
+ if (normalized.start < previousEnd || normalized.end > shift.end) {
121
+ planningValidation('/assignments/breaks', 'breaks must be ordered, non-overlapping and inside the shift');
122
+ }
123
+ previousEnd = normalized.end;
124
+ return normalized;
125
+ });
126
+ return { ...assignment, breaks };
127
+ }
128
+ export function workSegments(shift, assignment) {
129
+ const segments = [];
130
+ let cursor = Date.parse(shift.start);
131
+ for (const entry of assignment.breaks) {
132
+ const start = Date.parse(entry.start);
133
+ if (start > cursor)
134
+ segments.push([cursor, start]);
135
+ cursor = Date.parse(entry.end);
136
+ }
137
+ if (cursor < Date.parse(shift.end))
138
+ segments.push([cursor, Date.parse(shift.end)]);
139
+ return segments;
140
+ }
141
+ /** Local clock boundaries classify work, while UTC elapsed minutes prevent DST from inventing or losing time. */
142
+ export function calculateWorkingTime(shift, assignment, timeZone, policy) {
143
+ const formatter = localFormatter(timeZone);
144
+ const workingTime = {
145
+ customer: assignment.customer.toHexString(), daytimeMinutes: 0, nighttimeMinutes: 0,
146
+ breakMinutes: assignment.breaks.reduce((total, entry) => total + (Date.parse(entry.end) - Date.parse(entry.start)) / minuteMilliseconds, 0),
147
+ };
148
+ const daytime = policy.daytime;
149
+ for (const [start, end] of workSegments(shift, assignment)) {
150
+ let cursor = start;
151
+ // Real UTC minutes keep classification correct through both skipped and repeated DST hours.
152
+ while (cursor < end) {
153
+ const next = Math.min(end, (Math.floor(cursor / minuteMilliseconds) + 1) * minuteMilliseconds);
154
+ const local = localParts(formatter, cursor);
155
+ const isDaytime = daytime.start < daytime.end
156
+ ? local.clock >= daytime.start && local.clock < daytime.end
157
+ : local.clock >= daytime.start || local.clock < daytime.end;
158
+ workingTime[isDaytime ? 'daytimeMinutes' : 'nighttimeMinutes'] += (next - cursor) / minuteMilliseconds;
159
+ cursor = next;
160
+ }
161
+ }
162
+ return workingTime;
163
+ }
164
+ export function workedDates(shift, assignment, timeZone) {
165
+ const formatter = localFormatter(timeZone);
166
+ const result = new Set();
167
+ for (const [start, end] of workSegments(shift, assignment)) {
168
+ // A six-hour step cannot skip a calendar date, including across timezone transitions.
169
+ for (let cursor = start; cursor < end; cursor += 6 * 60 * minuteMilliseconds) {
170
+ result.add(localParts(formatter, cursor).date);
171
+ }
172
+ result.add(localParts(formatter, end - 1).date);
173
+ }
174
+ return result;
175
+ }
176
+ /** Break-excluded work minutes keyed by local calendar date; UTC stepping keeps DST from inventing or losing time. */
177
+ export function workedMinutesByDate(shift, assignment, timeZone) {
178
+ const formatter = localFormatter(timeZone);
179
+ const result = new Map();
180
+ for (const [start, end] of workSegments(shift, assignment)) {
181
+ for (let cursor = start; cursor < end; cursor += minuteMilliseconds) {
182
+ const next = Math.min(end, cursor + minuteMilliseconds);
183
+ const date = localParts(formatter, cursor).date;
184
+ result.set(date, (result.get(date) ?? 0) + (next - cursor) / minuteMilliseconds);
185
+ }
186
+ }
187
+ return result;
188
+ }
189
+ /** Short interruptions reduce net work, but cannot satisfy break rights or reset uninterrupted working time. */
190
+ export function breakViolation(shift, assignment, policy) {
191
+ if (!policy.breaks.enabled)
192
+ return false;
193
+ const requirements = policy.breaks;
194
+ let cursor = Date.parse(shift.start);
195
+ let continuous = 0;
196
+ let maximumContinuous = 0;
197
+ let qualifyingBreaks = 0;
198
+ let allBreaks = 0;
199
+ for (const entry of assignment.breaks) {
200
+ continuous += (Date.parse(entry.start) - cursor) / minuteMilliseconds;
201
+ maximumContinuous = Math.max(maximumContinuous, continuous);
202
+ const duration = (Date.parse(entry.end) - Date.parse(entry.start)) / minuteMilliseconds;
203
+ allBreaks += duration;
204
+ if (duration >= requirements.minimumQualifyingBreakMinutes) {
205
+ qualifyingBreaks += duration;
206
+ continuous = 0;
207
+ }
208
+ cursor = Date.parse(entry.end);
209
+ }
210
+ maximumContinuous = Math.max(maximumContinuous, continuous + (Date.parse(shift.end) - cursor) / minuteMilliseconds);
211
+ const worked = (Date.parse(shift.end) - Date.parse(shift.start)) / minuteMilliseconds - allBreaks;
212
+ return maximumContinuous > requirements.maximumContinuousWorkMinutes
213
+ || requirements.requirements.some((entry) => worked > entry.afterWorkMinutes && qualifyingBreaks < entry.minimumTotalBreakMinutes);
214
+ }
215
+ //# sourceMappingURL=shiftPlanningTime.js.map