@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
@@ -0,0 +1,73 @@
1
+ /*****************************************************************************
2
+ * @file src/FcmToken.js
3
+ *****************************************************************************/
4
+ import FireModel from "@shisyamo4131/air-firebase-v2";
5
+ import { defField } from "./parts/fieldDefinitions.js";
6
+
7
+ /*****************************************************************************
8
+ * @class FcmToken
9
+ * @extends FireModel
10
+ *
11
+ * Firebase Cloud Messaging (FCM) のトークンをグローバル管理するためのコレクション
12
+ *
13
+ * - usePrefix: false でグローバルコレクション(Companies のサブコレクションではない)
14
+ * - ドキュメントID: FCMトークンそのもの
15
+ * - 同じトークンは同じデバイスを指すため、最後にログインしたユーザーの情報で上書きされる
16
+ *
17
+ * @property {string} token - FCMトークン(ドキュメントIDと同じ)
18
+ * @property {string} uid - ユーザーID(Firebase Authentication UID)
19
+ * @property {string} employeeId - 従業員ID
20
+ * @property {string} companyId - 会社ID
21
+ * @property {Date} updatedAt - 最終更新日時
22
+ *****************************************************************************/
23
+ export default class FcmToken extends FireModel {
24
+ static className = "FCMトークン";
25
+ static collectionPath = "FcmTokens";
26
+ static usePrefix = false; // グローバルコレクション
27
+
28
+ static classProps = {
29
+ token: defField("token", {
30
+ required: true,
31
+ hidden: true,
32
+ }),
33
+ uid: defField("uid", {
34
+ required: true,
35
+ hidden: true,
36
+ }),
37
+ companyId: defField("companyId", {
38
+ required: true,
39
+ hidden: true,
40
+ }),
41
+ updatedAt: defField("dateTimeAt", {
42
+ label: "更新日時",
43
+ default: () => new Date(),
44
+ hidden: true,
45
+ }),
46
+ };
47
+
48
+ /**
49
+ * fcmToken ドキュメントはドキュメントIDにトークンそのものが使われることを前提としているため、
50
+ * create メソッドをオーバーライドして、updateOptions に docId が含まれていることを確認する。
51
+ * @param {*} updateOptions
52
+ * @returns {Promise<DocumentReference>}
53
+ */
54
+ async create(updateOptions = {}) {
55
+ const { docId } = updateOptions;
56
+ if (!docId) {
57
+ throw new Error(
58
+ "FCMトークンのドキュメントIDはトークンそのものを指定してください",
59
+ );
60
+ }
61
+ return await super.create(updateOptions);
62
+ }
63
+
64
+ /**
65
+ * FCMトークンは更新できないため、常にエラーを投げる
66
+ * @param {*} updateOptions
67
+ */
68
+ async update(updateOptions = {}) {
69
+ throw new Error(
70
+ "FCMトークンは更新できません。新しいトークンでドキュメントを作成してください。",
71
+ );
72
+ }
73
+ }
@@ -0,0 +1,409 @@
1
+ /*****************************************************************************
2
+ * @file ./src/Insurance.js
3
+ * @description 保険(健康保険、厚生年金保険、雇用保険)ベースクラス
4
+ *****************************************************************************/
5
+ import { BaseClass } from "@shisyamo4131/air-firebase-v2";
6
+ import { VALUES as INSURANCE_STATUS } from "./constants/insurance-status.js";
7
+ import { defField } from "./parts/fieldDefinitions.js";
8
+ import { formatJstDate } from "./utils/index.js";
9
+
10
+ export const ERROR_MESSAGES = Object.freeze({
11
+ INVALID_TRANSITION: (allowedStates) =>
12
+ `不正な処理です。${allowedStates} の状態でなければ処理は行えません。`,
13
+ PROCESSING_ONLY: (operation) =>
14
+ `不正な処理です。'加入手続き中' の状態でなければ${operation}は行えません。`,
15
+ ENROLLED_ONLY: (operation) =>
16
+ `不正な処理です。'${INSURANCE_STATUS.ENROLLED.title}' の状態でなければ${operation}はできません。`,
17
+ REQUIRED_FIELD: (field) => `${field}の指定が必要です。`,
18
+ REQUIRED_DATE: (field) => `日付オブジェクトでの${field}の指定が必要です。`,
19
+ NO_HISTORY: "復元する履歴がありません。",
20
+ CANCEL_FIRST:
21
+ "手続き中は復元処理を行えません。加入手続き取下げ処理を行ってください。",
22
+ });
23
+
24
+ /*****************************************************************************
25
+ * @class Insurance
26
+ *
27
+ * @property {string} status - 保険の状態を表す文字列。`INSURANCE_STATUS` のいずれかの値を取ります。
28
+ * @property {string} previousStatus - 直前の保険の状態を表す文字列。`INSURANCE_STATUS` のいずれかの値を取ります。
29
+ * @property {Date|null} enrollmentDateAt - 加入日を表す日付オブジェクト。加入していない場合は null になります。
30
+ * @property {string|null} number - 被保険者番号(整理記号)を表す文字列。加入していない場合は null になります。
31
+ * @property {Date|null} lossDateAt - 喪失日を表す日付オブジェクト。加入していない場合は null になります。
32
+ * @property {string|null} lossReason - 喪失理由を表す文字列。加入していない場合は null になります。
33
+ * @property {boolean} isProcessing - 加入手続き中であるかどうかを表す真偽値。加入手続き中の場合は true、そうでない場合は false になります。
34
+ * @property {boolean} isRetire - 退職による喪失であるかどうかを表す真偽値。退職による喪失の場合は true、そうでない場合は false になります。
35
+ * @property {Array} history - 状態遷移の履歴を記録するための配列。各要素は状態遷移前の状態を表すオブジェクトです。
36
+ *
37
+ * @property {string} enrollmentDate - `enrollmentDateAt` に基づく YYYY-MM-DD 形式の日付文字列 (読み取り専用)
38
+ *
39
+ * @note
40
+ * - 各プロパティは `現在の状態` を表すもので、状態の遷移には対応するメソッドを利用します。
41
+ * - `lossDateAt`(喪失日)、`lossReason`(喪失理由)、`isRetire`(退職フラグ)は、`現在の状態` という意味では不要なプロパティですが、
42
+ * 資格喪失処理の際に必要になる情報であるため、プロパティとして用意しています。
43
+ * - 状態を遷移させるために必要な情報をこのインスタンスに設定(入力)する場合、
44
+ * 必ず複製したインスタンスのプロパティを編集し、複製元インスタンスの
45
+ * 状態遷移用メソッドを呼び出す際に引数として渡す、という形で利用してください。
46
+ * - 各状態遷移メソッドでは、`lossDateAt`(喪失日)、`lossReason`(喪失理由)、`isRetire`(退職フラグ)は必ず初期化されます。
47
+ *
48
+ * @method exempt - 現在の状態を `EXEMPT (適用除外)` に更新します。
49
+ * @method enroll - 現在の状態を `ENROLLED (加入)` に更新します。
50
+ * @method enrolled - 加入手続き中の状態を加入完了の状態に更新します。
51
+ * @method cancelEnroll - 加入手続き中の状態を加入前の状態に更新します。
52
+ * @method loss - 現在加入中の保険の喪失処理を行います。
53
+ * @method rollback - 履歴から最新の状態を復元します。
54
+ *****************************************************************************/
55
+ export default class Insurance extends BaseClass {
56
+ static className = "Insurance";
57
+ static classProps = {
58
+ status: defField("insuranceStatus", { required: true }),
59
+ previousStatus: defField("insuranceStatus", { default: "" }),
60
+ enrollmentDateAt: defField("enrollmentDateAt"),
61
+ number: defField("insuranceNumber"),
62
+ lossDateAt: defField("lossDateAt"),
63
+ lossReason: defField("lossReason"),
64
+ isProcessing: defField("check", { label: "加入手続き中", default: false }),
65
+ isRetire: defField("check", { label: "退職", default: false }),
66
+ history: defField("array"), // 状態遷移の履歴を記録するための配列
67
+ };
68
+
69
+ /**
70
+ * 状態遷移のルールを定義します。
71
+ * - `NOT_ENROLLED (未加入)` からは `ENROLLED (加入)` または `EXEMPT (適用除外)` に遷移可能です。
72
+ * - `ENROLLED (加入)` からは `NOT_ENROLLED (未加入)` または `EXEMPT (適用除外)` に遷移可能です。
73
+ * - `EXEMPT (適用除外)` からは `ENROLLED (加入)` に遷移可能です。
74
+ * - その他の遷移は不正とします。
75
+ */
76
+ static VALID_TRANSITIONS = {
77
+ // `NOT_ENROLLED` -> `ENROLLED` または `EXEMPT`
78
+ [INSURANCE_STATUS.NOT_ENROLLED.value]: [
79
+ INSURANCE_STATUS.ENROLLED.value,
80
+ INSURANCE_STATUS.EXEMPT.value,
81
+ ],
82
+
83
+ // `ENROLLED` -> `NOT_ENROLLED` または `EXEMPT`
84
+ [INSURANCE_STATUS.ENROLLED.value]: [
85
+ INSURANCE_STATUS.NOT_ENROLLED.value,
86
+ INSURANCE_STATUS.EXEMPT.value,
87
+ ],
88
+
89
+ // `EXEMPT` -> `ENROLLED`
90
+ [INSURANCE_STATUS.EXEMPT.value]: [INSURANCE_STATUS.ENROLLED.value],
91
+ };
92
+
93
+ afterInitialize(item = {}) {
94
+ super.afterInitialize(item);
95
+
96
+ Object.defineProperties(this, {
97
+ /**
98
+ * enrollmentDate - `enrollmentDateAt` に基づく YYYY-MM-DD 形式の日付文字列 (読み取り専用)
99
+ */
100
+ enrollmentDate: {
101
+ configurable: true,
102
+ enumerable: true,
103
+ get() {
104
+ return formatJstDate(this.enrollmentDateAt) || "";
105
+ },
106
+ set(v) {},
107
+ },
108
+ });
109
+ }
110
+
111
+ /**
112
+ * 状態チェック用ヘルパーメソッド
113
+ */
114
+ isNotEnrolled() {
115
+ return this.status === INSURANCE_STATUS.NOT_ENROLLED.value;
116
+ }
117
+
118
+ isEnrolled() {
119
+ return this.status === INSURANCE_STATUS.ENROLLED.value;
120
+ }
121
+
122
+ isExempt() {
123
+ return this.status === INSURANCE_STATUS.EXEMPT.value;
124
+ }
125
+
126
+ // 手続き状態チェック
127
+ isProcessingEnrollment() {
128
+ return this.isEnrolled() && this.isProcessing;
129
+ }
130
+
131
+ isEnrollmentComplete() {
132
+ return this.isEnrolled() && !this.isProcessing;
133
+ }
134
+
135
+ /**
136
+ * 状態遷移が可能かどうかを判定します。
137
+ * @param {string} newStatus 遷移先の状態
138
+ * @returns {boolean|string} 遷移可能な場合は true、遷移不可能な場合は許可されている状態のタイトル文字列(例: "'未加入' または '加入'")
139
+ */
140
+ _canTransitionTo(newStatus) {
141
+ const isValid =
142
+ Insurance.VALID_TRANSITIONS[this.status]?.includes(newStatus) || false;
143
+
144
+ if (!isValid) {
145
+ // 遷移先の状態に遷移可能な元の状態のタイトルを返す
146
+ const allowedStatuses = Object.values(INSURANCE_STATUS).filter((s) =>
147
+ Insurance.VALID_TRANSITIONS[s.value]?.includes(newStatus),
148
+ );
149
+
150
+ const allowedTitles = allowedStatuses
151
+ .map((s) => `'${s.title}'`)
152
+ .join(" または ");
153
+
154
+ return allowedTitles || "不正な状態";
155
+ }
156
+
157
+ return true;
158
+ }
159
+
160
+ /**
161
+ * 履歴エントリを作成します。
162
+ * @param {Object} options
163
+ * @param {Date|null} options.lossDateAt 喪失日
164
+ * @param {String|null} options.lossReason 喪失理由
165
+ * @returns {Object} 履歴エントリ
166
+ */
167
+ _createHistoryEntry({ lossDateAt = null, lossReason = null } = {}) {
168
+ return {
169
+ status: this.status,
170
+ previousStatus: this.previousStatus,
171
+ enrollmentDateAt: this.enrollmentDateAt,
172
+ lossDateAt,
173
+ lossReason,
174
+ number: this.number,
175
+ };
176
+ }
177
+
178
+ /**
179
+ * 状態を `EXEMPT (適用除外)` に更新します。
180
+ * - `NOT_ENROLLED (未加入)` または `ENROLLED (加入)` の状態でなければ加入手続きは行えません。
181
+ * - 現在加入中の場合、加入の履歴を記録します。ただし、加入手続き中 (isProcessing が true) である場合は、加入の実態がなかったこととし、履歴に記録しません。
182
+ * - 手続き中である場合、状態が `ENROLLED` から `EXEMPT` に更新される実務上の取り扱いは「加入申請の取り下げ(訂正)」に該当するものとし、加入の実態がなかったこととします。
183
+ * - 現在加入中の場合、`lossDateAt (喪失日)` と `lossReason (喪失理由)` の指定が必要です。
184
+ * - `EXEMPT` 状態への更新に伴い、加入日を null に更新します。
185
+ * - `EXEMPT` 状態への更新に伴い、加入手続き中である場合があるため `isProcessing` を false に更新します。
186
+ * - `EXEMPT` 状態への更新に伴い、被保険者番号(整理記号)を null に更新します。
187
+ * @param {Object} options
188
+ * @param {Date|null} options.lossDateAt 喪失日(現在加入中の場合に必要)
189
+ * @param {String|null} options.lossReason 喪失理由(現在加入中の場合に必要)
190
+ * @returns {void}
191
+ * @throws {Error} `status` が `NOT_ENROLLED (未加入)` または `ENROLLED (加入)` でない場合にエラーをスローします。
192
+ * @throws {Error} 現在加入中の場合に `lossDateAt` が日付オブジェクトでない場合にエラーをスローします。
193
+ * @throws {Error} 現在加入中の場合に `lossReason` が指定されていない場合にエラーをスローします。
194
+ */
195
+ exempt({ lossDateAt, lossReason } = {}) {
196
+ // validation
197
+ const transitionCheck = this._canTransitionTo(
198
+ INSURANCE_STATUS.EXEMPT.value,
199
+ );
200
+ if (transitionCheck !== true) {
201
+ throw new Error(
202
+ `不正な処理です。${transitionCheck} の状態でなければ適用除外処理は行えません。`,
203
+ );
204
+ }
205
+
206
+ // 履歴に記録するための状態を作成
207
+ const currentStatus = this._createHistoryEntry({ lossDateAt, lossReason });
208
+
209
+ // 加入完了している場合の検証(手続き中の場合は不要)
210
+ if (this.isEnrollmentComplete()) {
211
+ // validation
212
+ if (!lossDateAt || !(lossDateAt instanceof Date)) {
213
+ throw new Error(ERROR_MESSAGES.REQUIRED_DATE("喪失日"));
214
+ }
215
+ if (!lossReason) {
216
+ throw new Error(ERROR_MESSAGES.REQUIRED_FIELD("喪失理由"));
217
+ }
218
+ }
219
+
220
+ // 現在加入中の場合、履歴に反映(ただし、手続き中である場合を除く)
221
+ // 手続き中である場合、状態が `ENROLLED` から `EXEMPT` に更新される実務上の取り扱いは
222
+ // 「加入申請の取り下げ(訂正)」に該当するものとし、加入の実態がなかったこととする。
223
+ if (this.isEnrollmentComplete()) {
224
+ this.history.push(currentStatus);
225
+ }
226
+
227
+ this.previousStatus = this.status;
228
+ this.status = INSURANCE_STATUS.EXEMPT.value;
229
+ this.enrollmentDateAt = null;
230
+ this.isProcessing = false; // 加入手続き中である場合があるため false に更新しておく
231
+ this.number = null;
232
+ this.lossDateAt = null; // 念のため null に更新しておく
233
+ this.lossReason = null; // 念のため null に更新しておく
234
+ this.isRetire = false; // 念のため false に更新しておく
235
+ }
236
+
237
+ /**
238
+ * 状態を `ENROLLED (加入)` に更新します。
239
+ * - `NOT_ENROLLED (未加入)` または `EXEMPT (適用除外)` の状態でなければ加入手続きは行えません。
240
+ * - `isProcessing` が true の場合、被保険者番号(整理記号)の指定が必要です。
241
+ * @param {Object} options
242
+ * @param {Date} options.enrollmentDateAt 資格取得日
243
+ * @param {String} options.number 被保険者番号(整理記号)
244
+ * @param {Boolean} options.isProcessing 加入手続き中フラグ(true の場合、加入手続き中の状態で更新します)
245
+ * @returns {void}
246
+ * @throws {Error} `status` が `NOT_ENROLLED (未加入)` または `EXEMPT (適用除外)` でない場合にエラーをスローします。
247
+ * @throws {Error} `enrollmentDateAt` が日付オブジェクトでない場合にエラーをスローします。
248
+ * @throws {Error} `isProcessing` が true で `number` が指定されていない場合にエラーをスローします。
249
+ */
250
+ enroll({ enrollmentDateAt, number, isProcessing = false } = {}) {
251
+ // validation
252
+ const transitionCheck = this._canTransitionTo(
253
+ INSURANCE_STATUS.ENROLLED.value,
254
+ );
255
+ if (transitionCheck !== true) {
256
+ throw new Error(
257
+ `不正な処理です。${transitionCheck} の状態でなければ加入手続きは行えません。`,
258
+ );
259
+ }
260
+
261
+ if (!enrollmentDateAt || !(enrollmentDateAt instanceof Date)) {
262
+ throw new Error(ERROR_MESSAGES.REQUIRED_DATE("資格取得日"));
263
+ }
264
+
265
+ if (!isProcessing && !number) {
266
+ throw new Error(
267
+ ERROR_MESSAGES.REQUIRED_FIELD("被保険者番号(整理記号)"),
268
+ );
269
+ }
270
+
271
+ // 状態を更新
272
+ this.previousStatus = this.status;
273
+ this.status = INSURANCE_STATUS.ENROLLED.value;
274
+ this.enrollmentDateAt = enrollmentDateAt;
275
+ this.number = isProcessing ? null : number;
276
+ this.isProcessing = !!isProcessing;
277
+ this.lossDateAt = null; // 念のため null に更新しておく
278
+ this.lossReason = null; // 念のため null に更新しておく
279
+ this.isRetire = false; // 念のため false に更新しておく
280
+ }
281
+
282
+ /**
283
+ * 加入手続き中の状態を加入完了の状態に更新します。
284
+ * - 被保険者番号(整理記号)が決定したタイミングで行われる処理です。
285
+ * - `ENROLLED (加入)` かつ `isProcessing = true` の状態でなければ加入手続き完了処理は行えません。
286
+ * - 被保険者番号(整理記号)の指定が必要です。
287
+ * @param {Object} options
288
+ * @param {String} options.number 被保険者番号(整理記号)
289
+ * @returns {void}
290
+ * @throws {Error} `status` が `ENROLLED (加入)` で `isProcessing` が true でない場合にエラーをスローします。
291
+ * @throws {Error} 被保険者番号(整理記号)が指定されていない場合にエラーをスローします。
292
+ */
293
+ enrolled({ number } = {}) {
294
+ // validation
295
+ if (!this.isProcessingEnrollment()) {
296
+ throw new Error(ERROR_MESSAGES.PROCESSING_ONLY("加入手続き完了処理"));
297
+ }
298
+
299
+ if (!number) {
300
+ throw new Error(
301
+ ERROR_MESSAGES.REQUIRED_FIELD("被保険者番号(整理記号)"),
302
+ );
303
+ }
304
+
305
+ // 状態を更新
306
+ this.number = number;
307
+ this.isProcessing = false;
308
+ this.lossDateAt = null; // 念のため null に更新しておく
309
+ this.lossReason = null; // 念のため null に更新しておく
310
+ this.isRetire = false; // 念のため false に更新しておく
311
+ }
312
+
313
+ /**
314
+ * 加入手続き中の状態を加入前の状態に更新します。
315
+ * - `ENROLLED (加入)` かつ `isProcessing = true` の状態でなければ加入手続き取下げ処理は行えません。
316
+ * - 加入手続き取下げ処理は、加入の実態がなかったこととするため、履歴には記録しません。
317
+ * @returns {void}
318
+ * @throws {Error} `status` が `ENROLLED (加入)` で `isProcessing` が true でない場合にエラーをスローします。
319
+ */
320
+ cancelEnroll() {
321
+ // validation
322
+ if (!this.isProcessingEnrollment()) {
323
+ throw new Error(ERROR_MESSAGES.PROCESSING_ONLY("加入手続き取下げ処理"));
324
+ }
325
+
326
+ // 状態を更新
327
+ this.status = this.previousStatus;
328
+ this.previousStatus = null;
329
+ this.enrollmentDateAt = null;
330
+ this.number = null;
331
+ this.isProcessing = false;
332
+ this.lossDateAt = null; // 念のため null に更新しておく
333
+ this.lossReason = null; // 念のため null に更新しておく
334
+ this.isRetire = false; // 念のため false に更新しておく
335
+ }
336
+
337
+ /**
338
+ * 加入している保険の喪失処理を行います。
339
+ * - `ENROLLED (加入)` の状態でなければ喪失処理は行えません。
340
+ * - `lossDateAt`(喪失日)、`lossReason`(喪失理由)の指定が必要です。
341
+ * - `isRetire` が true の場合、状態を `EXEMPT (適用除外)` ではなく `NOT_ENROLLED (未加入)` に更新します。
342
+ * - `isRetire` が false の場合、状態を `EXEMPT (適用除外)` に更新します。
343
+ * - `history` プロパティに、更新前の状態を記録します。
344
+ * @param {*} options
345
+ * @param {*} options.lossDateAt 喪失日
346
+ * @param {*} options.lossReason 喪失理由
347
+ * @param {*} options.isRetire 退職フラグ(true の場合、状態を `EXEMPT (適用除外)` ではなく `NOT_ENROLLED (未加入)` に更新します)
348
+ * @returns {void}
349
+ * @throws {Error} `status` が `ENROLLED (加入)` でない場合にエラーをスローします。
350
+ * @throws {Error} 喪失日が日付オブジェクトでない場合にエラーをスローします。
351
+ * @throws {Error} 喪失理由が指定されていない場合にエラーをスローします。
352
+ */
353
+ loss({ lossDateAt, lossReason, isRetire = false } = {}) {
354
+ // validation
355
+ if (!this.isEnrolled()) {
356
+ throw new Error(ERROR_MESSAGES.ENROLLED_ONLY("喪失手続き"));
357
+ }
358
+ if (!lossDateAt || !(lossDateAt instanceof Date)) {
359
+ throw new Error(ERROR_MESSAGES.REQUIRED_DATE("喪失日"));
360
+ }
361
+ if (!lossReason) {
362
+ throw new Error(ERROR_MESSAGES.REQUIRED_FIELD("喪失理由"));
363
+ }
364
+
365
+ // 履歴に記録するための状態を作成
366
+ const currentStatus = this._createHistoryEntry({ lossDateAt, lossReason });
367
+
368
+ this.history.push(currentStatus);
369
+
370
+ this.previousStatus = this.status;
371
+ this.status = isRetire
372
+ ? INSURANCE_STATUS.NOT_ENROLLED.value
373
+ : INSURANCE_STATUS.EXEMPT.value;
374
+ this.enrollmentDateAt = null;
375
+ this.number = null;
376
+ this.lossDateAt = null; // 念のため null に更新しておく
377
+ this.lossReason = null; // 念のため null に更新しておく
378
+ this.isRetire = false; // 念のため false に更新しておく
379
+ }
380
+
381
+ /**
382
+ * 履歴から最新の状態を復元します。
383
+ * - `history` プロパティに復元できる状態が存在しない場合、エラーをスローします。
384
+ * - 復元処理は、加入手続き中である場合は行えません。加入手続き中である場合は、加入手続き取下げ処理を行ってください。
385
+ * - 復元処理は、`history` プロパティから最新の状態を取得し、`status`、`previousStatus`、`enrollmentDateAt`、`number` プロパティを復元します。
386
+ * - 復元処理は、`isProcessing` プロパティを false に更新します。
387
+ * @returns {void}
388
+ * @throws {Error} `history` プロパティに復元できる状態が存在しない場合にエラーをスローします。
389
+ * @throws {Error} 加入手続き中である場合にエラーをスローします。
390
+ */
391
+ rollback() {
392
+ // validation
393
+ if (this.history.length === 0) {
394
+ throw new Error(ERROR_MESSAGES.NO_HISTORY);
395
+ }
396
+ if (this.isProcessing) {
397
+ throw new Error(ERROR_MESSAGES.CANCEL_FIRST);
398
+ }
399
+ const latestStatus = this.history.pop();
400
+ this.status = latestStatus.status;
401
+ this.previousStatus = latestStatus.previousStatus;
402
+ this.enrollmentDateAt = latestStatus.enrollmentDateAt;
403
+ this.number = latestStatus.number;
404
+ this.isProcessing = false; // 念のため false に戻しておく
405
+ this.lossDateAt = null; // 念のため null に戻しておく
406
+ this.lossReason = null; // 念のため null に戻しておく
407
+ this.isRetire = false; // 念のため false に戻しておく
408
+ }
409
+ }
@@ -0,0 +1,95 @@
1
+ /*****************************************************************************
2
+ * @file src/Notification.js
3
+ *****************************************************************************/
4
+ import FireModel from "@shisyamo4131/air-firebase-v2";
5
+ import { defField } from "./parts/fieldDefinitions.js";
6
+
7
+ /*****************************************************************************
8
+ * @class Notification
9
+ * @extends FireModel
10
+ *
11
+ * @description
12
+ * 通知ドキュメントのスキーマ定義。
13
+ * Companies/{companyId}/Notifications/{notificationId} に格納される。
14
+ * 通知の送信履歴と結果を管理するための親ドキュメント。
15
+ *
16
+ * @property {string} title - 通知タイトル
17
+ * @property {string} body - 通知本文
18
+ * @property {string} imageUrl - 画像URL(任意)
19
+ * @property {Object} data - カスタムデータ(任意)
20
+ * @property {Array<string>} recipientUserIds - 送信先ユーザーIDの配列
21
+ * @property {number} totalCount - 送信対象数
22
+ * @property {number} successCount - 送信成功数
23
+ * @property {number} failureCount - 送信失敗数
24
+ * @property {string} status - 送信ステータス(pending, processing, sent, failed, completed)
25
+ * @property {string} sourceType - 送信元タイプ(manual, arrangement, billing など)
26
+ * @property {string} sourceId - 送信元ドキュメントID
27
+ * @property {string} createdBy - 作成者UID
28
+ *
29
+ * @note
30
+ * - status の値:
31
+ * - pending: 送信待ち
32
+ * - processing: 送信中
33
+ * - sent: 送信成功
34
+ * - failed: 送信失敗
35
+ * - completed: 全送信完了
36
+ * - sourceType の例:
37
+ * - manual: 手動送信(UI から)
38
+ * - arrangement: 配置通知
39
+ * - billing: 請求通知
40
+ *
41
+ * @author shisyamo4131
42
+ *****************************************************************************/
43
+ export default class Notification extends FireModel {
44
+ static className = "通知";
45
+ static collectionPath = "Notifications";
46
+ static classProps = {
47
+ title: defField("oneLine", { label: "通知タイトル", required: true }),
48
+ body: defField("multipleLine", { label: "通知本文", required: true }),
49
+ imageUrl: defField("oneLine", { label: "画像URL", required: false }),
50
+ data: defField("object", { label: "カスタムデータ", required: false }),
51
+ recipientUserIds: defField("recipientUserIds", { required: false }),
52
+ totalCount: defField("number", {
53
+ label: "送信対象数",
54
+ default: 0,
55
+ required: false,
56
+ hidden: true,
57
+ }),
58
+ successCount: defField("number", {
59
+ label: "送信成功数",
60
+ default: 0,
61
+ required: false,
62
+ hidden: true,
63
+ }),
64
+ failureCount: defField("number", {
65
+ label: "送信失敗数",
66
+ default: 0,
67
+ required: false,
68
+ hidden: true,
69
+ }),
70
+ status: defField("oneLine", {
71
+ label: "送信ステータス",
72
+ default: "pending",
73
+ required: false,
74
+ hidden: true,
75
+ }),
76
+ sourceType: defField("oneLine", {
77
+ label: "送信元タイプ",
78
+ default: "",
79
+ required: false,
80
+ hidden: true,
81
+ }),
82
+ sourceId: defField("oneLine", {
83
+ label: "送信元ID",
84
+ default: "",
85
+ required: false,
86
+ hidden: true,
87
+ }),
88
+ createdBy: defField("oneLine", {
89
+ label: "作成者",
90
+ default: "",
91
+ required: false,
92
+ hidden: true,
93
+ }),
94
+ };
95
+ }
@@ -0,0 +1,68 @@
1
+ /*****************************************************************************
2
+ * @file src/NotificationRecipient.js
3
+ *****************************************************************************/
4
+ import { BaseClass } from "@shisyamo4131/air-firebase-v2";
5
+ import { defField } from "./parts/fieldDefinitions.js";
6
+
7
+ /*****************************************************************************
8
+ * @class NotificationRecipient
9
+ * @extends BaseClass
10
+ *
11
+ * @description
12
+ * 通知の受信者情報を管理するサブコレクションのスキーマ定義。
13
+ * Companies/{companyId}/Notifications/{notificationId}/Recipients/{recipientId} に格納される。
14
+ * 個別ユーザーごとの送信結果を記録する。
15
+ *
16
+ * @property {string} notificationId - 親 Notification ドキュメントのID
17
+ * @property {string} userId - 受信者のユーザーID
18
+ * @property {string} status - 送信ステータス(pending, sent, failed)
19
+ * @property {Date} sentAt - 送信日時
20
+ * @property {string} error - エラーメッセージ
21
+ *
22
+ * @note
23
+ * - NotificationRecipient は BaseClass を継承(FireModel ではない)
24
+ * - Notification ドキュメントのサブコレクションとして格納される
25
+ * - Cloud Functions のみが作成・更新、アプリ側は読み取り専用
26
+ * - status の値:
27
+ * - pending: 送信待ち
28
+ * - sent: 送信成功
29
+ * - failed: 送信失敗
30
+ * - sentAt: 送信が完了した日時(成功・失敗問わず)
31
+ * - error: 送信失敗時のエラーメッセージ
32
+ *
33
+ * @author shisyamo4131
34
+ *****************************************************************************/
35
+ export default class NotificationRecipient extends BaseClass {
36
+ static className = "通知受信者";
37
+ static classProps = {
38
+ notificationId: defField("oneLine", {
39
+ label: "通知ID",
40
+ default: "",
41
+ required: true,
42
+ hidden: true,
43
+ }),
44
+ userId: defField("oneLine", {
45
+ label: "ユーザーID",
46
+ default: "",
47
+ required: true,
48
+ hidden: true,
49
+ }),
50
+ status: defField("oneLine", {
51
+ label: "送信ステータス",
52
+ default: "pending",
53
+ required: false,
54
+ hidden: true,
55
+ }),
56
+ sentAt: defField("dateTimeAt", {
57
+ label: "送信日時",
58
+ required: false,
59
+ hidden: true,
60
+ }),
61
+ error: defField("oneLine", {
62
+ label: "エラーメッセージ",
63
+ default: "",
64
+ required: false,
65
+ hidden: true,
66
+ }),
67
+ };
68
+ }