@shisyamo4131/air-guard-v2-schemas 2.4.2-dev.6 → 2.4.2-dev.61

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
@@ -1,225 +1,553 @@
1
- /**
1
+ /*****************************************************************************
2
2
  * @file src/Employee.js
3
- * @author shisyamo4131
4
- * @version 1.0.0
5
- */
3
+ *****************************************************************************/
6
4
  import FireModel from "@shisyamo4131/air-firebase-v2";
7
5
  import { defField } from "./parts/fieldDefinitions.js";
8
6
  import { defAccessor } from "./parts/accessorDefinitions.js";
9
7
  import { VALUES as EMPLOYMENT_STATUS_VALUES } from "./constants/employment-status.js";
10
8
  import { VALUES as BLOOD_TYPE_VALUES } from "./constants/blood-type.js";
9
+ import { VALUES as EMERGENCY_CONTACT_RELATION_VALUES } from "./constants/emergency-contact-relation.js";
11
10
  import Certification from "./Certification.js";
12
11
  import { GeocodableMixin } from "./mixins/GeocodableMixin.js";
12
+ import { VALIDATION_ERRORS } from "./errorDefinitions.js";
13
+ import Insurance from "./Insurance.js";
13
14
 
14
- const classProps = {
15
- code: defField("code", { label: "従業員コード" }),
16
- lastName: defField("lastName", { required: true }),
17
- firstName: defField("firstName", { required: true }),
18
- lastNameKana: defField("lastNameKana", { required: true }),
19
- firstNameKana: defField("firstNameKana", { required: true }),
20
- displayName: defField("displayName", { required: true }),
21
- gender: defField("gender", { required: true }),
22
- dateOfBirth: defField("dateOfBirth", { required: true }),
23
- zipcode: defField("zipcode", { required: true }),
24
- prefCode: defField("prefCode", { required: true }),
25
- city: defField("city", { required: true }),
26
- address: defField("address", { required: true }),
27
- building: defField("building"),
28
- location: defField("location", { hidden: true }),
29
- mobile: defField("mobile"),
30
- email: defField("email", { required: false }),
31
- dateOfHire: defField("dateOfHire", { required: true }),
32
- employmentStatus: defField("employmentStatus", { required: true }),
33
- title: defField("title"),
34
- dateOfTermination: defField("dateOfTermination", {
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,
15
+ /*****************************************************************************
16
+ * @class Employee
17
+ * @extends GeocodableMixin(FireModel)
18
+ *
19
+ * @property {string} code - 従業員コード
20
+ * @property {string} lastName -
21
+ * @property {string} firstName -
22
+ * @property {string} lastNameKana - 姓(カナ)
23
+ * @property {string} firstNameKana - 名(カナ)
24
+ * @property {string} displayName - 表示名
25
+ * @property {string} gender - 性別
26
+ * @property {Date} dateOfBirth - 生年月日
27
+ * @property {string} zipcode - 郵便番号
28
+ * @property {string} prefCode - 都道府県コード
29
+ * @property {string} city - 市区町村
30
+ * @property {string} address - 住所
31
+ * @property {string} building - 建物名
32
+ * @property {object} location - 地理的な位置情報
33
+ * @property {string} mobile - 携帯電話番号
34
+ * @property {string} email - メールアドレス
35
+ * @property {Date} dateOfHire - 入社日
36
+ * @property {string} employmentStatus - 雇用状況
37
+ * @property {string} title - 肩書
38
+ * @property {Date} dateOfTermination - 退職日
39
+ * @property {string} reasonOfTermination - 退職理由
40
+ * @property {boolean} isForeigner - 外国人かどうか
41
+ * @property {string} foreignName - 外国人氏名
42
+ * @property {string} nationality - 国籍
43
+ * @property {string} residenceStatus - 在留資格
44
+ * @property {boolean} hasPeriodOfStayLimit - 在留期間制限の有無
45
+ * @property {Date} periodOfStay - 在留期間満了日
46
+ * @property {boolean} hasWorkRestrictions - 就労制限の有無
47
+ * @property {boolean} hasSecurityGuardRegistration - 警備員資格登録の有無
48
+ * @property {Date} dateOfSecurityGuardRegistration - 警備員資格登録日
49
+ * @property {string} bloodType - 血液型
50
+ * @property {string} emergencyContactName - 緊急連絡先氏名
51
+ * @property {string} emergencyContactRelation - 緊急連絡先との関係
52
+ * @property {string} emergencyContactRelationDetail - 緊急連絡先との関係詳細
53
+ * @property {string} emergencyContactAddress - 緊急連絡先住所
54
+ * @property {string} emergencyContactPhone - 緊急連絡先電話番号
55
+ * @property {string} domicile - 本籍地
56
+ * @property {Array<Certification>} securityCertifications - 警備員資格情報の配列
57
+ * @property {Insurance} employmentInsurance - 雇用保険情報
58
+ * @property {string} remarks - 備考
59
+ *
60
+ * @property {string} fullName - 姓と名を結合したフルネーム(読み取り専用)
61
+ * @property {string} fullNameKana - 姓と名を結合したフルネーム(カナ、読み取り専用)
62
+ * @property {string} fullAddress - 都道府県、市区町村、住所を結合したフルアドレス(読み取り専用)
63
+ * @property {string} prefecture - `prefCode` から派生した都道府県名(読み取り専用)
64
+ *
65
+ * @getter
66
+ * @property {number} age - `dateOfBirth` から計算された年齢(読み取り専用)
67
+ * @property {number} yearsOfService - `dateOfHire` から計算された勤続年数(読み取り専用)
68
+ *
69
+ * @static
70
+ * @property {Object} EMPLOYMENT_STATUS - 雇用状況の定数オブジェクト
71
+ * @property {string} STATUS_ACTIVE - 在職中の雇用状況を表す定数
72
+ * @property {string} STATUS_TERMINATED - 退職済みの雇用状況を表す定数
73
+ *
74
+ * @function toTerminated - 現在の従業員インスタンスを退職済みに変更する関数
75
+ *****************************************************************************/
76
+ export default class Employee extends GeocodableMixin(FireModel) {
77
+ static className = "従業員";
78
+ static collectionPath = "Employees";
79
+ static useAutonumber = false;
80
+ static logicalDelete = true;
81
+ static classProps = {
82
+ code: defField("code", { label: "従業員コード" }),
83
+ lastName: defField("lastName", { required: true }),
84
+ firstName: defField("firstName", { required: true }),
85
+ lastNameKana: defField("lastNameKana", { required: true }),
86
+ firstNameKana: defField("firstNameKana", { required: true }),
87
+ displayName: defField("displayName", { required: true }),
88
+ gender: defField("gender", { required: true }),
89
+ dateOfBirth: defField("dateOfBirth", { required: true }),
90
+ zipcode: defField("zipcode", { required: true }),
91
+ prefCode: defField("prefCode", { required: true }),
92
+ city: defField("city", { required: true }),
93
+ address: defField("address", { required: true }),
94
+ building: defField("building"),
95
+ location: defField("location", { hidden: true }),
96
+ mobile: defField("mobile"),
97
+ email: defField("email", { required: false }),
98
+ dateOfHire: defField("dateOfHire", { required: true }),
99
+ employmentStatus: defField("employmentStatus", { required: true }),
100
+ title: defField("title"),
101
+
102
+ /**
103
+ * 退職年月日
104
+ * - `employmentStatus` が `TERMINATED` の場合、必須フィールド。
105
+ * - `employmentStatus` が `ACTIVE` の場合、入力不可(disabled)。
106
+ * - バリデーションルール:
107
+ * - `employmentStatus` が `TERMINATED` の場合、必須。
108
+ * - `employmentStatus` が `TERMINATED` の場合、`dateOfHire` より前の日付は不可。
109
+ * - `employmentStatus` が `ACTIVE` の場合、常に null でなければならない。
110
+ * - フロントエンドのフォームでは、`employmentStatus` の値に応じて入力フィールドの表示/非表示や必須/任意を切り替えることが推奨される。
111
+ */
112
+ dateOfTermination: defField("dateOfTermination", {
113
+ validator: (value, item) => {
114
+ if (
115
+ item.employmentStatus === EMPLOYMENT_STATUS_VALUES.TERMINATED.value
116
+ ) {
117
+ if (!value) {
118
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
119
+ "INVALID_DATE_OF_TERMINATION",
120
+ "dateOfTermination is required when employmentStatus is 'terminated'.",
121
+ { ja: "在職区分が '退職' の場合、退職年月日は必須です。" },
122
+ );
123
+ }
124
+ if (!(value instanceof Date)) {
125
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
126
+ "INVALID_DATE_OF_TERMINATION",
127
+ "dateOfTermination must be a Date object.",
128
+ { ja: "退職年月日はDateオブジェクトである必要があります。" },
129
+ );
130
+ }
131
+ if (value < item.dateOfHire) {
132
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
133
+ "INVALID_DATE_OF_TERMINATION",
134
+ "dateOfTermination cannot be earlier than dateOfHire.",
135
+ { ja: "退職年月日は入社日より前に設定できません。" },
136
+ );
137
+ }
138
+ }
139
+ return true;
140
+ },
141
+ component: {
142
+ attrs: {
143
+ required: ({ item }) =>
144
+ item.employmentStatus === EMPLOYMENT_STATUS_VALUES.TERMINATED.value,
145
+ disabled: ({ item }) =>
146
+ item.employmentStatus !== EMPLOYMENT_STATUS_VALUES.TERMINATED.value,
147
+ },
148
+ },
149
+ }),
150
+ reasonOfTermination: defField("reasonOfTermination", {
151
+ validator: (value, item) => {
152
+ if (
153
+ item.employmentStatus === EMPLOYMENT_STATUS_VALUES.TERMINATED.value
154
+ ) {
155
+ if (!value) {
156
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
157
+ "INVALID_REASON_OF_TERMINATION",
158
+ "reasonOfTermination is required when employmentStatus is 'terminated'.",
159
+ { ja: "在職区分が '退職' の場合、退職理由は必須です。" },
160
+ );
161
+ }
162
+ if (typeof value !== "string") {
163
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
164
+ "INVALID_REASON_OF_TERMINATION",
165
+ "reasonOfTermination must be a string.",
166
+ { ja: "退職理由は文字列である必要があります。" },
167
+ );
168
+ }
169
+ }
170
+ return true;
171
+ },
172
+ component: {
173
+ attrs: {
174
+ required: ({ item }) =>
175
+ item.employmentStatus === EMPLOYMENT_STATUS_VALUES.TERMINATED.value,
176
+ disabled: ({ item }) =>
177
+ item.employmentStatus !== EMPLOYMENT_STATUS_VALUES.TERMINATED.value,
178
+ },
179
+ },
180
+ }),
181
+
182
+ // Foreign related fields
183
+ isForeigner: defField("isForeigner"),
184
+ foreignName: defField("foreignName", {
185
+ validator: (value, item) => {
186
+ if (item.isForeigner) {
187
+ if (!value) {
188
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
189
+ "FOREIGN_NAME_REQUIRED",
190
+ "foreignName is required when isForeigner is true.",
191
+ { ja: "外国籍の場合、外国名は必須です。" },
192
+ );
193
+ }
194
+ if (typeof value !== "string") {
195
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
196
+ "FOREIGN_NAME_INVALID",
197
+ "foreignName must be a string.",
198
+ { ja: "外国名は文字列である必要があります。" },
199
+ );
200
+ }
201
+ }
202
+ return true;
203
+ },
204
+ component: {
205
+ attrs: {
206
+ required: ({ item }) => item.isForeigner,
207
+ disabled: ({ item }) => !item.isForeigner,
208
+ },
41
209
  },
42
- },
43
- }),
44
- reasonOfTermination: defField("reasonOfTermination", {
45
- component: {
46
- attrs: {
47
- required: ({ item }) =>
48
- item.employmentStatus === EMPLOYMENT_STATUS_VALUES.TERMINATED.value,
49
- disabled: ({ item }) =>
50
- item.employmentStatus !== EMPLOYMENT_STATUS_VALUES.TERMINATED.value,
210
+ }),
211
+ nationality: defField("nationality", {
212
+ validator: (value, item) => {
213
+ if (item.isForeigner) {
214
+ if (!value) {
215
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
216
+ "NATIONALITY_REQUIRED",
217
+ "nationality is required when isForeigner is true.",
218
+ { ja: "外国籍の場合、国籍は必須です。" },
219
+ );
220
+ }
221
+ if (typeof value !== "string") {
222
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
223
+ "NATIONALITY_INVALID",
224
+ "nationality must be a string.",
225
+ { ja: "国籍は文字列である必要があります。" },
226
+ );
227
+ }
228
+ }
229
+ return true;
230
+ },
231
+ component: {
232
+ attrs: {
233
+ required: ({ item }) => item.isForeigner,
234
+ disabled: ({ item }) => !item.isForeigner,
235
+ },
236
+ },
237
+ }),
238
+ residenceStatus: defField("residenceStatus", {
239
+ validator: (value, item) => {
240
+ if (item.isForeigner) {
241
+ if (!value) {
242
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
243
+ "RESIDENCE_STATUS_REQUIRED",
244
+ "residenceStatus is required when isForeigner is true.",
245
+ { ja: "外国籍の場合、在留資格は必須です。" },
246
+ );
247
+ }
248
+ if (typeof value !== "string") {
249
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
250
+ "RESIDENCE_STATUS_INVALID",
251
+ "residenceStatus must be a string.",
252
+ { ja: "在留資格は文字列である必要があります。" },
253
+ );
254
+ }
255
+ }
256
+ return true;
257
+ },
258
+ component: {
259
+ attrs: {
260
+ required: ({ item }) => item.isForeigner,
261
+ disabled: ({ item }) => !item.isForeigner,
262
+ },
263
+ },
264
+ }),
265
+ hasPeriodOfStayLimit: defField("hasPeriodOfStayLimit", {
266
+ component: {
267
+ attrs: {
268
+ disabled: ({ item }) => !item.isForeigner,
269
+ },
270
+ },
271
+ }),
272
+ periodOfStay: defField("periodOfStay", {
273
+ validator: (value, item) => {
274
+ if (item.isForeigner && item.hasPeriodOfStayLimit) {
275
+ if (!value) {
276
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
277
+ "PERIOD_OF_STAY_REQUIRED",
278
+ "periodOfStay is required when isForeigner is true and hasPeriodOfStayLimit is true.",
279
+ {
280
+ ja: "外国籍で在留期間制限がある場合、在留期間満了日は必須です。",
281
+ },
282
+ );
283
+ }
284
+ if (!(value instanceof Date)) {
285
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
286
+ "PERIOD_OF_STAY_INVALID",
287
+ "periodOfStay must be a Date.",
288
+ { ja: "在留期間満了日は日付である必要があります。" },
289
+ );
290
+ }
291
+ }
292
+ return true;
51
293
  },
52
- },
53
- }),
54
-
55
- // Foreign related fields
56
- isForeigner: defField("isForeigner"),
57
- foreignName: defField("foreignName", {
58
- component: {
59
- attrs: {
60
- required: ({ item }) => item.isForeigner,
61
- disabled: ({ item }) => !item.isForeigner,
294
+ component: {
295
+ attrs: {
296
+ required: ({ item }) => item.isForeigner && item.hasPeriodOfStayLimit,
297
+ disabled: ({ item }) =>
298
+ !item.isForeigner || !item.hasPeriodOfStayLimit,
299
+ },
62
300
  },
63
- },
64
- }),
65
- nationality: defField("nationality", {
66
- component: {
67
- attrs: {
68
- required: ({ item }) => item.isForeigner,
69
- disabled: ({ item }) => !item.isForeigner,
301
+ }),
302
+ hasWorkRestrictions: defField("hasWorkRestrictions", {
303
+ component: {
304
+ attrs: {
305
+ disabled: ({ item }) => !item.isForeigner,
306
+ },
70
307
  },
71
- },
72
- }),
73
- residenceStatus: defField("residenceStatus", {
74
- component: {
75
- attrs: {
76
- required: ({ item }) => item.isForeigner,
77
- disabled: ({ item }) => !item.isForeigner,
308
+ }),
309
+ // Security guard related fields
310
+ hasSecurityGuardRegistration: defField("check", { label: "警備員登録" }),
311
+ dateOfSecurityGuardRegistration: defField(
312
+ "dateOfSecurityGuardRegistration",
313
+ {
314
+ validator: (value, item) => {
315
+ if (item.hasSecurityGuardRegistration) {
316
+ if (!value) {
317
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
318
+ "SECURITY_GUARD_REGISTRATION_DATE_REQUIRED",
319
+ "dateOfSecurityGuardRegistration is required when hasSecurityGuardRegistration is true.",
320
+ { ja: "警備員登録がある場合、警備員登録日は必須です。" },
321
+ );
322
+ }
323
+ if (!(value instanceof Date)) {
324
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
325
+ "SECURITY_GUARD_REGISTRATION_DATE_INVALID",
326
+ "dateOfSecurityGuardRegistration must be a Date.",
327
+ { ja: "警備員登録日は日付である必要があります。" },
328
+ );
329
+ }
330
+ }
331
+ return true;
332
+ },
333
+ component: {
334
+ attrs: {
335
+ required: ({ item }) => item.hasSecurityGuardRegistration,
336
+ disabled: ({ item }) => !item.hasSecurityGuardRegistration,
337
+ },
338
+ },
339
+ },
340
+ ),
341
+ bloodType: defField("bloodType", {
342
+ validator: (value, item) => {
343
+ if (item.hasSecurityGuardRegistration) {
344
+ if (!value) {
345
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
346
+ "BLOOD_TYPE_REQUIRED",
347
+ "bloodType is required when hasSecurityGuardRegistration is true.",
348
+ { ja: "警備員登録がある場合、血液型は必須です。" },
349
+ );
350
+ }
351
+ if (
352
+ !Object.values(BLOOD_TYPE_VALUES).some((v) => v.value === value)
353
+ ) {
354
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
355
+ "BLOOD_TYPE_INVALID",
356
+ "bloodType must be a valid value.",
357
+ { ja: "血液型は有効な値である必要があります。" },
358
+ );
359
+ }
360
+ }
361
+ return true;
362
+ },
363
+ component: {
364
+ attrs: {
365
+ required: ({ item }) => item.hasSecurityGuardRegistration,
366
+ disabled: ({ item }) => !item.hasSecurityGuardRegistration,
367
+ },
368
+ },
369
+ }),
370
+ emergencyContactName: defField("emergencyContactName", {
371
+ validator: (value, item) => {
372
+ if (item.hasSecurityGuardRegistration) {
373
+ if (!value) {
374
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
375
+ "EMERGENCY_CONTACT_NAME_REQUIRED",
376
+ "emergencyContactName is required when hasSecurityGuardRegistration is true.",
377
+ { ja: "警備員登録がある場合、緊急連絡先の名前は必須です。" },
378
+ );
379
+ }
380
+ if (typeof value !== "string") {
381
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
382
+ "EMERGENCY_CONTACT_NAME_INVALID",
383
+ "emergencyContactName must be a valid string.",
384
+ { ja: "緊急連絡先の名前は有効な文字列である必要があります。" },
385
+ );
386
+ }
387
+ }
388
+ return true;
389
+ },
390
+ component: {
391
+ attrs: {
392
+ required: ({ item }) => item.hasSecurityGuardRegistration,
393
+ disabled: ({ item }) => !item.hasSecurityGuardRegistration,
394
+ },
395
+ },
396
+ }),
397
+ emergencyContactRelation: defField("emergencyContactRelation", {
398
+ validator: (value, item) => {
399
+ if (item.hasSecurityGuardRegistration) {
400
+ if (!value) {
401
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
402
+ "EMERGENCY_CONTACT_RELATION_REQUIRED",
403
+ "emergencyContactRelation is required when hasSecurityGuardRegistration is true.",
404
+ { ja: "警備員登録がある場合、緊急連絡先の関係は必須です。" },
405
+ );
406
+ }
407
+ if (
408
+ !Object.values(EMERGENCY_CONTACT_RELATION_VALUES).some(
409
+ (v) => v.value === value,
410
+ )
411
+ ) {
412
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
413
+ "EMERGENCY_CONTACT_RELATION_INVALID",
414
+ "emergencyContactRelation must be a valid value.",
415
+ { ja: "緊急連絡先の関係は有効な値である必要があります。" },
416
+ );
417
+ }
418
+ }
419
+ return true;
78
420
  },
79
- },
80
- }),
81
- periodOfStay: defField("periodOfStay", {
82
- component: {
83
- attrs: {
84
- required: ({ item }) => item.isForeigner,
85
- disabled: ({ item }) => !item.isForeigner,
421
+ component: {
422
+ attrs: {
423
+ required: ({ item }) => item.hasSecurityGuardRegistration,
424
+ disabled: ({ item }) => !item.hasSecurityGuardRegistration,
425
+ },
86
426
  },
87
- },
88
- }),
89
-
90
- // Security guard related fields
91
- hasSecurityGuardRegistration: defField("check", { label: "警備員登録" }),
92
- dateOfSecurityGuardRegistration: defField("dateOfSecurityGuardRegistration", {
93
- component: {
94
- attrs: {
95
- required: ({ item }) => item.hasSecurityGuardRegistration,
96
- disabled: ({ item }) => !item.hasSecurityGuardRegistration,
427
+ }),
428
+ emergencyContactRelationDetail: defField("emergencyContactRelationDetail", {
429
+ validator: (value, item) => {
430
+ if (item.hasSecurityGuardRegistration) {
431
+ if (!value) {
432
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
433
+ "EMERGENCY_CONTACT_RELATION_DETAIL_REQUIRED",
434
+ "emergencyContactRelationDetail is required when hasSecurityGuardRegistration is true.",
435
+ { ja: "警備員登録がある場合、緊急連絡先の詳細は必須です。" },
436
+ );
437
+ }
438
+ if (typeof value !== "string") {
439
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
440
+ "EMERGENCY_CONTACT_RELATION_DETAIL_INVALID",
441
+ "emergencyContactRelationDetail must be a valid string.",
442
+ { ja: "緊急連絡先の詳細は有効な文字列である必要があります。" },
443
+ );
444
+ }
445
+ }
446
+ return true;
97
447
  },
98
- },
99
- }),
100
- bloodType: defField("bloodType", {
101
- component: {
102
- attrs: {
103
- required: ({ item }) => item.hasSecurityGuardRegistration,
104
- disabled: ({ item }) => !item.hasSecurityGuardRegistration,
448
+ component: {
449
+ attrs: {
450
+ required: ({ item }) => item.hasSecurityGuardRegistration,
451
+ disabled: ({ item }) => !item.hasSecurityGuardRegistration,
452
+ },
105
453
  },
106
- },
107
- }),
108
- emergencyContactName: defField("emergencyContactName", {
109
- component: {
110
- attrs: {
111
- required: ({ item }) => item.hasSecurityGuardRegistration,
112
- disabled: ({ item }) => !item.hasSecurityGuardRegistration,
454
+ }),
455
+ emergencyContactAddress: defField("emergencyContactAddress", {
456
+ validator: (value, item) => {
457
+ if (item.hasSecurityGuardRegistration) {
458
+ if (!value) {
459
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
460
+ "EMERGENCY_CONTACT_ADDRESS_REQUIRED",
461
+ "emergencyContactAddress is required when hasSecurityGuardRegistration is true.",
462
+ { ja: "警備員登録がある場合、緊急連絡先の住所は必須です。" },
463
+ );
464
+ }
465
+ if (typeof value !== "string") {
466
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
467
+ "EMERGENCY_CONTACT_ADDRESS_INVALID",
468
+ "emergencyContactAddress must be a valid string.",
469
+ { ja: "緊急連絡先の住所は有効な文字列である必要があります。" },
470
+ );
471
+ }
472
+ }
473
+ return true;
113
474
  },
114
- },
115
- }),
116
- emergencyContactRelation: defField("emergencyContactRelation", {
117
- component: {
118
- attrs: {
119
- required: ({ item }) => item.hasSecurityGuardRegistration,
120
- disabled: ({ item }) => !item.hasSecurityGuardRegistration,
475
+ component: {
476
+ attrs: {
477
+ required: ({ item }) => item.hasSecurityGuardRegistration,
478
+ disabled: ({ item }) => !item.hasSecurityGuardRegistration,
479
+ },
121
480
  },
122
- },
123
- }),
124
- emergencyContactRelationDetail: defField("emergencyContactRelationDetail", {
125
- component: {
126
- attrs: {
127
- required: ({ item }) => item.hasSecurityGuardRegistration,
128
- disabled: ({ item }) => !item.hasSecurityGuardRegistration,
481
+ }),
482
+ emergencyContactPhone: defField("emergencyContactPhone", {
483
+ validator: (value, item) => {
484
+ if (item.hasSecurityGuardRegistration) {
485
+ if (!value) {
486
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
487
+ "EMERGENCY_CONTACT_PHONE_REQUIRED",
488
+ "emergencyContactPhone is required when hasSecurityGuardRegistration is true.",
489
+ { ja: "警備員登録がある場合、緊急連絡先の電話番号は必須です。" },
490
+ );
491
+ }
492
+ if (typeof value !== "string") {
493
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
494
+ "EMERGENCY_CONTACT_PHONE_INVALID",
495
+ "emergencyContactPhone must be a valid string.",
496
+ {
497
+ ja: "緊急連絡先の電話番号は有効な文字列である必要があります。",
498
+ },
499
+ );
500
+ }
501
+ }
502
+ return true;
129
503
  },
130
- },
131
- }),
132
- emergencyContactAddress: defField("emergencyContactAddress", {
133
- component: {
134
- attrs: {
135
- required: ({ item }) => item.hasSecurityGuardRegistration,
136
- disabled: ({ item }) => !item.hasSecurityGuardRegistration,
504
+ component: {
505
+ attrs: {
506
+ required: ({ item }) => item.hasSecurityGuardRegistration,
507
+ disabled: ({ item }) => !item.hasSecurityGuardRegistration,
508
+ },
137
509
  },
138
- },
139
- }),
140
- emergencyContactPhone: defField("emergencyContactPhone", {
141
- component: {
142
- attrs: {
143
- required: ({ item }) => item.hasSecurityGuardRegistration,
144
- disabled: ({ item }) => !item.hasSecurityGuardRegistration,
510
+ }),
511
+ domicile: defField("domicile", {
512
+ validator: (value, item) => {
513
+ if (item.hasSecurityGuardRegistration) {
514
+ if (!value) {
515
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
516
+ "DOMICILE_REQUIRED",
517
+ "domicile is required when hasSecurityGuardRegistration is true.",
518
+ { ja: "警備員登録がある場合、本籍地は必須です。" },
519
+ );
520
+ }
521
+ if (typeof value !== "string") {
522
+ return VALIDATION_ERRORS.CUSTOM_ERROR(
523
+ "DOMICILE_INVALID",
524
+ "domicile must be a valid string.",
525
+ { ja: "本籍地は有効な文字列である必要があります。" },
526
+ );
527
+ }
528
+ }
529
+ return true;
145
530
  },
146
- },
147
- }),
148
- domicile: defField("domicile", {
149
- component: {
150
- attrs: {
151
- required: ({ item }) => item.hasSecurityGuardRegistration,
152
- disabled: ({ item }) => !item.hasSecurityGuardRegistration,
531
+ component: {
532
+ attrs: {
533
+ required: ({ item }) => item.hasSecurityGuardRegistration,
534
+ disabled: ({ item }) => !item.hasSecurityGuardRegistration,
535
+ },
153
536
  },
154
- },
155
- }),
156
- securityCertifications: defField("array", {
157
- label: "保有資格",
158
- customClass: Certification,
159
- }),
160
- remarks: defField("remarks"),
161
- };
537
+ }),
538
+ securityCertifications: defField("array", {
539
+ label: "保有資格",
540
+ customClass: Certification,
541
+ }),
542
+
543
+ // 加入保険
544
+ employmentInsurance: defField("employmentInsurance", {
545
+ default: () => new Insurance(),
546
+ customClass: Insurance,
547
+ }),
548
+ remarks: defField("remarks"),
549
+ };
162
550
 
163
- /*****************************************************************************
164
- * @prop {string} code - Employee code.
165
- * @prop {string} lastName - Last name.
166
- * @prop {string} firstName - First name.
167
- * @prop {string} lastNameKana - Last name in Kana.
168
- * @prop {string} firstNameKana - First name in Kana.
169
- * @prop {string} displayName - Display name.
170
- * @prop {string} gender - Gender.
171
- * @prop {Date} dateOfBirth - Date of birth.
172
- * @prop {string} zipcode - Postal code.
173
- * @prop {string} prefCode - Prefecture code.
174
- * @prop {string} city - City name.
175
- * @prop {string} address - Address details.
176
- * @prop {string} building - Building name.
177
- * @prop {object} location - Geographical location.
178
- * @prop {string} mobile - Mobile phone number.
179
- * @prop {string} email - Email address.
180
- * @prop {Date} dateOfHire - Date of hire.
181
- * @prop {string} employmentStatus - Employment status.
182
- * @prop {string} title - Job title.
183
- * @prop {Date} dateOfTermination - Date of termination.
184
- * @prop {string} reasonOfTermination - Reason for termination.
185
- * @prop {boolean} isForeigner - Is the employee a foreigner.
186
- * @prop {string} foreignName - Foreign name.
187
- * @prop {string} nationality - Nationality.
188
- * @prop {string} residenceStatus - Residence status.
189
- * @prop {Date} periodOfStay - Period of stay expiration date.
190
- * @prop {boolean} hasSecurityGuardRegistration - Has security guard registration.
191
- * @prop {Date} dateOfSecurityGuardRegistration - Date of security guard registration.
192
- * @prop {string} bloodType - Blood type.
193
- * @prop {string} emergencyContactName - Emergency contact name.
194
- * @prop {string} emergencyContactRelation - Emergency contact relation.
195
- * @prop {string} emergencyContactRelationDetail - Emergency contact relation detail.
196
- * @prop {string} emergencyContactAddress - Emergency contact address.
197
- * @prop {string} emergencyContactPhone - Emergency contact phone number.
198
- * @prop {string} domicile - Domicile.
199
- * @prop {Array<Certification>} securityCertifications - Array of security certifications.
200
- * @prop {string} remarks - Additional remarks.
201
- *
202
- * @prop {string} fullName - Full name combining last and first names (read-only)
203
- * @prop {string} fullNameKana - Full name in Kana combining last and first names (read-only)
204
- * @prop {string} fullAddress - Full address combining prefecture, city, and address (read-only)
205
- * @prop {string} prefecture - Prefecture name derived from `prefCode` (read-only)
206
- *
207
- * @getter
208
- * @prop {number} age - Age calculated from `dateOfBirth` (read-only)
209
- * @prop {number} yearsOfService - Years of service calculated from `dateOfHire` (read-only)
210
- *
211
- * @static
212
- * @prop {string} STATUS_ACTIVE - constant for active employment status
213
- * @prop {string} STATUS_TERMINATED - constant for terminated employment status
214
- *
215
- * @function toTerminated - Change the current employee instance to terminated status.
216
- *****************************************************************************/
217
- export default class Employee extends GeocodableMixin(FireModel) {
218
- static className = "従業員";
219
- static collectionPath = "Employees";
220
- static useAutonumber = false;
221
- static logicalDelete = true;
222
- static classProps = classProps;
223
551
  static tokenFields = [
224
552
  "code",
225
553
  "lastName",
@@ -238,6 +566,9 @@ export default class Employee extends GeocodableMixin(FireModel) {
238
566
  static STATUS_ACTIVE = EMPLOYMENT_STATUS_VALUES.ACTIVE.value;
239
567
  static STATUS_TERMINATED = EMPLOYMENT_STATUS_VALUES.TERMINATED.value;
240
568
 
569
+ /** 2026-03-18 追加 */
570
+ static EMPLOYMENT_STATUS = EMPLOYMENT_STATUS_VALUES;
571
+
241
572
  constructor(item = {}) {
242
573
  super(item);
243
574
 
@@ -344,108 +675,61 @@ export default class Employee extends GeocodableMixin(FireModel) {
344
675
  }
345
676
 
346
677
  /**
347
- * 外国籍の場合の必須フィールドを検証します。
348
- * - エラーがある場合は例外をスローします。
678
+ * 退職状態に関連するフィールドを初期化します。
679
+ * - `employmentStatus` が `TERMINATED` でない場合、以下のプロパティを初期化します。
680
+ * - `dateOfTermination`
681
+ * - `reasonOfTermination`
682
+ * @returns {void}
683
+ */
684
+ _initTerminatedFields() {
685
+ if (this.employmentStatus !== EMPLOYMENT_STATUS_VALUES.TERMINATED.value) {
686
+ this.dateOfTermination = null;
687
+ this.reasonOfTermination = null;
688
+ }
689
+ }
690
+
691
+ /**
692
+ * 外国籍に関連するフィールドを初期化します。
349
693
  * - `isForeigner` が false の場合、以下のプロパティを初期化します。
350
694
  * - `foreignName`
351
- * - `nationality`
352
- * - `residenceStatus`
353
- * - `periodOfStay`
695
+ * - `nationality`
696
+ * - `residenceStatus`
697
+ * - `hasPeriodOfStayLimit`
698
+ * - `periodOfStay`
699
+ * - `hasWorkRestrictions`
700
+ * - `isForeigner` が true で `hasPeriodOfStayLimit` が false の場合、`periodOfStay` を初期化します。
354
701
  * @returns {void}
355
- * @throws {Error} 外国籍の場合に必須フィールドが未入力の場合。
356
702
  */
357
- _validateForeignerRequiredFields() {
358
- if (this.isForeigner) {
359
- if (!this.foreignName) {
360
- throw new Error(
361
- "[Employee.js] foreignName is required when isForeigner is true."
362
- );
363
- }
364
- if (!this.nationality) {
365
- throw new Error(
366
- "[Employee.js] nationality is required when isForeigner is true."
367
- );
368
- }
369
- if (!this.residenceStatus) {
370
- throw new Error(
371
- "[Employee.js] residenceStatus is required when isForeigner is true."
372
- );
373
- }
374
- if (!this.periodOfStay) {
375
- throw new Error(
376
- "[Employee.js] periodOfStay is required when isForeigner is true."
377
- );
378
- }
379
- } else {
380
- // 外国籍でない場合、関連フィールドを初期化
703
+ _initForeignerFields() {
704
+ if (!this.isForeigner) {
381
705
  this.foreignName = null;
382
706
  this.nationality = null;
383
707
  this.residenceStatus = null;
708
+ this.hasPeriodOfStayLimit = false;
384
709
  this.periodOfStay = null;
710
+ this.hasWorkRestrictions = false;
711
+ } else {
712
+ if (!this.hasPeriodOfStayLimit) {
713
+ this.periodOfStay = null;
714
+ }
385
715
  }
386
716
  }
387
717
 
388
718
  /**
389
- * 退職済である場合の必須フィールドを検証します。
390
- * - エラーがある場合は例外をスローします。
391
- * - `employmentStatus` が `terminated` の場合、以下のプロパティを必須とします。
392
- * - `dateOfTermination`
393
- * - `reasonOfTermination`
394
- * - `employmentStatus` が `active` の場合、`dateOfTermination`, `reasonOfTermination` を初期化します。
719
+ * 警備員登録に関連するフィールドを初期化します。
720
+ * - `hasSecurityGuardRegistration` が false の場合、以下のプロパティを初期化します。
721
+ * - `dateOfSecurityGuardRegistration`
722
+ * - `bloodType`
723
+ * - `emergencyContactName`
724
+ * - `emergencyContactRelation`
725
+ * - `emergencyContactRelationDetail`
726
+ * - `emergencyContactAddress`
727
+ * - `emergencyContactPhone`
728
+ * - `domicile`
395
729
  * @returns {void}
396
- * @throws {Error} 退職済の場合に必須フィールドが未入力の場合。
397
730
  */
398
- _validateTerminatedRequiredFields() {
399
- if (this.employmentStatus === EMPLOYMENT_STATUS_VALUES.TERMINATED.value) {
400
- if (!this.dateOfTermination) {
401
- throw new Error(
402
- "[Employee.js] dateOfTermination is required when employmentStatus is 'terminated'."
403
- );
404
- }
405
- if (!this.reasonOfTermination) {
406
- throw new Error(
407
- "[Employee.js] reasonOfTermination is required when employmentStatus is 'terminated'."
408
- );
409
- }
410
- } else {
411
- this.dateOfTermination = null;
412
- this.reasonOfTermination = null;
413
- }
414
- }
415
-
416
- _validateSecurityGuardFields() {
417
- if (this.hasSecurityGuardRegistration) {
418
- if (!this.dateOfSecurityGuardRegistration) {
419
- throw new Error(
420
- "[Employee.js] dateOfSecurityGuardRegistration is required when hasSecurityGuardRegistration is true."
421
- );
422
- }
423
- if (!this.emergencyContactName) {
424
- throw new Error(
425
- "[Employee.js] emergencyContactName is required when hasSecurityGuardRegistration is true."
426
- );
427
- }
428
- if (!this.emergencyContactRelationDetail) {
429
- throw new Error(
430
- "[Employee.js] emergencyContactRelationDetail is required when hasSecurityGuardRegistration is true."
431
- );
432
- }
433
- if (!this.emergencyContactAddress) {
434
- throw new Error(
435
- "[Employee.js] emergencyContactAddress is required when hasSecurityGuardRegistration is true."
436
- );
437
- }
438
- if (!this.emergencyContactPhone) {
439
- throw new Error(
440
- "[Employee.js] emergencyContactPhone is required when hasSecurityGuardRegistration is true."
441
- );
442
- }
443
- if (!this.domicile) {
444
- throw new Error(
445
- "[Employee.js] domicile is required when hasSecurityGuardRegistration is true."
446
- );
447
- }
448
- } else {
731
+ _initSecurityGuardFields() {
732
+ if (!this.hasSecurityGuardRegistration) {
449
733
  this.dateOfSecurityGuardRegistration = null;
450
734
  this.bloodType = BLOOD_TYPE_VALUES.A.value;
451
735
  this.emergencyContactName = null;
@@ -470,9 +754,11 @@ export default class Employee extends GeocodableMixin(FireModel) {
470
754
  */
471
755
  async beforeCreate(args = {}) {
472
756
  await super.beforeCreate(args);
473
- this._validateForeignerRequiredFields();
474
- this._validateTerminatedRequiredFields();
475
- this._validateSecurityGuardFields();
757
+ // this._validateForeignerRequiredFields();
758
+ this._initForeignerFields();
759
+ this._initTerminatedFields();
760
+ // this._validateSecurityGuardFields();
761
+ this._initSecurityGuardFields();
476
762
  }
477
763
 
478
764
  /**
@@ -497,13 +783,15 @@ export default class Employee extends GeocodableMixin(FireModel) {
497
783
  this._beforeData.employmentStatus === Employee.STATUS_ACTIVE
498
784
  ) {
499
785
  throw new Error(
500
- "[Employee.js] Direct changes to employmentStatus to 'terminated' are not allowed. Use toTerminated() method instead."
786
+ "[Employee.js] Direct changes to employmentStatus to 'terminated' are not allowed. Use toTerminated() method instead.",
501
787
  );
502
788
  }
503
789
 
504
- this._validateForeignerRequiredFields();
505
- this._validateTerminatedRequiredFields();
506
- this._validateSecurityGuardFields();
790
+ // this._validateForeignerRequiredFields();
791
+ this._initForeignerFields();
792
+ this._initTerminatedFields();
793
+ // this._validateSecurityGuardFields();
794
+ this._initSecurityGuardFields();
507
795
  }
508
796
 
509
797
  /**
@@ -520,23 +808,23 @@ export default class Employee extends GeocodableMixin(FireModel) {
520
808
  async toTerminated(dateOfTermination, reasonOfTermination, options = {}) {
521
809
  if (!this.docId) {
522
810
  throw new Error(
523
- "[Employee.js] docId is required to terminate an employee."
811
+ "[Employee.js] docId is required to terminate an employee.",
524
812
  );
525
813
  }
526
814
  if (!dateOfTermination || !(dateOfTermination instanceof Date)) {
527
815
  throw new Error(
528
- "[Employee.js] A valid dateOfTermination is required to terminate an employee."
816
+ "[Employee.js] A valid dateOfTermination is required to terminate an employee.",
529
817
  );
530
818
  }
531
819
  if (dateOfTermination < this.dateOfHire) {
532
820
  throw new Error(
533
- "[Employee.js] dateOfTermination cannot be earlier than dateOfHire."
821
+ "[Employee.js] dateOfTermination cannot be earlier than dateOfHire.",
534
822
  );
535
823
  }
536
824
 
537
825
  if (!reasonOfTermination || typeof reasonOfTermination !== "string") {
538
826
  throw new Error(
539
- "[Employee.js] A valid reasonOfTermination is required to terminate an employee."
827
+ "[Employee.js] A valid reasonOfTermination is required to terminate an employee.",
540
828
  );
541
829
  }
542
830