@diy-accounting-uk/diya-gl 1.2.47 → 1.2.49
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 +4 -3
- package/dist/app/bin/view.js +3 -17
- package/dist/app/data/releases.json +52 -0
- package/dist/app/lib/app-resources.js +9 -3
- package/dist/app/lib/calculators/se-derivations.js +2 -13
- package/dist/app/lib/derivations/itsa.js +7 -7
- package/dist/app/lib/derivations/vat-return.js +3 -3
- package/dist/app/lib/package-version.js +28 -0
- package/dist/app/lib/provenance-data.js +1 -1
- package/dist/app/lib/xlsx-exporter.js +27 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -196,9 +196,10 @@ format text is CC BY 4.0, so you can implement the format in anything with attri
|
|
|
196
196
|
The workbook templates are not part of this package. `write-workbook` and the MCP `save_workbook`
|
|
197
197
|
tool fetch the template they need from
|
|
198
198
|
[spreadsheets.diyaccounting.co.uk](https://spreadsheets.diyaccounting.co.uk) the first time they run,
|
|
199
|
-
print the terms, and cache it for later runs. The templates are the company's
|
|
200
|
-
PolyForm Internal Use License 1.0.0 with an additional grant: use them for your
|
|
201
|
-
your clients' accounts if you are an accountant or a bookkeeper, and do not
|
|
199
|
+
print the terms, and cache it for later runs of the same version. The templates are the company's
|
|
200
|
+
own work, under the PolyForm Internal Use License 1.0.0 with an additional grant: use them for your
|
|
201
|
+
own accounts, or for your clients' accounts if you are an accountant or a bookkeeper, and do not
|
|
202
|
+
redistribute them.
|
|
202
203
|
|
|
203
204
|
This repository does not accept contributions.
|
|
204
205
|
|
package/dist/app/bin/view.js
CHANGED
|
@@ -15,30 +15,16 @@
|
|
|
15
15
|
// one directory per package version and origin host.
|
|
16
16
|
|
|
17
17
|
import { spawn } from "child_process";
|
|
18
|
-
import { existsSync, readFileSync } from "fs";
|
|
19
18
|
import { tmpdir } from "os";
|
|
20
|
-
import {
|
|
21
|
-
import { fileURLToPath } from "url";
|
|
19
|
+
import { join, resolve } from "path";
|
|
22
20
|
import { readBookFragment } from "./link.js";
|
|
23
21
|
import { FRAGMENT_KEY } from "../lib/diya-gl-link.js";
|
|
24
22
|
import { DEFAULT_ORIGIN, startViewServer } from "../lib/diya-gl-view-server.js";
|
|
23
|
+
import { packageVersion } from "../lib/package-version.js";
|
|
25
24
|
|
|
26
25
|
const USAGE =
|
|
27
26
|
"Usage: diya-gl view <book directory | diya-gl zip | diya-gl JSON file | workbook> [--port <number>] [--origin <url>] [--no-open]";
|
|
28
27
|
|
|
29
|
-
// The nearest package.json above this file: the package's own in dist/, the
|
|
30
|
-
// repository's in a checkout. Both carry the version the cache is keyed on.
|
|
31
|
-
function packageVersion() {
|
|
32
|
-
let directory = dirname(fileURLToPath(import.meta.url));
|
|
33
|
-
for (;;) {
|
|
34
|
-
const candidate = join(directory, "package.json");
|
|
35
|
-
if (existsSync(candidate)) return JSON.parse(readFileSync(candidate, "utf8")).version;
|
|
36
|
-
const parent = dirname(directory);
|
|
37
|
-
if (parent === directory) throw new Error("No package.json found above " + import.meta.url);
|
|
38
|
-
directory = parent;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
28
|
function parseArgs(argv) {
|
|
43
29
|
const args = argv.slice(2);
|
|
44
30
|
let port = 0;
|
|
@@ -86,7 +72,7 @@ function openInBrowser(url) {
|
|
|
86
72
|
async function main() {
|
|
87
73
|
const { source, port, origin, open } = parseArgs(process.argv);
|
|
88
74
|
const { product, fragment, length, warning } = await readBookFragment(source);
|
|
89
|
-
const cacheDir = join(tmpdir(), "diya-gl-view", packageVersion(), new URL(origin).host.replace(/[^A-Za-z0-9.-]/g, "_"));
|
|
75
|
+
const cacheDir = join(tmpdir(), "diya-gl-view", await packageVersion(), new URL(origin).host.replace(/[^A-Za-z0-9.-]/g, "_"));
|
|
90
76
|
const running = await startViewServer({ cacheDir, origin, port });
|
|
91
77
|
const url = `http://${running.host}:${running.port}/${product}.html#${FRAGMENT_KEY}=${fragment}`;
|
|
92
78
|
console.log(url);
|
|
@@ -1455,6 +1455,58 @@
|
|
|
1455
1455
|
"scorecard": "101608 passed, 0 warnings, 0 failed"
|
|
1456
1456
|
}
|
|
1457
1457
|
}
|
|
1458
|
+
},
|
|
1459
|
+
{
|
|
1460
|
+
"tag": "diya-gl-v1.2.47",
|
|
1461
|
+
"date": "2026-10-05",
|
|
1462
|
+
"formatVersion": "diya-gl/1",
|
|
1463
|
+
"engineVersion": "1.2.47+26b9e6ec",
|
|
1464
|
+
"taxDataHash": "51bdff60e993",
|
|
1465
|
+
"reconciledCommit": "54e8731a91279975e61b1a232fbb67fda89515f0",
|
|
1466
|
+
"templates": {
|
|
1467
|
+
"bst": {
|
|
1468
|
+
"hash": "0d003e10e00c",
|
|
1469
|
+
"scorecard": "812 passed, 0 warnings, 0 failed"
|
|
1470
|
+
},
|
|
1471
|
+
"taxi": {
|
|
1472
|
+
"hash": "1ff69654d64d",
|
|
1473
|
+
"scorecard": "700 passed, 0 warnings, 0 failed"
|
|
1474
|
+
},
|
|
1475
|
+
"se": {
|
|
1476
|
+
"hash": "064605ae76ed",
|
|
1477
|
+
"scorecard": "8015 passed, 0 warnings, 0 failed"
|
|
1478
|
+
},
|
|
1479
|
+
"ltd": {
|
|
1480
|
+
"hash": "a65ab09cf35c",
|
|
1481
|
+
"scorecard": "101608 passed, 0 warnings, 0 failed"
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
},
|
|
1485
|
+
{
|
|
1486
|
+
"tag": "diya-gl-v1.2.48",
|
|
1487
|
+
"date": "2026-10-05",
|
|
1488
|
+
"formatVersion": "diya-gl/1",
|
|
1489
|
+
"engineVersion": "1.2.48+bf25fb49",
|
|
1490
|
+
"taxDataHash": "51bdff60e993",
|
|
1491
|
+
"reconciledCommit": "54e8731a91279975e61b1a232fbb67fda89515f0",
|
|
1492
|
+
"templates": {
|
|
1493
|
+
"bst": {
|
|
1494
|
+
"hash": "0d003e10e00c",
|
|
1495
|
+
"scorecard": "812 passed, 0 warnings, 0 failed"
|
|
1496
|
+
},
|
|
1497
|
+
"taxi": {
|
|
1498
|
+
"hash": "1ff69654d64d",
|
|
1499
|
+
"scorecard": "700 passed, 0 warnings, 0 failed"
|
|
1500
|
+
},
|
|
1501
|
+
"se": {
|
|
1502
|
+
"hash": "064605ae76ed",
|
|
1503
|
+
"scorecard": "8015 passed, 0 warnings, 0 failed"
|
|
1504
|
+
},
|
|
1505
|
+
"ltd": {
|
|
1506
|
+
"hash": "a65ab09cf35c",
|
|
1507
|
+
"scorecard": "101608 passed, 0 warnings, 0 failed"
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1458
1510
|
}
|
|
1459
1511
|
]
|
|
1460
1512
|
}
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
// Node's own modules load on the first read rather than with this file, so a
|
|
20
20
|
// bundle whose caller supplies a loader never pulls them in.
|
|
21
21
|
|
|
22
|
+
import { packageVersion } from "./package-version.js";
|
|
23
|
+
|
|
22
24
|
// The resource space is app/ with one named exception. The two v2 schemas are
|
|
23
25
|
// published by the site, not carried in app/, and the site serves them at
|
|
24
26
|
// /schema/ — so "schema/" is a root of its own rather than a directory under
|
|
@@ -31,7 +33,10 @@ const SCHEMA_ROOT_FROM_APP = ["..", "web", "spreadsheets.diyaccounting.co.uk", "
|
|
|
31
33
|
// checkout has app/templates and reads it straight off disk. A packaged install
|
|
32
34
|
// has not, so the loader takes the same file from the site, which already
|
|
33
35
|
// serves the templates the DIYA-GL pages use, and keeps it in a user cache so
|
|
34
|
-
// every later run works with no network.
|
|
36
|
+
// every later run of the same version works with no network. The cache is keyed
|
|
37
|
+
// on the package version and the source host, because the anchors the reader
|
|
38
|
+
// checks a package against move with the version: a template cached by an
|
|
39
|
+
// older version writes a package the newer reader refuses.
|
|
35
40
|
//
|
|
36
41
|
// Both the current /diya-gl/ and legacy /books/ paths resolve — the old one
|
|
37
42
|
// through a redirect — so published versions built before the path moved keep
|
|
@@ -71,11 +76,12 @@ async function fetchTemplate(path) {
|
|
|
71
76
|
const { dirname, resolve } = await import("path");
|
|
72
77
|
const { homedir } = await import("os");
|
|
73
78
|
|
|
79
|
+
const source = templateSource();
|
|
74
80
|
const cacheRoot = process.env.XDG_CACHE_HOME || resolve(homedir(), ".cache");
|
|
75
|
-
const
|
|
81
|
+
const sourceHost = new URL(source).host.replace(/[^A-Za-z0-9.-]/g, "_");
|
|
82
|
+
const cached = resolve(cacheRoot, "diya-gl", await packageVersion(), sourceHost, path);
|
|
76
83
|
if (existsSync(cached)) return readFileSync(cached);
|
|
77
84
|
|
|
78
|
-
const source = templateSource();
|
|
79
85
|
announceTemplateTerms(source);
|
|
80
86
|
|
|
81
87
|
const url = `${source}/${path}`;
|
|
@@ -12,9 +12,7 @@
|
|
|
12
12
|
// this module never restates a box's API field name in code, so a mapping
|
|
13
13
|
// correction there reaches both derivations with no code change.
|
|
14
14
|
|
|
15
|
-
import
|
|
16
|
-
import { resolve, dirname } from "path";
|
|
17
|
-
import { fileURLToPath } from "url";
|
|
15
|
+
import mapping from "../../data/hmrc/sa103-mtd-mapping.json" with { type: "json" };
|
|
18
16
|
import { diyaGlToScenario } from "../diya-gl-loader.js";
|
|
19
17
|
import { calculateSeCells } from "./se.js";
|
|
20
18
|
import { SE_YEAR_END_MONTH, SBA_CLAIM_ROWS } from "../../products/se.js";
|
|
@@ -22,17 +20,8 @@ import { extractTaxYearStart, parseDate } from "../scenario-loader.js";
|
|
|
22
20
|
import { shiftMonths, periodShiftMonths } from "../period-shift.js";
|
|
23
21
|
import { splitVat } from "../tax/vat.js";
|
|
24
22
|
|
|
25
|
-
let cachedMapping = null;
|
|
26
|
-
|
|
27
|
-
// Resolved on first use, not at import: the engine bundle loads in the browser,
|
|
28
|
-
// where import.meta.url is an http URL and fileURLToPath throws.
|
|
29
23
|
export function loadMapping() {
|
|
30
|
-
|
|
31
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
32
|
-
const mappingPath = resolve(here, "..", "..", "data", "hmrc", "sa103-mtd-mapping.json");
|
|
33
|
-
cachedMapping = JSON.parse(readFileSync(mappingPath, "utf8"));
|
|
34
|
-
}
|
|
35
|
-
return cachedMapping;
|
|
24
|
+
return mapping;
|
|
36
25
|
}
|
|
37
26
|
|
|
38
27
|
function sa103fBoxes() {
|
|
@@ -49,8 +49,8 @@ function taxYearFileNameFor(label) {
|
|
|
49
49
|
return `se-${start}-${end}`;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
async function taxDataFor(book, taxYear) {
|
|
53
|
-
const options = taxYear ? { taxYearName: taxYearFileNameFor(taxYear) } : {};
|
|
52
|
+
async function taxDataFor(book, taxYear, resources) {
|
|
53
|
+
const options = taxYear ? { taxYearName: taxYearFileNameFor(taxYear), resources } : { resources };
|
|
54
54
|
const taxData = await loadTaxDataForBook(book, options);
|
|
55
55
|
const label = taxData?.tax_year?.label;
|
|
56
56
|
if (!label) throw new Error("the tax year file declares no tax_year.label");
|
|
@@ -153,13 +153,13 @@ function periodLabel(period) {
|
|
|
153
153
|
* @param {Object} book - parsed book.toml
|
|
154
154
|
* @param {Array} lines - parsed lines.jsonl entries
|
|
155
155
|
* @param {{periodEndDate?: string, quarterlyPeriodType?: "standard"|"calendar", taxYear?: string}} [params] -
|
|
156
|
-
* quarterlyPeriodType defaults to standard (6 April quarters); taxYear (as 2025-26) overrides the year the
|
|
156
|
+
* resources is the loader the tax data reads through (defaults to reading app/); quarterlyPeriodType defaults to standard (6 April quarters); taxYear (as 2025-26) overrides the year the
|
|
157
157
|
* book's dates imply
|
|
158
158
|
* @returns {Promise<Object>} { taxYear, shape, quarterlyPeriodType, fieldSlots, periods, warnings }
|
|
159
159
|
*/
|
|
160
|
-
export async function deriveItsaQuarterlyUpdate(book, lines, { periodEndDate, quarterlyPeriodType, taxYear } = {}) {
|
|
160
|
+
export async function deriveItsaQuarterlyUpdate(book, lines, { periodEndDate, quarterlyPeriodType, taxYear, resources } = {}) {
|
|
161
161
|
requireSelfEmployedBook(book, lines, "deriveItsaQuarterlyUpdate");
|
|
162
|
-
const taxData = await taxDataFor(book, taxYear);
|
|
162
|
+
const taxData = await taxDataFor(book, taxYear, resources);
|
|
163
163
|
const label = taxData.tax_year.label;
|
|
164
164
|
const shape = loadMapping().api.years[label].quarterly;
|
|
165
165
|
const periodType = quarterlyPeriodType || "standard";
|
|
@@ -217,9 +217,9 @@ export async function deriveItsaQuarterlyUpdate(book, lines, { periodEndDate, qu
|
|
|
217
217
|
* @param {{taxYear?: string}} [params] - taxYear (as 2025-26) overrides the year the book's dates imply
|
|
218
218
|
* @returns {Promise<Object>} { taxYear, fieldSlots, allowances, adjustments, omitted, warnings }
|
|
219
219
|
*/
|
|
220
|
-
export async function deriveItsaAnnualSubmission(book, lines, { taxYear } = {}) {
|
|
220
|
+
export async function deriveItsaAnnualSubmission(book, lines, { taxYear, resources } = {}) {
|
|
221
221
|
requireSelfEmployedBook(book, lines, "deriveItsaAnnualSubmission");
|
|
222
|
-
const taxData = await taxDataFor(book, taxYear);
|
|
222
|
+
const taxData = await taxDataFor(book, taxYear, resources);
|
|
223
223
|
const label = taxData.tax_year.label;
|
|
224
224
|
const slots = annualFieldSlotsForTaxYear(label);
|
|
225
225
|
|
|
@@ -141,12 +141,12 @@ function reconcile(label, attributed, engine) {
|
|
|
141
141
|
* @param {Object} book - parsed book.toml
|
|
142
142
|
* @param {Array} lines - parsed lines.jsonl entries
|
|
143
143
|
* @param {{periodEnd: string, periodStart?: string, periodKey?: string}} params - periodEnd is the
|
|
144
|
-
* obligation's period end (YYYY-MM-DD); periodStart, when given, must open that quarter; periodKey is
|
|
144
|
+
* obligation's period end (YYYY-MM-DD); resources is the loader the tax data reads through (defaults to reading app/); periodStart, when given, must open that quarter; periodKey is
|
|
145
145
|
* echoed back untouched
|
|
146
146
|
* @returns {Promise<Object>} { vatRegistrationNumber, periodKey, periodStart, periodEnd, dueDate, scheme,
|
|
147
147
|
* months, boxes, hmrc, lines, notes }
|
|
148
148
|
*/
|
|
149
|
-
export async function deriveVatReturn(book, lines, { periodEnd, periodStart, periodKey } = {}) {
|
|
149
|
+
export async function deriveVatReturn(book, lines, { periodEnd, periodStart, periodKey, resources } = {}) {
|
|
150
150
|
if (!book || !lines) throw new Error("deriveVatReturn requires a book and its lines");
|
|
151
151
|
if (!periodEnd) throw new Error("deriveVatReturn requires periodEnd (the obligation's period end, YYYY-MM-DD)");
|
|
152
152
|
const entity = book.entityInformation ?? {};
|
|
@@ -157,7 +157,7 @@ export async function deriveVatReturn(book, lines, { periodEnd, periodStart, per
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
const product = productOf(book);
|
|
160
|
-
const taxData = await loadTaxDataForBook(book);
|
|
160
|
+
const taxData = await loadTaxDataForBook(book, { resources });
|
|
161
161
|
const results = calculateFromDiyaGl(book, lines, product, taxData, diyaGlToScenario(book, lines, product));
|
|
162
162
|
const rows = interfaceRows(results);
|
|
163
163
|
const wantedEnd = isoDate(periodEnd);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2006-2026 DIY Accounting Limited
|
|
3
|
+
//
|
|
4
|
+
// package-version.js — the version of the engine that is running, read from
|
|
5
|
+
// the nearest package.json above this file: the package's own in an install,
|
|
6
|
+
// the repository's in a checkout. Both carry the same version, and the caches
|
|
7
|
+
// of files fetched from the site are keyed on it.
|
|
8
|
+
//
|
|
9
|
+
// Node's own modules load on the call rather than with this file, so a bundle
|
|
10
|
+
// that never asks for the version never pulls them in.
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @returns {Promise<string>}
|
|
14
|
+
*/
|
|
15
|
+
export async function packageVersion() {
|
|
16
|
+
const { existsSync, readFileSync } = await import("fs");
|
|
17
|
+
const { dirname, join } = await import("path");
|
|
18
|
+
const { fileURLToPath } = await import("url");
|
|
19
|
+
|
|
20
|
+
let directory = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
for (;;) {
|
|
22
|
+
const candidate = join(directory, "package.json");
|
|
23
|
+
if (existsSync(candidate)) return JSON.parse(readFileSync(candidate, "utf8")).version;
|
|
24
|
+
const parent = dirname(directory);
|
|
25
|
+
if (parent === directory) throw new Error("No package.json found above " + import.meta.url);
|
|
26
|
+
directory = parent;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -2258,6 +2258,27 @@ async function seAccountingPeriodFromSheet(set) {
|
|
|
2258
2258
|
return { start, end };
|
|
2259
2259
|
}
|
|
2260
2260
|
|
|
2261
|
+
// The Taxi package's own accounting period. Its end is the box "Date your
|
|
2262
|
+
// books or accounts are made up to" (Business Details!N25, a formula on
|
|
2263
|
+
// Admin!B17, the 5 April the writer sets for the package's tax year); the
|
|
2264
|
+
// period opens the day after the same date a year earlier. periodCovered()'s
|
|
2265
|
+
// guess would run the period 1 April to 31 March instead.
|
|
2266
|
+
const TAXI_PERIOD_END_CELL = { sheet: "Business Details", cell: "N25" };
|
|
2267
|
+
|
|
2268
|
+
async function taxiAccountingPeriodFromSheet(set) {
|
|
2269
|
+
const workbookName = singleWorkbookName(set);
|
|
2270
|
+
const sheet = await openSheet(await openWorkbook(set, workbookName), TAXI_PERIOD_END_CELL.sheet);
|
|
2271
|
+
const end = sheet ? dateAt(sheet.xml, TAXI_PERIOD_END_CELL.cell, sheet.sharedStrings) : undefined;
|
|
2272
|
+
if (!end) {
|
|
2273
|
+
throw new Error(
|
|
2274
|
+
`${workbookName}: no date in ${TAXI_PERIOD_END_CELL.sheet}!${TAXI_PERIOD_END_CELL.cell}, the date the books are made up to`,
|
|
2275
|
+
);
|
|
2276
|
+
}
|
|
2277
|
+
const [year, month, day] = end.split("-").map(Number);
|
|
2278
|
+
const start = new Date(Date.UTC(year - 1, month - 1, day + 1)).toISOString().slice(0, 10);
|
|
2279
|
+
return { start, end };
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2261
2282
|
function assign(target, key, value) {
|
|
2262
2283
|
if (value !== undefined && value !== "" && value !== 0) target[key] = value;
|
|
2263
2284
|
}
|
|
@@ -2685,11 +2706,13 @@ export async function extractBook(set, product, lines, cellMap, options = {}) {
|
|
|
2685
2706
|
}
|
|
2686
2707
|
}
|
|
2687
2708
|
|
|
2688
|
-
// SE
|
|
2689
|
-
// whose dates do not run April to March still has every
|
|
2690
|
-
// onto the package's own April-March tab grid.
|
|
2709
|
+
// SE and Taxi read their true accounting period off the printed boxes,
|
|
2710
|
+
// since a book whose dates do not run April to March still has every
|
|
2711
|
+
// posting written onto the package's own April-March tab grid.
|
|
2691
2712
|
const period =
|
|
2692
|
-
|
|
2713
|
+
product === "taxi"
|
|
2714
|
+
? await taxiAccountingPeriodFromSheet(set)
|
|
2715
|
+
: (product === "se" && (await seAccountingPeriodFromSheet(set))) || periodCovered(await extractPeriodStartMonth(set, product), lines);
|
|
2693
2716
|
const book = {
|
|
2694
2717
|
documentInfo: {
|
|
2695
2718
|
entriesType: "journal",
|