@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,505 @@
1
+ /*****************************************************************************
2
+ * ArrangementNotification Model ver 1.0.0
3
+ * @author shisyamo4131
4
+ * ---------------------------------------------------------------------------
5
+ * - Model representing arrangement notifications for employees extending SiteOperationScheduleDetail.
6
+ * - The `docId` is fixed to `${siteOperationScheduleId}-${workerId}` to allow recreation of documents.
7
+ * - Status-based state management with specific transition methods.
8
+ * - Overrides `totalWorkMinutes` to use actual work times instead of scheduled times.
9
+ * - Direct updates are disabled; use status transition methods instead.
10
+ * ---------------------------------------------------------------------------
11
+ * @prop {Date} confirmedAt - Confirmation date and time
12
+ * @prop {string} arrivedAt - Arrival time (HH:MM format)
13
+ * @prop {string} leavedAt - Leave time (HH:MM format)
14
+ * @prop {string} actualStartTime - Actual start time (HH:MM format)
15
+ * @prop {boolean} actualIsStartNextDay - Actual next day start flag
16
+ * @prop {string} actualEndTime - Actual end time (HH:MM format)
17
+ * @prop {number} actualBreakMinutes - Actual break time (minutes)
18
+ * @prop {string} status - Arrangement notification status
19
+ * ---------------------------------------------------------------------------
20
+ * @computed {Date} actualStartAt - Actual start date and time (Date object) (read-only)
21
+ * - Returns a Date object with `actualStartTime` set based on `dateAt`.
22
+ * - If `isStartNextDay` is true, add 1 day.
23
+ * @computed {Date} actualEndAt - Actual end date and time (Date object) (read-only)
24
+ * - Returns a Date object with `actualEndTime` set based on `dateAt`.
25
+ * - If `isStartNextDay` is true, add 1 day.
26
+ * - If `isSpansNextDay` is true, add 1 day.
27
+ * @computed {number} totalWorkMinutes - Total working time in minutes (excluding actual break time) (read-only)
28
+ * - **OVERRIDE**: Calculated using actual times instead of scheduled times.
29
+ * - Calculated as the difference between `actualEndAt` and `actualStartAt` minus `actualBreakMinutes`
30
+ * ---------------------------------------------------------------------------
31
+ * @getter {boolean} isArranged - Arranged status flag (read-only)
32
+ * - Returns `true` if status is `ARRANGED`
33
+ * @getter {boolean} isConfirmed - Confirmed status flag (read-only)
34
+ * - Returns `true` if status is `CONFIRMED`
35
+ * @getter {boolean} isArrived - Arrived status flag (read-only)
36
+ * - Returns `true` if status is `ARRIVED`
37
+ * @getter {boolean} isLeaved - Leaved status flag (read-only)
38
+ * - Returns `true` if status is `LEAVED`
39
+ * ---------------------------------------------------------------------------
40
+ * @inherited - The following properties are inherited from SiteOperationScheduleDetail:
41
+ * @prop {string} siteOperationScheduleId - Site Operation Schedule ID
42
+ * ---------------------------------------------------------------------------
43
+ * @inherited - The following properties are inherited from OperationDetail (via SiteOperationScheduleDetail):
44
+ * @prop {string} id - Employee or Outsourcer document ID
45
+ * @prop {number} index - Identifier index for Outsourcer (always 0 for Employee)
46
+ * @prop {boolean} isEmployee - Employee flag (true: Employee, false: Outsourcer)
47
+ * @prop {number} amount - Number of placements (always fixed at 1)
48
+ * @prop {string} siteId - Site ID
49
+ * @prop {boolean} isQualified - Qualified flag
50
+ * @prop {boolean} isOjt - OJT flag
51
+ * ---------------------------------------------------------------------------
52
+ * @inherited - The following properties are inherited from WorkingResult (via SiteOperationScheduleDetail):
53
+ * @prop {Date} dateAt - Placement date (trigger property)
54
+ * @prop {string} dayType - Day type (e.g., `WEEKDAY`, `WEEKEND`, `HOLIDAY`)
55
+ * @prop {string} shiftType - `DAY` or `NIGHT`
56
+ * @prop {string} startTime - Start time (HH:MM format)
57
+ * @prop {boolean} isStartNextDay - Next day start flag
58
+ * - `true` if the actual work starts the day after the placement date `dateAt`
59
+ * @prop {string} endTime - End time (HH:MM format)
60
+ * @prop {number} breakMinutes - Break time (minutes)
61
+ * @prop {number} regulationWorkMinutes - Regulation work minutes
62
+ * ---------------------------------------------------------------------------
63
+ * @inherited - The following computed properties are inherited from OperationDetail (via SiteOperationScheduleDetail):
64
+ * @computed {string} workerId - Worker ID (read-only)
65
+ * - For Employee, it's the same as `id`, for Outsourcer, it's a concatenation of `id` and `index` with ':'
66
+ * @computed {string|null} employeeId - Employee ID (null if not applicable) (read-only)
67
+ * @computed {string|null} outsourcerId - Outsourcer ID (null if not applicable) (read-only)
68
+ * ---------------------------------------------------------------------------
69
+ * @inherited - The following computed properties are inherited from WorkingResult (via SiteOperationScheduleDetail):
70
+ * @computed {string} key - Unique key combining `date`, `dayType`, and `shiftType` (read-only)
71
+ * @computed {string} date - Date string in YYYY-MM-DD format based on `dateAt` (read-only)
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 {Date} startAt - Start date and time (Date object) (read-only)
75
+ * - Returns a Date object with `startTime` set based on `dateAt`.
76
+ * - If `isStartNextDay` is true, add 1 day.
77
+ * @computed {Date} endAt - End date and time (Date object) (read-only)
78
+ * - Returns a Date object with `endTime` set based on `dateAt`.
79
+ * - If `isStartNextDay` is true, add 1 day.
80
+ * - If `isSpansNextDay` is true, add 1 day.
81
+ * @computed {number} regularTimeWorkMinutes - Regular working time in minutes (read-only)
82
+ * - The portion of `totalWorkMinutes` that is considered within the contract's `regulationWorkMinutes`.
83
+ * @computed {number} overtimeWorkMinutes - Overtime work in minutes (read-only)
84
+ * - Calculated as `totalWorkMinutes` minus `regulationWorkMinutes`
85
+ * ---------------------------------------------------------------------------
86
+ * @inherited - The following getter properties are inherited from WorkingResult (via SiteOperationScheduleDetail):
87
+ * @getter {number} startHour - Start hour (0-23) (read-only)
88
+ * - Extracted from `startTime`.
89
+ * @getter {number} startMinute - Start minute (0-59) (read-only)
90
+ * - Extracted from `startTime`.
91
+ * @getter {number} endHour - End hour (0-23) (read-only)
92
+ * - Extracted from `endTime`.
93
+ * @getter {number} endMinute - End minute (0-59) (read-only)
94
+ * - Extracted from `endTime`.
95
+ * ---------------------------------------------------------------------------
96
+ * @removed - The following properties from SiteOperationScheduleDetail are removed:
97
+ * @removed {boolean} hasNotification - Notification flag (not needed in ArrangementNotification)
98
+ * @removed {string} notificationKey - Notification key (not needed in ArrangementNotification)
99
+ * ---------------------------------------------------------------------------
100
+ * @method {function} create - Override to fix `docId` for recreation
101
+ * - Ensures `docId` is set to `${siteOperationScheduleId}-${workerId}`.
102
+ * - Allows recreation of ArrangementNotification documents.
103
+ * - @param {Object} updateOptions - Options for creating the document
104
+ * @method {function} update - Disabled
105
+ * - Direct updates to ArrangementNotification are not allowed.
106
+ * - Throws an error if called. Use status transition methods instead.
107
+ * - @param {Object} updateOptions - Options for updating the document
108
+ * @method {function} toArranged - Change status to `ARRANGED`
109
+ * - Sets actual times to scheduled times, resets timestamps, updates status.
110
+ * - @param {Object} updateOptions - Options for updating the document
111
+ * @method {function} toConfirmed - Change status to `CONFIRMED`
112
+ * - Sets actual times to scheduled times, sets `confirmedAt`, resets other timestamps, updates status.
113
+ * - @param {Object} updateOptions - Options for updating the document
114
+ * @method {function} toArrived - Change status to `ARRIVED`
115
+ * - Sets actual times to scheduled times, sets `arrivedAt`, retains or sets `confirmedAt`, updates status.
116
+ * - @param {Object} updateOptions - Options for updating the document
117
+ * @method {function} toLeaved - Change status to `LEAVED`
118
+ * - Sets actual times from parameters, sets `leavedAt`, retains or sets other timestamps, updates status.
119
+ * - @param {Object} timeOptions - Time options (actualStartTime, actualEndTime, actualBreakMinutes, actualIsStartNextDay)
120
+ * - @param {Object} updateOptions - Options for updating the document
121
+ * @method {function} fetchDocsBySiteOperationScheduleId - Get documents by `siteOperationScheduleId` (static)
122
+ * - @param {string} id - The site operation schedule ID
123
+ * - @returns {Promise<Array>} - The fetched documents
124
+ * @method {function} bulkDelete - Bulk delete arrangement notifications (static)
125
+ * - @param {Object} options - Deletion options (siteOperationScheduleId, workerIds)
126
+ * - @param {Object} [transaction] - Optional Firestore transaction object
127
+ * - @returns {Promise<void>}
128
+ * ---------------------------------------------------------------------------
129
+ * @inherited - The following method is inherited from WorkingResult (via SiteOperationScheduleDetail):
130
+ * @method {function} setDateAtCallback - Callback method called when `dateAt` is set
131
+ * - Override this method in subclasses to add custom behavior when `dateAt` changes.
132
+ * - By default, updates `dayType` based on the new `dateAt` value.
133
+ * - @param {Date} v - The new `dateAt` value
134
+ *****************************************************************************/
135
+ import SiteOperationScheduleDetail from "./SiteOperationScheduleDetail.js";
136
+ import { getDateAt, ContextualError } from "./utils/index.js";
137
+ import { defField } from "./parts/fieldDefinitions.js";
138
+ import {
139
+ VALUES,
140
+ OPTIONS,
141
+ } from "./constants/arrangement-notification-status.js";
142
+ import { runTransaction } from "firebase/firestore";
143
+
144
+ const classProps = {
145
+ status: defField("arrangementNotificationStatus", { required: true }),
146
+ ...SiteOperationScheduleDetail.classProps,
147
+ confirmedAt: defField("time", { label: "配置確認日時" }),
148
+ arrivedAt: defField("time", { label: "上番日時" }),
149
+ leavedAt: defField("time", { label: "下番日時" }),
150
+ actualStartTime: defField("time", {
151
+ label: "実際の開始時刻",
152
+ required: true,
153
+ }),
154
+ actualIsStartNextDay: defField("check", { label: "翌日開始" }),
155
+ actualEndTime: defField("time", {
156
+ label: "実際の終了時刻",
157
+ required: true,
158
+ }),
159
+ actualBreakMinutes: defField("breakMinutes", {
160
+ default: 60,
161
+ required: true,
162
+ }),
163
+ };
164
+
165
+ export default class ArrangementNotification extends SiteOperationScheduleDetail {
166
+ static className = "配置通知";
167
+ static collectionPath = "ArrangementNotifications";
168
+ static useAutonumber = false;
169
+ static logicalDelete = false;
170
+ static classProps = classProps;
171
+
172
+ static STATUSES = VALUES;
173
+ static STATUS_ARRANGED = VALUES.ARRANGED.value;
174
+ static STATUS_CONFIRMED = VALUES.CONFIRMED.value;
175
+ static STATUS_ARRIVED = VALUES.ARRIVED.value;
176
+ static STATUS_LEAVED = VALUES.LEAVED.value;
177
+ static STATUS_OPTIONS = OPTIONS;
178
+
179
+ afterInitialize(item = {}) {
180
+ // Define computed properties that are defined on SiteOperationScheduleDetail
181
+ super.afterInitialize(item);
182
+
183
+ // Define additional computed properties specific to ArrangementNotification
184
+ Object.defineProperties(this, {
185
+ /**
186
+ * 実際の開始日時(Date オブジェクト)
187
+ * - `dateAt` を基に、`actualStartTime` を設定した Date オブジェクトを返す。
188
+ * - `isStartNextDay` が true の場合は1日加算。
189
+ */
190
+ actualStartAt: {
191
+ configurable: true,
192
+ enumerable: true,
193
+ get: () => {
194
+ const dateOffset = this.isStartNextDay ? 1 : 0;
195
+ return getDateAt(this.dateAt, this.actualStartTime, dateOffset);
196
+ },
197
+ set: (v) => {},
198
+ },
199
+ /**
200
+ * 実際の終了日時(Date オブジェクト)
201
+ * - `dateAt` を基に、`actualEndTime` を設定した Date オブジェクトを返す。
202
+ * - `isStartNextDay` が true の場合は1日加算。
203
+ * - `isSpansNextDay` が true の場合は1日加算。
204
+ */
205
+ actualEndAt: {
206
+ configurable: true,
207
+ enumerable: true,
208
+ get: () => {
209
+ const dateOffset =
210
+ (this.isSpansNextDay ? 1 : 0) + (this.isStartNextDay ? 1 : 0);
211
+ return getDateAt(this.dateAt, this.actualEndTime, dateOffset);
212
+ },
213
+ set: (v) => {},
214
+ },
215
+ totalWorkMinutes: {
216
+ configurable: true,
217
+ enumerable: true,
218
+ get: () => {
219
+ const start = this.actualStartAt;
220
+ const end = this.actualEndAt;
221
+ const breakMinutes = this.actualBreakMinutes || 0;
222
+ const diff = (end - start) / (1000 * 60); // ミリ秒を分に変換
223
+ return Math.max(0, diff - breakMinutes);
224
+ },
225
+ set: (v) => {},
226
+ },
227
+ });
228
+
229
+ // Remove inherited properties that are not needed in ArrangementNotification
230
+ delete this.hasNotification;
231
+ delete this.notificationKey;
232
+ }
233
+
234
+ get isArranged() {
235
+ return this.status === VALUES.ARRANGED.value;
236
+ }
237
+
238
+ get isConfirmed() {
239
+ return this.status === VALUES.CONFIRMED.value;
240
+ }
241
+
242
+ get isArrived() {
243
+ return this.status === VALUES.ARRIVED.value;
244
+ }
245
+
246
+ get isLeaved() {
247
+ return this.status === VALUES.LEAVED.value;
248
+ }
249
+
250
+ /**
251
+ * Override `create`.
252
+ * - Ensures `docId` is fixed to allow recreation of ArrangementNotification documents.
253
+ * @param {Object} updateOptions - Options for creating the notification.
254
+ * @param {boolean} updateOptions.useAutonumber - Whether to use autonumbering.
255
+ * @param {Object} updateOptions.transaction - The Firestore transaction object.
256
+ * @param {function} updateOptions.callBack - The callback function.
257
+ * @param {string} updateOptions.prefix - The prefix.
258
+ */
259
+ async create(updateOptions = {}) {
260
+ const context = {
261
+ method: "create",
262
+ className: "ArrangementNotification",
263
+ arguments: updateOptions,
264
+ state: this.toObject(),
265
+ };
266
+ try {
267
+ if (!this.siteOperationScheduleId || !this.workerId) {
268
+ throw new Error("siteOperationScheduleId and workerId are required");
269
+ }
270
+ const docId = `${this.siteOperationScheduleId}-${this.workerId}`;
271
+ return await super.create({ ...updateOptions, docId });
272
+ } catch (error) {
273
+ throw new ContextualError(error.message, context);
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Override `update`.
279
+ * - Direct updates to ArrangementNotification are not allowed.
280
+ * - Use status transition methods instead.
281
+ * @param {Object} updateOptions - Options for updating the notification.
282
+ * @param {Object} updateOptions.transaction - The Firestore transaction object.
283
+ * @param {function} updateOptions.callBack - The callback function.
284
+ * @param {string} updateOptions.prefix - The prefix.
285
+ * @returns {Promise<void>}
286
+ */
287
+ async update(updateOptions = {}) {
288
+ // return Promise.reject(new Error("Update method is not implemented"));
289
+ const context = {
290
+ method: "update",
291
+ className: "ArrangementNotification",
292
+ arguments: updateOptions,
293
+ state: this.toObject(),
294
+ };
295
+ try {
296
+ if (this.status === VALUES.ARRANGED.value) {
297
+ await this.toArranged(updateOptions);
298
+ } else if (this.status === VALUES.CONFIRMED.value) {
299
+ await this.toConfirmed(updateOptions);
300
+ } else if (this.status === VALUES.ARRIVED.value) {
301
+ await this.toArrived(updateOptions);
302
+ } else if (this.status === VALUES.LEAVED.value) {
303
+ await this.toLeaved(updateOptions);
304
+ }
305
+ } catch (error) {
306
+ throw new ContextualError(error.message, context);
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Change status to `ARRANGED`.
312
+ * @param {Object} updateOptions - Options for updating the notification.
313
+ * @param {Object} updateOptions.transaction - The Firestore transaction object.
314
+ * @param {function} updateOptions.callBack - The callback function.
315
+ * @param {string} updateOptions.prefix - The prefix.
316
+ */
317
+ async toArranged(updateOptions = {}) {
318
+ const context = {
319
+ method: "toArranged",
320
+ className: "ArrangementNotification",
321
+ arguments: updateOptions,
322
+ state: this.toObject(),
323
+ };
324
+ try {
325
+ this.actualStartTime = this.startTime;
326
+ this.actualEndTime = this.endTime;
327
+ this.actualBreakMinutes = 60;
328
+ this.actualIsStartNextDay = this.isStartNextDay;
329
+ this.confirmedAt = null;
330
+ this.arrivedAt = null;
331
+ this.leavedAt = null;
332
+ this.status = VALUES.ARRANGED.value;
333
+ await super.update(updateOptions);
334
+ } catch (error) {
335
+ throw new ContextualError(error.message, context);
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Change status to `CONFIRMED`.
341
+ * @param {Object} updateOptions - Options for updating the notification.
342
+ * @param {Object} updateOptions.transaction - The Firestore transaction object.
343
+ * @param {function} updateOptions.callBack - The callback function.
344
+ * @param {string} updateOptions.prefix - The prefix.
345
+ */
346
+ async toConfirmed(updateOptions = {}) {
347
+ const context = {
348
+ method: "toConfirmed",
349
+ className: "ArrangementNotification",
350
+ arguments: updateOptions,
351
+ state: this.toObject(),
352
+ };
353
+ try {
354
+ this.actualStartTime = this.startTime;
355
+ this.actualEndTime = this.endTime;
356
+ this.actualBreakMinutes = 60;
357
+ this.actualIsStartNextDay = this.isStartNextDay;
358
+ this.confirmedAt = new Date();
359
+ this.arrivedAt = null;
360
+ this.leavedAt = null;
361
+ this.status = VALUES.CONFIRMED.value;
362
+ await super.update(updateOptions);
363
+ } catch (error) {
364
+ throw new ContextualError(error.message, context);
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Change status to `ARRIVED`.
370
+ * @param {Object} updateOptions - Options for updating the notification.
371
+ * @param {Object} updateOptions.transaction - The Firestore transaction object.
372
+ * @param {function} updateOptions.callBack - The callback function.
373
+ * @param {string} updateOptions.prefix - The prefix.
374
+ */
375
+ async toArrived(updateOptions = {}) {
376
+ const context = {
377
+ method: "toArrived",
378
+ className: "ArrangementNotification",
379
+ arguments: updateOptions,
380
+ state: this.toObject(),
381
+ };
382
+ try {
383
+ this.actualStartTime = this.startTime;
384
+ this.actualEndTime = this.endTime;
385
+ this.actualBreakMinutes = 60;
386
+ this.actualIsStartNextDay = this.isStartNextDay;
387
+ this.confirmedAt = this.confirmAt ? this.confirmAt : new Date();
388
+ this.arrivedAt = new Date();
389
+ this.leavedAt = null;
390
+ this.status = VALUES.ARRIVED.value;
391
+ await super.update(updateOptions);
392
+ } catch (error) {
393
+ throw new ContextualError(error.message, context);
394
+ }
395
+ }
396
+
397
+ /**
398
+ * Change status to `LEAVED`.
399
+ * @param {Object} updateOptions - Options for updating the notification.
400
+ * @param {Object} updateOptions.transaction - The Firestore transaction object.
401
+ * @param {function} updateOptions.callBack - The callback function.
402
+ * @param {string} updateOptions.prefix - The prefix.
403
+ */
404
+ async toLeaved(updateOptions = {}, timeOptions = {}) {
405
+ const context = {
406
+ method: "toLeaved",
407
+ className: "ArrangementNotification",
408
+ arguments: { ...timeOptions, ...updateOptions },
409
+ state: this.toObject(),
410
+ };
411
+ try {
412
+ this.confirmedAt = this.confirmAt ? this.confirmAt : new Date();
413
+ this.arrivedAt = this.arrivedAt ? this.arrivedAt : new Date();
414
+ this.leavedAt = new Date();
415
+ this.status = VALUES.LEAVED.value;
416
+ await super.update(updateOptions);
417
+ } catch (error) {
418
+ throw new ContextualError(error.message, context);
419
+ }
420
+ }
421
+
422
+ /**
423
+ * Fetch ArrangementNotification documents by site operation schedule ID.
424
+ * @param {string} id - The site operation schedule ID.
425
+ * @returns {Promise<Array>} - The fetched documents.
426
+ */
427
+ static async fetchDocsBySiteOperationScheduleId(id) {
428
+ const context = {
429
+ method: "fetchDocsBySiteOperationScheduleId",
430
+ className: "ArrangementNotification",
431
+ arguments: { id },
432
+ };
433
+ try {
434
+ const instance = new ArrangementNotification();
435
+ const constraints = [["where", "siteOperationScheduleId", "==", id]];
436
+ const result = await instance.fetchDocs({ constraints });
437
+
438
+ return result;
439
+ } catch (error) {
440
+ throw new ContextualError(error.message, context);
441
+ }
442
+ }
443
+
444
+ /**
445
+ * Delete multiple ArrangementNotification documents by their IDs.
446
+ * - If a transaction is provided, the deletions will be performed within that transaction.
447
+ * - If no transaction is provided, a new transaction will be created for the deletions.
448
+ * - If the `workerIds` array is empty, the method will return immediately without performing any operations.
449
+ * @param {Object} options - Options for bulk deletion.
450
+ * @param {string} options.siteOperationScheduleId - The site operation schedule ID.
451
+ * @param {Array<string>} [options.workerIds=[]] - An array of worker IDs whose notifications should be deleted.
452
+ * @param {Object} [transaction] - The Firestore transaction object.
453
+ * @returns {Promise<void>}
454
+ */
455
+ static async bulkDelete(options = {}, transaction) {
456
+ const context = {
457
+ method: "bulkDelete",
458
+ className: "ArrangementNotification",
459
+ arguments: { ...options, transaction },
460
+ };
461
+ try {
462
+ const { siteOperationScheduleId, workerIds = [] } = options;
463
+ if (!siteOperationScheduleId) {
464
+ throw new Error("siteOperationScheduleId is required");
465
+ }
466
+
467
+ if (!Array.isArray(workerIds)) {
468
+ throw new Error("workerIds must be an array");
469
+ }
470
+
471
+ const performTransaction = async (txn) => {
472
+ // Delete all notification documents if workerIds is empty.
473
+ if (workerIds.length === 0) {
474
+ const fn = ArrangementNotification.fetchDocsBySiteOperationScheduleId;
475
+ const docs = await fn(siteOperationScheduleId);
476
+ if (docs.length === 0) return;
477
+ await Promise.all(
478
+ docs.map((doc) => doc.delete({ transaction: txn }))
479
+ );
480
+ }
481
+
482
+ // Delete specific notification documents if workerIds are provided.
483
+ else {
484
+ const docIds = workerIds.map(
485
+ (id) => `${siteOperationScheduleId}-${id}`
486
+ );
487
+ const promises = docIds.map((id) => {
488
+ const instance = new ArrangementNotification({ docId: id });
489
+ return instance.delete({ transaction: txn });
490
+ });
491
+ await Promise.all(promises);
492
+ }
493
+ };
494
+
495
+ if (transaction) {
496
+ await performTransaction(transaction);
497
+ } else {
498
+ const firestore = this.getAdapter().firestore;
499
+ await runTransaction(firestore, performTransaction);
500
+ }
501
+ } catch (error) {
502
+ throw new ContextualError(error.message, context);
503
+ }
504
+ }
505
+ }
package/src/Billing.js ADDED
@@ -0,0 +1,159 @@
1
+ /*****************************************************************************
2
+ * Billing
3
+ * @version 1.0.0
4
+ * @description A model for billing data.
5
+ * @author shisyamo4131
6
+ *
7
+ * @prop {string} customerId - customer document id
8
+ * @prop {string} siteId - site document id
9
+ * @prop {string} billingMonth - billing month (YYYY-MM format)
10
+ * @prop {Date} billingDate - billing date
11
+ * @prop {Date} paymentDueDate - payment due date
12
+ * @prop {string} status - status (DRAFT/CONFIRMED/PAID/CANCELLED)
13
+ * @prop {Array<OperationResult>} items - operation result items (raw data)
14
+ * @prop {Object} adjustment - adjustment
15
+ * @prop {string} remarks - remarks
16
+ * @prop {Object} pdfInfo - PDF information
17
+ *
18
+ * @computed {number} subtotal - subtotal (excluding tax)
19
+ * @computed {number} taxAmount - tax amount
20
+ * @computed {number} totalAmount - total amount (including tax)
21
+ * @computed {Array<Object>} itemsSummary - billing items summary for display
22
+ *****************************************************************************/
23
+
24
+ import FireModel from "@shisyamo4131/air-firebase-v2";
25
+ import { defField } from "./parts/fieldDefinitions.js";
26
+ import OperationResult from "./OperationResult.js";
27
+
28
+ const STATUS = {
29
+ DRAFT: "DRAFT",
30
+ CONFIRMED: "CONFIRMED",
31
+ PAID: "PAID",
32
+ CANCELLED: "CANCELLED",
33
+ };
34
+
35
+ const classProps = {
36
+ customerId: defField("customerId", { required: true }),
37
+ siteId: defField("siteId", { required: true }),
38
+ billingMonth: defField("oneLine", { required: true }),
39
+ billingDate: defField("date"),
40
+ paymentDueDate: defField("date"),
41
+ status: defField("oneLine", { default: STATUS.DRAFT }),
42
+
43
+ // OperationResult の生データを配列で保持
44
+ items: defField("array", { customClass: OperationResult }),
45
+
46
+ adjustment: defField("object", {
47
+ default: {
48
+ amount: 0,
49
+ description: "",
50
+ },
51
+ }),
52
+
53
+ remarks: defField("multipleLine"),
54
+
55
+ pdfInfo: defField("object", {
56
+ default: {
57
+ url: null,
58
+ generatedAt: null,
59
+ version: 1,
60
+ },
61
+ }),
62
+ };
63
+
64
+ export default class Billing extends FireModel {
65
+ static className = "請求";
66
+ static collectionPath = "Billings";
67
+ static useAutonumber = false;
68
+ static logicalDelete = false;
69
+ static classProps = classProps;
70
+ static STATUS = STATUS;
71
+
72
+ afterInitialize(item = {}) {
73
+ super.afterInitialize(item);
74
+
75
+ // 小計(税抜)を計算
76
+ Object.defineProperty(this, "subtotal", {
77
+ get() {
78
+ const itemsTotal = this.items.reduce((sum, item) => {
79
+ return sum + (item.salesAmount || 0);
80
+ }, 0);
81
+ return itemsTotal + (this.adjustment?.amount || 0);
82
+ },
83
+ set() {},
84
+ enumerable: true,
85
+ });
86
+
87
+ // 消費税額を計算
88
+ Object.defineProperty(this, "taxAmount", {
89
+ get() {
90
+ return Math.floor(this.subtotal * 0.1); // 10% 切り捨て
91
+ },
92
+ set() {},
93
+ enumerable: true,
94
+ });
95
+
96
+ // 合計金額(税込)を計算
97
+ Object.defineProperty(this, "totalAmount", {
98
+ get() {
99
+ return this.subtotal + this.taxAmount;
100
+ },
101
+ set() {},
102
+ enumerable: true,
103
+ });
104
+
105
+ // 表示用の明細サマリーを生成
106
+ Object.defineProperty(this, "itemsSummary", {
107
+ get() {
108
+ return this.items.map((item) => ({
109
+ operationResultId: item.docId,
110
+ workDate: item.dateAt,
111
+ shiftType: item.shiftType,
112
+ dayType: item.dayType,
113
+ base: {
114
+ quantity: item.statistics?.base?.quantity || 0,
115
+ unitPrice: item.unitPriceBase || 0,
116
+ regularAmount: item.sales?.base?.regularAmount || 0,
117
+ overtimeMinutes: item.statistics?.base?.overtimeMinutes || 0,
118
+ overtimeUnitPrice: item.overtimeUnitPriceBase || 0,
119
+ overtimeAmount: item.sales?.base?.overtimeAmount || 0,
120
+ total: item.sales?.base?.total || 0,
121
+ },
122
+ qualified: {
123
+ quantity: item.statistics?.qualified?.quantity || 0,
124
+ unitPrice: item.unitPriceQualified || 0,
125
+ regularAmount: item.sales?.qualified?.regularAmount || 0,
126
+ overtimeMinutes: item.statistics?.qualified?.overtimeMinutes || 0,
127
+ overtimeUnitPrice: item.overtimeUnitPriceQualified || 0,
128
+ overtimeAmount: item.sales?.qualified?.overtimeAmount || 0,
129
+ total: item.sales?.qualified?.total || 0,
130
+ },
131
+ subtotal: item.salesAmount || 0,
132
+ remarks: item.remarks || "",
133
+ }));
134
+ },
135
+ set() {},
136
+ enumerable: true,
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Confirm the billing
142
+ */
143
+ confirm() {
144
+ if (this.status !== STATUS.DRAFT) {
145
+ throw new Error("Only draft billings can be confirmed");
146
+ }
147
+ this.status = STATUS.CONFIRMED;
148
+ }
149
+
150
+ /**
151
+ * Mark as paid
152
+ */
153
+ markAsPaid() {
154
+ if (this.status !== STATUS.CONFIRMED) {
155
+ throw new Error("Only confirmed billings can be marked as paid");
156
+ }
157
+ this.status = STATUS.PAID;
158
+ }
159
+ }