@shisyamo4131/air-guard-v2-schemas 2.3.7-dev.5 → 2.3.7-dev.51

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"),
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,
70
+ },
71
+ },
72
+ }),
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,
49
86
  },
50
87
  },
51
88
  }),
52
- residenceStatus: {
53
- type: String,
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,
107
+ },
108
+ },
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", {
119
+ component: {
120
+ attrs: {
121
+ required: ({ item }) => item.hasSecurityGuardRegistration,
122
+ disabled: ({ item }) => !item.hasSecurityGuardRegistration,
123
+ },
124
+ },
125
+ }),
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,
61
147
  },
62
148
  },
63
- },
64
- periodOfStay: defField("dateAt", {
65
- label: "在留期間満了日",
149
+ }),
150
+ domicile: defField("domicile", {
66
151
  component: {
67
152
  attrs: {
68
- required: (item) => item.isForeigner,
153
+ required: ({ item }) => item.hasSecurityGuardRegistration,
154
+ disabled: ({ item }) => !item.hasSecurityGuardRegistration,
69
155
  },
70
156
  },
71
157
  }),
72
- remarks: defField("multipleLine", { label: "備考" }),
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,124 @@ 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);
257
+
258
+ // Define computed properties
130
259
  Object.defineProperties(this, {
131
260
  fullName: defAccessor("fullName"),
261
+ fullNameKana: defAccessor("fullNameKana"),
132
262
  fullAddress: defAccessor("fullAddress"),
133
263
  prefecture: defAccessor("prefecture"),
134
264
  });
265
+
266
+ // Define trigger fields
267
+ let _lastName = this.lastName;
268
+ let _firstName = this.firstName;
269
+ Object.defineProperties(this, {
270
+ lastName: {
271
+ configurable: true,
272
+ enumerable: true,
273
+ get() {
274
+ return _lastName;
275
+ },
276
+ set(v) {
277
+ if (v !== _lastName) {
278
+ _lastName = v;
279
+ this.displayName = `${_lastName}${this.firstName || ""}`.trim();
280
+ }
281
+ },
282
+ },
283
+ firstName: {
284
+ configurable: true,
285
+ enumerable: true,
286
+ get() {
287
+ return _firstName;
288
+ },
289
+ set(v) {
290
+ if (v !== _firstName) {
291
+ _firstName = v;
292
+ this.displayName = `${this.lastName || ""}${_firstName}`.trim();
293
+ }
294
+ },
295
+ },
296
+ });
297
+ }
298
+
299
+ /**
300
+ * 生年月日から年齢を計算します。
301
+ * @returns {{years: number, months: number}|null} 年齢(年数と月数)。dateOfBirthが設定されていない場合はnull。
302
+ */
303
+ get age() {
304
+ if (!this.dateOfBirth) return null;
305
+ const today = new Date();
306
+
307
+ let years = today.getUTCFullYear() - this.dateOfBirth.getUTCFullYear();
308
+ let months = today.getUTCMonth() - this.dateOfBirth.getUTCMonth();
309
+ const days = today.getUTCDate() - this.dateOfBirth.getUTCDate();
310
+
311
+ if (days < 0) {
312
+ months--;
313
+ }
314
+
315
+ if (months < 0) {
316
+ years--;
317
+ months += 12;
318
+ }
319
+
320
+ return { years, months };
321
+ }
322
+
323
+ /**
324
+ * 入社日からの勤続年数を計算します。
325
+ * - 退職日が設定されている場合は、退職日までの勤続年数を計算します。
326
+ * @returns {{years: number, months: number}|null} 勤続年数(年数と月数)。dateOfHireが設定されていない場合はnull。
327
+ */
328
+ get yearsOfService() {
329
+ if (!this.dateOfHire) return null;
330
+ const today = this.dateOfTermination || new Date();
331
+
332
+ let years = today.getUTCFullYear() - this.dateOfHire.getUTCFullYear();
333
+ let months = today.getUTCMonth() - this.dateOfHire.getUTCMonth();
334
+ const days = today.getUTCDate() - this.dateOfHire.getUTCDate();
335
+
336
+ if (days < 0) {
337
+ months--;
338
+ }
339
+
340
+ if (months < 0) {
341
+ years--;
342
+ months += 12;
343
+ }
344
+
345
+ return { years, months };
135
346
  }
136
347
 
137
348
  /**
138
349
  * 外国籍の場合の必須フィールドを検証します。
139
- * エラーがある場合は例外をスローします。
350
+ * - エラーがある場合は例外をスローします。
351
+ * - `isForeigner` が false の場合、以下のプロパティを初期化します。
352
+ * - `foreignName`
353
+ * - `nationality`
354
+ * - `residenceStatus`
355
+ * - `periodOfStay`
356
+ * @returns {void}
357
+ * @throws {Error} 外国籍の場合に必須フィールドが未入力の場合。
140
358
  */
141
359
  _validateForeignerRequiredFields() {
142
360
  if (this.isForeigner) {
@@ -160,20 +378,84 @@ export default class Employee extends FireModel {
160
378
  "[Employee.js] periodOfStay is required when isForeigner is true."
161
379
  );
162
380
  }
381
+ } else {
382
+ // 外国籍でない場合、関連フィールドを初期化
383
+ this.foreignName = null;
384
+ this.nationality = null;
385
+ this.residenceStatus = null;
386
+ this.periodOfStay = null;
163
387
  }
164
388
  }
165
389
 
166
390
  /**
167
391
  * 退職済である場合の必須フィールドを検証します。
168
- * エラーがある場合は例外をスローします。
392
+ * - エラーがある場合は例外をスローします。
393
+ * - `employmentStatus` が `terminated` の場合、以下のプロパティを必須とします。
394
+ * - `dateOfTermination`
395
+ * - `reasonOfTermination`
396
+ * - `employmentStatus` が `active` の場合、`dateOfTermination`, `reasonOfTermination` を初期化します。
397
+ * @returns {void}
398
+ * @throws {Error} 退職済の場合に必須フィールドが未入力の場合。
169
399
  */
170
400
  _validateTerminatedRequiredFields() {
171
- if (this.employmentStatus === "terminated") {
401
+ if (this.employmentStatus === EMPLOYMENT_STATUS_VALUES.TERMINATED.value) {
172
402
  if (!this.dateOfTermination) {
173
403
  throw new Error(
174
404
  "[Employee.js] dateOfTermination is required when employmentStatus is 'terminated'."
175
405
  );
176
406
  }
407
+ if (!this.reasonOfTermination) {
408
+ throw new Error(
409
+ "[Employee.js] reasonOfTermination is required when employmentStatus is 'terminated'."
410
+ );
411
+ }
412
+ } else {
413
+ this.dateOfTermination = null;
414
+ this.reasonOfTermination = null;
415
+ }
416
+ }
417
+
418
+ _validateSecurityGuardFields() {
419
+ if (this.hasSecurityGuardRegistration) {
420
+ if (!this.dateOfSecurityGuardRegistration) {
421
+ throw new Error(
422
+ "[Employee.js] dateOfSecurityGuardRegistration is required when hasSecurityGuardRegistration is true."
423
+ );
424
+ }
425
+ if (!this.emergencyContactName) {
426
+ throw new Error(
427
+ "[Employee.js] emergencyContactName is required when hasSecurityGuardRegistration is true."
428
+ );
429
+ }
430
+ if (!this.emergencyContactRelationDetail) {
431
+ throw new Error(
432
+ "[Employee.js] emergencyContactRelationDetail is required when hasSecurityGuardRegistration is true."
433
+ );
434
+ }
435
+ if (!this.emergencyContactAddress) {
436
+ throw new Error(
437
+ "[Employee.js] emergencyContactAddress is required when hasSecurityGuardRegistration is true."
438
+ );
439
+ }
440
+ if (!this.emergencyContactPhone) {
441
+ throw new Error(
442
+ "[Employee.js] emergencyContactPhone is required when hasSecurityGuardRegistration is true."
443
+ );
444
+ }
445
+ if (!this.domicile) {
446
+ throw new Error(
447
+ "[Employee.js] domicile is required when hasSecurityGuardRegistration is true."
448
+ );
449
+ }
450
+ } else {
451
+ this.dateOfSecurityGuardRegistration = null;
452
+ this.bloodType = BLOOD_TYPE_VALUES.A.value;
453
+ this.emergencyContactName = null;
454
+ this.emergencyContactRelation = null;
455
+ this.emergencyContactRelationDetail = null;
456
+ this.emergencyContactAddress = null;
457
+ this.emergencyContactPhone = null;
458
+ this.domicile = null;
177
459
  }
178
460
  }
179
461
 
@@ -181,21 +463,98 @@ export default class Employee extends FireModel {
181
463
  * 新しい従業員ドキュメントが作成される前に実行されるフック。
182
464
  * - 親クラスの `beforeCreate` を呼び出します。
183
465
  * - 従業員が外国人の場合、外国人名と国籍が未入力であればエラーをスローします。
466
+ * @param {Object} args - Creation options.
467
+ * @param {string} [args.docId] - Document ID to use (optional).
468
+ * @param {boolean} [args.useAutonumber=true] - Whether to use auto-numbering.
469
+ * @param {Object} [args.transaction] - Firestore transaction.
470
+ * @param {Function} [args.callBack] - Callback function.
471
+ * @param {string} [args.prefix] - Path prefix.
184
472
  */
185
- async beforeCreate() {
186
- await super.beforeCreate();
473
+ async beforeCreate(args = {}) {
474
+ await super.beforeCreate(args);
187
475
  this._validateForeignerRequiredFields();
188
476
  this._validateTerminatedRequiredFields();
477
+ this._validateSecurityGuardFields();
189
478
  }
190
479
 
191
480
  /**
192
481
  * 従業員ドキュメントが更新される前に実行されるフック。
193
482
  * - 親クラスの `beforeUpdate` を呼び出します。
194
483
  * - 従業員が外国人の場合、外国人名と国籍が未入力であればエラーをスローします。
484
+ * @param {Object} args - Creation options.
485
+ * @param {Object} [args.transaction] - Firestore transaction.
486
+ * @param {Function} [args.callBack] - Callback function.
487
+ * @param {string} [args.prefix] - Path prefix.
195
488
  */
196
- async beforeUpdate() {
197
- await super.beforeUpdate();
489
+ async beforeUpdate(args = {}) {
490
+ await super.beforeUpdate(args);
491
+
492
+ // `employmentStatus` の `terminated` への直接変更の禁止
493
+ // - 従業員を退職させる場合、様々なチェックが必要になることが想定されるため、専用メソッドとして `toTerminated` を使用する。
494
+ // - `employmentStatus` を `terminated` に変更する場合は、必ず `toTerminated` メソッドを使用すること。
495
+ // - 一度退職処理した従業員の復帰処理は現状想定していないが、将来的に必要になった場合は `toActive` メソッド等を追加実装すること。
496
+ if (
497
+ !this._skipToTerminatedCheck &&
498
+ this.employmentStatus === Employee.STATUS_TERMINATED &&
499
+ this._beforeData.employmentStatus === Employee.STATUS_ACTIVE
500
+ ) {
501
+ throw new Error(
502
+ "[Employee.js] Direct changes to employmentStatus to 'terminated' are not allowed. Use toTerminated() method instead."
503
+ );
504
+ }
505
+
198
506
  this._validateForeignerRequiredFields();
199
507
  this._validateTerminatedRequiredFields();
508
+ this._validateSecurityGuardFields();
509
+ }
510
+
511
+ /**
512
+ * 現在インスタンスに読み込まれている従業員を退職状態に変更します。
513
+ * @param {Date} dateOfTermination - 退職日(Dateオブジェクト)
514
+ * @param {string} reasonOfTermination - 退職理由
515
+ * @param {Object} options - パラメータオブジェクト
516
+ * @param {Function|null} [options.transaction=null] - Firestore トランザクション関数
517
+ * @param {Function|null} [options.callBack=null] - カスタム処理用コールバック
518
+ * @param {string|null} [options.prefix=null] - パスのプレフィックス
519
+ * @returns {Promise<DocumentReference>} 更新されたドキュメントの参照
520
+ * @throws {Error} docIdが存在しない場合、または有効なdateOfTerminationが提供されていない場合。
521
+ */
522
+ async toTerminated(dateOfTermination, reasonOfTermination, options = {}) {
523
+ if (!this.docId) {
524
+ throw new Error(
525
+ "[Employee.js] docId is required to terminate an employee."
526
+ );
527
+ }
528
+ if (!dateOfTermination || !(dateOfTermination instanceof Date)) {
529
+ throw new Error(
530
+ "[Employee.js] A valid dateOfTermination is required to terminate an employee."
531
+ );
532
+ }
533
+ if (dateOfTermination < this.dateOfHire) {
534
+ throw new Error(
535
+ "[Employee.js] dateOfTermination cannot be earlier than dateOfHire."
536
+ );
537
+ }
538
+
539
+ if (!reasonOfTermination || typeof reasonOfTermination !== "string") {
540
+ throw new Error(
541
+ "[Employee.js] A valid reasonOfTermination is required to terminate an employee."
542
+ );
543
+ }
544
+
545
+ this.employmentStatus = Employee.STATUS_TERMINATED;
546
+ this.dateOfTermination = dateOfTermination;
547
+ this.reasonOfTermination = reasonOfTermination;
548
+
549
+ this._skipToTerminatedCheck = true;
550
+
551
+ try {
552
+ return await this.update(options);
553
+ } catch (error) {
554
+ this.rollback();
555
+ throw error;
556
+ } finally {
557
+ this._skipToTerminatedCheck = false;
558
+ }
200
559
  }
201
560
  }
@@ -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
  /**