@shisyamo4131/air-guard-v2-schemas 2.4.2-dev.8 → 2.4.2-dev.81

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