@shisyamo4131/air-guard-v2-schemas 1.0.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 (38) hide show
  1. package/index.js +15 -0
  2. package/package.json +44 -0
  3. package/src/Agreement.js +262 -0
  4. package/src/ArrangementNotification.js +505 -0
  5. package/src/Billing.js +159 -0
  6. package/src/Company.js +176 -0
  7. package/src/Customer.js +98 -0
  8. package/src/Employee.js +201 -0
  9. package/src/Operation.js +779 -0
  10. package/src/OperationBilling.js +193 -0
  11. package/src/OperationDetail.js +147 -0
  12. package/src/OperationResult.js +437 -0
  13. package/src/OperationResultDetail.js +72 -0
  14. package/src/Outsourcer.js +46 -0
  15. package/src/RoundSetting.js +123 -0
  16. package/src/Site.js +192 -0
  17. package/src/SiteOperationSchedule.js +503 -0
  18. package/src/SiteOperationScheduleDetail.js +99 -0
  19. package/src/SiteOrder.js +62 -0
  20. package/src/Tax.js +39 -0
  21. package/src/User.js +41 -0
  22. package/src/WorkingResult.js +297 -0
  23. package/src/apis/index.js +9 -0
  24. package/src/constants/arrangement-notification-status.js +68 -0
  25. package/src/constants/billing-unit-type.js +15 -0
  26. package/src/constants/contract-status.js +15 -0
  27. package/src/constants/day-type.js +44 -0
  28. package/src/constants/employment-status.js +15 -0
  29. package/src/constants/gender.js +11 -0
  30. package/src/constants/index.js +9 -0
  31. package/src/constants/prefectures.js +56 -0
  32. package/src/constants/shift-type.js +20 -0
  33. package/src/constants/site-status.js +15 -0
  34. package/src/parts/accessorDefinitions.js +109 -0
  35. package/src/parts/fieldDefinitions.js +642 -0
  36. package/src/utils/ContextualError.js +49 -0
  37. package/src/utils/CutoffDate.js +223 -0
  38. package/src/utils/index.js +48 -0
@@ -0,0 +1,779 @@
1
+ /*****************************************************************************
2
+ * Operation Model ver 1.0.0
3
+ * @author shisyamo4131
4
+ * ---------------------------------------------------------------------------
5
+ * - Base class of SiteOperationSchedule based on WorkingResult.
6
+ * - `dateAt` property indicates the date of operation (placement date)
7
+ * used for billing purposes.
8
+ * Actual working day may differ from this date.
9
+ * - `siteId`, `dateAt`, `shiftType`, and `regulationWorkMinutes` are
10
+ * automatically synchronized to all assigned employees and outsourcers
11
+ * when they are changed on the Operation instance.
12
+ * - `startTime`, `endTime`, and `breakMinutes` are NOT synchronized here.
13
+ * They should be synchronized at `SiteOperationSchedule` level instead.
14
+ * ---------------------------------------------------------------------------
15
+ * @prop {string} siteId - Site document ID (trigger property)
16
+ * - Automatically synchronizes to all `employees` and `outsourcers` when changed.
17
+ * @prop {number} requiredPersonnel - Required number of personnel
18
+ * @prop {boolean} qualificationRequired - Qualification required flag
19
+ * @prop {string} workDescription - Work description
20
+ * @prop {string} remarks - Remarks
21
+ * @prop {Array<OperationDetail>} employees - Assigned employees
22
+ * - Array of `OperationDetail` instances representing assigned employees
23
+ * @prop {Array<OperationDetail>} outsourcers - Assigned outsourcers
24
+ * - Array of `OperationDetail` instances representing assigned outsourcers
25
+ * ---------------------------------------------------------------------------
26
+ * @computed {Array<string>} employeeIds - Array of employee IDs from `employees` (read-only)
27
+ * @computed {Array<string>} outsourcerIds - Array of outsourcer IDs from `outsourcers` (read-only)
28
+ * @computed {number} employeesCount - Count of assigned employees (read-only)
29
+ * @computed {number} outsourcersCount - Count of assigned outsourcers (sum of amounts) (read-only)
30
+ * @computed {boolean} isPersonnelShortage - Indicates if there is a shortage of personnel (read-only)
31
+ * - `true` if the sum of `employeesCount` and `outsourcersCount` is less than `requiredPersonnel`
32
+ * @computed {Array<OperationDetail>} workers - Combined array of `employees` and `outsourcers`
33
+ * - Getter: Returns concatenated array of employees and outsourcers
34
+ * - Setter: Splits array into employees and outsourcers based on `isEmployee` property
35
+ * ---------------------------------------------------------------------------
36
+ * @getter {string} groupKey - Combines `siteId`, `shiftType`, and `date` to indicate operation grouping (read-only)
37
+ * @getter {boolean} isEmployeesChanged - Indicates whether the employees have changed (read-only)
38
+ * - Returns true if the employee IDs have changed compared to `_beforeData`
39
+ * @getter {boolean} isOutsourcersChanged - Indicates whether the outsourcers have changed (read-only)
40
+ * - Returns true if the outsourcer IDs have changed compared to `_beforeData`
41
+ * @getter {Array<OperationDetail>} addedWorkers - An array of workers that have been added (read-only)
42
+ * - Workers that exist in current data but not in `_beforeData`
43
+ * @getter {Array<OperationDetail>} removedWorkers - An array of workers that have been removed (read-only)
44
+ * - Workers that exist in `_beforeData` but not in current data
45
+ * @getter {Array<OperationDetail>} updatedWorkers - An array of workers that have been updated (read-only)
46
+ * - Workers whose `startTime`, `isStartNextDay`, `endTime`, `breakMinutes`, `isQualified`, or `isOjt` have changed
47
+ * ---------------------------------------------------------------------------
48
+ * @inherited - The following properties are inherited from WorkingResult:
49
+ * @prop {Date} dateAt - Date of operation (placement date) (trigger property)
50
+ * - Automatically synchronizes to all `employees` and `outsourcers` when changed.
51
+ * @prop {string} dayType - Day type (e.g., `WEEKDAY`, `WEEKEND`, `HOLIDAY`)
52
+ * @prop {string} shiftType - `DAY` or `NIGHT` (trigger property)
53
+ * - Automatically synchronizes to all `employees` and `outsourcers` when changed.
54
+ * @prop {string} startTime - Start time (HH:MM format)
55
+ * @prop {boolean} isStartNextDay - Next day start flag
56
+ * - `true` if the actual work starts the day after the placement date `dateAt`
57
+ * @prop {string} endTime - End time (HH:MM format)
58
+ * @prop {number} breakMinutes - Break time (minutes)
59
+ * @prop {number} regulationWorkMinutes - Regulation work minutes (trigger property)
60
+ * - Indicates the maximum working time treated as regular working hours.
61
+ * - A new value will be synchronized to all `employees` and `outsourcers`.
62
+ * ---------------------------------------------------------------------------
63
+ * @inherited - The following computed properties are inherited from WorkingResult:
64
+ * @computed {string} date - Date string in YYYY-MM-DD format based on `dateAt` (read-only)
65
+ * @computed {Date} startAt - Start date and time (Date object) (read-only)
66
+ * - Returns a Date object with `startTime` set based on `dateAt`.
67
+ * - If `isStartNextDay` is true, add 1 day.
68
+ * @computed {Date} endAt - End date and time (Date object) (read-only)
69
+ * - Returns a Date object with `endTime` set based on `dateAt`.
70
+ * - If `isStartNextDay` is true, add 1 day.
71
+ * - If `isSpansNextDay` is true, add 1 day.
72
+ * @computed {boolean} isSpansNextDay - Flag indicating whether the date spans from start date to end date (read-only)
73
+ * - `true` if `startTime` is later than `endTime`
74
+ * @computed {number} totalWorkMinutes - Total working time in minutes (excluding break time) (read-only)
75
+ * - Calculated as the difference between `endAt` and `startAt` minus `breakMinutes`
76
+ * @computed {number} regularTimeWorkMinutes - Regular working time in minutes (read-only)
77
+ * - The portion of `totalWorkMinutes` that is considered within the contract's `regulationWorkMinutes`.
78
+ * @computed {number} overtimeWorkMinutes - Overtime work in minutes (read-only)
79
+ * - Calculated as `totalWorkMinutes` minus `regulationWorkMinutes`
80
+ * ---------------------------------------------------------------------------
81
+ * @inherited - The following getter properties are inherited from WorkingResult:
82
+ * @getter {number} startHour - Start hour (0-23) (read-only)
83
+ * - Extracted from `startTime`.
84
+ * @getter {number} startMinute - Start minute (0-59) (read-only)
85
+ * - Extracted from `startTime`.
86
+ * @getter {number} endHour - End hour (0-23) (read-only)
87
+ * - Extracted from `endTime`.
88
+ * @getter {number} endMinute - End minute (0-59) (read-only)
89
+ * - Extracted from `endTime`.
90
+ * ---------------------------------------------------------------------------
91
+ * @method {function} addWorker - Adds a new worker (employee or outsourcer)
92
+ * - @param {Object} options - Options for adding a worker
93
+ * - @param {string} options.id - The worker ID (employeeId or outsourcerId)
94
+ * - @param {boolean} [options.isEmployee=true] - Whether the worker is an employee
95
+ * - @param {number} [index=0] - Insertion position. If -1, adds to the end
96
+ * @method {function} moveWorker - Moves the position of a worker (employee or outsourcer)
97
+ * - @param {Object} options - Options for changing worker position
98
+ * - @param {number} options.oldIndex - The original index
99
+ * - @param {number} options.newIndex - The new index
100
+ * - @param {boolean} [options.isEmployee=true] - True for employee, false for outsourcer
101
+ * @method {function} changeWorker - Changes the details of a worker
102
+ * - @param {Object} newWorker - New worker object
103
+ * @method {function} removeWorker - Removes a worker (employee or outsourcer)
104
+ * - @param {Object} options - Options for removing a worker
105
+ * - @param {string} options.workerId - The ID of the employee or outsourcer
106
+ * - @param {boolean} [options.isEmployee=true] - True for employee, false for outsourcer
107
+ * @method {function} setSiteIdCallback - Callback method called when `siteId` is set
108
+ * - Override this method in subclasses to add custom behavior when `siteId` changes.
109
+ * - By default, does nothing.
110
+ * - @param {string} v - The new `siteId` value
111
+ * @method {function} setShiftTypeCallback - Callback method called when `shiftType` is set
112
+ * - Override this method in subclasses to add custom behavior when `shiftType` changes.
113
+ * - By default, does nothing.
114
+ * - @param {string} v - The new `shiftType` value
115
+ * @method {function} setRegulationWorkMinutesCallback - Callback method called when `regulationWorkMinutes` is set
116
+ * - Override this method in subclasses to add custom behavior when `regulationWorkMinutes` changes.
117
+ * - By default, does nothing.
118
+ * - @param {number} v - The new `regulationWorkMinutes` value
119
+ * ---------------------------------------------------------------------------
120
+ * @static
121
+ * @method groupKeyDivider
122
+ * Returns an array dividing the key into siteId, shiftType, and date.
123
+ * @param {Object|string} key - The combined key string or object
124
+ * @returns {Array<string>} - Array containing [siteId, shiftType, date]
125
+ * @throws {Error} - If the key is invalid.
126
+ * ---------------------------------------------------------------------------
127
+ * @inherited - The following method is inherited from WorkingResult:
128
+ * @method {function} setDateAtCallback - Callback method called when `dateAt` is set
129
+ * - Override this method in subclasses to add custom behavior when `dateAt` changes.
130
+ * - By default, updates `dayType` based on the new `dateAt` value and synchronizes to workers.
131
+ * - @param {Date} v - The new `dateAt` value
132
+ *****************************************************************************/
133
+ import WorkingResult from "./WorkingResult.js";
134
+ import OperationDetail from "./OperationDetail.js";
135
+ import { defField } from "./parts/fieldDefinitions.js";
136
+ import { DAY_TYPE } from "./constants/day-type.js";
137
+ import { SHIFT_TYPE } from "./constants/shift-type.js";
138
+
139
+ const classProps = {
140
+ siteId: defField("siteId", { required: true }),
141
+ ...WorkingResult.classProps, // Inherited from WorkingResult.js
142
+ requiredPersonnel: defField("number", {
143
+ label: "必要人数",
144
+ required: true,
145
+ }),
146
+ qualificationRequired: defField("check", { label: "要資格者" }),
147
+ workDescription: defField("workDescription"),
148
+ remarks: defField("multipleLine", { label: "備考" }),
149
+ employees: defField("array", { customClass: OperationDetail }),
150
+ outsourcers: defField("array", {
151
+ customClass: OperationDetail,
152
+ }),
153
+ };
154
+
155
+ /**
156
+ * Wrapper to define computed properties.
157
+ * @param {*} obj
158
+ * @param {*} properties
159
+ */
160
+ function defineComputedProperties(obj, properties) {
161
+ const descriptors = {};
162
+ for (const [key, descriptor] of Object.entries(properties)) {
163
+ descriptors[key] = {
164
+ configurable: true,
165
+ enumerable: true,
166
+ ...descriptor,
167
+ };
168
+ }
169
+ Object.defineProperties(obj, descriptors);
170
+ }
171
+
172
+ export default class Operation extends WorkingResult {
173
+ static className = "稼働ベース";
174
+ static collectionPath = "Operations";
175
+ static useAutonumber = false;
176
+ static logicalDelete = false;
177
+ static classProps = classProps;
178
+
179
+ static DAY_TYPE = DAY_TYPE;
180
+ static DAY_TYPE_DAY = DAY_TYPE.DAY;
181
+ static DAY_TYPE_NIGHT = DAY_TYPE.NIGHT;
182
+
183
+ static SHIFT_TYPE = SHIFT_TYPE;
184
+ static SHIFT_TYPE_DAY = SHIFT_TYPE.DAY.value;
185
+ static SHIFT_TYPE_NIGHT = SHIFT_TYPE.NIGHT.value;
186
+
187
+ /**
188
+ * Constructor
189
+ * @param {*} item
190
+ */
191
+ constructor(item = {}) {
192
+ if (new.target == Operation) {
193
+ throw new Error(
194
+ `Operation is an abstract class and cannot be instantiated directly.`
195
+ );
196
+ }
197
+ super(item);
198
+ }
199
+
200
+ /**
201
+ * setSiteIdCallback
202
+ * - Callback method called when `siteId` is set.
203
+ * - Override this method in subclasses to add custom behavior when `siteId` changes.
204
+ * @param {*} v
205
+ */
206
+ setSiteIdCallback(v) {}
207
+
208
+ /**
209
+ * setDateAtCallback
210
+ * - Callback method called when `dateAt` is set.
211
+ * - Override this method in subclasses to add custom behavior when `dateAt` changes.
212
+ * @param {*} v
213
+ */
214
+ setDateAtCallback(v) {
215
+ super.setDateAtCallback(v);
216
+ this.employees.forEach((emp) => (emp.dateAt = v));
217
+ this.outsourcers.forEach((out) => (out.dateAt = v));
218
+ }
219
+
220
+ /**
221
+ * setShiftTypeCallback
222
+ * - Callback method called when `shiftType` is set.
223
+ * - Override this method in subclasses to add custom behavior when `shiftType` changes.
224
+ * @param {*} v
225
+ */
226
+ setShiftTypeCallback(v) {}
227
+
228
+ /**
229
+ * setRegulationWorkMinutesCallback
230
+ * - Callback method called when `regulationWorkMinutes` is set.
231
+ * - Override this method in subclasses to add custom behavior when `regulationWorkMinutes` changes.
232
+ * @param {*} v
233
+ */
234
+ setRegulationWorkMinutesCallback(v) {}
235
+
236
+ /**
237
+ * afterInitialize
238
+ */
239
+ afterInitialize(item = {}) {
240
+ super.afterInitialize(item);
241
+
242
+ /***********************************************************
243
+ * TRIGGERS FOR SYNCRONIZATION TO EMPLOYEES AND OUTSOURCERS
244
+ * ---------------------------------------------------------
245
+ * When `siteId`, `dateAt`, `shiftType`, and `regulationWorkMinutes`
246
+ * are changed on the Operation instance,
247
+ * the corresponding properties on all employees and outsourcers
248
+ * are automatically updated to keep them in sync.
249
+ * [NOTE]
250
+ * `startTime`, `endTime`, and `breakMinutes` are NOT synchronized here.
251
+ * They should be synchronized at `SiteOperationSchedule` level instead.
252
+ ***********************************************************/
253
+ let _siteId = this.siteId;
254
+ let _shiftType = this.shiftType;
255
+ let _regulationWorkMinutes = this.regulationWorkMinutes;
256
+ defineComputedProperties(this, {
257
+ siteId: {
258
+ get() {
259
+ return _siteId;
260
+ },
261
+ set(v) {
262
+ if (!!v && typeof v !== "string") {
263
+ throw new Error(`siteId must be a string. siteId: ${v}`);
264
+ }
265
+ if (_siteId === v) return;
266
+ _siteId = v;
267
+ this.employees.forEach((emp) => (emp.siteId = v));
268
+ this.outsourcers.forEach((out) => (out.siteId = v));
269
+ this.setSiteIdCallback(v);
270
+ },
271
+ },
272
+ shiftType: {
273
+ get() {
274
+ return _shiftType;
275
+ },
276
+ set(v) {
277
+ if (typeof v !== "string") {
278
+ throw new Error(`shiftType must be a string. shiftType: ${v}`);
279
+ }
280
+ if (!SHIFT_TYPE[v]) {
281
+ throw new Error(`Invalid shiftType value. shiftType: ${v}`);
282
+ }
283
+ if (_shiftType === v) return;
284
+ _shiftType = v;
285
+ this.employees.forEach((emp) => (emp.shiftType = v));
286
+ this.outsourcers.forEach((out) => (out.shiftType = v));
287
+ this.setShiftTypeCallback(v);
288
+ },
289
+ },
290
+ regulationWorkMinutes: {
291
+ get() {
292
+ return _regulationWorkMinutes;
293
+ },
294
+ set(v) {
295
+ if (typeof v !== "number" || isNaN(v) || v < 0) {
296
+ throw new Error(
297
+ `regulationWorkMinutes must be a non-negative number. regulationWorkMinutes: ${v}`
298
+ );
299
+ }
300
+ if (_regulationWorkMinutes === v) return;
301
+ _regulationWorkMinutes = v;
302
+ this.employees.forEach((emp) => (emp.regulationWorkMinutes = v));
303
+ this.outsourcers.forEach((out) => (out.regulationWorkMinutes = v));
304
+ this.setRegulationWorkMinutesCallback(v);
305
+ },
306
+ },
307
+ });
308
+
309
+ /** define computed properies */
310
+ defineComputedProperties(this, {
311
+ /** Returns an array of employee IDs */
312
+ employeeIds: {
313
+ get() {
314
+ return this.employees.map((emp) => emp.employeeId);
315
+ },
316
+ set(v) {},
317
+ },
318
+ /** Returns an array of outsourcer IDs */
319
+ outsourcerIds: {
320
+ get() {
321
+ return this.outsourcers.map((out) => out.outsourcerId);
322
+ },
323
+ set(v) {},
324
+ },
325
+ /** Returns the count of assigned employees */
326
+ employeesCount: {
327
+ get() {
328
+ return this.employees.length;
329
+ },
330
+ set(v) {},
331
+ },
332
+ /** Returns the count of assigned outsourcers (sum of amounts) */
333
+ outsourcersCount: {
334
+ get() {
335
+ return this.outsourcers.reduce((sum, i) => sum + i.amount, 0);
336
+ },
337
+ set(v) {},
338
+ },
339
+ /** Returns whether there is a personnel shortage */
340
+ isPersonnelShortage: {
341
+ get() {
342
+ const totalRequired = this.requiredPersonnel || 0;
343
+ const totalAssigned = this.employeesCount + this.outsourcersCount;
344
+ return totalAssigned < totalRequired;
345
+ },
346
+ set(v) {},
347
+ },
348
+ /** Returns a combined array of employees and outsourcers */
349
+ workers: {
350
+ get() {
351
+ return this.employees.concat(this.outsourcers);
352
+ },
353
+ set(v) {
354
+ const employees = v.filter((emp) => emp.isEmployee);
355
+ const outsourcers = v.filter((out) => !out.isEmployee);
356
+ this.employees = employees;
357
+ this.outsourcers = outsourcers;
358
+ },
359
+ },
360
+ });
361
+
362
+ /** Define custom methods for employees and outsourcers */
363
+ const self = this;
364
+ Object.defineProperties(this.employees, {
365
+ /**
366
+ * Adds a new employee to the `employees` property with the specified ID.
367
+ * - The element added is as an instance specified by `employees.customClass`.
368
+ * - Throws an error if the specified employee ID already exists in the `employees` property.
369
+ * - `startAt`, `endAt`, and `breakMinutes` are taken from the current instance.
370
+ * - `employeeId` is required.
371
+ * [Note]
372
+ * Any options other than `id` and `amount` are accepted and used as initial values
373
+ * for the new instance.
374
+ * @param {Object} args - arguments.
375
+ * @param {string} args.id - The employee's ID.
376
+ * @param {number} args.amount - amount.
377
+ * @param {number} [index=0] - Insertion position. If -1, adds to the end.
378
+ * @returns {Object} - The added employee object.
379
+ * @throws {Error} - If the employee ID already exists.
380
+ */
381
+ add: {
382
+ value: function (args = {}, index = 0) {
383
+ const { id } = args;
384
+ if (!id || typeof id !== "string") {
385
+ throw new Error(
386
+ `Employee ID is required and must be a string. id: ${id}`
387
+ );
388
+ }
389
+ if (this.some((emp) => emp.workerId === id)) {
390
+ throw new Error(`Employee with ID ${id} already exists.`);
391
+ }
392
+ const schema = self.constructor.classProps?.employees?.customClass;
393
+ if (!schema || typeof schema !== "function") {
394
+ throw new Error("employees.customClass is not defined.");
395
+ }
396
+ const newEmployee = new schema({
397
+ ...self.toObject(),
398
+ ...args,
399
+ isEmployee: true, // Force override to true
400
+ });
401
+ if (index === -1) {
402
+ this.push(newEmployee);
403
+ } else {
404
+ this.splice(index, 0, newEmployee);
405
+ }
406
+ return newEmployee;
407
+ },
408
+ writable: false,
409
+ enumerable: false,
410
+ },
411
+ /**
412
+ * Moves the position of an employee in the employees array.
413
+ * @param {number} oldIndex - The original index.
414
+ * @param {number} newIndex - The new index.
415
+ */
416
+ move: {
417
+ value: function (oldIndex, newIndex) {
418
+ if (newIndex > this.length - 1) {
419
+ throw new Error(
420
+ `Employees must be placed before outsourcers. newIndex: ${newIndex}, employees.length: ${this.length}`
421
+ );
422
+ }
423
+ if (newIndex < 0 || newIndex >= this.length) {
424
+ throw new Error(`Invalid new index: ${newIndex}`);
425
+ }
426
+ const employee = this.splice(oldIndex, 1)[0];
427
+ this.splice(newIndex, 0, employee);
428
+ },
429
+ writable: false,
430
+ enumerable: false,
431
+ },
432
+ /**
433
+ * Changes the details of an existing employee in the employees array.
434
+ * @param {Object} newEmployee - The updated employee object.
435
+ * @throws {Error} - If the employee is not found.
436
+ */
437
+ change: {
438
+ value: function (newEmployee) {
439
+ const index = this.findIndex(
440
+ (e) => e.workerId === newEmployee.workerId
441
+ );
442
+ if (index < 0) {
443
+ throw new Error("Worker not found in employees.");
444
+ }
445
+ this[index] = newEmployee;
446
+ },
447
+ writable: false,
448
+ enumerable: false,
449
+ },
450
+ /**
451
+ * Removes the employee corresponding to `employeeId` from this.employees.
452
+ * @param {string} employeeId - The employee's ID
453
+ * @throws {Error} - If the employee ID is not found.
454
+ */
455
+ remove: {
456
+ value: function (employeeId) {
457
+ const index = this.findIndex((emp) => emp.workerId === employeeId);
458
+ if (index === -1) {
459
+ throw new Error(`Employee with ID "${employeeId}" not found.`);
460
+ }
461
+ this.splice(index, 1);
462
+ },
463
+ writable: false,
464
+ enumerable: false,
465
+ },
466
+ });
467
+ Object.defineProperties(this.outsourcers, {
468
+ /**
469
+ * Adds a new outsourcer to the `outsourcers` property with the specified ID.
470
+ * - The element added is as an instance specified by `outsourcers.customClass`.
471
+ * - If the specified outsourcer ID already exists in the `outsourcers` property, increases the amount.
472
+ * - `startTime`, `endTime`, and `isStartNextDay` are taken from the current instance.
473
+ * - `outsourcerId` is required.
474
+ * [Note]
475
+ * Any options other than `id` and `amount` are accepted and used as initial values
476
+ * for the new instance.
477
+ * @param {Object} args - arguments.
478
+ * @param {string} args.id - The outsourcer's ID.
479
+ * @param {number} args.amount - amount.
480
+ * @param {number} [index=0] - Insertion position. If -1, adds to the end.
481
+ * @return {Object} - The added outsourcer object.
482
+ * @throws {Error} - If the outsourcer ID is not provided.
483
+ */
484
+ add: {
485
+ value: function (args = {}, index = 0) {
486
+ const { id } = args;
487
+ if (!id || typeof id !== "string") {
488
+ throw new Error(
489
+ `Outsourcer ID is required and must be a string. id: ${id}`
490
+ );
491
+ }
492
+ const maxIndex = this.reduce((result, out) => {
493
+ if (out.outsourcerId === id) {
494
+ return Math.max(result, Number(out.index));
495
+ }
496
+ return result;
497
+ }, 0);
498
+
499
+ const schema = self.constructor.classProps.outsourcers.customClass;
500
+ if (!schema || typeof schema !== "function") {
501
+ throw new Error("outsourcers.customClass is not defined.");
502
+ }
503
+ const newOutsourcer = new schema({
504
+ ...self.toObject(),
505
+ ...args,
506
+ index: maxIndex + 1, // Always set to the next index
507
+ isEmployee: false, // Force override to false
508
+ });
509
+
510
+ if (index === -1) {
511
+ this.push(newOutsourcer);
512
+ } else {
513
+ this.splice(index, 0, newOutsourcer);
514
+ }
515
+ return newOutsourcer;
516
+ },
517
+ writable: false,
518
+ enumerable: false,
519
+ },
520
+ /**
521
+ * Moves the position of an outsourcer in the outsourcers array.
522
+ * - `oldIndex` and `newIndex` are offset by the number of employees.
523
+ * @param {number} oldIndex - The original index.
524
+ * @param {number} newIndex - The new index.
525
+ */
526
+ move: {
527
+ value: function (oldIndex, newIndex) {
528
+ if (newIndex <= self.employees.length - 1) {
529
+ throw new Error(
530
+ `Outsourcers must be placed after employees. newIndex: ${newIndex}, employees.length: ${self.employees.length}`
531
+ );
532
+ }
533
+ const internalOldIndex = Math.max(
534
+ 0,
535
+ oldIndex - self.employees.length
536
+ );
537
+ const internalNewIndex = Math.max(
538
+ 0,
539
+ newIndex - self.employees.length
540
+ );
541
+ if (internalOldIndex < 0 || internalOldIndex >= this.length) {
542
+ throw new Error(`Invalid old index: ${internalOldIndex}`);
543
+ }
544
+ if (internalNewIndex < 0 || internalNewIndex >= this.length) {
545
+ throw new Error(`Invalid new index: ${internalNewIndex}`);
546
+ }
547
+ const outsourcer = this.splice(internalOldIndex, 1)[0];
548
+ this.splice(internalNewIndex, 0, outsourcer);
549
+ },
550
+ writable: false,
551
+ enumerable: false,
552
+ },
553
+ /**
554
+ * Changes the details of an existing outsourcer in the outsourcers array.
555
+ * @param {Object} newOutsourcer - The updated outsourcer object.
556
+ * @throws {Error} - If the outsourcer is not found.
557
+ */
558
+ change: {
559
+ value: function (newOutsourcer) {
560
+ const index = this.findIndex(
561
+ (e) => e.workerId === newOutsourcer.workerId
562
+ );
563
+ if (index < 0) {
564
+ throw new Error("Worker not found in outsourcers.");
565
+ }
566
+ this[index] = newOutsourcer;
567
+ },
568
+ writable: false,
569
+ enumerable: false,
570
+ },
571
+ /**
572
+ * Removes the outsourcer corresponding to `outsourcerId` from this.outsourcers.
573
+ * - Throws an error for invalid values or if not found.
574
+ * @param {string} outsourcerId - The ID of the outsourcer.
575
+ * @throws {Error} - If the outsourcer ID is not found.
576
+ */
577
+ remove: {
578
+ value: function (outsourcerId) {
579
+ const index = this.findIndex((out) => out.workerId === outsourcerId);
580
+ if (index === -1) {
581
+ throw new Error(`Outsourcer with ID "${outsourcerId}" not found.`);
582
+ }
583
+ this.splice(index, 1);
584
+ },
585
+ writable: false,
586
+ enumerable: false,
587
+ },
588
+ });
589
+ /** Remove unnecessary properties */
590
+ delete this.key; // From workingResult.js
591
+ }
592
+
593
+ /***************************************************************************
594
+ * STATES
595
+ ***************************************************************************/
596
+ /**
597
+ * Returns a string combining siteId, shiftType, and date.
598
+ * - Indicates where this operation belongs.
599
+ * @returns {string} - The group key.
600
+ */
601
+ get groupKey() {
602
+ return `${this.siteId}-${this.shiftType}-${this.date}`;
603
+ }
604
+
605
+ /**
606
+ * Returns whether the employees have changed.
607
+ * - Returns true if the employee IDs have changed.
608
+ * - Returns false if the employee IDs have not changed or
609
+ * are the same even if the order has changed.
610
+ * @returns {boolean} - Whether the employees have changed.
611
+ */
612
+ get isEmployeesChanged() {
613
+ const current = this.employeeIds || [];
614
+ const before = this._beforeData?.employeeIds || [];
615
+ return current.sort().join(",") !== before.sort().join(",");
616
+ }
617
+
618
+ /**
619
+ * Returns whether the outsourcers have changed.
620
+ * - Returns true if the outsourcer IDs have changed.
621
+ * - Returns false if the outsourcer IDs have not changed or
622
+ * are the same even if the order has changed.
623
+ * @returns {boolean} - Whether the outsourcers have changed.
624
+ */
625
+ get isOutsourcersChanged() {
626
+ const current = this.outsourcerIds || [];
627
+ const before = this._beforeData?.outsourcerIds || [];
628
+ return current.sort().join(",") !== before.sort().join(",");
629
+ }
630
+
631
+ /**
632
+ * Returns a filtered array of workers that have been added.
633
+ * @returns {Array<OperationDetail>} - Array of added workers.
634
+ */
635
+ get addedWorkers() {
636
+ const current = this.workers || [];
637
+ if (current.length === 0) return [];
638
+ const before = this._beforeData?.workers || [];
639
+ const isAdded = (emp) => !before.some((e) => e.workerId === emp.workerId);
640
+ return current.filter(isAdded);
641
+ }
642
+
643
+ /**
644
+ * Returns a filtered array of workers that have been removed.
645
+ * Note: The returned elements do not exist in this.workers.
646
+ * @returns {Array<OperationDetail>} - Array of removed workers.
647
+ */
648
+ get removedWorkers() {
649
+ const before = this._beforeData?.workers || [];
650
+ if (before.length === 0) return [];
651
+ const current = this.workers || [];
652
+ const isRemoved = (emp) =>
653
+ !current.some((e) => e.workerId === emp.workerId);
654
+ return before.filter(isRemoved);
655
+ }
656
+
657
+ /**
658
+ * Returns a filtered array of workers that have been updated.
659
+ * - Compares `startTime`, `isStartNextDay`, `endTime`, and `breakMinutes` properties.
660
+ * @returns {Array<OperationDetail>} - Array of updated workers.
661
+ */
662
+ get updatedWorkers() {
663
+ const before = this._beforeData.workers || [];
664
+ if (before.length === 0) return [];
665
+ const current = this.workers || [];
666
+ const keys = [
667
+ "startTime",
668
+ "isStartNextDay",
669
+ "endTime",
670
+ "breakMinutes",
671
+ "isQualified",
672
+ "isOjt",
673
+ ];
674
+ const isUpdated = (emp) => {
675
+ const worker = before.find((e) => e.workerId === emp.workerId);
676
+ if (!worker) return false;
677
+ return keys.some((key) => emp[key] !== worker[key]);
678
+ };
679
+ return current.filter(isUpdated);
680
+ }
681
+
682
+ /***************************************************************************
683
+ * METHODS
684
+ ***************************************************************************/
685
+ /**
686
+ * Adds a new worker.
687
+ * - Calls the appropriate method based on the value of `isEmployee`.
688
+ * @param {Object} options - Options for adding a worker.
689
+ * @param {string} options.id - The worker ID (employeeId or outsourcerId)
690
+ * @param {boolean} [options.isEmployee=true] - Whether the worker is an employee
691
+ * @param {number} [index=0] - Insertion position. If -1, adds to the end.
692
+ */
693
+ addWorker(options = {}, index = 0) {
694
+ const { isEmployee = true } = options;
695
+ if (isEmployee) {
696
+ this.employees.add(options, index);
697
+ } else {
698
+ this.outsourcers.add(options, index);
699
+ }
700
+ }
701
+
702
+ /**
703
+ * Moves the position of workers.
704
+ * @param {Object} options - Options for changing worker position.
705
+ * @param {number} options.oldIndex - The original index.
706
+ * @param {number} options.newIndex - The new index.
707
+ * @param {boolean} [options.isEmployee=true] - True for employee, false for outsourcer.
708
+ */
709
+ moveWorker(options) {
710
+ const { oldIndex, newIndex, isEmployee = true } = options;
711
+ if (typeof oldIndex !== "number" || typeof newIndex !== "number") {
712
+ throw new Error(
713
+ "oldIndex and newIndex are required and must be numbers."
714
+ );
715
+ }
716
+ if (isEmployee) {
717
+ this.employees.move(oldIndex, newIndex);
718
+ } else {
719
+ this.outsourcers.move(oldIndex, newIndex);
720
+ }
721
+ }
722
+
723
+ /**
724
+ * Changes the details of a worker.
725
+ * @param {Object} newWorker - New worker object
726
+ */
727
+ changeWorker(newWorker) {
728
+ if (newWorker.isEmployee) {
729
+ this.employees.change(newWorker);
730
+ } else {
731
+ this.outsourcers.change(newWorker);
732
+ }
733
+ }
734
+
735
+ /**
736
+ * Removes an employee or outsourcer from the schedule.
737
+ * @param {Object} options - Options for removing a worker.
738
+ * @param {string} options.workerId - The ID of the employee or outsourcer.
739
+ * @param {boolean} [options.isEmployee=true] - True for employee, false for outsourcer.
740
+ */
741
+ removeWorker(options) {
742
+ const { workerId, isEmployee = true } = options;
743
+ if (isEmployee) {
744
+ this.employees.remove(workerId);
745
+ } else {
746
+ this.outsourcers.remove(workerId);
747
+ }
748
+ }
749
+
750
+ /**
751
+ * Returns an array dividing the key into siteId, shiftType, and date.
752
+ * @param {Object|string} key
753
+ * @returns {Array<string>} - [siteId, shiftType, date]
754
+ * @throws {Error} - If the key is invalid.
755
+ */
756
+ static groupKeyDivider(key = {}) {
757
+ if (!key) throw new Error("key is required.");
758
+
759
+ switch (typeof key) {
760
+ case "object":
761
+ if (!key.siteId || !key.shiftType || !key.date) {
762
+ throw new Error(
763
+ "key must contain siteId, shiftType, and date properties."
764
+ );
765
+ }
766
+ return [key.siteId, key.shiftType, key.date];
767
+ case "string":
768
+ const [siteId, shiftType, year, month, day] = key.split("-");
769
+ if (!siteId || !shiftType || !year || !month || !day) {
770
+ throw new Error(
771
+ "key must be in the format 'siteId-shiftType-year-month-day'."
772
+ );
773
+ }
774
+ return [siteId, shiftType, `${year}-${month}-${day}`];
775
+ default:
776
+ throw new Error("Invalid key type.");
777
+ }
778
+ }
779
+ }