@diy-accounting-uk/diya-gl 1.2.63 → 1.2.64
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/dist/app/data/releases.json +26 -0
- package/dist/app/data/render-unrepresentable/ltd.json +3 -0
- package/dist/app/lib/calculators/ltd.js +5 -2
- package/dist/app/lib/diya-gl-loader.js +3 -1
- package/dist/app/lib/provenance-data.js +2 -2
- package/dist/app/lib/provenance.js +6 -1
- package/dist/app/lib/scenario-extractor.js +1 -0
- package/dist/app/lib/scenario-loader.js +9 -1
- package/dist/app/lib/share-nominal.js +51 -0
- package/dist/app/lib/xlsx-exporter.js +4 -5
- package/dist/app/products/ltd.js +15 -6
- package/dist/web/spreadsheets.diyaccounting.co.uk/public/schema/diya-gl-book-v2.schema.json +1 -1
- package/package.json +1 -1
|
@@ -1871,6 +1871,32 @@
|
|
|
1871
1871
|
"scorecard": "101608 passed, 0 warnings, 0 failed"
|
|
1872
1872
|
}
|
|
1873
1873
|
}
|
|
1874
|
+
},
|
|
1875
|
+
{
|
|
1876
|
+
"tag": "diya-gl-v1.2.63",
|
|
1877
|
+
"date": "2026-10-28",
|
|
1878
|
+
"formatVersion": "diya-gl/1",
|
|
1879
|
+
"engineVersion": "1.2.63+2c4a0a8c",
|
|
1880
|
+
"taxDataHash": "51bdff60e993",
|
|
1881
|
+
"reconciledCommit": "54e8731a91279975e61b1a232fbb67fda89515f0",
|
|
1882
|
+
"templates": {
|
|
1883
|
+
"bst": {
|
|
1884
|
+
"hash": "0d003e10e00c",
|
|
1885
|
+
"scorecard": "812 passed, 0 warnings, 0 failed"
|
|
1886
|
+
},
|
|
1887
|
+
"taxi": {
|
|
1888
|
+
"hash": "1ff69654d64d",
|
|
1889
|
+
"scorecard": "700 passed, 0 warnings, 0 failed"
|
|
1890
|
+
},
|
|
1891
|
+
"se": {
|
|
1892
|
+
"hash": "064605ae76ed",
|
|
1893
|
+
"scorecard": "8015 passed, 0 warnings, 0 failed"
|
|
1894
|
+
},
|
|
1895
|
+
"ltd": {
|
|
1896
|
+
"hash": "b90d8b0f54a0",
|
|
1897
|
+
"scorecard": "101608 passed, 0 warnings, 0 failed"
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1874
1900
|
}
|
|
1875
1901
|
]
|
|
1876
1902
|
}
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
"cell/Cashaccount.xlsx!Mar!A1": "Bank account headers; bank view reads TrialBalance.",
|
|
3
3
|
"cell/Cashaccount.xlsx!Mar!A2": "Bank account headers; bank view reads TrialBalance.",
|
|
4
4
|
"cell/Companysecretary.xlsx!RegisterofMembers!F1": "RegisterofMembers headers; company view renders register.",
|
|
5
|
+
"cell/Companysecretary.xlsx!RegisterofMembers!F3": "RegisterofMembers nominal value column; company view renders the register.",
|
|
6
|
+
"cell/Companysecretary.xlsx!RegisterofMembers!F4": "RegisterofMembers nominal value column; company view renders the register.",
|
|
7
|
+
"cell/Companysecretary.xlsx!RegisterofMembers!F5": "RegisterofMembers nominal value column; company view renders the register.",
|
|
5
8
|
"cell/Companysecretary.xlsx!RegisterofMembers!G1": "RegisterofMembers headers; company view renders register.",
|
|
6
9
|
"cell/Creditcardaccount.xlsx!Mar!A1": "Bank account headers; bank view reads TrialBalance.",
|
|
7
10
|
"cell/Creditcardaccount.xlsx!Mar!A2": "Bank account headers; bank view reads TrialBalance.",
|
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
} from "../payslips-layout.js";
|
|
57
57
|
import { addMonths, endOfMonth, SHEET_BLANK } from "./shared.js";
|
|
58
58
|
import { registerOfficers } from "../scenario-loader.js";
|
|
59
|
+
import { registerNominalValue } from "../share-nominal.js";
|
|
59
60
|
import { attributionWriter, entriesOf, entryOf, fieldEntriesOf, unionOf } from "../entry-attribution.js";
|
|
60
61
|
|
|
61
62
|
const SHORT_MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
@@ -237,7 +238,8 @@ const REGISTER_MEMBER_ROWS = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1
|
|
|
237
238
|
// and the register of directors' interests one a row from row 2 as well.
|
|
238
239
|
const DIRECTOR_SECRETARY_OFFICER_ROWS = [2, 3, 4, 5, 6, 7, 8];
|
|
239
240
|
const DIRECTORS_INTERESTS_ROWS = [2, 3, 4, 5, 6];
|
|
240
|
-
|
|
241
|
+
// The template's own fully-paid ordinary share placeholder row.
|
|
242
|
+
const PLACEHOLDER_SHARE_NOMINAL_VALUE = 1;
|
|
241
243
|
const CHARGE_REGISTER_ROWS = [2, 3, 4, 5, 6];
|
|
242
244
|
const HP_AGREEMENT_ROWS = [8, 10];
|
|
243
245
|
const EXPENSES_FORM_MONTHS = Array.from({ length: 12 }, (_, index) => `Month ${String(index + 1).padStart(2, "0")}`);
|
|
@@ -1335,12 +1337,13 @@ function buildPayslipsCalendar(taxData, period, tabs) {
|
|
|
1335
1337
|
// placeholder, which is where F1 takes the nominal value from whether or not
|
|
1336
1338
|
// a scenario names any members.
|
|
1337
1339
|
function buildCompanySecretary(scenario) {
|
|
1338
|
-
const register = { F1: SHARE_NOMINAL_VALUE };
|
|
1339
1340
|
const members = scenario.members || [];
|
|
1341
|
+
const register = { F1: registerNominalValue(members.map((member) => member.nominal_value)) ?? PLACEHOLDER_SHARE_NOMINAL_VALUE };
|
|
1340
1342
|
members.forEach((member, index) => {
|
|
1341
1343
|
const row = REGISTER_MEMBER_ROWS[index];
|
|
1342
1344
|
if (row === undefined) return;
|
|
1343
1345
|
register[`A${row}`] = member.name;
|
|
1346
|
+
register[`F${row}`] = member.nominal_value;
|
|
1344
1347
|
register[`G${row}`] = member.shares;
|
|
1345
1348
|
});
|
|
1346
1349
|
// The directors' report prints the first two members a line each, so those
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
import { totalBusinessMiles, calculateMileageAllowance, FIXTURE_CAR_MILEAGE_RATES } from "./tax/mileage.js";
|
|
38
38
|
import { compareLines } from "./diya-gl-canonical.js";
|
|
39
39
|
import { tagEntry, entryOf } from "./entry-attribution.js";
|
|
40
|
+
import { memberNominalValue, registerNominalValue } from "./share-nominal.js";
|
|
40
41
|
|
|
41
42
|
// The Taxi Driver masters keep their own chart of accounts (fuel at 5100,
|
|
42
43
|
// fixed assets at 7000, ...), so filtering by BST_PURCHASE_CODE_MAP -- built
|
|
@@ -699,10 +700,11 @@ export function diyaGlToScenario(book, lines, product) {
|
|
|
699
700
|
}
|
|
700
701
|
if (product === "ltd" && book.members?.length > 0) {
|
|
701
702
|
scenario.members = book.members.map((member) => {
|
|
702
|
-
const entry = { name: member.name, shares: member.shares };
|
|
703
|
+
const entry = { name: member.name, shares: member.shares, nominal_value: memberNominalValue(member, book) };
|
|
703
704
|
if (member.acquiredDate !== undefined) entry.acquired = member.acquiredDate;
|
|
704
705
|
return entry;
|
|
705
706
|
});
|
|
707
|
+
registerNominalValue(scenario.members.map((member) => member.nominal_value));
|
|
706
708
|
}
|
|
707
709
|
// The Board Minute sheet has one meeting date and one declared figure, so
|
|
708
710
|
// a book with more than one dividend voucher still reaches the sheet as
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
// stamped by any of the three surfaces carries the same values.
|
|
12
12
|
|
|
13
13
|
export const PROVENANCE_DATA = {
|
|
14
|
-
formatVersion: "diya-gl/
|
|
15
|
-
engineVersion: "1.2.
|
|
14
|
+
formatVersion: "diya-gl/2",
|
|
15
|
+
engineVersion: "1.2.64+abe8b184",
|
|
16
16
|
taxDataHash: "51bdff60e993",
|
|
17
17
|
reconciledCommit: "54e8731a91279975e61b1a232fbb67fda89515f0",
|
|
18
18
|
templates: {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// reads the exact same generated values.
|
|
16
16
|
|
|
17
17
|
import { PROVENANCE_DATA } from "./provenance-data.js";
|
|
18
|
+
import { memberNominalValue } from "./share-nominal.js";
|
|
18
19
|
|
|
19
20
|
// The same schema-name-to-product-id mapping xlsx-exporter.js's
|
|
20
21
|
// productIdOf() carries, kept as a small map of its own rather than an
|
|
@@ -68,7 +69,11 @@ export function provenanceStamps(product) {
|
|
|
68
69
|
export function stampBook(book) {
|
|
69
70
|
const product = productOf(book);
|
|
70
71
|
if (!product) return book;
|
|
71
|
-
|
|
72
|
+
const stamped = { ...book, documentInfo: { ...book.documentInfo, ...provenanceStamps(product) } };
|
|
73
|
+
// A book that predates the format states no nominal value, and the format it
|
|
74
|
+
// is stamped with requires one on every member: state the £1 it was read as.
|
|
75
|
+
if (book.members) stamped.members = book.members.map((member) => ({ ...member, nominalValue: memberNominalValue(member, book) }));
|
|
76
|
+
return stamped;
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
/**
|
|
@@ -1094,6 +1094,7 @@ export function formatScenarioToml(metadata, grouped, expected) {
|
|
|
1094
1094
|
parts.push("[[members]]");
|
|
1095
1095
|
parts.push(`name = "${escapeTomlString(member.name)}"`);
|
|
1096
1096
|
parts.push(`shares = ${member.shares}`);
|
|
1097
|
+
if (member.nominal_value !== undefined) parts.push(`nominal_value = ${member.nominal_value}`);
|
|
1097
1098
|
if (member.acquired) parts.push(`acquired = ${member.acquired}`);
|
|
1098
1099
|
parts.push("");
|
|
1099
1100
|
}
|
|
@@ -86,6 +86,14 @@ export function registerOfficers(scenario) {
|
|
|
86
86
|
.map((employee) => ({ name: employee.name, role: "Director" }));
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
// A scenario file written before members carried a nominal value priced every
|
|
90
|
+
// share at £1.
|
|
91
|
+
const PRE_NOMINAL_VALUE_SHARE_PRICE = 1;
|
|
92
|
+
|
|
89
93
|
export function loadScenario(path) {
|
|
90
|
-
|
|
94
|
+
const scenario = parseTOML(readFileSync(path, "utf8"));
|
|
95
|
+
for (const member of scenario.members || []) {
|
|
96
|
+
if (member.nominal_value === undefined) member.nominal_value = PRE_NOMINAL_VALUE_SHARE_PRICE;
|
|
97
|
+
}
|
|
98
|
+
return scenario;
|
|
91
99
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2006-2026 DIY Accounting Limited
|
|
3
|
+
//
|
|
4
|
+
// share-nominal.js — the nominal value of one share on a book's register of
|
|
5
|
+
// members. A book written under diya-gl/2 or later states it on every member.
|
|
6
|
+
// A book that predates that format, or carries no format marker, priced every
|
|
7
|
+
// share at £1, so its members read as £1 here and nowhere else.
|
|
8
|
+
|
|
9
|
+
export const NOMINAL_VALUE_FORMAT = 2;
|
|
10
|
+
|
|
11
|
+
// The numeric part of a "diya-gl/2" or "diya-gl-books/1" marker, or undefined
|
|
12
|
+
// where the book carries none.
|
|
13
|
+
export function bookFormatNumber(book) {
|
|
14
|
+
const marker = book?.documentInfo?.["diya-gl:formatVersion"];
|
|
15
|
+
const match = typeof marker === "string" ? /\/(\d+)$/.exec(marker) : null;
|
|
16
|
+
return match ? Number(match[1]) : undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function statesNominalValue(book) {
|
|
20
|
+
const format = bookFormatNumber(book);
|
|
21
|
+
return format !== undefined && format >= NOMINAL_VALUE_FORMAT;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The nominal value per share for one member of the book's register.
|
|
26
|
+
* @param {Object} member - a members[] entry
|
|
27
|
+
* @param {Object} book - the parsed book, for its format marker
|
|
28
|
+
* @returns {number}
|
|
29
|
+
*/
|
|
30
|
+
export function memberNominalValue(member, book) {
|
|
31
|
+
if (member.nominalValue !== undefined) return member.nominalValue;
|
|
32
|
+
if (!statesNominalValue(book)) return 1;
|
|
33
|
+
throw new Error(
|
|
34
|
+
`members[] "${member.name}" has no nominalValue, and the book's format ${book.documentInfo["diya-gl:formatVersion"]} states one on every member`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The one nominal value the register prices every holding at. The members
|
|
40
|
+
* sheet takes its nominal value from a single cell, so members that differ
|
|
41
|
+
* cannot be carried.
|
|
42
|
+
* @param {number[]} nominalValues - one resolved value per member
|
|
43
|
+
* @returns {number|undefined} undefined for an empty register
|
|
44
|
+
*/
|
|
45
|
+
export function registerNominalValue(nominalValues) {
|
|
46
|
+
const values = new Set(nominalValues);
|
|
47
|
+
if (values.size > 1) {
|
|
48
|
+
throw new Error(`The register of members prices shares at ${[...values].join(" and ")}; the members sheet carries one nominal value`);
|
|
49
|
+
}
|
|
50
|
+
return values.size === 1 ? [...values][0] : undefined;
|
|
51
|
+
}
|
|
@@ -2410,11 +2410,10 @@ async function registersFrom(companySecretaryZip) {
|
|
|
2410
2410
|
const name = textAt(xml, `${MEMBER_COLUMNS.name}${row}`, sharedStrings);
|
|
2411
2411
|
const shares = numberAt(xml, `${MEMBER_COLUMNS.shares}${row}`, sharedStrings);
|
|
2412
2412
|
if (!name || !shares) continue;
|
|
2413
|
-
const
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
// schema's own note on this field says the same.
|
|
2413
|
+
const nominalValue = numberAt(xml, `${MEMBER_COLUMNS.nominalValue}${row}`, sharedStrings);
|
|
2414
|
+
if (nominalValue === undefined)
|
|
2415
|
+
throw new Error(`RegisterofMembers ${MEMBER_COLUMNS.nominalValue}${row}: member "${name}" holds shares with no nominal value`);
|
|
2416
|
+
const member = { memberID: `M${members.length + 1}`, name, shares, nominalValue };
|
|
2418
2417
|
assign(member, "acquiredDate", dateAt(xml, `${MEMBER_COLUMNS.acquired}${row}`, sharedStrings));
|
|
2419
2418
|
members.push(member);
|
|
2420
2419
|
}
|
package/dist/app/products/ltd.js
CHANGED
|
@@ -183,7 +183,6 @@ export const CHARGE_REGISTER_COLUMNS = { date: "A", asset: "B", valuation: "C",
|
|
|
183
183
|
// creditor the bank's DV payments settle.
|
|
184
184
|
export const REGISTER_MEMBER_ROWS = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19];
|
|
185
185
|
export const REGISTER_MEMBER_COLUMNS = { name: "A", acquired: "C", nominalValue: "F", shares: "G" };
|
|
186
|
-
const SHARE_NOMINAL_VALUE = 1;
|
|
187
186
|
export const BOARD_MINUTE_CELLS = { date: "F2", dividendDeclared: "E4" };
|
|
188
187
|
|
|
189
188
|
// ── Register of directors and directors' interests (Companysecretary.xlsx) ─
|
|
@@ -603,8 +602,8 @@ export function cellWrites(scenario, targetStartYear, yearEndMonth) {
|
|
|
603
602
|
if (hubWrites.OpenAccounts) hubWrites.OpenAccounts = inSheetOrder(hubWrites.OpenAccounts);
|
|
604
603
|
|
|
605
604
|
// Companysecretary.xlsx: the charges register, the share register and the
|
|
606
|
-
// board minute.
|
|
607
|
-
//
|
|
605
|
+
// board minute. Each member row carries the nominal value
|
|
606
|
+
// of a share the scenario states for it.
|
|
608
607
|
const companysecretaryWrites = {};
|
|
609
608
|
if (scenario.charges) {
|
|
610
609
|
companysecretaryWrites["Charges&Debentures"] = {};
|
|
@@ -633,7 +632,7 @@ export function cellWrites(scenario, targetStartYear, yearEndMonth) {
|
|
|
633
632
|
const row = REGISTER_MEMBER_ROWS[index];
|
|
634
633
|
if (row === undefined) return;
|
|
635
634
|
register[`${REGISTER_MEMBER_COLUMNS.name}${row}`] = member.name;
|
|
636
|
-
register[`${REGISTER_MEMBER_COLUMNS.nominalValue}${row}`] =
|
|
635
|
+
register[`${REGISTER_MEMBER_COLUMNS.nominalValue}${row}`] = member.nominal_value;
|
|
637
636
|
register[`${REGISTER_MEMBER_COLUMNS.shares}${row}`] = member.shares;
|
|
638
637
|
if (member.acquired) {
|
|
639
638
|
const acquired = parseDate(member.acquired);
|
|
@@ -1876,14 +1875,18 @@ export function multiFileOptions(yearEndMonth) {
|
|
|
1876
1875
|
...payslipsMonthTabReads(tabNames),
|
|
1877
1876
|
},
|
|
1878
1877
|
"Companysecretary.xlsx": {
|
|
1879
|
-
// One member a row: A the name, G the holding. F1 is the sheet's own
|
|
1878
|
+
// One member a row: A the name, F the nominal value of a share, G the holding. F1 is the sheet's own
|
|
1880
1879
|
// nominal-value formula (=F3) and G1 its shares-issued total
|
|
1881
1880
|
// (=SUM(G3:G19)). The directors' report prints the first two members
|
|
1882
1881
|
// a line each.
|
|
1883
1882
|
"RegisterofMembers": [
|
|
1884
1883
|
"F1",
|
|
1885
1884
|
"G1",
|
|
1886
|
-
...REGISTER_MEMBER_ROWS.flatMap((row) => [
|
|
1885
|
+
...REGISTER_MEMBER_ROWS.flatMap((row) => [
|
|
1886
|
+
`${REGISTER_MEMBER_COLUMNS.name}${row}`,
|
|
1887
|
+
`${REGISTER_MEMBER_COLUMNS.nominalValue}${row}`,
|
|
1888
|
+
`${REGISTER_MEMBER_COLUMNS.shares}${row}`,
|
|
1889
|
+
]),
|
|
1887
1890
|
],
|
|
1888
1891
|
// F2 is the date the board met and E4 the dividend it declared. The
|
|
1889
1892
|
// directors' report and the trial balance both read E4 across the
|
|
@@ -3059,6 +3062,12 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
|
|
|
3059
3062
|
member.shares,
|
|
3060
3063
|
0,
|
|
3061
3064
|
);
|
|
3065
|
+
check(
|
|
3066
|
+
`Register of members: row ${row} prices ${member.name}'s shares`,
|
|
3067
|
+
num(register[`${REGISTER_MEMBER_COLUMNS.nominalValue}${row}`]),
|
|
3068
|
+
member.nominal_value,
|
|
3069
|
+
0,
|
|
3070
|
+
);
|
|
3062
3071
|
});
|
|
3063
3072
|
}
|
|
3064
3073
|
if (report && expected.members) {
|
|
@@ -619,7 +619,7 @@
|
|
|
619
619
|
"nominalValue": {
|
|
620
620
|
"type": "number",
|
|
621
621
|
"minimum": 0,
|
|
622
|
-
"description": "Nominal value per share.
|
|
622
|
+
"description": "diya-gl: extension \u2014 Nominal value per share, in pounds. Required on every member of a book whose diya-gl:formatVersion is diya-gl/2 or later; a book that predates it, or carries no format marker, reads as 1. The members sheet carries one nominal value, so every member of a book states the same figure."
|
|
623
623
|
},
|
|
624
624
|
"acquiredDate": {
|
|
625
625
|
"type": "string",
|