@shisyamo4131/air-guard-v2-schemas 2.3.7-dev.4 → 2.3.7-dev.40

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.
package/src/Employee.js CHANGED
@@ -6,7 +6,9 @@
6
6
  import FireModel from "@shisyamo4131/air-firebase-v2";
7
7
  import { defField } from "./parts/fieldDefinitions.js";
8
8
  import { defAccessor } from "./parts/accessorDefinitions.js";
9
- import { VALUES } from "./constants/employment-status.js";
9
+ import { VALUES as EMPLOYMENT_STATUS_VALUES } from "./constants/employment-status.js";
10
+ import { VALUES as BLOOD_TYPE_VALUES } from "./constants/blood-type.js";
11
+ import Certification from "./Certification.js";
10
12
 
11
13
  const classProps = {
12
14
  code: defField("code", { label: "従業員コード" }),
@@ -15,92 +17,204 @@ const classProps = {
15
17
  lastNameKana: defField("lastNameKana", { required: true }),
16
18
  firstNameKana: defField("firstNameKana", { required: true }),
17
19
  displayName: defField("displayName", { required: true }),
18
- title: defField("oneLine", { label: "肩書", required: true }),
19
20
  gender: defField("gender", { required: true }),
20
- dateOfBirth: defField("dateAt", { label: "生年月日", required: true }),
21
+ dateOfBirth: defField("dateOfBirth", { required: true }),
21
22
  zipcode: defField("zipcode", { required: true }),
22
23
  prefCode: defField("prefCode", { required: true }),
23
24
  city: defField("city", { required: true }),
24
25
  address: defField("address", { required: true }),
25
26
  building: defField("building"),
26
- location: defField("location", { hidden: true }), // 非表示でOK
27
- dateOfHire: defField("dateAt", { label: "入社日", required: true }),
27
+ location: defField("location", { hidden: true }),
28
+ mobile: defField("mobile", { required: true }),
29
+ email: defField("email", { required: false }),
30
+ dateOfHire: defField("dateOfHire", { required: true }),
28
31
  employmentStatus: defField("employmentStatus", { required: true }),
29
- dateOfTermination: defField("dateAt", {
30
- label: "退職日",
32
+ title: defField("title"),
33
+ dateOfTermination: defField("dateOfTermination", {
34
+ default: null,
35
+ component: {
36
+ attrs: {
37
+ required: ({ item }) =>
38
+ item.employmentStatus === EMPLOYMENT_STATUS_VALUES.TERMINATED.value,
39
+ disabled: ({ item }) =>
40
+ item.employmentStatus !== EMPLOYMENT_STATUS_VALUES.TERMINATED.value,
41
+ },
42
+ },
43
+ }),
44
+ reasonOfTermination: defField("reasonOfTermination", {
31
45
  component: {
32
46
  attrs: {
33
- required: (item) => item.employmentStatus === "terminated",
47
+ required: ({ item }) =>
48
+ item.employmentStatus === EMPLOYMENT_STATUS_VALUES.TERMINATED.value,
49
+ disabled: ({ item }) =>
50
+ item.employmentStatus !== EMPLOYMENT_STATUS_VALUES.TERMINATED.value,
34
51
  },
35
52
  },
36
53
  }),
54
+
55
+ // Foreign related fields
37
56
  isForeigner: defField("isForeigner"),
38
57
  foreignName: defField("foreignName", {
39
58
  component: {
40
59
  attrs: {
41
- required: (item) => item.isForeigner,
60
+ required: ({ item }) => item.isForeigner,
61
+ disabled: ({ item }) => !item.isForeigner,
42
62
  },
43
63
  },
44
64
  }),
45
65
  nationality: defField("nationality", {
46
66
  component: {
47
67
  attrs: {
48
- required: (item) => item.isForeigner,
68
+ required: ({ item }) => item.isForeigner,
69
+ disabled: ({ item }) => !item.isForeigner,
49
70
  },
50
71
  },
51
72
  }),
52
- residenceStatus: {
53
- type: String,
73
+ residenceStatus: defField("residenceStatus", {
74
+ component: {
75
+ attrs: {
76
+ required: ({ item }) => item.isForeigner,
77
+ disabled: ({ item }) => !item.isForeigner,
78
+ },
79
+ },
80
+ }),
81
+ periodOfStay: defField("periodOfStay", {
82
+ component: {
83
+ attrs: {
84
+ required: ({ item }) => item.isForeigner,
85
+ disabled: ({ item }) => !item.isForeigner,
86
+ },
87
+ },
88
+ }),
89
+
90
+ // Security guard related fields
91
+ hasSecurityGuardRegistration: defField("check", { label: "警備員登録" }),
92
+ dateOfSecurityGuardRegistration: defField("dateAt", {
93
+ label: "警備員登録日",
54
94
  default: null,
55
- label: "在留資格",
56
- required: undefined,
57
95
  component: {
58
- name: "air-text-field",
59
96
  attrs: {
60
- required: (item) => item.isForeigner,
97
+ required: ({ item }) => item.hasSecurityGuardRegistration,
98
+ disabled: ({ item }) => !item.hasSecurityGuardRegistration,
99
+ },
100
+ },
101
+ }),
102
+ bloodType: defField("bloodType", {
103
+ component: {
104
+ attrs: {
105
+ required: ({ item }) => item.hasSecurityGuardRegistration,
106
+ disabled: ({ item }) => !item.hasSecurityGuardRegistration,
61
107
  },
62
108
  },
63
- },
64
- periodOfStay: defField("dateAt", {
65
- label: "在留期間満了日",
109
+ }),
110
+ emergencyContactName: defField("emergencyContactName", {
111
+ component: {
112
+ attrs: {
113
+ required: ({ item }) => item.hasSecurityGuardRegistration,
114
+ disabled: ({ item }) => !item.hasSecurityGuardRegistration,
115
+ },
116
+ },
117
+ }),
118
+ emergencyContactRelation: defField("emergencyContactRelation", {
66
119
  component: {
67
120
  attrs: {
68
- required: (item) => item.isForeigner,
121
+ required: ({ item }) => item.hasSecurityGuardRegistration,
122
+ disabled: ({ item }) => !item.hasSecurityGuardRegistration,
69
123
  },
70
124
  },
71
125
  }),
72
- remarks: defField("multipleLine", { label: "備考" }),
126
+ emergencyContactRelationDetail: defField("emergencyContactRelationDetail", {
127
+ component: {
128
+ attrs: {
129
+ required: ({ item }) => item.hasSecurityGuardRegistration,
130
+ disabled: ({ item }) => !item.hasSecurityGuardRegistration,
131
+ },
132
+ },
133
+ }),
134
+ emergencyContactAddress: defField("emergencyContactAddress", {
135
+ component: {
136
+ attrs: {
137
+ required: ({ item }) => item.hasSecurityGuardRegistration,
138
+ disabled: ({ item }) => !item.hasSecurityGuardRegistration,
139
+ },
140
+ },
141
+ }),
142
+ emergencyContactPhone: defField("emergencyContactPhone", {
143
+ component: {
144
+ attrs: {
145
+ required: ({ item }) => item.hasSecurityGuardRegistration,
146
+ disabled: ({ item }) => !item.hasSecurityGuardRegistration,
147
+ },
148
+ },
149
+ }),
150
+ domicile: defField("domicile", {
151
+ component: {
152
+ attrs: {
153
+ required: ({ item }) => item.hasSecurityGuardRegistration,
154
+ disabled: ({ item }) => !item.hasSecurityGuardRegistration,
155
+ },
156
+ },
157
+ }),
158
+ securityCertifications: defField("array", {
159
+ label: "保有資格",
160
+ customClass: Certification,
161
+ }),
162
+ remarks: defField("remarks"),
73
163
  };
74
164
 
75
165
  /*****************************************************************************
76
- * Employee Model
77
- * @props {string} code - Employee code.
78
- * @props {string} lastName - Last name.
79
- * @props {string} firstName - First name.
80
- * @props {string} lastNameKana - Last name in Kana.
81
- * @props {string} firstNameKana - First name in Kana.
82
- * @props {string} displayName - Display name.
83
- * @props {string} title - Job title.
84
- * @props {string} gender - Gender.
85
- * @props {Date} dateOfBirth - Date of birth.
86
- * @props {string} zipcode - Postal code.
87
- * @props {string} prefCode - Prefecture code.
88
- * @props {string} city - City name.
89
- * @props {string} address - Address details.
90
- * @props {string} building - Building name.
91
- * @props {object} location - Geographical location.
92
- * @props {Date} dateOfHire - Date of hire.
93
- * @props {string} employmentStatus - Employment status.
94
- * @props {Date} dateOfTermination - Date of termination.
95
- * @props {boolean} isForeigner - Is the employee a foreigner.
96
- * @props {string} foreignName - Foreign name.
97
- * @props {string} nationality - Nationality.
98
- * @props {string} residenceStatus - Residence status.
99
- * @props {Date} periodOfStay - Period of stay expiration date.
100
- * @props {string} remarks - Additional remarks.
101
- * @computed {string} fullName - Full name combining last and first names (read-only)
102
- * @computed {string} fullAddress - Full address combining prefecture, city, and address (read-only)
103
- * @computed {string} prefecture - Prefecture name derived from `prefCode` (read-only)
166
+ * @prop {string} code - Employee code.
167
+ * @prop {string} lastName - Last name.
168
+ * @prop {string} firstName - First name.
169
+ * @prop {string} lastNameKana - Last name in Kana.
170
+ * @prop {string} firstNameKana - First name in Kana.
171
+ * @prop {string} displayName - Display name.
172
+ * @prop {string} gender - Gender.
173
+ * @prop {Date} dateOfBirth - Date of birth.
174
+ * @prop {string} zipcode - Postal code.
175
+ * @prop {string} prefCode - Prefecture code.
176
+ * @prop {string} city - City name.
177
+ * @prop {string} address - Address details.
178
+ * @prop {string} building - Building name.
179
+ * @prop {object} location - Geographical location.
180
+ * @prop {string} mobile - Mobile phone number.
181
+ * @prop {string} email - Email address.
182
+ * @prop {Date} dateOfHire - Date of hire.
183
+ * @prop {string} employmentStatus - Employment status.
184
+ * @prop {string} title - Job title.
185
+ * @prop {Date} dateOfTermination - Date of termination.
186
+ * @prop {string} reasonOfTermination - Reason for termination.
187
+ * @prop {boolean} isForeigner - Is the employee a foreigner.
188
+ * @prop {string} foreignName - Foreign name.
189
+ * @prop {string} nationality - Nationality.
190
+ * @prop {string} residenceStatus - Residence status.
191
+ * @prop {Date} periodOfStay - Period of stay expiration date.
192
+ * @prop {boolean} hasSecurityGuardRegistration - Has security guard registration.
193
+ * @prop {Date} dateOfSecurityGuardRegistration - Date of security guard registration.
194
+ * @prop {string} bloodType - Blood type.
195
+ * @prop {string} emergencyContactName - Emergency contact name.
196
+ * @prop {string} emergencyContactRelation - Emergency contact relation.
197
+ * @prop {string} emergencyContactRelationDetail - Emergency contact relation detail.
198
+ * @prop {string} emergencyContactAddress - Emergency contact address.
199
+ * @prop {string} emergencyContactPhone - Emergency contact phone number.
200
+ * @prop {string} domicile - Domicile.
201
+ * @prop {Array<Certification>} securityCertifications - Array of security certifications.
202
+ * @prop {string} remarks - Additional remarks.
203
+ *
204
+ * @prop {string} fullName - Full name combining last and first names (read-only)
205
+ * @prop {string} fullNameKana - Full name in Kana combining last and first names (read-only)
206
+ * @prop {string} fullAddress - Full address combining prefecture, city, and address (read-only)
207
+ * @prop {string} prefecture - Prefecture name derived from `prefCode` (read-only)
208
+ *
209
+ * @getter
210
+ * @prop {number} age - Age calculated from `dateOfBirth` (read-only)
211
+ * @prop {number} yearsOfService - Years of service calculated from `dateOfHire` (read-only)
212
+ *
213
+ * @static
214
+ * @prop {string} STATUS_ACTIVE - constant for active employment status
215
+ * @prop {string} STATUS_TERMINATED - constant for terminated employment status
216
+ *
217
+ * @function toTerminated - Change the current employee instance to terminated status.
104
218
  *****************************************************************************/
105
219
  export default class Employee extends FireModel {
106
220
  static className = "従業員";
@@ -109,6 +223,7 @@ export default class Employee extends FireModel {
109
223
  static logicalDelete = true;
110
224
  static classProps = classProps;
111
225
  static tokenFields = [
226
+ "code",
112
227
  "lastName",
113
228
  "firstName",
114
229
  "lastNameKana",
@@ -122,21 +237,90 @@ export default class Employee extends FireModel {
122
237
  { title: "名前", key: "fullName" },
123
238
  ];
124
239
 
125
- static STATUS_ACTIVE = VALUES.ACTIVE.value;
126
- static STATUS_TERMINATED = VALUES.TERMINATED.value;
240
+ static STATUS_ACTIVE = EMPLOYMENT_STATUS_VALUES.ACTIVE.value;
241
+ static STATUS_TERMINATED = EMPLOYMENT_STATUS_VALUES.TERMINATED.value;
242
+
243
+ constructor(item = {}) {
244
+ super(item);
245
+
246
+ // Internal flag to skip terminated status check during toTerminated method
247
+ Object.defineProperty(this, "_skipToTerminatedCheck", {
248
+ writable: true,
249
+ enumerable: false,
250
+ configurable: true,
251
+ value: false,
252
+ });
253
+ }
127
254
 
128
255
  afterInitialize(item = {}) {
129
256
  super.afterInitialize(item);
130
257
  Object.defineProperties(this, {
131
258
  fullName: defAccessor("fullName"),
259
+ fullNameKana: defAccessor("fullNameKana"),
132
260
  fullAddress: defAccessor("fullAddress"),
133
261
  prefecture: defAccessor("prefecture"),
134
262
  });
135
263
  }
136
264
 
265
+ /**
266
+ * 生年月日から年齢を計算します。
267
+ * @returns {{years: number, months: number}|null} 年齢(年数と月数)。dateOfBirthが設定されていない場合はnull。
268
+ */
269
+ get age() {
270
+ if (!this.dateOfBirth) return null;
271
+ const today = new Date();
272
+
273
+ let years = today.getUTCFullYear() - this.dateOfBirth.getUTCFullYear();
274
+ let months = today.getUTCMonth() - this.dateOfBirth.getUTCMonth();
275
+ const days = today.getUTCDate() - this.dateOfBirth.getUTCDate();
276
+
277
+ if (days < 0) {
278
+ months--;
279
+ }
280
+
281
+ if (months < 0) {
282
+ years--;
283
+ months += 12;
284
+ }
285
+
286
+ return { years, months };
287
+ }
288
+
289
+ /**
290
+ * 入社日からの勤続年数を計算します。
291
+ * - 退職日が設定されている場合は、退職日までの勤続年数を計算します。
292
+ * @returns {{years: number, months: number}|null} 勤続年数(年数と月数)。dateOfHireが設定されていない場合はnull。
293
+ */
294
+ get yearsOfService() {
295
+ if (!this.dateOfHire) return null;
296
+ const today = this.dateOfTermination || new Date();
297
+
298
+ let years = today.getUTCFullYear() - this.dateOfHire.getUTCFullYear();
299
+ let months = today.getUTCMonth() - this.dateOfHire.getUTCMonth();
300
+ const days = today.getUTCDate() - this.dateOfHire.getUTCDate();
301
+
302
+ if (days < 0) {
303
+ months--;
304
+ }
305
+
306
+ if (months < 0) {
307
+ years--;
308
+ months += 12;
309
+ }
310
+
311
+ return { years, months };
312
+ }
313
+
137
314
  /**
138
315
  * 外国籍の場合の必須フィールドを検証します。
139
- * エラーがある場合は例外をスローします。
316
+ * - エラーがある場合は例外をスローします。
317
+ * - `isForeigner` が false の場合、以下のプロパティを初期化します。
318
+ * - `foreignName`
319
+ * - `nationality`
320
+ * - `residenceStatus`
321
+ * - `periodOfStay`
322
+ * @returns {void}
323
+ * @throws {Error} 外国籍の場合に必須フィールドが未入力の場合。
140
324
  */
141
325
  _validateForeignerRequiredFields() {
142
326
  if (this.isForeigner) {
@@ -160,20 +344,84 @@ export default class Employee extends FireModel {
160
344
  "[Employee.js] periodOfStay is required when isForeigner is true."
161
345
  );
162
346
  }
347
+ } else {
348
+ // 外国籍でない場合、関連フィールドを初期化
349
+ this.foreignName = null;
350
+ this.nationality = null;
351
+ this.residenceStatus = null;
352
+ this.periodOfStay = null;
163
353
  }
164
354
  }
165
355
 
166
356
  /**
167
357
  * 退職済である場合の必須フィールドを検証します。
168
- * エラーがある場合は例外をスローします。
358
+ * - エラーがある場合は例外をスローします。
359
+ * - `employmentStatus` が `terminated` の場合、以下のプロパティを必須とします。
360
+ * - `dateOfTermination`
361
+ * - `reasonOfTermination`
362
+ * - `employmentStatus` が `active` の場合、`dateOfTermination`, `reasonOfTermination` を初期化します。
363
+ * @returns {void}
364
+ * @throws {Error} 退職済の場合に必須フィールドが未入力の場合。
169
365
  */
170
366
  _validateTerminatedRequiredFields() {
171
- if (this.employmentStatus === "terminated") {
367
+ if (this.employmentStatus === EMPLOYMENT_STATUS_VALUES.TERMINATED.value) {
172
368
  if (!this.dateOfTermination) {
173
369
  throw new Error(
174
370
  "[Employee.js] dateOfTermination is required when employmentStatus is 'terminated'."
175
371
  );
176
372
  }
373
+ if (!this.reasonOfTermination) {
374
+ throw new Error(
375
+ "[Employee.js] reasonOfTermination is required when employmentStatus is 'terminated'."
376
+ );
377
+ }
378
+ } else {
379
+ this.dateOfTermination = null;
380
+ this.reasonOfTermination = null;
381
+ }
382
+ }
383
+
384
+ _validateSecurityGuardFields() {
385
+ if (this.hasSecurityGuardRegistration) {
386
+ if (!this.dateOfSecurityGuardRegistration) {
387
+ throw new Error(
388
+ "[Employee.js] dateOfSecurityGuardRegistration is required when hasSecurityGuardRegistration is true."
389
+ );
390
+ }
391
+ if (!this.emergencyContactName) {
392
+ throw new Error(
393
+ "[Employee.js] emergencyContactName is required when hasSecurityGuardRegistration is true."
394
+ );
395
+ }
396
+ if (!this.emergencyContactRelationDetail) {
397
+ throw new Error(
398
+ "[Employee.js] emergencyContactRelationDetail is required when hasSecurityGuardRegistration is true."
399
+ );
400
+ }
401
+ if (!this.emergencyContactAddress) {
402
+ throw new Error(
403
+ "[Employee.js] emergencyContactAddress is required when hasSecurityGuardRegistration is true."
404
+ );
405
+ }
406
+ if (!this.emergencyContactPhone) {
407
+ throw new Error(
408
+ "[Employee.js] emergencyContactPhone is required when hasSecurityGuardRegistration is true."
409
+ );
410
+ }
411
+ if (!this.domicile) {
412
+ throw new Error(
413
+ "[Employee.js] domicile is required when hasSecurityGuardRegistration is true."
414
+ );
415
+ }
416
+ } else {
417
+ this.dateOfSecurityGuardRegistration = null;
418
+ this.bloodType = BLOOD_TYPE_VALUES.A.value;
419
+ this.emergencyContactName = null;
420
+ this.emergencyContactRelation = null;
421
+ this.emergencyContactRelationDetail = null;
422
+ this.emergencyContactAddress = null;
423
+ this.emergencyContactPhone = null;
424
+ this.domicile = null;
177
425
  }
178
426
  }
179
427
 
@@ -181,21 +429,98 @@ export default class Employee extends FireModel {
181
429
  * 新しい従業員ドキュメントが作成される前に実行されるフック。
182
430
  * - 親クラスの `beforeCreate` を呼び出します。
183
431
  * - 従業員が外国人の場合、外国人名と国籍が未入力であればエラーをスローします。
432
+ * @param {Object} args - Creation options.
433
+ * @param {string} [args.docId] - Document ID to use (optional).
434
+ * @param {boolean} [args.useAutonumber=true] - Whether to use auto-numbering.
435
+ * @param {Object} [args.transaction] - Firestore transaction.
436
+ * @param {Function} [args.callBack] - Callback function.
437
+ * @param {string} [args.prefix] - Path prefix.
184
438
  */
185
- async beforeCreate() {
186
- await super.beforeCreate();
439
+ async beforeCreate(args = {}) {
440
+ await super.beforeCreate(args);
187
441
  this._validateForeignerRequiredFields();
188
442
  this._validateTerminatedRequiredFields();
443
+ this._validateSecurityGuardFields();
189
444
  }
190
445
 
191
446
  /**
192
447
  * 従業員ドキュメントが更新される前に実行されるフック。
193
448
  * - 親クラスの `beforeUpdate` を呼び出します。
194
449
  * - 従業員が外国人の場合、外国人名と国籍が未入力であればエラーをスローします。
450
+ * @param {Object} args - Creation options.
451
+ * @param {Object} [args.transaction] - Firestore transaction.
452
+ * @param {Function} [args.callBack] - Callback function.
453
+ * @param {string} [args.prefix] - Path prefix.
195
454
  */
196
- async beforeUpdate() {
197
- await super.beforeUpdate();
455
+ async beforeUpdate(args = {}) {
456
+ await super.beforeUpdate(args);
457
+
458
+ // `employmentStatus` の `terminated` への直接変更の禁止
459
+ // - 従業員を退職させる場合、様々なチェックが必要になることが想定されるため、専用メソッドとして `toTerminated` を使用する。
460
+ // - `employmentStatus` を `terminated` に変更する場合は、必ず `toTerminated` メソッドを使用すること。
461
+ // - 一度退職処理した従業員の復帰処理は現状想定していないが、将来的に必要になった場合は `toActive` メソッド等を追加実装すること。
462
+ if (
463
+ !this._skipToTerminatedCheck &&
464
+ this.employmentStatus === Employee.STATUS_TERMINATED &&
465
+ this._beforeData.employmentStatus === Employee.STATUS_ACTIVE
466
+ ) {
467
+ throw new Error(
468
+ "[Employee.js] Direct changes to employmentStatus to 'terminated' are not allowed. Use toTerminated() method instead."
469
+ );
470
+ }
471
+
198
472
  this._validateForeignerRequiredFields();
199
473
  this._validateTerminatedRequiredFields();
474
+ this._validateSecurityGuardFields();
475
+ }
476
+
477
+ /**
478
+ * 現在インスタンスに読み込まれている従業員を退職状態に変更します。
479
+ * @param {Date} dateOfTermination - 退職日(Dateオブジェクト)
480
+ * @param {string} reasonOfTermination - 退職理由
481
+ * @param {Object} options - パラメータオブジェクト
482
+ * @param {Function|null} [options.transaction=null] - Firestore トランザクション関数
483
+ * @param {Function|null} [options.callBack=null] - カスタム処理用コールバック
484
+ * @param {string|null} [options.prefix=null] - パスのプレフィックス
485
+ * @returns {Promise<DocumentReference>} 更新されたドキュメントの参照
486
+ * @throws {Error} docIdが存在しない場合、または有効なdateOfTerminationが提供されていない場合。
487
+ */
488
+ async toTerminated(dateOfTermination, reasonOfTermination, options = {}) {
489
+ if (!this.docId) {
490
+ throw new Error(
491
+ "[Employee.js] docId is required to terminate an employee."
492
+ );
493
+ }
494
+ if (!dateOfTermination || !(dateOfTermination instanceof Date)) {
495
+ throw new Error(
496
+ "[Employee.js] A valid dateOfTermination is required to terminate an employee."
497
+ );
498
+ }
499
+ if (dateOfTermination < this.dateOfHire) {
500
+ throw new Error(
501
+ "[Employee.js] dateOfTermination cannot be earlier than dateOfHire."
502
+ );
503
+ }
504
+
505
+ if (!reasonOfTermination || typeof reasonOfTermination !== "string") {
506
+ throw new Error(
507
+ "[Employee.js] A valid reasonOfTermination is required to terminate an employee."
508
+ );
509
+ }
510
+
511
+ this.employmentStatus = Employee.STATUS_TERMINATED;
512
+ this.dateOfTermination = dateOfTermination;
513
+ this.reasonOfTermination = reasonOfTermination;
514
+
515
+ this._skipToTerminatedCheck = true;
516
+
517
+ try {
518
+ return await this.update(options);
519
+ } catch (error) {
520
+ this.rollback();
521
+ throw error;
522
+ } finally {
523
+ this._skipToTerminatedCheck = false;
524
+ }
200
525
  }
201
526
  }
@@ -181,7 +181,6 @@
181
181
  * @method delete - Override delete method to allow deletion even when isLocked is true
182
182
  *****************************************************************************/
183
183
  import OperationResult from "./OperationResult.js";
184
- import Operation from "./Operation.js";
185
184
 
186
185
  export default class OperationBilling extends OperationResult {
187
186
  static className = "稼働請求";
@@ -192,28 +191,43 @@ export default class OperationBilling extends OperationResult {
192
191
  { title: "売上金額", key: "salesAmount", value: "salesAmount" },
193
192
  ];
194
193
 
195
- async create() {
196
- return Promise.reject(new Error("[OperationBilling.js] Not implemented."));
194
+ /**
195
+ * Override beforeUpdate to skip `isLocked` check and sync customerId and apply agreement if key changed
196
+ * @param {Object} args - Creation options.
197
+ * @param {Object} [args.transaction] - Firestore transaction.
198
+ * @param {Function} [args.callBack] - Callback function.
199
+ * @param {string} [args.prefix] - Path prefix.
200
+ * @returns {Promise<void>}
201
+ */
202
+ async beforeUpdate(args = {}) {
203
+ await super.beforeUpdate(args);
204
+ // Sync customerId and apply agreement if key changed
205
+ if (this.key === this._beforeData.key) return;
206
+ await this._syncCustomerIdAndApplyAgreement(args);
197
207
  }
198
208
 
199
209
  /**
200
- * Override update method to allow editing even when isLocked is true
201
- * @param {*} options
202
- * @returns {Promise<void>}
210
+ * Override create method to disallow creation of OperationBilling instances
211
+ * @returns
203
212
  */
204
- async update(options = {}) {
205
- // isLockedのチェックをスキップして、親クラス(Operation)のupdateを直接呼び出す
206
- return await Operation.prototype.update.call(this, options);
213
+ async create() {
214
+ return Promise.reject(
215
+ new Error(
216
+ "[OperationBilling.js] Creation of OperationBilling is not implemented."
217
+ )
218
+ );
207
219
  }
208
220
 
209
221
  /**
210
- * Override delete method to allow deletion even when isLocked is true
211
- * @param {*} options
222
+ * Override delete method to disallow deletion of OperationBilling instances
212
223
  * @returns {Promise<void>}
213
224
  */
214
- async delete(options = {}) {
215
- // isLockedのチェックをスキップして、親クラス(Operation)のdeleteを直接呼び出す
216
- return await Operation.prototype.delete.call(this, options);
225
+ async delete() {
226
+ return Promise.reject(
227
+ new Error(
228
+ "[OperationBilling.js] Deletion of OperationBilling is not implemented."
229
+ )
230
+ );
217
231
  }
218
232
 
219
233
  /**