@sneat/extension-docus-contract 0.0.1 → 0.0.2
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.
|
@@ -1,5 +1,252 @@
|
|
|
1
1
|
import { standardDocTypesByID } from '@sneat/extension-assetus-contract';
|
|
2
2
|
|
|
3
|
+
// Docus product-surface extension of the shared assetus document model.
|
|
4
|
+
//
|
|
5
|
+
// The canonical document data model (asset core + `document` extra + the
|
|
6
|
+
// per-doc-type field schema `IDocTypeStandardFields` / `standardDocTypesByID`
|
|
7
|
+
// + multi-contact refs via `memberIDs`/`membersInfo`) lives in
|
|
8
|
+
// `@sneat/extension-assetus-contract` (see the `unified-assetus-data-model`
|
|
9
|
+
// spec — "marriage certificate allows two members"). Docus does not fork
|
|
10
|
+
// that model; it narrows/presents it (same relationship as AnyMeter over
|
|
11
|
+
// trackus).
|
|
12
|
+
//
|
|
13
|
+
// What's missing from the *live* assetus registry for the document types this
|
|
14
|
+
// module adds is (a) per-role tagging of multiple contacts (today it is a
|
|
15
|
+
// single flat `members.max` count, no way to say "spouse 1" vs "spouse 2" or
|
|
16
|
+
// "child" vs "parent"), and (b) named typed fields beyond the fixed
|
|
17
|
+
// `title/number/issuedBy/issuedOn/validTill` slots (nationality, categories,
|
|
18
|
+
// institution, ...). Both are modelled here, locally, as a strict superset
|
|
19
|
+
// that stays representable inside the existing assetus DBO shape:
|
|
20
|
+
// - extra named fields are written into the assetus-provided generic
|
|
21
|
+
// `fieldsStr`/`fieldsDate`/`fieldsInt` bags (`IAssetRichFields`), not a
|
|
22
|
+
// new bespoke store.
|
|
23
|
+
// - the *contact-role selections themselves* (spouse 1/2, child, parent
|
|
24
|
+
// 1/2, holder, ...) are NOT persisted as a bespoke member/role array on
|
|
25
|
+
// the document. The canonical target for "this document relates these
|
|
26
|
+
// contacts, with these roles" is the shared **linkage** system
|
|
27
|
+
// (`sneat-core-modules/linkage/dbo4linkage` + `facade4linkage`):
|
|
28
|
+
// every linkable item is an `ItemRef{ExtID, Collection, ItemID}`, and
|
|
29
|
+
// role-tagged, bidirectional relationships between two ItemRefs are
|
|
30
|
+
// created/updated via `facade4linkage.SetRelated` /
|
|
31
|
+
// `update_item_relationships`. A document relating 2 spouses, or a
|
|
32
|
+
// child + parent(s), is exactly that: role-tagged edges from the
|
|
33
|
+
// document's ItemRef to each contact's ItemRef. This file only models
|
|
34
|
+
// the *shape* those edges would take (`IDocLinkageEdgeDraft`,
|
|
35
|
+
// `toLinkageEdgeDrafts`) and keeps it covered by tests — it does not
|
|
36
|
+
// call any facade (this frontend has no linkage client wired in yet)
|
|
37
|
+
// and does not persist the role tags anywhere. Only the flat,
|
|
38
|
+
// already-assetus-modelled `memberIDs`/`membersInfo` are written to the
|
|
39
|
+
// document today (see new-document-page.component.ts).
|
|
40
|
+
//
|
|
41
|
+
// Fable: persist as linkage edges (facade4linkage, role-tagged ItemRef) —
|
|
42
|
+
// do not bake a bespoke member/role array into the document DBO. Once a
|
|
43
|
+
// linkage client is reachable from this app, `toLinkageEdgeDrafts()`'s
|
|
44
|
+
// output feeds one `SetRelated` call per edge (documents feed the graph:
|
|
45
|
+
// a marriage cert creates a spouse<->spouse edge, a birth cert creates
|
|
46
|
+
// parent<->child edges).
|
|
47
|
+
//
|
|
48
|
+
// Fable: fold into assetus IDocTypeDef (canonical registry) — once assetus's
|
|
49
|
+
// IDocTypeDef supports per-role contact slots and named extra fields, this
|
|
50
|
+
// file's `docTypeSchemas` should be deleted and its data merged upstream;
|
|
51
|
+
// docus would go back to being a pure presentation layer, matching the
|
|
52
|
+
// existing TODO in `doc-type-presentation.ts`.
|
|
53
|
+
const docTypeCategoryLabels = {
|
|
54
|
+
identity: 'Identity',
|
|
55
|
+
family: 'Family',
|
|
56
|
+
vehicle: 'Vehicle',
|
|
57
|
+
education: 'Education',
|
|
58
|
+
legal: 'Legal',
|
|
59
|
+
};
|
|
60
|
+
function contactItemRef(spaceID, contactID) {
|
|
61
|
+
return {
|
|
62
|
+
extID: 'contactus',
|
|
63
|
+
collection: `contacts@${spaceID}`,
|
|
64
|
+
itemID: contactID,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function documentItemRef(spaceID, documentID) {
|
|
68
|
+
return {
|
|
69
|
+
extID: 'docus',
|
|
70
|
+
collection: `documents@${spaceID}`,
|
|
71
|
+
itemID: documentID,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Maps a document's contact-role selections 1:1 onto the linkage edges a
|
|
76
|
+
* real `facade4linkage.SetRelated` call would create: one role-tagged edge
|
|
77
|
+
* per selected contact, from the document's ItemRef to the contact's
|
|
78
|
+
* ItemRef. Pure mapping only — does not call any facade.
|
|
79
|
+
*/
|
|
80
|
+
function toLinkageEdgeDrafts(spaceID, documentID, refs) {
|
|
81
|
+
const from = documentItemRef(spaceID, documentID);
|
|
82
|
+
return refs.map((r) => ({
|
|
83
|
+
role: r.role,
|
|
84
|
+
from,
|
|
85
|
+
to: contactItemRef(spaceID, r.contactID),
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
// The flagship examples (marriage = exactly 2 contacts, birth = 2-3 contacts)
|
|
89
|
+
// plus a handful of everyday identity/education/legal document types.
|
|
90
|
+
const docTypeSchemas = {
|
|
91
|
+
marriage_cert: {
|
|
92
|
+
id: 'marriage_cert',
|
|
93
|
+
title: 'Marriage certificate',
|
|
94
|
+
emoji: '💍',
|
|
95
|
+
category: 'family',
|
|
96
|
+
contactRoles: [
|
|
97
|
+
{ id: 'spouse1', label: 'Spouse', min: 1, max: 1 },
|
|
98
|
+
{ id: 'spouse2', label: 'Spouse', min: 1, max: 1 },
|
|
99
|
+
],
|
|
100
|
+
fields: [
|
|
101
|
+
{ id: 'date', label: 'Date of marriage', type: 'date', required: true },
|
|
102
|
+
{ id: 'place', label: 'Place', type: 'text' },
|
|
103
|
+
{
|
|
104
|
+
id: 'number',
|
|
105
|
+
label: 'Certificate number',
|
|
106
|
+
type: 'text',
|
|
107
|
+
required: true,
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
birth_cert: {
|
|
112
|
+
id: 'birth_cert',
|
|
113
|
+
title: 'Birth certificate',
|
|
114
|
+
emoji: '👼',
|
|
115
|
+
category: 'family',
|
|
116
|
+
contactRoles: [
|
|
117
|
+
{ id: 'child', label: 'Child', min: 1, max: 1 },
|
|
118
|
+
{ id: 'parent1', label: 'Parent', min: 1, max: 1 },
|
|
119
|
+
{ id: 'parent2', label: 'Parent', min: 0, max: 1 },
|
|
120
|
+
],
|
|
121
|
+
fields: [
|
|
122
|
+
{
|
|
123
|
+
id: 'dateOfBirth',
|
|
124
|
+
label: 'Date of birth',
|
|
125
|
+
type: 'date',
|
|
126
|
+
required: true,
|
|
127
|
+
},
|
|
128
|
+
{ id: 'place', label: 'Place of birth', type: 'text' },
|
|
129
|
+
{
|
|
130
|
+
id: 'number',
|
|
131
|
+
label: 'Registration number',
|
|
132
|
+
type: 'text',
|
|
133
|
+
required: true,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
passport: {
|
|
138
|
+
id: 'passport',
|
|
139
|
+
title: 'Passport',
|
|
140
|
+
emoji: '🛂',
|
|
141
|
+
category: 'identity',
|
|
142
|
+
contactRoles: [{ id: 'holder', label: 'Holder', min: 1, max: 1 }],
|
|
143
|
+
fields: [
|
|
144
|
+
{ id: 'number', label: 'Passport number', type: 'text', required: true },
|
|
145
|
+
{ id: 'nationality', label: 'Nationality', type: 'text' },
|
|
146
|
+
{ id: 'issuedOn', label: 'Issue date', type: 'date', required: true },
|
|
147
|
+
{ id: 'expiresOn', label: 'Expiry date', type: 'date', required: true },
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
driving_license: {
|
|
151
|
+
id: 'driving_license',
|
|
152
|
+
title: 'Driving licence',
|
|
153
|
+
emoji: '🚗',
|
|
154
|
+
category: 'identity',
|
|
155
|
+
contactRoles: [{ id: 'holder', label: 'Holder', min: 1, max: 1 }],
|
|
156
|
+
fields: [
|
|
157
|
+
{ id: 'number', label: 'Licence number', type: 'text', required: true },
|
|
158
|
+
{ id: 'categories', label: 'Categories', type: 'text' },
|
|
159
|
+
{ id: 'issuedOn', label: 'Issue date', type: 'date', required: true },
|
|
160
|
+
{ id: 'expiresOn', label: 'Expiry date', type: 'date' },
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
// Fable: fold into assetus IDocTypeDef (canonical registry) — `id_card` is
|
|
164
|
+
// not yet present in the live `standardDocTypesByID`.
|
|
165
|
+
id_card: {
|
|
166
|
+
id: 'id_card',
|
|
167
|
+
title: 'National ID card',
|
|
168
|
+
emoji: '🪪',
|
|
169
|
+
category: 'identity',
|
|
170
|
+
contactRoles: [{ id: 'holder', label: 'Holder', min: 1, max: 1 }],
|
|
171
|
+
fields: [
|
|
172
|
+
{ id: 'number', label: 'ID number', type: 'text', required: true },
|
|
173
|
+
{ id: 'issuedOn', label: 'Issue date', type: 'date' },
|
|
174
|
+
{ id: 'expiresOn', label: 'Expiry date', type: 'date' },
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
// Fable: fold into assetus IDocTypeDef (canonical registry) — new type.
|
|
178
|
+
diploma: {
|
|
179
|
+
id: 'diploma',
|
|
180
|
+
title: 'Diploma / certificate',
|
|
181
|
+
emoji: '🎓',
|
|
182
|
+
category: 'education',
|
|
183
|
+
contactRoles: [{ id: 'recipient', label: 'Recipient', min: 1, max: 1 }],
|
|
184
|
+
fields: [
|
|
185
|
+
{ id: 'institution', label: 'Institution', type: 'text', required: true },
|
|
186
|
+
{ id: 'title', label: 'Title / degree', type: 'text', required: true },
|
|
187
|
+
{ id: 'date', label: 'Date', type: 'date', required: true },
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
// Fable: fold into assetus IDocTypeDef (canonical registry) — new type.
|
|
191
|
+
employment_contract: {
|
|
192
|
+
id: 'employment_contract',
|
|
193
|
+
title: 'Employment contract',
|
|
194
|
+
emoji: '🧑💼',
|
|
195
|
+
category: 'legal',
|
|
196
|
+
contactRoles: [
|
|
197
|
+
{ id: 'employee', label: 'Employee', min: 1, max: 1 },
|
|
198
|
+
{ id: 'employer', label: 'Employer', min: 1, max: 1 },
|
|
199
|
+
],
|
|
200
|
+
fields: [
|
|
201
|
+
{ id: 'startDate', label: 'Start date', type: 'date', required: true },
|
|
202
|
+
{ id: 'role', label: 'Role / position', type: 'text', required: true },
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
function getDocTypeSchema(id) {
|
|
207
|
+
return id ? docTypeSchemas[id] : undefined;
|
|
208
|
+
}
|
|
209
|
+
/** Doc-type schemas grouped for a sensibly-sectioned type picker. */
|
|
210
|
+
function groupedDocTypeSchemas() {
|
|
211
|
+
const categories = Object.keys(docTypeCategoryLabels);
|
|
212
|
+
return categories
|
|
213
|
+
.map((category) => ({
|
|
214
|
+
category,
|
|
215
|
+
label: docTypeCategoryLabels[category],
|
|
216
|
+
items: Object.values(docTypeSchemas).filter((s) => s.category === category),
|
|
217
|
+
}))
|
|
218
|
+
.filter((g) => g.items.length > 0);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Validates the min/max contact-count rules for every role of a schema.
|
|
222
|
+
* Returns a human-readable error per violated role; empty array = valid.
|
|
223
|
+
*/
|
|
224
|
+
function validateDocContactRoles(schema, refs) {
|
|
225
|
+
const errors = [];
|
|
226
|
+
for (const role of schema.contactRoles) {
|
|
227
|
+
const count = refs.filter((r) => r.role === role.id).length;
|
|
228
|
+
if (count < role.min) {
|
|
229
|
+
errors.push(role.min === role.max && role.max === 1
|
|
230
|
+
? `${role.label} is required`
|
|
231
|
+
: `${role.label}: at least ${role.min} required`);
|
|
232
|
+
}
|
|
233
|
+
else if (count > role.max) {
|
|
234
|
+
errors.push(`${role.label}: choose at most ${role.max}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return errors;
|
|
238
|
+
}
|
|
239
|
+
/** Validates the required typed fields of a schema. Empty array = valid. */
|
|
240
|
+
function validateDocFields(schema, values) {
|
|
241
|
+
const errors = [];
|
|
242
|
+
for (const f of schema.fields) {
|
|
243
|
+
if (f.required && !values[f.id]?.trim()) {
|
|
244
|
+
errors.push(`${f.label} is required`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return errors;
|
|
248
|
+
}
|
|
249
|
+
|
|
3
250
|
// Display title + emoji for each document type.
|
|
4
251
|
//
|
|
5
252
|
// The legacy @sneat/mod-assetus-core standardDocTypesByID carried `title` and
|
|
@@ -17,16 +264,30 @@ const docTypePresentation = {
|
|
|
17
264
|
marriage_cert: { title: 'Marriage certificate', emoji: '💍' },
|
|
18
265
|
};
|
|
19
266
|
// The live doc types, each decorated with its display title + emoji, in the
|
|
20
|
-
// lib's declaration order
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
267
|
+
// lib's declaration order, PLUS the docus-only additions from
|
|
268
|
+
// `doc-contact-roles.ts` (e.g. id_card, diploma, employment_contract) that
|
|
269
|
+
// are not yet in the shared assetus registry. Types present in both (e.g.
|
|
270
|
+
// marriage_cert, birth_cert, passport, driving_license) keep the assetus
|
|
271
|
+
// registry as the source of the id, with docus supplying the missing
|
|
272
|
+
// title/emoji as before.
|
|
273
|
+
const docTypeListItems = [
|
|
274
|
+
...Object.values(standardDocTypesByID).map((def) => ({
|
|
275
|
+
id: def.id,
|
|
276
|
+
title: docTypePresentation[def.id]?.title ?? def.id,
|
|
277
|
+
emoji: docTypePresentation[def.id]?.emoji,
|
|
278
|
+
})),
|
|
279
|
+
...Object.values(docTypeSchemas)
|
|
280
|
+
.filter((schema) => !(schema.id in standardDocTypesByID))
|
|
281
|
+
.map((schema) => ({
|
|
282
|
+
id: schema.id,
|
|
283
|
+
title: schema.title,
|
|
284
|
+
emoji: schema.emoji,
|
|
285
|
+
})),
|
|
286
|
+
];
|
|
26
287
|
|
|
27
288
|
/**
|
|
28
289
|
* Generated bundle index. Do not edit.
|
|
29
290
|
*/
|
|
30
291
|
|
|
31
|
-
export { docTypeListItems };
|
|
292
|
+
export { contactItemRef, docTypeCategoryLabels, docTypeListItems, docTypeSchemas, documentItemRef, getDocTypeSchema, groupedDocTypeSchemas, toLinkageEdgeDrafts, validateDocContactRoles, validateDocFields };
|
|
32
293
|
//# sourceMappingURL=sneat-extension-docus-contract.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sneat-extension-docus-contract.mjs","sources":["../../../../../../libs/extensions/docus/contract/src/lib/dbo/doc-type-presentation.ts","../../../../../../libs/extensions/docus/contract/src/sneat-extension-docus-contract.ts"],"sourcesContent":["import { standardDocTypesByID } from '@sneat/extension-assetus-contract';\n\n// Display title + emoji for each document type.\n//\n// The legacy @sneat/mod-assetus-core standardDocTypesByID carried `title` and\n// `emoji` on every entry; the live @sneat/extension-assetus IDocTypeDef is\n// validation-only ({ id, fields }) and dropped these presentation fields (and\n// renamed birth_certificate/marriage_certificate -> birth_cert/marriage_cert).\n// This map re-supplies the labels docus renders so the migration is\n// presentation-neutral. TODO: fold title/emoji back into the lib's IDocTypeDef\n// and delete this map.\nconst docTypePresentation: Record<string, { title: string; emoji?: string }> = {\n other: { title: 'Other' },\n passport: { title: 'Passport', emoji: '🛂' },\n driving_license: { title: 'Driving license', emoji: '🚗' },\n birth_cert: { title: 'Birth certificate', emoji: '👼' },\n marriage_cert: { title: 'Marriage certificate', emoji: '💍' },\n};\n\nexport interface IDocTypeListItem {\n readonly id: string;\n readonly title: string;\n readonly emoji?: string;\n}\n\n// The live doc types, each decorated with its display title + emoji, in the\n// lib's declaration order.\nexport const docTypeListItems: readonly IDocTypeListItem[] = Object.values(\n standardDocTypesByID,\n).map((def) => ({\n id: def.id,\n title: docTypePresentation[def.id]?.title ?? def.id,\n emoji: docTypePresentation[def.id]?.emoji,\n}));\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM,mBAAmB,GAAsD;AAC7E,IAAA,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE;IACzB,QAAQ,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE;IAC5C,eAAe,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,IAAI,EAAE;IAC1D,UAAU,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,IAAI,EAAE;IACvD,aAAa,EAAE,EAAE,KAAK,EAAE,sBAAsB,EAAE,KAAK,EAAE,IAAI,EAAE;CAC9D;AAQD;AACA;AACO,MAAM,gBAAgB,GAAgC,MAAM,CAAC,MAAM,CACxE,oBAAoB,CACrB,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM;IACd,EAAE,EAAE,GAAG,CAAC,EAAE;AACV,IAAA,KAAK,EAAE,mBAAmB,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,KAAK,IAAI,GAAG,CAAC,EAAE;IACnD,KAAK,EAAE,mBAAmB,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,KAAK;AAC1C,CAAA,CAAC;;ACjCF;;AAEG;;;;"}
|
|
1
|
+
{"version":3,"file":"sneat-extension-docus-contract.mjs","sources":["../../../../../../libs/extensions/docus/contract/src/lib/dbo/doc-contact-roles.ts","../../../../../../libs/extensions/docus/contract/src/lib/dbo/doc-type-presentation.ts","../../../../../../libs/extensions/docus/contract/src/sneat-extension-docus-contract.ts"],"sourcesContent":["// Docus product-surface extension of the shared assetus document model.\n//\n// The canonical document data model (asset core + `document` extra + the\n// per-doc-type field schema `IDocTypeStandardFields` / `standardDocTypesByID`\n// + multi-contact refs via `memberIDs`/`membersInfo`) lives in\n// `@sneat/extension-assetus-contract` (see the `unified-assetus-data-model`\n// spec — \"marriage certificate allows two members\"). Docus does not fork\n// that model; it narrows/presents it (same relationship as AnyMeter over\n// trackus).\n//\n// What's missing from the *live* assetus registry for the document types this\n// module adds is (a) per-role tagging of multiple contacts (today it is a\n// single flat `members.max` count, no way to say \"spouse 1\" vs \"spouse 2\" or\n// \"child\" vs \"parent\"), and (b) named typed fields beyond the fixed\n// `title/number/issuedBy/issuedOn/validTill` slots (nationality, categories,\n// institution, ...). Both are modelled here, locally, as a strict superset\n// that stays representable inside the existing assetus DBO shape:\n// - extra named fields are written into the assetus-provided generic\n// `fieldsStr`/`fieldsDate`/`fieldsInt` bags (`IAssetRichFields`), not a\n// new bespoke store.\n// - the *contact-role selections themselves* (spouse 1/2, child, parent\n// 1/2, holder, ...) are NOT persisted as a bespoke member/role array on\n// the document. The canonical target for \"this document relates these\n// contacts, with these roles\" is the shared **linkage** system\n// (`sneat-core-modules/linkage/dbo4linkage` + `facade4linkage`):\n// every linkable item is an `ItemRef{ExtID, Collection, ItemID}`, and\n// role-tagged, bidirectional relationships between two ItemRefs are\n// created/updated via `facade4linkage.SetRelated` /\n// `update_item_relationships`. A document relating 2 spouses, or a\n// child + parent(s), is exactly that: role-tagged edges from the\n// document's ItemRef to each contact's ItemRef. This file only models\n// the *shape* those edges would take (`IDocLinkageEdgeDraft`,\n// `toLinkageEdgeDrafts`) and keeps it covered by tests — it does not\n// call any facade (this frontend has no linkage client wired in yet)\n// and does not persist the role tags anywhere. Only the flat,\n// already-assetus-modelled `memberIDs`/`membersInfo` are written to the\n// document today (see new-document-page.component.ts).\n//\n// Fable: persist as linkage edges (facade4linkage, role-tagged ItemRef) —\n// do not bake a bespoke member/role array into the document DBO. Once a\n// linkage client is reachable from this app, `toLinkageEdgeDrafts()`'s\n// output feeds one `SetRelated` call per edge (documents feed the graph:\n// a marriage cert creates a spouse<->spouse edge, a birth cert creates\n// parent<->child edges).\n//\n// Fable: fold into assetus IDocTypeDef (canonical registry) — once assetus's\n// IDocTypeDef supports per-role contact slots and named extra fields, this\n// file's `docTypeSchemas` should be deleted and its data merged upstream;\n// docus would go back to being a pure presentation layer, matching the\n// existing TODO in `doc-type-presentation.ts`.\n\n/** Sensible groupings for the new-document type picker. */\nexport type DocTypeCategory =\n | 'identity'\n | 'family'\n | 'vehicle'\n | 'education'\n | 'legal';\n\nexport const docTypeCategoryLabels: Record<DocTypeCategory, string> = {\n identity: 'Identity',\n family: 'Family',\n vehicle: 'Vehicle',\n education: 'Education',\n legal: 'Legal',\n};\n\n/** A named contact slot a document type requires/allows (e.g. \"spouse 1\"). */\nexport interface IDocContactRoleDef {\n readonly id: string;\n readonly label: string;\n /** Minimum number of contacts that must fill this role. */\n readonly min: number;\n /** Maximum number of contacts that may fill this role. */\n readonly max: number;\n}\n\nexport type DocFieldType = 'text' | 'date' | 'number';\n\n/** A typed field beyond the standard assetus title/number/issuedOn/validTill. */\nexport interface IDocFieldDef {\n readonly id: string;\n readonly label: string;\n readonly type: DocFieldType;\n readonly required?: boolean;\n}\n\nexport interface IDocTypeSchema {\n readonly id: string;\n readonly title: string;\n readonly emoji?: string;\n readonly category: DocTypeCategory;\n readonly contactRoles: readonly IDocContactRoleDef[];\n readonly fields: readonly IDocFieldDef[];\n}\n\n/** A single contact reference tagged with the role it fills on a document. */\nexport interface IDocContactRef {\n readonly role: string;\n readonly contactID: string;\n}\n\n// --- Linkage-edge shape (not persisted here — see the header comment) ---\n//\n// Mirrors `sneat-core-modules/linkage/dbo4linkage.ItemRef`: every linkable\n// item (document, asset, contact, contract, ...) is identified by\n// {ExtID, Collection, ItemID}.\nexport interface IDocLinkageItemRef {\n readonly extID: string;\n readonly collection: string;\n readonly itemID: string;\n}\n\n/**\n * One role-tagged linkage edge a document implies once it can be persisted\n * through `facade4linkage` (`SetRelated`/`update_item_relationships`).\n *\n * Fable: persist as linkage edges (facade4linkage, role-tagged ItemRef) — do\n * not bake a bespoke member/role array. This type + `toLinkageEdgeDrafts`\n * exist so the mapping from \"contact-role selections in the new-document\n * form\" to \"linkage edges\" is defined and tested, ready for the day a\n * linkage client is wired into this app; nothing here is written to the\n * document DBO.\n */\nexport interface IDocLinkageEdgeDraft {\n readonly role: string;\n readonly from: IDocLinkageItemRef;\n readonly to: IDocLinkageItemRef;\n}\n\nexport function contactItemRef(\n spaceID: string,\n contactID: string,\n): IDocLinkageItemRef {\n return {\n extID: 'contactus',\n collection: `contacts@${spaceID}`,\n itemID: contactID,\n };\n}\n\nexport function documentItemRef(\n spaceID: string,\n documentID: string,\n): IDocLinkageItemRef {\n return {\n extID: 'docus',\n collection: `documents@${spaceID}`,\n itemID: documentID,\n };\n}\n\n/**\n * Maps a document's contact-role selections 1:1 onto the linkage edges a\n * real `facade4linkage.SetRelated` call would create: one role-tagged edge\n * per selected contact, from the document's ItemRef to the contact's\n * ItemRef. Pure mapping only — does not call any facade.\n */\nexport function toLinkageEdgeDrafts(\n spaceID: string,\n documentID: string,\n refs: readonly IDocContactRef[],\n): IDocLinkageEdgeDraft[] {\n const from = documentItemRef(spaceID, documentID);\n return refs.map((r) => ({\n role: r.role,\n from,\n to: contactItemRef(spaceID, r.contactID),\n }));\n}\n\n// The flagship examples (marriage = exactly 2 contacts, birth = 2-3 contacts)\n// plus a handful of everyday identity/education/legal document types.\nexport const docTypeSchemas: Record<string, IDocTypeSchema> = {\n marriage_cert: {\n id: 'marriage_cert',\n title: 'Marriage certificate',\n emoji: '💍',\n category: 'family',\n contactRoles: [\n { id: 'spouse1', label: 'Spouse', min: 1, max: 1 },\n { id: 'spouse2', label: 'Spouse', min: 1, max: 1 },\n ],\n fields: [\n { id: 'date', label: 'Date of marriage', type: 'date', required: true },\n { id: 'place', label: 'Place', type: 'text' },\n {\n id: 'number',\n label: 'Certificate number',\n type: 'text',\n required: true,\n },\n ],\n },\n birth_cert: {\n id: 'birth_cert',\n title: 'Birth certificate',\n emoji: '👼',\n category: 'family',\n contactRoles: [\n { id: 'child', label: 'Child', min: 1, max: 1 },\n { id: 'parent1', label: 'Parent', min: 1, max: 1 },\n { id: 'parent2', label: 'Parent', min: 0, max: 1 },\n ],\n fields: [\n {\n id: 'dateOfBirth',\n label: 'Date of birth',\n type: 'date',\n required: true,\n },\n { id: 'place', label: 'Place of birth', type: 'text' },\n {\n id: 'number',\n label: 'Registration number',\n type: 'text',\n required: true,\n },\n ],\n },\n passport: {\n id: 'passport',\n title: 'Passport',\n emoji: '🛂',\n category: 'identity',\n contactRoles: [{ id: 'holder', label: 'Holder', min: 1, max: 1 }],\n fields: [\n { id: 'number', label: 'Passport number', type: 'text', required: true },\n { id: 'nationality', label: 'Nationality', type: 'text' },\n { id: 'issuedOn', label: 'Issue date', type: 'date', required: true },\n { id: 'expiresOn', label: 'Expiry date', type: 'date', required: true },\n ],\n },\n driving_license: {\n id: 'driving_license',\n title: 'Driving licence',\n emoji: '🚗',\n category: 'identity',\n contactRoles: [{ id: 'holder', label: 'Holder', min: 1, max: 1 }],\n fields: [\n { id: 'number', label: 'Licence number', type: 'text', required: true },\n { id: 'categories', label: 'Categories', type: 'text' },\n { id: 'issuedOn', label: 'Issue date', type: 'date', required: true },\n { id: 'expiresOn', label: 'Expiry date', type: 'date' },\n ],\n },\n // Fable: fold into assetus IDocTypeDef (canonical registry) — `id_card` is\n // not yet present in the live `standardDocTypesByID`.\n id_card: {\n id: 'id_card',\n title: 'National ID card',\n emoji: '🪪',\n category: 'identity',\n contactRoles: [{ id: 'holder', label: 'Holder', min: 1, max: 1 }],\n fields: [\n { id: 'number', label: 'ID number', type: 'text', required: true },\n { id: 'issuedOn', label: 'Issue date', type: 'date' },\n { id: 'expiresOn', label: 'Expiry date', type: 'date' },\n ],\n },\n // Fable: fold into assetus IDocTypeDef (canonical registry) — new type.\n diploma: {\n id: 'diploma',\n title: 'Diploma / certificate',\n emoji: '🎓',\n category: 'education',\n contactRoles: [{ id: 'recipient', label: 'Recipient', min: 1, max: 1 }],\n fields: [\n { id: 'institution', label: 'Institution', type: 'text', required: true },\n { id: 'title', label: 'Title / degree', type: 'text', required: true },\n { id: 'date', label: 'Date', type: 'date', required: true },\n ],\n },\n // Fable: fold into assetus IDocTypeDef (canonical registry) — new type.\n employment_contract: {\n id: 'employment_contract',\n title: 'Employment contract',\n emoji: '🧑💼',\n category: 'legal',\n contactRoles: [\n { id: 'employee', label: 'Employee', min: 1, max: 1 },\n { id: 'employer', label: 'Employer', min: 1, max: 1 },\n ],\n fields: [\n { id: 'startDate', label: 'Start date', type: 'date', required: true },\n { id: 'role', label: 'Role / position', type: 'text', required: true },\n ],\n },\n};\n\nexport function getDocTypeSchema(\n id: string | undefined,\n): IDocTypeSchema | undefined {\n return id ? docTypeSchemas[id] : undefined;\n}\n\n/** Doc-type schemas grouped for a sensibly-sectioned type picker. */\nexport function groupedDocTypeSchemas(): readonly {\n category: DocTypeCategory;\n label: string;\n items: readonly IDocTypeSchema[];\n}[] {\n const categories = Object.keys(docTypeCategoryLabels) as DocTypeCategory[];\n return categories\n .map((category) => ({\n category,\n label: docTypeCategoryLabels[category],\n items: Object.values(docTypeSchemas).filter(\n (s) => s.category === category,\n ),\n }))\n .filter((g) => g.items.length > 0);\n}\n\n/**\n * Validates the min/max contact-count rules for every role of a schema.\n * Returns a human-readable error per violated role; empty array = valid.\n */\nexport function validateDocContactRoles(\n schema: IDocTypeSchema,\n refs: readonly IDocContactRef[],\n): string[] {\n const errors: string[] = [];\n for (const role of schema.contactRoles) {\n const count = refs.filter((r) => r.role === role.id).length;\n if (count < role.min) {\n errors.push(\n role.min === role.max && role.max === 1\n ? `${role.label} is required`\n : `${role.label}: at least ${role.min} required`,\n );\n } else if (count > role.max) {\n errors.push(`${role.label}: choose at most ${role.max}`);\n }\n }\n return errors;\n}\n\n/** Validates the required typed fields of a schema. Empty array = valid. */\nexport function validateDocFields(\n schema: IDocTypeSchema,\n values: Readonly<Record<string, string | undefined>>,\n): string[] {\n const errors: string[] = [];\n for (const f of schema.fields) {\n if (f.required && !values[f.id]?.trim()) {\n errors.push(`${f.label} is required`);\n }\n }\n return errors;\n}\n","import { standardDocTypesByID } from '@sneat/extension-assetus-contract';\nimport { docTypeSchemas } from './doc-contact-roles';\n\n// Display title + emoji for each document type.\n//\n// The legacy @sneat/mod-assetus-core standardDocTypesByID carried `title` and\n// `emoji` on every entry; the live @sneat/extension-assetus IDocTypeDef is\n// validation-only ({ id, fields }) and dropped these presentation fields (and\n// renamed birth_certificate/marriage_certificate -> birth_cert/marriage_cert).\n// This map re-supplies the labels docus renders so the migration is\n// presentation-neutral. TODO: fold title/emoji back into the lib's IDocTypeDef\n// and delete this map.\nconst docTypePresentation: Record<string, { title: string; emoji?: string }> = {\n other: { title: 'Other' },\n passport: { title: 'Passport', emoji: '🛂' },\n driving_license: { title: 'Driving license', emoji: '🚗' },\n birth_cert: { title: 'Birth certificate', emoji: '👼' },\n marriage_cert: { title: 'Marriage certificate', emoji: '💍' },\n};\n\nexport interface IDocTypeListItem {\n readonly id: string;\n readonly title: string;\n readonly emoji?: string;\n}\n\n// The live doc types, each decorated with its display title + emoji, in the\n// lib's declaration order, PLUS the docus-only additions from\n// `doc-contact-roles.ts` (e.g. id_card, diploma, employment_contract) that\n// are not yet in the shared assetus registry. Types present in both (e.g.\n// marriage_cert, birth_cert, passport, driving_license) keep the assetus\n// registry as the source of the id, with docus supplying the missing\n// title/emoji as before.\nexport const docTypeListItems: readonly IDocTypeListItem[] = [\n ...Object.values(standardDocTypesByID).map((def) => ({\n id: def.id,\n title: docTypePresentation[def.id]?.title ?? def.id,\n emoji: docTypePresentation[def.id]?.emoji,\n })),\n ...Object.values(docTypeSchemas)\n .filter((schema) => !(schema.id in standardDocTypesByID))\n .map((schema) => ({\n id: schema.id,\n title: schema.title,\n emoji: schema.emoji,\n })),\n];\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -5,5 +5,84 @@ interface IDocTypeListItem {
|
|
|
5
5
|
}
|
|
6
6
|
declare const docTypeListItems: readonly IDocTypeListItem[];
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
/** Sensible groupings for the new-document type picker. */
|
|
9
|
+
type DocTypeCategory = 'identity' | 'family' | 'vehicle' | 'education' | 'legal';
|
|
10
|
+
declare const docTypeCategoryLabels: Record<DocTypeCategory, string>;
|
|
11
|
+
/** A named contact slot a document type requires/allows (e.g. "spouse 1"). */
|
|
12
|
+
interface IDocContactRoleDef {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly label: string;
|
|
15
|
+
/** Minimum number of contacts that must fill this role. */
|
|
16
|
+
readonly min: number;
|
|
17
|
+
/** Maximum number of contacts that may fill this role. */
|
|
18
|
+
readonly max: number;
|
|
19
|
+
}
|
|
20
|
+
type DocFieldType = 'text' | 'date' | 'number';
|
|
21
|
+
/** A typed field beyond the standard assetus title/number/issuedOn/validTill. */
|
|
22
|
+
interface IDocFieldDef {
|
|
23
|
+
readonly id: string;
|
|
24
|
+
readonly label: string;
|
|
25
|
+
readonly type: DocFieldType;
|
|
26
|
+
readonly required?: boolean;
|
|
27
|
+
}
|
|
28
|
+
interface IDocTypeSchema {
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly title: string;
|
|
31
|
+
readonly emoji?: string;
|
|
32
|
+
readonly category: DocTypeCategory;
|
|
33
|
+
readonly contactRoles: readonly IDocContactRoleDef[];
|
|
34
|
+
readonly fields: readonly IDocFieldDef[];
|
|
35
|
+
}
|
|
36
|
+
/** A single contact reference tagged with the role it fills on a document. */
|
|
37
|
+
interface IDocContactRef {
|
|
38
|
+
readonly role: string;
|
|
39
|
+
readonly contactID: string;
|
|
40
|
+
}
|
|
41
|
+
interface IDocLinkageItemRef {
|
|
42
|
+
readonly extID: string;
|
|
43
|
+
readonly collection: string;
|
|
44
|
+
readonly itemID: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* One role-tagged linkage edge a document implies once it can be persisted
|
|
48
|
+
* through `facade4linkage` (`SetRelated`/`update_item_relationships`).
|
|
49
|
+
*
|
|
50
|
+
* Fable: persist as linkage edges (facade4linkage, role-tagged ItemRef) — do
|
|
51
|
+
* not bake a bespoke member/role array. This type + `toLinkageEdgeDrafts`
|
|
52
|
+
* exist so the mapping from "contact-role selections in the new-document
|
|
53
|
+
* form" to "linkage edges" is defined and tested, ready for the day a
|
|
54
|
+
* linkage client is wired into this app; nothing here is written to the
|
|
55
|
+
* document DBO.
|
|
56
|
+
*/
|
|
57
|
+
interface IDocLinkageEdgeDraft {
|
|
58
|
+
readonly role: string;
|
|
59
|
+
readonly from: IDocLinkageItemRef;
|
|
60
|
+
readonly to: IDocLinkageItemRef;
|
|
61
|
+
}
|
|
62
|
+
declare function contactItemRef(spaceID: string, contactID: string): IDocLinkageItemRef;
|
|
63
|
+
declare function documentItemRef(spaceID: string, documentID: string): IDocLinkageItemRef;
|
|
64
|
+
/**
|
|
65
|
+
* Maps a document's contact-role selections 1:1 onto the linkage edges a
|
|
66
|
+
* real `facade4linkage.SetRelated` call would create: one role-tagged edge
|
|
67
|
+
* per selected contact, from the document's ItemRef to the contact's
|
|
68
|
+
* ItemRef. Pure mapping only — does not call any facade.
|
|
69
|
+
*/
|
|
70
|
+
declare function toLinkageEdgeDrafts(spaceID: string, documentID: string, refs: readonly IDocContactRef[]): IDocLinkageEdgeDraft[];
|
|
71
|
+
declare const docTypeSchemas: Record<string, IDocTypeSchema>;
|
|
72
|
+
declare function getDocTypeSchema(id: string | undefined): IDocTypeSchema | undefined;
|
|
73
|
+
/** Doc-type schemas grouped for a sensibly-sectioned type picker. */
|
|
74
|
+
declare function groupedDocTypeSchemas(): readonly {
|
|
75
|
+
category: DocTypeCategory;
|
|
76
|
+
label: string;
|
|
77
|
+
items: readonly IDocTypeSchema[];
|
|
78
|
+
}[];
|
|
79
|
+
/**
|
|
80
|
+
* Validates the min/max contact-count rules for every role of a schema.
|
|
81
|
+
* Returns a human-readable error per violated role; empty array = valid.
|
|
82
|
+
*/
|
|
83
|
+
declare function validateDocContactRoles(schema: IDocTypeSchema, refs: readonly IDocContactRef[]): string[];
|
|
84
|
+
/** Validates the required typed fields of a schema. Empty array = valid. */
|
|
85
|
+
declare function validateDocFields(schema: IDocTypeSchema, values: Readonly<Record<string, string | undefined>>): string[];
|
|
86
|
+
|
|
87
|
+
export { contactItemRef, docTypeCategoryLabels, docTypeListItems, docTypeSchemas, documentItemRef, getDocTypeSchema, groupedDocTypeSchemas, toLinkageEdgeDrafts, validateDocContactRoles, validateDocFields };
|
|
88
|
+
export type { DocFieldType, DocTypeCategory, IDocContactRef, IDocContactRoleDef, IDocFieldDef, IDocLinkageEdgeDraft, IDocLinkageItemRef, IDocTypeListItem, IDocTypeSchema };
|