@dforge-core/metadata 0.0.41 → 0.0.43
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/CHANGELOG.md +34 -1
- package/dist/dsl/index.js +4 -4
- package/dist/dsl/index.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +19 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/schemas/entity.schema.json +32 -1
- package/schemas/registers.schema.json +60 -3
- package/schemas/roles.schema.json +5 -2
- package/src/dsl/builtins.ts +4 -4
- package/src/index.ts +1 -0
- package/src/registers/check-binding.ts +19 -1
- package/src/registers/types.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dforge-core/metadata",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.43",
|
|
4
4
|
"description": "Canonical dForge authoring registries — field types, base datatypes, column types, traits, data-view kinds and chart types — with derivation helpers. Runtime-dependency-free; the single source of truth shared by editors, the web app and the CLI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "dForge",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"ajv": "^8.20.0",
|
|
55
55
|
"tsup": "^8.5.1",
|
|
56
56
|
"typescript": "^6.0.3",
|
|
57
|
-
"vitest": "^
|
|
57
|
+
"vitest": "^5.0.4"
|
|
58
58
|
},
|
|
59
59
|
"scripts": {
|
|
60
60
|
"sync-schemas": "node scripts/sync-schemas.mjs",
|
|
@@ -192,11 +192,20 @@
|
|
|
192
192
|
"type": "array",
|
|
193
193
|
"minItems": 1,
|
|
194
194
|
"items": { "type": "string" },
|
|
195
|
-
"description": "Indexed columns, in order. Install refuses any key other than fields and
|
|
195
|
+
"description": "Indexed columns, in order. Install refuses any key other than fields, using, unique and where - \"columns\", which a unique constraint accepts, included."
|
|
196
196
|
},
|
|
197
197
|
"using": {
|
|
198
198
|
"enum": ["btree", "hash", "gin", "gist", "brin"],
|
|
199
199
|
"description": "Index method. Omitted, btree."
|
|
200
|
+
},
|
|
201
|
+
"unique": {
|
|
202
|
+
"type": "boolean",
|
|
203
|
+
"description": "A unique index. With \"where\" it is unique among the rows the condition selects, which a unique constraint cannot be (unique among the rows that are not deleted)."
|
|
204
|
+
},
|
|
205
|
+
"where": {
|
|
206
|
+
"type": "string",
|
|
207
|
+
"minLength": 1,
|
|
208
|
+
"description": "A partial index: SQL condition over the table's columns, e.g. \"is_deleted = false\". Only the rows it selects are indexed, and a query uses the index only when its own filter implies the condition. One condition, without ';', '$' or comments. Changing it rebuilds the index on upgrade."
|
|
200
209
|
}
|
|
201
210
|
}
|
|
202
211
|
}
|
|
@@ -209,6 +218,26 @@
|
|
|
209
218
|
"$ref": "#/$defs/periodConfig",
|
|
210
219
|
"description": "Period entity configuration. Requires 'period' trait."
|
|
211
220
|
},
|
|
221
|
+
"periodGuards": {
|
|
222
|
+
"type": "array",
|
|
223
|
+
"description": "Closed-period locks for a document that posts to no register. Each guard names a period entity, the date the document's period follows from, and the column that references the period. The platform fills that column from the date and makes it a foreign key, so a document is written only into a period that exists and is open, and a period documents refer to cannot be deleted. Not allowed on an extension.",
|
|
224
|
+
"items": {
|
|
225
|
+
"type": "object",
|
|
226
|
+
"required": ["periodEntity", "periodDateField", "periodField", "lockedFields"],
|
|
227
|
+
"properties": {
|
|
228
|
+
"periodEntity": { "type": "string", "description": "The period entity ('period' trait): this module's, or another module's as 'module.entity'." },
|
|
229
|
+
"periodDateField": { "type": "string", "description": "The document's date column the period is resolved from." },
|
|
230
|
+
"periodField": { "type": "string", "description": "The document's column referencing the period's key (varchar, like the key). Filled by the platform from the date; whatever a writer puts there is replaced." },
|
|
231
|
+
"periodKeyField": { "type": "string", "default": "period_key", "description": "The period entity's key column. Must be its primary key or unique." },
|
|
232
|
+
"periodClosedField": { "type": "string", "default": "closed", "description": "The period entity's closed flag." },
|
|
233
|
+
"lockedFields": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "Fields a closed period freezes. Insert and delete are always refused there, and the date is always watched; [\"periodDateField\"] refuses just that, [\"*\"] freezes the whole row." },
|
|
234
|
+
"lineEntity": { "type": "string", "description": "The document's line entity, guarded through its header's date." },
|
|
235
|
+
"lineField": { "type": "string", "description": "The line's foreign key to the header." },
|
|
236
|
+
"lockedLineFields": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "Line fields a closed period freezes; [\"*\"] for the whole row. Required with lineEntity." }
|
|
237
|
+
},
|
|
238
|
+
"additionalProperties": false
|
|
239
|
+
}
|
|
240
|
+
},
|
|
212
241
|
"accumulation": {
|
|
213
242
|
"$ref": "#/$defs/accumulationConfig",
|
|
214
243
|
"description": "Accumulation registry configuration (simple document without lines). Requires 'accumulation' trait."
|
|
@@ -330,6 +359,7 @@
|
|
|
330
359
|
"map": { "type": "object", "additionalProperties": { "oneOf": [ { "type": "string" }, { "type": "object", "required": ["debit", "credit"], "additionalProperties": false, "properties": { "debit": { "type": "string" }, "credit": { "type": "string" } } } ] }, "description": "Register dimension or resource name -> this document's column carrying it. On an accounting (L) column a resource or dimension the register splits by side maps to { \"debit\": \"<line column>\", \"credit\": \"<line column>\" } instead of one column. Every resource the register declares must be mapped (install refuses otherwise); a dimension left out binds to a column of the same name. Only meaningful with 'register'. On an accumulation (A) column a value may be a one-hop reference path, '<reference column>.<target column>', read through the reference at posting time (off the line when the binding posts from lines), or an expression: a string starting with '=' holding an F-column formula over the same row with one-hop reference reads — \"=COALESCE([transit_warehouse_id], [dest_warehouse_id])\"." },
|
|
331
360
|
"headerMap": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Register dimension or resource name -> this DOCUMENT's column carrying it, for a binding that posts from LINES. A line binding reads every other value off the line, and a dimension that belongs to the document (a dealer, an organisation, a date) cannot be put on the line: a G column is a same-row expression or an aggregate over a child set, so it cannot read a parent. Explicit rather than a fallback, because 'not on the line, try the header' turns a typo into a silent read of an unrelated header column with the same name. Install refuses: a name on neither table, a field also named in 'map', a column also read off the line by 'map', a column that is part of the line's key, a column 'lockedFields' does not freeze (the posting is keyed by it, so it has to hold still while the document is posted), and a RESOURCE unless the register is 'info' (a quantity from the document would be added once per line; an information register's records replace rather than add). A value may be a one-hop reference path off the document, whose reference key column 'lockedFields' must then freeze, or an expression over the document ('=...', see 'map')." },
|
|
332
361
|
"groupLinesBy": { "type": "array", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true, "description": "Line columns whose distinct values each post ONE movement, instead of one per line — a production report posting 'this unit worked today' once per unit with a count of 1. 'map' is then read over a group row: the grouped line columns, every column of the document, and one-hop reads through a reference of either, so an expression may mix the document and the group key. A quoted constant is stamped once per group; a numeric line column mapped to a resource is summed over the group. Install refuses: a binding that posts from the document, an L column, a grouped column the line does not store or 'lockedLineFields' does not freeze, a line column read that is neither grouped nor a summed resource, a non-numeric summed resource, a column both grouped and summed, an ungrouped line resource on an 'info' register, a name both the line and the document carry, and a document column read that 'lockedFields' does not freeze. Accumulation (A) columns only." },
|
|
362
|
+
"lineDateField": { "type": "string", "description": "A line column dating that line's movement instead of 'dateField' — for lines that happen on days of their own under one document date (a limit card issuing through the month and dated at its end). A line that leaves it empty moves on 'dateField'. With 'periodEntity', each line's period must exist and be open, at posting and unposting and at every write to the line, judged by the date the movement is stamped with (the line's or the document's, after 'dateTrunc'). On an information register that records changes only, two lines may not write one key: post one line per key. Install refuses: a register that keeps no date, a binding that posts from the document, a column that is not a stored 'date' or 'timestamp' without time zone, one 'lockedLineFields' does not freeze, one 'groupLinesBy' omits when lines are grouped, and a binding with 'periodDimField'. Accumulation (A) columns only." },
|
|
333
363
|
"dateField": { "type": "string", "description": "This document's date column, supplying the date the register stamps on each journal row. Required once the register declares a 'journalDate' — one writer that supplied none would make every balance read as of a date quietly short, across all writers. On an accumulation (A) column it may be a one-hop reference path, '<reference column>.<target column>', or an expression over the document ('=...', see 'map')." },
|
|
334
364
|
"dateTrunc": { "type": "string", "enum": ["day", "month", "year"], "description": "Takes the date 'dateField' supplies back to the start of its day, month or year before the register stamps it — for a document that carries a time while the register's records sit on whole days. The calendar is the value's as stored; no time zone is applied. Needs 'dateField'. Accumulation (A) columns only." },
|
|
335
365
|
"skipPostingWhenEmpty": { "type": "array", "items": { "type": "string" }, "description": "THE EXCEPTION, not the normal way to model this — read the guide before declaring it. Register dimensions (by the register's names) whose empty value means THIS POSTING records nothing: the line, or the document when the binding posts from the document. One posting is skipped, never the register and never the document. When that is all the document would have recorded it is left unposted to this register, and the flag is per binding, so its other registers post as usual. An empty dimension NOT listed refuses the posting, naming the row and the dimension. It leaves a document fully posted while some of its lines produced no movement, which usually means the document is modelling two kinds of line in one table: since 'lineEntity' is declared per BINDING, prefer giving each register its own line table (goods lines post to stock, service lines do not) so that every line of each table posts. Declare this only when the distinction is not structural but lives in the data — a batch or serial number filled on some rows and not others — so there is no second table to put those rows in. Accumulation (A) columns only. Formerly skipWhenNull: a package using the old name is refused at install, while a tenant's already-stored params keep being read under it." },
|
|
@@ -352,6 +382,7 @@
|
|
|
352
382
|
"dependencies": {
|
|
353
383
|
"headerMap": ["lineEntity"],
|
|
354
384
|
"groupLinesBy": ["lineEntity"],
|
|
385
|
+
"lineDateField": ["lineEntity", "dateField"],
|
|
355
386
|
"dateTrunc": ["dateField"],
|
|
356
387
|
"lineEntity": ["lineField", "lockedLineFields"],
|
|
357
388
|
"lineField": ["lineEntity"],
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"account": false,
|
|
25
25
|
"debitAccount": false, "creditAccount": false,
|
|
26
26
|
"debitAnalyticFields": false, "creditAnalyticFields": false,
|
|
27
|
-
"sideDimensions": false
|
|
27
|
+
"sideDimensions": false, "analyticTotals": false
|
|
28
28
|
},
|
|
29
29
|
"description": "A balance register keeps state, so it needs the entity that holds it. It dates its movements on the journal row under 'journalDate'; 'period' belongs to an information register, whose records ARE the dated thing. Its writer is recorded on the journal row, which is what 'recorder' names for an information register that has no journal."
|
|
30
30
|
}
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"account": false,
|
|
42
42
|
"debitAccount": false, "creditAccount": false,
|
|
43
43
|
"debitAnalyticFields": false, "creditAnalyticFields": false,
|
|
44
|
-
"sideDimensions": false,
|
|
44
|
+
"sideDimensions": false, "analyticTotals": false,
|
|
45
45
|
"allowNegative": false,
|
|
46
46
|
"autoCreate": false
|
|
47
47
|
},
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"account": false,
|
|
61
61
|
"debitAccount": false, "creditAccount": false,
|
|
62
62
|
"debitAnalyticFields": false, "creditAnalyticFields": false,
|
|
63
|
-
"sideDimensions": false,
|
|
63
|
+
"sideDimensions": false, "analyticTotals": false,
|
|
64
64
|
"totals": false,
|
|
65
65
|
"allowNegative": false,
|
|
66
66
|
"autoCreate": false
|
|
@@ -241,6 +241,63 @@
|
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
243
|
},
|
|
244
|
+
"analyticTotals": {
|
|
245
|
+
"type": "object",
|
|
246
|
+
"description": "An 'accounting' register's per-period cache keyed by the analytics as well: a cached row is keyed by (account, cached dimensions, analytic slots, period), for the accounts the chart flags. It is what makes a balance READ BY AN ANALYTIC cheap - stock by item and warehouse, settlements by counterparty and contract - which 'totals' leaves to the postings because over a whole ledger the analytics are where the rows are. getAccountBalance / getAccountTurnover read whole periods from it and the part-period at the edge from the postings. Beside 'totals', never instead of it, and in an entity of its own. Like 'totals' it caches the turnover within each period, so a backdated posting touches one row; a write to the journal that was not a posting, or a change to an account's flag, marks it stale until it is rebuilt.",
|
|
247
|
+
"required": ["entity", "period", "periodField", "accountField", "accountFlag", "analyticFields", "resources"],
|
|
248
|
+
"additionalProperties": false,
|
|
249
|
+
"properties": {
|
|
250
|
+
"entity": {
|
|
251
|
+
"type": "string",
|
|
252
|
+
"description": "Entity holding the cached rows. Keyed by ONE surrogate column the platform mints (the 'identity' trait), because a slot or a dimension may be empty and a PRIMARY KEY holds no NULL; the platform keeps a unique index over the cache's own key, NULLS NOT DISTINCT, and merges on it. It carries the account column, a column per cached dimension, the jsonb slot columns, the period column and both columns of every resource."
|
|
253
|
+
},
|
|
254
|
+
"period": {
|
|
255
|
+
"type": "string",
|
|
256
|
+
"enum": ["day", "month", "quarter", "year"],
|
|
257
|
+
"description": "How much one cached row covers, as for 'totals'."
|
|
258
|
+
},
|
|
259
|
+
"periodField": {
|
|
260
|
+
"type": "string",
|
|
261
|
+
"pattern": "^[a-z_][a-z0-9_]*$",
|
|
262
|
+
"description": "Column holding the FIRST date of the period a row covers."
|
|
263
|
+
},
|
|
264
|
+
"accountField": {
|
|
265
|
+
"type": "string",
|
|
266
|
+
"pattern": "^[a-z_][a-z0-9_]*$",
|
|
267
|
+
"description": "Column holding the account a cached row belongs to. A compound posting folds into two rows, one per side, each with that side's analytics."
|
|
268
|
+
},
|
|
269
|
+
"accountFlag": {
|
|
270
|
+
"type": "string",
|
|
271
|
+
"pattern": "^[a-z_][a-z0-9_]*$",
|
|
272
|
+
"description": "A bool column of the register's chart: the accounts where it is true are cached, and no other. Changing it on an account marks the cache stale until it is rebuilt (install, or dforge register rebuild-totals --cache analytic)."
|
|
273
|
+
},
|
|
274
|
+
"dimensions": {
|
|
275
|
+
"type": "array",
|
|
276
|
+
"items": { "type": "string", "pattern": "^[a-z_][a-z0-9_]*$" },
|
|
277
|
+
"description": "Which of the register's dimensions the cache is grouped by; omitted, it is all of them. Unlike 'totals' a nullable one may be cached: an empty value is a key value of its own."
|
|
278
|
+
},
|
|
279
|
+
"analyticFields": {
|
|
280
|
+
"type": "array",
|
|
281
|
+
"minItems": 1,
|
|
282
|
+
"items": { "type": "string", "pattern": "^[a-z_][a-z0-9_]*$" },
|
|
283
|
+
"description": "The cache's jsonb columns holding the analytic slots, slot N in the Nth, one per slot a posting side carries (the wider side's count on a compound register). Each holds the analytic's entity and key, which is what a read filters and groups by."
|
|
284
|
+
},
|
|
285
|
+
"resources": {
|
|
286
|
+
"type": "object",
|
|
287
|
+
"description": "Every resource the register declares, each with its 'debitField' and 'creditField' columns, as for an accounting register's 'totals'.",
|
|
288
|
+
"minProperties": 1,
|
|
289
|
+
"additionalProperties": {
|
|
290
|
+
"type": "object",
|
|
291
|
+
"required": ["debitField", "creditField"],
|
|
292
|
+
"additionalProperties": false,
|
|
293
|
+
"properties": {
|
|
294
|
+
"debitField": { "type": "string", "pattern": "^[a-z_][a-z0-9_]*$" },
|
|
295
|
+
"creditField": { "type": "string", "pattern": "^[a-z_][a-z0-9_]*$" }
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
},
|
|
244
301
|
"period": {
|
|
245
302
|
"type": "string",
|
|
246
303
|
"pattern": "^[a-z_][a-z0-9_]*$",
|
|
@@ -18,11 +18,14 @@
|
|
|
18
18
|
},
|
|
19
19
|
"rights": {
|
|
20
20
|
"type": "object",
|
|
21
|
-
"description": "
|
|
21
|
+
"description": "Security object → rights string. Keys are PREFIX-NAMESPACED so an action and an entity may share a code: a bare key is an entity code in this module ('invoice'), 'module.entity' is an entity owned by a module this one depends on ('parties.party'), 'action:<action_cd>' is an action from this module's ui/actions.json, and 'sp:<sp_cd>' is a stored procedure from logic/stored_procedures.json ('sp:module.sp_cd' for a dependency's), and 'report:<report_cd>' is a report from ui/reports.json ('report:module.report_cd' for a dependency's). A grant on an action written WITHOUT the 'action:' prefix is read as an entity code, resolves to nothing, and is silently dropped at install — action.execute then returns PERMISSION_DENIED.",
|
|
22
|
+
"propertyNames": {
|
|
23
|
+
"pattern": "^(action:[A-Za-z_][A-Za-z0-9_-]*|(sp|report):([A-Za-z_][A-Za-z0-9_-]*\\.)?[A-Za-z_][A-Za-z0-9_-]*|([A-Za-z_][A-Za-z0-9_-]*\\.)?[A-Za-z_][A-Za-z0-9_-]*)$"
|
|
24
|
+
},
|
|
22
25
|
"additionalProperties": {
|
|
23
26
|
"type": "string",
|
|
24
27
|
"pattern": "^[SIUDCE]*$",
|
|
25
|
-
"description": "Rights: S=Select, I=Insert, U=Update, D=Delete, C=Clone, E=Execute"
|
|
28
|
+
"description": "Rights: S=Select, I=Insert, U=Update, D=Delete, C=Clone, E=Execute. Entity objects take any of SIUDC; actions, stored procedures and reports take E."
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
31
|
},
|
package/src/dsl/builtins.ts
CHANGED
|
@@ -99,15 +99,15 @@ export const BUILTINS: Builtin[] = [
|
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
101
|
name: "getAccountBalance",
|
|
102
|
-
signature: "getAccountBalance(register, account, filter?, asOf?)",
|
|
102
|
+
signature: "getAccountBalance(register, account, filter?, asOf?, options?)",
|
|
103
103
|
returns: "object",
|
|
104
|
-
doc: "Balance of one account of an accounting register: `{debit, credit, net}` per resource. `filter` narrows by a dimension, or by an analytic kind the ACCOUNT declares — an analytic takes the key alone, or `{ entity, value }` to name one entity of a kind that is a union of several.",
|
|
104
|
+
doc: "Balance of one account of an accounting register: `{debit, credit, net}` per resource. `filter` narrows by a dimension, or by an analytic kind the ACCOUNT declares — an analytic takes the key alone, or `{ entity, value }` to name one entity of a kind that is a union of several. `options`: `groupBy` (a name or array — returns an array, a row per combination with a non-zero balance), `exclude: { entity, key }` (leaves out that document's postings) and `before` (a moment, exclusive, in place of `asOf`).",
|
|
105
105
|
},
|
|
106
106
|
{
|
|
107
107
|
name: "getAccountTurnover",
|
|
108
|
-
signature: "getAccountTurnover(register, account, filter, from, to)",
|
|
108
|
+
signature: "getAccountTurnover(register, account, filter, from, to, options?)",
|
|
109
109
|
returns: "object",
|
|
110
|
-
doc: "What moved through one account over an interval: `{debit, credit, net}` per resource. `filter` narrows as in `getAccountBalance` and may be `null`; `from` and `to` are both required.",
|
|
110
|
+
doc: "What moved through one account over an interval: `{debit, credit, net}` per resource. `filter` narrows as in `getAccountBalance` and may be `null`; `from` and `to` are both required. `options` takes `groupBy` and `exclude` as in `getAccountBalance`; a grouped turnover has a row per combination that moved.",
|
|
111
111
|
},
|
|
112
112
|
{
|
|
113
113
|
name: "getCorrespondence",
|
package/src/index.ts
CHANGED
|
@@ -48,18 +48,36 @@ function checkPosting(params: Raw, ledger: boolean, error: RegisterReporter): vo
|
|
|
48
48
|
if (hasKey(params, "groupLinesBy")) {
|
|
49
49
|
error(["groupLinesBy"], "register-group-lines", "'groupLinesBy' needs lines to group — name the 'lineEntity'.");
|
|
50
50
|
}
|
|
51
|
+
if (hasKey(params, "lineDateField")) {
|
|
52
|
+
error(["lineDateField"], "register-line-date", "'lineDateField' dates the lines a posting is made from — name the 'lineEntity', or date the document with 'dateField'.");
|
|
53
|
+
}
|
|
51
54
|
}
|
|
52
55
|
if (ledger) {
|
|
53
56
|
if (!lineEntity) {
|
|
54
57
|
error(["lineEntity"], "register-line", "An L column posts lines: 'lineEntity' and 'lineField' are required.");
|
|
55
58
|
}
|
|
56
|
-
for (const key of ["headerMap", "skipPostingWhenEmpty", "dateTrunc", "groupLinesBy"]) {
|
|
59
|
+
for (const key of ["headerMap", "skipPostingWhenEmpty", "dateTrunc", "groupLinesBy", "lineDateField"]) {
|
|
57
60
|
if (hasKey(params, key)) {
|
|
58
61
|
error([key], "register-ledger-only", `'${key}' is read by A columns only.`);
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
const dateField = safeTrim(params.dateField);
|
|
66
|
+
const lineDateField = safeTrim(params.lineDateField);
|
|
67
|
+
if (lineEntity && lineDateField && !ledger) {
|
|
68
|
+
if (!dateField) {
|
|
69
|
+
error(["dateField"], "register-line-date", "'lineDateField' falls back to the document's 'dateField' for a line with no date — name a 'dateField'.");
|
|
70
|
+
}
|
|
71
|
+
if (hasKey(params, "periodDimField")) {
|
|
72
|
+
error(["lineDateField"], "register-line-date", "'lineDateField' and 'periodDimField' cannot be combined: the period dimension is the document's one period, and a line dated in another month would post under it.");
|
|
73
|
+
}
|
|
74
|
+
if (!lockedLines.includes("*") && !lockedLines.includes(lineDateField)) {
|
|
75
|
+
error(["lockedLineFields"], "register-line-date", `'${lineDateField}' must be in 'lockedLineFields': a posted line re-dated would leave its movement under the old date.`);
|
|
76
|
+
}
|
|
77
|
+
if (Array.isArray(params.groupLinesBy) && !stringList(params.groupLinesBy).includes(lineDateField)) {
|
|
78
|
+
error(["groupLinesBy"], "register-line-date", `'${lineDateField}' must be in 'groupLinesBy': a group posts one movement, which has one date only when its lines share it.`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
63
81
|
if (hasKey(params, "dateTrunc") && !dateField) {
|
|
64
82
|
error(["dateTrunc"], "register-date", "'dateTrunc' needs a 'dateField'.");
|
|
65
83
|
}
|
package/src/registers/types.ts
CHANGED
|
@@ -119,6 +119,8 @@ export interface RegisterBindingParams {
|
|
|
119
119
|
lineEntity?: string;
|
|
120
120
|
lineField?: string;
|
|
121
121
|
groupLinesBy?: string[];
|
|
122
|
+
/** On a line posting: the line column dating that line's movement instead of `dateField`. */
|
|
123
|
+
lineDateField?: string;
|
|
122
124
|
/** Register dimension or resource name → column of the row read. */
|
|
123
125
|
map?: Record<string, RegisterMapValue>;
|
|
124
126
|
/** On a line posting: register name → DOCUMENT column. */
|