@lilaquadrat/studio 12.1.0 → 12.2.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 (138) 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/shiftPlanningReports.d.ts +7 -0
  25. package/lib/src/helpers/shiftPlanningReports.js +135 -0
  26. package/lib/src/helpers/shiftPlanningReports.js.map +1 -0
  27. package/lib/src/helpers/shiftPlanningSchemas.d.ts +143 -0
  28. package/lib/src/helpers/shiftPlanningSchemas.js +87 -0
  29. package/lib/src/helpers/shiftPlanningSchemas.js.map +1 -0
  30. package/lib/src/helpers/shiftPlanningTime.d.ts +24 -0
  31. package/lib/src/helpers/shiftPlanningTime.js +191 -0
  32. package/lib/src/helpers/shiftPlanningTime.js.map +1 -0
  33. package/lib/src/helpers/shiftPlanningValidation.d.ts +26 -0
  34. package/lib/src/helpers/shiftPlanningValidation.js +53 -0
  35. package/lib/src/helpers/shiftPlanningValidation.js.map +1 -0
  36. package/lib/src/helpers/structureMatch.d.ts +15 -0
  37. package/lib/src/helpers/structureMatch.js +70 -0
  38. package/lib/src/helpers/structureMatch.js.map +1 -0
  39. package/lib/src/interfaces/PaymentProviderBase.d.ts +14 -0
  40. package/lib/src/interfaces/PaymentProviderBase.js +2 -0
  41. package/lib/src/interfaces/PaymentProviderBase.js.map +1 -0
  42. package/lib/src/models/customers.model.js +10 -0
  43. package/lib/src/models/customers.model.js.map +1 -1
  44. package/lib/src/models/employeeAbsence.model.d.ts +4 -0
  45. package/lib/src/models/employeeAbsence.model.js +11 -0
  46. package/lib/src/models/employeeAbsence.model.js.map +1 -0
  47. package/lib/src/models/historySeen.model.d.ts +4 -0
  48. package/lib/src/models/historySeen.model.js +30 -0
  49. package/lib/src/models/historySeen.model.js.map +1 -0
  50. package/lib/src/models/listsParticipants.model.js +11 -0
  51. package/lib/src/models/listsParticipants.model.js.map +1 -1
  52. package/lib/src/models/permissions.model.js +28 -0
  53. package/lib/src/models/permissions.model.js.map +1 -1
  54. package/lib/src/models/shift.model.d.ts +4 -0
  55. package/lib/src/models/shift.model.js +17 -0
  56. package/lib/src/models/shift.model.js.map +1 -0
  57. package/lib/src/models/shiftPlan.model.d.ts +4 -0
  58. package/lib/src/models/shiftPlan.model.js +14 -0
  59. package/lib/src/models/shiftPlan.model.js.map +1 -0
  60. package/lib/src/models/shiftPlanDays.model.d.ts +4 -0
  61. package/lib/src/models/shiftPlanDays.model.js +15 -0
  62. package/lib/src/models/shiftPlanDays.model.js.map +1 -0
  63. package/lib/src/models/shiftPlanningSettings.model.d.ts +4 -0
  64. package/lib/src/models/shiftPlanningSettings.model.js +10 -0
  65. package/lib/src/models/shiftPlanningSettings.model.js.map +1 -0
  66. package/lib/src/models/workAreas.model.d.ts +4 -0
  67. package/lib/src/models/workAreas.model.js +12 -0
  68. package/lib/src/models/workAreas.model.js.map +1 -0
  69. package/lib/src/services/customers.service.d.ts +11 -0
  70. package/lib/src/services/customers.service.js +27 -0
  71. package/lib/src/services/customers.service.js.map +1 -1
  72. package/lib/src/services/employeeAbsences.service.d.ts +18 -0
  73. package/lib/src/services/employeeAbsences.service.js +57 -0
  74. package/lib/src/services/employeeAbsences.service.js.map +1 -0
  75. package/lib/src/services/history.service.d.ts +15 -0
  76. package/lib/src/services/history.service.js +36 -0
  77. package/lib/src/services/history.service.js.map +1 -1
  78. package/lib/src/services/historySeen.service.d.ts +32 -0
  79. package/lib/src/services/historySeen.service.js +66 -0
  80. package/lib/src/services/historySeen.service.js.map +1 -0
  81. package/lib/src/services/listParticipants.service.d.ts +143 -12
  82. package/lib/src/services/listParticipants.service.js +145 -6
  83. package/lib/src/services/listParticipants.service.js.map +1 -1
  84. package/lib/src/services/lists.service.d.ts +9 -0
  85. package/lib/src/services/lists.service.js +28 -0
  86. package/lib/src/services/lists.service.js.map +1 -1
  87. package/lib/src/services/paymentProviders.service.d.ts +5 -0
  88. package/lib/src/services/paymentProviders.service.js +10 -0
  89. package/lib/src/services/paymentProviders.service.js.map +1 -1
  90. package/lib/src/services/shiftPlanDays.service.d.ts +22 -0
  91. package/lib/src/services/shiftPlanDays.service.js +80 -0
  92. package/lib/src/services/shiftPlanDays.service.js.map +1 -0
  93. package/lib/src/services/shiftPlanningSettings.service.d.ts +11 -0
  94. package/lib/src/services/shiftPlanningSettings.service.js +27 -0
  95. package/lib/src/services/shiftPlanningSettings.service.js.map +1 -0
  96. package/lib/src/services/shiftPlans.service.d.ts +24 -0
  97. package/lib/src/services/shiftPlans.service.js +139 -0
  98. package/lib/src/services/shiftPlans.service.js.map +1 -0
  99. package/lib/src/services/shifts.service.d.ts +29 -0
  100. package/lib/src/services/shifts.service.js +260 -0
  101. package/lib/src/services/shifts.service.js.map +1 -0
  102. package/lib/src/services/structures.service.d.ts +2 -0
  103. package/lib/src/services/structures.service.js +7 -0
  104. package/lib/src/services/structures.service.js.map +1 -1
  105. package/lib/src/services/workAreas.service.d.ts +18 -0
  106. package/lib/src/services/workAreas.service.js +67 -0
  107. package/lib/src/services/workAreas.service.js.map +1 -0
  108. package/lib/tests/buildProductName.spec.d.ts +1 -0
  109. package/lib/tests/buildProductName.spec.js +27 -0
  110. package/lib/tests/buildProductName.spec.js.map +1 -0
  111. package/lib/tests/customersServiceStructures.spec.d.ts +1 -0
  112. package/lib/tests/customersServiceStructures.spec.js +146 -0
  113. package/lib/tests/customersServiceStructures.spec.js.map +1 -0
  114. package/lib/tests/historySeenService.spec.d.ts +1 -0
  115. package/lib/tests/historySeenService.spec.js +159 -0
  116. package/lib/tests/historySeenService.spec.js.map +1 -0
  117. package/lib/tests/listParticipantsPlanning.spec.d.ts +1 -0
  118. package/lib/tests/listParticipantsPlanning.spec.js +83 -0
  119. package/lib/tests/listParticipantsPlanning.spec.js.map +1 -0
  120. package/lib/tests/listParticipantsServiceStructures.spec.d.ts +1 -0
  121. package/lib/tests/listParticipantsServiceStructures.spec.js +176 -0
  122. package/lib/tests/listParticipantsServiceStructures.spec.js.map +1 -0
  123. package/lib/tests/listsServiceCreateExternalProducts.spec.d.ts +1 -0
  124. package/lib/tests/listsServiceCreateExternalProducts.spec.js +73 -0
  125. package/lib/tests/listsServiceCreateExternalProducts.spec.js.map +1 -0
  126. package/lib/tests/paymentProviders.spec.d.ts +1 -0
  127. package/lib/tests/paymentProviders.spec.js +119 -0
  128. package/lib/tests/paymentProviders.spec.js.map +1 -0
  129. package/lib/tests/paymentProvidersServiceGetProduct.spec.d.ts +1 -0
  130. package/lib/tests/paymentProvidersServiceGetProduct.spec.js +33 -0
  131. package/lib/tests/paymentProvidersServiceGetProduct.spec.js.map +1 -0
  132. package/lib/tests/shiftPlanning.spec.d.ts +1 -0
  133. package/lib/tests/shiftPlanning.spec.js +835 -0
  134. package/lib/tests/shiftPlanning.spec.js.map +1 -0
  135. package/lib/tests/structureMatch.spec.d.ts +1 -0
  136. package/lib/tests/structureMatch.spec.js +64 -0
  137. package/lib/tests/structureMatch.spec.js.map +1 -0
  138. package/package.json +2 -2
@@ -0,0 +1,80 @@
1
+ import { ObjectId } from 'mongodb';
2
+ import Immutable from '../Immutable.class.js';
3
+ import ImmutableError from '../classes/errors/Immutable.error.js';
4
+ import DayModel from '../models/shiftPlanDays.model.js';
5
+ import ShiftPlansService from './shiftPlans.service.js';
6
+ import ShiftsService from './shifts.service.js';
7
+ import WorkAreasService from './workAreas.service.js';
8
+ import { assertUniquePlanningIds, planningPage, planningPagination } from '../helpers/shiftPlanningValidation.js';
9
+ import { planningValidation } from '../helpers/shiftPlanningTime.js';
10
+ /** A day is the scheduling container under a plan; its work-area links become the columns shifts are placed in. */
11
+ export class ShiftPlanDaysService extends Immutable {
12
+ constructor() {
13
+ super(...arguments);
14
+ this.model = DayModel;
15
+ }
16
+ async configuration(input, options) {
17
+ const links = input.areas ?? [];
18
+ assertUniquePlanningIds(links.map((link) => link.area.toHexString()), '/areas');
19
+ const areas = await WorkAreasService.areasForPlanning(links.map((link) => link.area), options);
20
+ if (areas.length !== new Set(links.map((link) => link.area.toHexString())).size)
21
+ planningValidation('/areas', 'work areas must exist in this project');
22
+ return { date: input.date, ...(input.name ? { name: input.name } : {}), ...(links.length ? { areas: links } : {}) };
23
+ }
24
+ async createDay(plan, input, options) {
25
+ await ShiftPlansService.planForScheduling(plan, options);
26
+ const existing = await this.model.db.findOne({ company: options.company, project: options.project, plan, date: input.date });
27
+ if (existing)
28
+ planningValidation('/date', 'must be unique within the plan');
29
+ const created = await this.create({ _id: new ObjectId(), company: options.company, project: options.project, plan, ...await this.configuration(input, options) }, options.user, options.app);
30
+ return (await this.singleDay(plan, created._id, options));
31
+ }
32
+ async singleDay(plan, day, options) {
33
+ return this.model.db.findOne({ _id: day, company: options.company, project: options.project, plan });
34
+ }
35
+ async dayForScheduling(plan, day, options, required = true) {
36
+ const document = await this.singleDay(plan, day, options);
37
+ if (!document && required)
38
+ throw new ImmutableError('SHIFT_PLAN_DAY_NOT_FOUND');
39
+ return document;
40
+ }
41
+ async changeDay(plan, day, input, options) {
42
+ const previous = await this.dayForScheduling(plan, day, options);
43
+ const duplicate = await this.model.db.findOne({ _id: { $ne: day }, company: options.company, project: options.project, plan, date: input.date });
44
+ if (duplicate)
45
+ planningValidation('/date', 'must be unique within the plan');
46
+ await ShiftPlansService.planForScheduling(plan, options);
47
+ const configuration = await this.configuration(input, options);
48
+ await ShiftsService.assertDayReferences(previous, configuration, options);
49
+ await this.immutable({ _id: day, company: options.company, project: options.project, plan }, configuration, options.user, 'change', options.app);
50
+ return (await this.singleDay(plan, day, options));
51
+ }
52
+ async removeDay(plan, day, options) {
53
+ await this.dayForScheduling(plan, day, options);
54
+ if (await ShiftsService.hasDayShifts(day, options))
55
+ throw new ImmutableError('SHIFT_PLAN_DAY_IN_USE');
56
+ await this.delete(day, options.user, options.app);
57
+ }
58
+ async listDays(plan, filters, options) {
59
+ await ShiftPlansService.planForScheduling(plan, options);
60
+ const query = { company: options.company, project: options.project, plan };
61
+ const { site, limit, skip } = planningPagination(filters);
62
+ const all = await this.model.db.countDocuments(query);
63
+ const days = await this.model.db.find(query).sort({ date: 1, _id: 1 }).skip(skip).limit(limit).toArray();
64
+ return planningPage(days, all, site, limit);
65
+ }
66
+ daysForPlans(plans, options) {
67
+ if (!plans.length)
68
+ return Promise.resolve([]);
69
+ return this.model.db.find({ company: options.company, project: options.project, plan: { $in: plans } }).sort({ date: 1, _id: 1 }).toArray();
70
+ }
71
+ plansInRange(from, to, options) {
72
+ const date = { ...(from ? { $gte: from } : {}), ...(to ? { $lt: to } : {}) };
73
+ return this.model.db.distinct('plan', { company: options.company, project: options.project, date });
74
+ }
75
+ hasWorkAreaReference(area, options) {
76
+ return this.model.db.countDocuments({ company: options.company, project: options.project, 'areas.area': area }).then((count) => count > 0);
77
+ }
78
+ }
79
+ export default new ShiftPlanDaysService();
80
+ //# sourceMappingURL=shiftPlanDays.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shiftPlanDays.service.js","sourceRoot":"","sources":["../../../src/services/shiftPlanDays.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { ShiftPlanningSettings, ShiftPlanningSettingsInput, ShiftPlanningProjectOptions } from '@lilaquadrat/interfaces';
2
+ import Immutable from '../Immutable.class.js';
3
+ /** Company defaults remain separate from plan overrides so changing a default does not erase explicit choices. */
4
+ export declare class ShiftPlanningSettingsService extends Immutable<ShiftPlanningSettings> {
5
+ model: import("../classes/shiftPlanningModel.class.js").default<ShiftPlanningSettings>;
6
+ singleSettings(options: ShiftPlanningProjectOptions): Promise<ShiftPlanningSettings | null>;
7
+ /** Existing schedules are retained when stricter defaults introduce attention items. */
8
+ changeSettings(input: ShiftPlanningSettingsInput, options: ShiftPlanningProjectOptions): Promise<ShiftPlanningSettings>;
9
+ }
10
+ declare const _default: ShiftPlanningSettingsService;
11
+ export default _default;
@@ -0,0 +1,27 @@
1
+ import Immutable from '../Immutable.class.js';
2
+ import SettingsModel from '../models/shiftPlanningSettings.model.js';
3
+ import { validatePolicy } from '../helpers/shiftPlanningTime.js';
4
+ /** Company defaults remain separate from plan overrides so changing a default does not erase explicit choices. */
5
+ export class ShiftPlanningSettingsService extends Immutable {
6
+ constructor() {
7
+ super(...arguments);
8
+ this.model = SettingsModel;
9
+ }
10
+ async singleSettings(options) {
11
+ return this.model.db.findOne({ company: options.company, project: options.project });
12
+ }
13
+ /** Existing schedules are retained when stricter defaults introduce attention items. */
14
+ async changeSettings(input, options) {
15
+ validatePolicy(input?.settings);
16
+ const previous = await this.singleSettings(options);
17
+ if (previous) {
18
+ await this.immutable({ _id: previous._id, company: options.company, project: options.project }, input, options.user, 'change', options.app);
19
+ }
20
+ else {
21
+ await this.create({ company: options.company, project: options.project, settings: input.settings }, options.user, options.app);
22
+ }
23
+ return (await this.singleSettings(options));
24
+ }
25
+ }
26
+ export default new ShiftPlanningSettingsService();
27
+ //# sourceMappingURL=shiftPlanningSettings.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shiftPlanningSettings.service.js","sourceRoot":"","sources":["../../../src/services/shiftPlanningSettings.service.ts"],"names":[],"mappings":"AACA,OAAO,SAAS,MAAM,uBAAuB,CAAC;AAC9C,OAAO,aAAa,MAAM,0CAA0C,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEjE,kHAAkH;AAClH,MAAM,OAAO,4BAA6B,SAAQ,SAAgC;IAAlF;;QACE,UAAK,GAAG,aAAa,CAAC;IAiBxB,CAAC;IAfC,KAAK,CAAC,cAAc,CAAC,OAAoC;QACvD,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACvF,CAAC;IAED,wFAAwF;IACxF,KAAK,CAAC,cAAc,CAAC,KAAiC,EAAE,OAAoC;QAC1F,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QAChC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QACpD,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9I,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;QACjI,CAAC;QACD,OAAO,CAAC,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAE,CAAC;IAC/C,CAAC;CACF;AAED,eAAe,IAAI,4BAA4B,EAAE,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { ObjectId, WithId } from 'mongodb';
2
+ import type { ShiftPlan, ShiftPlanChangeInput, ShiftPlanCreateInput, ShiftPlanFilters, ShiftPlanResponse, ShiftPlanningPage, ShiftPlanningProjectOptions, ShiftPlanningCompanyOptions } from '@lilaquadrat/interfaces';
3
+ import Immutable from '../Immutable.class.js';
4
+ /** A plan is the top-level scheduling unit; its employee list is an optional, reusable reference. Days are separate documents. */
5
+ export declare class ShiftPlansService extends Immutable<ShiftPlan> {
6
+ model: import("../classes/shiftPlanningModel.class.js").default<ShiftPlan>;
7
+ private employeeList;
8
+ /** One binding per work area, each category must belong to the linked employee list. */
9
+ private workAreaCategories;
10
+ private configuration;
11
+ /** Name/date overlap is intentional: departments and successive weeks may reuse the same pool. */
12
+ createPlan(input: ShiftPlanCreateInput, options: ShiftPlanningProjectOptions): Promise<ShiftPlanResponse>;
13
+ singlePlan(plan: ObjectId, options: ShiftPlanningProjectOptions): Promise<ShiftPlanResponse | null>;
14
+ changePlan(plan: ObjectId, input: ShiftPlanChangeInput, options: ShiftPlanningProjectOptions): Promise<ShiftPlanResponse>;
15
+ listPlans(filters: ShiftPlanFilters, options: ShiftPlanningProjectOptions): Promise<ShiftPlanningPage<ShiftPlanResponse>>;
16
+ planForScheduling(plan: ObjectId, options: ShiftPlanningProjectOptions): Promise<WithId<ShiftPlan>>;
17
+ planForScheduling(plan: ObjectId, options: ShiftPlanningProjectOptions, required: false): Promise<WithId<ShiftPlan> | null>;
18
+ plansForProject(options: ShiftPlanningProjectOptions, list?: ObjectId): Promise<WithId<ShiftPlan>[]>;
19
+ /** Labor checks may span projects; callers return only diagnostics, never these other plans' details. */
20
+ plansForCompany(plans: ObjectId[], options: ShiftPlanningCompanyOptions): Promise<WithId<ShiftPlan>[]>;
21
+ private responses;
22
+ }
23
+ declare const _default: ShiftPlansService;
24
+ export default _default;
@@ -0,0 +1,139 @@
1
+ import Immutable from '../Immutable.class.js';
2
+ import ImmutableError from '../classes/errors/Immutable.error.js';
3
+ import PlanModel from '../models/shiftPlan.model.js';
4
+ import ListsService from './lists.service.js';
5
+ import WorkAreasService from './workAreas.service.js';
6
+ import ShiftPlanDaysService from './shiftPlanDays.service.js';
7
+ import ShiftsService from './shifts.service.js';
8
+ import SettingsService from './shiftPlanningSettings.service.js';
9
+ import { effectivePolicy, localFormatter, normalizeDate, planningValidation, validatePolicy } from '../helpers/shiftPlanningTime.js';
10
+ import { assertUniquePlanningIds, planningPage, planningPagination, planningSearch } from '../helpers/shiftPlanningValidation.js';
11
+ /** A plan is the top-level scheduling unit; its employee list is an optional, reusable reference. Days are separate documents. */
12
+ export class ShiftPlansService extends Immutable {
13
+ constructor() {
14
+ super(...arguments);
15
+ this.model = PlanModel;
16
+ }
17
+ async employeeList(list, options) {
18
+ if (!list)
19
+ return undefined;
20
+ const result = await ListsService.singleForPlanning(list, options);
21
+ if (!result)
22
+ throw new ImmutableError('LIST_NOT_FOUND');
23
+ return result;
24
+ }
25
+ /** One binding per work area, each category must belong to the linked employee list. */
26
+ async workAreaCategories(input, list, options) {
27
+ if (input.workAreaCategories === undefined)
28
+ return undefined;
29
+ const entries = input.workAreaCategories.filter((entry) => (entry.categoryIds ?? []).length);
30
+ if (!entries.length)
31
+ return [];
32
+ const areaIds = entries.map((entry) => entry.area);
33
+ assertUniquePlanningIds(areaIds.map((area) => area.toHexString()), '/workAreaCategories');
34
+ const areas = await WorkAreasService.areasForPlanning(areaIds, options);
35
+ if (areas.length !== new Set(areaIds.map((area) => area.toHexString())).size)
36
+ planningValidation('/workAreaCategories', 'work areas must exist in this project');
37
+ const listCategoryIds = new Set((list?.categories ?? []).map((category) => category._id.toHexString()));
38
+ entries.forEach((entry) => {
39
+ const ids = entry.categoryIds ?? [];
40
+ assertUniquePlanningIds(ids.map((category) => category.toHexString()), '/workAreaCategories/categoryIds');
41
+ if (!list)
42
+ planningValidation('/workAreaCategories/categoryIds', 'categories require a linked employee list');
43
+ if (ids.some((category) => !listCategoryIds.has(category.toHexString())))
44
+ planningValidation('/workAreaCategories/categoryIds', 'categories must belong to this employee list');
45
+ });
46
+ return entries;
47
+ }
48
+ async configuration(input, list, options) {
49
+ localFormatter(input.timeZone);
50
+ const settings = await SettingsService.singleSettings(options);
51
+ const overrides = input.settingsOverrides ?? {};
52
+ validatePolicy({ ...settings?.settings, ...overrides });
53
+ const workAreaCategories = await this.workAreaCategories(input, list, options);
54
+ if (input.workHours !== undefined && input.workHours.start >= input.workHours.end)
55
+ planningValidation('/workHours/end', 'end must be after start');
56
+ return {
57
+ name: input.name, timeZone: input.timeZone, settingsOverrides: overrides,
58
+ ...(workAreaCategories !== undefined ? { workAreaCategories } : {}),
59
+ ...(input.defaultParticipantState !== undefined ? { defaultParticipantState: input.defaultParticipantState } : {}),
60
+ ...(input.workHours !== undefined ? { workHours: input.workHours } : {}),
61
+ };
62
+ }
63
+ /** Name/date overlap is intentional: departments and successive weeks may reuse the same pool. */
64
+ async createPlan(input, options) {
65
+ const { list } = input;
66
+ const listDocument = await this.employeeList(list, options);
67
+ const configuration = await this.configuration(input, listDocument, options);
68
+ const created = await this.create({ company: options.company, project: options.project, ...(list ? { list } : {}), ...configuration }, options.user, options.app);
69
+ return (await this.singlePlan(created._id, options));
70
+ }
71
+ async singlePlan(plan, options) {
72
+ const document = await this.planForScheduling(plan, options, false);
73
+ if (!document)
74
+ return null;
75
+ const response = (await this.responses([document], options))[0];
76
+ // The plan detail is edited as a whole, so one request carries its schedule instead of a separate shifts list.
77
+ const shifts = await ShiftsService.listShifts(plan, {}, options);
78
+ return { ...response, shifts: shifts.data };
79
+ }
80
+ async changePlan(plan, input, options) {
81
+ await this.planForScheduling(plan, options);
82
+ const { list } = input;
83
+ const listDocument = await this.employeeList(list, options);
84
+ const configuration = await this.configuration(input, listDocument, options);
85
+ await ShiftsService.assertPlanTimeZone(plan, configuration.timeZone, options);
86
+ await this.immutable({ _id: plan, company: options.company, project: options.project }, { ...configuration, ...(list ? { list } : {}) }, options.user, 'change', options.app);
87
+ return (await this.singlePlan(plan, options));
88
+ }
89
+ async listPlans(filters, options) {
90
+ const from = filters.from ? normalizeDate(filters.from) : undefined;
91
+ const to = filters.to ? normalizeDate(filters.to) : undefined;
92
+ if (from && to && from >= to)
93
+ planningValidation('/to', 'must be after from');
94
+ const query = { company: options.company, project: options.project };
95
+ if (filters.list)
96
+ query.list = filters.list;
97
+ const search = planningSearch(filters.search);
98
+ if (search)
99
+ query.name = { $regex: search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), $options: 'i' };
100
+ if (from || to) {
101
+ const plans = await ShiftPlanDaysService.plansInRange(from, to, options);
102
+ query._id = { $in: plans };
103
+ }
104
+ const { site, limit, skip } = planningPagination(filters);
105
+ const all = await this.model.db.countDocuments(query);
106
+ const plans = await this.model.db.find(query).sort({ name: 1, _id: 1 }).skip(skip).limit(limit).toArray();
107
+ return planningPage(await this.responses(plans, options), all, site, limit);
108
+ }
109
+ async planForScheduling(plan, options, required = true) {
110
+ const document = await this.model.db.findOne({ _id: plan, company: options.company, project: options.project });
111
+ if (!document && required)
112
+ throw new ImmutableError('SHIFT_PLAN_NOT_FOUND');
113
+ return document;
114
+ }
115
+ plansForProject(options, list) {
116
+ return this.model.db.find({ company: options.company, project: options.project, ...(list ? { list } : {}) }).toArray();
117
+ }
118
+ /** Labor checks may span projects; callers return only diagnostics, never these other plans' details. */
119
+ plansForCompany(plans, options) {
120
+ return this.model.db.find({ company: options.company, _id: { $in: plans } }).toArray();
121
+ }
122
+ async responses(plans, options) {
123
+ const settings = await SettingsService.singleSettings(options);
124
+ const days = await ShiftPlanDaysService.daysForPlans(plans.map((plan) => plan._id), options);
125
+ const areas = await WorkAreasService.areasForPlanning(days.flatMap((day) => (day.areas ?? []).map((link) => link.area)), options);
126
+ const listIds = plans.map((plan) => plan.list).filter((list) => !!list);
127
+ const lists = listIds.length ? await ListsService.listsForPlanning(listIds, options) : [];
128
+ return plans.map((plan) => {
129
+ const planDays = days.filter((day) => day.plan.equals(plan._id));
130
+ const list = plan.list ? lists.find((document) => document._id.equals(plan.list)) : undefined;
131
+ return { ...plan, days: planDays, effectiveSettings: effectivePolicy(settings?.settings, plan.settingsOverrides),
132
+ workAreas: areas.filter((area) => planDays.some((day) => (day.areas ?? []).some((link) => link.area.equals(area._id)))),
133
+ ...(list?.categories?.length ? { categories: list.categories } : {}),
134
+ };
135
+ });
136
+ }
137
+ }
138
+ export default new ShiftPlansService();
139
+ //# sourceMappingURL=shiftPlans.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shiftPlans.service.js","sourceRoot":"","sources":["../../../src/services/shiftPlans.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { ObjectId, WithId } from 'mongodb';
2
+ import type { MemberShift, MemberShiftFilters, Shift, ShiftAssignmentsInput, ShiftCreateInput, ShiftInput, ShiftPlanDay, ShiftPlanningFilters, ShiftPlanningPage, ShiftResponse, ShiftPlanningProjectOptions } from '@lilaquadrat/interfaces';
3
+ import Immutable from '../Immutable.class.js';
4
+ /** Scheduling owns employee allocation; work areas and plans only supply reusable identity/configuration. */
5
+ export declare class ShiftsService extends Immutable<Shift> {
6
+ model: import("../classes/shiftPlanningModel.class.js").default<Shift>;
7
+ private normalizedTime;
8
+ private document;
9
+ private resolvedAssignments;
10
+ createShift(plan: ObjectId, input: ShiftCreateInput, options: ShiftPlanningProjectOptions): Promise<ShiftResponse>;
11
+ singleShift(plan: ObjectId, shift: ObjectId, options: ShiftPlanningProjectOptions): Promise<ShiftResponse | null>;
12
+ listShifts(plan: ObjectId, filters: ShiftPlanningFilters, options: ShiftPlanningProjectOptions): Promise<ShiftPlanningPage<ShiftResponse>>;
13
+ /** Absolute break times survive edits; moving a shift outside them must be resolved explicitly by the planner. */
14
+ changeShift(plan: ObjectId, shift: ObjectId, input: ShiftInput, options: ShiftPlanningProjectOptions): Promise<ShiftResponse>;
15
+ removeShift(plan: ObjectId, shift: ObjectId, options: ShiftPlanningProjectOptions): Promise<void>;
16
+ replaceAssignments(plan: ObjectId, shift: ObjectId, input: ShiftAssignmentsInput, options: ShiftPlanningProjectOptions): Promise<ShiftResponse>;
17
+ hasWorkAreaAssignments(area: ObjectId, options: ShiftPlanningProjectOptions): Promise<boolean>;
18
+ /** A plan cannot change its time zone while existing shifts would no longer match their day's local date. */
19
+ assertPlanTimeZone(plan: ObjectId, timeZone: string, options: ShiftPlanningProjectOptions): Promise<void>;
20
+ hasDayShifts(day: ObjectId, options: ShiftPlanningProjectOptions): Promise<boolean>;
21
+ /** A day cannot drop an area or change its date while shifts still reference it. */
22
+ assertDayReferences(previous: WithId<ShiftPlanDay>, configuration: Pick<ShiftPlanDay, 'date' | 'areas'>, options: ShiftPlanningProjectOptions): Promise<void>;
23
+ myShifts(customer: ObjectId, filters: MemberShiftFilters, options: ShiftPlanningProjectOptions): Promise<ShiftPlanningPage<MemberShift>>;
24
+ private reportContext;
25
+ private reports;
26
+ private validateSchedule;
27
+ }
28
+ declare const _default: ShiftsService;
29
+ export default _default;
@@ -0,0 +1,260 @@
1
+ import { ObjectId } from 'mongodb';
2
+ import Immutable from '../Immutable.class.js';
3
+ import ImmutableError from '../classes/errors/Immutable.error.js';
4
+ import ShiftModel from '../models/shift.model.js';
5
+ import ShiftPlansService from './shiftPlans.service.js';
6
+ import ShiftPlanDaysService from './shiftPlanDays.service.js';
7
+ import WorkAreasService from './workAreas.service.js';
8
+ import SettingsService from './shiftPlanningSettings.service.js';
9
+ import AbsencesService from './employeeAbsences.service.js';
10
+ import ListsService from './lists.service.js';
11
+ import ParticipantsService from './listParticipants.service.js';
12
+ import CustomersService from './customers.service.js';
13
+ import { interval, localDate, maximumShiftMilliseconds, normalizeBreaks, planningValidation } from '../helpers/shiftPlanningTime.js';
14
+ import { assertUniquePlanningIds, equalAssignment, equalAssignmentSets, equalBreaks, planningIntervalFilter, planningPage, planningPagination, violationIdentity, } from '../helpers/shiftPlanningValidation.js';
15
+ import { boundedPlanningInstant, reportShift, scheduleWindowMilliseconds, unavailableParticipantStates } from '../helpers/shiftPlanningReports.js';
16
+ /** Scheduling owns employee allocation; work areas and plans only supply reusable identity/configuration. */
17
+ export class ShiftsService extends Immutable {
18
+ constructor() {
19
+ super(...arguments);
20
+ this.model = ShiftModel;
21
+ }
22
+ normalizedTime(input, plan, day) {
23
+ const normalized = { ...input, ...interval(input.start, input.end) };
24
+ if (Date.parse(normalized.end) - Date.parse(normalized.start) > maximumShiftMilliseconds)
25
+ planningValidation('/end', 'a shift must not exceed seven days');
26
+ if (localDate(normalized.start, plan.timeZone) !== day.date) {
27
+ planningValidation('/day', 'must match the local start date of its day');
28
+ }
29
+ if (!(day.areas ?? []).some((link) => link.area.equals(input.area))) {
30
+ planningValidation('/area', 'must be a work area linked to this day');
31
+ }
32
+ return normalized;
33
+ }
34
+ async document(plan, shift, options) {
35
+ await ShiftPlansService.planForScheduling(plan, options);
36
+ const document = await this.model.db.findOne({ _id: shift, company: options.company, project: options.project, plan });
37
+ if (!document)
38
+ throw new ImmutableError('SHIFT_NOT_FOUND');
39
+ return document;
40
+ }
41
+ async resolvedAssignments(inputs, shift, options, previous) {
42
+ assertUniquePlanningIds(inputs.map((entry) => entry.participant.toHexString()), '/assignments');
43
+ const { list } = shift;
44
+ if (inputs.length && !list)
45
+ planningValidation('/assignments', 'assignments require a linked employee list');
46
+ const assignments = [];
47
+ for (const input of inputs) {
48
+ const breaks = (input.breaks ?? []).map((entry) => interval(entry.start, entry.end));
49
+ const existing = previous?.assignments.find((entry) => entry.participant.equals(input.participant));
50
+ // Retain the original customer anchor when the list entry has since been canceled or removed.
51
+ if (existing && equalBreaks(existing.breaks, breaks)) {
52
+ assignments.push(normalizeBreaks(shift, existing));
53
+ continue;
54
+ }
55
+ const participant = await ParticipantsService.singleForPlanning(list, input.participant, options);
56
+ if (!participant || unavailableParticipantStates.has(participant.state))
57
+ planningValidation('/assignments/participant', 'participant is not available in this list');
58
+ const customer = (await CustomersService.peopleForPlanning([participant.user], options.company))[0];
59
+ if (!customer)
60
+ planningValidation('/assignments/participant', 'participant must identify a person in this company');
61
+ assignments.push(normalizeBreaks(shift, { participant: input.participant, customer: customer._id, breaks }));
62
+ }
63
+ // Several list entries can represent one person; participant identity alone cannot prevent double allocation.
64
+ assertUniquePlanningIds(assignments.map((entry) => entry.customer.toHexString()), '/assignments');
65
+ return assignments;
66
+ }
67
+ async createShift(plan, input, options) {
68
+ const configuration = await ShiftPlansService.planForScheduling(plan, options);
69
+ const day = await ShiftPlanDaysService.dayForScheduling(plan, input.day, options);
70
+ const list = configuration.list;
71
+ const { assignments, ...time } = input;
72
+ const candidate = { ...this.normalizedTime(time, configuration, day), _id: new ObjectId(),
73
+ company: options.company, project: options.project, plan, ...(list ? { list } : {}), assignments: [],
74
+ };
75
+ candidate.assignments = await this.resolvedAssignments(assignments ?? [], candidate, options);
76
+ await this.validateSchedule(candidate, options);
77
+ const { _id, ...data } = candidate;
78
+ const created = await this.create(data, options.user, options.app);
79
+ return (await this.singleShift(plan, created._id, options));
80
+ }
81
+ async singleShift(plan, shift, options) {
82
+ await ShiftPlansService.planForScheduling(plan, options);
83
+ const document = await this.model.db.findOne({ _id: shift, company: options.company, project: options.project, plan });
84
+ return document ? (await this.reports([document], options, undefined, undefined, plan))[0] : null;
85
+ }
86
+ async listShifts(plan, filters, options) {
87
+ await ShiftPlansService.planForScheduling(plan, options);
88
+ const query = { company: options.company, project: options.project, plan, ...planningIntervalFilter(filters) };
89
+ if (filters.area)
90
+ query.area = filters.area;
91
+ const { site, limit, skip } = planningPagination(filters);
92
+ const all = await this.model.db.countDocuments(query);
93
+ const documents = await this.model.db.find(query).sort({ start: 1, _id: 1 }).skip(skip).limit(limit).toArray();
94
+ return planningPage(await this.reports(documents, options, undefined, undefined, plan), all, site, limit);
95
+ }
96
+ /** Absolute break times survive edits; moving a shift outside them must be resolved explicitly by the planner. */
97
+ async changeShift(plan, shift, input, options) {
98
+ const previous = await this.document(plan, shift, options);
99
+ const configuration = await ShiftPlansService.planForScheduling(plan, options);
100
+ const day = await ShiftPlanDaysService.dayForScheduling(plan, input.day, options);
101
+ const time = this.normalizedTime(input, configuration, day);
102
+ const candidate = { ...previous, ...time };
103
+ candidate.assignments = candidate.assignments.map((assignment) => normalizeBreaks(candidate, assignment));
104
+ await this.validateSchedule(candidate, options, previous);
105
+ const list = configuration.list;
106
+ await this.immutable({ _id: shift, company: options.company, project: options.project, plan }, { ...time, ...(list ? { list } : {}) }, options.user, 'change', options.app);
107
+ return (await this.singleShift(plan, shift, options));
108
+ }
109
+ async removeShift(plan, shift, options) {
110
+ await this.document(plan, shift, options);
111
+ await this.delete(shift, options.user, options.app);
112
+ }
113
+ async replaceAssignments(plan, shift, input, options) {
114
+ const previous = await this.document(plan, shift, options);
115
+ const configuration = await ShiftPlansService.planForScheduling(plan, options);
116
+ const list = configuration.list;
117
+ // The plan owns the employee list; a shift persisted before the plan was linked must resolve against it.
118
+ const target = { ...previous, ...(list ? { list } : {}) };
119
+ const assignments = await this.resolvedAssignments(input.assignments, target, options, previous);
120
+ if (!equalAssignmentSets(assignments, previous.assignments)) {
121
+ await this.validateSchedule({ ...target, assignments }, options, previous);
122
+ await this.immutable({ _id: shift, company: options.company, project: options.project, plan }, { assignments, ...(list ? { list } : {}) }, options.user, 'change', options.app, undefined, undefined, 'assignments');
123
+ }
124
+ return (await this.singleShift(plan, shift, options));
125
+ }
126
+ async hasWorkAreaAssignments(area, options) {
127
+ return await this.model.db.countDocuments({ company: options.company, project: options.project, area }) > 0;
128
+ }
129
+ /** A plan cannot change its time zone while existing shifts would no longer match their day's local date. */
130
+ async assertPlanTimeZone(plan, timeZone, options) {
131
+ const shifts = await this.model.db.find({ company: options.company, project: options.project, plan }).toArray();
132
+ if (!shifts.length)
133
+ return;
134
+ const days = await ShiftPlanDaysService.daysForPlans([plan], options);
135
+ if (shifts.some((shift) => {
136
+ const day = days.find((entry) => entry._id.equals(shift.day));
137
+ return !day || localDate(shift.start, timeZone) !== day.date;
138
+ }))
139
+ throw new ImmutableError('SHIFT_PLAN_REFERENCES_IN_USE');
140
+ }
141
+ async hasDayShifts(day, options) {
142
+ return await this.model.db.countDocuments({ company: options.company, project: options.project, day }) > 0;
143
+ }
144
+ /** A day cannot drop an area or change its date while shifts still reference it. */
145
+ async assertDayReferences(previous, configuration, options) {
146
+ const plan = await ShiftPlansService.planForScheduling(previous.plan, options);
147
+ const shifts = await this.model.db.find({ company: options.company, project: options.project, day: previous._id }).toArray();
148
+ if (shifts.some((shift) => localDate(shift.start, plan.timeZone) !== configuration.date
149
+ || !(configuration.areas ?? []).some((link) => link.area.equals(shift.area)))) {
150
+ throw new ImmutableError('SHIFT_PLAN_REFERENCES_IN_USE');
151
+ }
152
+ }
153
+ async myShifts(customer, filters, options) {
154
+ if (!await CustomersService.memberForPlanning(customer, options))
155
+ throw new ImmutableError('MEMBER_NOT_ALLOWED');
156
+ const plans = await ShiftPlansService.plansForProject(options, filters.list);
157
+ const lists = await ListsService.listsForPlanning(plans.map((plan) => plan.list).filter((list) => list !== undefined), options);
158
+ const days = await ShiftPlanDaysService.daysForPlans(plans.map((plan) => plan._id), options);
159
+ const areas = await WorkAreasService.areasForPlanning(days.flatMap((day) => (day.areas ?? []).map((link) => link.area)), options);
160
+ const planLinks = (plan) => days.filter((day) => day.plan.equals(plan._id)).flatMap((day) => day.areas ?? []);
161
+ const visiblePlans = plans.filter((plan) => plan.list && lists.some((list) => list._id.equals(plan.list))
162
+ && planLinks(plan).some((link) => areas.some((area) => area._id.equals(link.area))));
163
+ const { site, limit, skip } = planningPagination(filters);
164
+ if (!visiblePlans.length)
165
+ return planningPage([], 0, site, limit);
166
+ // Filtering usable references before pagination avoids disclosing totals for orphaned assignments.
167
+ const query = { company: options.company, project: options.project, 'assignments.customer': customer, ...planningIntervalFilter(filters),
168
+ $or: visiblePlans.map((plan) => ({ plan: plan._id, list: plan.list,
169
+ area: { $in: planLinks(plan).map((link) => link.area).filter((area) => areas.some((entry) => entry._id.equals(area))) },
170
+ assignments: { $elemMatch: { customer } },
171
+ })), };
172
+ const all = await this.model.db.countDocuments(query);
173
+ const shifts = await this.model.db.find(query).sort({ start: 1, _id: 1 }).skip(skip).limit(limit).toArray();
174
+ const data = [];
175
+ for (const shift of shifts) {
176
+ const plan = visiblePlans.find((entry) => entry._id.equals(shift.plan));
177
+ const list = lists.find((entry) => entry._id.equals(shift.list));
178
+ const assignment = shift.assignments.find((entry) => entry.customer.equals(customer));
179
+ const area = areas.find((entry) => entry._id.equals(shift.area));
180
+ const day = days.find((entry) => entry._id.equals(shift.day));
181
+ if (!plan || !list || !assignment || !area || !day)
182
+ continue;
183
+ // Explicit projection keeps future internal area/shift fields out of the member contract automatically.
184
+ const publicArea = { _id: area._id, name: area.name };
185
+ if (area.description !== undefined)
186
+ publicArea.description = area.description;
187
+ if (area.location !== undefined)
188
+ publicArea.location = area.location;
189
+ if (area.address !== undefined)
190
+ publicArea.address = area.address;
191
+ data.push({ _id: shift._id, day: day.date, title: shift.title, start: shift.start, end: shift.end,
192
+ plan: { _id: plan._id, name: plan.name, timeZone: plan.timeZone }, list: { _id: list._id, name: list.name }, area: publicArea, breaks: assignment.breaks,
193
+ });
194
+ }
195
+ return planningPage(data, all, site, limit);
196
+ }
197
+ async reportContext(documents, options, replacement, plan) {
198
+ const customerIds = documents.flatMap((shift) => shift.assignments.map((entry) => entry.customer));
199
+ const earliest = Math.min(...documents.map((shift) => Date.parse(shift.start)));
200
+ const latest = Math.max(...documents.map((shift) => Date.parse(shift.end)));
201
+ // Plan reads stay inside their plan; scheduling writes omit the scope to keep their company-wide conflict checks.
202
+ const schedules = await this.model.db.find({ company: options.company, ...(plan ? { plan } : {}), 'assignments.customer': { $in: customerIds },
203
+ end: { $gt: boundedPlanningInstant(earliest - scheduleWindowMilliseconds) }, start: { $lt: boundedPlanningInstant(latest + scheduleWindowMilliseconds) },
204
+ }).toArray();
205
+ if (replacement) {
206
+ const index = schedules.findIndex((shift) => shift._id.equals(replacement._id));
207
+ if (index >= 0)
208
+ schedules[index] = replacement;
209
+ else
210
+ schedules.push(replacement);
211
+ }
212
+ const plans = await ShiftPlansService.plansForCompany([...schedules.map((shift) => shift.plan), ...documents.map((shift) => shift.plan)], options);
213
+ const settings = (await SettingsService.singleSettings(options))?.settings;
214
+ const absences = await AbsencesService.absencesForPlanning(customerIds, boundedPlanningInstant(earliest), boundedPlanningInstant(latest), options);
215
+ const participantIds = documents.flatMap((shift) => shift.assignments.map((entry) => entry.participant));
216
+ const participants = await ParticipantsService.participantsForPlanning(participantIds, options.company);
217
+ const participantDetails = await ParticipantsService.participantsForPlanningWithDetails(participantIds, options.company);
218
+ const customers = await CustomersService.peopleForPlanning(customerIds, options.company);
219
+ return { schedules, plans, settings, absences, participants, participantDetails, customers };
220
+ }
221
+ async reports(documents, options, replacement, evidence, plan) {
222
+ if (!documents.length)
223
+ return [];
224
+ const context = await this.reportContext(documents, options, replacement, plan);
225
+ return documents.map((shift) => reportShift(shift, context, evidence === undefined, evidence));
226
+ }
227
+ async validateSchedule(candidate, options, previous) {
228
+ const customers = [...candidate.assignments, ...(previous?.assignments ?? [])].map((entry) => entry.customer);
229
+ if (!customers.length)
230
+ return;
231
+ const earliest = Math.min(Date.parse(candidate.start), previous ? Date.parse(previous.start) : Infinity);
232
+ const latest = Math.max(Date.parse(candidate.end), previous ? Date.parse(previous.end) : -Infinity);
233
+ const neighbors = await this.model.db.find({ company: options.company, 'assignments.customer': { $in: customers },
234
+ end: { $gte: new Date(earliest).toISOString() }, start: { $lte: boundedPlanningInstant(latest + scheduleWindowMilliseconds) },
235
+ }).toArray();
236
+ const baseline = new Map();
237
+ await this.reports(neighbors, options, undefined, baseline);
238
+ const updatedEvidence = new Map();
239
+ const updated = await this.reports([...neighbors.filter((shift) => !shift._id.equals(candidate._id)), candidate], options, candidate, updatedEvidence);
240
+ const changedCustomers = new Set(candidate.assignments.filter((assignment) => !previous || previous.start !== candidate.start || previous.end !== candidate.end
241
+ || !previous.assignments.some((existing) => equalAssignment(existing, assignment))).map((assignment) => assignment.customer.toHexString()));
242
+ // Sickness/settings can invalidate retained work. Permit unrelated edits and improvements, while rejecting new or worsened harm.
243
+ const violations = updated.flatMap((report) => report.violations.filter((violation) => {
244
+ const identity = violationIdentity(violation);
245
+ return (report._id.equals(candidate._id) && changedCustomers.has(violation.customer))
246
+ || (updatedEvidence.get(identity) ?? 0) > (baseline.get(identity) ?? 0);
247
+ }));
248
+ if (violations.some((violation) => violation.rule === 'settingsMissing' && violation.shift === candidate._id.toHexString())) {
249
+ throw new ImmutableError('SHIFT_PLANNING_SETTINGS_NOT_CONFIGURED');
250
+ }
251
+ if (violations.length) {
252
+ throw new ImmutableError('SHIFT_PLANNING_RULE_VIOLATION', [{
253
+ instancePath: '/assignments', schemaPath: '#/shiftPlanning', keyword: 'shiftPlanning', params: { violations },
254
+ message: 'assignments violate the effective working-time policy',
255
+ }]);
256
+ }
257
+ }
258
+ }
259
+ export default new ShiftsService();
260
+ //# sourceMappingURL=shifts.service.js.map