ngx-t-forms-types 0.0.33 → 0.0.35

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.
@@ -0,0 +1,32 @@
1
+ /**
2
+ * How an import session decides that two rows are the same record.
3
+ *
4
+ * Passed to the import controller's `runImport` / `runMultipleInputImport`
5
+ * as per-import data — which fields identify a record in *this* form — not
6
+ * as application configuration. Only rows of the one session are compared;
7
+ * records already persisted are the server's to check.
8
+ *
9
+ * @public
10
+ */
11
+ export interface ImportIdentity {
12
+ /**
13
+ * `formControlName`s that together identify a record (for a multiple-input
14
+ * import: the child inputs' `formControlName`s). A row whose values for all
15
+ * of them repeat an earlier row's is a duplicate of that row. Empty or
16
+ * absent disables duplicate detection. A name the form does not have
17
+ * rejects the run rather than matching nothing.
18
+ */
19
+ readonly distinctKeys: readonly string[];
20
+ /**
21
+ * What to do with a duplicate.
22
+ *
23
+ * - `skip` — the row is never processed: no tower, no fetches. It is
24
+ * reported with status `duplicate` and `duplicateOf` naming the row it
25
+ * repeats. The default.
26
+ * - `flag` — the row is processed and validated like any other and keeps
27
+ * its real status; only `duplicateOf` marks it. For hosts that want to
28
+ * show what the duplicate contained. Such a row still must not be
29
+ * imported.
30
+ */
31
+ readonly onDuplicate?: 'skip' | 'flag';
32
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -14,7 +14,11 @@ export interface ImportProgress {
14
14
  pending: number;
15
15
  /** Rows currently processing. */
16
16
  processing: number;
17
- /** `valid` + `overridable` + `invalid` — i.e. rows that fully settled (regardless of validity). */
17
+ /**
18
+ * `valid` + `overridable` + `invalid` + rows held with status `duplicate` —
19
+ * i.e. rows that reached an outcome. `complete + error === total` once a
20
+ * run has finished, duplicates included.
21
+ */
18
22
  complete: number;
19
23
  /** Rows that settled with no errors. */
20
24
  valid: number;
@@ -24,6 +28,13 @@ export interface ImportProgress {
24
28
  invalid: number;
25
29
  /** Rows that threw before settling. */
26
30
  error: number;
31
+ /**
32
+ * Rows recognised as repeating an earlier row (`duplicateOf` set). Under
33
+ * the default `skip` policy these carry status `duplicate` and are counted
34
+ * in `complete`; under `flag` they keep their real status and are counted
35
+ * there as well.
36
+ */
37
+ duplicate: number;
27
38
  /** Underlying array — useful for rendering per-row UI. */
28
39
  rows: ImportRowState[];
29
40
  }
@@ -13,12 +13,19 @@
13
13
  * pre-process error (a row carrying both blocking and overridable errors is
14
14
  * `invalid`, not `overridable`).
15
15
  * - `error` — `_processRow` threw before settle (tower init failure, etc.).
16
+ * - `duplicate` — the row repeats an earlier row of the session on the
17
+ * fields the import was given as its identity (`ImportIdentity`), and is
18
+ * held out of the import; {@link ImportRowState.duplicateOf} names the row
19
+ * it repeats. A row skipped before its tower was built has no other
20
+ * verdict; one recognised only after it settled keeps its settled value
21
+ * and errors under this status. Under `onDuplicate: 'flag'` a duplicate
22
+ * keeps its real status instead and only `duplicateOf` marks it.
16
23
  *
17
24
  * Upstreamed from `ngx-t-forms` per DECISIONS.md D-016.
18
25
  *
19
26
  * @public
20
27
  */
21
- export type ImportRowStatus = 'pending' | 'processing' | 'valid' | 'overridable' | 'invalid' | 'error';
28
+ export type ImportRowStatus = 'pending' | 'processing' | 'valid' | 'overridable' | 'invalid' | 'error' | 'duplicate';
22
29
  /**
23
30
  * Per-row state recorded throughout an import session.
24
31
  *
@@ -63,4 +70,48 @@ export interface ImportRowState {
63
70
  colErrors?: Record<string, string>;
64
71
  /** Free-form message attached when `status === 'error'`. */
65
72
  errorMessage?: string;
73
+ /**
74
+ * formControlName → the cells of a choice field (select, autocomplete,
75
+ * paginated selection table) whose text matched MORE THAN ONE of the
76
+ * field's options, with the options it could be. The import accepts an
77
+ * option's visible label (or, for a table, any of its column values) in
78
+ * place of its stored value; a unique match is substituted silently, a
79
+ * tie is reported here so the user can pick, and the row stays `invalid`
80
+ * (`colErrors` carries `ambiguousOption:<text>`) until each is settled.
81
+ * A multi-select cell lists one entry per unsettled value.
82
+ */
83
+ optionChoices?: Record<string, ImportOptionChoice[]>;
84
+ /**
85
+ * The `rowIndex` of the earlier row this row repeats, when the session was
86
+ * given an `ImportIdentity` and this row's identity matches. Always the
87
+ * LOWEST index of the matching group — first wins — so the relation is
88
+ * stable and a host can offer "go to row N". Set under both duplicate
89
+ * policies; under `skip` the row's `status` is `duplicate` as well. Cleared
90
+ * when a re-run changes the identities so that the row no longer repeats
91
+ * anything.
92
+ */
93
+ duplicateOf?: number;
94
+ }
95
+ /**
96
+ * One option an imported cell could stand for.
97
+ *
98
+ * @public
99
+ */
100
+ export interface ImportOptionCandidate {
101
+ /** The value the form stores when this option is chosen. */
102
+ value: unknown;
103
+ /** What the form shows for the option — its label, or a table row's column values. */
104
+ label: string;
105
+ }
106
+ /**
107
+ * An imported cell (or one value of a multi-select cell) whose text matched
108
+ * several options of a choice field. See {@link ImportRowState.optionChoices}.
109
+ *
110
+ * @public
111
+ */
112
+ export interface ImportOptionChoice {
113
+ /** The text as it was imported. */
114
+ text: string;
115
+ /** The options it matched, in the field's option order. */
116
+ candidates: ImportOptionCandidate[];
66
117
  }
@@ -1,2 +1,3 @@
1
- export { ImportRowState, ImportRowStatus } from './ImportRowState.js';
1
+ export { ImportOptionCandidate, ImportOptionChoice, ImportRowState, ImportRowStatus } from './ImportRowState.js';
2
2
  export { ImportProgress } from './ImportProgress.js';
3
+ export { ImportIdentity } from './ImportIdentity.js';
@@ -1,5 +1,6 @@
1
1
  import { MscoaValuetValidationErrors } from "../environment/IStoreFunctions.js";
2
2
  import { IBasicFormInput } from "./BasicFormInputInterface.js";
3
+ import { MinInputMapInput } from "./MinimumInputRequiredInterface.js";
3
4
  import { ScoaInnerInput } from "./MscoaInput.js";
4
5
  export interface IVersion {
5
6
  versionNumber: number;
@@ -120,6 +121,34 @@ export interface IIncludedSegmentConfig {
120
121
  additionalAccounts: string[];
121
122
  segmentExtension: boolean;
122
123
  id: string;
124
+ /**
125
+ * Optional account-path limit for the picker: a colon-separated path into this
126
+ * segment's branch of the account tree, e.g.
127
+ * `Net Assets:Reserves and Funds:Revaluation Reserve`. When set, the account
128
+ * picker shows only that branch — its ancestors stay visible as context but
129
+ * cannot be selected — so only accounts at or beneath it can be chosen.
130
+ *
131
+ * Absent or empty means every branch of the segment is selectable, which is
132
+ * exactly how every segment saved before this member existed behaves. A path
133
+ * the loaded tree does not contain is ignored the same way, so a stale limit
134
+ * can never lock a segment out of every account.
135
+ */
136
+ limitToChildOfAccount?: string;
137
+ /**
138
+ * Optional dynamic source for {@link IIncludedSegmentConfig.limitToChildOfAccount}:
139
+ * another form input whose CURRENT value supplies the account path at the
140
+ * moment the picker opens. Shaped exactly like a required-input `mapTo`
141
+ * binding ({@link MinInputMapInput}); the runtime reads the input by `inputId`
142
+ * from the form the MSCOA field belongs to (an account-level custom input of
143
+ * the same MSCOA field is reachable too).
144
+ *
145
+ * The mapped value is honoured when it is a string — or an array of strings,
146
+ * one per level — naming a branch that exists in the segment tree. Otherwise
147
+ * the fixed `limitToChildOfAccount` applies when it resolves, and failing that
148
+ * every branch stays selectable. The mapping can therefore never lock the
149
+ * segment on its own, and an unfilled source input falls back gracefully.
150
+ */
151
+ limitToChildOfAccountMapTo?: MinInputMapInput;
123
152
  }
124
153
  /**
125
154
  * How the {@link IDualCashExclusionConfig.rules} combine into one verdict.
@@ -198,6 +227,18 @@ export interface IScoaInputConfig {
198
227
  * non-Dual accounting bases.
199
228
  */
200
229
  dualCashExclusion?: IDualCashExclusionConfig;
230
+ /**
231
+ * Opt-in: lets the user pick an account at ANY level of the tree, not only a
232
+ * posting-level one (`PostingLevel === 'Y'`, plus `BreakDownAllowed === 'Y'`
233
+ * on the PROJECT segment). Absent or `false` keeps the posting-level
234
+ * restriction every input had before this member existed.
235
+ *
236
+ * The restriction is enforced by the picker alone — nothing downstream
237
+ * re-validates posting level — so enabling it widens what a form can capture,
238
+ * and a consumer that posts the captured account to a ledger must be prepared
239
+ * for a group-level account.
240
+ */
241
+ allowNonPostingLevelAccount?: boolean;
201
242
  }
202
243
  export interface IGetTreeResponse {
203
244
  message: string;
@@ -1,4 +1,10 @@
1
1
  import Joi from 'joi';
2
+ /**
3
+ * Joi shape of the `MinInputMapInput` a segment may name as the dynamic source
4
+ * of its account-path limit. `inputId` is the only member the runtime reads;
5
+ * the other three are the descriptive half the builder stamps alongside it.
6
+ */
7
+ export declare const AccountPathLimitSourceSchema: Joi.ObjectSchema<any>;
2
8
  export declare const IncludedSegmentConfigSchema: Joi.ObjectSchema<any>;
3
9
  export declare const DualCashExclusionRuleSchema: Joi.ObjectSchema<any>;
4
10
  export declare const DualCashExclusionConfigSchema: Joi.ObjectSchema<any>;
@@ -1,5 +1,16 @@
1
1
  import Joi from 'joi';
2
2
  import { AccountingBasis } from '../interfaces/formInput/IMscoaAccount.js';
3
+ /**
4
+ * Joi shape of the `MinInputMapInput` a segment may name as the dynamic source
5
+ * of its account-path limit. `inputId` is the only member the runtime reads;
6
+ * the other three are the descriptive half the builder stamps alongside it.
7
+ */
8
+ export const AccountPathLimitSourceSchema = Joi.object({
9
+ inputId: Joi.string().required(),
10
+ formControlName: Joi.string().allow('').optional(),
11
+ dataType: Joi.string().allow('').optional(),
12
+ element: Joi.string().allow('').optional()
13
+ });
3
14
  export const IncludedSegmentConfigSchema = Joi.object({
4
15
  segment: Joi.string(),
5
16
  customSegment: Joi.string().optional(),
@@ -10,7 +21,9 @@ export const IncludedSegmentConfigSchema = Joi.object({
10
21
  inheritValueFromAccrual: Joi.boolean().optional(),
11
22
  vatSelectonActive: Joi.boolean().optional(),
12
23
  segmentExtension: Joi.boolean().required(),
13
- id: Joi.string().required()
24
+ id: Joi.string().required(),
25
+ limitToChildOfAccount: Joi.string().allow('').optional(),
26
+ limitToChildOfAccountMapTo: AccountPathLimitSourceSchema.optional()
14
27
  });
15
28
  export const DualCashExclusionRuleSchema = Joi.object({
16
29
  id: Joi.string().optional(),
@@ -32,7 +45,8 @@ export const MscoaInputConfigSchema = Joi.object({
32
45
  accountingBasis: Joi.string().valid(...Object.values(AccountingBasis)).optional(),
33
46
  extensionAccountsForSegments: Joi.array().items(Joi.string()).optional(),
34
47
  showAllSegments: Joi.boolean().required(),
35
- dualCashExclusion: DualCashExclusionConfigSchema.optional()
48
+ dualCashExclusion: DualCashExclusionConfigSchema.optional(),
49
+ allowNonPostingLevelAccount: Joi.boolean().optional()
36
50
  });
37
51
  export function validateMscoaInputConfig(value) {
38
52
  const { error, value: validatedValue } = MscoaInputConfigSchema.validate(value);
@@ -237,6 +237,7 @@ export declare const draftMemberSchemas: {
237
237
  inheritValueFromAccrual: import("@hashbrownai/core/src/schema/base").SchemaForUnion<boolean | null>;
238
238
  vatSelectonActive: import("@hashbrownai/core/src/schema/base").SchemaForUnion<boolean | null>;
239
239
  segmentExtension: s.BooleanType;
240
+ limitToChildOfAccount: import("@hashbrownai/core/src/schema/base").SchemaForUnion<string | null>;
240
241
  }>>;
241
242
  cashSegments: import("@hashbrownai/core/src/schema/base").SchemaForUnion<{
242
243
  segment: string;
@@ -248,9 +249,11 @@ export declare const draftMemberSchemas: {
248
249
  inheritValueFromAccrual: boolean | null;
249
250
  vatSelectonActive: boolean | null;
250
251
  segmentExtension: boolean;
252
+ limitToChildOfAccount: string | null;
251
253
  }[] | null>;
252
254
  accountValueLabel: s.EnumType<string[]>;
253
255
  showAllSegments: s.BooleanType;
256
+ allowNonPostingLevelAccount: import("@hashbrownai/core/src/schema/base").SchemaForUnion<boolean | null>;
254
257
  extensionAccountsForSegments: s.ArrayType<s.StringType>;
255
258
  dualCashExclusion: import("@hashbrownai/core/src/schema/base").SchemaForUnion<{
256
259
  rules: {
@@ -58,6 +58,12 @@ import { AssertNever } from '../internal/coupling.js';
58
58
  * `string | undefined`. The builder's segment form declares it
59
59
  * `Validators.required`, so a row without one cannot be saved by hand either.
60
60
  * Narrower than the interface is the permitted direction; wider is not.
61
+ *
62
+ * `limitToChildOfAccountMapTo` — the run-time binding that reads a segment's
63
+ * account-path limit off another form input — is not offered, for the reason
64
+ * `inputs` is not: it cross-references an input by id, and ids are stamped by
65
+ * the builder after generation. The fixed `limitToChildOfAccount` path IS
66
+ * offered; the binding is authored in the builder's segment editor.
61
67
  */
62
68
  export interface MscoaSegmentDraft {
63
69
  segment: string;
@@ -69,6 +75,7 @@ export interface MscoaSegmentDraft {
69
75
  inheritValueFromAccrual: boolean | null;
70
76
  vatSelectonActive: boolean | null;
71
77
  segmentExtension: boolean;
78
+ limitToChildOfAccount: string | null;
72
79
  }
73
80
  export interface MscoaDualCashExclusionRuleDraft {
74
81
  pattern: string;
@@ -89,6 +96,7 @@ export interface MscoaConfigDraft {
89
96
  extensionAccountsForSegments: string[];
90
97
  showAllSegments: boolean;
91
98
  dualCashExclusion: MscoaDualCashExclusionDraft | null;
99
+ allowNonPostingLevelAccount: boolean | null;
92
100
  }
93
101
  /**
94
102
  * `DualCashExclusionMatchMode` is a string-union type with no runtime object to
@@ -130,6 +138,7 @@ export declare const mscoaConfigSchema: s.ObjectType<{
130
138
  inheritValueFromAccrual: import("@hashbrownai/core/src/schema/base").SchemaForUnion<boolean | null>;
131
139
  vatSelectonActive: import("@hashbrownai/core/src/schema/base").SchemaForUnion<boolean | null>;
132
140
  segmentExtension: s.BooleanType;
141
+ limitToChildOfAccount: import("@hashbrownai/core/src/schema/base").SchemaForUnion<string | null>;
133
142
  }>>;
134
143
  cashSegments: import("@hashbrownai/core/src/schema/base").SchemaForUnion<{
135
144
  segment: string;
@@ -141,9 +150,11 @@ export declare const mscoaConfigSchema: s.ObjectType<{
141
150
  inheritValueFromAccrual: boolean | null;
142
151
  vatSelectonActive: boolean | null;
143
152
  segmentExtension: boolean;
153
+ limitToChildOfAccount: string | null;
144
154
  }[] | null>;
145
155
  accountValueLabel: s.EnumType<string[]>;
146
156
  showAllSegments: s.BooleanType;
157
+ allowNonPostingLevelAccount: import("@hashbrownai/core/src/schema/base").SchemaForUnion<boolean | null>;
147
158
  extensionAccountsForSegments: s.ArrayType<s.StringType>;
148
159
  dualCashExclusion: import("@hashbrownai/core/src/schema/base").SchemaForUnion<{
149
160
  rules: {
@@ -57,6 +57,7 @@ const segmentSchema = s.object('One segment of the chart of accounts the user pi
57
57
  // form already persisted with it.
58
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
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
+ limitToChildOfAccount: optional(s.string('Optional colon-separated path into this segment\'s branch of the chart, such as "Net Assets:Reserves and Funds:Revaluation Reserve". When set, the user can only pick accounts at or beneath that branch; the levels above it are shown for context but cannot be selected. Use it when the form captures one known category — a specific reserve, one asset class — and the account must not fall outside it. Leave null to offer the whole segment, which is the usual case. A path the chart does not contain is ignored and the whole segment is offered anyway, so guess nothing: name a branch only when the request states it.')),
60
61
  });
61
62
  /**
62
63
  * One cash-exclusion rule.
@@ -104,6 +105,7 @@ export const mscoaConfigSchema = s.object(describe('How this control asks the us
104
105
  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
106
  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
107
  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.'),
108
+ allowNonPostingLevelAccount: optional(s.boolean('True lets the user pick an account at any level of the chart, not only a posting-level account. Leave null — posting-level accounts only, the default every existing input has — unless the request says a group account must be selectable, such as a budget captured at a summary line.')),
107
109
  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
110
  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
111
  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)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngx-t-forms-types",
3
- "version": "0.0.33",
3
+ "version": "0.0.35",
4
4
  "description": "Typings and interfaces for the ngx-t-forms library for dynamic forms.",
5
5
  "keywords": [
6
6
  "typings",