@diy-accounting-uk/diya-gl 1.2.59 → 1.2.61
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 +52 -0
- package/dist/app/lib/ixbrl-accounts.js +208 -34
- package/dist/app/lib/ixbrl-reader.js +14 -3
- package/dist/app/lib/ixbrl-summary-book.js +23 -8
- package/dist/app/lib/ixbrl-uk-gaap.js +202 -0
- package/dist/app/lib/provenance-data.js +1 -1
- package/package.json +1 -1
|
@@ -1767,6 +1767,58 @@
|
|
|
1767
1767
|
"scorecard": "101608 passed, 0 warnings, 0 failed"
|
|
1768
1768
|
}
|
|
1769
1769
|
}
|
|
1770
|
+
},
|
|
1771
|
+
{
|
|
1772
|
+
"tag": "diya-gl-v1.2.59",
|
|
1773
|
+
"date": "2026-10-27",
|
|
1774
|
+
"formatVersion": "diya-gl/1",
|
|
1775
|
+
"engineVersion": "1.2.59+9d3b814a",
|
|
1776
|
+
"taxDataHash": "51bdff60e993",
|
|
1777
|
+
"reconciledCommit": "54e8731a91279975e61b1a232fbb67fda89515f0",
|
|
1778
|
+
"templates": {
|
|
1779
|
+
"bst": {
|
|
1780
|
+
"hash": "0d003e10e00c",
|
|
1781
|
+
"scorecard": "812 passed, 0 warnings, 0 failed"
|
|
1782
|
+
},
|
|
1783
|
+
"taxi": {
|
|
1784
|
+
"hash": "1ff69654d64d",
|
|
1785
|
+
"scorecard": "700 passed, 0 warnings, 0 failed"
|
|
1786
|
+
},
|
|
1787
|
+
"se": {
|
|
1788
|
+
"hash": "064605ae76ed",
|
|
1789
|
+
"scorecard": "8015 passed, 0 warnings, 0 failed"
|
|
1790
|
+
},
|
|
1791
|
+
"ltd": {
|
|
1792
|
+
"hash": "a65ab09cf35c",
|
|
1793
|
+
"scorecard": "101608 passed, 0 warnings, 0 failed"
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
},
|
|
1797
|
+
{
|
|
1798
|
+
"tag": "diya-gl-v1.2.60",
|
|
1799
|
+
"date": "2026-10-27",
|
|
1800
|
+
"formatVersion": "diya-gl/1",
|
|
1801
|
+
"engineVersion": "1.2.60+602620de",
|
|
1802
|
+
"taxDataHash": "51bdff60e993",
|
|
1803
|
+
"reconciledCommit": "54e8731a91279975e61b1a232fbb67fda89515f0",
|
|
1804
|
+
"templates": {
|
|
1805
|
+
"bst": {
|
|
1806
|
+
"hash": "0d003e10e00c",
|
|
1807
|
+
"scorecard": "812 passed, 0 warnings, 0 failed"
|
|
1808
|
+
},
|
|
1809
|
+
"taxi": {
|
|
1810
|
+
"hash": "1ff69654d64d",
|
|
1811
|
+
"scorecard": "700 passed, 0 warnings, 0 failed"
|
|
1812
|
+
},
|
|
1813
|
+
"se": {
|
|
1814
|
+
"hash": "064605ae76ed",
|
|
1815
|
+
"scorecard": "8015 passed, 0 warnings, 0 failed"
|
|
1816
|
+
},
|
|
1817
|
+
"ltd": {
|
|
1818
|
+
"hash": "a65ab09cf35c",
|
|
1819
|
+
"scorecard": "101608 passed, 0 warnings, 0 failed"
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1770
1822
|
}
|
|
1771
1823
|
]
|
|
1772
1824
|
}
|
|
@@ -19,6 +19,7 @@ import { readIxbrl, IxbrlError } from "./ixbrl-reader.js";
|
|
|
19
19
|
export { IxbrlError };
|
|
20
20
|
|
|
21
21
|
const MATURITY = "MaturitiesOrExpirationPeriodsDimension";
|
|
22
|
+
const CURRENT_INSTRUMENT = "FinancialInstrumentCurrentNon-currentDimension";
|
|
22
23
|
const EQUITY_CLASS = "EquityClassesDimension";
|
|
23
24
|
const PPE_CLASS = "PropertyPlantEquipmentClassesDimension";
|
|
24
25
|
|
|
@@ -41,6 +42,8 @@ export const FIXED_ASSET_ACCOUNTS = {
|
|
|
41
42
|
motorVehicles: "0040",
|
|
42
43
|
};
|
|
43
44
|
|
|
45
|
+
const NATURE_NOTE = "micro-entity and Format 2 profit and loss";
|
|
46
|
+
|
|
44
47
|
/**
|
|
45
48
|
* The mapping, one row per concept and member the reader maps. `figure` is
|
|
46
49
|
* the name the forms and builders use; `account` is where the summary book
|
|
@@ -89,6 +92,31 @@ export const MAPPING_TABLE = [
|
|
|
89
92
|
note: "WithinOneYear; posts as a trade creditor, which the balance sheet shows within one year",
|
|
90
93
|
},
|
|
91
94
|
{ concept: "AccruedLiabilitiesNotExpressedWithinCreditorsSubtotal", figure: "otherCreditors", account: "2100", note: "WithinOneYear" },
|
|
95
|
+
{ concept: "TaxationSocialSecurityPayable", figure: "taxationSocialSecurity", account: "2100", note: "WithinOneYear" },
|
|
96
|
+
{
|
|
97
|
+
concept: "AccruedLiabilities",
|
|
98
|
+
dimension: CURRENT_INSTRUMENT,
|
|
99
|
+
member: "CurrentFinancialInstruments",
|
|
100
|
+
figure: "accruals",
|
|
101
|
+
account: "2100",
|
|
102
|
+
note: "on a context that also carries WithinOneYear",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
concept: "BankBorrowingsOverdrafts",
|
|
106
|
+
dimension: MATURITY,
|
|
107
|
+
member: "WithinOneYear",
|
|
108
|
+
figure: "bankLoansOverdrafts",
|
|
109
|
+
account: "2100",
|
|
110
|
+
note: "with or without the WithinOneYear member",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
concept: "BankBorrowingsOverdrafts",
|
|
114
|
+
dimension: MATURITY,
|
|
115
|
+
member: "AfterOneYear",
|
|
116
|
+
figure: "bankLoansAfterOneYear",
|
|
117
|
+
account: "2600",
|
|
118
|
+
note: "a part of creditorsAfterOneYear, which states it",
|
|
119
|
+
},
|
|
92
120
|
{
|
|
93
121
|
concept: "Creditors",
|
|
94
122
|
dimension: MATURITY,
|
|
@@ -133,21 +161,77 @@ export const MAPPING_TABLE = [
|
|
|
133
161
|
account: "1200",
|
|
134
162
|
note: "a receipt coded K",
|
|
135
163
|
},
|
|
164
|
+
{
|
|
165
|
+
concept: "InterestPayableSimilarChargesFinanceCosts",
|
|
166
|
+
figure: "interestPayable",
|
|
167
|
+
account: "5803",
|
|
168
|
+
note: "the ltd book's loan interest account",
|
|
169
|
+
},
|
|
136
170
|
{ concept: "TaxTaxCreditOnProfitOrLossOnOrdinaryActivities", figure: "tax", account: "-", note: "not posted; diya-gl works the tax" },
|
|
137
171
|
{ concept: "GrossProfitLoss", figure: "grossProfit", account: "-", note: "a check" },
|
|
138
172
|
{ concept: "OperatingProfitLoss", figure: "operatingProfit", account: "-", note: "a check" },
|
|
139
173
|
{ concept: "ProfitLossOnOrdinaryActivitiesBeforeTax", figure: "profitBeforeTax", account: "-", note: "a check" },
|
|
140
174
|
{ concept: "ProfitLoss", figure: "profit", account: "-", note: "a check" },
|
|
141
|
-
{ concept: "OtherOperatingIncomeFormat2", figure: "otherOperatingIncome", account: "4003", note:
|
|
142
|
-
{
|
|
143
|
-
|
|
175
|
+
{ concept: "OtherOperatingIncomeFormat2", figure: "otherOperatingIncome", account: "4003", note: NATURE_NOTE },
|
|
176
|
+
{
|
|
177
|
+
concept: "OtherOperatingIncome",
|
|
178
|
+
figure: "otherOperatingIncome",
|
|
179
|
+
account: "4003",
|
|
180
|
+
note: `${NATURE_NOTE}; read when the Format 2 name is absent`,
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
concept: "IncreaseDecreaseInStocksInventoriesFinishedGoodsWorkInProgress",
|
|
184
|
+
figure: "changeInStocks",
|
|
185
|
+
account: "-",
|
|
186
|
+
note: "Format 2 profit and loss; not posted, the stock movement of the balance sheets already moves 1100 and 5000, and a difference is listed",
|
|
187
|
+
},
|
|
188
|
+
{ concept: "OwnWorkCapitalised", figure: "ownWorkCapitalised", account: "4003", note: "Format 2 profit and loss" },
|
|
189
|
+
{ concept: "RawMaterialsConsumablesUsed", figure: "rawMaterialsConsumables", account: "5000", note: NATURE_NOTE },
|
|
190
|
+
{
|
|
191
|
+
concept: "OtherExternalCharges",
|
|
192
|
+
figure: "otherExternalCharges",
|
|
193
|
+
account: "5001",
|
|
194
|
+
note: "Format 2 profit and loss; the sub-contractors account",
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
concept: "StaffCostsEmployeeBenefitsExpense",
|
|
198
|
+
figure: "staffCosts",
|
|
199
|
+
account: "5101",
|
|
200
|
+
note: `${NATURE_NOTE}; the sum of the three lines below when absent`,
|
|
201
|
+
},
|
|
202
|
+
{ concept: "WagesSalaries", figure: "wagesSalaries", account: "5101", note: "part of staffCosts" },
|
|
203
|
+
{ concept: "SocialSecurityCosts", figure: "socialSecurityCosts", account: "5101", note: "part of staffCosts" },
|
|
204
|
+
{
|
|
205
|
+
concept: "PensionOtherPost-employmentBenefitCostsOtherPensionCosts",
|
|
206
|
+
figure: "pensionCosts",
|
|
207
|
+
account: "5101",
|
|
208
|
+
note: "part of staffCosts",
|
|
209
|
+
},
|
|
144
210
|
{
|
|
145
211
|
concept: "DepreciationAmortisationImpairmentExpense",
|
|
146
212
|
figure: "depreciation",
|
|
147
213
|
account: "depreciationRate",
|
|
148
|
-
note:
|
|
214
|
+
note: NATURE_NOTE,
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
concept: "DepreciationAmortisationExpense",
|
|
218
|
+
figure: "depreciation",
|
|
219
|
+
account: "depreciationRate",
|
|
220
|
+
note: `${NATURE_NOTE}; read when the impairment name is absent`,
|
|
221
|
+
},
|
|
222
|
+
{ concept: "OtherOperatingExpensesFormat2", figure: "otherOperatingExpenses", account: "5501", note: NATURE_NOTE },
|
|
223
|
+
{
|
|
224
|
+
concept: "OtherOperatingExpensesByNature",
|
|
225
|
+
figure: "otherOperatingExpenses",
|
|
226
|
+
account: "5501",
|
|
227
|
+
note: `${NATURE_NOTE}; read when the Format 2 name is absent`,
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
concept: "ProfitLossBeforeTax",
|
|
231
|
+
figure: "profitBeforeTax",
|
|
232
|
+
account: "-",
|
|
233
|
+
note: "a check; read when ProfitLossOnOrdinaryActivitiesBeforeTax is absent",
|
|
149
234
|
},
|
|
150
|
-
{ concept: "OtherOperatingExpensesFormat2", figure: "otherOperatingExpenses", account: "5501", note: "micro-entity profit and loss" },
|
|
151
235
|
{
|
|
152
236
|
concept: "GainLossOnDisposalsPropertyPlantEquipment",
|
|
153
237
|
figure: "gainOnDisposal",
|
|
@@ -262,7 +346,8 @@ function balanceSheetAt(set, endDay, notes, label) {
|
|
|
262
346
|
set.instant("CalledUpShareCapitalNotPaidNotExpressedAsCurrentAsset", endDay),
|
|
263
347
|
]);
|
|
264
348
|
const provisions = set.instant("ProvisionsForLiabilitiesBalanceSheetSubtotal", endDay);
|
|
265
|
-
const
|
|
349
|
+
const bankLoansAfterOneYear = set.instant("BankBorrowingsOverdrafts", endDay, { [MATURITY]: "AfterOneYear" });
|
|
350
|
+
const afterOneYear = set.instant("Creditors", endDay, { [MATURITY]: "AfterOneYear" }) ?? bankLoansAfterOneYear;
|
|
266
351
|
if (provisions) notes.push(`${label}: provisions for liabilities of ${provisions} are added to creditors after more than one year.`);
|
|
267
352
|
|
|
268
353
|
const equityClasses = set.find("Equity", null, (fact) => fact.period.instant === endDay && Object.keys(fact.dimensions).length === 1);
|
|
@@ -296,6 +381,13 @@ function balanceSheetAt(set, endDay, notes, label) {
|
|
|
296
381
|
set.maturity("OtherCreditors", endDay, ["WithinOneYear", null]),
|
|
297
382
|
set.maturity("AccruedLiabilitiesNotExpressedWithinCreditorsSubtotal", endDay, ["WithinOneYear", null]),
|
|
298
383
|
]),
|
|
384
|
+
taxationSocialSecurity: set.maturity("TaxationSocialSecurityPayable", endDay, ["WithinOneYear", null]),
|
|
385
|
+
accruals: set.instant("AccruedLiabilities", endDay, {
|
|
386
|
+
[CURRENT_INSTRUMENT]: "CurrentFinancialInstruments",
|
|
387
|
+
[MATURITY]: "WithinOneYear",
|
|
388
|
+
}),
|
|
389
|
+
bankLoansOverdrafts: set.maturity("BankBorrowingsOverdrafts", endDay, ["WithinOneYear", null]),
|
|
390
|
+
bankLoansAfterOneYear,
|
|
299
391
|
creditorsWithinOneYear: set.instant("Creditors", endDay, { [MATURITY]: "WithinOneYear" }),
|
|
300
392
|
creditorsAfterOneYear: sumDefined([afterOneYear, provisions]),
|
|
301
393
|
provisions,
|
|
@@ -308,22 +400,76 @@ function balanceSheetAt(set, endDay, notes, label) {
|
|
|
308
400
|
});
|
|
309
401
|
}
|
|
310
402
|
|
|
311
|
-
|
|
403
|
+
// The concepts that say which format the profit and loss account takes. Format 1 analyses costs by
|
|
404
|
+
// function; Format 2 by nature. A Format 2 concept in a Format 1 file belongs to a note.
|
|
405
|
+
const FORMAT_1_CONCEPTS = new Set([
|
|
406
|
+
"CostSales",
|
|
407
|
+
"GrossProfitLoss",
|
|
408
|
+
"DistributionCosts",
|
|
409
|
+
"AdministrativeExpenses",
|
|
410
|
+
"OtherOperatingIncomeFormat1",
|
|
411
|
+
"OtherOperatingExpensesFormat1",
|
|
412
|
+
]);
|
|
413
|
+
const FORMAT_2_CONCEPTS = new Set([
|
|
414
|
+
"IncreaseDecreaseInStocksInventoriesFinishedGoodsWorkInProgress",
|
|
415
|
+
"OwnWorkCapitalised",
|
|
416
|
+
"OtherOperatingIncomeFormat2",
|
|
417
|
+
"RawMaterialsConsumablesUsed",
|
|
418
|
+
"OtherExternalCharges",
|
|
419
|
+
"StaffCostsEmployeeBenefitsExpense",
|
|
420
|
+
"WagesSalaries",
|
|
421
|
+
"SocialSecurityCosts",
|
|
422
|
+
"PensionOtherPost-employmentBenefitCostsOtherPensionCosts",
|
|
423
|
+
"DepreciationAmortisationImpairmentExpense",
|
|
424
|
+
"DepreciationAmortisationExpense",
|
|
425
|
+
"OtherOperatingExpensesFormat2",
|
|
426
|
+
"OtherOperatingExpensesByNature",
|
|
427
|
+
]);
|
|
428
|
+
|
|
429
|
+
// 1 or 2: the format of the profit and loss account the facts ending on one of the days state.
|
|
430
|
+
function profitAndLossFormat(set, endDays) {
|
|
431
|
+
for (const endDay of endDays) {
|
|
432
|
+
const names = set.facts.filter((fact) => fact.period.endDate === endDay).map((fact) => fact.localName);
|
|
433
|
+
if (names.some((name) => FORMAT_1_CONCEPTS.has(name))) return 1;
|
|
434
|
+
if (names.some((name) => FORMAT_2_CONCEPTS.has(name))) return 2;
|
|
435
|
+
}
|
|
436
|
+
return 1;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function profitAndLossAt(set, endDay, byNature) {
|
|
440
|
+
const staffCosts = byNature ? (set.duration("StaffCostsEmployeeBenefitsExpense", endDay) ?? undefined) : undefined;
|
|
441
|
+
const wagesSalaries = byNature ? set.duration("WagesSalaries", endDay) : undefined;
|
|
442
|
+
const socialSecurityCosts = byNature ? set.duration("SocialSecurityCosts", endDay) : undefined;
|
|
443
|
+
const pensionCosts = byNature ? set.duration("PensionOtherPost-employmentBenefitCostsOtherPensionCosts", endDay) : undefined;
|
|
444
|
+
const nature = byNature
|
|
445
|
+
? {
|
|
446
|
+
changeInStocks: set.duration("IncreaseDecreaseInStocksInventoriesFinishedGoodsWorkInProgress", endDay),
|
|
447
|
+
ownWorkCapitalised: set.duration("OwnWorkCapitalised", endDay),
|
|
448
|
+
otherOperatingIncome: set.duration("OtherOperatingIncomeFormat2", endDay) ?? set.duration("OtherOperatingIncome", endDay),
|
|
449
|
+
rawMaterialsConsumables: set.duration("RawMaterialsConsumablesUsed", endDay),
|
|
450
|
+
otherExternalCharges: set.duration("OtherExternalCharges", endDay),
|
|
451
|
+
staffCosts: staffCosts ?? sumDefined([wagesSalaries, socialSecurityCosts, pensionCosts]),
|
|
452
|
+
wagesSalaries,
|
|
453
|
+
socialSecurityCosts,
|
|
454
|
+
pensionCosts,
|
|
455
|
+
depreciation:
|
|
456
|
+
set.duration("DepreciationAmortisationImpairmentExpense", endDay) ?? set.duration("DepreciationAmortisationExpense", endDay),
|
|
457
|
+
otherOperatingExpenses:
|
|
458
|
+
set.duration("OtherOperatingExpensesFormat2", endDay) ?? set.duration("OtherOperatingExpensesByNature", endDay),
|
|
459
|
+
}
|
|
460
|
+
: {};
|
|
312
461
|
return pick({
|
|
462
|
+
...nature,
|
|
313
463
|
turnover: set.duration("TurnoverRevenue", endDay),
|
|
314
464
|
costOfSales: set.duration("CostSales", endDay),
|
|
315
465
|
grossProfit: set.duration("GrossProfitLoss", endDay),
|
|
316
466
|
administrativeExpenses: set.duration("AdministrativeExpenses", endDay),
|
|
317
467
|
operatingProfit: set.duration("OperatingProfitLoss", endDay),
|
|
318
468
|
interestReceivable: set.duration("OtherInterestReceivableSimilarIncomeFinanceIncome", endDay),
|
|
319
|
-
|
|
469
|
+
interestPayable: set.duration("InterestPayableSimilarChargesFinanceCosts", endDay),
|
|
470
|
+
profitBeforeTax: set.duration("ProfitLossOnOrdinaryActivitiesBeforeTax", endDay) ?? set.duration("ProfitLossBeforeTax", endDay),
|
|
320
471
|
tax: set.duration("TaxTaxCreditOnProfitOrLossOnOrdinaryActivities", endDay),
|
|
321
472
|
profit: set.duration("ProfitLoss", endDay) ?? set.duration("ProfitLossOnOrdinaryActivitiesAfterTax", endDay),
|
|
322
|
-
otherOperatingIncome: set.duration("OtherOperatingIncomeFormat2", endDay),
|
|
323
|
-
rawMaterialsConsumables: set.duration("RawMaterialsConsumablesUsed", endDay),
|
|
324
|
-
staffCosts: set.duration("StaffCostsEmployeeBenefitsExpense", endDay),
|
|
325
|
-
depreciation: set.duration("DepreciationAmortisationImpairmentExpense", endDay),
|
|
326
|
-
otherOperatingExpenses: set.duration("OtherOperatingExpensesFormat2", endDay),
|
|
327
473
|
gainOnDisposal: set.duration("GainLossOnDisposalsPropertyPlantEquipment", endDay),
|
|
328
474
|
});
|
|
329
475
|
}
|
|
@@ -430,10 +576,13 @@ function problem(message) {
|
|
|
430
576
|
return new IxbrlError(message);
|
|
431
577
|
}
|
|
432
578
|
|
|
579
|
+
// The lines that add up to the creditors falling due within one year.
|
|
580
|
+
const WITHIN_LINES = ["bankLoansOverdrafts", "tradeCreditors", "corporationTax", "taxationSocialSecurity", "otherCreditors", "accruals"];
|
|
581
|
+
|
|
433
582
|
function checkBalance(year, label) {
|
|
434
583
|
const s = year;
|
|
435
584
|
const current = s.currentAssets ?? sumOf([s.stocks, s.debtors, s.cashAtBank].map((v) => v ?? 0));
|
|
436
|
-
const within = s.creditorsWithinOneYear ?? sumOf(
|
|
585
|
+
const within = s.creditorsWithinOneYear ?? sumOf(WITHIN_LINES.map((line) => s[line] ?? 0));
|
|
437
586
|
const net = (s.fixedAssets ?? 0) + current - within - (s.creditorsAfterOneYear ?? 0);
|
|
438
587
|
const equity = s.capitalAndReserves ?? s.netAssets;
|
|
439
588
|
if (s.netCurrentAssets !== undefined && !close(current - within, s.netCurrentAssets)) {
|
|
@@ -473,7 +622,7 @@ function checkTotals(year, label, regime) {
|
|
|
473
622
|
if (over || under)
|
|
474
623
|
throw problem(`The ${label} current assets of ${s.currentAssets} differ from the lines under it, which add up to ${lines}.`);
|
|
475
624
|
}
|
|
476
|
-
const creditors = sumDefined(
|
|
625
|
+
const creditors = sumDefined(WITHIN_LINES.map((line) => s[line]));
|
|
477
626
|
if (s.creditorsWithinOneYear !== undefined && creditors !== undefined) {
|
|
478
627
|
const over = creditors - s.creditorsWithinOneYear > WITHIN;
|
|
479
628
|
const under = s.creditorsWithinOneYear - creditors > WITHIN && regime === "small";
|
|
@@ -511,30 +660,49 @@ function checkProfitAndLoss(pl, label) {
|
|
|
511
660
|
if (
|
|
512
661
|
pl.profitBeforeTax !== undefined &&
|
|
513
662
|
pl.operatingProfit !== undefined &&
|
|
514
|
-
!close(pl.operatingProfit + n(pl.interestReceivable), pl.profitBeforeTax)
|
|
663
|
+
!close(pl.operatingProfit + n(pl.interestReceivable) - n(pl.interestPayable), pl.profitBeforeTax)
|
|
515
664
|
) {
|
|
516
|
-
mismatch("profit before tax", pl.operatingProfit + n(pl.interestReceivable), pl.profitBeforeTax);
|
|
665
|
+
mismatch("profit before tax", pl.operatingProfit + n(pl.interestReceivable) - n(pl.interestPayable), pl.profitBeforeTax);
|
|
517
666
|
}
|
|
518
667
|
if (pl.profit !== undefined && pl.profitBeforeTax !== undefined && !close(pl.profitBeforeTax - n(pl.tax), pl.profit)) {
|
|
519
668
|
mismatch("profit for the year", pl.profitBeforeTax - n(pl.tax), pl.profit);
|
|
520
669
|
}
|
|
521
|
-
const
|
|
522
|
-
if (
|
|
523
|
-
mismatch("profit
|
|
670
|
+
const byNature = natureProfitBeforeTax(pl);
|
|
671
|
+
if (byNature !== undefined && pl.operatingProfit !== undefined && !close(byNature, pl.operatingProfit)) {
|
|
672
|
+
mismatch("operating profit", byNature, pl.operatingProfit);
|
|
673
|
+
}
|
|
674
|
+
if (
|
|
675
|
+
byNature !== undefined &&
|
|
676
|
+
pl.profit !== undefined &&
|
|
677
|
+
!close(byNature + n(pl.interestReceivable) - n(pl.interestPayable) - n(pl.tax), pl.profit)
|
|
678
|
+
) {
|
|
679
|
+
mismatch("profit for the year", byNature + n(pl.interestReceivable) - n(pl.interestPayable) - n(pl.tax), pl.profit);
|
|
524
680
|
}
|
|
525
681
|
}
|
|
526
682
|
|
|
527
|
-
// The micro-entity
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
683
|
+
// The profit and loss account by nature (micro-entity lines and Format 2): turnover, the change in
|
|
684
|
+
// stocks, own work capitalised and other income, less raw materials, external charges, staff costs,
|
|
685
|
+
// depreciation and other expenses. Before interest, so a micro-entity's figure is its profit before tax.
|
|
686
|
+
export function natureProfitBeforeTax(pl) {
|
|
687
|
+
const hasNatureLine = [
|
|
688
|
+
"changeInStocks",
|
|
689
|
+
"ownWorkCapitalised",
|
|
690
|
+
"otherOperatingIncome",
|
|
691
|
+
"rawMaterialsConsumables",
|
|
692
|
+
"otherExternalCharges",
|
|
693
|
+
"staffCosts",
|
|
694
|
+
"depreciation",
|
|
695
|
+
"otherOperatingExpenses",
|
|
696
|
+
].some((key) => pl[key] !== undefined);
|
|
697
|
+
if (!hasNatureLine || pl.turnover === undefined) return undefined;
|
|
533
698
|
const n = (value) => value ?? 0;
|
|
534
699
|
return (
|
|
535
700
|
pl.turnover +
|
|
701
|
+
n(pl.changeInStocks) +
|
|
702
|
+
n(pl.ownWorkCapitalised) +
|
|
536
703
|
n(pl.otherOperatingIncome) -
|
|
537
704
|
n(pl.rawMaterialsConsumables) -
|
|
705
|
+
n(pl.otherExternalCharges) -
|
|
538
706
|
n(pl.staffCosts) -
|
|
539
707
|
n(pl.depreciation) -
|
|
540
708
|
n(pl.otherOperatingExpenses)
|
|
@@ -611,8 +779,11 @@ export function mapIxbrlAccounts(source, expect = {}, { suiteConcepts } = {}) {
|
|
|
611
779
|
const notes = [];
|
|
612
780
|
const currentYear = balanceSheetAt(set, period.end, notes, "Current year");
|
|
613
781
|
const priorYear = priorEnd ? balanceSheetAt(set, priorEnd, notes, "Prior year") : {};
|
|
614
|
-
const
|
|
615
|
-
|
|
782
|
+
const profitAndLossFormatNumber = profitAndLossFormat(set, priorEnd ? [period.end, priorEnd] : [period.end]);
|
|
783
|
+
// A micro-entity file's lines read as the micro lines; a Format 2 small company file reads the same lines and more.
|
|
784
|
+
const byNature = regime === "micro" || profitAndLossFormatNumber === 2;
|
|
785
|
+
const currentPl = profitAndLossAt(set, period.end, byNature);
|
|
786
|
+
const priorPl = priorEnd ? profitAndLossAt(set, priorEnd, byNature) : {};
|
|
616
787
|
const fixedAssetNote = priorEnd ? fixedAssetNoteAt(set, period.end, priorEnd) : null;
|
|
617
788
|
// The note's totals and net book values restate what its classes and the balance sheet already say.
|
|
618
789
|
for (const fact of set.facts) {
|
|
@@ -689,13 +860,15 @@ export function mapIxbrlAccounts(source, expect = {}, { suiteConcepts } = {}) {
|
|
|
689
860
|
for (const fact of [shares, nominal]) if (fact) set.consumed.add(fact);
|
|
690
861
|
const shareClassMember = (fact) => (fact && Object.keys(fact.dimensions).length === 1 ? local(Object.values(fact.dimensions)[0]) : null);
|
|
691
862
|
|
|
692
|
-
if (regime === "small" && hasProfitAndLoss && currentPl.costOfSales === undefined && currentPl.turnover === undefined) {
|
|
693
|
-
notes.push("This file's profit and loss account is Format 2, which the small company form does not take.");
|
|
694
|
-
}
|
|
695
|
-
|
|
696
863
|
const principalActivity = textFact(set, "DescriptionPrincipalActivities") ?? null;
|
|
697
864
|
const approvalDate = textFact(set, "DateAuthorisationFinancialStatementsForIssue") ?? null;
|
|
698
|
-
const definedBySuite = read.suite.date ? suiteConcepts?.(read.suite.date) : undefined;
|
|
865
|
+
const definedBySuite = read.suite.date && read.suite.family !== "UK-GAAP" ? suiteConcepts?.(read.suite.date) : undefined;
|
|
866
|
+
const sourceConcepts = {};
|
|
867
|
+
// A name stated both on its own and on a dimension (Equity) takes the UK GAAP name of the fact on its own.
|
|
868
|
+
const translated = set.facts.filter((fact) => fact.sourceConcept);
|
|
869
|
+
for (const fact of [...translated.filter((f) => Object.keys(f.dimensions).length === 0), ...translated]) {
|
|
870
|
+
sourceConcepts[fact.localName] ??= fact.sourceConcept;
|
|
871
|
+
}
|
|
699
872
|
const unmapped = set.facts
|
|
700
873
|
.filter((fact) => !set.consumed.has(fact) && fact.value !== "" && !isBoilerplate(fact))
|
|
701
874
|
.map((fact) => ({
|
|
@@ -717,7 +890,7 @@ export function mapIxbrlAccounts(source, expect = {}, { suiteConcepts } = {}) {
|
|
|
717
890
|
filleted,
|
|
718
891
|
hasProfitAndLoss,
|
|
719
892
|
balanceSheet: { currentYear, priorYear },
|
|
720
|
-
profitAndLoss: hasProfitAndLoss ? { currentYear: currentPl, priorYear: priorPl } : null,
|
|
893
|
+
profitAndLoss: hasProfitAndLoss ? { format: profitAndLossFormatNumber, currentYear: currentPl, priorYear: priorPl } : null,
|
|
721
894
|
fixedAssetNote,
|
|
722
895
|
averageEmployees: averageEmployees ?? null,
|
|
723
896
|
principalActivity,
|
|
@@ -734,6 +907,7 @@ export function mapIxbrlAccounts(source, expect = {}, { suiteConcepts } = {}) {
|
|
|
734
907
|
: null,
|
|
735
908
|
firstAccounts: Boolean(expect.firstAccounts),
|
|
736
909
|
notes,
|
|
910
|
+
sourceConcepts,
|
|
737
911
|
unmapped,
|
|
738
912
|
};
|
|
739
913
|
}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
// server run.
|
|
19
19
|
|
|
20
20
|
import { DOMParser } from "@xmldom/xmldom";
|
|
21
|
+
import { UK_GAAP_SUITE_DATE, translateUkGaapFacts } from "./ixbrl-uk-gaap.js";
|
|
21
22
|
|
|
22
23
|
const NS_IX_11 = "http://www.xbrl.org/2013/inlineXBRL";
|
|
23
24
|
const NS_IX_10 = "http://www.xbrl.org/2008/inlineXBRL";
|
|
@@ -41,7 +42,8 @@ const TRANSFORMATION_REGISTRIES = [
|
|
|
41
42
|
|
|
42
43
|
const FRC_NAMESPACE = /^http:\/\/xbrl\.frc\.org\.uk\/(fr|cd|reports)\/(\d{4}-\d{2}-\d{2})\/(.+)$/;
|
|
43
44
|
const FRC_ENTRY_POINT = /xbrl\.frc\.org\.uk\/([^/]+)\/(\d{4}-\d{2}-\d{2})\//;
|
|
44
|
-
const OLD_UK_GAAP_NAMESPACE = /^http:\/\/www\.xbrl\.org\/uk\/(gaap|cd|fr)\b/;
|
|
45
|
+
const OLD_UK_GAAP_NAMESPACE = /^http:\/\/www\.xbrl\.org\/uk\/(?:gaap|cd|fr|reports)\/[^/]+\/(\d{4}-\d{2}-\d{2})\b/;
|
|
46
|
+
const OLD_UK_GAAP_ENTRY_POINT = /xbrl\.org\/uk\/(?:gaap|cd|fr|reports)\/[^/]+\/(\d{4}-\d{2}-\d{2})\//;
|
|
45
47
|
|
|
46
48
|
const DROP_THE_SET = "Drop all the files of the set together, or the zip.";
|
|
47
49
|
|
|
@@ -456,6 +458,7 @@ function suiteOf(namespaces, schemaRefs) {
|
|
|
456
458
|
suite.date = match[2];
|
|
457
459
|
} else if (/xbrl\.org\/uk\//.test(href)) {
|
|
458
460
|
suite.family = "UK-GAAP";
|
|
461
|
+
suite.date ??= href.match(OLD_UK_GAAP_ENTRY_POINT)?.[1] ?? null;
|
|
459
462
|
}
|
|
460
463
|
}
|
|
461
464
|
for (const namespace of namespaces) {
|
|
@@ -465,6 +468,7 @@ function suiteOf(namespaces, schemaRefs) {
|
|
|
465
468
|
suite.modules.add(match[3]);
|
|
466
469
|
} else if (OLD_UK_GAAP_NAMESPACE.test(namespace)) {
|
|
467
470
|
suite.family = "UK-GAAP";
|
|
471
|
+
suite.date ??= namespace.match(OLD_UK_GAAP_NAMESPACE)[1];
|
|
468
472
|
}
|
|
469
473
|
}
|
|
470
474
|
return suite;
|
|
@@ -475,10 +479,17 @@ const SUITE_LABELS = {
|
|
|
475
479
|
"IFRS": "IFRS",
|
|
476
480
|
"Charities": "Charities SORP",
|
|
477
481
|
"UKSEF": "UK Single Electronic Format",
|
|
478
|
-
"UK-GAAP": "old UK GAAP",
|
|
479
482
|
};
|
|
480
483
|
|
|
481
484
|
function checkSuite(suite) {
|
|
485
|
+
if (suite.family === "UK-GAAP") {
|
|
486
|
+
if (suite.date !== UK_GAAP_SUITE_DATE) {
|
|
487
|
+
throw new IxbrlError(
|
|
488
|
+
`This file uses the UK GAAP taxonomy of ${suite.date ?? "an unstated date"}; the form reads the ${UK_GAAP_SUITE_DATE} suite.`,
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
482
493
|
if (suite.family && !READ_SUITES.includes(suite.family)) {
|
|
483
494
|
const label = SUITE_LABELS[suite.family] ?? suite.family;
|
|
484
495
|
throw new IxbrlError(`This file uses the ${label} taxonomy; the form takes FRS 102 and FRS 105 accounts.`);
|
|
@@ -663,7 +674,7 @@ export function readIxbrlSet(documents) {
|
|
|
663
674
|
return {
|
|
664
675
|
kind,
|
|
665
676
|
suite: { family: suite.family, date: suite.date, modules: [...suite.modules] },
|
|
666
|
-
facts,
|
|
677
|
+
facts: suite.family === "UK-GAAP" ? translateUkGaapFacts(facts) : facts,
|
|
667
678
|
contexts,
|
|
668
679
|
units,
|
|
669
680
|
leftOutTargets: [...leftOut],
|
|
@@ -18,7 +18,7 @@ import { buildNewBook } from "./diya-gl-new-book.js";
|
|
|
18
18
|
import { diyaGlToScenario } from "./diya-gl-loader.js";
|
|
19
19
|
import { calculateFromDiyaGl } from "./diya-gl-calculator.js";
|
|
20
20
|
import { buildOpeningBalance, toV2OpeningBalances } from "./scenario-extractor.js";
|
|
21
|
-
import { FIXED_ASSET_ACCOUNTS, FIXED_ASSET_CLASSES, IxbrlError,
|
|
21
|
+
import { FIXED_ASSET_ACCOUNTS, FIXED_ASSET_CLASSES, IxbrlError, natureProfitBeforeTax } from "./ixbrl-accounts.js";
|
|
22
22
|
|
|
23
23
|
// The register's class name for each class the accounts note uses.
|
|
24
24
|
const REGISTER_CLASS = {
|
|
@@ -70,7 +70,13 @@ function sheetOf(balanceSheet) {
|
|
|
70
70
|
const stocks = n(balanceSheet.stocks);
|
|
71
71
|
const debtors = n(balanceSheet.debtors);
|
|
72
72
|
const currentAssets = balanceSheet.currentAssets ?? stocks + debtors + n(balanceSheet.cashAtBank);
|
|
73
|
-
const lineCreditors =
|
|
73
|
+
const lineCreditors =
|
|
74
|
+
n(balanceSheet.bankLoansOverdrafts) +
|
|
75
|
+
n(balanceSheet.tradeCreditors) +
|
|
76
|
+
n(balanceSheet.corporationTax) +
|
|
77
|
+
n(balanceSheet.taxationSocialSecurity) +
|
|
78
|
+
n(balanceSheet.otherCreditors) +
|
|
79
|
+
n(balanceSheet.accruals);
|
|
74
80
|
const within = balanceSheet.creditorsWithinOneYear ?? lineCreditors;
|
|
75
81
|
const corporationTax = n(balanceSheet.corporationTax);
|
|
76
82
|
const calledUp = n(balanceSheet.calledUpShareCapital);
|
|
@@ -284,8 +290,6 @@ class LineWriter {
|
|
|
284
290
|
}
|
|
285
291
|
}
|
|
286
292
|
|
|
287
|
-
const referenceFor = (concept) => `IXBRL-${concept}`;
|
|
288
|
-
|
|
289
293
|
// The opening journal: the balance sheet at the start of the year.
|
|
290
294
|
function openingJournal(writer, date, opening, assetClasses) {
|
|
291
295
|
const reference = "OB-001";
|
|
@@ -323,11 +327,14 @@ function profitAndLossLines(profitAndLoss, { stockMovement, depreciation, tagged
|
|
|
323
327
|
};
|
|
324
328
|
add("TurnoverRevenue", "sales", "4000", profitAndLoss.turnover, "Turnover");
|
|
325
329
|
add("OtherOperatingIncomeFormat2", "sales", "4003", profitAndLoss.otherOperatingIncome, "Other operating income");
|
|
330
|
+
add("OwnWorkCapitalised", "sales", "4003", profitAndLoss.ownWorkCapitalised, "Own work capitalised");
|
|
326
331
|
const costOfSales = profitAndLoss.costOfSales !== undefined ? profitAndLoss.costOfSales : profitAndLoss.rawMaterialsConsumables;
|
|
327
332
|
const costConcept = profitAndLoss.costOfSales !== undefined ? "CostSales" : "RawMaterialsConsumablesUsed";
|
|
328
333
|
if (costOfSales !== undefined || stockMovement !== 0)
|
|
329
334
|
add(costConcept, "purchases", "5000", n(costOfSales) + stockMovement, "Cost of sales");
|
|
335
|
+
add("OtherExternalCharges", "purchases", "5001", profitAndLoss.otherExternalCharges, "Other external charges");
|
|
330
336
|
add("StaffCostsEmployeeBenefitsExpense", "purchases", "5101", profitAndLoss.staffCosts, "Staff costs");
|
|
337
|
+
add("InterestPayableSimilarChargesFinanceCosts", "purchases", "5803", profitAndLoss.interestPayable, "Interest payable");
|
|
331
338
|
if (profitAndLoss.administrativeExpenses !== undefined) {
|
|
332
339
|
// The engine works the gain or loss on disposal from the sale, so it comes out of the expenses the file nets it into.
|
|
333
340
|
add(
|
|
@@ -354,9 +361,10 @@ function profitAndLossLines(profitAndLoss, { stockMovement, depreciation, tagged
|
|
|
354
361
|
|
|
355
362
|
function plProfitBeforeTax(profitAndLoss) {
|
|
356
363
|
if (profitAndLoss.profitBeforeTax !== undefined) return profitAndLoss.profitBeforeTax;
|
|
357
|
-
const micro =
|
|
358
|
-
|
|
359
|
-
if (
|
|
364
|
+
const micro = natureProfitBeforeTax(profitAndLoss);
|
|
365
|
+
const netInterest = n(profitAndLoss.interestReceivable) - n(profitAndLoss.interestPayable);
|
|
366
|
+
if (micro !== undefined) return micro + netInterest;
|
|
367
|
+
if (profitAndLoss.operatingProfit !== undefined) return profitAndLoss.operatingProfit + netInterest;
|
|
360
368
|
if (profitAndLoss.profit !== undefined) return profitAndLoss.profit + n(profitAndLoss.tax);
|
|
361
369
|
return undefined;
|
|
362
370
|
}
|
|
@@ -372,6 +380,7 @@ function bookShell(accounts, period, taxData, extras) {
|
|
|
372
380
|
book.documentInfo.entriesComment = extras.comment;
|
|
373
381
|
if (accounts.principalActivity) book.entityInformation.organizationDescription = accounts.principalActivity;
|
|
374
382
|
if (number) book.entityInformation["diya-gl:companyNumber"] = number;
|
|
383
|
+
book.accounts.purchases["5001"] = { "accountMainDescription": "Sub contractors", "diya-gl:column": "P" };
|
|
375
384
|
book.accounts.purchases["5101"] = { "accountMainDescription": "Employee wages (non-PAYE)", "diya-gl:column": "S" };
|
|
376
385
|
book.accounts.purchases["5501"] = { "accountMainDescription": "General admin", "diya-gl:column": "AA" };
|
|
377
386
|
book.accounts.sales["4006"] = { accountMainDescription: "Fixed asset sales" };
|
|
@@ -428,6 +437,7 @@ async function buildYear(accounts, which, deps) {
|
|
|
428
437
|
const period = current ? accounts.period : accounts.priorPeriod;
|
|
429
438
|
const closingSheet = sheetOf(accounts.balanceSheet[current ? "currentYear" : "priorYear"]);
|
|
430
439
|
const profitAndLoss = accounts.profitAndLoss?.[current ? "currentYear" : "priorYear"] ?? null;
|
|
440
|
+
const referenceFor = (concept, statedConcept = concept) => `IXBRL-${accounts.sourceConcepts?.[statedConcept] ?? concept}`;
|
|
431
441
|
const taxData = await loadTaxData(period, deps);
|
|
432
442
|
const additionsRate = taxData.depreciation?.[ADDITIONS_RATE_KEY] ?? 0;
|
|
433
443
|
|
|
@@ -496,6 +506,11 @@ async function buildYear(accounts, which, deps) {
|
|
|
496
506
|
};
|
|
497
507
|
};
|
|
498
508
|
let posted = postsFor(0);
|
|
509
|
+
if (current && profitAndLoss?.changeInStocks !== undefined && Math.abs(profitAndLoss.changeInStocks - stockMovement) > 1) {
|
|
510
|
+
differences.push(
|
|
511
|
+
`${label}: the file states a change in stocks of ${profitAndLoss.changeInStocks} and its balance sheets a stock movement of ${stockMovement}.`,
|
|
512
|
+
);
|
|
513
|
+
}
|
|
499
514
|
|
|
500
515
|
const build = (flow) => {
|
|
501
516
|
// The prior year's opening balance sheet: the closing one worked back.
|
|
@@ -513,7 +528,7 @@ async function buildYear(accounts, which, deps) {
|
|
|
513
528
|
writer.invoice(
|
|
514
529
|
"purchases",
|
|
515
530
|
period.end,
|
|
516
|
-
referenceFor("PropertyPlantEquipmentAdditions"),
|
|
531
|
+
referenceFor("PropertyPlantEquipmentAdditions", "AdditionsOtherThanThroughBusinessCombinationsPropertyPlantEquipment"),
|
|
517
532
|
"5900",
|
|
518
533
|
additionsAmount,
|
|
519
534
|
"Fixed asset additions",
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2006-2026 DIY Accounting Limited
|
|
3
|
+
//
|
|
4
|
+
// ixbrl-uk-gaap.js — the translation from the old UK GAAP taxonomy (the
|
|
5
|
+
// 2009-09-01 suite: uk-gaap, uk-bus and uk-direp) to the FRS 102 names that
|
|
6
|
+
// ixbrl-accounts.js reads. The reader applies it once, after it has found the
|
|
7
|
+
// suite, so the mapping, the checks and the summary book read FRS 102 names
|
|
8
|
+
// only. A translated fact keeps `sourceConcept`, the UK GAAP name it came
|
|
9
|
+
// from. A UK GAAP name this table does not hold keeps its own name and its
|
|
10
|
+
// own namespace, and the mapping lists it as unmapped.
|
|
11
|
+
|
|
12
|
+
export const UK_GAAP_SUITE_DATE = "2009-09-01";
|
|
13
|
+
|
|
14
|
+
const NS_UK_GAAP = `http://www.xbrl.org/uk/gaap/core/${UK_GAAP_SUITE_DATE}`;
|
|
15
|
+
const NS_UK_BUS = `http://www.xbrl.org/uk/cd/business/${UK_GAAP_SUITE_DATE}`;
|
|
16
|
+
const NS_UK_DIREP = `http://www.xbrl.org/uk/reports/direp/${UK_GAAP_SUITE_DATE}`;
|
|
17
|
+
|
|
18
|
+
// The namespaces a translated fact, dimension and member take. The mapping
|
|
19
|
+
// matches by local name, so these only say the name is an FRS 102 one.
|
|
20
|
+
const FRS_NAMESPACE = {
|
|
21
|
+
"uk-gaap": "http://xbrl.frc.org.uk/fr/2022-01-01/core",
|
|
22
|
+
"uk-bus": "http://xbrl.frc.org.uk/cd/2022-01-01/business",
|
|
23
|
+
"uk-direp": "http://xbrl.frc.org.uk/reports/2022-01-01/direp",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const MODULE_OF_NAMESPACE = { [NS_UK_GAAP]: "uk-gaap", [NS_UK_BUS]: "uk-bus", [NS_UK_DIREP]: "uk-direp" };
|
|
27
|
+
|
|
28
|
+
const MATURITY = "MaturitiesOrExpirationPeriodsDimension";
|
|
29
|
+
const EQUITY_CLASS = "EquityClassesDimension";
|
|
30
|
+
const WITHIN = "WithinOneYear";
|
|
31
|
+
const AFTER = "AfterOneYear";
|
|
32
|
+
|
|
33
|
+
const row = (module, concept, to = concept, dimension, member) => ({
|
|
34
|
+
module,
|
|
35
|
+
concept,
|
|
36
|
+
to,
|
|
37
|
+
...(dimension ? { dimension, member } : {}),
|
|
38
|
+
});
|
|
39
|
+
const gaap = (concept, to, dimension, member) => row("uk-gaap", concept, to, dimension, member);
|
|
40
|
+
const bus = (concept, to) => row("uk-bus", concept, to);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* One row per UK GAAP name: the FRS 102 local name, and the dimension and
|
|
44
|
+
* member the translated fact gains (replacing the fact's own value for that
|
|
45
|
+
* dimension). `OtherOperatingIncome` is the one row whose target depends on
|
|
46
|
+
* the file: `toWithGrossProfit` when the file states `CostSales` or
|
|
47
|
+
* `GrossProfitLoss`, `to` otherwise.
|
|
48
|
+
*/
|
|
49
|
+
export const UK_GAAP_TO_FRS_102 = [
|
|
50
|
+
gaap("FixedAssets"),
|
|
51
|
+
gaap("TangibleFixedAssets", "PropertyPlantEquipment"),
|
|
52
|
+
gaap("TangibleFixedAssetsCostOrValuation", "PropertyPlantEquipmentGrossCost"),
|
|
53
|
+
gaap("TangibleFixedAssetsAdditions", "AdditionsOtherThanThroughBusinessCombinationsPropertyPlantEquipment"),
|
|
54
|
+
gaap("TangibleFixedAssetsDisposals", "DisposalsPropertyPlantEquipment"),
|
|
55
|
+
gaap("TangibleFixedAssetsDepreciation", "AccumulatedDepreciationImpairmentPropertyPlantEquipment"),
|
|
56
|
+
gaap("TangibleFixedAssetsDepreciationChargedInPeriod", "IncreaseFromDepreciationChargeForYearPropertyPlantEquipment"),
|
|
57
|
+
gaap("TangibleFixedAssetsDepreciationDecreaseIncreaseOnDisposals", "DisposalsDecreaseInDepreciationImpairmentPropertyPlantEquipment"),
|
|
58
|
+
gaap("StocksInventory", "TotalInventories"),
|
|
59
|
+
gaap("Debtors"),
|
|
60
|
+
gaap("DebtorsDueWithinOneYear", "Debtors", MATURITY, WITHIN),
|
|
61
|
+
gaap("PrepaymentsAccruedIncomeNotExpressedWithinCurrentAssetSubtotal"),
|
|
62
|
+
gaap("CashBankInHand", "CashBankOnHand"),
|
|
63
|
+
gaap("CurrentAssets"),
|
|
64
|
+
gaap("NetCurrentAssetsLiabilities"),
|
|
65
|
+
gaap("TotalAssetsLessCurrentLiabilities"),
|
|
66
|
+
gaap("CreditorsDueWithinOneYear", "Creditors", MATURITY, WITHIN),
|
|
67
|
+
gaap("CreditorsDueAfterOneYear", "Creditors", MATURITY, AFTER),
|
|
68
|
+
gaap("TradeCreditors", "TradeCreditorsTradePayables", MATURITY, WITHIN),
|
|
69
|
+
gaap("TradeCreditorsWithinOneYear", "TradeCreditorsTradePayables", MATURITY, WITHIN),
|
|
70
|
+
gaap("CorporationTax", "CorporationTaxPayable"),
|
|
71
|
+
gaap("CorporationTaxDueWithinOneYear", "CorporationTaxPayable"),
|
|
72
|
+
gaap("OtherCreditors", "OtherCreditors", MATURITY, WITHIN),
|
|
73
|
+
gaap("OtherCreditorsDueWithinOneYear", "OtherCreditors", MATURITY, WITHIN),
|
|
74
|
+
gaap("TaxationSocialSecurityDueWithinOneYear", "TaxationSocialSecurityPayable", MATURITY, WITHIN),
|
|
75
|
+
gaap("AccrualsDeferredIncomeWithinOneYear", "AccruedLiabilitiesNotExpressedWithinCreditorsSubtotal", MATURITY, WITHIN),
|
|
76
|
+
gaap("ProvisionsForLiabilitiesCharges", "ProvisionsForLiabilitiesBalanceSheetSubtotal"),
|
|
77
|
+
gaap("NetAssetsLiabilitiesIncludingPensionAssetLiability", "NetAssetsLiabilities"),
|
|
78
|
+
gaap("CalledUpShareCapital", "Equity", EQUITY_CLASS, "ShareCapital"),
|
|
79
|
+
gaap("ProfitLossAccountReserve", "Equity", EQUITY_CLASS, "RetainedEarningsAccumulatedLosses"),
|
|
80
|
+
gaap("RevaluationReserve", "Equity", EQUITY_CLASS, "RevaluationReserve"),
|
|
81
|
+
gaap("ShareholderFunds", "Equity"),
|
|
82
|
+
gaap("TurnoverGrossOperatingRevenue", "TurnoverRevenue"),
|
|
83
|
+
gaap("CostSales"),
|
|
84
|
+
gaap("GrossProfitLoss"),
|
|
85
|
+
gaap("AdministrativeExpenses"),
|
|
86
|
+
gaap("OperatingProfitLoss"),
|
|
87
|
+
gaap("ProfitLossOnOrdinaryActivitiesBeforeTax"),
|
|
88
|
+
gaap("ProfitLossOnOrdinaryActivitiesAfterTax"),
|
|
89
|
+
gaap("OtherInterestReceivableSimilarIncome", "OtherInterestReceivableSimilarIncomeFinanceIncome"),
|
|
90
|
+
gaap("InterestPayableSimilarCharges", "InterestPayableSimilarChargesFinanceCosts"),
|
|
91
|
+
gaap("TaxOnProfitOrLossOnOrdinaryActivities", "TaxTaxCreditOnProfitOrLossOnOrdinaryActivities"),
|
|
92
|
+
gaap("ProfitLossForPeriod", "ProfitLoss"),
|
|
93
|
+
{ ...gaap("OtherOperatingIncome", "OtherOperatingIncomeFormat2"), toWithGrossProfit: "OtherOperatingIncomeFormat1" },
|
|
94
|
+
gaap("RawMaterialsConsumables", "RawMaterialsConsumablesUsed"),
|
|
95
|
+
gaap("OtherExternalCharges"),
|
|
96
|
+
gaap("OwnWorkCapitalised"),
|
|
97
|
+
gaap("WagesSalaries"),
|
|
98
|
+
gaap("SocialSecurityCosts"),
|
|
99
|
+
gaap("IncreaseDecreaseInStocksFinishedGoodsWorkInProgress", "IncreaseDecreaseInStocksInventoriesFinishedGoodsWorkInProgress"),
|
|
100
|
+
gaap("StaffCosts", "StaffCostsEmployeeBenefitsExpense"),
|
|
101
|
+
gaap("DepreciationOtherAmountsWrittenOffTangibleIntangibleFixedAssets", "DepreciationAmortisationImpairmentExpense"),
|
|
102
|
+
gaap("OtherOperatingChargesFormat2", "OtherOperatingExpensesFormat2"),
|
|
103
|
+
gaap("AverageNumberEmployeesDuringPeriod"),
|
|
104
|
+
gaap("ParValueShare"),
|
|
105
|
+
gaap("ShareCapitalAllottedCalledUpPaid", "ShareCapital"),
|
|
106
|
+
gaap("TradeDebtors", "TradeDebtorsTradeReceivables"),
|
|
107
|
+
gaap("OtherDebtors"),
|
|
108
|
+
gaap("IntangibleFixedAssets", "IntangibleAssets"),
|
|
109
|
+
gaap("BankBorrowingsOverdrafts"),
|
|
110
|
+
bus("UKCompaniesHouseRegisteredNumber"),
|
|
111
|
+
bus("EntityCurrentLegalOrRegisteredName"),
|
|
112
|
+
bus("BalanceSheetDate"),
|
|
113
|
+
bus("StartDateForPeriodCoveredByReport"),
|
|
114
|
+
bus("EndDateForPeriodCoveredByReport"),
|
|
115
|
+
bus("DescriptionPrincipalActivities"),
|
|
116
|
+
bus("NameEntityOfficer"),
|
|
117
|
+
bus("EntityDormant", "EntityDormantTruefalse"),
|
|
118
|
+
gaap("NameDirectorSigningAccounts", "DirectorSigningFinancialStatements"),
|
|
119
|
+
gaap("DateApprovalAccounts", "DateAuthorisationFinancialStatementsForIssue"),
|
|
120
|
+
gaap("NumberSharesAllotted", "NumberSharesIssuedFullyPaid"),
|
|
121
|
+
gaap("NominalValueSharesIssued", "NominalValueAllottedShareCapital"),
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
// The old suite names some dimensions and members the FRS 102 suite names differently. Members not listed keep their name.
|
|
125
|
+
export const UK_GAAP_DIMENSIONS_TO_FRS_102 = {
|
|
126
|
+
MaturitiesDimension: { to: MATURITY, members: {} },
|
|
127
|
+
TangibleFixedAssetClassesDimension: {
|
|
128
|
+
to: "PropertyPlantEquipmentClassesDimension",
|
|
129
|
+
members: { FixturesFittings: "FurnitureFittings" },
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const ROWS = new Map(UK_GAAP_TO_FRS_102.map((entry) => [`${entry.module}:${entry.concept}`, entry]));
|
|
134
|
+
|
|
135
|
+
const localOf = (key) => key.slice(key.indexOf("#") + 1);
|
|
136
|
+
|
|
137
|
+
function translatedDimensions(dimensions, entry, module) {
|
|
138
|
+
const out = {};
|
|
139
|
+
for (const [dimensionKey, memberKey] of Object.entries(dimensions)) {
|
|
140
|
+
const renamed = UK_GAAP_DIMENSIONS_TO_FRS_102[localOf(dimensionKey)];
|
|
141
|
+
if (renamed && MODULE_OF_NAMESPACE[dimensionKey.slice(0, dimensionKey.indexOf("#"))]) {
|
|
142
|
+
const member = localOf(memberKey);
|
|
143
|
+
out[`${FRS_NAMESPACE["uk-gaap"]}#${renamed.to}`] = `${FRS_NAMESPACE["uk-gaap"]}#${renamed.members[member] ?? member}`;
|
|
144
|
+
} else {
|
|
145
|
+
out[dimensionKey] = memberKey;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (entry.dimension) {
|
|
149
|
+
out[`${FRS_NAMESPACE[module]}#${entry.dimension}`] = `${FRS_NAMESPACE[module]}#${entry.member}`;
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function factKey(fact) {
|
|
155
|
+
const dimensions = Object.entries(fact.dimensions)
|
|
156
|
+
.sort(([a], [b]) => (a < b ? -1 : 1))
|
|
157
|
+
.map(([dimension, member]) => `${dimension}=${member}`)
|
|
158
|
+
.join(",");
|
|
159
|
+
const period = fact.period.instant ?? `${fact.period.startDate}/${fact.period.endDate}`;
|
|
160
|
+
return `${fact.namespace}#${fact.localName}|${fact.entity.scheme}|${fact.entity.identifier}|${period}|${dimensions}|${fact.unit ?? ""}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Translate the UK GAAP facts of a read into FRS 102 names.
|
|
165
|
+
* Two UK GAAP names that translate to the same fact (a total and the same
|
|
166
|
+
* total restated within one year) give one fact when they agree; when they
|
|
167
|
+
* differ the first is translated and the second keeps its UK GAAP name.
|
|
168
|
+
* @param {Array<Object>} facts - facts from the reader
|
|
169
|
+
* @returns {Array<Object>} the facts, translated where the table holds the name
|
|
170
|
+
*/
|
|
171
|
+
export function translateUkGaapFacts(facts) {
|
|
172
|
+
const grossProfitStated = facts.some(
|
|
173
|
+
(fact) => fact.namespace === NS_UK_GAAP && (fact.localName === "CostSales" || fact.localName === "GrossProfitLoss"),
|
|
174
|
+
);
|
|
175
|
+
const out = [];
|
|
176
|
+
const taken = new Map();
|
|
177
|
+
for (const fact of facts) {
|
|
178
|
+
const module = MODULE_OF_NAMESPACE[fact.namespace];
|
|
179
|
+
const entry = module ? ROWS.get(`${module}:${fact.localName}`) : undefined;
|
|
180
|
+
if (!entry) {
|
|
181
|
+
out.push(fact);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const localName = grossProfitStated && entry.toWithGrossProfit ? entry.toWithGrossProfit : entry.to;
|
|
185
|
+
const translated = {
|
|
186
|
+
...fact,
|
|
187
|
+
namespace: FRS_NAMESPACE[module],
|
|
188
|
+
localName,
|
|
189
|
+
dimensions: translatedDimensions(fact.dimensions, entry, module),
|
|
190
|
+
sourceConcept: fact.localName,
|
|
191
|
+
};
|
|
192
|
+
const key = factKey(translated);
|
|
193
|
+
const earlier = taken.get(key);
|
|
194
|
+
if (earlier === undefined) {
|
|
195
|
+
taken.set(key, translated);
|
|
196
|
+
out.push(translated);
|
|
197
|
+
} else if (earlier.value !== translated.value) {
|
|
198
|
+
out.push(fact);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
}
|