@opencxh/domain 1.235.0 → 1.238.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/dist/entities/company/types.d.ts +2 -0
- package/dist/entities/contact/types.d.ts +8 -0
- package/dist/entities/document-template/index.d.ts +3 -0
- package/dist/entities/document-template/locale.d.ts +38 -0
- package/dist/entities/document-template/tokens.d.ts +39 -0
- package/dist/entities/document-template/tokens.test.d.ts +1 -0
- package/dist/entities/document-template/types.d.ts +87 -0
- package/dist/entities/kb/locales.d.ts +6 -32
- package/dist/entities/kb/types.d.ts +9 -0
- package/dist/entities/organization/types.d.ts +8 -0
- package/dist/index.cjs +21 -14
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1626 -1392
- package/dist/platform/document-parse.d.ts +2 -0
- package/dist/platform/document-text.d.ts +14 -0
- package/dist/platform/document-text.test.d.ts +1 -0
- package/dist/platform/locales.d.ts +38 -0
- package/dist/platform/locales.test.d.ts +1 -0
- package/package.json +1 -1
- /package/dist/entities/{kb/locales.test.d.ts → document-template/locale.test.d.ts} +0 -0
|
@@ -39,6 +39,8 @@ export interface Company {
|
|
|
39
39
|
phone?: string;
|
|
40
40
|
website?: string;
|
|
41
41
|
notes?: string;
|
|
42
|
+
/** The language to write to this company in. The rung under {@link Contact.locale}. */
|
|
43
|
+
locale?: string;
|
|
42
44
|
}
|
|
43
45
|
/** What a resolve returns: which company, and how we got there. */
|
|
44
46
|
export interface CompanyMatch {
|
|
@@ -47,6 +47,14 @@ export interface Contact {
|
|
|
47
47
|
keys?: string[];
|
|
48
48
|
/** Namespaced source references (`ms:AAMk…`, `google:people/c123`, `hubspot:42`). */
|
|
49
49
|
externalIds?: string[];
|
|
50
|
+
/**
|
|
51
|
+
* The language to write to this person in (`nl`, `en`).
|
|
52
|
+
*
|
|
53
|
+
* On the contact and not only on the company, because a Dutch company has a German buyer often
|
|
54
|
+
* enough to matter. Absent means "no answer here" and the reader falls to the next rung — see
|
|
55
|
+
* `recipientLocale`.
|
|
56
|
+
*/
|
|
57
|
+
locale?: string;
|
|
50
58
|
origin?: ContactOrigin;
|
|
51
59
|
/** When a shadow row last saw its source. There is no refresh job; touching refreshes. */
|
|
52
60
|
syncedAt?: number;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which language to write to somebody in, and which template row that means.
|
|
3
|
+
*
|
|
4
|
+
* Three rungs, and the figure is the same one this platform uses everywhere: the most specific
|
|
5
|
+
* answer that exists wins, whole.
|
|
6
|
+
*
|
|
7
|
+
* | Rung | Says |
|
|
8
|
+
* |---|---|
|
|
9
|
+
* | the contact | this person reads German |
|
|
10
|
+
* | the company | this customer is Dutch |
|
|
11
|
+
* | the organisation | what we write by default |
|
|
12
|
+
*
|
|
13
|
+
* Without it somebody picks a language by hand on every letter, and sooner or later a Dutch
|
|
14
|
+
* final notice lands at a German customer.
|
|
15
|
+
*/
|
|
16
|
+
export interface RecipientLocaleSources {
|
|
17
|
+
/** `Contact.locale` — the person. */
|
|
18
|
+
contactLocale?: string;
|
|
19
|
+
/** `Company.locale` — the customer. */
|
|
20
|
+
companyLocale?: string;
|
|
21
|
+
/** The organisation's own language. The floor; there is nothing under it. */
|
|
22
|
+
organizationLocale?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function recipientLocale(sources: RecipientLocaleSources): string | undefined;
|
|
25
|
+
/** The minimum a template row has to carry to be picked. */
|
|
26
|
+
export interface LocalizedTemplate {
|
|
27
|
+
key: string;
|
|
28
|
+
locale?: string;
|
|
29
|
+
archived?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The template for this key in this language, or the nearest thing that exists.
|
|
33
|
+
*
|
|
34
|
+
* A reminder written only in Dutch still has to go out to a German customer — in Dutch. Returning
|
|
35
|
+
* nothing there would mean no letter at all, which is worse than the wrong language. The order is
|
|
36
|
+
* therefore: the language asked for, the organisation's own, then whatever is there.
|
|
37
|
+
*/
|
|
38
|
+
export declare function pickTemplate<T extends LocalizedTemplate>(templates: readonly T[], key: string, locale?: string, organizationLocale?: string): T | undefined;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { DocumentBlock } from '../../platform/document-blocks';
|
|
2
|
+
import { TemplateCondition } from './types';
|
|
3
|
+
/** A flat or nested plain object. Arrays resolve by index: `regels.0.naam`. */
|
|
4
|
+
export type TemplateContext = Record<string, unknown>;
|
|
5
|
+
/** Walk a dotted path. Returns `undefined` for anything the path does not reach. */
|
|
6
|
+
export declare function lookup(context: TemplateContext, path: string): unknown;
|
|
7
|
+
export interface FillResult {
|
|
8
|
+
text: string;
|
|
9
|
+
/** Paths that resolved to nothing, in the order they appeared. Deduplicated. */
|
|
10
|
+
unresolved: string[];
|
|
11
|
+
}
|
|
12
|
+
/** One string, filled in. */
|
|
13
|
+
export declare function fillText(text: string, context: TemplateContext): FillResult;
|
|
14
|
+
/** Does this block appear? A block with no condition always does. */
|
|
15
|
+
export declare function conditionHolds(condition: TemplateCondition | undefined, context: TemplateContext): boolean;
|
|
16
|
+
export interface FillDocumentResult {
|
|
17
|
+
blocks: DocumentBlock[];
|
|
18
|
+
unresolved: string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Blocks the caller computes, keyed by the `block_id` of the template block they replace.
|
|
22
|
+
*
|
|
23
|
+
* The one thing a token cannot say: *"the lines go here"*. A quote's line table and its totals are
|
|
24
|
+
* derived from the document, not written by the template author — so the author places an empty
|
|
25
|
+
* `table` with `block_id: "lines"` and the caller hands over what belongs there.
|
|
26
|
+
*
|
|
27
|
+
* Deliberately a replacement and not a repetition primitive: a loop would need an expression
|
|
28
|
+
* language to say what it loops over, and the caller already holds the data.
|
|
29
|
+
*/
|
|
30
|
+
export type DocumentSlots = Record<string, readonly DocumentBlock[]>;
|
|
31
|
+
/**
|
|
32
|
+
* A whole template, filled in: conditions applied, slots substituted, tokens replaced everywhere
|
|
33
|
+
* text lives.
|
|
34
|
+
*
|
|
35
|
+
* Every text-bearing field is walked, not just `text` — a token in a table cell or a letterhead
|
|
36
|
+
* value is the ordinary case for an invoice, and missing one would leave `{factuur.nummer}`
|
|
37
|
+
* printed on a document that went out the door.
|
|
38
|
+
*/
|
|
39
|
+
export declare function fillDocument(blocks: readonly DocumentBlock[], context: TemplateContext, conditions?: Record<string, TemplateCondition>, slots?: DocumentSlots): FillDocumentResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { DocumentBlock } from '../../platform/document-blocks';
|
|
2
|
+
/**
|
|
3
|
+
* A document template: the blocks a generated document is built from.
|
|
4
|
+
*
|
|
5
|
+
* A work order, a quote, a first reminder, a final notice. What it is **not** is the letterhead
|
|
6
|
+
* and the footer — those are the organisation's house style, drawn around every document, so a
|
|
7
|
+
* change to the address does not mean editing six templates.
|
|
8
|
+
*
|
|
9
|
+
* See `plans/DOCUMENTS-2026-09-14.md` §7.
|
|
10
|
+
*/
|
|
11
|
+
export interface DocumentTemplate {
|
|
12
|
+
id: string;
|
|
13
|
+
organizationId: string;
|
|
14
|
+
/** `quote`, `invoice`, `reminder_1`, `reminder_final`, `work_order`… Unique per language. */
|
|
15
|
+
key: string;
|
|
16
|
+
name: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Which document kind may use it. Absent = free-standing (a letter, a work order).
|
|
20
|
+
*
|
|
21
|
+
* A string and not a union: the kinds live in `apps/sales` and this app must not need to know
|
|
22
|
+
* them to store one.
|
|
23
|
+
*/
|
|
24
|
+
documentKindKey?: string;
|
|
25
|
+
/** The body. Plain document blocks — the same thing the editor and the renderer already speak. */
|
|
26
|
+
blocks: DocumentBlock[];
|
|
27
|
+
/**
|
|
28
|
+
* Which blocks only appear sometimes, keyed by `block_id`.
|
|
29
|
+
*
|
|
30
|
+
* **Beside the blocks, not wrapped around them.** A condition is part of the template, not of
|
|
31
|
+
* the document: keeping it out of the block means the editor, the renderer and the markdown
|
|
32
|
+
* export need to know nothing about it, and a template's body stays a thing any of them can
|
|
33
|
+
* open. A block with no entry here always appears.
|
|
34
|
+
*/
|
|
35
|
+
conditions?: Record<string, TemplateCondition>;
|
|
36
|
+
/**
|
|
37
|
+
* The language this template is written in.
|
|
38
|
+
*
|
|
39
|
+
* A Dutch reminder and its English version are **two rows**, tied by
|
|
40
|
+
* {@link DocumentTemplate.translationGroupId} — exactly how `KbArticle` does it, and for the
|
|
41
|
+
* same reason: a per-key text map cannot carry a translated table, and a translated list is not
|
|
42
|
+
* a string. The price is that the structure can drift between languages; the alternative is a
|
|
43
|
+
* mechanism that cannot express half the documents.
|
|
44
|
+
*/
|
|
45
|
+
locale?: string;
|
|
46
|
+
/** Ties the language versions together. Defaults to the template's own id. */
|
|
47
|
+
translationGroupId?: string;
|
|
48
|
+
isDefault?: boolean;
|
|
49
|
+
archived?: boolean;
|
|
50
|
+
createdBy: string;
|
|
51
|
+
createdAt?: number;
|
|
52
|
+
updatedAt?: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* When a block appears.
|
|
56
|
+
*
|
|
57
|
+
* Three operators and no expression language. "Show the direct-debit paragraph when the customer
|
|
58
|
+
* pays by direct debit" is the whole job; anything past that is a rule, and a rule belongs in the
|
|
59
|
+
* app that decides it.
|
|
60
|
+
*/
|
|
61
|
+
export interface TemplateCondition {
|
|
62
|
+
/** A path into the same context the tokens read: `klant.incasso`. */
|
|
63
|
+
key: string;
|
|
64
|
+
/** Show when the value equals this. */
|
|
65
|
+
equals?: unknown;
|
|
66
|
+
/** Show when the value is present (`true`) or absent (`false`). */
|
|
67
|
+
exists?: boolean;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The organisation's paper: what is drawn around every document, whichever template made it.
|
|
71
|
+
*
|
|
72
|
+
* Three fields, and the shortness is the point. The logo, the address and the VAT/KvK numbers are
|
|
73
|
+
* **not** here — they are already on the organisation (`logo`, `address`, `billing`), and a second
|
|
74
|
+
* copy is the one that goes stale. What is left is what a document needs and nothing here had.
|
|
75
|
+
*/
|
|
76
|
+
export interface OrganizationHouseStyle {
|
|
77
|
+
/** Hex, for the rules and the heading accents. */
|
|
78
|
+
accentColor?: string;
|
|
79
|
+
/** The line under every document. */
|
|
80
|
+
footerText?: string;
|
|
81
|
+
/**
|
|
82
|
+
* The account to pay into. The one identifier a letter needs that this platform did not already
|
|
83
|
+
* have — the VAT and chamber-of-commerce numbers live on `Organization.billing`, and a second
|
|
84
|
+
* copy is the one that goes stale.
|
|
85
|
+
*/
|
|
86
|
+
iban?: string;
|
|
87
|
+
}
|
|
@@ -1,35 +1,9 @@
|
|
|
1
|
-
import { KbLocale } from './types';
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
2
|
+
* The language list under its knowledge-base names.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* The labels are endonyms — a language names itself the same way whoever reads
|
|
10
|
-
* the picker. So this is a fixed map on purpose, and not the "never freeze
|
|
11
|
-
* labels at module load" case the design system warns about.
|
|
12
|
-
*
|
|
13
|
-
* In domain rather than in the kb app because both ends need the same answer:
|
|
14
|
-
* the language picker in the editor, and the writing assistant's system prompt,
|
|
15
|
-
* which has to name the language it must write in. Two copies would drift, and
|
|
16
|
-
* the way you would find out is an article coming back in the wrong language.
|
|
17
|
-
*/
|
|
18
|
-
export interface KbLocaleOption {
|
|
19
|
-
code: KbLocale;
|
|
20
|
-
/** The language's own name, for a picker. */
|
|
21
|
-
label: string;
|
|
22
|
-
/** Its English name, for telling a model which language to write in. */
|
|
23
|
-
english: string;
|
|
24
|
-
}
|
|
25
|
-
export declare const KB_LOCALES: KbLocaleOption[];
|
|
26
|
-
/** The language's own name, or the bare code for one we do not list. */
|
|
27
|
-
export declare function localeName(code: string): string;
|
|
28
|
-
/**
|
|
29
|
-
* How to name this language to a model.
|
|
30
|
-
*
|
|
31
|
-
* A bare BCP-47 tag is a weak instruction — `"nl"` inside an otherwise English
|
|
32
|
-
* prompt is not enough to stop a model answering in English. Both names plus
|
|
33
|
-
* the tag leaves nothing to infer.
|
|
4
|
+
* It moved to `platform/locales.ts` when a second caller appeared: a contact and a company now
|
|
5
|
+
* carry the language to write to them in, and `apps/crm` importing something called `KB_LOCALES`
|
|
6
|
+
* reads like a mistake. These aliases exist so the nineteen files in `apps/kb` that use the old
|
|
7
|
+
* names keep working unchanged.
|
|
34
8
|
*/
|
|
35
|
-
export
|
|
9
|
+
export { LOCALES as KB_LOCALES, type LocaleOption as KbLocaleOption, localeInstruction, localeName, } from '../../platform/locales';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DocumentBlock } from '../../platform/document-blocks';
|
|
1
2
|
/** A language a knowledge base publishes in, e.g. `nl`, `en`, `de`. */
|
|
2
3
|
export type KbLocale = string;
|
|
3
4
|
export interface KnowledgeBase {
|
|
@@ -62,7 +63,15 @@ export interface KbArticle {
|
|
|
62
63
|
knowledgeBaseId: string;
|
|
63
64
|
categoryId?: string;
|
|
64
65
|
title: string;
|
|
66
|
+
/**
|
|
67
|
+
* The article as document blocks — the source of truth. Absent on a row written before the
|
|
68
|
+
* store moved to blocks; readers fall back to parsing {@link KbArticle.body}.
|
|
69
|
+
*/
|
|
70
|
+
blocks?: DocumentBlock[];
|
|
71
|
+
/** The same article as markdown. Derived on write, never parsed back. */
|
|
65
72
|
body: string;
|
|
73
|
+
/** The same article as plain text: what the embedding and the keyword score read. */
|
|
74
|
+
text?: string;
|
|
66
75
|
status: "draft" | "published";
|
|
67
76
|
authorId?: string;
|
|
68
77
|
order: number;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OrganizationHouseStyle } from '../document-template/types';
|
|
1
2
|
import { OrganizationProfile } from './profile';
|
|
2
3
|
export interface OrganizationAddress {
|
|
3
4
|
street?: string;
|
|
@@ -40,6 +41,13 @@ export interface Organization {
|
|
|
40
41
|
* which steps apply and what to propose. See `profile.ts`.
|
|
41
42
|
*/
|
|
42
43
|
profile?: OrganizationProfile;
|
|
44
|
+
/**
|
|
45
|
+
* What is drawn around every generated document — accent, footer, KvK/VAT/IBAN.
|
|
46
|
+
*
|
|
47
|
+
* The logo and the address are not in here: they are fields of their own on this record, and a
|
|
48
|
+
* document reads them from there. See `OrganizationHouseStyle`.
|
|
49
|
+
*/
|
|
50
|
+
houseStyle?: OrganizationHouseStyle;
|
|
43
51
|
/** Small company logo stored inline as a base64 data-URI. */
|
|
44
52
|
logo?: string;
|
|
45
53
|
/**
|