@dforge-core/metadata 0.0.45 → 0.0.46
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 +18 -0
- package/dist/index.d.ts +40 -1
- package/dist/index.js +127 -35
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schemas/registers.schema.json +8 -3
- package/src/index.ts +8 -0
- package/src/registers/check-declaration.ts +39 -34
- package/src/registers/columns.ts +114 -0
- package/src/registers/entities.ts +27 -6
- package/src/registers/index.ts +3 -1
- package/src/registers/read.ts +1 -0
- package/src/registers/types.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@ All notable changes to `@dforge-core/metadata` are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this package adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.0.46] — 2026-10-25
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Register column slots:** `REGISTER_COLUMN_SLOTS` lists every place a register declaration names a
|
|
13
|
+
stored column — the entity it lives on, what to call it, whether it is summed. `registerColumnSlot(path,
|
|
14
|
+
kind?)` resolves a path to its slot and `registerColumnHost(def, host)` names the entity a slot is on, so an
|
|
15
|
+
editor can offer the right entity's columns at a path. `checkRegister` reads columns through the same table.
|
|
16
|
+
- **`column-not-numeric` warning:** a column that a balance, turnover or accounting register adds up (its
|
|
17
|
+
resources, and its totals cache's) must store a scalar number. It runs when the `RegisterEntityLookup` returns
|
|
18
|
+
`datatypes` (a new optional `Map` of storage type per column) and skips a column whose type is unknown.
|
|
19
|
+
An information register's resources are returned, not summed, so they may be any type.
|
|
20
|
+
- `isNumericDatatype()` and the `RegisterHosts` type.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- **`unknown-column` message** for a register column quotes the entity: `... isn't a stored column of 'stock'.`
|
|
25
|
+
|
|
8
26
|
## [0.0.45] — 2026-10-24
|
|
9
27
|
|
|
10
28
|
### Added
|
package/dist/index.d.ts
CHANGED
|
@@ -1702,6 +1702,8 @@ interface RegisterDef {
|
|
|
1702
1702
|
debitField: string;
|
|
1703
1703
|
creditField: string;
|
|
1704
1704
|
}>;
|
|
1705
|
+
/** Accounting only: the posting column a loaded posting keeps its document's key in, so `exclude` can leave it out. */
|
|
1706
|
+
importedKeyField?: string;
|
|
1705
1707
|
resources: Record<string, RegisterResourceDef>;
|
|
1706
1708
|
totals?: RegisterTotalsDef;
|
|
1707
1709
|
/** `balance`: may the state go below zero (default false). */
|
|
@@ -1785,6 +1787,35 @@ interface RegisterIssue {
|
|
|
1785
1787
|
code: string;
|
|
1786
1788
|
}
|
|
1787
1789
|
|
|
1790
|
+
/** The entity a column slot is stored on. `carrier` is `entity` on a balance / information register, `journal` otherwise. */
|
|
1791
|
+
type RegisterColumnHost = "entity" | "journal" | "carrier" | "totals" | "composition";
|
|
1792
|
+
interface RegisterColumnSlot {
|
|
1793
|
+
/** Where in a declaration; `*` matches any key or index at that depth. */
|
|
1794
|
+
path: readonly string[];
|
|
1795
|
+
on: RegisterColumnHost;
|
|
1796
|
+
/** What the column is called in a message; `{0}`, `{1}`… stand for the `*` matches in order. */
|
|
1797
|
+
what: string;
|
|
1798
|
+
/**
|
|
1799
|
+
* The kinds of register for which the column is added up and so must store a
|
|
1800
|
+
* number. An information register's resources are what a slice returns — a
|
|
1801
|
+
* currency code as readily as a price — so it is not among them.
|
|
1802
|
+
*/
|
|
1803
|
+
numericFor?: readonly RegisterKind[];
|
|
1804
|
+
}
|
|
1805
|
+
declare const REGISTER_COLUMN_SLOTS: readonly RegisterColumnSlot[];
|
|
1806
|
+
interface ResolvedColumnSlot {
|
|
1807
|
+
on: RegisterColumnHost;
|
|
1808
|
+
/** The column's name for a message, with the path's keys filled in. */
|
|
1809
|
+
what: string;
|
|
1810
|
+
numeric: boolean;
|
|
1811
|
+
}
|
|
1812
|
+
/** The slot a path inside one declaration names a column at; nothing for a path that isn't a column. `kind` is the register's, which decides whether the column must be numeric. */
|
|
1813
|
+
declare function registerColumnSlot(path: IssuePath, kind?: RegisterKind): ResolvedColumnSlot | undefined;
|
|
1814
|
+
/** The code of the entity a slot's host names in this declaration. */
|
|
1815
|
+
declare function registerColumnHost(def: Raw, on: RegisterColumnHost): string | undefined;
|
|
1816
|
+
/** Whether a storage type (`numeric(18,2)`, `int8`) is a scalar number; the whole type must match, so `integer[]` is not. */
|
|
1817
|
+
declare const isNumericDatatype: (datatype: string) => boolean;
|
|
1818
|
+
|
|
1788
1819
|
/**
|
|
1789
1820
|
* What an entity a declaration names looks like, as far as the caller knows.
|
|
1790
1821
|
* `columns` is left undefined when they can't be listed (a dependency known
|
|
@@ -1792,6 +1823,8 @@ interface RegisterIssue {
|
|
|
1792
1823
|
*/
|
|
1793
1824
|
interface RegisterEntityInfo {
|
|
1794
1825
|
columns?: Iterable<string>;
|
|
1826
|
+
/** Storage type per column (`numeric(18,2)`); a column missing here has no numeric rule run on it. */
|
|
1827
|
+
datatypes?: ReadonlyMap<string, string>;
|
|
1795
1828
|
}
|
|
1796
1829
|
/**
|
|
1797
1830
|
* Resolves an entity code as `registers.json` writes it — bare for the module's
|
|
@@ -1800,6 +1833,12 @@ interface RegisterEntityInfo {
|
|
|
1800
1833
|
* the module is known and has no such entity.
|
|
1801
1834
|
*/
|
|
1802
1835
|
type RegisterEntityLookup = (code: string) => RegisterEntityInfo | null | undefined;
|
|
1836
|
+
type ResolvedRegisterEntity = RegisterEntityInfo | null | undefined;
|
|
1837
|
+
/** The entities of a declaration resolved so far, by the slot host that names them. */
|
|
1838
|
+
type RegisterHosts = Partial<Record<RegisterColumnHost, {
|
|
1839
|
+
entity: ResolvedRegisterEntity;
|
|
1840
|
+
code: string | undefined;
|
|
1841
|
+
}>>;
|
|
1803
1842
|
|
|
1804
1843
|
/**
|
|
1805
1844
|
* Check one declaration against the schema's per-kind rules and the installer's
|
|
@@ -2062,4 +2101,4 @@ declare const flagDefs: readonly {
|
|
|
2062
2101
|
label: string;
|
|
2063
2102
|
}[];
|
|
2064
2103
|
|
|
2065
|
-
export { AGG_TYPE_LIST, type AccumulationConfig, AggType, type AlignType, type AuditHistoryLevel, type BaseDatatypeCd, type CalendarViewConfig, type CardFieldDef, type CardFieldEntry, type CardLayout, type CardLayoutDef, type CardLayoutsFile, type CardSection, type CardSetConfig, type CardTabRef, type ColumnDef, type ColumnGroupSection, type ColumnTypeCd, type ColumnTypeDef, DEP_COLUMN_TYPES, type DataSource, type DataViewDef, type DataViewsFile, type Dataset, type DepColumn, type DepColumnMode, type DepColumnType, type DepEntity, type DepProvenance, type DepProvenanceKind, type DepsFile, type DeriveOptions, type DiagramDef, type DiagramDetail, type DiagramEntity, type DiagramFile, type DiagramRelation, type DiagramViewConfig, type DomainDef, type DomainsFile, type EntityDef, type EntityEvent, type EntityReaction, type EntityReference, type EntityViewColumnDef, type EntityViewDef, FILTER_GROUP_OPERATORS, FILTER_OPERATORS, type FieldDef, type FieldLink, type FieldOption, type FieldTypeCd, type FieldTypeDef, type Filter, type FilterCondition, type FilterGroup, type FilterGroupOperator, type FilterOperator, type FlagCd, type FolderDef, type FolderEntityBinding, type FoldersFile, type JobDef, type JobsFile, type KanbanViewConfig, type LedgerRegistryEntry, type ListLevelConfig, type ListViewConfig, type ManifestAuthor, type ManifestDef, type MenuDef, type MenuItemDef, type MenuItemType, type MenusFile, type ModuleDependency, type ModuleFeature, NUMERIC_TOTAL, type NamedKind, type NumberSequenceDef, type ParamDef, type ParamDefBase, type PeriodConfig, type PrintMargins, type PrintPageSettings, type PrintTemplateDef, type PrintTemplatesFile, REGISTER_CODE, REGISTER_COLUMN_NAME, REGISTER_KINDS, REGISTER_TOTALS_PERIODS, type RegisterBindingParams, type RegisterBindingTarget, type RegisterCompositionDef, type RegisterDef, type RegisterEntityInfo, type RegisterEntityLookup, type RegisterIssue, type RegisterKind, type RegisterMapValue, type RegisterResourceDef, type RegisterSource, type RegisterTotalsDef, type RegisterTotalsPeriod, type RegistersFile, type ReportDef, type ReportEntityAttachment, type ReportLayout, type ReportPanel, type ReportQuery, type ReportsFile, type RoleDef, type RolesFile, type SeedDataFile, type SeedDataRepeat, type SetSection, type SettingBaseDatatype, type SettingConstraintDef, type SettingDef, type SettingsFile, type SignConfig, type SortClause, type SpColumnDef, type SpParamDef, type StoredProcedureDef, type StoredProceduresFile, type TabGroupSection, type TraitCd, type TraitDef, type TraitFieldConflict, type TraitsFile, type TranslatedAction, type TranslatedEntity, type TranslatedMenu, type TranslatedOption, type TranslatedReport, type TranslatedWithOptions, type TranslationsFile, type TreeGridViewConfig, type TriggerDef, type TriggersFile, type ViewColumn, type ViewType, type VizType, type WebhookPayload, type WebhookSubscription, type WebhooksFile, baseDatatypes, baseToDbDatatype, chartTypes, checkRegister, checkRegisterBinding, checkRegisters, columnTypes, dataViewKinds, defaultParams, deriveBaseDatatype, deriveDbDatatype, expandTrait, expandTraits, fieldTypeCds, fieldTypes, fieldTypesByColumnType, flagDefs, getColumnType, getFieldType, getIdentityKeys, getLinks, getPrimaryKeys, getTrait, isFieldTypeCd, isPlainColumnRef, isRegisterKind, registerCarrier, registerDimensions, registerLineMode, registerRefusedProps, registerRequiredProps, registerResourceShape, registerResources, settingDefsOf, traitFieldConflicts, traits, vizTypes };
|
|
2104
|
+
export { AGG_TYPE_LIST, type AccumulationConfig, AggType, type AlignType, type AuditHistoryLevel, type BaseDatatypeCd, type CalendarViewConfig, type CardFieldDef, type CardFieldEntry, type CardLayout, type CardLayoutDef, type CardLayoutsFile, type CardSection, type CardSetConfig, type CardTabRef, type ColumnDef, type ColumnGroupSection, type ColumnTypeCd, type ColumnTypeDef, DEP_COLUMN_TYPES, type DataSource, type DataViewDef, type DataViewsFile, type Dataset, type DepColumn, type DepColumnMode, type DepColumnType, type DepEntity, type DepProvenance, type DepProvenanceKind, type DepsFile, type DeriveOptions, type DiagramDef, type DiagramDetail, type DiagramEntity, type DiagramFile, type DiagramRelation, type DiagramViewConfig, type DomainDef, type DomainsFile, type EntityDef, type EntityEvent, type EntityReaction, type EntityReference, type EntityViewColumnDef, type EntityViewDef, FILTER_GROUP_OPERATORS, FILTER_OPERATORS, type FieldDef, type FieldLink, type FieldOption, type FieldTypeCd, type FieldTypeDef, type Filter, type FilterCondition, type FilterGroup, type FilterGroupOperator, type FilterOperator, type FlagCd, type FolderDef, type FolderEntityBinding, type FoldersFile, type JobDef, type JobsFile, type KanbanViewConfig, type LedgerRegistryEntry, type ListLevelConfig, type ListViewConfig, type ManifestAuthor, type ManifestDef, type MenuDef, type MenuItemDef, type MenuItemType, type MenusFile, type ModuleDependency, type ModuleFeature, NUMERIC_TOTAL, type NamedKind, type NumberSequenceDef, type ParamDef, type ParamDefBase, type PeriodConfig, type PrintMargins, type PrintPageSettings, type PrintTemplateDef, type PrintTemplatesFile, REGISTER_CODE, REGISTER_COLUMN_NAME, REGISTER_COLUMN_SLOTS, REGISTER_KINDS, REGISTER_TOTALS_PERIODS, type RegisterBindingParams, type RegisterBindingTarget, type RegisterColumnHost, type RegisterColumnSlot, type RegisterCompositionDef, type RegisterDef, type RegisterEntityInfo, type RegisterEntityLookup, type RegisterHosts, type RegisterIssue, type RegisterKind, type RegisterMapValue, type RegisterResourceDef, type RegisterSource, type RegisterTotalsDef, type RegisterTotalsPeriod, type RegistersFile, type ReportDef, type ReportEntityAttachment, type ReportLayout, type ReportPanel, type ReportQuery, type ReportsFile, type ResolvedColumnSlot, type RoleDef, type RolesFile, type SeedDataFile, type SeedDataRepeat, type SetSection, type SettingBaseDatatype, type SettingConstraintDef, type SettingDef, type SettingsFile, type SignConfig, type SortClause, type SpColumnDef, type SpParamDef, type StoredProcedureDef, type StoredProceduresFile, type TabGroupSection, type TraitCd, type TraitDef, type TraitFieldConflict, type TraitsFile, type TranslatedAction, type TranslatedEntity, type TranslatedMenu, type TranslatedOption, type TranslatedReport, type TranslatedWithOptions, type TranslationsFile, type TreeGridViewConfig, type TriggerDef, type TriggersFile, type ViewColumn, type ViewType, type VizType, type WebhookPayload, type WebhookSubscription, type WebhooksFile, baseDatatypes, baseToDbDatatype, chartTypes, checkRegister, checkRegisterBinding, checkRegisters, columnTypes, dataViewKinds, defaultParams, deriveBaseDatatype, deriveDbDatatype, expandTrait, expandTraits, fieldTypeCds, fieldTypes, fieldTypesByColumnType, flagDefs, getColumnType, getFieldType, getIdentityKeys, getLinks, getPrimaryKeys, getTrait, isFieldTypeCd, isNumericDatatype, isPlainColumnRef, isRegisterKind, registerCarrier, registerColumnHost, registerColumnSlot, registerDimensions, registerLineMode, registerRefusedProps, registerRequiredProps, registerResourceShape, registerResources, settingDefsOf, traitFieldConflicts, traits, vizTypes };
|
package/dist/index.js
CHANGED
|
@@ -469,7 +469,8 @@ var ACCOUNTING_PROPERTIES = [
|
|
|
469
469
|
"creditAccount",
|
|
470
470
|
"debitAnalyticFields",
|
|
471
471
|
"creditAnalyticFields",
|
|
472
|
-
"sideDimensions"
|
|
472
|
+
"sideDimensions",
|
|
473
|
+
"importedKeyField"
|
|
473
474
|
];
|
|
474
475
|
var BALANCE_PROPERTIES = ["allowNegative", "autoCreate"];
|
|
475
476
|
var REFUSED = {
|
|
@@ -493,8 +494,79 @@ var KIND_LABEL = {
|
|
|
493
494
|
accounting: { sentence: "An accounting register", inline: "an accounting register" }
|
|
494
495
|
};
|
|
495
496
|
|
|
497
|
+
// src/registers/columns.ts
|
|
498
|
+
var SUMMED = ["balance", "turnover", "accounting"];
|
|
499
|
+
var slot = (path, on, what, summed = false) => ({
|
|
500
|
+
path: path.split("."),
|
|
501
|
+
on,
|
|
502
|
+
what,
|
|
503
|
+
...summed ? { numericFor: SUMMED } : {}
|
|
504
|
+
});
|
|
505
|
+
var REGISTER_COLUMN_SLOTS = [
|
|
506
|
+
slot("journalDirection", "journal", "Journal direction column"),
|
|
507
|
+
slot("journalDate", "journal", "Journal date column"),
|
|
508
|
+
slot("period", "entity", "Period column"),
|
|
509
|
+
slot("recorder", "entity", "Recorder column"),
|
|
510
|
+
slot("dimensions.*", "carrier", "Dimension"),
|
|
511
|
+
slot("resources.*.field", "carrier", "Resource '{0}' column", true),
|
|
512
|
+
slot("resources.*.debitField", "carrier", "Resource '{0}' column", true),
|
|
513
|
+
slot("resources.*.creditField", "carrier", "Resource '{0}' column", true),
|
|
514
|
+
slot("account", "journal", "Account column"),
|
|
515
|
+
slot("debitAccount", "journal", "Debit account column"),
|
|
516
|
+
slot("creditAccount", "journal", "Credit account column"),
|
|
517
|
+
slot("analyticFields.*", "journal", "Analytic slot column"),
|
|
518
|
+
slot("debitAnalyticFields.*", "journal", "Analytic slot column"),
|
|
519
|
+
slot("creditAnalyticFields.*", "journal", "Analytic slot column"),
|
|
520
|
+
slot("sideDimensions.*.debitField", "journal", "Dimension '{0}' debit column"),
|
|
521
|
+
slot("sideDimensions.*.creditField", "journal", "Dimension '{0}' credit column"),
|
|
522
|
+
slot("composition.account", "composition", "Composition 'account' column"),
|
|
523
|
+
slot("composition.slot", "composition", "Composition 'slot' column"),
|
|
524
|
+
slot("composition.analytic", "composition", "Composition 'analytic' column"),
|
|
525
|
+
slot("totals.periodField", "totals", "Totals period column"),
|
|
526
|
+
slot("totals.accountField", "totals", "Totals account column"),
|
|
527
|
+
slot("totals.resources.*.inField", "totals", "Totals '{0}' inField", true),
|
|
528
|
+
slot("totals.resources.*.outField", "totals", "Totals '{0}' outField", true),
|
|
529
|
+
slot("totals.resources.*.debitField", "totals", "Totals '{0}' debitField", true),
|
|
530
|
+
slot("totals.resources.*.creditField", "totals", "Totals '{0}' creditField", true)
|
|
531
|
+
];
|
|
532
|
+
function registerColumnSlot(path, kind) {
|
|
533
|
+
for (const candidate of REGISTER_COLUMN_SLOTS) {
|
|
534
|
+
if (candidate.path.length !== path.length) {
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
const captured = [];
|
|
538
|
+
const matches = candidate.path.every((segment, i) => {
|
|
539
|
+
if (segment !== "*") {
|
|
540
|
+
return segment === path[i];
|
|
541
|
+
}
|
|
542
|
+
captured.push(String(path[i]));
|
|
543
|
+
return true;
|
|
544
|
+
});
|
|
545
|
+
if (matches) {
|
|
546
|
+
return { on: candidate.on, what: candidate.what.replace(/\{(\d+)\}/g, (_, n) => captured[Number(n)] ?? ""), numeric: !!kind && !!candidate.numericFor?.includes(kind) };
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return void 0;
|
|
550
|
+
}
|
|
551
|
+
function registerColumnHost(def, on) {
|
|
552
|
+
switch (on) {
|
|
553
|
+
case "entity":
|
|
554
|
+
return safeTrim(def.entity);
|
|
555
|
+
case "journal":
|
|
556
|
+
return safeTrim(def.journal);
|
|
557
|
+
case "carrier":
|
|
558
|
+
return registerCarrier(def);
|
|
559
|
+
case "totals":
|
|
560
|
+
return isRecord(def.totals) ? safeTrim(def.totals.entity) : void 0;
|
|
561
|
+
case "composition":
|
|
562
|
+
return isRecord(def.composition) ? safeTrim(def.composition.entity) : void 0;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
var NUMERIC_DATATYPE = /^(?:(?:numeric|decimal)(?:\s*\(\s*\d+(?:\s*,\s*\d+)?\s*\))?|int[248]?|integer|bigint|smallint|(?:big|small)?serial[248]?|float[48]?|double precision|real|money)$/i;
|
|
566
|
+
var isNumericDatatype = (datatype) => NUMERIC_DATATYPE.test(datatype.trim());
|
|
567
|
+
|
|
496
568
|
// src/registers/entities.ts
|
|
497
|
-
function createEntityChecks(lookup, error, warning) {
|
|
569
|
+
function createEntityChecks(lookup, error, warning, hosts, kind) {
|
|
498
570
|
const resolved = /* @__PURE__ */ new Map();
|
|
499
571
|
const columnSets = /* @__PURE__ */ new WeakMap();
|
|
500
572
|
const entityAt = (path, code, what) => {
|
|
@@ -519,17 +591,27 @@ function createEntityChecks(lookup, error, warning) {
|
|
|
519
591
|
}
|
|
520
592
|
return columnSets.get(entity) ?? null;
|
|
521
593
|
};
|
|
522
|
-
const columnAt = (path,
|
|
594
|
+
const columnAt = (path, column) => {
|
|
523
595
|
if (typeof column !== "string" || !column.trim()) {
|
|
524
596
|
return;
|
|
525
597
|
}
|
|
598
|
+
const slot2 = registerColumnSlot(path, kind);
|
|
599
|
+
if (!slot2) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
526
602
|
if (!REGISTER_COLUMN_NAME.test(column)) {
|
|
527
|
-
error(path, "column-name", `${what} '${column}' isn't a column name (lowercase letters, digits and '_').`);
|
|
603
|
+
error(path, "column-name", `${slot2.what} '${column}' isn't a column name (lowercase letters, digits and '_').`);
|
|
528
604
|
return;
|
|
529
605
|
}
|
|
530
|
-
const
|
|
606
|
+
const host = hosts[slot2.on];
|
|
607
|
+
const columns = columnsOf(host?.entity);
|
|
531
608
|
if (columns && !columns.has(column)) {
|
|
532
|
-
warning(path, "unknown-column", `${what} '${column}' isn't a stored column of ${
|
|
609
|
+
warning(path, "unknown-column", `${slot2.what} '${column}' isn't a stored column of ${host?.code ? `'${host.code}'` : `the ${slot2.on} entity`}.`);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
const datatype = host?.entity?.datatypes?.get(column);
|
|
613
|
+
if (slot2.numeric && datatype && !isNumericDatatype(datatype)) {
|
|
614
|
+
warning(path, "column-not-numeric", `${slot2.what} '${column}' is ${datatype}, not a number.`);
|
|
533
615
|
}
|
|
534
616
|
};
|
|
535
617
|
return { entityAt, columnAt };
|
|
@@ -576,7 +658,7 @@ function checkProperties({ def, kind, error }) {
|
|
|
576
658
|
error(["recordChangesOnly"], "record-changes-only", "'recordChangesOnly' needs a 'recorder' \u2014 only a document's record can be compared with the slice in effect.");
|
|
577
659
|
}
|
|
578
660
|
}
|
|
579
|
-
function resolveEntities(def, kind, checks) {
|
|
661
|
+
function resolveEntities(def, kind, checks, hosts) {
|
|
580
662
|
const { entityAt, columnAt } = checks;
|
|
581
663
|
const entityCode = safeTrim(def.entity);
|
|
582
664
|
const journalCode = safeTrim(def.journal);
|
|
@@ -586,13 +668,17 @@ function resolveEntities(def, kind, checks) {
|
|
|
586
668
|
if (kind === "accounting") {
|
|
587
669
|
entityAt(["chart"], safeTrim(def.chart), "Chart");
|
|
588
670
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
671
|
+
const carrier = carriedByEntity ? entity : journal;
|
|
672
|
+
hosts.entity = { entity, code: entityCode };
|
|
673
|
+
hosts.journal = { entity: journal, code: journalCode };
|
|
674
|
+
hosts.carrier = { entity: carrier, code: registerCarrier(def) };
|
|
675
|
+
columnAt(["journalDirection"], def.journalDirection);
|
|
676
|
+
columnAt(["journalDate"], def.journalDate);
|
|
677
|
+
columnAt(["period"], def.period);
|
|
678
|
+
columnAt(["recorder"], def.recorder);
|
|
679
|
+
return { journalCode };
|
|
594
680
|
}
|
|
595
|
-
function checkDimensions({ def, dimensions, sides,
|
|
681
|
+
function checkDimensions({ def, dimensions, sides, columnAt, error }) {
|
|
596
682
|
if (!Array.isArray(def.dimensions) || dimensions.length === 0) {
|
|
597
683
|
error(["dimensions"], "dimensions", "A register needs at least one dimension.");
|
|
598
684
|
}
|
|
@@ -605,7 +691,7 @@ function checkDimensions({ def, dimensions, sides, carrier, carrierCode, columnA
|
|
|
605
691
|
}
|
|
606
692
|
dimensions.forEach((name, i) => {
|
|
607
693
|
if (!(name in sides)) {
|
|
608
|
-
columnAt(["dimensions", i],
|
|
694
|
+
columnAt(["dimensions", i], name);
|
|
609
695
|
}
|
|
610
696
|
});
|
|
611
697
|
}
|
|
@@ -635,7 +721,7 @@ function checkResourceShape(ctx, name, resource, path) {
|
|
|
635
721
|
}
|
|
636
722
|
}
|
|
637
723
|
function checkResources(ctx) {
|
|
638
|
-
const { def, kind, resources, dimensions,
|
|
724
|
+
const { def, kind, resources, dimensions, columnAt, error } = ctx;
|
|
639
725
|
if (!isRecord(def.resources) || resources.length === 0) {
|
|
640
726
|
error(["resources"], "resources", "A register needs at least one resource.");
|
|
641
727
|
}
|
|
@@ -655,7 +741,7 @@ function checkResources(ctx) {
|
|
|
655
741
|
if (!column) {
|
|
656
742
|
continue;
|
|
657
743
|
}
|
|
658
|
-
columnAt(path(key),
|
|
744
|
+
columnAt(path(key), column);
|
|
659
745
|
const other = storage.get(column);
|
|
660
746
|
if (other && other !== name) {
|
|
661
747
|
error(path(key), "resource-column-shared", `Resources '${other}' and '${name}' both store into '${column}' \u2014 posting would add both into one column.`);
|
|
@@ -672,10 +758,10 @@ function checkResources(ctx) {
|
|
|
672
758
|
}
|
|
673
759
|
}
|
|
674
760
|
function checkAnalyticSlotColumns(ctx, key, columns) {
|
|
675
|
-
columns.forEach((column, i) => ctx.columnAt([key, i],
|
|
761
|
+
columns.forEach((column, i) => ctx.columnAt([key, i], column));
|
|
676
762
|
}
|
|
677
763
|
function checkSideDimensions(ctx) {
|
|
678
|
-
const { dimensions, sides,
|
|
764
|
+
const { dimensions, sides, columnAt, error } = ctx;
|
|
679
765
|
for (const [name, side] of Object.entries(sides)) {
|
|
680
766
|
const base = ["sideDimensions", name];
|
|
681
767
|
if (!dimensions.includes(name)) {
|
|
@@ -690,12 +776,12 @@ function checkSideDimensions(ctx) {
|
|
|
690
776
|
same: [...base, "creditField"],
|
|
691
777
|
sameMessage: `Two-sided dimension '${name}': the two columns must differ.`
|
|
692
778
|
});
|
|
693
|
-
columnAt([...base, "debitField"],
|
|
694
|
-
columnAt([...base, "creditField"],
|
|
779
|
+
columnAt([...base, "debitField"], debitField);
|
|
780
|
+
columnAt([...base, "creditField"], creditField);
|
|
695
781
|
}
|
|
696
782
|
}
|
|
697
783
|
function checkCompoundAccounts(ctx) {
|
|
698
|
-
const { def,
|
|
784
|
+
const { def, columnAt, error } = ctx;
|
|
699
785
|
refuseKeys(def, ["account", "analyticFields"], error, "line-mode", (key) => `'${key}' belongs to a classic register; a compound one names 'debitAccount' and 'creditAccount'.`);
|
|
700
786
|
const debit = safeTrim(def.debitAccount);
|
|
701
787
|
const credit = safeTrim(def.creditAccount);
|
|
@@ -705,8 +791,8 @@ function checkCompoundAccounts(ctx) {
|
|
|
705
791
|
same: ["creditAccount"],
|
|
706
792
|
sameMessage: "'debitAccount' and 'creditAccount' must differ \u2014 one column would record no correspondence."
|
|
707
793
|
});
|
|
708
|
-
columnAt(["debitAccount"],
|
|
709
|
-
columnAt(["creditAccount"],
|
|
794
|
+
columnAt(["debitAccount"], debit);
|
|
795
|
+
columnAt(["creditAccount"], credit);
|
|
710
796
|
const debitSlots = stringList(def.debitAnalyticFields);
|
|
711
797
|
const creditSlots = stringList(def.creditAnalyticFields);
|
|
712
798
|
if (debitSlots.length > MAX_ANALYTIC_SLOTS || creditSlots.length > MAX_ANALYTIC_SLOTS) {
|
|
@@ -723,12 +809,12 @@ function checkCompoundAccounts(ctx) {
|
|
|
723
809
|
return needsComposition;
|
|
724
810
|
}
|
|
725
811
|
function checkClassicAccounts(ctx) {
|
|
726
|
-
const { def,
|
|
812
|
+
const { def, columnAt, error } = ctx;
|
|
727
813
|
refuseKeys(def, ["debitAccount", "creditAccount", "debitAnalyticFields", "creditAnalyticFields", "sideDimensions"], error, "line-mode", (key) => `'${key}' belongs to a compound register.`);
|
|
728
814
|
if (!safeTrim(def.account)) {
|
|
729
815
|
error(["account"], "accounts", "A classic register names the posting's account column ('account').");
|
|
730
816
|
}
|
|
731
|
-
columnAt(["account"],
|
|
817
|
+
columnAt(["account"], safeTrim(def.account));
|
|
732
818
|
const slots = stringList(def.analyticFields);
|
|
733
819
|
if (slots.length > MAX_ANALYTIC_SLOTS) {
|
|
734
820
|
error(["analyticFields"], "analytic-slots", `At most ${MAX_ANALYTIC_SLOTS} analytic slots.`);
|
|
@@ -737,7 +823,7 @@ function checkClassicAccounts(ctx) {
|
|
|
737
823
|
return slots.length > 0;
|
|
738
824
|
}
|
|
739
825
|
function checkComposition(ctx, needsComposition) {
|
|
740
|
-
const { def, entityAt, columnAt, error } = ctx;
|
|
826
|
+
const { def, entityAt, columnAt, hosts, error } = ctx;
|
|
741
827
|
const composition = isRecord(def.composition) ? def.composition : void 0;
|
|
742
828
|
if (needsComposition && !composition) {
|
|
743
829
|
error(["composition"], "composition", "Analytic slots need a 'composition' \u2014 where the chart says which analytic each account takes in which slot.");
|
|
@@ -754,9 +840,9 @@ function checkComposition(ctx, needsComposition) {
|
|
|
754
840
|
}
|
|
755
841
|
}
|
|
756
842
|
const code = safeTrim(composition.entity);
|
|
757
|
-
|
|
843
|
+
hosts.composition = { entity: entityAt(["composition", "entity"], code, "Composition entity"), code };
|
|
758
844
|
for (const key of ["account", "slot", "analytic"]) {
|
|
759
|
-
columnAt(["composition", key],
|
|
845
|
+
columnAt(["composition", key], composition[key]);
|
|
760
846
|
}
|
|
761
847
|
}
|
|
762
848
|
function checkAccounting(ctx) {
|
|
@@ -771,7 +857,7 @@ function checkAccounting(ctx) {
|
|
|
771
857
|
checkComposition(ctx, mode === "compound" ? checkCompoundAccounts(ctx) : checkClassicAccounts(ctx));
|
|
772
858
|
}
|
|
773
859
|
function checkTotals(ctx) {
|
|
774
|
-
const { def, kind, dimensions, resources, entityAt, columnAt, error } = ctx;
|
|
860
|
+
const { def, kind, dimensions, resources, entityAt, columnAt, hosts, error } = ctx;
|
|
775
861
|
const totals = isRecord(def.totals) ? def.totals : void 0;
|
|
776
862
|
if (!totals || kind === "info") {
|
|
777
863
|
return;
|
|
@@ -780,7 +866,7 @@ function checkTotals(ctx) {
|
|
|
780
866
|
if (!totalsCode) {
|
|
781
867
|
error(["totals", "entity"], "totals", "The totals cache needs its entity.");
|
|
782
868
|
}
|
|
783
|
-
|
|
869
|
+
hosts.totals = { entity: entityAt(["totals", "entity"], totalsCode, "Totals entity"), code: totalsCode };
|
|
784
870
|
const period = safeTrim(totals.period);
|
|
785
871
|
if (!period || !REGISTER_TOTALS_PERIODS.includes(period)) {
|
|
786
872
|
error(["totals", "period"], "totals", "The totals cache needs a period: day, month, quarter or year.");
|
|
@@ -788,12 +874,12 @@ function checkTotals(ctx) {
|
|
|
788
874
|
if (!safeTrim(totals.periodField)) {
|
|
789
875
|
error(["totals", "periodField"], "totals", "The totals cache needs 'periodField' \u2014 the column holding each period's first date.");
|
|
790
876
|
}
|
|
791
|
-
columnAt(["totals", "periodField"],
|
|
877
|
+
columnAt(["totals", "periodField"], safeTrim(totals.periodField));
|
|
792
878
|
if (kind === "accounting") {
|
|
793
879
|
if (!safeTrim(totals.accountField)) {
|
|
794
880
|
error(["totals", "accountField"], "totals", "An accounting register's cache needs 'accountField'.");
|
|
795
881
|
}
|
|
796
|
-
columnAt(["totals", "accountField"],
|
|
882
|
+
columnAt(["totals", "accountField"], safeTrim(totals.accountField));
|
|
797
883
|
stringList(totals.dimensions).forEach((name, i) => {
|
|
798
884
|
if (!dimensions.includes(name)) {
|
|
799
885
|
error(["totals", "dimensions", i], "totals", `Totals dimension '${name}' isn't a dimension of the register.`);
|
|
@@ -819,7 +905,7 @@ function checkTotals(ctx) {
|
|
|
819
905
|
if (!safeTrim(entry[key])) {
|
|
820
906
|
error(["totals", "resources", name], "totals", `Totals resource '${name}' needs '${key}'.`);
|
|
821
907
|
}
|
|
822
|
-
columnAt(["totals", "resources", name, key],
|
|
908
|
+
columnAt(["totals", "resources", name, key], safeTrim(entry[key]));
|
|
823
909
|
}
|
|
824
910
|
}
|
|
825
911
|
for (const name of Object.keys(cached)) {
|
|
@@ -855,14 +941,16 @@ function checkRegister(raw, lookup) {
|
|
|
855
941
|
}
|
|
856
942
|
const kind = def.kind;
|
|
857
943
|
checkProperties({ def, kind, error });
|
|
858
|
-
const
|
|
944
|
+
const hosts = {};
|
|
945
|
+
const checks = createEntityChecks(lookup, error, warning, hosts, kind);
|
|
859
946
|
const ctx = {
|
|
860
947
|
def,
|
|
861
948
|
kind,
|
|
862
949
|
error,
|
|
863
950
|
warning,
|
|
951
|
+
hosts,
|
|
864
952
|
...checks,
|
|
865
|
-
...resolveEntities(def, kind, checks),
|
|
953
|
+
...resolveEntities(def, kind, checks, hosts),
|
|
866
954
|
dimensions: registerDimensions(def),
|
|
867
955
|
sides: isRecord(def.sideDimensions) ? def.sideDimensions : {},
|
|
868
956
|
resources: registerResources(def)
|
|
@@ -1116,6 +1204,7 @@ export {
|
|
|
1116
1204
|
NUMERIC_TOTAL,
|
|
1117
1205
|
REGISTER_CODE,
|
|
1118
1206
|
REGISTER_COLUMN_NAME,
|
|
1207
|
+
REGISTER_COLUMN_SLOTS,
|
|
1119
1208
|
REGISTER_KINDS,
|
|
1120
1209
|
REGISTER_TOTALS_PERIODS,
|
|
1121
1210
|
baseDatatypes,
|
|
@@ -1142,9 +1231,12 @@ export {
|
|
|
1142
1231
|
getPrimaryKeys,
|
|
1143
1232
|
getTrait,
|
|
1144
1233
|
isFieldTypeCd,
|
|
1234
|
+
isNumericDatatype,
|
|
1145
1235
|
isPlainColumnRef,
|
|
1146
1236
|
isRegisterKind,
|
|
1147
1237
|
registerCarrier,
|
|
1238
|
+
registerColumnHost,
|
|
1239
|
+
registerColumnSlot,
|
|
1148
1240
|
registerDimensions,
|
|
1149
1241
|
registerLineMode,
|
|
1150
1242
|
registerRefusedProps,
|