ngx-t-forms-types 0.0.30 → 0.0.33
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/interfaces/Form/formSubmissionHandleInterface.d.ts +6 -0
- package/dist/interfaces/FormBuilder/DefaultEelement.js +76 -0
- package/dist/interfaces/FormBuilder/DefaultInputConfigInterface.d.ts +13 -0
- package/dist/interfaces/FormBuilder/FormInputKeys.d.ts +6 -0
- package/dist/interfaces/FormBuilder/FormInputKeys.js +6 -0
- package/dist/interfaces/FormBuilder/inputConfig/ElementEditConfig.js +188 -3
- package/dist/interfaces/formInput/APIDataFetchingConfigurationInterface.d.ts +34 -0
- package/dist/interfaces/formInput/BasicFormInputInterface.d.ts +1 -0
- package/dist/interfaces/formInput/BasicFormInputInterface.js +1 -0
- package/dist/interfaces/formInput/IMscoaAccount.d.ts +19 -3
- package/dist/interfaces/formInput/ISelectInputInterface.d.ts +12 -0
- package/dist/interfaces/formInput/MultipleInterface.d.ts +10 -0
- package/dist/interfaces/formInput/WorkflowDocumentPicker.d.ts +2 -0
- package/dist/schemas/FormInputSchema.js +41 -2
- package/dist/schemas/MatOptionsSchema.js +7 -0
- package/dist/schemas/MscoaConfigSchema.js +1 -0
- package/dist/schemas/index.d.ts +2 -1
- package/dist/schemas/index.js +2 -1
- package/dist/skillet/authoring-rules.d.ts +41 -0
- package/dist/skillet/authoring-rules.js +61 -0
- package/dist/skillet/extra-members.d.ts +143 -0
- package/dist/skillet/extra-members.js +79 -0
- package/dist/skillet/form.d.ts +170 -0
- package/dist/skillet/form.js +139 -0
- package/dist/skillet/index.d.ts +43 -0
- package/dist/skillet/index.js +46 -0
- package/dist/skillet/input-members.d.ts +375 -0
- package/dist/skillet/input-members.js +186 -0
- package/dist/skillet/internal/coupling.d.ts +63 -0
- package/dist/skillet/internal/coupling.js +69 -0
- package/dist/skillet/internal/editor-guidance.d.ts +64 -0
- package/dist/skillet/internal/editor-guidance.js +291 -0
- package/dist/skillet/members/calculated-field.d.ts +155 -0
- package/dist/skillet/members/calculated-field.js +105 -0
- package/dist/skillet/members/conditional.d.ts +69 -0
- package/dist/skillet/members/conditional.js +16 -0
- package/dist/skillet/members/document-picker.d.ts +182 -0
- package/dist/skillet/members/document-picker.js +111 -0
- package/dist/skillet/members/mat-options.d.ts +187 -0
- package/dist/skillet/members/mat-options.js +92 -0
- package/dist/skillet/members/mscoa.d.ts +182 -0
- package/dist/skillet/members/mscoa.js +129 -0
- package/dist/skillet/members/pagination.d.ts +59 -0
- package/dist/skillet/members/pagination.js +16 -0
- package/dist/skillet/members/table.d.ts +178 -0
- package/dist/skillet/members/table.js +105 -0
- package/dist/skillet/members/validators.d.ts +197 -0
- package/dist/skillet/members/validators.js +74 -0
- package/dist/skillet/members/value.d.ts +154 -0
- package/dist/skillet/members/value.js +97 -0
- package/dist/skillet/shared-types.d.ts +57 -0
- package/dist/skillet/shared-types.js +69 -0
- package/dist/skillet/tests/house-rules.spec.d.ts +1 -0
- package/dist/skillet/tests/house-rules.spec.js +162 -0
- package/dist/skillet/tests/skillet-coupling.spec.d.ts +1 -0
- package/dist/skillet/tests/skillet-coupling.spec.js +191 -0
- package/dist/skillet/tests/variant-assembly.spec.d.ts +1 -0
- package/dist/skillet/tests/variant-assembly.spec.js +643 -0
- package/dist/skillet/variants.d.ts +138 -0
- package/dist/skillet/variants.js +536 -0
- package/dist/skillet/workflow-context.d.ts +95 -0
- package/dist/skillet/workflow-context.js +128 -0
- package/package.json +78 -60
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { s } from '@hashbrownai/core';
|
|
2
|
+
import type { AccountingBasis, DualCashExclusionMatchMode } from '../../interfaces/formInput/IMscoaAccount.js';
|
|
3
|
+
import { AssertNever } from '../internal/coupling.js';
|
|
4
|
+
/**
|
|
5
|
+
* The `mscoaConfig` member: how a municipal SCOA account picker is set up.
|
|
6
|
+
*
|
|
7
|
+
* One `mscoaSelection` element asks a user to pick accounts out of the chart of
|
|
8
|
+
* accounts, segment by segment. This object says which segments it asks for,
|
|
9
|
+
* how each account is labelled back to them, which accounting basis is being
|
|
10
|
+
* captured, and — for a dual basis — when the cash side is not actually
|
|
11
|
+
* required.
|
|
12
|
+
*
|
|
13
|
+
* ## Why this produces a draft
|
|
14
|
+
*
|
|
15
|
+
* `IScoaInputConfig` requires `inputs: ScoaInnerInput[]`, and a
|
|
16
|
+
* `ScoaInnerInput` is a whole `FormColumnInputs` carrying its own `id` plus a
|
|
17
|
+
* `linkedSegmentId` pointing at a segment row that does not exist until the
|
|
18
|
+
* segments themselves have been assigned ids. Asking a model for that is asking
|
|
19
|
+
* it to invent two sets of identifiers and then cross-reference them, which it
|
|
20
|
+
* will do, plausibly and wrongly. So `inputs` is not offered here at all; the
|
|
21
|
+
* application supplies `inputs: []` when it completes the draft, and the
|
|
22
|
+
* builder's own "additional inputs" editor is where those are authored.
|
|
23
|
+
*
|
|
24
|
+
* The same reasoning retires `id` from segments and from exclusion rules. Both
|
|
25
|
+
* are row keys stamped by the builder's record editors (`uuidv4()` at the point
|
|
26
|
+
* of save), never typed by a human — which is exactly why `id` and `sectionId`
|
|
27
|
+
* sit in `SYSTEM_OWNED_INPUT_MEMBERS` one level up. A model-authored id is at
|
|
28
|
+
* best noise and at worst a collision with a row that already exists.
|
|
29
|
+
*
|
|
30
|
+
* ## Draft-completion obligations
|
|
31
|
+
*
|
|
32
|
+
* Before this output can be handed to `validateMscoaInputConfig`, the caller
|
|
33
|
+
* must:
|
|
34
|
+
*
|
|
35
|
+
* 1. add `inputs: []` — required by `MscoaInputConfigSchema` and deliberately
|
|
36
|
+
* absent here;
|
|
37
|
+
* 2. stamp an `id` on every entry of `segments`, `cashSegments` and
|
|
38
|
+
* `dualCashExclusion.rules`;
|
|
39
|
+
* 3. strip the nulls standing in for absent optional members — Joi rejects a
|
|
40
|
+
* `null` where it declared an optional string.
|
|
41
|
+
*
|
|
42
|
+
* None of the three is optional, and none of them is knowable from a prompt.
|
|
43
|
+
*
|
|
44
|
+
* ## Deliberate narrowing
|
|
45
|
+
*
|
|
46
|
+
* `IScoaInputConfig` also declares `label` and `hint`. `MscoaInputConfigSchema`
|
|
47
|
+
* does not, and a Joi object rejects unknown keys by default — so emitting
|
|
48
|
+
* either would fail the very validation this schema exists to pass. The visible
|
|
49
|
+
* label of the control is the input's own `label` member, one level up. Joi
|
|
50
|
+
* wins, and they are omitted.
|
|
51
|
+
*
|
|
52
|
+
* `accountValueLabel` is narrowed from `string` to the pool the settings panel
|
|
53
|
+
* offers, derived from {@link MSCOA_ACCOUNT_VALUE_LABEL_OPTIONS} at evaluation
|
|
54
|
+
* rather than retyped — the same treatment the enum-backed nodes get, for the
|
|
55
|
+
* same reason: a hand-copied list is a list that drifts.
|
|
56
|
+
*
|
|
57
|
+
* `segment` is asked for on every segment row, although the interface types it
|
|
58
|
+
* `string | undefined`. The builder's segment form declares it
|
|
59
|
+
* `Validators.required`, so a row without one cannot be saved by hand either.
|
|
60
|
+
* Narrower than the interface is the permitted direction; wider is not.
|
|
61
|
+
*/
|
|
62
|
+
export interface MscoaSegmentDraft {
|
|
63
|
+
segment: string;
|
|
64
|
+
customSegment: string | null;
|
|
65
|
+
label: string;
|
|
66
|
+
readOnly: boolean;
|
|
67
|
+
singleSelect: boolean;
|
|
68
|
+
additionalAccounts: string[];
|
|
69
|
+
inheritValueFromAccrual: boolean | null;
|
|
70
|
+
vatSelectonActive: boolean | null;
|
|
71
|
+
segmentExtension: boolean;
|
|
72
|
+
}
|
|
73
|
+
export interface MscoaDualCashExclusionRuleDraft {
|
|
74
|
+
pattern: string;
|
|
75
|
+
flags: string | null;
|
|
76
|
+
matchField: string | null;
|
|
77
|
+
segment: string | null;
|
|
78
|
+
description: string | null;
|
|
79
|
+
}
|
|
80
|
+
export interface MscoaDualCashExclusionDraft {
|
|
81
|
+
rules: MscoaDualCashExclusionRuleDraft[] | null;
|
|
82
|
+
matchMode: DualCashExclusionMatchMode | null;
|
|
83
|
+
}
|
|
84
|
+
export interface MscoaConfigDraft {
|
|
85
|
+
segments: MscoaSegmentDraft[];
|
|
86
|
+
cashSegments: MscoaSegmentDraft[] | null;
|
|
87
|
+
accountValueLabel: string;
|
|
88
|
+
accountingBasis: AccountingBasis | null;
|
|
89
|
+
extensionAccountsForSegments: string[];
|
|
90
|
+
showAllSegments: boolean;
|
|
91
|
+
dualCashExclusion: MscoaDualCashExclusionDraft | null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* `DualCashExclusionMatchMode` is a string-union type with no runtime object to
|
|
95
|
+
* read, so its members are restated under {@link exhaustive} rather than
|
|
96
|
+
* derived — drop one and this stops compiling.
|
|
97
|
+
*/
|
|
98
|
+
export declare const DUAL_CASH_MATCH_MODE_VALUES: readonly ["any", "all"];
|
|
99
|
+
/**
|
|
100
|
+
* The `mscoaConfig` schema.
|
|
101
|
+
*
|
|
102
|
+
* A plain const, not a factory: nothing in here is chosen from candidates the
|
|
103
|
+
* caller holds. The one member that draws on a fixed pool — `accountValueLabel`
|
|
104
|
+
* — reads that pool out of this package at evaluation.
|
|
105
|
+
*
|
|
106
|
+
* ## Where the editor guidance is attached, and where it is not
|
|
107
|
+
*
|
|
108
|
+
* There is exactly ONE editor row for this whole object, at path
|
|
109
|
+
* `['mscoaConfig']`: a composite `MscoaConfig` editor that replaced eight
|
|
110
|
+
* separate rows (accountingBasis, accountValueLabel, showAllSegments, segments,
|
|
111
|
+
* cashSegments, the two dual-cash members and extensionAccountsForSegments).
|
|
112
|
+
* So {@link describe} is called once, on the object the row actually edits, and
|
|
113
|
+
* the members below carry authored descriptions only.
|
|
114
|
+
*
|
|
115
|
+
* That is a deliberate choice rather than an oversight. Calling
|
|
116
|
+
* `describe(base, 'mscoaConfig')` on each member compiles and would append the
|
|
117
|
+
* same forty-word hint — ending in "Opens a guided setup", which is advice for
|
|
118
|
+
* someone looking at a settings panel — to all seven of them. Layering it once,
|
|
119
|
+
* on the composite, says the same thing in one place.
|
|
120
|
+
*/
|
|
121
|
+
export declare const mscoaConfigSchema: s.ObjectType<{
|
|
122
|
+
accountingBasis: import("@hashbrownai/core/src/schema/base").SchemaForUnion<AccountingBasis | null>;
|
|
123
|
+
segments: s.ArrayType<s.ObjectType<{
|
|
124
|
+
segment: s.StringType;
|
|
125
|
+
customSegment: import("@hashbrownai/core/src/schema/base").SchemaForUnion<string | null>;
|
|
126
|
+
label: s.StringType;
|
|
127
|
+
readOnly: s.BooleanType;
|
|
128
|
+
singleSelect: s.BooleanType;
|
|
129
|
+
additionalAccounts: s.ArrayType<s.StringType>;
|
|
130
|
+
inheritValueFromAccrual: import("@hashbrownai/core/src/schema/base").SchemaForUnion<boolean | null>;
|
|
131
|
+
vatSelectonActive: import("@hashbrownai/core/src/schema/base").SchemaForUnion<boolean | null>;
|
|
132
|
+
segmentExtension: s.BooleanType;
|
|
133
|
+
}>>;
|
|
134
|
+
cashSegments: import("@hashbrownai/core/src/schema/base").SchemaForUnion<{
|
|
135
|
+
segment: string;
|
|
136
|
+
customSegment: string | null;
|
|
137
|
+
label: string;
|
|
138
|
+
readOnly: boolean;
|
|
139
|
+
singleSelect: boolean;
|
|
140
|
+
additionalAccounts: string[];
|
|
141
|
+
inheritValueFromAccrual: boolean | null;
|
|
142
|
+
vatSelectonActive: boolean | null;
|
|
143
|
+
segmentExtension: boolean;
|
|
144
|
+
}[] | null>;
|
|
145
|
+
accountValueLabel: s.EnumType<string[]>;
|
|
146
|
+
showAllSegments: s.BooleanType;
|
|
147
|
+
extensionAccountsForSegments: s.ArrayType<s.StringType>;
|
|
148
|
+
dualCashExclusion: import("@hashbrownai/core/src/schema/base").SchemaForUnion<{
|
|
149
|
+
rules: {
|
|
150
|
+
pattern: string;
|
|
151
|
+
flags: string | null;
|
|
152
|
+
matchField: string | null;
|
|
153
|
+
segment: string | null;
|
|
154
|
+
description: string | null;
|
|
155
|
+
}[] | null;
|
|
156
|
+
matchMode: "any" | "all" | null;
|
|
157
|
+
} | null>;
|
|
158
|
+
}>;
|
|
159
|
+
/**
|
|
160
|
+
* The schema produces the draft it declares.
|
|
161
|
+
*
|
|
162
|
+
* Same shape of check as `_NoDraftDrift` in `input-members.ts`, and there for
|
|
163
|
+
* the same reason: the expansion from draft to `IScoaInputConfig` belongs to
|
|
164
|
+
* the caller, but the draft itself stays pinned to the node that generates it.
|
|
165
|
+
* Loosen a member below and this fails here rather than three layers away.
|
|
166
|
+
*/
|
|
167
|
+
export type _NoMscoaDraftDrift = AssertNever<[
|
|
168
|
+
s.Infer<typeof mscoaConfigSchema>
|
|
169
|
+
] extends [MscoaConfigDraft] ? never : 'mscoaConfig'>;
|
|
170
|
+
/**
|
|
171
|
+
* Members of a generated `mscoaConfig` the application must supply before Joi
|
|
172
|
+
* will accept it.
|
|
173
|
+
*
|
|
174
|
+
* `inputs` is the notable one: `MscoaInputConfigSchema` REQUIRES it, and the
|
|
175
|
+
* model is never asked for it, so a draft that is not expanded fails
|
|
176
|
+
* validation rather than merely losing a detail. An empty array satisfies it —
|
|
177
|
+
* the segments' inner inputs are generated from the segments themselves.
|
|
178
|
+
*
|
|
179
|
+
* `dualCashExclusion.rules[].id` is deliberately absent from this list: Joi
|
|
180
|
+
* marks that one optional, so leaving it unset is valid.
|
|
181
|
+
*/
|
|
182
|
+
export declare const MSCOA_DRAFT_COMPLETION: readonly ["mscoaConfig.inputs (application supplies; an empty array is valid)", "mscoaConfig.segments[].id", "mscoaConfig.cashSegments[].id"];
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { s } from '@hashbrownai/core';
|
|
2
|
+
import { MSCOA_ACCOUNT_VALUE_LABEL_OPTIONS } from '../../interfaces/FormBuilder/inputConfig/ElementEditConfig.js';
|
|
3
|
+
import { exhaustive, optional } from '../internal/coupling.js';
|
|
4
|
+
import { describe, describeIn } from '../internal/editor-guidance.js';
|
|
5
|
+
import { accountingBasisSchema } from '../shared-types.js';
|
|
6
|
+
// --- the account-label pool -------------------------------------------------
|
|
7
|
+
/**
|
|
8
|
+
* The fields an account may be displayed by, read off the option pool the
|
|
9
|
+
* unified MSCOA setup editor renders for `accountValueLabel`.
|
|
10
|
+
*/
|
|
11
|
+
const ACCOUNT_VALUE_LABEL_ENTRIES = MSCOA_ACCOUNT_VALUE_LABEL_OPTIONS.map((option) => option.value);
|
|
12
|
+
/** Compares a label to its value ignoring case, spacing and punctuation. */
|
|
13
|
+
const flatten = (text) => text.replace(/[^a-z0-9]/gi, '').toLowerCase();
|
|
14
|
+
/**
|
|
15
|
+
* The handful of entries whose label says something their value does not.
|
|
16
|
+
*
|
|
17
|
+
* `internal/editor-guidance` rejects editor `options` as a description source
|
|
18
|
+
* on the evidence that the labels merely restate the values. That holds for
|
|
19
|
+
* most of this pool too — `SCOAAccount` is labelled "SCOA Account", `SA34B` is
|
|
20
|
+
* labelled "SA34B" — and those are dropped here rather than repeated. What is
|
|
21
|
+
* left is the residue that genuinely disambiguates: `AccountNumber` is the
|
|
22
|
+
* FULL account number and `AccountNumberShortened` is the one an administrator
|
|
23
|
+
* calls "the account number", which is not a distinction a model can recover
|
|
24
|
+
* from the two identifiers alone.
|
|
25
|
+
*/
|
|
26
|
+
const ACCOUNT_VALUE_LABEL_GLOSS = MSCOA_ACCOUNT_VALUE_LABEL_OPTIONS.filter((option) => flatten(option.label) !== flatten(option.value))
|
|
27
|
+
.map((option) => `${option.value} is "${option.label}"`)
|
|
28
|
+
.join('; ');
|
|
29
|
+
// --- schema -----------------------------------------------------------------
|
|
30
|
+
/**
|
|
31
|
+
* `DualCashExclusionMatchMode` is a string-union type with no runtime object to
|
|
32
|
+
* read, so its members are restated under {@link exhaustive} rather than
|
|
33
|
+
* derived — drop one and this stops compiling.
|
|
34
|
+
*/
|
|
35
|
+
export const DUAL_CASH_MATCH_MODE_VALUES = exhaustive()(['any', 'all']);
|
|
36
|
+
/**
|
|
37
|
+
* One segment row: which slice of the chart of accounts the user picks from,
|
|
38
|
+
* and how that pick behaves.
|
|
39
|
+
*
|
|
40
|
+
* Shared by `segments` and `cashSegments` rather than split into two nodes.
|
|
41
|
+
* Two members are basis-specific (`inheritValueFromAccrual` is meaningless off
|
|
42
|
+
* the cash side, `vatSelectonActive` off the ITEM segment), and Skillet cannot
|
|
43
|
+
* express conditional applicability structurally — so, as with `min` and `max`
|
|
44
|
+
* on the input members, the condition is stated in the description where it can
|
|
45
|
+
* still stop a model setting a flag that cannot mean anything.
|
|
46
|
+
*/
|
|
47
|
+
const segmentSchema = s.object('One segment of the chart of accounts the user picks an account from.', {
|
|
48
|
+
segment: s.string('The segment key exactly as the chart of accounts spells it, upper case — ITEM, FUNCTION, FUND, PROJECT, REGION and so on. The real list comes from the loaded account tree, so use the key the municipality actually publishes rather than inventing a plausible one. On an extension row this names the segment being extended.'),
|
|
49
|
+
customSegment: optional(s.string('The upper-cased key an extension row stores its accounts under, derived from the label (a row labelled "Retention" is keyed RETENTION). Leave null on an ordinary segment, which is keyed by its segment key instead.')),
|
|
50
|
+
label: s.string('The heading shown above this row in the account chart, in title case. Name what the user is choosing, not the segment code.'),
|
|
51
|
+
readOnly: s.boolean('Whether the row is displayed but cannot be changed by the user. Set this on a segment whose account is fixed by policy or copied from elsewhere.'),
|
|
52
|
+
singleSelect: s.boolean('True when the row captures one account only. False adds a second account to every row in this table, so the user picks a debit and a credit pair. ITEM is the only segment that carries two legs; every other segment holds the same account on both sides. So set it false on ITEM alone, and only when the form genuinely captures both sides.'),
|
|
53
|
+
additionalAccounts: s.array('Extra account columns shown on this row, named exactly as they appear in extensionAccountsForSegments on this same configuration. A name that is not in that list renders nothing. Leave empty when the row needs no extra columns.', s.string('An extension-account name declared in extensionAccountsForSegments.')),
|
|
54
|
+
inheritValueFromAccrual: optional(s.boolean('Cash rows only: copies the account selected on the matching accrual row instead of asking again. Leave null on an accrual segment.')),
|
|
55
|
+
// The member really is spelled `vatSelectonActive` in the stored shape. It
|
|
56
|
+
// is not corrected here: renaming it would silently drop the flag on every
|
|
57
|
+
// form already persisted with it.
|
|
58
|
+
vatSelectonActive: optional(s.boolean('Adds the VAT-status column to the account chart. It applies to the ITEM segment, which is the only one carrying a VAT treatment; leave null elsewhere.')),
|
|
59
|
+
segmentExtension: s.boolean('True for an extra level authored under a segment rather than a segment of the published chart. An extension row is keyed by customSegment and may reuse a segment key another row already claims; an ordinary row may not.'),
|
|
60
|
+
});
|
|
61
|
+
/**
|
|
62
|
+
* One cash-exclusion rule.
|
|
63
|
+
*
|
|
64
|
+
* These are the only members here with per-property guidance to draw on: the
|
|
65
|
+
* `mscoaConfig` row opens a composite editor, and its `secondaryElementEditorConfig`
|
|
66
|
+
* carries a hand-written hint for each of `pattern`, `flags`, `matchField`,
|
|
67
|
+
* `segment` and `description`. {@link describeIn} reads them from that secondary editor
|
|
68
|
+
* rather than from the top-level index — see `internal/editor-guidance` for why
|
|
69
|
+
* those rows are indexed under the row that opens them instead of being
|
|
70
|
+
* prefixed with its path.
|
|
71
|
+
*/
|
|
72
|
+
const dualCashExclusionRuleSchema = s.object('One condition under which the cash side is not required.', {
|
|
73
|
+
pattern: s.string(describeIn('A JavaScript regular expression, written without the surrounding slashes, tested against each selected accrual account.', ['mscoaConfig'], 'pattern')),
|
|
74
|
+
flags: optional(s.string(describeIn('Regular-expression flags for the pattern above. Leave null for an exact-case match.', ['mscoaConfig'], 'flags'))),
|
|
75
|
+
matchField: optional(s.enumeration(describeIn('Overrides, for THIS rule only, the labelled field the pattern is tested against; accountValueLabel above is the default. Set it when the condition keys off something the control does not display — a description field, say, on a control that shows account numbers. Leave null otherwise, which is the usual case.', ['mscoaConfig'], 'matchField'), ACCOUNT_VALUE_LABEL_ENTRIES)),
|
|
76
|
+
segment: optional(s.string(describeIn('Restricts the test to the accounts chosen for one accrual segment, named by its upper-cased segment key. It must be a segment this configuration actually declares — a scope nothing declares can never match. Leave null to test every accrual segment.', ['mscoaConfig'], 'segment'))),
|
|
77
|
+
description: optional(s.string(describeIn('A note for administrators explaining the business intent of this condition. It is never shown to the person filling in the form.', ['mscoaConfig'], 'description'))),
|
|
78
|
+
});
|
|
79
|
+
/**
|
|
80
|
+
* The `mscoaConfig` schema.
|
|
81
|
+
*
|
|
82
|
+
* A plain const, not a factory: nothing in here is chosen from candidates the
|
|
83
|
+
* caller holds. The one member that draws on a fixed pool — `accountValueLabel`
|
|
84
|
+
* — reads that pool out of this package at evaluation.
|
|
85
|
+
*
|
|
86
|
+
* ## Where the editor guidance is attached, and where it is not
|
|
87
|
+
*
|
|
88
|
+
* There is exactly ONE editor row for this whole object, at path
|
|
89
|
+
* `['mscoaConfig']`: a composite `MscoaConfig` editor that replaced eight
|
|
90
|
+
* separate rows (accountingBasis, accountValueLabel, showAllSegments, segments,
|
|
91
|
+
* cashSegments, the two dual-cash members and extensionAccountsForSegments).
|
|
92
|
+
* So {@link describe} is called once, on the object the row actually edits, and
|
|
93
|
+
* the members below carry authored descriptions only.
|
|
94
|
+
*
|
|
95
|
+
* That is a deliberate choice rather than an oversight. Calling
|
|
96
|
+
* `describe(base, 'mscoaConfig')` on each member compiles and would append the
|
|
97
|
+
* same forty-word hint — ending in "Opens a guided setup", which is advice for
|
|
98
|
+
* someone looking at a settings panel — to all seven of them. Layering it once,
|
|
99
|
+
* on the composite, says the same thing in one place.
|
|
100
|
+
*/
|
|
101
|
+
export const mscoaConfigSchema = s.object(describe('How this control asks the user to select a municipal SCOA account.', 'mscoaConfig'), {
|
|
102
|
+
accountingBasis: optional(accountingBasisSchema),
|
|
103
|
+
segments: s.array('The accrual segments the user picks an account for, in the order they should appear. At least one is required, and this is the list the control is built from — an empty one gives the user nothing to choose. A budget capture usually asks for all seven segments of the chart.', segmentSchema, { minItems: 1 }),
|
|
104
|
+
cashSegments: optional(s.array('The segments captured on the cash side. Only meaningful when the accounting basis is cash or dual; leave null for an accrual-only input. A cash row that mirrors an accrual one should normally set inheritValueFromAccrual rather than ask the user twice.', segmentSchema)),
|
|
105
|
+
accountValueLabel: s.enumeration(`Which field of the account identifies it to the user, everywhere this control shows one. Choose the field an administrator would read back in the chart of accounts. ${ACCOUNT_VALUE_LABEL_GLOSS}.`, ACCOUNT_VALUE_LABEL_ENTRIES),
|
|
106
|
+
showAllSegments: s.boolean('True reflects every segment of the loaded account tree, read-only, instead of only the ones listed here — the application reconciles the list against the tree, so segments authored here are kept but not exclusive. False shows exactly the segments listed and nothing else, which is the usual choice.'),
|
|
107
|
+
extensionAccountsForSegments: s.array('Names of extra account columns this control offers on top of the ordinary account, each becoming a column on the chart. A segment opts into one by naming it in its own additionalAccounts. Leave empty when the control captures ordinary accounts only.', s.string('An extension-account name, such as a counter-account this form also captures.')),
|
|
108
|
+
dualCashExclusion: optional(s.object('Dual basis only: when the cash side is NOT required, despite the basis asking for both. Leave null to require both sides always, which is the default behaviour and the right answer unless the form has a stated exception.', {
|
|
109
|
+
rules: optional(s.array('The conditions tested against the accounts the user selected on the accrual side. A rule whose pattern does not compile never matches, so it can never suppress the cash side by accident.', dualCashExclusionRuleSchema)),
|
|
110
|
+
matchMode: optional(s.enumeration("How the rules combine into one verdict: 'any' suppresses cash as soon as one rule matches, 'all' only when every rule does. Absent behaves as 'any'.", [...DUAL_CASH_MATCH_MODE_VALUES])),
|
|
111
|
+
})),
|
|
112
|
+
});
|
|
113
|
+
/**
|
|
114
|
+
* Members of a generated `mscoaConfig` the application must supply before Joi
|
|
115
|
+
* will accept it.
|
|
116
|
+
*
|
|
117
|
+
* `inputs` is the notable one: `MscoaInputConfigSchema` REQUIRES it, and the
|
|
118
|
+
* model is never asked for it, so a draft that is not expanded fails
|
|
119
|
+
* validation rather than merely losing a detail. An empty array satisfies it —
|
|
120
|
+
* the segments' inner inputs are generated from the segments themselves.
|
|
121
|
+
*
|
|
122
|
+
* `dualCashExclusion.rules[].id` is deliberately absent from this list: Joi
|
|
123
|
+
* marks that one optional, so leaving it unset is valid.
|
|
124
|
+
*/
|
|
125
|
+
export const MSCOA_DRAFT_COMPLETION = [
|
|
126
|
+
'mscoaConfig.inputs (application supplies; an empty array is valid)',
|
|
127
|
+
'mscoaConfig.segments[].id',
|
|
128
|
+
'mscoaConfig.cashSegments[].id',
|
|
129
|
+
];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { s } from '@hashbrownai/core';
|
|
2
|
+
/**
|
|
3
|
+
* The `paginationSelectionConfig` member: how a paged selection table lists and
|
|
4
|
+
* identifies its records.
|
|
5
|
+
*
|
|
6
|
+
* ## This member has no interface behind it
|
|
7
|
+
*
|
|
8
|
+
* Unusually, nothing in this package declares `paginationSelectionConfig` on a
|
|
9
|
+
* form input. It exists as a key in `AllFormInputPrimaryKeys`, and as
|
|
10
|
+
* `paginationSelectionConfigSchema` in `schemas/FormInputSchema.ts`, but no
|
|
11
|
+
* member of the `FormColumnInputs` intersection has that name — which is why it
|
|
12
|
+
* falls under `UnbackedInputMember` in `input-members.ts`.
|
|
13
|
+
*
|
|
14
|
+
* That matters here rather than being trivia: the `_NoValueDrift` assertion
|
|
15
|
+
* resolves an unbacked key to `unknown`, and everything is assignable to
|
|
16
|
+
* `unknown`, so the compiler cannot check this node against anything. The Joi
|
|
17
|
+
* schema is therefore the ONLY thing holding it, and it is followed literally.
|
|
18
|
+
* A description that drifts from `paginationSelectionConfigSchema` will not
|
|
19
|
+
* fail the build the way the backed members do; it will fail validation at
|
|
20
|
+
* runtime.
|
|
21
|
+
*
|
|
22
|
+
* ## Why this is a draft
|
|
23
|
+
*
|
|
24
|
+
* Each column carries a required `id`, a builder-assigned row key, handled as
|
|
25
|
+
* every other assigned identifier is: omitted here and stamped on expansion.
|
|
26
|
+
*
|
|
27
|
+
* `primaryIdentifierKey` is omitted outright. It is an array of key-tree nodes
|
|
28
|
+
* built by the settings panel from a real sample document, and the editor row
|
|
29
|
+
* says an unset value means `_id` is used — so an absent one is both authorable
|
|
30
|
+
* and correct, whereas a model inventing tree nodes is neither.
|
|
31
|
+
*/
|
|
32
|
+
export interface PaginationColumnDraft {
|
|
33
|
+
key: string;
|
|
34
|
+
label: string;
|
|
35
|
+
}
|
|
36
|
+
export interface PaginationSelectionConfigDraft {
|
|
37
|
+
useLocalPagination: boolean | null;
|
|
38
|
+
columns: PaginationColumnDraft[];
|
|
39
|
+
}
|
|
40
|
+
export declare const paginationSelectionConfigDraftSchema: s.ObjectType<{
|
|
41
|
+
useLocalPagination: import("@hashbrownai/core/src/schema/base").SchemaForUnion<boolean | null>;
|
|
42
|
+
columns: s.ArrayType<s.ObjectType<{
|
|
43
|
+
key: s.StringType;
|
|
44
|
+
label: s.StringType;
|
|
45
|
+
}>>;
|
|
46
|
+
}>;
|
|
47
|
+
/**
|
|
48
|
+
* The node produces the draft it declares.
|
|
49
|
+
*
|
|
50
|
+
* This is the only compile-time check available for this member — see the note
|
|
51
|
+
* above about it having no interface to be checked against.
|
|
52
|
+
*/
|
|
53
|
+
export type _NoPaginationDraftDrift = [
|
|
54
|
+
s.Infer<typeof paginationSelectionConfigDraftSchema>
|
|
55
|
+
] extends [PaginationSelectionConfigDraft] ? never : [
|
|
56
|
+
'paginationSelectionConfigDraftSchema drifted from PaginationSelectionConfigDraft'
|
|
57
|
+
];
|
|
58
|
+
/** Members of a generated config the application must supply. */
|
|
59
|
+
export declare const PAGINATION_DRAFT_COMPLETION: readonly ["paginationSelectionConfig.columns[].id"];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { s } from '@hashbrownai/core';
|
|
2
|
+
import { AllFormInputPrimaryKeys } from '../../interfaces/FormBuilder/FormInputKeys.js';
|
|
3
|
+
import { optional } from '../internal/coupling.js';
|
|
4
|
+
import { describe } from '../internal/editor-guidance.js';
|
|
5
|
+
const paginationColumnSchema = s.object('One column shown in the selection table.', {
|
|
6
|
+
key: s.string('The property on each record whose value fills this column.'),
|
|
7
|
+
label: s.string('The column heading, in sentence case. Name what the column shows.'),
|
|
8
|
+
});
|
|
9
|
+
export const paginationSelectionConfigDraftSchema = s.object('How the selection table pages through records and which of their properties it shows.', {
|
|
10
|
+
useLocalPagination: optional(s.boolean(describe('Whether paging is done in the browser over an already-fetched list.', AllFormInputPrimaryKeys.PaginationSelectionConfig, 'useLocalPagination'))),
|
|
11
|
+
columns: s.array('The columns shown in the table, in order. Pick the few properties a user needs to tell one record from another.', paginationColumnSchema),
|
|
12
|
+
});
|
|
13
|
+
/** Members of a generated config the application must supply. */
|
|
14
|
+
export const PAGINATION_DRAFT_COMPLETION = [
|
|
15
|
+
'paginationSelectionConfig.columns[].id',
|
|
16
|
+
];
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { s } from '@hashbrownai/core';
|
|
2
|
+
import type { IMatrixInput } from '../../interfaces/formInput/MatrixInputInterface.js';
|
|
3
|
+
import type { TableColumnConfigInterface } from '../../interfaces/formInput/TableConfigurationsInterface.js';
|
|
4
|
+
import { AssertNever } from '../internal/coupling.js';
|
|
5
|
+
import { calculatedFieldRulesDraftSchema } from './calculated-field.js';
|
|
6
|
+
/**
|
|
7
|
+
* The two members that configure a table-shaped control: where a matrix reads
|
|
8
|
+
* its rows from, and how a table's columns are laid out.
|
|
9
|
+
*
|
|
10
|
+
* `matrixTableConfig` is a single string and models directly. `tableConfig`
|
|
11
|
+
* does not, and the reason is worth stating up front: its `columnsConfig` is a
|
|
12
|
+
* record keyed by column name, and Skillet has no record node. That one fact
|
|
13
|
+
* decides the shape of everything below.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The `matrixTableConfig` member: which data set a matrix control renders.
|
|
17
|
+
*
|
|
18
|
+
* `{ dataSource: string }` — the whole type. Nothing here is builder-assigned
|
|
19
|
+
* and nothing is unexpressible, so this is one of the few nested configs that
|
|
20
|
+
* models as itself, with no draft and no narrowing.
|
|
21
|
+
*/
|
|
22
|
+
export declare const matrixTableConfigSchema: s.ObjectType<{
|
|
23
|
+
dataSource: s.StringType;
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* The four values `TableColumnConfigInterface['type']` admits.
|
|
27
|
+
*
|
|
28
|
+
* A string union, not an enum, so there is no runtime object to derive from and
|
|
29
|
+
* the members have to be restated. {@link exhaustive} makes restating them
|
|
30
|
+
* safe: drop one, or add a fifth to the union, and the tuple collapses to
|
|
31
|
+
* `never` and this file stops compiling.
|
|
32
|
+
*
|
|
33
|
+
* `'caluculated'` is spelled that way in the source interface. It is NOT
|
|
34
|
+
* corrected here. It is the value stored on every table already in the
|
|
35
|
+
* database, so the misspelling is the contract; "fixing" it would emit a
|
|
36
|
+
* literal nothing reads and quietly break every calculated column generated
|
|
37
|
+
* from this point on.
|
|
38
|
+
*/
|
|
39
|
+
export declare const TABLE_COLUMN_TYPE_VALUES: readonly ["caluculated", "property", "checkBox", "primaryKey"];
|
|
40
|
+
/**
|
|
41
|
+
* One column, as the model authors it.
|
|
42
|
+
*
|
|
43
|
+
* Identical to `TableColumnConfigInterface` but for `key`, which the stored
|
|
44
|
+
* shape does not carry because there it IS the record key. Optional members
|
|
45
|
+
* surface as `| null`: Skillet has no `optional()`, so every declared key is
|
|
46
|
+
* emitted and `null` is how the model says "not set".
|
|
47
|
+
*/
|
|
48
|
+
export interface TableColumnDraft {
|
|
49
|
+
/**
|
|
50
|
+
* The name this column is filed under in `columnsConfig`, and the name
|
|
51
|
+
* `displayedColumnsInOrder` refers to it by.
|
|
52
|
+
*/
|
|
53
|
+
key: string;
|
|
54
|
+
type: TableColumnConfigInterface['type'];
|
|
55
|
+
property: string;
|
|
56
|
+
label: string;
|
|
57
|
+
inEdit: boolean;
|
|
58
|
+
checkUpto: string;
|
|
59
|
+
calculatedFieldRules: s.Infer<typeof calculatedFieldRulesDraftSchema>;
|
|
60
|
+
reductionFunction: 'sum' | null;
|
|
61
|
+
colorScale: string | null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* What the model produces in place of a `TableConfigurationsInterface`.
|
|
65
|
+
*
|
|
66
|
+
* The columns arrive as a LIST, each carrying its own key. The application
|
|
67
|
+
* folds that list into the `columnsConfig` record — `key` becomes the record
|
|
68
|
+
* key and the rest of the entry becomes the value — before the input is
|
|
69
|
+
* validated. Until it does, this is not a `TableConfigurationsInterface` and
|
|
70
|
+
* must not be treated as one.
|
|
71
|
+
*/
|
|
72
|
+
export interface TableConfigDraft {
|
|
73
|
+
displayedColumnsInOrder: string[];
|
|
74
|
+
columns: TableColumnDraft[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The `tableConfig` member: the columns of a table control.
|
|
78
|
+
*
|
|
79
|
+
* ## Why this produces a draft
|
|
80
|
+
*
|
|
81
|
+
* `columnsConfig` is declared `{ [key: string]: TableColumnConfigInterface | any }`
|
|
82
|
+
* — a record keyed by the column's own name, decided per table by whoever is
|
|
83
|
+
* authoring it. Skillet has no record or map node, so there is no way to say
|
|
84
|
+
* "an object whose keys I do not know yet and whose values all look like this".
|
|
85
|
+
* The only expressible alternatives are both bad: a fixed set of column names,
|
|
86
|
+
* which would be wrong for every table but the one it was written for, or an
|
|
87
|
+
* open object, which a model fills with structure nothing reads.
|
|
88
|
+
*
|
|
89
|
+
* So the record is INVERTED into a list. Each entry carries the key it is to be
|
|
90
|
+
* filed under, and the application folds the list back into the record before
|
|
91
|
+
* validation. A list is also the shape the second half of this config already
|
|
92
|
+
* wants: `displayedColumnsInOrder` is an ordered list of those same keys, and
|
|
93
|
+
* asking a model to keep an array and an object's key set in agreement is
|
|
94
|
+
* asking for the two to disagree.
|
|
95
|
+
*
|
|
96
|
+
* ## What is not defined here
|
|
97
|
+
*
|
|
98
|
+
* `calculatedFieldRules` belongs to `./calculated-field` and is composed in.
|
|
99
|
+
* The interface declares it REQUIRED on every column, not just calculated ones,
|
|
100
|
+
* so the node is present on every column too; the description says what to do
|
|
101
|
+
* with it on a column that computes nothing.
|
|
102
|
+
*/
|
|
103
|
+
export declare const tableConfigSchema: s.ObjectType<{
|
|
104
|
+
displayedColumnsInOrder: s.ArrayType<s.StringType>;
|
|
105
|
+
columns: s.ArrayType<s.ObjectType<{
|
|
106
|
+
key: s.StringType;
|
|
107
|
+
type: s.EnumType<["caluculated", "property", "checkBox", "primaryKey"]>;
|
|
108
|
+
property: s.StringType;
|
|
109
|
+
label: s.StringType;
|
|
110
|
+
inEdit: s.BooleanType;
|
|
111
|
+
checkUpto: s.StringType;
|
|
112
|
+
calculatedFieldRules: s.ObjectType<{
|
|
113
|
+
formula: s.StringType;
|
|
114
|
+
variables: s.ArrayType<import("@hashbrownai/core/src/schema/base").SchemaForUnion<{
|
|
115
|
+
bindingType: "field";
|
|
116
|
+
variable: string;
|
|
117
|
+
label: string;
|
|
118
|
+
formControlName: string;
|
|
119
|
+
} | {
|
|
120
|
+
bindingType: "listAggregate";
|
|
121
|
+
variable: string;
|
|
122
|
+
label: string;
|
|
123
|
+
formControlName: string;
|
|
124
|
+
parentInputFormControl: string;
|
|
125
|
+
function: import("../../index.js").CalculationFunctions;
|
|
126
|
+
applyFunctionToCol: string;
|
|
127
|
+
applyFunctionToLabel: string | null;
|
|
128
|
+
filterValuesByCol: string | null;
|
|
129
|
+
filterValuesByThisColLabel: string | null;
|
|
130
|
+
tableCell: boolean | null;
|
|
131
|
+
}>>;
|
|
132
|
+
decimalPlaces: import("@hashbrownai/core/src/schema/base").SchemaForUnion<number | null>;
|
|
133
|
+
roundingMode: import("@hashbrownai/core/src/schema/base").SchemaForUnion<"FLOOR" | "CEIL" | "ROUND" | null>;
|
|
134
|
+
getFormulaFromAFormInput: import("@hashbrownai/core/src/schema/base").SchemaForUnion<boolean | null>;
|
|
135
|
+
formControlWithFormula: import("@hashbrownai/core/src/schema/base").SchemaForUnion<string | null>;
|
|
136
|
+
}>;
|
|
137
|
+
reductionFunction: import("@hashbrownai/core/src/schema/base").SchemaForUnion<"sum" | null>;
|
|
138
|
+
colorScale: import("@hashbrownai/core/src/schema/base").SchemaForUnion<string | null>;
|
|
139
|
+
}>>;
|
|
140
|
+
}>;
|
|
141
|
+
/**
|
|
142
|
+
* `matrixTableConfig` models as itself, so it is held to the real member type.
|
|
143
|
+
*/
|
|
144
|
+
export type _NoMatrixTableConfigDrift = AssertNever<[
|
|
145
|
+
s.Infer<typeof matrixTableConfigSchema>
|
|
146
|
+
] extends [
|
|
147
|
+
NonNullable<IMatrixInput['matrixTableConfig']>
|
|
148
|
+
] ? never : 'matrixTableConfig'>;
|
|
149
|
+
/**
|
|
150
|
+
* `tableConfig` is held to its DRAFT, not to `TableConfigurationsInterface`.
|
|
151
|
+
*
|
|
152
|
+
* The two differ in shape on purpose — a list of keyed columns against a record
|
|
153
|
+
* of columns — so asserting against the interface would fail by design and tell
|
|
154
|
+
* us nothing. What is worth checking is that the node keeps producing the draft
|
|
155
|
+
* the application's folding step expects, and that is what this checks. The
|
|
156
|
+
* fold itself is the caller's contract and no compiler here can see it.
|
|
157
|
+
*/
|
|
158
|
+
export type _NoTableConfigDraftDrift = AssertNever<[
|
|
159
|
+
s.Infer<typeof tableConfigSchema>
|
|
160
|
+
] extends [TableConfigDraft] ? never : 'tableConfig'>;
|
|
161
|
+
/**
|
|
162
|
+
* The draft column still covers every member of the stored column.
|
|
163
|
+
*
|
|
164
|
+
* This is the assertion the draft would otherwise lose. Because
|
|
165
|
+
* {@link _NoTableConfigDraftDrift} checks the node against a type declared in
|
|
166
|
+
* this file, both could drift away from `TableColumnConfigInterface` together
|
|
167
|
+
* without anything failing. A field added to the stored column surfaces here
|
|
168
|
+
* instead, by name.
|
|
169
|
+
*/
|
|
170
|
+
export type _TableColumnShapeCovered = AssertNever<Exclude<keyof TableColumnConfigInterface, keyof Omit<TableColumnDraft, 'key'>>>;
|
|
171
|
+
/**
|
|
172
|
+
* What the application must do to a generated `tableConfig`.
|
|
173
|
+
*
|
|
174
|
+
* The first entry is not a missing value but a change of shape: the column LIST
|
|
175
|
+
* must be folded into the `columnsConfig` record before anything will accept
|
|
176
|
+
* it. See {@link TableConfigDraft}.
|
|
177
|
+
*/
|
|
178
|
+
export declare const TABLE_DRAFT_COMPLETION: readonly ["tableConfig.columns[] folded into columnsConfig, keyed by each entry's `key`", "tableConfig.columnsConfig[].calculatedFieldRules (see CALCULATED_FIELD_DRAFT_COMPLETION)"];
|