@xml-auditfile/auditfile 0.0.0-stage → 0.0.2-alpha.3

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.
Files changed (83) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +183 -2
  3. package/package.json +62 -4
  4. package/src/auditfile.js +186 -0
  5. package/src/definitions/dialect.js +40 -0
  6. package/src/definitions/element-definition.js +27 -0
  7. package/src/definitions/localized-text.js +41 -0
  8. package/src/definitions/manifest.js +160 -0
  9. package/src/definitions/names.js +42 -0
  10. package/src/definitions/resources.js +29 -0
  11. package/src/definitions/view-definition.js +160 -0
  12. package/src/diagnostics/collector.js +139 -0
  13. package/src/diagnostics/diagnostic.js +53 -0
  14. package/src/diagnostics/report.js +56 -0
  15. package/src/generated/spec.js +74 -0
  16. package/src/index.js +20 -0
  17. package/src/internal/composite-key.js +2 -0
  18. package/src/internal/decimal.js +193 -0
  19. package/src/internal/encoding.js +118 -0
  20. package/src/internal/json.js +106 -0
  21. package/src/internal/sha256.js +147 -0
  22. package/src/internal/streams.js +232 -0
  23. package/src/internal/xml-reader.js +247 -0
  24. package/src/memory/memory-sink.js +68 -0
  25. package/src/reading/mapping-plan.js +91 -0
  26. package/src/reading/read-options.js +136 -0
  27. package/src/reading/read-session.js +446 -0
  28. package/src/reading/row-loader.js +362 -0
  29. package/src/reading/row.js +123 -0
  30. package/src/reading/source.js +146 -0
  31. package/src/reading/value-converter.js +136 -0
  32. package/src/schema/schema.js +256 -0
  33. package/src/sinks/sink.js +53 -0
  34. package/src/validation/reference-checker.js +146 -0
  35. package/src/validation/xaf-rules.js +167 -0
  36. package/src/versioning/version-resolver.js +119 -0
  37. package/src/versioning/version.js +217 -0
  38. package/src/views/view-result.js +20 -0
  39. package/src/views/view-runner.js +449 -0
  40. package/src/xsd/datatypes.js +141 -0
  41. package/src/xsd/regex.js +121 -0
  42. package/src/xsd/schema-set.js +555 -0
  43. package/src/xsd/validator.js +484 -0
  44. package/types/auditfile.d.ts +57 -0
  45. package/types/definitions/dialect.d.ts +27 -0
  46. package/types/definitions/element-definition.d.ts +16 -0
  47. package/types/definitions/localized-text.d.ts +25 -0
  48. package/types/definitions/manifest.d.ts +118 -0
  49. package/types/definitions/names.d.ts +25 -0
  50. package/types/definitions/resources.d.ts +9 -0
  51. package/types/definitions/view-definition.d.ts +85 -0
  52. package/types/diagnostics/collector.d.ts +38 -0
  53. package/types/diagnostics/diagnostic.d.ts +37 -0
  54. package/types/diagnostics/report.d.ts +32 -0
  55. package/types/generated/spec.d.ts +6 -0
  56. package/types/index.d.ts +18 -0
  57. package/types/internal/composite-key.d.ts +2 -0
  58. package/types/internal/decimal.d.ts +55 -0
  59. package/types/internal/encoding.d.ts +43 -0
  60. package/types/internal/json.d.ts +17 -0
  61. package/types/internal/sha256.d.ts +17 -0
  62. package/types/internal/streams.d.ts +35 -0
  63. package/types/internal/xml-reader.d.ts +50 -0
  64. package/types/memory/memory-sink.d.ts +29 -0
  65. package/types/reading/mapping-plan.d.ts +45 -0
  66. package/types/reading/read-options.d.ts +69 -0
  67. package/types/reading/read-session.d.ts +28 -0
  68. package/types/reading/row-loader.d.ts +18 -0
  69. package/types/reading/row.d.ts +53 -0
  70. package/types/reading/source.d.ts +73 -0
  71. package/types/reading/value-converter.d.ts +15 -0
  72. package/types/schema/schema.d.ts +116 -0
  73. package/types/sinks/sink.d.ts +59 -0
  74. package/types/validation/reference-checker.d.ts +11 -0
  75. package/types/validation/xaf-rules.d.ts +13 -0
  76. package/types/versioning/version-resolver.d.ts +25 -0
  77. package/types/versioning/version.d.ts +71 -0
  78. package/types/views/view-result.d.ts +13 -0
  79. package/types/views/view-runner.d.ts +1 -0
  80. package/types/xsd/datatypes.d.ts +40 -0
  81. package/types/xsd/regex.d.ts +6 -0
  82. package/types/xsd/schema-set.d.ts +20 -0
  83. package/types/xsd/validator.d.ts +48 -0
@@ -0,0 +1,167 @@
1
+ import { ColumnType } from '../definitions/manifest.js';
2
+ import { Location } from '../diagnostics/collector.js';
3
+ import { Decimal } from '../internal/decimal.js';
4
+
5
+ /** Audit file type these rules apply to. */
6
+ export const XAF = 'xaf';
7
+
8
+ const AMOUNT = 'amnt';
9
+ const AMOUNT_TYPE = 'amnt_tp';
10
+ const DEBIT = 'D';
11
+ const CREDIT = 'C';
12
+
13
+ /**
14
+ * A table of lines with an amount and a debit/credit indicator, with its columns looked up once.
15
+ * Null when the table or a column does not exist, or has another type, in this version.
16
+ */
17
+ function findLines(table) {
18
+ const amount = table?.findColumn(AMOUNT) ?? null;
19
+ const amountType = table?.findColumn(AMOUNT_TYPE) ?? null;
20
+ if (amount === null || amountType === null || ![ColumnType.Decimal, ColumnType.Integer].includes(amount.type)
21
+ || amountType.type !== ColumnType.String) {
22
+ return null;
23
+ }
24
+
25
+ return { table, amount, amountType };
26
+ }
27
+
28
+ /** Line count and debit and credit totals. */
29
+ class Totals {
30
+ count = 0;
31
+ debit = Decimal.ZERO;
32
+ credit = Decimal.ZERO;
33
+
34
+ add(row, lines) {
35
+ this.count++;
36
+ const amount = row.getDecimal(lines.amount);
37
+ if (amount === null) {
38
+ return;
39
+ }
40
+
41
+ const type = row.getString(lines.amountType);
42
+ if (type === DEBIT) {
43
+ this.debit = this.debit.add(amount);
44
+ } else if (type === CREDIT) {
45
+ this.credit = this.credit.add(amount);
46
+ }
47
+ }
48
+ }
49
+
50
+ function get(map, key) {
51
+ let totals = map.get(key);
52
+ if (totals === undefined) {
53
+ totals = new Totals();
54
+ map.set(key, totals);
55
+ }
56
+
57
+ return totals;
58
+ }
59
+
60
+ /** Compares a header value with the total of the lines. */
61
+ function compare(row, field, expected, actual, diagnostics) {
62
+ if (expected === null || expected === undefined) {
63
+ return;
64
+ }
65
+
66
+ const equal = expected instanceof Decimal ? expected.equals(actual) : expected === actual;
67
+ if (!equal) {
68
+ diagnostics.add('XAF-R002', new Location(row.part, null, null, row.table.path),
69
+ { field, table: row.table.name, expected: String(expected), actual: String(actual) });
70
+ }
71
+ }
72
+
73
+ /** Header counts and totals of one kind of line, e.g. the transaction lines against company/transactions. */
74
+ class TotalsRule {
75
+ #byParent = new Map();
76
+ #all = new Totals();
77
+
78
+ constructor(lines, owner, perParent, count, debit, credit) {
79
+ this.lines = lines;
80
+ this.owner = owner;
81
+ this.perParent = perParent;
82
+ this.count = count;
83
+ this.debit = debit;
84
+ this.credit = credit;
85
+ }
86
+
87
+ onRow(row, diagnostics) {
88
+ if (row.table === this.lines.table) {
89
+ const totals = this.perParent && row.parentId !== null ? get(this.#byParent, row.parentId) : this.#all;
90
+ totals.add(row, this.lines);
91
+ return;
92
+ }
93
+
94
+ if (row.table !== this.owner) {
95
+ return;
96
+ }
97
+
98
+ let actual = this.#all;
99
+ if (this.perParent) {
100
+ actual = this.#byParent.get(row.id) ?? new Totals();
101
+ this.#byParent.delete(row.id);
102
+ }
103
+
104
+ compare(row, this.count.name, row.getInteger(this.count), actual.count, diagnostics);
105
+ compare(row, this.debit.name, row.getDecimal(this.debit), actual.debit, diagnostics);
106
+ compare(row, this.credit.name, row.getDecimal(this.credit), actual.credit, diagnostics);
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Business rules for Auditfile Financieel (XAF):
112
+ * every transaction balances, and the counts and totals in the headers match the lines.
113
+ * Rules whose tables or columns do not exist in a version are skipped. Columns are looked up once, not per row.
114
+ */
115
+ export class XafRules {
116
+ #diagnostics;
117
+ #transactions = new Map();
118
+ #rules = [];
119
+ #lines;
120
+ #transaction;
121
+ #transactionNumber;
122
+
123
+ constructor(schema, diagnostics) {
124
+ this.#diagnostics = diagnostics;
125
+ this.#lines = findLines(schema.findTable('tr_line'));
126
+ this.#transaction = schema.findTable('transaction');
127
+ this.#transactionNumber = this.#transaction?.findColumn('nr') ?? null;
128
+
129
+ this.#addRule(schema, 'tr_line', 'company', false, 'transactions_lines_count', 'transactions_total_debit', 'transactions_total_credit');
130
+ this.#addRule(schema, 'ob_line', 'company', false, 'opening_balance_lines_count', 'opening_balance_total_debit', 'opening_balance_total_credit');
131
+ this.#addRule(schema, 'sb_line', 'subledger', true, 'lines_count', 'total_debit', 'total_credit');
132
+ this.#addRule(schema, 'ob_sb_line', 'ob_subledger', true, 'lines_count', 'total_debit', 'total_credit');
133
+ }
134
+
135
+ onRow(row) {
136
+ if (this.#lines !== null && row.table === this.#lines.table && row.parentId !== null) {
137
+ get(this.#transactions, row.parentId).add(row, this.#lines);
138
+ } else if (row.table === this.#transaction && this.#transactions.has(row.id)) {
139
+ const lines = this.#transactions.get(row.id);
140
+ this.#transactions.delete(row.id);
141
+ if (!lines.debit.equals(lines.credit)) {
142
+ this.#diagnostics.add('XAF-R001', new Location(row.part, null, null, row.table.path),
143
+ { transaction: this.#transactionNumber === null ? null : row.get(this.#transactionNumber), debit: lines.debit, credit: lines.credit });
144
+ }
145
+ }
146
+
147
+ for (const rule of this.#rules) {
148
+ rule.onRow(row, this.#diagnostics);
149
+ }
150
+ }
151
+
152
+ onEnd() {}
153
+
154
+ #addRule(schema, child, owner, perParent, count, debit, credit) {
155
+ const lines = findLines(schema.findTable(child));
156
+ const ownerTable = schema.findTable(owner);
157
+ const countColumn = ownerTable?.findColumn(count) ?? null;
158
+ const debitColumn = ownerTable?.findColumn(debit) ?? null;
159
+ const creditColumn = ownerTable?.findColumn(credit) ?? null;
160
+ if (lines === null || countColumn?.type !== ColumnType.Integer
161
+ || debitColumn?.type !== ColumnType.Decimal || creditColumn?.type !== ColumnType.Decimal) {
162
+ return;
163
+ }
164
+
165
+ this.#rules.push(new TotalsRule(lines, ownerTable, perParent, countColumn, debitColumn, creditColumn));
166
+ }
167
+ }
@@ -0,0 +1,119 @@
1
+ import { DialectAction } from '../definitions/names.js';
2
+ import { Location } from '../diagnostics/collector.js';
3
+ import { VersionSource } from '../schema/schema.js';
4
+ import { AuditfileVersion } from './version.js';
5
+
6
+ /** The outcome of version resolution. */
7
+ export class Resolution {
8
+ constructor(version, source, fileNamespace, dialect) {
9
+ /** @type {AuditfileVersion} */
10
+ this.version = version;
11
+ this.source = source;
12
+ this.fileNamespace = fileNamespace;
13
+ /** @type {import('../definitions/dialect.js').Dialect | null} Set later when header/softwareDesc selects a dialect. */
14
+ this.dialect = dialect;
15
+ }
16
+
17
+ get needsMapping() {
18
+ return this.fileNamespace !== this.version.namespace;
19
+ }
20
+
21
+ /**
22
+ * Recognizes a dialect by the software description in the header, when none was recognized by namespace.
23
+ * Returns true when a dialect was found.
24
+ * @param {string} software
25
+ * @param {boolean} applyDialects
26
+ */
27
+ useSoftware(software, applyDialects) {
28
+ if (this.dialect !== null || !applyDialects) {
29
+ return false;
30
+ }
31
+
32
+ this.dialect = this.version.dialects.find((d) => d.softwareDescription === software) ?? null;
33
+ return this.dialect !== null;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Determines the version: forced by the caller, then the registry (official namespace, alias or dialect),
39
+ * then optionally the resolver. Anything else is fatal.
40
+ * @returns {Resolution | null}
41
+ */
42
+ export function resolveVersion(rootName, fileNamespace, options, diagnostics) {
43
+ const resolution = find(fileNamespace, options, diagnostics);
44
+ if (resolution === null) {
45
+ diagnostics.add('XAF-V001', Location.NONE, { namespace: fileNamespace });
46
+ return null;
47
+ }
48
+
49
+ const version = resolution.version;
50
+ if (rootName !== version.rootElement) {
51
+ diagnostics.add('XAF-V005', Location.NONE, { element: rootName, expected: version.rootElement });
52
+ return null;
53
+ }
54
+
55
+ if (!version.hasOfficialXsd) {
56
+ diagnostics.add('XAF-V006', Location.NONE, { version: version.key });
57
+ }
58
+
59
+ const replace = resolution.dialect?.corrections.find((c) => c.action === DialectAction.ReplaceNamespace && c.from === fileNamespace);
60
+ if (replace !== undefined) {
61
+ diagnostics.add(replace.code, Location.NONE, { from: fileNamespace, to: version.namespace });
62
+ }
63
+
64
+ return resolution;
65
+ }
66
+
67
+ function find(fileNamespace, options, diagnostics) {
68
+ const forced = options.version;
69
+ if (forced !== null) {
70
+ if (fileNamespace !== forced.namespace) {
71
+ diagnostics.add('XAF-V003', Location.NONE, { version: forced.key, namespace: fileNamespace });
72
+ }
73
+
74
+ return new Resolution(forced, VersionSource.Forced, fileNamespace, findDialect(forced, fileNamespace, options));
75
+ }
76
+
77
+ for (const version of AuditfileVersion.all) {
78
+ if (version.namespace === fileNamespace) {
79
+ return new Resolution(version, VersionSource.Official, fileNamespace, null);
80
+ }
81
+ }
82
+
83
+ for (const version of AuditfileVersion.all) {
84
+ const dialect = findDialect(version, fileNamespace, options);
85
+ if (version.aliases.includes(fileNamespace) || dialect !== null) {
86
+ diagnostics.add('XAF-V002', Location.NONE, { namespace: fileNamespace, version: version.key });
87
+ return new Resolution(version, VersionSource.Alias, fileNamespace, dialect);
88
+ }
89
+ }
90
+
91
+ if (options.useResolver) {
92
+ const derived = derive(fileNamespace);
93
+ if (derived !== null) {
94
+ diagnostics.add('XAF-V004', Location.NONE, { version: derived.key, namespace: fileNamespace });
95
+ return new Resolution(derived, VersionSource.Resolved, fileNamespace, null);
96
+ }
97
+ }
98
+
99
+ return null;
100
+ }
101
+
102
+ function findDialect(version, fileNamespace, options) {
103
+ return options.applyDialects ? version.dialects.find((d) => d.namespaces.includes(fileNamespace)) ?? null : null;
104
+ }
105
+
106
+ const digits = (value) => value.replace(/\D/g, '');
107
+
108
+ /**
109
+ * Derives type and version from the namespace segments, e.g. ".../XAF/3.2/" or ".../xaf/3_2".
110
+ * Returns a version only when exactly one matches.
111
+ */
112
+ function derive(fileNamespace) {
113
+ const segments = fileNamespace.toLowerCase().split(/[/:#]/).filter((s) => s.length > 0);
114
+ const candidates = AuditfileVersion.all.filter((v) =>
115
+ segments.includes(v.type)
116
+ && segments.some((s) => digits(s).length > 0 && digits(s) === digits(v.version) && /^[\d._]+$/.test(s)));
117
+
118
+ return candidates.length === 1 ? candidates[0] : null;
119
+ }
@@ -0,0 +1,217 @@
1
+ import { Dialect } from '../definitions/dialect.js';
2
+ import { ElementDefinition } from '../definitions/element-definition.js';
3
+ import { AuditfileManifest } from '../definitions/manifest.js';
4
+ import { exists, readBytes, readJson, readText } from '../definitions/resources.js';
5
+ import { ViewDefinition } from '../definitions/view-definition.js';
6
+ import { objects, string } from '../internal/json.js';
7
+ import { Sha256 } from '../internal/sha256.js';
8
+ import { SchemaSet } from '../xsd/schema-set.js';
9
+
10
+ /** @type {AuditfileVersion[] | null} */
11
+ let registry = null;
12
+
13
+ function lazy(factory) {
14
+ let value;
15
+ let created = false;
16
+ return () => {
17
+ if (!created) {
18
+ value = factory();
19
+ created = true;
20
+ }
21
+
22
+ return value;
23
+ };
24
+ }
25
+
26
+ /**
27
+ * One audit file type and version, with its manifest, XSD, definitions and dialects.
28
+ * Use the constants, e.g. `AuditfileVersion.Financial.V3_2`, or {@link AuditfileVersion.parse} with "xaf/3.2".
29
+ */
30
+ export class AuditfileVersion {
31
+ #manifest;
32
+ #definitions;
33
+ #dialects;
34
+ #views;
35
+ #schemaSet;
36
+
37
+ /** @internal */
38
+ constructor(json) {
39
+ /** Registry key, e.g. "xaf/3.2". */
40
+ this.key = string(json, 'key');
41
+ /** Audit file type, e.g. "xaf". */
42
+ this.type = string(json, 'type');
43
+ /** Type name used for version objects, e.g. "financial". */
44
+ this.typeName = string(json, 'typeName');
45
+ /** Version, e.g. "3.2". */
46
+ this.version = string(json, 'version');
47
+ /** Folder of the definitions, e.g. "standards/xaf/3_2". */
48
+ this.folder = string(json, 'folder');
49
+ /** Name of the root element. */
50
+ this.rootElement = string(json, 'rootElement');
51
+ /** "official" or "reconstructed". */
52
+ this.provenance = string(json, 'provenance');
53
+
54
+ const namespaces = objects(json, 'namespaces');
55
+ const official = namespaces.find((n) => string(n, 'kind') === 'official');
56
+ if (official === undefined) {
57
+ throw new SyntaxError(`registry.json: ${this.key} has no official namespace.`);
58
+ }
59
+
60
+ /** The official namespace. */
61
+ this.namespace = string(official, 'uri');
62
+ /** @type {readonly string[]} Other namespaces known to be used for this version. */
63
+ this.aliases = Object.freeze(namespaces.filter((n) => string(n, 'kind') === 'alias').map((n) => string(n, 'uri')));
64
+
65
+ const folder = this.folder;
66
+ this.#manifest = lazy(() => new AuditfileManifest(readJson(`${folder}/manifest.json`)));
67
+ this.#definitions = lazy(() => new Map(objects(readJson(`${folder}/definition.json`), 'entries')
68
+ .map((e) => new ElementDefinition(e)).map((d) => [d.key, d])));
69
+ this.#dialects = lazy(() => Object.freeze(objects(readJson(`${folder}/dialects.json`), 'dialects').map((d) => new Dialect(d))));
70
+ this.#views = lazy(() => ViewDefinition.load(this.type));
71
+ this.#schemaSet = lazy(() => AuditfileVersion.compileSchemaSet(`${folder}/auditfile.xsd`, (name) => (exists(name) ? readText(name) : undefined)));
72
+ Object.freeze(this);
73
+ }
74
+
75
+ /** @type {readonly AuditfileVersion[]} All versions this reader knows, in registry order. */
76
+ static get all() {
77
+ registry ??= Object.freeze(objects(readJson('registry.json'), 'standards').map((s) => new AuditfileVersion(s)));
78
+ return registry;
79
+ }
80
+
81
+ /** True when the XSD is the official one published by the Belastingdienst. */
82
+ get hasOfficialXsd() {
83
+ return this.provenance === 'official';
84
+ }
85
+
86
+ /** @type {AuditfileManifest} Structure: tables and columns. */
87
+ get manifest() {
88
+ return this.#manifest();
89
+ }
90
+
91
+ /** @type {ReadonlyMap<string, ElementDefinition>} Definitions by key ("tr_line", "tr_line.amnt"). */
92
+ get definitions() {
93
+ return this.#definitions();
94
+ }
95
+
96
+ /** @type {readonly Dialect[]} Known dialects of this version. */
97
+ get dialects() {
98
+ return this.#dialects();
99
+ }
100
+
101
+ /** @type {readonly ViewDefinition[]} Views of this audit file type, e.g. the trial balance. Shared by all versions of the type. */
102
+ get views() {
103
+ return this.#views();
104
+ }
105
+
106
+ /**
107
+ * Finds a view by name, or returns null.
108
+ * @param {string} name
109
+ */
110
+ findView(name) {
111
+ return this.views.find((v) => v.name === name) ?? null;
112
+ }
113
+
114
+ /**
115
+ * Finds a version by key, e.g. "xaf/3.2" (case-insensitive). Returns null when unknown.
116
+ * @param {string} key
117
+ */
118
+ static find(key) {
119
+ const lower = String(key).toLowerCase();
120
+ return AuditfileVersion.all.find((v) => v.key.toLowerCase() === lower) ?? null;
121
+ }
122
+
123
+ /**
124
+ * Returns the version for a key such as "xaf/3.2".
125
+ * @param {string} key
126
+ * @throws {RangeError} The key is unknown.
127
+ */
128
+ static parse(key) {
129
+ if (typeof key !== 'string') {
130
+ throw new TypeError('key must be a string.');
131
+ }
132
+
133
+ const version = AuditfileVersion.find(key);
134
+ if (version === null) {
135
+ throw new RangeError(`Unknown audit file version '${key}'.`);
136
+ }
137
+
138
+ return version;
139
+ }
140
+
141
+ /** The bytes of the XSD of this version. */
142
+ openXsd() {
143
+ return readBytes(`${this.folder}/auditfile.xsd`);
144
+ }
145
+
146
+ /** The compiled XSD of this version, ready for validation. Compiled once and shared; it is immutable. */
147
+ createSchemaSet() {
148
+ return this.#schemaSet();
149
+ }
150
+
151
+ toString() {
152
+ return this.key;
153
+ }
154
+
155
+ /**
156
+ * @internal
157
+ * Compiles an XSD. Includes and imports are resolved only through `load`, never from disk or network.
158
+ * @param {string} name
159
+ * @param {(name: string) => string | undefined} load
160
+ */
161
+ static compileSchemaSet(name, load) {
162
+ return SchemaSet.compile(name, load);
163
+ }
164
+
165
+ /** @internal */
166
+ static sha256(bytes) {
167
+ return Sha256.hex(bytes);
168
+ }
169
+ }
170
+
171
+ function constant(key) {
172
+ return { enumerable: true, get: () => AuditfileVersion.parse(key) };
173
+ }
174
+
175
+ // The version constants. A test checks that every registry entry has a constant here and vice versa.
176
+ // Names follow the registry: version with underscores, e.g. V3_2 for 3.2.
177
+
178
+ /** Auditfile Financieel (XAF). */
179
+ AuditfileVersion.Financial = Object.freeze(Object.defineProperties({}, {
180
+ /** XAF 3.2 (2014, supplemented in 2017). */
181
+ V3_2: constant('xaf/3.2'),
182
+ /** XAF 4.0.3. */
183
+ V4_0_3: constant('xaf/4.0.3'),
184
+ }));
185
+
186
+ /** Auditfile Afrekensystemen (XAA). */
187
+ AuditfileVersion.CashRegister = Object.freeze(Object.defineProperties({}, {
188
+ V3_2: constant('xaa/3.2'),
189
+ }));
190
+
191
+ /** Auditfile Boordcomputer Taxi (XAB). */
192
+ AuditfileVersion.TaxiOnboardComputer = Object.freeze(Object.defineProperties({}, {
193
+ V1_0: constant('xab/1.0'),
194
+ }));
195
+
196
+ /** Auditfile Ritregistratie (XAR). */
197
+ AuditfileVersion.TripRegistration = Object.freeze(Object.defineProperties({}, {
198
+ V1_2: constant('xar/1.2'),
199
+ V2_3_6_1: constant('xar/2.3.6.1'),
200
+ }));
201
+
202
+ /** Auditfile Salaris (XAS). */
203
+ AuditfileVersion.Payroll = Object.freeze(Object.defineProperties({}, {
204
+ V2018_1_0: constant('xas/2018.1.0'),
205
+ V2019_1_0: constant('xas/2019.1.0'),
206
+ V2020_1_0: constant('xas/2020.1.0'),
207
+ V2021_1_1: constant('xas/2021.1.1'),
208
+ V2022_1_1: constant('xas/2022.1.1'),
209
+ V2023_1_0: constant('xas/2023.1.0'),
210
+ V2024_1_0: constant('xas/2024.1.0'),
211
+ V2025_1_0: constant('xas/2025.1.0'),
212
+ V2026_1_0: constant('xas/2026.1.0'),
213
+ V2027_1_0: constant('xas/2027.1.0'),
214
+ }));
215
+
216
+ /** The groups of version constants, for the test that compares them with the registry. */
217
+ export const VERSION_GROUPS = Object.freeze(['Financial', 'CashRegister', 'TaxiOnboardComputer', 'TripRegistration', 'Payroll']);
@@ -0,0 +1,20 @@
1
+ /** The outcome of running a {@link ViewDefinition}. */
2
+ export class ViewResult {
3
+ /** @internal */
4
+ constructor(view, isAvailable, rows, diagnostics) {
5
+ /** @type {import('../definitions/view-definition.js').ViewDefinition} The view that was run. */
6
+ this.view = view;
7
+ /** False when the version lacks a table or column the view needs (XAF-Q001); there are no rows then. */
8
+ this.isAvailable = isAvailable;
9
+ /** @type {readonly (readonly unknown[])[]} Result rows, sorted by the view's orderBy; one value per column. */
10
+ this.rows = Object.freeze(rows.map((row) => Object.freeze(row)));
11
+ /** @type {readonly import('../diagnostics/diagnostic.js').Diagnostic[]} Findings while running the view. */
12
+ this.diagnostics = Object.freeze([...diagnostics]);
13
+ Object.freeze(this);
14
+ }
15
+
16
+ /** Output columns; each row has one value per column, in this order. */
17
+ get columns() {
18
+ return this.view.columns;
19
+ }
20
+ }