@shisyamo4131/air-guard-v2-schemas 2.3.7-dev.1 → 2.3.7-dev.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shisyamo4131/air-guard-v2-schemas",
3
- "version": "2.3.7-dev.1",
3
+ "version": "2.3.7-dev.11",
4
4
  "description": "Schemas for AirGuard V2",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/src/Company.js CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Company Model
3
- * @version 1.2.0
3
+ * @version 1.3.0
4
4
  * @author shisyamo4131
5
+ * @update 2025-12-01 Add Stripe integration fields (stripeCustomerId, subscription).
5
6
  * @update 2025-11-27 Add bank information fields for billing.
6
7
  * @update 2025-11-23 Set `usePrefix` to false.
7
8
  */
@@ -92,6 +93,24 @@ const classProps = {
92
93
  },
93
94
  },
94
95
  }),
96
+
97
+ /** Stripe連携フィールド */
98
+ stripeCustomerId: defField("oneLine", {
99
+ label: "Stripe顧客ID",
100
+ hidden: true,
101
+ length: 100,
102
+ }),
103
+
104
+ subscription: defField("object", {
105
+ label: "サブスクリプション情報",
106
+ hidden: true,
107
+ default: () => ({
108
+ id: null,
109
+ status: null,
110
+ currentPeriodEnd: null,
111
+ employeeLimit: 10,
112
+ }),
113
+ }),
95
114
  };
96
115
 
97
116
  export default class Company extends FireModel {
@@ -109,6 +128,22 @@ export default class Company extends FireModel {
109
128
  prefecture: defAccessor("prefecture"),
110
129
  });
111
130
 
131
+ Object.defineProperties(this, {
132
+ hasBankInfo: {
133
+ enumerable: true,
134
+ configurable: true,
135
+ get() {
136
+ return !!(
137
+ this.bankName &&
138
+ this.branchName &&
139
+ this.accountNumber &&
140
+ this.accountHolder
141
+ );
142
+ },
143
+ set() {},
144
+ },
145
+ });
146
+
112
147
  /*************************************************************************
113
148
  * CUSTOM METHODS FOR siteOrder ARRAY
114
149
  * Note: These methods modify the siteOrder array directly.
package/src/Operation.js CHANGED
@@ -2,9 +2,8 @@
2
2
  * Operation Model ver 1.0.0
3
3
  * @author shisyamo4131
4
4
  * ---------------------------------------------------------------------------
5
- * - Base class of SiteOperationSchedule based on WorkingResult.
6
- * - `dateAt` property indicates the date of operation (placement date)
7
- * used for billing purposes.
5
+ * - Base class of SiteOperationSchedule and OperationResult based on WorkingResult.
6
+ * - `dateAt` property indicates the date of operation (placement date) used for billing purposes.
8
7
  * Actual working day may differ from this date.
9
8
  * - `siteId`, `dateAt`, `shiftType`, and `regulationWorkMinutes` are
10
9
  * automatically synchronized to all assigned employees and outsourcers
@@ -12,27 +11,51 @@
12
11
  * - `startTime`, `endTime`, and `breakMinutes` are NOT synchronized here.
13
12
  * They should be synchronized at `SiteOperationSchedule` level instead.
14
13
  * ---------------------------------------------------------------------------
15
- * @prop {string} siteId - Site document ID (trigger property)
14
+ * @prop {string} siteId
15
+ * - Site document ID (trigger property)
16
16
  * - Automatically synchronizes to all `employees` and `outsourcers` when changed.
17
- * @prop {number} requiredPersonnel - Required number of personnel
18
- * @prop {boolean} qualificationRequired - Qualification required flag
19
- * @prop {string} workDescription - Work description
20
- * @prop {string} remarks - Remarks
21
- * @prop {Array<OperationDetail>} employees - Assigned employees
17
+ *
18
+ * @prop {number} requiredPersonnel
19
+ * - Required number of personnel
20
+ *
21
+ * @prop {boolean} qualificationRequired
22
+ * - Qualification required flag
23
+ *
24
+ * @prop {string} workDescription
25
+ * - Work description
26
+ *
27
+ * @prop {string} remarks
28
+ * - Remarks
29
+ *
30
+ * @prop {Array<OperationDetail>} employees
31
+ * - Assigned employees
22
32
  * - Array of `OperationDetail` instances representing assigned employees
23
- * @prop {Array<OperationDetail>} outsourcers - Assigned outsourcers
33
+ *
34
+ * @prop {Array<OperationDetail>} outsourcers
35
+ * - Assigned outsourcers
24
36
  * - Array of `OperationDetail` instances representing assigned outsourcers
25
- * ---------------------------------------------------------------------------
26
- * @computed {Array<string>} employeeIds - Array of employee IDs from `employees` (read-only)
27
- * @computed {Array<string>} outsourcerIds - Array of outsourcer IDs from `outsourcers` (read-only)
28
- * @computed {number} employeesCount - Count of assigned employees (read-only)
29
- * @computed {number} outsourcersCount - Count of assigned outsourcers (sum of amounts) (read-only)
30
- * @computed {boolean} isPersonnelShortage - Indicates if there is a shortage of personnel (read-only)
37
+ *
38
+ * @prop {Array<string>} employeeIds - Array of employee IDs from `employees` (read-only)
39
+ * - Array of employee IDs from `employees` (read-only)
40
+ *
41
+ * @prop {Array<string>} outsourcerIds
42
+ * - Array of outsourcer IDs from `outsourcers` (read-only)
43
+ *
44
+ * @prop {number} employeesCount
45
+ * - Count of assigned employees (read-only)
46
+ *
47
+ * @prop {number} outsourcersCount
48
+ * - Count of assigned outsourcers (sum of amounts) (read-only)
49
+ *
50
+ * @prop {boolean} isPersonnelShortage
51
+ * - Indicates if there is a shortage of personnel (read-only)
31
52
  * - `true` if the sum of `employeesCount` and `outsourcersCount` is less than `requiredPersonnel`
32
- * @computed {Array<OperationDetail>} workers - Combined array of `employees` and `outsourcers`
53
+ *
54
+ * @prop {Array<OperationDetail>} workers
55
+ * - Combined array of `employees` and `outsourcers`
33
56
  * - Getter: Returns concatenated array of employees and outsourcers
34
57
  * - Setter: Splits array into employees and outsourcers based on `isEmployee` property
35
- * ---------------------------------------------------------------------------
58
+ *
36
59
  * @getter {string} groupKey - Combines `siteId`, `shiftType`, and `date` to indicate operation grouping (read-only)
37
60
  * @getter {boolean} isEmployeesChanged - Indicates whether the employees have changed (read-only)
38
61
  * - Returns true if the employee IDs have changed compared to `_beforeData`
@@ -46,6 +69,8 @@
46
69
  * - Workers whose `startTime`, `isStartNextDay`, `endTime`, `breakMinutes`, `isQualified`, or `isOjt` have changed
47
70
  * ---------------------------------------------------------------------------
48
71
  * @inherited - The following properties are inherited from WorkingResult:
72
+ * @prop {string} key - Unique key combining `siteId`, `date`, `dayType`, and `shiftType` (override/read-only)
73
+ * - A unique identifier for the working result, combining `siteId`, `date`, `dayType`, and `shiftType`.
49
74
  * @prop {Date} dateAt - Date of operation (placement date) (trigger property)
50
75
  * - Automatically synchronizes to all `employees` and `outsourcers` when changed.
51
76
  * @prop {string} dayType - Day type (e.g., `WEEKDAY`, `WEEKEND`, `HOLIDAY`)
@@ -87,6 +112,8 @@
87
112
  * - Extracted from `endTime`.
88
113
  * @getter {number} endMinute - End minute (0-59) (read-only)
89
114
  * - Extracted from `endTime`.
115
+ * @getter {boolean} isKeyChanged - Flag indicating whether the key has changed compared to previous data (read-only)
116
+ * - Compares the current `key` with the `key` in `_beforeData`.
90
117
  * ---------------------------------------------------------------------------
91
118
  * @method {function} addWorker - Adds a new worker (employee or outsourcer)
92
119
  * - @param {Object} options - Options for adding a worker
@@ -234,6 +261,21 @@ export default class Operation extends WorkingResult {
234
261
  afterInitialize(item = {}) {
235
262
  super.afterInitialize(item);
236
263
 
264
+ /**
265
+ * Override `key` computed property
266
+ * - `key`: Combines `siteId`, `date`, `dayType`, and `shiftType`.
267
+ */
268
+ Object.defineProperties(this, {
269
+ key: {
270
+ configurable: true,
271
+ enumberable: true,
272
+ get() {
273
+ return `${this.siteId}-${this.date}-${this.dayType}-${this.shiftType}`;
274
+ },
275
+ set() {},
276
+ },
277
+ });
278
+
237
279
  /***********************************************************
238
280
  * TRIGGERS FOR SYNCRONIZATION TO EMPLOYEES AND OUTSOURCERS
239
281
  * ---------------------------------------------------------
@@ -581,8 +623,6 @@ export default class Operation extends WorkingResult {
581
623
  enumerable: false,
582
624
  },
583
625
  });
584
- /** Remove unnecessary properties */
585
- delete this.key; // From workingResult.js
586
626
  }
587
627
 
588
628
  /***************************************************************************
@@ -10,8 +10,12 @@
10
10
  * - Automatically updates `billingDateAt` based on `dateAt` and `cutoffDate`.
11
11
  * - Introduces a lock mechanism (`isLocked`) to prevent edits when necessary.
12
12
  *
13
+ * @prop {string} key - Unique key combining `siteId`, `date`, `dayType`, and `shiftType` (override/read-only)
14
+ * - A unique identifier for the working result, combining `siteId`, `date`, `dayType`, and `shiftType`.
15
+ *
13
16
  * @prop {string} siteId - Site document ID (trigger property)
14
17
  * - Automatically synchronizes to all `employees` and `outsourcers` when changed.
18
+ *
15
19
  * @prop {Date} dateAt - Date of operation (placement date) (trigger property)
16
20
  * - Automatically synchronizes to all `employees` and `outsourcers` when changed.
17
21
  * - Used to determine `dayType`.
@@ -128,6 +132,8 @@
128
132
  * - Extracted from `endTime`.
129
133
  * @getter {number} endMinute - End minute (0-59) (read-only)
130
134
  * - Extracted from `endTime`.
135
+ * @getter {boolean} isKeyChanged - Flag indicating whether the key has changed compared to previous data (read-only)
136
+ * - Compares the current `key` with the `key` in `_beforeData`.
131
137
  *
132
138
  * @method refreshBillingDateAt - Refresh billingDateAt based on dateAt and cutoffDate
133
139
  * - Updates `billingDateAt` based on the current `dateAt` and `cutoffDate` values.
@@ -535,11 +541,11 @@ export default class OperationResult extends Operation {
535
541
  }
536
542
 
537
543
  /**
538
- * Synchronize customerId from siteId
544
+ * Synchronize customerId and apply (re-apply) agreement from siteId
539
545
  * @returns {Promise<void>}
540
546
  * @throws {Error} If the specified siteId does not exist
541
547
  */
542
- async _syncCustomerId() {
548
+ async _syncCustomerIdAndApplyAgreement() {
543
549
  if (!this.siteId) return;
544
550
  const siteInstance = new Site();
545
551
  const siteExists = await siteInstance.fetch({ docId: this.siteId });
@@ -549,6 +555,7 @@ export default class OperationResult extends Operation {
549
555
  );
550
556
  }
551
557
  this.customerId = siteInstance.customerId;
558
+ this.agreement = siteInstance.getAgreement(this);
552
559
  }
553
560
 
554
561
  /**
@@ -558,12 +565,12 @@ export default class OperationResult extends Operation {
558
565
  async beforeCreate() {
559
566
  await super.beforeCreate();
560
567
 
561
- // Sync customerId
562
- await this._syncCustomerId();
568
+ // Sync customerId and apply agreement
569
+ await this._syncCustomerIdAndApplyAgreement();
563
570
  }
564
571
 
565
572
  /**
566
- * Override beforeUpdate to sync customerId if siteId changed
573
+ * Override beforeUpdate to sync customerId and apply agreement if key changed
567
574
  * @returns {Promise<void>}
568
575
  */
569
576
  async beforeUpdate() {
@@ -576,9 +583,9 @@ export default class OperationResult extends Operation {
576
583
  );
577
584
  }
578
585
 
579
- // Sync customerId if siteId changed
580
- if (this.siteId === this._beforeData.siteId && this.customerId) return;
581
- await this._syncCustomerId();
586
+ // Sync customerId and apply agreement if key changed
587
+ if (this.key === this._beforeData.key) return;
588
+ await this._syncCustomerIdAndApplyAgreement();
582
589
  }
583
590
 
584
591
  /**
@@ -1,8 +1,9 @@
1
1
  /*****************************************************************************
2
2
  * SiteOperationSchedule Model ver 1.1.0
3
- * @version 1.1.0
3
+ * @version 1.1.1
4
4
  * @author shisyamo4131
5
5
  *
6
+ * @update 2025-11-28 v1.1.1 - Removed the `agreement` parameter from `syncToOperationResult` method.
6
7
  * @update 2025-11-22 v1.1.0 - Moved `duplicate`, `notify`, `syncToOperationResult`,
7
8
  * and `toEvent` methods from client side code.
8
9
  *
@@ -17,6 +18,9 @@
17
18
  * `siteId`, `dateAt`, `shiftType`, and `regulationWorkMinutes` are synchronized
18
19
  * in the parent `Operation` class.
19
20
  *
21
+ * @prop {string} key - Unique key combining `siteId`, `date`, `dayType`, and `shiftType` (override/read-only)
22
+ * - A unique identifier for the working result, combining `siteId`, `date`, `dayType`, and `shiftType`.
23
+ *
20
24
  * @prop {string|null} operationResultId - Associated OperationResult document ID
21
25
  * - If an OperationResult has been created based on this schedule, this property
22
26
  * holds the ID of that OperationResult document.
@@ -149,6 +153,9 @@
149
153
  * @getter {number} endMinute - End minute (0-59) (read-only)
150
154
  * - Extracted from `endTime`.
151
155
  *
156
+ * @getter {boolean} isKeyChanged - Flag indicating whether the key has changed compared to previous data (read-only)
157
+ * - Compares the current `key` with the `key` in `_beforeData`.
158
+ *
152
159
  * @method duplicate - Duplicates the SiteOperationSchedule for specified dates
153
160
  * - Creates new SiteOperationSchedule documents for each specified date,
154
161
  * excluding the original date and avoiding duplicates.
@@ -161,7 +168,8 @@
161
168
  * @method syncToOperationResult - Creates an OperationResult document based on the current SiteOperationSchedule
162
169
  * - The OperationResult document ID will be the same as the SiteOperationSchedule document ID.
163
170
  * - Sets the `operationResultId` property of the SiteOperationSchedule to the created OperationResult document ID.
164
- * - Accepts an `agreement` object containing necessary properties for creating the OperationResult.
171
+ * - If an OperationResult already exists, it will be overwritten.
172
+ * - [UPDATE 2025-11-28] Removed the `agreement` parameter. The agreement is now fetched internally.
165
173
  *
166
174
  * @method toEvent - Converts the SiteOperationSchedule instance to a VCalendar event object
167
175
  * - Returns an object with properties required for displaying events in Vuetify's VCalendar component.
@@ -673,9 +681,14 @@ export default class SiteOperationSchedule extends Operation {
673
681
  * 既に存在する場合は上書きされます。
674
682
  * - 現場稼働予定ドキュメントの `operationResultId` プロパティに
675
683
  * 作成された稼働実績ドキュメントの ID が設定されます。(当該ドキュメント ID と同一)
676
- * @param {Object} agreement - 取極め情報オブジェクト。稼働実績ドキュメントの生成に必要なプロパティを含みます。
684
+ * @param {Object} notifications - 配置通知オブジェクトのマップ。
685
+ * - キー: 配置通知の一意キー(`notificationKey` プロパティ)
686
+ * - 値: 配置通知ドキュメントオブジェクト
687
+ * @returns {Promise<void>}
688
+ *
689
+ * @update 2025-11-28 - Removed the `agreement` parameter.
677
690
  */
678
- async syncToOperationResult(agreement, notifications = {}) {
691
+ async syncToOperationResult(notifications = {}) {
679
692
  if (!this.docId) {
680
693
  throw new Error(
681
694
  "不正な処理です。作成前の現場稼働予定から稼働実績を作成することはできません。"
@@ -689,18 +702,12 @@ export default class SiteOperationSchedule extends Operation {
689
702
  return this[prop].map((w) => {
690
703
  const notification = notifications[w.notificationKey];
691
704
  if (!notification) return w;
692
- const {
693
- actualStartTime: startTime,
694
- actualEndTime: endTime,
695
- actualBreakMinutes: breakMinutes,
696
- actualIsStartNextDay: isStartNextDay,
697
- } = notification;
698
705
  return new SiteOperationScheduleDetail({
699
706
  ...w.toObject(),
700
- startTime,
701
- endTime,
702
- breakMinutes,
703
- isStartNextDay,
707
+ startTime: notification.actualStartTime,
708
+ endTime: notification.actualEndTime,
709
+ breakMinutes: notification.actualBreakMinutes,
710
+ isStartNextDay: notification.actualIsStartNextDay,
704
711
  });
705
712
  });
706
713
  };
@@ -712,10 +719,8 @@ export default class SiteOperationSchedule extends Operation {
712
719
  ...this.toObject(),
713
720
  employees,
714
721
  outsourcers,
715
- agreement: agreement || null,
716
722
  siteOperationScheduleId: this.docId,
717
723
  });
718
- operationResult.refreshBillingDateAt();
719
724
  await this.constructor.runTransaction(async (transaction) => {
720
725
  const docRef = await operationResult.create({
721
726
  docId: this.docId,
package/src/System.js ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * System.js
3
+ * @version 1.0.0
4
+ * @description System model
5
+ * @author shisyamo4131
6
+ */
7
+ import FireModel from "@shisyamo4131/air-firebase-v2";
8
+ import { defField } from "./parts/fieldDefinitions.js";
9
+
10
+ const classProps = {
11
+ isMaintenance: defField("check", { default: false, hidden: true }),
12
+ updatedAt: defField("dateAt", { default: null, hidden: true }),
13
+ lastMaintenanceBy: defField("oneLine", {
14
+ default: "admin-sdk",
15
+ hidden: true,
16
+ }),
17
+ };
18
+
19
+ /**
20
+ * @prop {boolean} isMaintenance - maintenance mode flag
21
+ * @prop {Date} updatedAt - last updated timestamp
22
+ * @prop {string} lastMaintenanceBy - identifier of the last maintainer
23
+ */
24
+ export default class System extends FireModel {
25
+ static className = "System";
26
+ static collectionPath = "System";
27
+ static usePrefix = false;
28
+ static useAutonumber = false;
29
+ static logicalDelete = false;
30
+ static classProps = classProps;
31
+
32
+ async create() {
33
+ return Promise.reject(new Error("[System.js] Not implemented."));
34
+ }
35
+
36
+ async update() {
37
+ return Promise.reject(new Error("[System.js] Not implemented."));
38
+ }
39
+
40
+ async delete() {
41
+ return Promise.reject(new Error("[System.js] Not implemented."));
42
+ }
43
+ }
@@ -7,40 +7,41 @@
7
7
  * - `dateAt` is defined as a trigger property. When it is set, `dayType` is automatically updated.
8
8
  * - Subclasses can override `setDateAtCallback` to add custom behavior when `dateAt` changes.
9
9
  * ---------------------------------------------------------------------------
10
- * @props {Date} dateAt - Applicable start date (trigger property)
11
- * @props {string} dayType - Day type (e.g., `WEEKDAY`, `WEEKEND`, `HOLIDAY`)
12
- * @props {string} shiftType - Shift type (`DAY`, `NIGHT`)
13
- * @props {string} startTime - Start time (HH:MM format)
14
- * @props {boolean} isStartNextDay - Next day start flag
10
+ * @prop {Date} dateAt - Applicable start date (trigger property)
11
+ * @prop {string} dayType - Day type (e.g., `WEEKDAY`, `WEEKEND`, `HOLIDAY`)
12
+ * @prop {string} shiftType - Shift type (`DAY`, `NIGHT`)
13
+ * @prop {string} startTime - Start time (HH:MM format)
14
+ * @prop {boolean} isStartNextDay - Next day start flag
15
15
  * - `true` if the actual work starts the day after the placement date `dateAt`
16
- * @props {string} endTime - End time (HH:MM format)
17
- * @props {number} breakMinutes - Break time (minutes)
18
- * @props {number} regulationWorkMinutes - Regulation work minutes
16
+ * @prop {string} endTime - End time (HH:MM format)
17
+ * @prop {number} breakMinutes - Break time (minutes)
18
+ * @prop {number} regulationWorkMinutes - Regulation work minutes
19
19
  * - The maximum working time defined by `unitPriceBase` (or `unitPriceQualified`).
20
20
  * - Exceeding this time is considered overtime.
21
- * ---------------------------------------------------------------------------
22
- * @computed {string} key - Unique key combining `date`, `dayType`, and `shiftType` (read-only)
21
+ *
22
+ * @computed
23
+ * @prop {string} key - Unique key combining `date`, `dayType`, and `shiftType` (read-only)
23
24
  * - A unique identifier for the working result, combining `date`, `dayType`, and `shiftType`.
24
- * @computed {string} date - Date string in YYYY-MM-DD format based on `dateAt` (read-only)
25
+ * @prop {string} date - Date string in YYYY-MM-DD format based on `dateAt` (read-only)
25
26
  * - Returns a string in the format YYYY-MM-DD based on `dateAt`.
26
- * @computed {boolean} isSpansNextDay - Flag indicating whether the date spans from start date to end date (read-only)
27
+ * @prop {boolean} isSpansNextDay - Flag indicating whether the date spans from start date to end date (read-only)
27
28
  * - `true` if `startTime` is later than `endTime`
28
- * @computed {Date} startAt - Start date and time (Date object) (read-only)
29
+ * @prop {Date} startAt - Start date and time (Date object) (read-only)
29
30
  * - Returns a Date object with `startTime` set based on `dateAt`.
30
31
  * - If `isStartNextDay` is true, add 1 day.
31
- * @computed {Date} endAt - End date and time (Date object) (read-only)
32
+ * @prop {Date} endAt - End date and time (Date object) (read-only)
32
33
  * - Returns a Date object with `endTime` set based on `dateAt`.
33
34
  * - If `isStartNextDay` is true, add 1 day.
34
35
  * - If `isSpansNextDay` is true, add 1 day.
35
- * @computed {number} totalWorkMinutes - Total working time in minutes (excluding break time) (read-only)
36
+ * @prop {number} totalWorkMinutes - Total working time in minutes (excluding break time) (read-only)
36
37
  * - Calculated as the difference between `endAt` and `startAt` minus `breakMinutes`
37
38
  * - If the difference between `endAt` and `startAt` is negative, returns 0.
38
39
  * - If `startAt` or `endAt` is not set, returns 0.
39
- * @computed {number} regularTimeWorkMinutes - Regular working time in minutes (read-only)
40
+ * @prop {number} regularTimeWorkMinutes - Regular working time in minutes (read-only)
40
41
  * - The portion of `totalWorkMinutes` that is considered within the contract's `regulationWorkMinutes`.
41
42
  * - If actual working time is less than regulation time (e.g., early leave), it equals `totalWorkMinutes`.
42
43
  * - If actual working time exceeds regulation time (overtime), it equals `regulationWorkMinutes`.
43
- * @computed {number} overtimeWorkMinutes - Overtime work in minutes (read-only)
44
+ * @prop {number} overtimeWorkMinutes - Overtime work in minutes (read-only)
44
45
  * - Calculated as `totalWorkMinutes` minus `regulationWorkMinutes`
45
46
  * - Overtime work is not negative; the minimum is 0.
46
47
  * ---------------------------------------------------------------------------
@@ -52,6 +53,8 @@
52
53
  * - Extracted from `endTime`.
53
54
  * @getter {number} endMinute - End minute (0-59) (read-only)
54
55
  * - Extracted from `endTime`.
56
+ * @getter {boolean} isKeyChanged - Flag indicating whether the key has changed compared to previous data (read-only)
57
+ * - Compares the current `key` with the `key` in `_beforeData`.
55
58
  * ---------------------------------------------------------------------------
56
59
  * @method {function} setDateAtCallback - Callback method called when `dateAt` is set
57
60
  * - Override this method in subclasses to add custom behavior when `dateAt` changes.
@@ -301,4 +304,15 @@ export default class WorkingResult extends FireModel {
301
304
  get endMinute() {
302
305
  return this.endTime ? Number(this.endTime.split(":")[1]) : 0;
303
306
  }
307
+
308
+ /**
309
+ * Returns whether the key has changed compared to the previous data.
310
+ * - Compares the current `key` with the `key` in `_beforeData`.
311
+ * - Returns `true` if they are different, otherwise `false`.
312
+ */
313
+ get isKeyChanged() {
314
+ const current = this.key;
315
+ const before = this._beforeData?.key;
316
+ return current !== before;
317
+ }
304
318
  }