@diy-accounting-uk/diya-gl 1.0.0
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/README.md +63 -0
- package/bin/diya-gl-mcp.js +10 -0
- package/bin/diya-gl-read-workbook.js +11 -0
- package/bin/diya-gl-recalc.js +10 -0
- package/bin/diya-gl-write-workbook.js +12 -0
- package/bin/diya-gl.js +24 -0
- package/bin/run-dist-bin.js +23 -0
- package/dist/app/bin/build-diya-gl-spec.js +680 -0
- package/dist/app/bin/build-packages.js +149 -0
- package/dist/app/bin/build-reconciliation-pages.js +1052 -0
- package/dist/app/bin/build-sitemaps.js +51 -0
- package/dist/app/bin/compliance-report.js +125 -0
- package/dist/app/bin/cross-package-reconciliation.js +92 -0
- package/dist/app/bin/diya-gl-mcp.js +27 -0
- package/dist/app/bin/export.js +318 -0
- package/dist/app/bin/extract-scenarios.js +1348 -0
- package/dist/app/bin/generate.js +353 -0
- package/dist/app/bin/judge-reconciliation.js +649 -0
- package/dist/app/bin/reconcile.js +247 -0
- package/dist/app/bin/report.js +291 -0
- package/dist/app/bin/verify-roundtrip.js +941 -0
- package/dist/app/bin/verify-stability.js +429 -0
- package/dist/app/bin/write-workbook.js +106 -0
- package/dist/app/data/filing/ct-computation-v1.1.toml +1033 -0
- package/dist/app/data/filing/ct600-v3.toml +2329 -0
- package/dist/app/data/filing/frs105-formats.toml +244 -0
- package/dist/app/data/fixture-master-gaps.json +247 -0
- package/dist/app/data/hmrc/SOURCE.md +14 -0
- package/dist/app/data/hmrc/form-layouts/bst.json +116 -0
- package/dist/app/data/hmrc/form-layouts/ltd.json +452 -0
- package/dist/app/data/hmrc/form-layouts/se.json +485 -0
- package/dist/app/data/hmrc/form-layouts/taxi.json +117 -0
- package/dist/app/data/hmrc/sa103-mtd-mapping.json +1078 -0
- package/dist/app/data/hmrc/sa103f_mapping_v3.csv +111 -0
- package/dist/app/data/judge-rubric.md +52 -0
- package/dist/app/data/ltd-2020.toml +49 -0
- package/dist/app/data/ltd-2021.toml +49 -0
- package/dist/app/data/ltd-2022.toml +49 -0
- package/dist/app/data/ltd-2023.toml +49 -0
- package/dist/app/data/ltd-2024.toml +49 -0
- package/dist/app/data/ltd-2025.toml +49 -0
- package/dist/app/data/ltd-2026.toml +53 -0
- package/dist/app/data/ltd-2027.toml +51 -0
- package/dist/app/data/render-unrepresentable/bst.json +24 -0
- package/dist/app/data/render-unrepresentable/ltd.json +863 -0
- package/dist/app/data/render-unrepresentable/se.json +455 -0
- package/dist/app/data/render-unrepresentable/taxi.json +16 -0
- package/dist/app/data/roundtrip-budget.json +38 -0
- package/dist/app/data/roundtrip-matrix-budget.json +26 -0
- package/dist/app/data/roundtrip-unrepresentable.json +401 -0
- package/dist/app/data/se-2020-2021.toml +52 -0
- package/dist/app/data/se-2021-2022.toml +52 -0
- package/dist/app/data/se-2022-2023.toml +55 -0
- package/dist/app/data/se-2023-2024.toml +54 -0
- package/dist/app/data/se-2024-2025.toml +51 -0
- package/dist/app/data/se-2025-2026.toml +51 -0
- package/dist/app/data/se-2026-2027.toml +51 -0
- package/dist/app/data/volatile-cells.json +16 -0
- package/dist/app/lib/anchors/bst.js +79 -0
- package/dist/app/lib/anchors/ltd.js +626 -0
- package/dist/app/lib/anchors/run.js +100 -0
- package/dist/app/lib/anchors/se.js +456 -0
- package/dist/app/lib/anchors/taxi.js +164 -0
- package/dist/app/lib/app-resources.js +80 -0
- package/dist/app/lib/book-checks/ltd.js +408 -0
- package/dist/app/lib/book-checks/se.js +687 -0
- package/dist/app/lib/book-checks/taxi.js +215 -0
- package/dist/app/lib/book-checks.js +872 -0
- package/dist/app/lib/books-engine.js +197 -0
- package/dist/app/lib/books-interchange.js +533 -0
- package/dist/app/lib/calculators/bst.js +381 -0
- package/dist/app/lib/calculators/ltd.js +1938 -0
- package/dist/app/lib/calculators/se.js +1239 -0
- package/dist/app/lib/calculators/shared.js +131 -0
- package/dist/app/lib/calculators/taxi.js +461 -0
- package/dist/app/lib/canonical-report-value.js +77 -0
- package/dist/app/lib/compliance-report.js +341 -0
- package/dist/app/lib/diya-gl-calculator.js +52 -0
- package/dist/app/lib/diya-gl-canonical.js +355 -0
- package/dist/app/lib/diya-gl-edits-ltd.js +144 -0
- package/dist/app/lib/diya-gl-edits.js +361 -0
- package/dist/app/lib/diya-gl-loader.js +774 -0
- package/dist/app/lib/diya-gl-schema.js +212 -0
- package/dist/app/lib/generator.js +1912 -0
- package/dist/app/lib/guide.js +33 -0
- package/dist/app/lib/headlines.js +293 -0
- package/dist/app/lib/link-caches.js +421 -0
- package/dist/app/lib/ltd-layout.js +139 -0
- package/dist/app/lib/mcp/diya-gl-tools.js +330 -0
- package/dist/app/lib/mcp/jsonrpc-stdio.js +66 -0
- package/dist/app/lib/mcp/server.js +65 -0
- package/dist/app/lib/overtype-sidecar.js +180 -0
- package/dist/app/lib/package-builder.js +202 -0
- package/dist/app/lib/payslips-date-chain.js +481 -0
- package/dist/app/lib/payslips-layout.js +235 -0
- package/dist/app/lib/period-shift.js +111 -0
- package/dist/app/lib/product-workbook.js +317 -0
- package/dist/app/lib/products.js +27 -0
- package/dist/app/lib/provenance-data.js +36 -0
- package/dist/app/lib/provenance.js +92 -0
- package/dist/app/lib/report-generator.js +374 -0
- package/dist/app/lib/report-indicators.js +468 -0
- package/dist/app/lib/report-serializer.js +392 -0
- package/dist/app/lib/scenario-extractor.js +1403 -0
- package/dist/app/lib/scenario-loader.js +84 -0
- package/dist/app/lib/sitemap-builder.js +59 -0
- package/dist/app/lib/spreadsheet-runner.js +566 -0
- package/dist/app/lib/tax/capital-allowances.js +77 -0
- package/dist/app/lib/tax/corporation-tax.js +144 -0
- package/dist/app/lib/tax/income-tax.js +122 -0
- package/dist/app/lib/tax/mileage.js +69 -0
- package/dist/app/lib/tax/national-insurance.js +31 -0
- package/dist/app/lib/tax/vat.js +188 -0
- package/dist/app/lib/tax-year.js +31 -0
- package/dist/app/lib/template-formula-map.js +253 -0
- package/dist/app/lib/workbook-set.js +153 -0
- package/dist/app/lib/xlsx-exporter.js +2815 -0
- package/dist/app/lib/xlsx-parts.js +111 -0
- package/dist/app/lib/xlsx-reader.js +84 -0
- package/dist/app/products/bst.js +727 -0
- package/dist/app/products/ltd.js +4270 -0
- package/dist/app/products/se.js +3403 -0
- package/dist/app/products/taxi.js +912 -0
- package/dist/app/templates/bst/bst-excel.xlsx +0 -0
- package/dist/app/templates/bst/meta.toml +27 -0
- package/dist/app/templates/ltd/Cashaccount.xlsx +0 -0
- package/dist/app/templates/ltd/Companysecretary.xlsx +0 -0
- package/dist/app/templates/ltd/Creditcardaccount.xlsx +0 -0
- package/dist/app/templates/ltd/Currentaccount.xlsx +0 -0
- package/dist/app/templates/ltd/Dividend Voucher.docx +0 -0
- package/dist/app/templates/ltd/Financialaccounts.xlsx +0 -0
- package/dist/app/templates/ltd/Fixedassets.xlsx +0 -0
- package/dist/app/templates/ltd/Payslips.xlsx +0 -0
- package/dist/app/templates/ltd/Purchases.xlsx +0 -0
- package/dist/app/templates/ltd/Sales.xlsx +0 -0
- package/dist/app/templates/ltd/Salesinvoice.xlsx +0 -0
- package/dist/app/templates/ltd/Savingaccount.xlsx +0 -0
- package/dist/app/templates/ltd/Vatreturns.xlsx +0 -0
- package/dist/app/templates/ltd/expensesform.xlsx +0 -0
- package/dist/app/templates/ltd/meta.toml +54 -0
- package/dist/app/templates/meta.toml +12 -0
- package/dist/app/templates/se/Bank.xlsx +0 -0
- package/dist/app/templates/se/Cash.xlsx +0 -0
- package/dist/app/templates/se/Financialaccounts.xlsx +0 -0
- package/dist/app/templates/se/Fixedassets.xlsx +0 -0
- package/dist/app/templates/se/Payslips.xlsx +0 -0
- package/dist/app/templates/se/Purchases.xlsx +0 -0
- package/dist/app/templates/se/Sales.xlsx +0 -0
- package/dist/app/templates/se/Salesinvoice.xlsx +0 -0
- package/dist/app/templates/se/Vat.xlsx +0 -0
- package/dist/app/templates/se/meta.toml +43 -0
- package/dist/app/templates/taxi/meta.toml +43 -0
- package/dist/app/templates/taxi/taxi-excel.xlsx +0 -0
- package/dist/web/spreadsheets.diyaccounting.co.uk/public/schema/diya-gl-book-v2.schema.json +713 -0
- package/dist/web/spreadsheets.diyaccounting.co.uk/public/schema/diya-gl-docs.md +930 -0
- package/dist/web/spreadsheets.diyaccounting.co.uk/public/schema/diya-gl-lines-v2.schema.json +220 -0
- package/package.json +37 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (C) 2026 DIY Accounting Ltd
|
|
3
|
+
//
|
|
4
|
+
// diya-gl-schema.js — The one validator for diya-gl book.toml and lines.jsonl
|
|
5
|
+
// data. Compiles the published v2 JSON Schemas (draft 2020-12) with ajv, and
|
|
6
|
+
// adds the two rules JSON Schema itself cannot state: that every line's
|
|
7
|
+
// accountMainID names a declared account, and that every
|
|
8
|
+
// diya-gl:hpAgreement, diya-gl:assetID and diya-gl:memberID names an entry
|
|
9
|
+
// on the matching book.toml register.
|
|
10
|
+
|
|
11
|
+
import Ajv2020 from "ajv/dist/2020.js";
|
|
12
|
+
import addFormats from "ajv-formats";
|
|
13
|
+
import standaloneCode from "ajv/dist/standalone/index.js";
|
|
14
|
+
import { readFileSync } from "fs";
|
|
15
|
+
import { resolve, dirname } from "path";
|
|
16
|
+
import { fileURLToPath } from "url";
|
|
17
|
+
import { BOOK_SCHEMA_RESOURCE, LINES_SCHEMA_RESOURCE, nodeResourceLoader } from "./app-resources.js";
|
|
18
|
+
|
|
19
|
+
// The two published schemas compile once, on the first validation rather than
|
|
20
|
+
// on import. A caller that cannot read files — the books page — hands them in
|
|
21
|
+
// with useSchemas() before validating anything; in Node they come off disk.
|
|
22
|
+
let validators = null;
|
|
23
|
+
|
|
24
|
+
function compileSchemas(bookSchema, linesSchema) {
|
|
25
|
+
const ajv = new Ajv2020({ allErrors: true });
|
|
26
|
+
addFormats(ajv);
|
|
27
|
+
return { book: ajv.compile(bookSchema), lines: ajv.compile(linesSchema) };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Supply the two v2 schemas rather than have them read from disk. The books
|
|
32
|
+
* page fetches them and calls this before its first validation.
|
|
33
|
+
* @param {Object} bookSchema - parsed diya-gl-book-v2.schema.json
|
|
34
|
+
* @param {Object} linesSchema - parsed diya-gl-lines-v2.schema.json
|
|
35
|
+
*/
|
|
36
|
+
export function useSchemas(bookSchema, linesSchema) {
|
|
37
|
+
validators = compileSchemas(bookSchema, linesSchema);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Supply the two validators already compiled, rather than have this module
|
|
42
|
+
* compile the schemas itself. They must be shaped exactly like ajv's own
|
|
43
|
+
* compiled validators: called with the parsed data, each returns a boolean
|
|
44
|
+
* and leaves its failures on its own `.errors` property.
|
|
45
|
+
*
|
|
46
|
+
* This is the injection seam for a caller with no `new Function` - a Content
|
|
47
|
+
* Security Policy with no `unsafe-eval` forbids the `new Function` inside
|
|
48
|
+
* ajv.compile, which useSchemas() and loadSchemasFrom() both reach. The
|
|
49
|
+
* books bundle takes a different route to the same result (see
|
|
50
|
+
* scripts/build-books-bundle.mjs: it resolves ajv's own imports to functions
|
|
51
|
+
* generateStandaloneValidatorSource() built ahead of time, so useSchemas()
|
|
52
|
+
* and loadSchemasFrom() keep working unchanged); this function is the direct
|
|
53
|
+
* seam for a caller - a test proving the two validator sources agree, or a
|
|
54
|
+
* future one - that would rather hand the compiled validators in itself.
|
|
55
|
+
* @param {{book: Function, lines: Function}} generated
|
|
56
|
+
*/
|
|
57
|
+
export function useValidators(generated) {
|
|
58
|
+
validators = generated;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Turn the two v2 schemas into the source of a standalone JavaScript module
|
|
63
|
+
* that exports `validateBook` and `validateLines` - ajv's own code
|
|
64
|
+
* generator, run once here rather than by `new Function` every time a page
|
|
65
|
+
* loads. The source still calls a bare `require("ajv-formats/dist/formats")`
|
|
66
|
+
* for its format checks, so a caller bundles it (esbuild, webpack, rollup)
|
|
67
|
+
* before running it anywhere the module has no `require`.
|
|
68
|
+
* @param {Object} bookSchema - parsed diya-gl-book-v2.schema.json
|
|
69
|
+
* @param {Object} linesSchema - parsed diya-gl-lines-v2.schema.json
|
|
70
|
+
* @returns {string} an ES module's source
|
|
71
|
+
*/
|
|
72
|
+
export function generateStandaloneValidatorSource(bookSchema, linesSchema) {
|
|
73
|
+
const ajv = new Ajv2020({ allErrors: true, code: { source: true, esm: true } });
|
|
74
|
+
addFormats(ajv);
|
|
75
|
+
ajv.addSchema(bookSchema, "book");
|
|
76
|
+
ajv.addSchema(linesSchema, "lines");
|
|
77
|
+
return standaloneCode(ajv, { validateBook: "book", validateLines: "lines" });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Load and compile the two schemas through a resource loader, for a caller
|
|
82
|
+
* that has one but would rather not parse the JSON itself.
|
|
83
|
+
* @param {{readText: (path: string) => Promise<string>}} [resources]
|
|
84
|
+
*/
|
|
85
|
+
export async function loadSchemasFrom(resources = nodeResourceLoader()) {
|
|
86
|
+
const [book, lines] = await Promise.all([
|
|
87
|
+
resources.readText(BOOK_SCHEMA_RESOURCE).then(JSON.parse),
|
|
88
|
+
resources.readText(LINES_SCHEMA_RESOURCE).then(JSON.parse),
|
|
89
|
+
]);
|
|
90
|
+
useSchemas(book, lines);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Validation is synchronous, and every caller in the pipeline relies on that,
|
|
94
|
+
// so the Node fallback reads the two files synchronously. A bundle with no
|
|
95
|
+
// file system reaches this only when the page forgot to call useSchemas(), and
|
|
96
|
+
// then it fails loudly rather than validating nothing.
|
|
97
|
+
function compiled() {
|
|
98
|
+
if (validators) return validators;
|
|
99
|
+
try {
|
|
100
|
+
const schemaDir = resolve(
|
|
101
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
102
|
+
"..",
|
|
103
|
+
"..",
|
|
104
|
+
"web",
|
|
105
|
+
"spreadsheets.diyaccounting.co.uk",
|
|
106
|
+
"public",
|
|
107
|
+
"schema",
|
|
108
|
+
);
|
|
109
|
+
const read = (fileName) => JSON.parse(readFileSync(resolve(schemaDir, fileName), "utf8"));
|
|
110
|
+
validators = compileSchemas(read("diya-gl-book-v2.schema.json"), read("diya-gl-lines-v2.schema.json"));
|
|
111
|
+
} catch (cause) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
"the diya-gl schemas could not be read; a caller with no file system has to call useSchemas() or loadSchemasFrom() first",
|
|
114
|
+
{
|
|
115
|
+
cause,
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return validators;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function formatAjvError(prefix, error) {
|
|
123
|
+
return `${prefix}${error.instancePath || "/"} ${error.message}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// TOML dates parse to JS Date instances (smol-toml, and every other TOML
|
|
127
|
+
// parser this book format has used), never to strings. The schema declares
|
|
128
|
+
// them as ISO 8601 strings, matching lines.jsonl and the schema's own JSON
|
|
129
|
+
// Schema "date" format, so a book fresh off the parser is walked once to
|
|
130
|
+
// turn every Date into the string the schema expects before it is checked.
|
|
131
|
+
function withIsoDates(value) {
|
|
132
|
+
if (value instanceof Date) return value.toISOString().slice(0, 10);
|
|
133
|
+
if (Array.isArray(value)) return value.map(withIsoDates);
|
|
134
|
+
if (value && typeof value === "object") {
|
|
135
|
+
const out = {};
|
|
136
|
+
for (const [key, v] of Object.entries(value)) out[key] = withIsoDates(v);
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
return value;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Validate a parsed book.toml against the published diya-gl-book-v2 schema.
|
|
144
|
+
* @param {Object} book - parsed book.toml (TOML dates as JS Date instances, as every TOML parser returns them)
|
|
145
|
+
* @returns {{valid: boolean, errors: string[]}}
|
|
146
|
+
*/
|
|
147
|
+
export function validateBook(book) {
|
|
148
|
+
const validateBookSchema = compiled().book;
|
|
149
|
+
const valid = validateBookSchema(withIsoDates(book));
|
|
150
|
+
const errors = valid ? [] : validateBookSchema.errors.map((e) => formatAjvError("book", e));
|
|
151
|
+
return { valid, errors };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Every section of the chart of accounts a line's accountMainID can name.
|
|
155
|
+
const ACCOUNT_SECTIONS = ["sales", "purchases", "bank", "capital", "assets", "liabilities"];
|
|
156
|
+
|
|
157
|
+
function declaredAccountCodes(book) {
|
|
158
|
+
const codes = new Set();
|
|
159
|
+
for (const section of ACCOUNT_SECTIONS) {
|
|
160
|
+
for (const code of Object.keys(book?.accounts?.[section] || {})) codes.add(code);
|
|
161
|
+
}
|
|
162
|
+
return codes;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function declaredIDs(entries, idField) {
|
|
166
|
+
return new Set((entries || []).map((entry) => entry[idField]).filter((id) => id !== undefined));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Validate a book's lines.jsonl entries against the published
|
|
171
|
+
* diya-gl-lines-v2 schema, then against the book: every accountMainID has
|
|
172
|
+
* to name a declared account, and every diya-gl:hpAgreement /
|
|
173
|
+
* diya-gl:assetID / diya-gl:memberID has to name an entry on the book's own
|
|
174
|
+
* hpAgreements / fixedAssets / members register. A register the book does
|
|
175
|
+
* not declare is not checked, because a product subset with no such
|
|
176
|
+
* register carries no lines that could name one either.
|
|
177
|
+
* @param {Array} lines - parsed lines.jsonl entries
|
|
178
|
+
* @param {Object} book - parsed book.toml, for the referential checks
|
|
179
|
+
* @returns {{valid: boolean, errors: string[]}}
|
|
180
|
+
*/
|
|
181
|
+
export function validateLines(lines, book) {
|
|
182
|
+
const validateLineSchema = compiled().lines;
|
|
183
|
+
const errors = [];
|
|
184
|
+
|
|
185
|
+
const accountCodes = declaredAccountCodes(book);
|
|
186
|
+
const hpAgreementIDs = book?.hpAgreements ? declaredIDs(book.hpAgreements, "agreementID") : null;
|
|
187
|
+
const assetIDs = book?.fixedAssets ? declaredIDs(book.fixedAssets, "assetID") : null;
|
|
188
|
+
const memberIDs = book?.members ? declaredIDs(book.members, "memberID") : null;
|
|
189
|
+
|
|
190
|
+
lines.forEach((line, index) => {
|
|
191
|
+
const label = `line ${index + 1} (${line.entryNumber || "no entryNumber"})`;
|
|
192
|
+
if (!validateLineSchema(line)) {
|
|
193
|
+
for (const e of validateLineSchema.errors) errors.push(formatAjvError(`${label}: `, e));
|
|
194
|
+
}
|
|
195
|
+
if (line.accountMainID !== undefined && !accountCodes.has(line.accountMainID)) {
|
|
196
|
+
errors.push(`${label}: accountMainID "${line.accountMainID}" is not declared in book.toml accounts`);
|
|
197
|
+
}
|
|
198
|
+
if (hpAgreementIDs && line["diya-gl:hpAgreement"] !== undefined && !hpAgreementIDs.has(line["diya-gl:hpAgreement"])) {
|
|
199
|
+
errors.push(
|
|
200
|
+
`${label}: diya-gl:hpAgreement "${line["diya-gl:hpAgreement"]}" does not match any hpAgreements[].agreementID in book.toml`,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
if (assetIDs && line["diya-gl:assetID"] !== undefined && !assetIDs.has(line["diya-gl:assetID"])) {
|
|
204
|
+
errors.push(`${label}: diya-gl:assetID "${line["diya-gl:assetID"]}" does not match any fixedAssets[].assetID in book.toml`);
|
|
205
|
+
}
|
|
206
|
+
if (memberIDs && line["diya-gl:memberID"] !== undefined && !memberIDs.has(line["diya-gl:memberID"])) {
|
|
207
|
+
errors.push(`${label}: diya-gl:memberID "${line["diya-gl:memberID"]}" does not match any members[].memberID in book.toml`);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
return { valid: errors.length === 0, errors };
|
|
212
|
+
}
|