@dforge-core/metadata 0.0.44 → 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 +32 -0
- package/dist/{actions-DvYTrCbP.d.ts → actions-D-0G_mqf.d.ts} +19 -1
- package/dist/dsl/index.d.ts +1 -1
- package/dist/dsl/index.js +13 -1
- package/dist/dsl/index.js.map +1 -1
- package/dist/index.d.ts +51 -3
- package/dist/index.js +130 -36
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schemas/entity.schema.json +1 -1
- package/schemas/registers.schema.json +8 -3
- package/src/actions.ts +19 -0
- package/src/derive.ts +1 -0
- package/src/dsl/builtins.ts +13 -1
- package/src/field-types.ts +1 -0
- package/src/index.ts +9 -1
- 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/src/types.ts +1 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { a as ActionDef, A as ActionExecutionMode, b as ActionsFile } from './actions-
|
|
1
|
+
export { a as ActionDef, A as ActionExecutionMode, b as ActionParamSetDef, c as ActionsFile } from './actions-D-0G_mqf.js';
|
|
2
2
|
|
|
3
3
|
/** A `{ cd, name }` pair for friendly pickers (view kinds, viz types, …). */
|
|
4
4
|
interface NamedKind {
|
|
@@ -16,7 +16,7 @@ type BaseDatatypeCd = "binary" | "bool" | "culture" | "cuid" | "date" | "guid" |
|
|
|
16
16
|
* isNullable:false at install). */
|
|
17
17
|
type FlagCd = "V" | "I" | "E" | "M";
|
|
18
18
|
/** Known field type codes from the field_type seed. */
|
|
19
|
-
type FieldTypeCd = "text" | "email" | "phone" | "url" | "textarea" | "number" | "currency" | "percent" | "checkbox" | "date" | "datetime" | "time" | "dropdown" | "flags" | "json" | "hidden" | "color" | "image" | "file" | "markdown" | "richtext" | "avatar" | "lookup" | "user" | "grid" | "list" | "tags" | "entitylink" | "code";
|
|
19
|
+
type FieldTypeCd = "text" | "email" | "phone" | "url" | "textarea" | "number" | "currency" | "percent" | "filesize" | "checkbox" | "date" | "datetime" | "time" | "dropdown" | "flags" | "json" | "hidden" | "color" | "image" | "file" | "markdown" | "richtext" | "avatar" | "lookup" | "user" | "grid" | "list" | "tags" | "entitylink" | "code";
|
|
20
20
|
/**
|
|
21
21
|
* A UI field type from the `field_type` table. The author picks one of these;
|
|
22
22
|
* the physical `dbDatatype` and `baseDatatypeCd` are derived from it.
|
|
@@ -248,6 +248,15 @@ declare const fieldTypes: readonly [{
|
|
|
248
248
|
readonly maxLen: null;
|
|
249
249
|
readonly precision: 2;
|
|
250
250
|
readonly useStringInFilter: false;
|
|
251
|
+
}, {
|
|
252
|
+
readonly cd: "filesize";
|
|
253
|
+
readonly name: "File Size";
|
|
254
|
+
readonly columnType: "D";
|
|
255
|
+
readonly baseDatatypeCd: "number";
|
|
256
|
+
readonly supportedAgg: "SCAM";
|
|
257
|
+
readonly maxLen: null;
|
|
258
|
+
readonly precision: null;
|
|
259
|
+
readonly useStringInFilter: false;
|
|
251
260
|
}, {
|
|
252
261
|
readonly cd: "checkbox";
|
|
253
262
|
readonly name: "Checkbox";
|
|
@@ -1693,6 +1702,8 @@ interface RegisterDef {
|
|
|
1693
1702
|
debitField: string;
|
|
1694
1703
|
creditField: string;
|
|
1695
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;
|
|
1696
1707
|
resources: Record<string, RegisterResourceDef>;
|
|
1697
1708
|
totals?: RegisterTotalsDef;
|
|
1698
1709
|
/** `balance`: may the state go below zero (default false). */
|
|
@@ -1776,6 +1787,35 @@ interface RegisterIssue {
|
|
|
1776
1787
|
code: string;
|
|
1777
1788
|
}
|
|
1778
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
|
+
|
|
1779
1819
|
/**
|
|
1780
1820
|
* What an entity a declaration names looks like, as far as the caller knows.
|
|
1781
1821
|
* `columns` is left undefined when they can't be listed (a dependency known
|
|
@@ -1783,6 +1823,8 @@ interface RegisterIssue {
|
|
|
1783
1823
|
*/
|
|
1784
1824
|
interface RegisterEntityInfo {
|
|
1785
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>;
|
|
1786
1828
|
}
|
|
1787
1829
|
/**
|
|
1788
1830
|
* Resolves an entity code as `registers.json` writes it — bare for the module's
|
|
@@ -1791,6 +1833,12 @@ interface RegisterEntityInfo {
|
|
|
1791
1833
|
* the module is known and has no such entity.
|
|
1792
1834
|
*/
|
|
1793
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
|
+
}>>;
|
|
1794
1842
|
|
|
1795
1843
|
/**
|
|
1796
1844
|
* Check one declaration against the schema's per-kind rules and the installer's
|
|
@@ -2053,4 +2101,4 @@ declare const flagDefs: readonly {
|
|
|
2053
2101
|
label: string;
|
|
2054
2102
|
}[];
|
|
2055
2103
|
|
|
2056
|
-
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
|
@@ -20,6 +20,7 @@ var fieldTypes = [
|
|
|
20
20
|
{ cd: "number", name: "Number", columnType: "D", baseDatatypeCd: "number", supportedAgg: "SCAM", maxLen: null, precision: null, useStringInFilter: false },
|
|
21
21
|
{ cd: "currency", name: "Currency", columnType: "D", baseDatatypeCd: "number", supportedAgg: "SCAM", maxLen: null, precision: 2, useStringInFilter: false },
|
|
22
22
|
{ cd: "percent", name: "Percent", columnType: "D", baseDatatypeCd: "number", supportedAgg: "CAM", maxLen: null, precision: 2, useStringInFilter: false },
|
|
23
|
+
{ cd: "filesize", name: "File Size", columnType: "D", baseDatatypeCd: "number", supportedAgg: "SCAM", maxLen: null, precision: null, useStringInFilter: false },
|
|
23
24
|
{ cd: "checkbox", name: "Checkbox", columnType: "D", baseDatatypeCd: "bool", supportedAgg: "C", maxLen: null, precision: null, useStringInFilter: false },
|
|
24
25
|
{ cd: "date", name: "Date", columnType: "D", baseDatatypeCd: "date", supportedAgg: "CM", maxLen: null, precision: null, useStringInFilter: false },
|
|
25
26
|
{ cd: "datetime", name: "Date & Time", columnType: "D", baseDatatypeCd: "timestamp", supportedAgg: "CM", maxLen: null, precision: null, useStringInFilter: false },
|
|
@@ -256,7 +257,8 @@ var NUMERIC_TOTAL = 18;
|
|
|
256
257
|
var TEXT_BACKED = /* @__PURE__ */ new Set(["textarea", "markdown", "richtext", "code"]);
|
|
257
258
|
var DB_DATATYPE_OVERRIDE = {
|
|
258
259
|
file: "jsonb",
|
|
259
|
-
image: "jsonb"
|
|
260
|
+
image: "jsonb",
|
|
261
|
+
filesize: "int8"
|
|
260
262
|
};
|
|
261
263
|
function deriveDbDatatype(fieldTypeCd, options = {}) {
|
|
262
264
|
const ft = getFieldType(fieldTypeCd);
|
|
@@ -467,7 +469,8 @@ var ACCOUNTING_PROPERTIES = [
|
|
|
467
469
|
"creditAccount",
|
|
468
470
|
"debitAnalyticFields",
|
|
469
471
|
"creditAnalyticFields",
|
|
470
|
-
"sideDimensions"
|
|
472
|
+
"sideDimensions",
|
|
473
|
+
"importedKeyField"
|
|
471
474
|
];
|
|
472
475
|
var BALANCE_PROPERTIES = ["allowNegative", "autoCreate"];
|
|
473
476
|
var REFUSED = {
|
|
@@ -491,8 +494,79 @@ var KIND_LABEL = {
|
|
|
491
494
|
accounting: { sentence: "An accounting register", inline: "an accounting register" }
|
|
492
495
|
};
|
|
493
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
|
+
|
|
494
568
|
// src/registers/entities.ts
|
|
495
|
-
function createEntityChecks(lookup, error, warning) {
|
|
569
|
+
function createEntityChecks(lookup, error, warning, hosts, kind) {
|
|
496
570
|
const resolved = /* @__PURE__ */ new Map();
|
|
497
571
|
const columnSets = /* @__PURE__ */ new WeakMap();
|
|
498
572
|
const entityAt = (path, code, what) => {
|
|
@@ -517,17 +591,27 @@ function createEntityChecks(lookup, error, warning) {
|
|
|
517
591
|
}
|
|
518
592
|
return columnSets.get(entity) ?? null;
|
|
519
593
|
};
|
|
520
|
-
const columnAt = (path,
|
|
594
|
+
const columnAt = (path, column) => {
|
|
521
595
|
if (typeof column !== "string" || !column.trim()) {
|
|
522
596
|
return;
|
|
523
597
|
}
|
|
598
|
+
const slot2 = registerColumnSlot(path, kind);
|
|
599
|
+
if (!slot2) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
524
602
|
if (!REGISTER_COLUMN_NAME.test(column)) {
|
|
525
|
-
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 '_').`);
|
|
526
604
|
return;
|
|
527
605
|
}
|
|
528
|
-
const
|
|
606
|
+
const host = hosts[slot2.on];
|
|
607
|
+
const columns = columnsOf(host?.entity);
|
|
529
608
|
if (columns && !columns.has(column)) {
|
|
530
|
-
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.`);
|
|
531
615
|
}
|
|
532
616
|
};
|
|
533
617
|
return { entityAt, columnAt };
|
|
@@ -574,7 +658,7 @@ function checkProperties({ def, kind, error }) {
|
|
|
574
658
|
error(["recordChangesOnly"], "record-changes-only", "'recordChangesOnly' needs a 'recorder' \u2014 only a document's record can be compared with the slice in effect.");
|
|
575
659
|
}
|
|
576
660
|
}
|
|
577
|
-
function resolveEntities(def, kind, checks) {
|
|
661
|
+
function resolveEntities(def, kind, checks, hosts) {
|
|
578
662
|
const { entityAt, columnAt } = checks;
|
|
579
663
|
const entityCode = safeTrim(def.entity);
|
|
580
664
|
const journalCode = safeTrim(def.journal);
|
|
@@ -584,13 +668,17 @@ function resolveEntities(def, kind, checks) {
|
|
|
584
668
|
if (kind === "accounting") {
|
|
585
669
|
entityAt(["chart"], safeTrim(def.chart), "Chart");
|
|
586
670
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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 };
|
|
592
680
|
}
|
|
593
|
-
function checkDimensions({ def, dimensions, sides,
|
|
681
|
+
function checkDimensions({ def, dimensions, sides, columnAt, error }) {
|
|
594
682
|
if (!Array.isArray(def.dimensions) || dimensions.length === 0) {
|
|
595
683
|
error(["dimensions"], "dimensions", "A register needs at least one dimension.");
|
|
596
684
|
}
|
|
@@ -603,7 +691,7 @@ function checkDimensions({ def, dimensions, sides, carrier, carrierCode, columnA
|
|
|
603
691
|
}
|
|
604
692
|
dimensions.forEach((name, i) => {
|
|
605
693
|
if (!(name in sides)) {
|
|
606
|
-
columnAt(["dimensions", i],
|
|
694
|
+
columnAt(["dimensions", i], name);
|
|
607
695
|
}
|
|
608
696
|
});
|
|
609
697
|
}
|
|
@@ -633,7 +721,7 @@ function checkResourceShape(ctx, name, resource, path) {
|
|
|
633
721
|
}
|
|
634
722
|
}
|
|
635
723
|
function checkResources(ctx) {
|
|
636
|
-
const { def, kind, resources, dimensions,
|
|
724
|
+
const { def, kind, resources, dimensions, columnAt, error } = ctx;
|
|
637
725
|
if (!isRecord(def.resources) || resources.length === 0) {
|
|
638
726
|
error(["resources"], "resources", "A register needs at least one resource.");
|
|
639
727
|
}
|
|
@@ -653,7 +741,7 @@ function checkResources(ctx) {
|
|
|
653
741
|
if (!column) {
|
|
654
742
|
continue;
|
|
655
743
|
}
|
|
656
|
-
columnAt(path(key),
|
|
744
|
+
columnAt(path(key), column);
|
|
657
745
|
const other = storage.get(column);
|
|
658
746
|
if (other && other !== name) {
|
|
659
747
|
error(path(key), "resource-column-shared", `Resources '${other}' and '${name}' both store into '${column}' \u2014 posting would add both into one column.`);
|
|
@@ -670,10 +758,10 @@ function checkResources(ctx) {
|
|
|
670
758
|
}
|
|
671
759
|
}
|
|
672
760
|
function checkAnalyticSlotColumns(ctx, key, columns) {
|
|
673
|
-
columns.forEach((column, i) => ctx.columnAt([key, i],
|
|
761
|
+
columns.forEach((column, i) => ctx.columnAt([key, i], column));
|
|
674
762
|
}
|
|
675
763
|
function checkSideDimensions(ctx) {
|
|
676
|
-
const { dimensions, sides,
|
|
764
|
+
const { dimensions, sides, columnAt, error } = ctx;
|
|
677
765
|
for (const [name, side] of Object.entries(sides)) {
|
|
678
766
|
const base = ["sideDimensions", name];
|
|
679
767
|
if (!dimensions.includes(name)) {
|
|
@@ -688,12 +776,12 @@ function checkSideDimensions(ctx) {
|
|
|
688
776
|
same: [...base, "creditField"],
|
|
689
777
|
sameMessage: `Two-sided dimension '${name}': the two columns must differ.`
|
|
690
778
|
});
|
|
691
|
-
columnAt([...base, "debitField"],
|
|
692
|
-
columnAt([...base, "creditField"],
|
|
779
|
+
columnAt([...base, "debitField"], debitField);
|
|
780
|
+
columnAt([...base, "creditField"], creditField);
|
|
693
781
|
}
|
|
694
782
|
}
|
|
695
783
|
function checkCompoundAccounts(ctx) {
|
|
696
|
-
const { def,
|
|
784
|
+
const { def, columnAt, error } = ctx;
|
|
697
785
|
refuseKeys(def, ["account", "analyticFields"], error, "line-mode", (key) => `'${key}' belongs to a classic register; a compound one names 'debitAccount' and 'creditAccount'.`);
|
|
698
786
|
const debit = safeTrim(def.debitAccount);
|
|
699
787
|
const credit = safeTrim(def.creditAccount);
|
|
@@ -703,8 +791,8 @@ function checkCompoundAccounts(ctx) {
|
|
|
703
791
|
same: ["creditAccount"],
|
|
704
792
|
sameMessage: "'debitAccount' and 'creditAccount' must differ \u2014 one column would record no correspondence."
|
|
705
793
|
});
|
|
706
|
-
columnAt(["debitAccount"],
|
|
707
|
-
columnAt(["creditAccount"],
|
|
794
|
+
columnAt(["debitAccount"], debit);
|
|
795
|
+
columnAt(["creditAccount"], credit);
|
|
708
796
|
const debitSlots = stringList(def.debitAnalyticFields);
|
|
709
797
|
const creditSlots = stringList(def.creditAnalyticFields);
|
|
710
798
|
if (debitSlots.length > MAX_ANALYTIC_SLOTS || creditSlots.length > MAX_ANALYTIC_SLOTS) {
|
|
@@ -721,12 +809,12 @@ function checkCompoundAccounts(ctx) {
|
|
|
721
809
|
return needsComposition;
|
|
722
810
|
}
|
|
723
811
|
function checkClassicAccounts(ctx) {
|
|
724
|
-
const { def,
|
|
812
|
+
const { def, columnAt, error } = ctx;
|
|
725
813
|
refuseKeys(def, ["debitAccount", "creditAccount", "debitAnalyticFields", "creditAnalyticFields", "sideDimensions"], error, "line-mode", (key) => `'${key}' belongs to a compound register.`);
|
|
726
814
|
if (!safeTrim(def.account)) {
|
|
727
815
|
error(["account"], "accounts", "A classic register names the posting's account column ('account').");
|
|
728
816
|
}
|
|
729
|
-
columnAt(["account"],
|
|
817
|
+
columnAt(["account"], safeTrim(def.account));
|
|
730
818
|
const slots = stringList(def.analyticFields);
|
|
731
819
|
if (slots.length > MAX_ANALYTIC_SLOTS) {
|
|
732
820
|
error(["analyticFields"], "analytic-slots", `At most ${MAX_ANALYTIC_SLOTS} analytic slots.`);
|
|
@@ -735,7 +823,7 @@ function checkClassicAccounts(ctx) {
|
|
|
735
823
|
return slots.length > 0;
|
|
736
824
|
}
|
|
737
825
|
function checkComposition(ctx, needsComposition) {
|
|
738
|
-
const { def, entityAt, columnAt, error } = ctx;
|
|
826
|
+
const { def, entityAt, columnAt, hosts, error } = ctx;
|
|
739
827
|
const composition = isRecord(def.composition) ? def.composition : void 0;
|
|
740
828
|
if (needsComposition && !composition) {
|
|
741
829
|
error(["composition"], "composition", "Analytic slots need a 'composition' \u2014 where the chart says which analytic each account takes in which slot.");
|
|
@@ -752,9 +840,9 @@ function checkComposition(ctx, needsComposition) {
|
|
|
752
840
|
}
|
|
753
841
|
}
|
|
754
842
|
const code = safeTrim(composition.entity);
|
|
755
|
-
|
|
843
|
+
hosts.composition = { entity: entityAt(["composition", "entity"], code, "Composition entity"), code };
|
|
756
844
|
for (const key of ["account", "slot", "analytic"]) {
|
|
757
|
-
columnAt(["composition", key],
|
|
845
|
+
columnAt(["composition", key], composition[key]);
|
|
758
846
|
}
|
|
759
847
|
}
|
|
760
848
|
function checkAccounting(ctx) {
|
|
@@ -769,7 +857,7 @@ function checkAccounting(ctx) {
|
|
|
769
857
|
checkComposition(ctx, mode === "compound" ? checkCompoundAccounts(ctx) : checkClassicAccounts(ctx));
|
|
770
858
|
}
|
|
771
859
|
function checkTotals(ctx) {
|
|
772
|
-
const { def, kind, dimensions, resources, entityAt, columnAt, error } = ctx;
|
|
860
|
+
const { def, kind, dimensions, resources, entityAt, columnAt, hosts, error } = ctx;
|
|
773
861
|
const totals = isRecord(def.totals) ? def.totals : void 0;
|
|
774
862
|
if (!totals || kind === "info") {
|
|
775
863
|
return;
|
|
@@ -778,7 +866,7 @@ function checkTotals(ctx) {
|
|
|
778
866
|
if (!totalsCode) {
|
|
779
867
|
error(["totals", "entity"], "totals", "The totals cache needs its entity.");
|
|
780
868
|
}
|
|
781
|
-
|
|
869
|
+
hosts.totals = { entity: entityAt(["totals", "entity"], totalsCode, "Totals entity"), code: totalsCode };
|
|
782
870
|
const period = safeTrim(totals.period);
|
|
783
871
|
if (!period || !REGISTER_TOTALS_PERIODS.includes(period)) {
|
|
784
872
|
error(["totals", "period"], "totals", "The totals cache needs a period: day, month, quarter or year.");
|
|
@@ -786,12 +874,12 @@ function checkTotals(ctx) {
|
|
|
786
874
|
if (!safeTrim(totals.periodField)) {
|
|
787
875
|
error(["totals", "periodField"], "totals", "The totals cache needs 'periodField' \u2014 the column holding each period's first date.");
|
|
788
876
|
}
|
|
789
|
-
columnAt(["totals", "periodField"],
|
|
877
|
+
columnAt(["totals", "periodField"], safeTrim(totals.periodField));
|
|
790
878
|
if (kind === "accounting") {
|
|
791
879
|
if (!safeTrim(totals.accountField)) {
|
|
792
880
|
error(["totals", "accountField"], "totals", "An accounting register's cache needs 'accountField'.");
|
|
793
881
|
}
|
|
794
|
-
columnAt(["totals", "accountField"],
|
|
882
|
+
columnAt(["totals", "accountField"], safeTrim(totals.accountField));
|
|
795
883
|
stringList(totals.dimensions).forEach((name, i) => {
|
|
796
884
|
if (!dimensions.includes(name)) {
|
|
797
885
|
error(["totals", "dimensions", i], "totals", `Totals dimension '${name}' isn't a dimension of the register.`);
|
|
@@ -817,7 +905,7 @@ function checkTotals(ctx) {
|
|
|
817
905
|
if (!safeTrim(entry[key])) {
|
|
818
906
|
error(["totals", "resources", name], "totals", `Totals resource '${name}' needs '${key}'.`);
|
|
819
907
|
}
|
|
820
|
-
columnAt(["totals", "resources", name, key],
|
|
908
|
+
columnAt(["totals", "resources", name, key], safeTrim(entry[key]));
|
|
821
909
|
}
|
|
822
910
|
}
|
|
823
911
|
for (const name of Object.keys(cached)) {
|
|
@@ -853,14 +941,16 @@ function checkRegister(raw, lookup) {
|
|
|
853
941
|
}
|
|
854
942
|
const kind = def.kind;
|
|
855
943
|
checkProperties({ def, kind, error });
|
|
856
|
-
const
|
|
944
|
+
const hosts = {};
|
|
945
|
+
const checks = createEntityChecks(lookup, error, warning, hosts, kind);
|
|
857
946
|
const ctx = {
|
|
858
947
|
def,
|
|
859
948
|
kind,
|
|
860
949
|
error,
|
|
861
950
|
warning,
|
|
951
|
+
hosts,
|
|
862
952
|
...checks,
|
|
863
|
-
...resolveEntities(def, kind, checks),
|
|
953
|
+
...resolveEntities(def, kind, checks, hosts),
|
|
864
954
|
dimensions: registerDimensions(def),
|
|
865
955
|
sides: isRecord(def.sideDimensions) ? def.sideDimensions : {},
|
|
866
956
|
resources: registerResources(def)
|
|
@@ -1114,6 +1204,7 @@ export {
|
|
|
1114
1204
|
NUMERIC_TOTAL,
|
|
1115
1205
|
REGISTER_CODE,
|
|
1116
1206
|
REGISTER_COLUMN_NAME,
|
|
1207
|
+
REGISTER_COLUMN_SLOTS,
|
|
1117
1208
|
REGISTER_KINDS,
|
|
1118
1209
|
REGISTER_TOTALS_PERIODS,
|
|
1119
1210
|
baseDatatypes,
|
|
@@ -1140,9 +1231,12 @@ export {
|
|
|
1140
1231
|
getPrimaryKeys,
|
|
1141
1232
|
getTrait,
|
|
1142
1233
|
isFieldTypeCd,
|
|
1234
|
+
isNumericDatatype,
|
|
1143
1235
|
isPlainColumnRef,
|
|
1144
1236
|
isRegisterKind,
|
|
1145
1237
|
registerCarrier,
|
|
1238
|
+
registerColumnHost,
|
|
1239
|
+
registerColumnSlot,
|
|
1146
1240
|
registerDimensions,
|
|
1147
1241
|
registerLineMode,
|
|
1148
1242
|
registerRefusedProps,
|