@shisyamo4131/air-guard-v2-schemas 2.3.8-dev.1 → 2.4.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 +1 -0
- package/package.json +1 -1
- package/src/Certification.js +6 -4
- package/src/Company.js +25 -5
- package/src/Employee.js +1 -4
- package/src/mixins/GeocodableMixin.js +149 -0
- package/src/parts/fieldDefinitions.js +37 -0
package/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { default as Company } from "./src/Company.js";
|
|
|
6
6
|
export { default as Customer, CustomerMinimal } from "./src/Customer.js";
|
|
7
7
|
export { default as CutoffDate } from "./src/utils/CutoffDate.js";
|
|
8
8
|
export { default as Employee } from "./src/Employee.js";
|
|
9
|
+
export { GeocodableMixin } from "./src/mixins/GeocodableMixin.js";
|
|
9
10
|
export { default as OperationBilling } from "./src/OperationBilling.js";
|
|
10
11
|
export { default as OperationResult } from "./src/OperationResult.js";
|
|
11
12
|
export { default as OperationResultDetail } from "./src/OperationResultDetail.js";
|
package/package.json
CHANGED
package/src/Certification.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* NOTE: 配列で管理されるので key が必要。2025-12-08 現在、資格名が重複することはない想定。
|
|
8
8
|
* 但し、2バイト文字が key として適切かは要検討。
|
|
9
|
+
*
|
|
10
|
+
* @update 2025-12-26 Set default to null for expirationDateAt
|
|
9
11
|
*/
|
|
10
12
|
import { BaseClass } from "@shisyamo4131/air-firebase-v2";
|
|
11
13
|
import { defField } from "./parts/fieldDefinitions.js";
|
|
@@ -13,10 +15,10 @@ import { defField } from "./parts/fieldDefinitions.js";
|
|
|
13
15
|
const classProps = {
|
|
14
16
|
name: defField("name", { label: "資格名", required: true }),
|
|
15
17
|
type: defField("certificationType", { required: true }),
|
|
16
|
-
issuedBy: defField("
|
|
17
|
-
issueDateAt: defField("
|
|
18
|
-
expirationDateAt: defField("
|
|
19
|
-
serialNumber: defField("
|
|
18
|
+
issuedBy: defField("issuedBy"),
|
|
19
|
+
issueDateAt: defField("issueDateAt", { required: true }),
|
|
20
|
+
expirationDateAt: defField("expirationDateAt"),
|
|
21
|
+
serialNumber: defField("serialNumber"),
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
/**
|
package/src/Company.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Company Model
|
|
3
|
-
* @version 1.
|
|
3
|
+
* @version 1.0.0
|
|
4
4
|
* @author shisyamo4131
|
|
5
|
-
* @update 2025-12-
|
|
6
|
-
* @update 2025-12-
|
|
7
|
-
* @update 2025-
|
|
8
|
-
* @update 2025-11-
|
|
5
|
+
* @update 2025-12-29 - Add `isCompleteRequiredFields` computed property.
|
|
6
|
+
* @update 2025-12-02 - Add maintenance information properties.
|
|
7
|
+
* @update 2025-12-01 - Add Stripe integration fields (stripeCustomerId, subscription).
|
|
8
|
+
* @update 2025-11-27 - Add bank information fields for billing.
|
|
9
|
+
* @update 2025-11-23 - Set `usePrefix` to false.
|
|
9
10
|
*/
|
|
10
11
|
import FireModel from "@shisyamo4131/air-firebase-v2";
|
|
11
12
|
import { defField } from "./parts/fieldDefinitions.js";
|
|
@@ -149,6 +150,25 @@ export default class Company extends FireModel {
|
|
|
149
150
|
},
|
|
150
151
|
set() {},
|
|
151
152
|
},
|
|
153
|
+
|
|
154
|
+
// Check if all `required` fields are filled.
|
|
155
|
+
// Used for validating whether the Company info is complete.
|
|
156
|
+
isCompleteRequiredFields: {
|
|
157
|
+
enumerable: true,
|
|
158
|
+
configurable: true,
|
|
159
|
+
get() {
|
|
160
|
+
return !!(
|
|
161
|
+
this.companyName &&
|
|
162
|
+
this.companyNameKana &&
|
|
163
|
+
this.zipcode &&
|
|
164
|
+
this.prefCode &&
|
|
165
|
+
this.city &&
|
|
166
|
+
this.address &&
|
|
167
|
+
this.tel
|
|
168
|
+
);
|
|
169
|
+
},
|
|
170
|
+
set() {},
|
|
171
|
+
},
|
|
152
172
|
});
|
|
153
173
|
|
|
154
174
|
/*************************************************************************
|
package/src/Employee.js
CHANGED
|
@@ -31,7 +31,6 @@ const classProps = {
|
|
|
31
31
|
employmentStatus: defField("employmentStatus", { required: true }),
|
|
32
32
|
title: defField("title"),
|
|
33
33
|
dateOfTermination: defField("dateOfTermination", {
|
|
34
|
-
default: null,
|
|
35
34
|
component: {
|
|
36
35
|
attrs: {
|
|
37
36
|
required: ({ item }) =>
|
|
@@ -89,9 +88,7 @@ const classProps = {
|
|
|
89
88
|
|
|
90
89
|
// Security guard related fields
|
|
91
90
|
hasSecurityGuardRegistration: defField("check", { label: "警備員登録" }),
|
|
92
|
-
dateOfSecurityGuardRegistration: defField("
|
|
93
|
-
label: "警備員登録日",
|
|
94
|
-
default: null,
|
|
91
|
+
dateOfSecurityGuardRegistration: defField("dateOfSecurityGuardRegistration", {
|
|
95
92
|
component: {
|
|
96
93
|
attrs: {
|
|
97
94
|
required: ({ item }) => item.hasSecurityGuardRegistration,
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/mixins/GeocodableMixin.js
|
|
3
|
+
* @description Geocoding 機能を提供する Mixin
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Geocoding 機能を提供する Mixin
|
|
8
|
+
* @param {Class} BaseClass - 継承元のクラス
|
|
9
|
+
* @returns {Class} Geocoding 機能を持つクラス
|
|
10
|
+
*/
|
|
11
|
+
export function GeocodableMixin(BaseClass) {
|
|
12
|
+
return class extends BaseClass {
|
|
13
|
+
/**
|
|
14
|
+
* Firestore 用のコンバーターを提供します。
|
|
15
|
+
* - location から geopoint を自動生成して Firestore に保存します。
|
|
16
|
+
* - toObject() は純粋なプレーンオブジェクトを返すため、コンポーネント側で安全に使用できます。
|
|
17
|
+
* @override
|
|
18
|
+
*/
|
|
19
|
+
static converter() {
|
|
20
|
+
const superConverter = super.converter();
|
|
21
|
+
return {
|
|
22
|
+
toFirestore: (instance) => {
|
|
23
|
+
const obj = superConverter.toFirestore(instance);
|
|
24
|
+
|
|
25
|
+
// location から geopoint を生成
|
|
26
|
+
if (obj.location?.lat && obj.location?.lng) {
|
|
27
|
+
try {
|
|
28
|
+
const adapter = this.getAdapter();
|
|
29
|
+
const GeoPoint = adapter.GeoPoint;
|
|
30
|
+
|
|
31
|
+
if (GeoPoint) {
|
|
32
|
+
obj.geopoint = new GeoPoint(obj.location.lat, obj.location.lng);
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.warn(
|
|
36
|
+
`[${this.className}.converter] GeoPoint generation failed:`,
|
|
37
|
+
error
|
|
38
|
+
);
|
|
39
|
+
obj.geopoint = null;
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
obj.geopoint = null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return obj;
|
|
46
|
+
},
|
|
47
|
+
fromFirestore: superConverter.fromFirestore,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 新しいドキュメントが作成される前に `location` を取得してセットします。
|
|
53
|
+
* @param {Object} args - Creation options.
|
|
54
|
+
* @param {boolean} [args.skipGeocoding=false] - Skip geocoding process.
|
|
55
|
+
* @returns {Promise<void>}
|
|
56
|
+
*/
|
|
57
|
+
async beforeCreate(args = {}) {
|
|
58
|
+
await super.beforeCreate(args);
|
|
59
|
+
|
|
60
|
+
if (!args.skipGeocoding) {
|
|
61
|
+
await this._geocodeAndSetLocation("beforeCreate");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* ドキュメントが更新される前に `location` を取得してセットします。
|
|
67
|
+
* 住所に変更がない場合はジオコーディングをスキップします。
|
|
68
|
+
* @param {Object} args - Update options.
|
|
69
|
+
* @param {boolean} [args.skipGeocoding=false] - Skip geocoding process.
|
|
70
|
+
* @returns {Promise<void>}
|
|
71
|
+
*/
|
|
72
|
+
async beforeUpdate(args = {}) {
|
|
73
|
+
await super.beforeUpdate(args);
|
|
74
|
+
|
|
75
|
+
const currentFullAddress = this.fullAddress;
|
|
76
|
+
const previousFullAddress = this._beforeData?.fullAddress;
|
|
77
|
+
|
|
78
|
+
// 住所に変更がない場合、またはskipGeocodingがtrueの場合はスキップ
|
|
79
|
+
if (
|
|
80
|
+
args.skipGeocoding ||
|
|
81
|
+
(this._beforeData && currentFullAddress === previousFullAddress)
|
|
82
|
+
) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await this._geocodeAndSetLocation("beforeUpdate");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* ジオコーディング処理
|
|
91
|
+
* fullAddress から緯度・経度・正規化された住所を取得し、location に設定
|
|
92
|
+
* @private
|
|
93
|
+
* @param {string} context - 呼び出し元のコンテキスト('beforeCreate' | 'beforeUpdate')
|
|
94
|
+
*/
|
|
95
|
+
async _geocodeAndSetLocation(context) {
|
|
96
|
+
const address = this.fullAddress;
|
|
97
|
+
|
|
98
|
+
if (!address || address.trim() === "") {
|
|
99
|
+
this.location = null;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!GeocodableMixin._geocodingFunction) {
|
|
104
|
+
console.warn(
|
|
105
|
+
`[${this.constructor.className}.${context}] Geocoding function not set. Skipping geocoding.`
|
|
106
|
+
);
|
|
107
|
+
this.location = null;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const coordinates = await GeocodableMixin._geocodingFunction(address);
|
|
113
|
+
|
|
114
|
+
if (coordinates && coordinates.lat && coordinates.lng) {
|
|
115
|
+
this.location = {
|
|
116
|
+
formattedAddress: coordinates.formattedAddress || address,
|
|
117
|
+
lat: coordinates.lat,
|
|
118
|
+
lng: coordinates.lng,
|
|
119
|
+
};
|
|
120
|
+
} else {
|
|
121
|
+
this.location = null;
|
|
122
|
+
}
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.warn(
|
|
125
|
+
`[${this.constructor.className}.${context}] Geocoding failed:`,
|
|
126
|
+
error
|
|
127
|
+
);
|
|
128
|
+
this.location = null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Geocoding 処理を行う関数(外部から注入)
|
|
136
|
+
* @type {Function|null}
|
|
137
|
+
* @static
|
|
138
|
+
*/
|
|
139
|
+
GeocodableMixin._geocodingFunction = null;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Geocoding 関数を設定します。
|
|
143
|
+
* すべての GeocodableMixin を継承したクラスで共有されます。
|
|
144
|
+
* @param {Function} fn - 住所文字列を受け取り、{ lat, lng, formattedAddress } を返す非同期関数
|
|
145
|
+
* @static
|
|
146
|
+
*/
|
|
147
|
+
GeocodableMixin.setGeocodingFunction = function (fn) {
|
|
148
|
+
GeocodableMixin._geocodingFunction = fn;
|
|
149
|
+
};
|
|
@@ -163,14 +163,39 @@ export const fieldDefinitions = {
|
|
|
163
163
|
dateOfBirth: {
|
|
164
164
|
...generalDefinitions.dateAt,
|
|
165
165
|
label: "生年月日",
|
|
166
|
+
default: null, // 2025-12-26 Set default to null
|
|
167
|
+
component: {
|
|
168
|
+
...generalDefinitions.dateAt.component,
|
|
169
|
+
attrs: {
|
|
170
|
+
viewMode: "year", // 2025-12-26 Added
|
|
171
|
+
},
|
|
172
|
+
},
|
|
166
173
|
},
|
|
167
174
|
dateOfHire: {
|
|
168
175
|
...generalDefinitions.dateAt,
|
|
169
176
|
label: "入社日",
|
|
170
177
|
},
|
|
178
|
+
// 2025-12-26 Added
|
|
179
|
+
dateOfSecurityGuardRegistration: {
|
|
180
|
+
...generalDefinitions.dateAt,
|
|
181
|
+
label: "警備員登録日",
|
|
182
|
+
default: null,
|
|
183
|
+
},
|
|
171
184
|
dateOfTermination: {
|
|
172
185
|
...generalDefinitions.dateAt,
|
|
173
186
|
label: "退職日",
|
|
187
|
+
default: null, // 2025-12-26 Set default to null
|
|
188
|
+
},
|
|
189
|
+
// 2025-12-26 Added
|
|
190
|
+
expirationDateAt: {
|
|
191
|
+
...generalDefinitions.dateAt,
|
|
192
|
+
label: "有効期限",
|
|
193
|
+
default: null,
|
|
194
|
+
},
|
|
195
|
+
// 2025-12-26 Added
|
|
196
|
+
issueDateAt: {
|
|
197
|
+
...generalDefinitions.dateAt,
|
|
198
|
+
label: "取得日",
|
|
174
199
|
},
|
|
175
200
|
periodOfStay: {
|
|
176
201
|
...generalDefinitions.dateAt,
|
|
@@ -350,6 +375,12 @@ export const fieldDefinitions = {
|
|
|
350
375
|
},
|
|
351
376
|
},
|
|
352
377
|
},
|
|
378
|
+
// 2025-12-26 Added
|
|
379
|
+
issuedBy: {
|
|
380
|
+
...generalDefinitions.oneLine,
|
|
381
|
+
label: "発行元",
|
|
382
|
+
length: 20,
|
|
383
|
+
},
|
|
353
384
|
lastName: {
|
|
354
385
|
...generalDefinitions.oneLine,
|
|
355
386
|
label: "姓",
|
|
@@ -409,6 +440,12 @@ export const fieldDefinitions = {
|
|
|
409
440
|
label: "退職理由",
|
|
410
441
|
length: 20,
|
|
411
442
|
},
|
|
443
|
+
// 2025-12-26 Added
|
|
444
|
+
serialNumber: {
|
|
445
|
+
...generalDefinitions.oneLine,
|
|
446
|
+
label: "証明書番号",
|
|
447
|
+
length: 20,
|
|
448
|
+
},
|
|
412
449
|
siteId: {
|
|
413
450
|
...generalDefinitions.oneLine,
|
|
414
451
|
label: "現場",
|