@shisyamo4131/air-guard-v2-schemas 1.3.1-dev.9 → 2.0.0

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/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { default as Agreement } from "./src/Agreement.js";
2
2
  export { default as ArrangementNotification } from "./src/ArrangementNotification.js";
3
+ export { default as Billing } from "./src/Billing.js";
3
4
  export { default as Company } from "./src/Company.js";
4
5
  export { default as Customer, CustomerMinimal } from "./src/Customer.js";
5
6
  export { default as CutoffDate } from "./src/utils/CutoffDate.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shisyamo4131/air-guard-v2-schemas",
3
- "version": "1.3.1-dev.9",
3
+ "version": "2.0.0",
4
4
  "description": "Schemas for AirGuard V2",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -139,7 +139,6 @@ import {
139
139
  VALUES,
140
140
  OPTIONS,
141
141
  } from "./constants/arrangement-notification-status.js";
142
- import { runTransaction } from "firebase/firestore";
143
142
 
144
143
  const classProps = {
145
144
  status: defField("arrangementNotificationStatus", { required: true }),
@@ -431,6 +430,14 @@ export default class ArrangementNotification extends SiteOperationScheduleDetail
431
430
  arguments: { id },
432
431
  };
433
432
  try {
433
+ // サーバー側での実行を禁止
434
+ if (this.type === "SERVER") {
435
+ throw new Error(
436
+ "fetchDocsBySiteOperationScheduleId is not supported on server side. " +
437
+ "Please use this method only on client side or implement server-specific logic with explicit prefix handling."
438
+ );
439
+ }
440
+
434
441
  const instance = new ArrangementNotification();
435
442
  const constraints = [["where", "siteOperationScheduleId", "==", id]];
436
443
  const result = await instance.fetchDocs({ constraints });
@@ -459,6 +466,14 @@ export default class ArrangementNotification extends SiteOperationScheduleDetail
459
466
  arguments: { ...options, transaction },
460
467
  };
461
468
  try {
469
+ // サーバー側での実行を禁止
470
+ if (this.type === "SERVER") {
471
+ throw new Error(
472
+ "bulkDelete is not supported on server side. " +
473
+ "Please use this method only on client side or implement server-specific logic with explicit prefix handling."
474
+ );
475
+ }
476
+
462
477
  const { siteOperationScheduleId, workerIds = [] } = options;
463
478
  if (!siteOperationScheduleId) {
464
479
  throw new Error("siteOperationScheduleId is required");
@@ -495,8 +510,7 @@ export default class ArrangementNotification extends SiteOperationScheduleDetail
495
510
  if (transaction) {
496
511
  await performTransaction(transaction);
497
512
  } else {
498
- const firestore = this.getAdapter().firestore;
499
- await runTransaction(firestore, performTransaction);
513
+ await this.runTransaction(performTransaction);
500
514
  }
501
515
  } catch (error) {
502
516
  throw new ContextualError(error.message, context);
package/src/Billing.js CHANGED
@@ -6,9 +6,8 @@
6
6
  *
7
7
  * @prop {string} customerId - customer document id
8
8
  * @prop {string} siteId - site document id
9
- * @prop {string} billingMonth - billing month (YYYY-MM format)
10
- * @prop {Date} billingDate - billing date
11
- * @prop {Date} paymentDueDate - payment due date
9
+ * @prop {Date} billingDateAt - billing date
10
+ * @prop {Date} paymentDueDateAt - payment due date
12
11
  *
13
12
  * @prop {Array} paymentRecords - payment records (not implemented yet)
14
13
  *
@@ -17,10 +16,14 @@
17
16
  * @prop {Object} adjustment - adjustment
18
17
  * @prop {string} remarks - remarks
19
18
  *
19
+ * @prop {string} billingMonth - billing month (YYYY-MM format) (read-only)
20
+ * @prop {Date} billingDate - billing date (YYYY-MM-DD format) (read-only)
21
+ * @prop {string} paymentDueMonth - payment due month (YYYY-MM format) (read-only)
22
+ * @prop {Date} paymentDueDate - payment due date (YYYY-MM-DD format) (read-only)
20
23
  * @prop {number} subtotal - subtotal (excluding tax) (computed-readonly)
21
24
  * @prop {number} taxAmount - tax amount (computed-readonly)
22
25
  * @prop {number} totalAmount - total amount (including tax) (computed-readonly)
23
- * @prop {Array<Object>} itemsSummary - billing items summary for display (computed-readonly)
26
+ * @prop {Array<Object>} summary - summary for display (computed-readonly)
24
27
  *****************************************************************************/
25
28
 
26
29
  import FireModel from "@shisyamo4131/air-firebase-v2";
@@ -37,9 +40,8 @@ const STATUS = {
37
40
  const classProps = {
38
41
  customerId: defField("customerId", { required: true }),
39
42
  siteId: defField("siteId", { required: true }),
40
- billingMonth: defField("oneLine", { required: true }),
41
- billingDateAt: defField("date"),
42
- paymentDueDateAt: defField("date"),
43
+ billingDateAt: defField("dateAt", { required: true }),
44
+ paymentDueDateAt: defField("dateAt"),
43
45
 
44
46
  // 入金管理用配列(現時点では未使用 将来の拡張用)
45
47
  paymentRecords: defField("array", { default: [] }), // Not implemented yet
@@ -66,6 +68,75 @@ export default class Billing extends FireModel {
66
68
  afterInitialize(item = {}) {
67
69
  super.afterInitialize(item);
68
70
 
71
+ // billingDate (YYYY-MM-DD) と billingMonth (YYYY-MM) の計算用プロパティを定義
72
+ Object.defineProperties(this, {
73
+ billingDate: {
74
+ configurable: true,
75
+ enumerable: true,
76
+ get() {
77
+ if (!this.billingDateAt) return null;
78
+ const jstDate = new Date(
79
+ this.billingDateAt.getTime() + 9 * 60 * 60 * 1000
80
+ ); /* JST補正 */
81
+ const year = jstDate.getUTCFullYear();
82
+ const month = jstDate.getUTCMonth() + 1;
83
+ const day = jstDate.getUTCDate();
84
+ return `${year}-${String(month).padStart(2, "0")}-${String(
85
+ day
86
+ ).padStart(2, "0")}`;
87
+ },
88
+ set(v) {},
89
+ },
90
+ billingMonth: {
91
+ configurable: true,
92
+ enumerable: true,
93
+ get() {
94
+ if (!this.billingDateAt) return null;
95
+ const jstDate = new Date(
96
+ this.billingDateAt.getTime() + 9 * 60 * 60 * 1000
97
+ ); /* JST補正 */
98
+ const year = jstDate.getUTCFullYear();
99
+ const month = jstDate.getUTCMonth() + 1;
100
+ return `${year}-${String(month).padStart(2, "0")}`;
101
+ },
102
+ set(v) {},
103
+ },
104
+ });
105
+
106
+ Object.defineProperties(this, {
107
+ paymentDueDate: {
108
+ configurable: true,
109
+ enumerable: true,
110
+ get() {
111
+ if (!this.paymentDueDateAt) return null;
112
+ const jstDate = new Date(
113
+ this.paymentDueDateAt.getTime() + 9 * 60 * 60 * 1000
114
+ ); /* JST補正 */
115
+ const year = jstDate.getUTCFullYear();
116
+ const month = jstDate.getUTCMonth() + 1;
117
+ const day = jstDate.getUTCDate();
118
+ return `${year}-${String(month).padStart(2, "0")}-${String(
119
+ day
120
+ ).padStart(2, "0")}`;
121
+ },
122
+ set(v) {},
123
+ },
124
+ paymentDueMonth: {
125
+ configurable: true,
126
+ enumerable: true,
127
+ get() {
128
+ if (!this.paymentDueDateAt) return null;
129
+ const jstDate = new Date(
130
+ this.paymentDueDateAt.getTime() + 9 * 60 * 60 * 1000
131
+ ); /* JST補正 */
132
+ const year = jstDate.getUTCFullYear();
133
+ const month = jstDate.getUTCMonth() + 1;
134
+ return `${year}-${String(month).padStart(2, "0")}`;
135
+ },
136
+ set(v) {},
137
+ },
138
+ });
139
+
69
140
  // 小計(税抜)を計算
70
141
  Object.defineProperty(this, "subtotal", {
71
142
  get() {
@@ -82,7 +153,9 @@ export default class Billing extends FireModel {
82
153
  // 消費税額を計算
83
154
  Object.defineProperty(this, "taxAmount", {
84
155
  get() {
85
- return Math.floor(this.subtotal * 0.1); // 10% 切り捨て
156
+ return this.operationResults.reduce((sum, item) => {
157
+ return sum + (item.tax || 0);
158
+ }, 0);
86
159
  },
87
160
  set() {},
88
161
  enumerable: true,
@@ -100,7 +173,7 @@ export default class Billing extends FireModel {
100
173
  });
101
174
 
102
175
  // 表示用の明細サマリーを生成
103
- Object.defineProperty(this, "itemsSummary", {
176
+ Object.defineProperty(this, "summary", {
104
177
  get() {
105
178
  return this.operationResults.map((item) => ({
106
179
  operationResultId: item.docId,
package/src/Customer.js CHANGED
@@ -29,6 +29,10 @@
29
29
  * @static
30
30
  * @prop {string} STATUS_ACTIVE - constant for active contract status
31
31
  * @prop {string} STATUS_TERMINATED - constant for terminated contract status
32
+ *
33
+ * @method getPaymentDueDateAt
34
+ * @param {Date} baseDate - base date in UTC (JST - 9 hours)
35
+ * @returns {Date} payment due date in UTC (JST - 9 hours)
32
36
  *****************************************************************************/
33
37
  import FireModel from "@shisyamo4131/air-firebase-v2";
34
38
  import { defField } from "./parts/fieldDefinitions.js";
@@ -99,6 +103,42 @@ export default class Customer extends FireModel {
99
103
  prefecture: defAccessor("prefecture"),
100
104
  });
101
105
  }
106
+
107
+ /**
108
+ * 支払期日を計算する
109
+ * @param {Date} baseDate - 基準日(JSTから9時間引いたUTC表現)
110
+ * @returns {Date} 支払期日(JSTから9時間引いたUTC表現)
111
+ */
112
+ getPaymentDueDateAt(baseDate) {
113
+ // UTC → JST に変換(+9時間)
114
+ const jstDate = new Date(baseDate.getTime() + 9 * 60 * 60 * 1000);
115
+
116
+ // UTCメソッドでJST相当の年月を取得
117
+ const year = jstDate.getUTCFullYear();
118
+ const month = jstDate.getUTCMonth();
119
+
120
+ // paymentMonth分加算した年月を計算
121
+ const targetMonth = month + this.paymentMonth;
122
+ const targetYear = year + Math.floor(targetMonth / 12);
123
+ const finalMonth = targetMonth % 12;
124
+
125
+ let dueDate;
126
+ if (this.paymentDate === CutoffDate.VALUES.END_OF_MONTH) {
127
+ // 月末の場合
128
+ dueDate = new Date(Date.UTC(targetYear, finalMonth + 1, 0));
129
+ } else {
130
+ // 指定日の場合
131
+ dueDate = new Date(Date.UTC(targetYear, finalMonth, this.paymentDate));
132
+
133
+ // 指定日が存在しない場合は月末にする
134
+ if (dueDate.getUTCMonth() !== finalMonth) {
135
+ dueDate = new Date(Date.UTC(targetYear, finalMonth + 1, 0));
136
+ }
137
+ }
138
+
139
+ // JST → UTC に変換(-9時間)
140
+ return new Date(dueDate.getTime() - 9 * 60 * 60 * 1000);
141
+ }
102
142
  }
103
143
 
104
144
  /*****************************************************************************
@@ -41,7 +41,6 @@
41
41
  * - Setter: Splits array into employees and outsourcers based on `isEmployee` property
42
42
  * @prop {string|null} siteOperationScheduleId - Associated SiteOperationSchedule document ID
43
43
  * - If this OperationResult was created from a SiteOperationSchedule, this property holds that ID.
44
- * - If this property is set, the instance cannot be deleted.
45
44
  * @prop {boolean} useAdjustedQuantity - Flag to indicate if adjusted quantities are used for billing
46
45
  * @prop {number} adjustedQuantityBase - Adjusted quantity for base workers
47
46
  * - Quantity used for billing base workers when `useAdjustedQuantity` is true.
@@ -80,9 +79,12 @@
80
79
  * @prop {number} overtimeWorkMinutes - Overtime work in minutes (read-only)
81
80
  * - Calculated as `totalWorkMinutes` minus `regulationWorkMinutes`
82
81
  * @prop {boolean} hasAgreement - Indicates if an Agreement is associated (read-only)
83
- * @prop {boolean} isValid - Indicates if the OperationResult is valid (read-only)
84
- * - Valid if either an Agreement is associated or adjusted quantities are not used.
85
- * - If `hasAgreement` is true, always valid.
82
+ * - `true` if `agreement` is set, otherwise `false`.
83
+ * @prop {string|false} isInvalid - Validation status (read-only)
84
+ * - Returns false if valid.
85
+ * - Returns reason code string if invalid:
86
+ * - `EMPTY_BILLING_DATE`: Billing date is missing.
87
+ * - `EMPTY_AGREEMENT`: Agreement is missing and `allowEmptyAgreement` is false.
86
88
  * @prop {Object} statistics - Statistics of workers (read-only)
87
89
  * - Contains counts and total work minutes for base and qualified workers, including OJT breakdowns.
88
90
  * - Structure: { base: {...}, qualified: {...}, total: {...} }
@@ -99,6 +101,8 @@
99
101
  * - Calculated using the `Tax` utility based on `salesAmount` and `date`.
100
102
  * @prop {number} billingAmount - Total billing amount including tax (read-only)
101
103
  * - Sum of `salesAmount` and `tax`.
104
+ * @prop {string|null} billingDate - Billing date in YYYY-MM-DD format (read-only)
105
+ * - Returns a string in the format YYYY-MM-DD based on `billingDateAt`.
102
106
  * @prop {string} billingMonth - Billing month in YYYY-MM format (read-only)
103
107
  * @prop {Array<string>} employeeIds - Array of employee IDs from `employees` (read-only)
104
108
  * @prop {Array<string>} outsourcerIds - Array of outsourcer IDs from `outsourcers` (read-only)
@@ -127,9 +131,6 @@
127
131
  * @getter {number} endMinute - End minute (0-59) (read-only)
128
132
  * - Extracted from `endTime`.
129
133
  *
130
- * @method beforeDelete - Override method to prevent deletion with siteOperationScheduleId
131
- * - Prevents deletion if the instance has `siteOperationScheduleId`.
132
- * - Throws an error if deletion is attempted on an instance created from SiteOperationSchedule.
133
134
  * @method refreshBillingDateAt - Refresh billingDateAt based on dateAt and cutoffDate
134
135
  * - Updates `billingDateAt` based on the current `dateAt` and `cutoffDate` values.
135
136
  * @method addWorker - Adds a new worker (employee or outsourcer)
@@ -167,12 +168,20 @@
167
168
  * - @param {Object|string} key - The combined key string or object
168
169
  * - @returns {Array<string>} - Array containing [siteId, shiftType, date]
169
170
  * - @throws {Error} - If the key is invalid.
171
+ * @method toggleLock - Toggle the lock status of an OperationResult document
172
+ * - @param {string} docId - Document ID
173
+ * - @param {boolean} value - Lock status value
174
+ * - @returns {Promise<void>}
170
175
  *
171
- * @override create - Override create method to indicate not implemented
176
+ * @override
177
+ * @method create - Override create method to indicate not implemented
172
178
  * - Creation of OperationBilling instances is not implemented, as billing records are typically
173
179
  * generated through the OperationResult class.
180
+ * @method update - Override update method to allow editing even when isLocked is true
181
+ * @method delete - Override delete method to allow deletion even when isLocked is true
174
182
  *****************************************************************************/
175
183
  import OperationResult from "./OperationResult.js";
184
+ import Operation from "./Operation.js";
176
185
 
177
186
  export default class OperationBilling extends OperationResult {
178
187
  static className = "稼働請求";
@@ -186,4 +195,45 @@ export default class OperationBilling extends OperationResult {
186
195
  async create() {
187
196
  return Promise.reject(new Error("[OperationBilling.js] Not implemented."));
188
197
  }
198
+
199
+ /**
200
+ * Override update method to allow editing even when isLocked is true
201
+ * @param {*} options
202
+ * @returns {Promise<void>}
203
+ */
204
+ async update(options = {}) {
205
+ // isLockedのチェックをスキップして、親クラス(Operation)のupdateを直接呼び出す
206
+ return await Operation.prototype.update.call(this, options);
207
+ }
208
+
209
+ /**
210
+ * Override delete method to allow deletion even when isLocked is true
211
+ * @param {*} options
212
+ * @returns {Promise<void>}
213
+ */
214
+ async delete(options = {}) {
215
+ // isLockedのチェックをスキップして、親クラス(Operation)のdeleteを直接呼び出す
216
+ return await Operation.prototype.delete.call(this, options);
217
+ }
218
+
219
+ /**
220
+ * Toggle the lock status of an OperationResult document
221
+ * @param {string} docId - Document ID
222
+ * @param {boolean} value - Lock status value
223
+ */
224
+ static async toggleLock(docId, value) {
225
+ if (!docId || typeof docId !== "string") {
226
+ throw new Error("Invalid docId provided to toggleLock method");
227
+ }
228
+ if (typeof value !== "boolean") {
229
+ throw new Error("Invalid value provided to toggleLock method");
230
+ }
231
+ const instance = new OperationBilling();
232
+ const doc = await instance.fetchDoc({ docId });
233
+ if (!doc) {
234
+ throw new Error(`OperationResult document with ID ${docId} not found`);
235
+ }
236
+ doc.isLocked = value;
237
+ await doc.update();
238
+ }
189
239
  }
@@ -5,7 +5,6 @@
5
5
  *
6
6
  * - Extends Operation class to represent the result of an operation.
7
7
  * - Also incorporates Agreement class properties for pricing and billing information.
8
- * - Prevents deletion if the instance has `siteOperationScheduleId`.
9
8
  * - Provides comprehensive billing calculations including statistics, sales amounts, and tax.
10
9
  * - Supports both daily and hourly billing with adjusted quantities.
11
10
  * - Automatically updates `billingDateAt` based on `dateAt` and `cutoffDate`.
@@ -41,7 +40,6 @@
41
40
  * - Setter: Splits array into employees and outsourcers based on `isEmployee` property
42
41
  * @prop {string|null} siteOperationScheduleId - Associated SiteOperationSchedule document ID
43
42
  * - If this OperationResult was created from a SiteOperationSchedule, this property holds that ID.
44
- * - If this property is set, the instance cannot be deleted.
45
43
  * @prop {boolean} useAdjustedQuantity - Flag to indicate if adjusted quantities are used for billing
46
44
  * @prop {number} adjustedQuantityBase - Adjusted quantity for base workers
47
45
  * - Quantity used for billing base workers when `useAdjustedQuantity` is true.
@@ -79,9 +77,12 @@
79
77
  * @prop {number} overtimeWorkMinutes - Overtime work in minutes (read-only)
80
78
  * - Calculated as `totalWorkMinutes` minus `regulationWorkMinutes`
81
79
  * @prop {boolean} hasAgreement - Indicates if an Agreement is associated (read-only)
82
- * @prop {boolean} isValid - Indicates if the OperationResult is valid (read-only)
83
- * - Valid if either an Agreement is associated or adjusted quantities are not used.
84
- * - If `hasAgreement` is true, always valid.
80
+ * - `true` if `agreement` is set, otherwise `false`.
81
+ * @prop {string|false} isInvalid - Validation status (read-only)
82
+ * - Returns false if valid.
83
+ * - Returns reason code string if invalid:
84
+ * - `EMPTY_BILLING_DATE`: Billing date is missing.
85
+ * - `EMPTY_AGREEMENT`: Agreement is missing and `allowEmptyAgreement` is false.
85
86
  * @prop {Object} statistics - Statistics of workers (read-only)
86
87
  * - Contains counts and total work minutes for base and qualified workers, including OJT breakdowns.
87
88
  * - Structure: { base: {...}, qualified: {...}, total: {...} }
@@ -98,6 +99,8 @@
98
99
  * - Calculated using the `Tax` utility based on `salesAmount` and `date`.
99
100
  * @prop {number} billingAmount - Total billing amount including tax (read-only)
100
101
  * - Sum of `salesAmount` and `tax`.
102
+ * @prop {string|null} billingDate - Billing date in YYYY-MM-DD format (read-only)
103
+ * - Returns a string in the format YYYY-MM-DD based on `billingDateAt`.
101
104
  * @prop {string} billingMonth - Billing month in YYYY-MM format (read-only)
102
105
  * @prop {Array<string>} employeeIds - Array of employee IDs from `employees` (read-only)
103
106
  * @prop {Array<string>} outsourcerIds - Array of outsourcer IDs from `outsourcers` (read-only)
@@ -126,9 +129,6 @@
126
129
  * @getter {number} endMinute - End minute (0-59) (read-only)
127
130
  * - Extracted from `endTime`.
128
131
  *
129
- * @method beforeDelete - Override method to prevent deletion with siteOperationScheduleId
130
- * - Prevents deletion if the instance has `siteOperationScheduleId`.
131
- * - Throws an error if deletion is attempted on an instance created from SiteOperationSchedule.
132
132
  * @method refreshBillingDateAt - Refresh billingDateAt based on dateAt and cutoffDate
133
133
  * - Updates `billingDateAt` based on the current `dateAt` and `cutoffDate` values.
134
134
  * @method addWorker - Adds a new worker (employee or outsourcer)
@@ -167,7 +167,11 @@
167
167
  * - @returns {Array<string>} - Array containing [siteId, shiftType, date]
168
168
  * - @throws {Error} - If the key is invalid.
169
169
  *
170
- * @override setDateAtCallback - Updates `billingDateAt` based on the new `dateAt` value.
170
+ * @override
171
+ * @method setDateAtCallback - Updates `billingDateAt` based on the new `dateAt` value.
172
+ * @method beforeCreate - Override to sync customerId from siteId
173
+ * @method beforeUpdate - Override to sync customerId from siteId when siteId changes
174
+ * @method beforeDelete - Override to prevent deletion if isLocked is true
171
175
  *****************************************************************************/
172
176
  import Operation from "./Operation.js";
173
177
  import Agreement from "./Agreement.js";
@@ -178,6 +182,7 @@ import Tax from "./tax.js";
178
182
  import { BILLING_UNIT_TYPE_PER_HOUR } from "./constants/billing-unit-type.js";
179
183
  import RoundSetting from "./RoundSetting.js";
180
184
  import CutoffDate from "./utils/CutoffDate.js";
185
+ import Site from "./Site.js";
181
186
 
182
187
  const classProps = {
183
188
  ...Operation.classProps,
@@ -202,7 +207,7 @@ const classProps = {
202
207
  label: "資格残業(調整)",
203
208
  default: 0,
204
209
  }),
205
- billingDateAt: defField("dateAt", { label: "請求日付" }),
210
+ billingDateAt: defField("dateAt", { label: "請求締日" }),
206
211
  employees: defField("array", { customClass: OperationResultDetail }),
207
212
  outsourcers: defField("array", {
208
213
  customClass: OperationResultDetail,
@@ -213,9 +218,21 @@ const classProps = {
213
218
  }),
214
219
  agreement: defField("object", { label: "取極め", customClass: Agreement }),
215
220
  allowEmptyAgreement: defField("check", {
216
- label: "取極めなしを無視",
221
+ label: "取極めなしを許容",
217
222
  default: false,
218
223
  }),
224
+
225
+ /**
226
+ * siteId から自動同期されるプロパティ
227
+ * - 従属する取引先の変更を不可としているため、ドキュメントの更新時に取得するだけで問題ない。
228
+ * - 但し、siteId が変更された時は再取得する必要がある。
229
+ */
230
+ customerId: defField("customerId", { required: true, hidden: true }),
231
+ };
232
+
233
+ const INVALID_REASON = {
234
+ EMPTY_BILLING_DATE: "EMPTY_BILLING_DATE",
235
+ EMPTY_AGREEMENT: "EMPTY_AGREEMENT",
219
236
  };
220
237
 
221
238
  export default class OperationResult extends Operation {
@@ -237,7 +254,6 @@ export default class OperationResult extends Operation {
237
254
  super.afterInitialize();
238
255
 
239
256
  /** Computed properties */
240
- let _agreement = this.agreement;
241
257
  Object.defineProperties(this, {
242
258
  statistics: {
243
259
  configurable: true,
@@ -408,7 +424,7 @@ export default class OperationResult extends Operation {
408
424
  },
409
425
  set(v) {},
410
426
  },
411
- billingMonth: {
427
+ billingDate: {
412
428
  configurable: true,
413
429
  enumerable: true,
414
430
  get() {
@@ -418,21 +434,26 @@ export default class OperationResult extends Operation {
418
434
  ); /* JST補正 */
419
435
  const year = jstDate.getUTCFullYear();
420
436
  const month = jstDate.getUTCMonth() + 1;
421
- return `${year}-${String(month).padStart(2, "0")}`;
437
+ const day = jstDate.getUTCDate();
438
+ return `${year}-${String(month).padStart(2, "0")}-${String(
439
+ day
440
+ ).padStart(2, "0")}`;
422
441
  },
423
442
  set(v) {},
424
443
  },
425
-
426
- agreement: {
444
+ billingMonth: {
427
445
  configurable: true,
428
446
  enumerable: true,
429
447
  get() {
430
- return _agreement;
431
- },
432
- set(v) {
433
- _agreement = v;
434
- this.refreshBillingDateAt();
448
+ if (!this.billingDateAt) return null;
449
+ const jstDate = new Date(
450
+ this.billingDateAt.getTime() + 9 * 60 * 60 * 1000
451
+ ); /* JST補正 */
452
+ const year = jstDate.getUTCFullYear();
453
+ const month = jstDate.getUTCMonth() + 1;
454
+ return `${year}-${String(month).padStart(2, "0")}`;
435
455
  },
456
+ set(v) {},
436
457
  },
437
458
  hasAgreement: {
438
459
  configurable: true,
@@ -442,16 +463,40 @@ export default class OperationResult extends Operation {
442
463
  },
443
464
  set(v) {},
444
465
  },
445
- isValid: {
466
+ isInvalid: {
446
467
  configurable: true,
447
468
  enumerable: true,
448
469
  get() {
449
- if (this.hasAgreement) return true;
450
- return this.allowEmptyAgreement;
470
+ if (!this.agreement && !this.allowEmptyAgreement) {
471
+ return INVALID_REASON.EMPTY_AGREEMENT;
472
+ }
473
+ if (!this.billingDateAt) {
474
+ return INVALID_REASON.EMPTY_BILLING_DATE;
475
+ }
476
+ return false;
451
477
  },
452
478
  set(v) {},
453
479
  },
454
480
  });
481
+
482
+ /** Triggers */
483
+ let _agreement = this.agreement;
484
+ Object.defineProperties(this, {
485
+ agreement: {
486
+ configurable: true,
487
+ enumerable: true,
488
+ get() {
489
+ return _agreement;
490
+ },
491
+ set(v) {
492
+ const oldKey = _agreement ? _agreement.key : null;
493
+ const newKey = v ? v.key : null;
494
+ if (oldKey === newKey) return;
495
+ _agreement = v;
496
+ this.refreshBillingDateAt();
497
+ },
498
+ },
499
+ });
455
500
  }
456
501
 
457
502
  /**
@@ -487,50 +532,65 @@ export default class OperationResult extends Operation {
487
532
  }
488
533
 
489
534
  /**
490
- * Override create method to validate billingDateAt when allowEmptyAgreement is true
491
- * @param {*} options
492
- * @returns {Promise<DocumentReference>}
535
+ * Synchronize customerId from siteId
536
+ * @returns {Promise<void>}
537
+ * @throws {Error} If the specified siteId does not exist
493
538
  */
494
- async create(options = {}) {
495
- if (this.allowEmptyAgreement && !this.billingDateAt) {
539
+ async _syncCustomerId() {
540
+ if (!this.siteId) return;
541
+ const siteInstance = new Site();
542
+ const siteExists = await siteInstance.fetch({ docId: this.siteId });
543
+ if (!siteExists) {
496
544
  throw new Error(
497
- "[OperationResult] Billing date is required when 'allowEmptyAgreement' is true."
545
+ `[OperationResult] The specified siteId (${this.siteId}) does not exist.`
498
546
  );
499
547
  }
500
- return await super.create(options);
548
+ this.customerId = siteInstance.customerId;
549
+ }
550
+
551
+ /**
552
+ * Override beforeCreate to sync customerId
553
+ * @returns {Promise<void>}
554
+ */
555
+ async beforeCreate() {
556
+ await super.beforeCreate();
557
+
558
+ // Sync customerId
559
+ await this._syncCustomerId();
501
560
  }
502
561
 
503
562
  /**
504
- * Override update method to prevent editing if isLocked is true
505
- * - Also validate billingDateAt when allowEmptyAgreement is true
506
- * @param {*} options
563
+ * Override beforeUpdate to sync customerId if siteId changed
507
564
  * @returns {Promise<void>}
508
565
  */
509
- async update(options = {}) {
566
+ async beforeUpdate() {
567
+ await super.beforeUpdate();
568
+
569
+ // Prevent editing if isLocked is true
510
570
  if (this.isLocked) {
511
571
  throw new Error(
512
572
  "[OperationResult] This OperationResult is locked and cannot be edited."
513
573
  );
514
574
  }
515
- if (this.allowEmptyAgreement && !this.billingDateAt) {
516
- throw new Error(
517
- "[OperationResult] Billing date is required when 'allowEmptyAgreement' is true."
518
- );
519
- }
520
- return await super.update(options);
575
+
576
+ // Sync customerId if siteId changed
577
+ if (this.siteId === this._beforeData.siteId && this.customerId) return;
578
+ await this._syncCustomerId();
521
579
  }
522
580
 
523
581
  /**
524
- * Override delete method to prevent deletion if isLocked is true
525
- * @param {*} options
582
+ * Override beforeDelete to prevent deletion if isLocked is true
526
583
  * @returns {Promise<void>}
584
+ * @throws {Error} If isLocked is true
527
585
  */
528
- async delete(options = {}) {
586
+ async beforeDelete() {
587
+ await super.beforeDelete();
588
+
589
+ // Prevent deletion if isLocked is true
529
590
  if (this.isLocked) {
530
591
  throw new Error(
531
592
  "[OperationResult] This OperationResult is locked and cannot be deleted."
532
593
  );
533
594
  }
534
- return await super.delete(options);
535
595
  }
536
596
  }
package/src/Site.js CHANGED
@@ -1,7 +1,10 @@
1
1
  /**
2
2
  * @file src/Site.js
3
3
  * @author shisyamo4131
4
- * @version 1.0.0
4
+ * @version 1.1.0
5
+ * @update 2025-11-20 version 0.2.0-bata
6
+ * - Prevent changing customer reference on update.
7
+ * - Move `customer` property to the top of classProps for better visibility.
5
8
  */
6
9
  import { default as FireModel } from "@shisyamo4131/air-firebase-v2";
7
10
  import { defField } from "./parts/fieldDefinitions.js";
@@ -12,6 +15,16 @@ import Agreement from "./Agreement.js";
12
15
  import { VALUES } from "./constants/site-status.js";
13
16
 
14
17
  const classProps = {
18
+ customer: defField("customer", {
19
+ required: true,
20
+ customClass: CustomerMinimal,
21
+ component: {
22
+ attrs: {
23
+ api: () => fetchDocsApi(CustomerMinimal),
24
+ noFilter: true,
25
+ },
26
+ },
27
+ }),
15
28
  code: defField("code", { label: "現場コード" }),
16
29
  name: defField("name", {
17
30
  label: "現場名",
@@ -29,16 +42,6 @@ const classProps = {
29
42
  address: defField("address", { required: true }),
30
43
  building: defField("building"),
31
44
  location: defField("location"),
32
- customer: defField("customer", {
33
- required: true,
34
- customClass: CustomerMinimal,
35
- component: {
36
- attrs: {
37
- api: () => fetchDocsApi(CustomerMinimal),
38
- noFilter: true,
39
- },
40
- },
41
- }),
42
45
  remarks: defField("multipleLine", { label: "備考" }),
43
46
  agreements: defField("array", { label: "取極め", customClass: Agreement }),
44
47
  status: defField("siteStatus", { required: true }),
@@ -115,6 +118,19 @@ export default class Site extends FireModel {
115
118
  static STATUS_ACTIVE = VALUES.ACTIVE.value;
116
119
  static STATUS_TERMINATED = VALUES.TERMINATED.value;
117
120
 
121
+ /**
122
+ * Override beforeUpdate to prevent changing customer reference.
123
+ * @returns {Promise<void>}
124
+ */
125
+ async beforeUpdate() {
126
+ await super.beforeUpdate();
127
+ if (this.customer.docId !== this._beforeData.customer.docId) {
128
+ return Promise.reject(
129
+ new Error("Not allowed to change customer reference.")
130
+ );
131
+ }
132
+ }
133
+
118
134
  afterInitialize(item = {}) {
119
135
  super.afterInitialize(item);
120
136
 
@@ -210,6 +210,18 @@ export const fieldDefinitions = {
210
210
  label: "市区町村",
211
211
  length: 10,
212
212
  },
213
+ customerId: {
214
+ ...generalDefinitions.oneLine,
215
+ label: "取引先",
216
+ component: {
217
+ name: "air-autocomplete-api",
218
+ attrs: {
219
+ itemValue: "docId",
220
+ itemTitle: "name",
221
+ noFilter: true,
222
+ },
223
+ },
224
+ },
213
225
  displayName: {
214
226
  ...generalDefinitions.oneLine,
215
227
  label: "表示名",