@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.
Files changed (157) hide show
  1. package/README.md +63 -0
  2. package/bin/diya-gl-mcp.js +10 -0
  3. package/bin/diya-gl-read-workbook.js +11 -0
  4. package/bin/diya-gl-recalc.js +10 -0
  5. package/bin/diya-gl-write-workbook.js +12 -0
  6. package/bin/diya-gl.js +24 -0
  7. package/bin/run-dist-bin.js +23 -0
  8. package/dist/app/bin/build-diya-gl-spec.js +680 -0
  9. package/dist/app/bin/build-packages.js +149 -0
  10. package/dist/app/bin/build-reconciliation-pages.js +1052 -0
  11. package/dist/app/bin/build-sitemaps.js +51 -0
  12. package/dist/app/bin/compliance-report.js +125 -0
  13. package/dist/app/bin/cross-package-reconciliation.js +92 -0
  14. package/dist/app/bin/diya-gl-mcp.js +27 -0
  15. package/dist/app/bin/export.js +318 -0
  16. package/dist/app/bin/extract-scenarios.js +1348 -0
  17. package/dist/app/bin/generate.js +353 -0
  18. package/dist/app/bin/judge-reconciliation.js +649 -0
  19. package/dist/app/bin/reconcile.js +247 -0
  20. package/dist/app/bin/report.js +291 -0
  21. package/dist/app/bin/verify-roundtrip.js +941 -0
  22. package/dist/app/bin/verify-stability.js +429 -0
  23. package/dist/app/bin/write-workbook.js +106 -0
  24. package/dist/app/data/filing/ct-computation-v1.1.toml +1033 -0
  25. package/dist/app/data/filing/ct600-v3.toml +2329 -0
  26. package/dist/app/data/filing/frs105-formats.toml +244 -0
  27. package/dist/app/data/fixture-master-gaps.json +247 -0
  28. package/dist/app/data/hmrc/SOURCE.md +14 -0
  29. package/dist/app/data/hmrc/form-layouts/bst.json +116 -0
  30. package/dist/app/data/hmrc/form-layouts/ltd.json +452 -0
  31. package/dist/app/data/hmrc/form-layouts/se.json +485 -0
  32. package/dist/app/data/hmrc/form-layouts/taxi.json +117 -0
  33. package/dist/app/data/hmrc/sa103-mtd-mapping.json +1078 -0
  34. package/dist/app/data/hmrc/sa103f_mapping_v3.csv +111 -0
  35. package/dist/app/data/judge-rubric.md +52 -0
  36. package/dist/app/data/ltd-2020.toml +49 -0
  37. package/dist/app/data/ltd-2021.toml +49 -0
  38. package/dist/app/data/ltd-2022.toml +49 -0
  39. package/dist/app/data/ltd-2023.toml +49 -0
  40. package/dist/app/data/ltd-2024.toml +49 -0
  41. package/dist/app/data/ltd-2025.toml +49 -0
  42. package/dist/app/data/ltd-2026.toml +53 -0
  43. package/dist/app/data/ltd-2027.toml +51 -0
  44. package/dist/app/data/render-unrepresentable/bst.json +24 -0
  45. package/dist/app/data/render-unrepresentable/ltd.json +863 -0
  46. package/dist/app/data/render-unrepresentable/se.json +455 -0
  47. package/dist/app/data/render-unrepresentable/taxi.json +16 -0
  48. package/dist/app/data/roundtrip-budget.json +38 -0
  49. package/dist/app/data/roundtrip-matrix-budget.json +26 -0
  50. package/dist/app/data/roundtrip-unrepresentable.json +401 -0
  51. package/dist/app/data/se-2020-2021.toml +52 -0
  52. package/dist/app/data/se-2021-2022.toml +52 -0
  53. package/dist/app/data/se-2022-2023.toml +55 -0
  54. package/dist/app/data/se-2023-2024.toml +54 -0
  55. package/dist/app/data/se-2024-2025.toml +51 -0
  56. package/dist/app/data/se-2025-2026.toml +51 -0
  57. package/dist/app/data/se-2026-2027.toml +51 -0
  58. package/dist/app/data/volatile-cells.json +16 -0
  59. package/dist/app/lib/anchors/bst.js +79 -0
  60. package/dist/app/lib/anchors/ltd.js +626 -0
  61. package/dist/app/lib/anchors/run.js +100 -0
  62. package/dist/app/lib/anchors/se.js +456 -0
  63. package/dist/app/lib/anchors/taxi.js +164 -0
  64. package/dist/app/lib/app-resources.js +80 -0
  65. package/dist/app/lib/book-checks/ltd.js +408 -0
  66. package/dist/app/lib/book-checks/se.js +687 -0
  67. package/dist/app/lib/book-checks/taxi.js +215 -0
  68. package/dist/app/lib/book-checks.js +872 -0
  69. package/dist/app/lib/books-engine.js +197 -0
  70. package/dist/app/lib/books-interchange.js +533 -0
  71. package/dist/app/lib/calculators/bst.js +381 -0
  72. package/dist/app/lib/calculators/ltd.js +1938 -0
  73. package/dist/app/lib/calculators/se.js +1239 -0
  74. package/dist/app/lib/calculators/shared.js +131 -0
  75. package/dist/app/lib/calculators/taxi.js +461 -0
  76. package/dist/app/lib/canonical-report-value.js +77 -0
  77. package/dist/app/lib/compliance-report.js +341 -0
  78. package/dist/app/lib/diya-gl-calculator.js +52 -0
  79. package/dist/app/lib/diya-gl-canonical.js +355 -0
  80. package/dist/app/lib/diya-gl-edits-ltd.js +144 -0
  81. package/dist/app/lib/diya-gl-edits.js +361 -0
  82. package/dist/app/lib/diya-gl-loader.js +774 -0
  83. package/dist/app/lib/diya-gl-schema.js +212 -0
  84. package/dist/app/lib/generator.js +1912 -0
  85. package/dist/app/lib/guide.js +33 -0
  86. package/dist/app/lib/headlines.js +293 -0
  87. package/dist/app/lib/link-caches.js +421 -0
  88. package/dist/app/lib/ltd-layout.js +139 -0
  89. package/dist/app/lib/mcp/diya-gl-tools.js +330 -0
  90. package/dist/app/lib/mcp/jsonrpc-stdio.js +66 -0
  91. package/dist/app/lib/mcp/server.js +65 -0
  92. package/dist/app/lib/overtype-sidecar.js +180 -0
  93. package/dist/app/lib/package-builder.js +202 -0
  94. package/dist/app/lib/payslips-date-chain.js +481 -0
  95. package/dist/app/lib/payslips-layout.js +235 -0
  96. package/dist/app/lib/period-shift.js +111 -0
  97. package/dist/app/lib/product-workbook.js +317 -0
  98. package/dist/app/lib/products.js +27 -0
  99. package/dist/app/lib/provenance-data.js +36 -0
  100. package/dist/app/lib/provenance.js +92 -0
  101. package/dist/app/lib/report-generator.js +374 -0
  102. package/dist/app/lib/report-indicators.js +468 -0
  103. package/dist/app/lib/report-serializer.js +392 -0
  104. package/dist/app/lib/scenario-extractor.js +1403 -0
  105. package/dist/app/lib/scenario-loader.js +84 -0
  106. package/dist/app/lib/sitemap-builder.js +59 -0
  107. package/dist/app/lib/spreadsheet-runner.js +566 -0
  108. package/dist/app/lib/tax/capital-allowances.js +77 -0
  109. package/dist/app/lib/tax/corporation-tax.js +144 -0
  110. package/dist/app/lib/tax/income-tax.js +122 -0
  111. package/dist/app/lib/tax/mileage.js +69 -0
  112. package/dist/app/lib/tax/national-insurance.js +31 -0
  113. package/dist/app/lib/tax/vat.js +188 -0
  114. package/dist/app/lib/tax-year.js +31 -0
  115. package/dist/app/lib/template-formula-map.js +253 -0
  116. package/dist/app/lib/workbook-set.js +153 -0
  117. package/dist/app/lib/xlsx-exporter.js +2815 -0
  118. package/dist/app/lib/xlsx-parts.js +111 -0
  119. package/dist/app/lib/xlsx-reader.js +84 -0
  120. package/dist/app/products/bst.js +727 -0
  121. package/dist/app/products/ltd.js +4270 -0
  122. package/dist/app/products/se.js +3403 -0
  123. package/dist/app/products/taxi.js +912 -0
  124. package/dist/app/templates/bst/bst-excel.xlsx +0 -0
  125. package/dist/app/templates/bst/meta.toml +27 -0
  126. package/dist/app/templates/ltd/Cashaccount.xlsx +0 -0
  127. package/dist/app/templates/ltd/Companysecretary.xlsx +0 -0
  128. package/dist/app/templates/ltd/Creditcardaccount.xlsx +0 -0
  129. package/dist/app/templates/ltd/Currentaccount.xlsx +0 -0
  130. package/dist/app/templates/ltd/Dividend Voucher.docx +0 -0
  131. package/dist/app/templates/ltd/Financialaccounts.xlsx +0 -0
  132. package/dist/app/templates/ltd/Fixedassets.xlsx +0 -0
  133. package/dist/app/templates/ltd/Payslips.xlsx +0 -0
  134. package/dist/app/templates/ltd/Purchases.xlsx +0 -0
  135. package/dist/app/templates/ltd/Sales.xlsx +0 -0
  136. package/dist/app/templates/ltd/Salesinvoice.xlsx +0 -0
  137. package/dist/app/templates/ltd/Savingaccount.xlsx +0 -0
  138. package/dist/app/templates/ltd/Vatreturns.xlsx +0 -0
  139. package/dist/app/templates/ltd/expensesform.xlsx +0 -0
  140. package/dist/app/templates/ltd/meta.toml +54 -0
  141. package/dist/app/templates/meta.toml +12 -0
  142. package/dist/app/templates/se/Bank.xlsx +0 -0
  143. package/dist/app/templates/se/Cash.xlsx +0 -0
  144. package/dist/app/templates/se/Financialaccounts.xlsx +0 -0
  145. package/dist/app/templates/se/Fixedassets.xlsx +0 -0
  146. package/dist/app/templates/se/Payslips.xlsx +0 -0
  147. package/dist/app/templates/se/Purchases.xlsx +0 -0
  148. package/dist/app/templates/se/Sales.xlsx +0 -0
  149. package/dist/app/templates/se/Salesinvoice.xlsx +0 -0
  150. package/dist/app/templates/se/Vat.xlsx +0 -0
  151. package/dist/app/templates/se/meta.toml +43 -0
  152. package/dist/app/templates/taxi/meta.toml +43 -0
  153. package/dist/app/templates/taxi/taxi-excel.xlsx +0 -0
  154. package/dist/web/spreadsheets.diyaccounting.co.uk/public/schema/diya-gl-book-v2.schema.json +713 -0
  155. package/dist/web/spreadsheets.diyaccounting.co.uk/public/schema/diya-gl-docs.md +930 -0
  156. package/dist/web/spreadsheets.diyaccounting.co.uk/public/schema/diya-gl-lines-v2.schema.json +220 -0
  157. package/package.json +37 -0
@@ -0,0 +1,353 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+ // Copyright (C) 2026 DIY Accounting Ltd
4
+ //
5
+ // generate.js — CLI entry point for spreadsheet and guide generation.
6
+ // Dispatches to product modules in app/products/ which define their own metadata.
7
+ //
8
+ // Usage:
9
+ // node app/bin/generate.js # all packages, all years
10
+ // node app/bin/generate.js --package bst # Basic Sole Trader only
11
+ // node app/bin/generate.js --package taxi # Taxi Driver only
12
+ // node app/bin/generate.js --years se-2024-2025 se-2025-2026 # specific years
13
+ // node app/bin/generate.js --skip-guide # spreadsheets only, no PDF
14
+ // node app/bin/generate.js --source-date-epoch 1711670400 # override PDF timestamp
15
+
16
+ import { parse as parseTOML } from "smol-toml";
17
+ import { readFileSync, writeFileSync, mkdirSync, readdirSync } from "fs";
18
+ import { resolve, dirname } from "path";
19
+ import { fileURLToPath } from "url";
20
+ import { generateSpreadsheet, packageNaming, applyYearEndSequence, monthEnd } from "../lib/generator.js";
21
+ import { generatePdf } from "../lib/guide.js";
22
+ import { runSpreadsheet, runMultiFileSpreadsheet } from "../lib/spreadsheet-runner.js";
23
+ import { loadDiyaGlData, diyaGlToScenario } from "../lib/diya-gl-loader.js";
24
+ import { saveWorkbook } from "../lib/product-workbook.js";
25
+ import { productModule } from "../lib/products.js";
26
+ import { PRODUCT as BST } from "../products/bst.js";
27
+ import { PRODUCT as TAXI } from "../products/taxi.js";
28
+ import { PRODUCT as SE } from "../products/se.js";
29
+ import { PRODUCT as LTD } from "../products/ltd.js";
30
+
31
+ const __dirname = dirname(fileURLToPath(import.meta.url));
32
+ const APP_DIR = resolve(__dirname, "..");
33
+ const ROOT = resolve(APP_DIR, "..");
34
+ const DATA_DIR = resolve(APP_DIR, "data");
35
+ const OUTPUT_DIR = resolve(ROOT, "packages");
36
+
37
+ const PRODUCTS = {
38
+ bst: BST,
39
+ taxi: TAXI,
40
+ se: SE,
41
+ ltd: LTD,
42
+ };
43
+
44
+ async function generateProduct(productDir, tomlPath, sourceDateEpoch, skipGuide, outputDir, opts = {}) {
45
+ const productMeta = parseTOML(readFileSync(resolve(productDir, "meta.toml"), "utf8"));
46
+ const sharedMeta = parseTOML(readFileSync(resolve(APP_DIR, "templates", "meta.toml"), "utf8"));
47
+
48
+ const taxData = opts.overrideTaxData || parseTOML(readFileSync(tomlPath, "utf8"));
49
+ const ty = taxData.tax_year || taxData.financial_year;
50
+ const yearEndMonth = opts.yearEndMonth || 0;
51
+ const endDate = new Date(ty.end);
52
+
53
+ // Skip packages whose year-end is more than 14 months from now.
54
+ // 14 months ensures the next tax year's packages are available from early March
55
+ // (covers the budget-in-March scenario where SE Apr 2027 is needed by 6 Mar 2026).
56
+ // The website download default uses a tighter 13-month window for the pre-selected year.
57
+ const cutoff = new Date();
58
+ cutoff.setUTCMonth(cutoff.getUTCMonth() + 14);
59
+ cutoff.setUTCDate(0); // last day of the month 13 months from now
60
+ if (endDate > cutoff) {
61
+ console.log(
62
+ `\nSkipping ${productMeta.product.name} for ${ty.label} (year-end ${endDate.toISOString().slice(0, 10)} is beyond cutoff ${cutoff.toISOString().slice(0, 10)})`,
63
+ );
64
+ return null;
65
+ }
66
+
67
+ console.log(`\nGenerating ${productMeta.product.name} for ${ty.label}...`);
68
+
69
+ const { dirName, xlsxFilename } = packageNaming(productMeta, sharedMeta, endDate);
70
+
71
+ const outDir = resolve(outputDir, dirName);
72
+ mkdirSync(outDir, { recursive: true });
73
+
74
+ if (productMeta.template.files) {
75
+ for (const templateFile of productMeta.template.files) {
76
+ let buffer = readFileSync(resolve(productDir, templateFile));
77
+ const fileKey = templateFile.replace(".xlsx", "").replace(".docx", "").toLowerCase();
78
+ const sheetsConfig = productMeta.sheets?.[fileKey];
79
+
80
+ if (sheetsConfig && Object.keys(sheetsConfig).length > 0) {
81
+ buffer = await generateSpreadsheet(buffer, taxData, sheetsConfig);
82
+ }
83
+
84
+ buffer = await applyYearEndSequence(buffer, templateFile, sheetsConfig, yearEndMonth, endDate, ty);
85
+
86
+ writeFileSync(resolve(outDir, templateFile), buffer);
87
+ }
88
+ console.log(` Written: ${dirName}/`);
89
+ console.log(` ${productMeta.template.files.length} files`);
90
+ } else {
91
+ // Single-file product (BST, Taxi)
92
+ const templatePath = resolve(productDir, productMeta.template.spreadsheet);
93
+ const templateBuffer = readFileSync(templatePath);
94
+
95
+ const xlsxBuffer = await generateSpreadsheet(templateBuffer, taxData, productMeta.sheets);
96
+
97
+ writeFileSync(resolve(outDir, xlsxFilename), xlsxBuffer);
98
+ console.log(` Written: ${dirName}/`);
99
+ console.log(` ${xlsxFilename}`);
100
+ }
101
+
102
+ // Generate PDF guides
103
+ if (!skipGuide) {
104
+ const guides = [];
105
+ if (productMeta.template.guide) {
106
+ guides.push({ md: productMeta.template.guide, pdf: productMeta.output.guide_filename });
107
+ }
108
+ if (productMeta.template.payslip_guide) {
109
+ guides.push({ md: productMeta.template.payslip_guide, pdf: productMeta.output.payslip_guide_filename });
110
+ }
111
+ for (const { md, pdf } of guides) {
112
+ const guideMd = resolve(productDir, md);
113
+ const guidePdf = resolve(outDir, pdf);
114
+ try {
115
+ await generatePdf(guideMd, guidePdf, sourceDateEpoch);
116
+ console.log(` Guide: ${pdf}`);
117
+ } catch (e) {
118
+ console.warn(` Warning: PDF guide generation failed (${pdf}) — ${e.message}`);
119
+ }
120
+ }
121
+ }
122
+
123
+ return { dirName, taxYear: ty.label, taxData };
124
+ }
125
+
126
+ function parseArgs(argv) {
127
+ const args = argv.slice(2);
128
+
129
+ let packageFilter = "all";
130
+ const pkgIdx = args.indexOf("--package");
131
+ if (pkgIdx !== -1 && args[pkgIdx + 1]) {
132
+ packageFilter = args[pkgIdx + 1];
133
+ }
134
+
135
+ let tomlFiles;
136
+ const yearsIdx = args.indexOf("--years");
137
+ if (yearsIdx !== -1) {
138
+ const years = [];
139
+ for (let i = yearsIdx + 1; i < args.length; i++) {
140
+ if (args[i].startsWith("--")) break;
141
+ years.push(args[i]);
142
+ }
143
+ tomlFiles = years.map((y) => resolve(DATA_DIR, `${y}.toml`));
144
+ } else {
145
+ tomlFiles = readdirSync(DATA_DIR)
146
+ .filter((f) => f.endsWith(".toml"))
147
+ .sort()
148
+ .map((f) => resolve(DATA_DIR, f));
149
+ }
150
+
151
+ let sourceDateEpoch;
152
+ const epochIdx = args.indexOf("--source-date-epoch");
153
+ if (epochIdx !== -1 && args[epochIdx + 1]) {
154
+ sourceDateEpoch = parseInt(args[epochIdx + 1], 10);
155
+ }
156
+
157
+ const skipGuide = args.includes("--skip-guide");
158
+
159
+ let yearEndFilter = null;
160
+ const yeIdx = args.indexOf("--year-end");
161
+ if (yeIdx !== -1) {
162
+ yearEndFilter = [];
163
+ for (let i = yeIdx + 1; i < args.length; i++) {
164
+ if (args[i].startsWith("--")) break;
165
+ yearEndFilter.push(args[i]);
166
+ }
167
+ }
168
+
169
+ let dataDir = null;
170
+ const dataIdx = args.indexOf("--data");
171
+ if (dataIdx !== -1 && args[dataIdx + 1]) {
172
+ dataDir = args[dataIdx + 1];
173
+ }
174
+
175
+ let outputDir = null;
176
+ const outIdx = args.indexOf("--output-dir");
177
+ if (outIdx !== -1) {
178
+ const value = args[outIdx + 1];
179
+ if (!value || value.startsWith("--")) {
180
+ throw new Error("--output-dir requires a path argument");
181
+ }
182
+ outputDir = value;
183
+ }
184
+
185
+ let offset = null;
186
+ const offIdx = args.indexOf("--offset");
187
+ if (offIdx !== -1 && args[offIdx + 1]) {
188
+ offset = args[offIdx + 1];
189
+ }
190
+
191
+ return { packageFilter, tomlFiles, sourceDateEpoch, skipGuide, yearEndFilter, dataDir, outputDir, offset };
192
+ }
193
+
194
+ export async function main() {
195
+ console.log("=== generate.js ===");
196
+
197
+ const {
198
+ packageFilter,
199
+ tomlFiles,
200
+ sourceDateEpoch,
201
+ skipGuide,
202
+ yearEndFilter,
203
+ dataDir,
204
+ outputDir: outputDirOverride,
205
+ offset,
206
+ } = parseArgs(process.argv);
207
+
208
+ // Determine which products to generate
209
+ const productsToGenerate = packageFilter === "all" ? Object.entries(PRODUCTS) : [[packageFilter, PRODUCTS[packageFilter]]];
210
+
211
+ for (const [key, product] of productsToGenerate) {
212
+ if (!product) {
213
+ console.error(`Unknown package: ${key}. Available: ${Object.keys(PRODUCTS).join(", ")}`);
214
+ process.exit(1);
215
+ }
216
+ }
217
+
218
+ const outputDir = outputDirOverride ? resolve(outputDirOverride) : OUTPUT_DIR;
219
+
220
+ console.log("Package: ", packageFilter === "all" ? "all" : packageFilter);
221
+ console.log(
222
+ "Tax data:",
223
+ tomlFiles.map((f) => f.split("/").pop()),
224
+ );
225
+ console.log("Output: ", outputDir);
226
+
227
+ mkdirSync(outputDir, { recursive: true });
228
+
229
+ const results = [];
230
+ for (const [, product] of productsToGenerate) {
231
+ const productDir = resolve(APP_DIR, "templates", product.dir);
232
+ const productMeta = parseTOML(readFileSync(resolve(productDir, "meta.toml"), "utf8"));
233
+
234
+ for (const tomlFile of tomlFiles) {
235
+ const tomlName = tomlFile.split("/").pop();
236
+ if (!tomlName.startsWith(productMeta.product.tax_regime + "-")) continue;
237
+
238
+ if (productMeta.product.tax_regime === "ltd") {
239
+ const taxData = parseTOML(readFileSync(tomlFile, "utf8"));
240
+ const fyStart = new Date(taxData.financial_year.start);
241
+ const fyEnd = new Date(taxData.financial_year.end);
242
+
243
+ for (let m = 0; m < 12; m++) {
244
+ const meYear = fyStart.getUTCFullYear() + Math.floor((fyStart.getUTCMonth() + m) / 12);
245
+ const meMonth = (fyStart.getUTCMonth() + m) % 12;
246
+ const yearEndDate = monthEnd(meYear, meMonth + 1);
247
+
248
+ if (yearEndDate > fyEnd) break;
249
+
250
+ const yeStr = yearEndDate.toISOString().slice(0, 10);
251
+ if (yearEndFilter && !yearEndFilter.includes(yeStr)) continue;
252
+
253
+ const overrideTaxData = JSON.parse(JSON.stringify(taxData));
254
+ overrideTaxData.financial_year.end = yearEndDate.toISOString().slice(0, 10);
255
+
256
+ const yearEndMonth = yearEndDate.getUTCMonth() + 1;
257
+ const result = await generateProduct(productDir, tomlFile, sourceDateEpoch, skipGuide, outputDir, {
258
+ overrideTaxData,
259
+ yearEndMonth,
260
+ });
261
+ if (result) results.push(result);
262
+ }
263
+ } else {
264
+ if (yearEndFilter) {
265
+ const td = parseTOML(readFileSync(tomlFile, "utf8"));
266
+ const tyEnd = (td.tax_year || td.financial_year).end;
267
+ const tyEndStr = typeof tyEnd === "string" ? tyEnd : new Date(tyEnd).toISOString().slice(0, 10);
268
+ if (!yearEndFilter.includes(tyEndStr)) continue;
269
+ }
270
+ const result = await generateProduct(productDir, tomlFile, sourceDateEpoch, skipGuide, outputDir);
271
+ if (result) results.push(result);
272
+ }
273
+ }
274
+ }
275
+
276
+ console.log(`\n=== Generated ${results.length} packages ===`);
277
+ for (const r of results) {
278
+ console.log(` ${r.taxYear}: ${r.dirName}/`);
279
+ }
280
+
281
+ // If --data provided, inject diya-gl data into the generated package and recalculate
282
+ if (dataDir && results.length > 0) {
283
+ if (!PRODUCTS[packageFilter]) {
284
+ console.error(`--data requires --package (not 'all'). Got: ${packageFilter}`);
285
+ process.exit(1);
286
+ }
287
+ const productMod = productModule(packageFilter);
288
+
289
+ const { book, lines } = loadDiyaGlData(resolve(dataDir), offset);
290
+
291
+ // Use the last generated package (most recent year-end)
292
+ const lastResult = results[results.length - 1];
293
+ const pkgDir = resolve(outputDir, lastResult.dirName);
294
+ // export.js and CI read the populated package flat from --output-dir (no
295
+ // dirName nesting), so an override still lands the recalculated files
296
+ // there directly; without one, recalculation happens in place.
297
+ const finalOutputDir = outputDirOverride ? resolve(outputDirOverride) : pkgDir;
298
+
299
+ const reads = productMod.standardReads();
300
+
301
+ console.log(`\n=== Injecting diya-gl data into ${lastResult.dirName} ===`);
302
+
303
+ if (packageFilter === "bst") {
304
+ // saveWorkbook takes the tax data this run selected rather than
305
+ // deriving it from the book, so --years still decides the year.
306
+ const { workbook, filename } = await saveWorkbook(book, lines, { taxData: lastResult.taxData });
307
+ await runSpreadsheet(workbook, {}, reads, {
308
+ saveRecalculatedTo: resolve(finalOutputDir, filename),
309
+ });
310
+ } else {
311
+ const scenario = diyaGlToScenario(book, lines, packageFilter);
312
+
313
+ // Extract year-end info from the directory name
314
+ const yearEndMatch = lastResult.dirName.match(/(\d{4})-(\d{2})-(\d{2})/);
315
+ const endYear = yearEndMatch ? parseInt(yearEndMatch[1], 10) : null;
316
+ const endMonth = yearEndMatch ? parseInt(yearEndMatch[2], 10) : null;
317
+ const startYear = endYear ? endYear - 1 : null;
318
+
319
+ const writes = productMod.cellWrites(scenario, startYear, endMonth);
320
+
321
+ if (productMod.MULTI_FILE) {
322
+ const { readdirSync: readdirSyncFs } = await import("fs");
323
+ const xlsxFiles = readdirSyncFs(pkgDir).filter((f) => f.endsWith(".xlsx"));
324
+ const fileBuffers = {};
325
+ for (const f of xlsxFiles) {
326
+ fileBuffers[f] = readFileSync(resolve(pkgDir, f));
327
+ }
328
+ await runMultiFileSpreadsheet(fileBuffers, writes, reads, "Financialaccounts.xlsx", {
329
+ saveRecalculatedTo: finalOutputDir,
330
+ });
331
+ } else {
332
+ const xlsxFiles = readdirSync(pkgDir).filter((f) => f.endsWith(".xlsx"));
333
+ if (xlsxFiles.length === 0) {
334
+ console.error(`No xlsx found in ${pkgDir}`);
335
+ process.exit(1);
336
+ }
337
+ const xlsxBuffer = readFileSync(resolve(pkgDir, xlsxFiles[0]));
338
+ await runSpreadsheet(xlsxBuffer, writes, reads, {
339
+ saveRecalculatedTo: resolve(finalOutputDir, xlsxFiles[0]),
340
+ });
341
+ }
342
+ }
343
+
344
+ console.log(`Populated package written to ${finalOutputDir}`);
345
+ }
346
+ }
347
+
348
+ if (import.meta.url === `file://${process.argv[1]}`) {
349
+ main().catch((err) => {
350
+ console.error(err);
351
+ process.exit(1);
352
+ });
353
+ }