@diy-accounting-uk/diya-gl 1.2.45 → 1.2.46
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 +30 -4
- package/dist/app/lib/book-checks/ltd.js +3 -5
- package/dist/app/lib/book-checks.js +10 -6
- package/dist/app/lib/calculators/ltd.js +13 -9
- package/dist/app/lib/derivations/vat-return.js +5 -4
- package/dist/app/lib/diya-gl-loader.js +2 -0
- package/dist/app/lib/generator.js +7 -4
- package/dist/app/lib/ltd-layout.js +43 -0
- package/dist/app/lib/provenance-data.js +4 -4
- package/dist/app/lib/scenario-extractor.js +37 -20
- package/dist/app/lib/spreadsheet-runner.js +4 -3
- package/dist/app/lib/xlsx-exporter.js +22 -7
- package/dist/app/products/ltd.js +29 -16
- package/dist/app/products/se.js +1 -0
- package/dist/web/spreadsheets.diyaccounting.co.uk/public/schema/diya-gl-docs.md +7 -0
- package/package.json +1 -1
|
@@ -1304,7 +1304,7 @@
|
|
|
1304
1304
|
"tag": "diya-gl-v1.2.41",
|
|
1305
1305
|
"date": "2026-10-01",
|
|
1306
1306
|
"formatVersion": "diya-gl/1",
|
|
1307
|
-
"engineVersion": "1.2.
|
|
1307
|
+
"engineVersion": "1.2.41+5b1fc93c5",
|
|
1308
1308
|
"taxDataHash": "89424bd36c0d",
|
|
1309
1309
|
"reconciledCommit": "54e8731a91279975e61b1a232fbb67fda89515f0",
|
|
1310
1310
|
"templates": {
|
|
@@ -1330,7 +1330,7 @@
|
|
|
1330
1330
|
"tag": "diya-gl-v1.2.42",
|
|
1331
1331
|
"date": "2026-10-03",
|
|
1332
1332
|
"formatVersion": "diya-gl/1",
|
|
1333
|
-
"engineVersion": "1.2.
|
|
1333
|
+
"engineVersion": "1.2.42+ed6c7809b",
|
|
1334
1334
|
"taxDataHash": "89424bd36c0d",
|
|
1335
1335
|
"reconciledCommit": "54e8731a91279975e61b1a232fbb67fda89515f0",
|
|
1336
1336
|
"templates": {
|
|
@@ -1356,7 +1356,7 @@
|
|
|
1356
1356
|
"tag": "diya-gl-v1.2.43",
|
|
1357
1357
|
"date": "2026-10-03",
|
|
1358
1358
|
"formatVersion": "diya-gl/1",
|
|
1359
|
-
"engineVersion": "1.2.
|
|
1359
|
+
"engineVersion": "1.2.43+971ca45ad",
|
|
1360
1360
|
"taxDataHash": "89424bd36c0d",
|
|
1361
1361
|
"reconciledCommit": "54e8731a91279975e61b1a232fbb67fda89515f0",
|
|
1362
1362
|
"templates": {
|
|
@@ -1382,7 +1382,33 @@
|
|
|
1382
1382
|
"tag": "diya-gl-v1.2.44",
|
|
1383
1383
|
"date": "2026-10-03",
|
|
1384
1384
|
"formatVersion": "diya-gl/1",
|
|
1385
|
-
"engineVersion": "1.2.
|
|
1385
|
+
"engineVersion": "1.2.44+4b8f90020",
|
|
1386
|
+
"taxDataHash": "89424bd36c0d",
|
|
1387
|
+
"reconciledCommit": "54e8731a91279975e61b1a232fbb67fda89515f0",
|
|
1388
|
+
"templates": {
|
|
1389
|
+
"bst": {
|
|
1390
|
+
"hash": "0d003e10e00c",
|
|
1391
|
+
"scorecard": "812 passed, 0 warnings, 0 failed"
|
|
1392
|
+
},
|
|
1393
|
+
"taxi": {
|
|
1394
|
+
"hash": "1ff69654d64d",
|
|
1395
|
+
"scorecard": "700 passed, 0 warnings, 0 failed"
|
|
1396
|
+
},
|
|
1397
|
+
"se": {
|
|
1398
|
+
"hash": "064605ae76ed",
|
|
1399
|
+
"scorecard": "8014 passed, 0 warnings, 0 failed"
|
|
1400
|
+
},
|
|
1401
|
+
"ltd": {
|
|
1402
|
+
"hash": "5bb7be9b6d97",
|
|
1403
|
+
"scorecard": "101605 passed, 0 warnings, 0 failed"
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
},
|
|
1407
|
+
{
|
|
1408
|
+
"tag": "diya-gl-v1.2.45",
|
|
1409
|
+
"date": "2026-10-04",
|
|
1410
|
+
"formatVersion": "diya-gl/1",
|
|
1411
|
+
"engineVersion": "1.2.45+3801b0c2",
|
|
1386
1412
|
"taxDataHash": "89424bd36c0d",
|
|
1387
1413
|
"reconciledCommit": "54e8731a91279975e61b1a232fbb67fda89515f0",
|
|
1388
1414
|
"templates": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { changeLinePostingDate } from "../diya-gl-edits.js";
|
|
17
17
|
import { LTD_PURCHASE_CODE_MAP, LTD_SALES_CODE_MAP } from "../scenario-extractor.js";
|
|
18
|
-
import { BANK_ACCOUNT_FILES, BANK_TRANSFER_CODES,
|
|
18
|
+
import { BANK_ACCOUNT_FILES, BANK_TRANSFER_CODES, ltdBankPlacement } from "../ltd-layout.js";
|
|
19
19
|
|
|
20
20
|
// The bank account each transfer letter names: Currentaccount is BB, so a
|
|
21
21
|
// BB-coded line on any other workbook is a transfer to or from the current
|
|
@@ -161,13 +161,11 @@ export const LTD_CHECK_SPECS = [
|
|
|
161
161
|
const fileName = bankFileOf(line);
|
|
162
162
|
if (!fileName) return true;
|
|
163
163
|
if (line.debitCreditCode !== "D" && line.debitCreditCode !== "C") return false;
|
|
164
|
-
|
|
165
|
-
const analysed = line.debitCreditCode === "D" ? layout.receiptCodes : layout.paymentCodes;
|
|
166
|
-
return !analysed.includes(line["diya-gl:bankCode"]);
|
|
164
|
+
return !ltdBankPlacement(fileName, line.debitCreditCode === "D", line["diya-gl:bankCode"], line.amount).analysed;
|
|
167
165
|
});
|
|
168
166
|
},
|
|
169
167
|
consequence: function () {
|
|
170
|
-
return "Each bank workbook analyses its own list of codes, and the four workbooks do not share one list -- the cash book has no receipt column for RV, RC or X.
|
|
168
|
+
return "Each bank workbook analyses its own list of codes, and the four workbooks do not share one list -- the cash book has no receipt column for RV, RC or X. A refund coded DR on a payment or CR on a receipt lands in the opposite block as a negative entry under its own code. Any other entry coded outside its workbook's list has no column to land in, and the package cannot be written at all.";
|
|
171
169
|
},
|
|
172
170
|
buildHelper: null,
|
|
173
171
|
apply: null,
|
|
@@ -684,9 +684,13 @@ function bankEntries(lines, bankCode, side) {
|
|
|
684
684
|
// straddling invoice is returned on a VAT period outside that year and is
|
|
685
685
|
// banked in a book this one does not hold, so it neither needs a settlement
|
|
686
686
|
// of its own nor takes up a bank line another invoice is waiting for.
|
|
687
|
-
|
|
687
|
+
// A credit note runs the other way from an invoice: its money goes back out
|
|
688
|
+
// as a refund (DR on a payment, CR on a receipt), or it is offset or written
|
|
689
|
+
// off and never reaches the bank. It neither wants a receipt or a payment
|
|
690
|
+
// the way an invoice does nor takes up one an invoice is waiting for.
|
|
691
|
+
function invoiceEntries(lines, journal) {
|
|
688
692
|
return lines.filter(function (line) {
|
|
689
|
-
return line.sourceJournalID === journal && !isStraddlingLine(line);
|
|
693
|
+
return line.sourceJournalID === journal && line.documentType !== "credit-note" && !isStraddlingLine(line);
|
|
690
694
|
});
|
|
691
695
|
}
|
|
692
696
|
|
|
@@ -769,7 +773,7 @@ const SETTLEMENT_KINDS = [
|
|
|
769
773
|
actionLabel: "Add the sale",
|
|
770
774
|
what: "sale",
|
|
771
775
|
sources: function (ctx) {
|
|
772
|
-
return unpaired(bankEntries(ctx.lines, RECEIPT_BANK_CODE, "D"),
|
|
776
|
+
return unpaired(bankEntries(ctx.lines, RECEIPT_BANK_CODE, "D"), invoiceEntries(ctx.lines, "sales"));
|
|
773
777
|
},
|
|
774
778
|
account: function (ctx) {
|
|
775
779
|
return ctx.chart.sales[0] || null;
|
|
@@ -785,7 +789,7 @@ const SETTLEMENT_KINDS = [
|
|
|
785
789
|
actionLabel: "Add the purchase",
|
|
786
790
|
what: "purchase",
|
|
787
791
|
sources: function (ctx) {
|
|
788
|
-
return unpaired(bankEntries(ctx.lines, PAYMENT_BANK_CODE, "C"),
|
|
792
|
+
return unpaired(bankEntries(ctx.lines, PAYMENT_BANK_CODE, "C"), invoiceEntries(ctx.lines, "purchases"));
|
|
789
793
|
},
|
|
790
794
|
account: function (ctx) {
|
|
791
795
|
return repostAccount(ctx, "purchases");
|
|
@@ -801,7 +805,7 @@ const SETTLEMENT_KINDS = [
|
|
|
801
805
|
actionLabel: "Add the receipt",
|
|
802
806
|
what: "receipt",
|
|
803
807
|
sources: function (ctx) {
|
|
804
|
-
return unpaired(
|
|
808
|
+
return unpaired(invoiceEntries(ctx.lines, "sales"), bankEntries(ctx.lines, RECEIPT_BANK_CODE, "D"));
|
|
805
809
|
},
|
|
806
810
|
account: settlementBankAccount,
|
|
807
811
|
build: function (ctx, source, account, entryNumber) {
|
|
@@ -815,7 +819,7 @@ const SETTLEMENT_KINDS = [
|
|
|
815
819
|
actionLabel: "Add the payment",
|
|
816
820
|
what: "payment",
|
|
817
821
|
sources: function (ctx) {
|
|
818
|
-
return unpaired(
|
|
822
|
+
return unpaired(invoiceEntries(ctx.lines, "purchases"), bankEntries(ctx.lines, PAYMENT_BANK_CODE, "C"));
|
|
819
823
|
},
|
|
820
824
|
account: settlementBankAccount,
|
|
821
825
|
build: function (ctx, source, account, entryNumber) {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
// makes the two comparable.
|
|
25
25
|
|
|
26
26
|
import { toExcelSerial } from "../spreadsheet-runner.js";
|
|
27
|
-
import { BANK_ACCOUNT_FILES, BANK_LAYOUTS, OPENING_FIXED_ASSET_COLUMNS, isLtdOpeningBankLine } from "../ltd-layout.js";
|
|
27
|
+
import { BANK_ACCOUNT_FILES, BANK_LAYOUTS, OPENING_FIXED_ASSET_COLUMNS, isLtdOpeningBankLine, ltdBankPlacement } from "../ltd-layout.js";
|
|
28
28
|
import { apportionCorporationTax, financialYearsInPeriod, financialYearNumber, financialYearRatesFor } from "../tax/corporation-tax.js";
|
|
29
29
|
import { calculateCapitalAllowances } from "../tax/capital-allowances.js";
|
|
30
30
|
import {
|
|
@@ -451,10 +451,11 @@ function bankMonthTotals(scenario, tabs, periodStart, entries) {
|
|
|
451
451
|
const tab = SHORT_MONTHS[parseDate(transaction.date).getUTCMonth()];
|
|
452
452
|
const month = file.months[tab];
|
|
453
453
|
if (!month) continue;
|
|
454
|
-
const
|
|
455
|
-
|
|
454
|
+
const placement = ltdBankPlacement(fileName, transaction.direction === "in", transaction.code, transaction.amount);
|
|
455
|
+
const receipt = placement.receipt;
|
|
456
|
+
month[receipt ? "receipts" : "payments"] += placement.amount;
|
|
456
457
|
const codes = receipt ? month.receiptCodes : month.paymentCodes;
|
|
457
|
-
codes[transaction.code] = (codes[transaction.code] || 0) +
|
|
458
|
+
codes[transaction.code] = (codes[transaction.code] || 0) + placement.amount;
|
|
458
459
|
if (entries) {
|
|
459
460
|
const monthEntries = entries[fileName].months[tab];
|
|
460
461
|
addEntry(monthEntries, receipt ? "receipts" : "payments", transaction);
|
|
@@ -1816,13 +1817,16 @@ function buildPublishedBalanceSheet(tb, admin) {
|
|
|
1816
1817
|
F6: fixedAssets,
|
|
1817
1818
|
E10: tb.EJ19,
|
|
1818
1819
|
E11: tb.EJ20,
|
|
1819
|
-
|
|
1820
|
+
// Cash and the intra transfers row stand in cash at bank whatever the
|
|
1821
|
+
// statement accounts do; only an overdrawn set of statement accounts
|
|
1822
|
+
// leaves it, for E19.
|
|
1823
|
+
E12: (statementAccounts > 0 ? statementAccounts : 0) + tb.EJ25 + tb.EJ26,
|
|
1820
1824
|
E16: -(tb.EJ28 + tb.EJ29 + tb.EJ30 + tb.EJ31),
|
|
1821
1825
|
E17: -tb.EJ35,
|
|
1822
1826
|
E18: -(tb.EJ32 + tb.EJ33 + tb.EJ34),
|
|
1823
|
-
// An overdrawn set of statement accounts
|
|
1824
|
-
//
|
|
1825
|
-
//
|
|
1827
|
+
// An overdrawn set of statement accounts stands as a creditor instead
|
|
1828
|
+
// (E19 = IF(SUM(EJ22:EJ24)<0, -SUM(EJ22:EJ24), 0)), mirroring E12's own
|
|
1829
|
+
// test.
|
|
1826
1830
|
E19: statementAccounts < 0 ? -statementAccounts : 0,
|
|
1827
1831
|
E29: -tb.EJ39,
|
|
1828
1832
|
E30: -tb.EJ40,
|
|
@@ -2523,7 +2527,7 @@ function attributeLtdResults(attribution, context) {
|
|
|
2523
2527
|
a.set(BS, "E10", EJ(19));
|
|
2524
2528
|
a.set(BS, "E11", EJ(20));
|
|
2525
2529
|
const statementAccounts = trialBalance.EJ22 + trialBalance.EJ23 + trialBalance.EJ24;
|
|
2526
|
-
a.set(BS, "E12", ...(statementAccounts > 0 ? [22, 23, 24, 25, 26] : [25]).map(EJ));
|
|
2530
|
+
a.set(BS, "E12", ...(statementAccounts > 0 ? [22, 23, 24, 25, 26] : [25, 26]).map(EJ));
|
|
2527
2531
|
a.set(BS, "E16", EJ(28), EJ(29), EJ(30), EJ(31));
|
|
2528
2532
|
a.set(BS, "E17", EJ(35));
|
|
2529
2533
|
a.set(BS, "E18", EJ(32), EJ(33), EJ(34));
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { calculateFromDiyaGl } from "../diya-gl-calculator.js";
|
|
12
12
|
import { diyaGlToScenario } from "../diya-gl-loader.js";
|
|
13
13
|
import { loadTaxDataForBook, productOf } from "../product-workbook.js";
|
|
14
|
-
import { isStraddlingLine, LTD_PURCHASE_CODE_MAP, LTD_SALES_CODE_MAP } from "../scenario-extractor.js";
|
|
14
|
+
import { isStraddlingLine, LTD_PURCHASE_CODE_MAP, LTD_SALES_CODE_MAP, signedAmount } from "../scenario-extractor.js";
|
|
15
15
|
import { dateFromExcelSerial } from "../calculators/shared.js";
|
|
16
16
|
import { VAT_RATE } from "../calculators/ltd.js";
|
|
17
17
|
import { splitVat, VATINTERFACE_FIRST_MONTH_ROW, VATINTERFACE_FIRST_ROW, VATINTERFACE_LAST_ROW } from "../tax/vat.js";
|
|
@@ -92,7 +92,8 @@ export function interfaceRows(results) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
function lineContribution(line, box) {
|
|
95
|
-
const
|
|
95
|
+
const gross = signedAmount(line);
|
|
96
|
+
const { vat, net } = splitGross(gross);
|
|
96
97
|
return {
|
|
97
98
|
entryNumber: line.entryNumber,
|
|
98
99
|
lineNumber: line.lineNumber ?? null,
|
|
@@ -100,7 +101,7 @@ function lineContribution(line, box) {
|
|
|
100
101
|
account: line.accountMainID,
|
|
101
102
|
reference: line.documentReference ?? null,
|
|
102
103
|
counterparty: line.detailComment ?? null,
|
|
103
|
-
gross: pence(
|
|
104
|
+
gross: pence(gross),
|
|
104
105
|
contributes: pence(box === "vat" ? vat : net),
|
|
105
106
|
};
|
|
106
107
|
}
|
|
@@ -122,7 +123,7 @@ function linesForQuarter(lines, quarterRows) {
|
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
function sumContributions(lines, part) {
|
|
125
|
-
return lines.reduce((total, line) => total + splitGross(line
|
|
126
|
+
return lines.reduce((total, line) => total + splitGross(signedAmount(line))[part], 0);
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
function reconcile(label, attributed, engine) {
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
splitStraddlingLines,
|
|
33
33
|
deriveStraddlingEntries,
|
|
34
34
|
signedAmount,
|
|
35
|
+
writtenOffBadDebtsNet,
|
|
35
36
|
} from "./scenario-extractor.js";
|
|
36
37
|
import { totalBusinessMiles, calculateMileageAllowance, FIXTURE_CAR_MILEAGE_RATES } from "./tax/mileage.js";
|
|
37
38
|
import { compareLines } from "./diya-gl-canonical.js";
|
|
@@ -421,6 +422,7 @@ export function diyaGlToScenario(book, lines, product) {
|
|
|
421
422
|
const mileageClaim = product === "se" ? calculateMileageAllowance(businessMiles, FIXTURE_CAR_MILEAGE_RATES) : 0;
|
|
422
423
|
expected.total_motor_net = Math.round(cashMotor / vatDivisor + mileageClaim);
|
|
423
424
|
expected.total_legal_net = Math.round((byCode.l || 0) / vatDivisor);
|
|
425
|
+
expected.total_bad_debts_net = writtenOffBadDebtsNet(salesLines, vatDivisor);
|
|
424
426
|
if (product === "ltd") {
|
|
425
427
|
expected.total_premises_net = Math.round((byCode.r || 0) / vatDivisor);
|
|
426
428
|
}
|
|
@@ -116,6 +116,7 @@ async function writeCoreProperties(zip) {
|
|
|
116
116
|
/**
|
|
117
117
|
* The company and the terms in an already-composed package: the workbooks the
|
|
118
118
|
* generator has no cells to edit, and the Company package's dividend voucher.
|
|
119
|
+
* A workbook also has every formula's cached value recalculated.
|
|
119
120
|
*
|
|
120
121
|
* @param {Uint8Array|Buffer} packageBuffer - an xlsx or docx
|
|
121
122
|
* @returns {Promise<Uint8Array>}
|
|
@@ -123,6 +124,7 @@ async function writeCoreProperties(zip) {
|
|
|
123
124
|
export async function applyCoreProperties(packageBuffer) {
|
|
124
125
|
const zip = await JSZip.loadAsync(packageBuffer);
|
|
125
126
|
await writeCoreProperties(zip);
|
|
127
|
+
if (zip.file("xl/workbook.xml")) await refreshZipCachedValues(zip);
|
|
126
128
|
stabilizeDirDates(zip);
|
|
127
129
|
return zip.generateAsync({ type: "uint8array", compression: "DEFLATE", compressionOptions: { level: 6 } });
|
|
128
130
|
}
|
|
@@ -1483,9 +1485,10 @@ export async function generateSpreadsheet(templateBuffer, taxData, sheetsConfig)
|
|
|
1483
1485
|
}
|
|
1484
1486
|
|
|
1485
1487
|
// An application that shows cached values instead of recalculating reads
|
|
1486
|
-
// every formula
|
|
1487
|
-
//
|
|
1488
|
-
|
|
1488
|
+
// every formula as the template left it, including those the template
|
|
1489
|
+
// itself caches stale, so every result is recalculated here and written
|
|
1490
|
+
// beside its formula.
|
|
1491
|
+
await refreshZipCachedValues(zip);
|
|
1489
1492
|
|
|
1490
1493
|
// Force full recalculation on open so cached formula values (e.g. G2=B23) update
|
|
1491
1494
|
const wbXml = await zip.file("xl/workbook.xml").async("string");
|
|
@@ -1983,7 +1986,7 @@ export async function applyYearEndSequence(xlsxBuffer, templateFile, sheetsConfi
|
|
|
1983
1986
|
}
|
|
1984
1987
|
|
|
1985
1988
|
if (buffer === xlsxBuffer || !templateFile.endsWith(".xlsx")) return buffer;
|
|
1986
|
-
return refreshCachedValues(
|
|
1989
|
+
return refreshCachedValues(null, buffer);
|
|
1987
1990
|
}
|
|
1988
1991
|
|
|
1989
1992
|
export function formatDateDDMMYY(date) {
|
|
@@ -126,6 +126,49 @@ function bankLayout(fileName) {
|
|
|
126
126
|
|
|
127
127
|
const BANK_LAYOUTS = Object.fromEntries(Object.values(BANK_ACCOUNT_FILES).map((fileName) => [fileName, bankLayout(fileName)]));
|
|
128
128
|
|
|
129
|
+
// A refund runs the opposite way to the code's own trade: a payment coded DR
|
|
130
|
+
// refunds a customer, a receipt coded CR is a refund from a supplier. All
|
|
131
|
+
// four workbooks analyse DR on receipts only and CR on payments only, so a
|
|
132
|
+
// refund lands in the opposite block under its own code with its amount
|
|
133
|
+
// negated. A negative DR receipt debits trade debtors and credits the bank,
|
|
134
|
+
// the same double entry as the refund itself.
|
|
135
|
+
const REFUND_CODES = new Set(["DR", "CR"]);
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Where a bank entry lands in its workbook's month tab.
|
|
139
|
+
* @param {string} fileName - one of BANK_ACCOUNT_FILES' workbooks
|
|
140
|
+
* @param {boolean} isReceipt - money in (debitCreditCode "D", direction "in")
|
|
141
|
+
* @param {string} code - the entry's bank code letter
|
|
142
|
+
* @param {number} amount - the entry's own amount, positive
|
|
143
|
+
* @returns {{receipt: boolean, amount: number, analysed: boolean}} the block
|
|
144
|
+
* it lands in, the signed amount it lands with, and whether that block has
|
|
145
|
+
* a column for its code
|
|
146
|
+
*/
|
|
147
|
+
export function ltdBankPlacement(fileName, isReceipt, code, amount) {
|
|
148
|
+
const layout = BANK_LAYOUTS[fileName];
|
|
149
|
+
const own = isReceipt ? layout.receiptCodes : layout.paymentCodes;
|
|
150
|
+
const opposite = isReceipt ? layout.paymentCodes : layout.receiptCodes;
|
|
151
|
+
if (!own.includes(code) && REFUND_CODES.has(code) && opposite.includes(code)) {
|
|
152
|
+
return { receipt: !isReceipt, amount: -amount, analysed: true };
|
|
153
|
+
}
|
|
154
|
+
return { receipt: isReceipt, amount, analysed: own.includes(code) };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The entry a workbook row stands for: the inverse of ltdBankPlacement. A
|
|
159
|
+
* negative DR receipt or CR payment row reads back as the refund it records.
|
|
160
|
+
* @param {boolean} inReceiptBlock - the row sits in the receipts block
|
|
161
|
+
* @param {string} code - the row's bank code letter
|
|
162
|
+
* @param {number} amount - the row's amount as the sheet holds it
|
|
163
|
+
* @returns {{debitCreditCode: string, amount: number}}
|
|
164
|
+
*/
|
|
165
|
+
export function ltdBankEntryOfRow(inReceiptBlock, code, amount) {
|
|
166
|
+
if (amount < 0 && ((inReceiptBlock && code === "DR") || (!inReceiptBlock && code === "CR"))) {
|
|
167
|
+
return { debitCreditCode: inReceiptBlock ? "C" : "D", amount: -amount };
|
|
168
|
+
}
|
|
169
|
+
return { debitCreditCode: inReceiptBlock ? "D" : "C", amount };
|
|
170
|
+
}
|
|
171
|
+
|
|
129
172
|
// OpenAccounts row 13 splits opening fixed assets across cost (G:K) and
|
|
130
173
|
// depreciation (M:Q), one column per asset class.
|
|
131
174
|
const OPENING_FIXED_ASSET_COLUMNS = {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
export const PROVENANCE_DATA = {
|
|
14
14
|
formatVersion: "diya-gl/1",
|
|
15
|
-
engineVersion: "1.2.
|
|
15
|
+
engineVersion: "1.2.46+f38ebcb4",
|
|
16
16
|
taxDataHash: "89424bd36c0d",
|
|
17
17
|
reconciledCommit: "54e8731a91279975e61b1a232fbb67fda89515f0",
|
|
18
18
|
templates: {
|
|
@@ -26,11 +26,11 @@ export const PROVENANCE_DATA = {
|
|
|
26
26
|
},
|
|
27
27
|
se: {
|
|
28
28
|
hash: "064605ae76ed",
|
|
29
|
-
scorecard: "
|
|
29
|
+
scorecard: "8015 passed, 0 warnings, 0 failed",
|
|
30
30
|
},
|
|
31
31
|
ltd: {
|
|
32
|
-
hash: "
|
|
33
|
-
scorecard: "
|
|
32
|
+
hash: "ab3dc062e15f",
|
|
33
|
+
scorecard: "101608 passed, 0 warnings, 0 failed",
|
|
34
34
|
},
|
|
35
35
|
},
|
|
36
36
|
};
|
|
@@ -364,9 +364,12 @@ function byPostingDate(a, b) {
|
|
|
364
364
|
* Receipts settle the oldest invoice first. One that names a customer with an
|
|
365
365
|
* invoice open settles that customer's own oldest invoice; the aggregate
|
|
366
366
|
* banking runs, which name no single customer, settle the oldest invoice on
|
|
367
|
-
* the book.
|
|
368
|
-
*
|
|
369
|
-
*
|
|
367
|
+
* the book. A credit note (a refund given or a bad debt written off) names
|
|
368
|
+
* its customer and never reaches the bank, so it clears that customer's
|
|
369
|
+
* oldest open invoice before any receipt is applied; an aggregate banking run
|
|
370
|
+
* cannot take the invoice a write-off was raised against. Whatever survives
|
|
371
|
+
* is the closing debtors listing, so the listing cannot drift away from the
|
|
372
|
+
* ledger the balance sheet publishes.
|
|
370
373
|
*
|
|
371
374
|
* @param {Array} lines - parsed lines.jsonl entries (any journal)
|
|
372
375
|
* @param {Array} openingDebtors - invoices brought forward, oldest first
|
|
@@ -374,18 +377,21 @@ function byPostingDate(a, b) {
|
|
|
374
377
|
*/
|
|
375
378
|
export function buildClosingDebtors(lines, openingDebtors) {
|
|
376
379
|
const invoices = openingDebtors.map((d) => ({ customer: d.customer, invoice: d.invoice, outstanding: d.amount }));
|
|
377
|
-
|
|
380
|
+
const salesLines = lines.filter((l) => l.sourceJournalID === "sales").sort(byPostingDate);
|
|
381
|
+
for (const line of salesLines.filter((l) => l.documentType !== "credit-note")) {
|
|
378
382
|
invoices.push({ customer: line.detailComment, invoice: line.documentReference, outstanding: line.amount });
|
|
379
383
|
}
|
|
380
384
|
|
|
381
385
|
const customersInvoiced = new Set(invoices.map((invoice) => invoice.customer));
|
|
386
|
+
const creditNotes = salesLines.filter((l) => l.documentType === "credit-note");
|
|
382
387
|
const receipts = lines
|
|
383
388
|
.filter((l) => l.sourceJournalID === "bank" && l["diya-gl:bankCode"] === DEBTOR_RECEIPT_CODE && l.debitCreditCode === "D")
|
|
384
389
|
.sort(byPostingDate);
|
|
385
390
|
|
|
386
|
-
for (const
|
|
387
|
-
const
|
|
388
|
-
|
|
391
|
+
for (const settlement of [...creditNotes, ...receipts]) {
|
|
392
|
+
const creditNote = settlement.sourceJournalID === "sales";
|
|
393
|
+
const payer = creditNote || customersInvoiced.has(settlement.detailComment) ? settlement.detailComment : null;
|
|
394
|
+
let unapplied = settlement.amount;
|
|
389
395
|
for (const invoice of invoices) {
|
|
390
396
|
if (unapplied <= 0) break;
|
|
391
397
|
if (invoice.outstanding <= 0) continue;
|
|
@@ -395,8 +401,10 @@ export function buildClosingDebtors(lines, openingDebtors) {
|
|
|
395
401
|
unapplied -= settled;
|
|
396
402
|
}
|
|
397
403
|
if (unapplied > 0.005) {
|
|
404
|
+
const what = creditNote ? "Credit note" : "Bank receipt";
|
|
405
|
+
const verb = creditNote ? "credits" : "banks";
|
|
398
406
|
throw new Error(
|
|
399
|
-
|
|
407
|
+
`${what} ${settlement.entryNumber} ${verb} ${settlement.amount} from ${settlement.detailComment}, ${unapplied} of it against no open invoice`,
|
|
400
408
|
);
|
|
401
409
|
}
|
|
402
410
|
}
|
|
@@ -526,23 +534,31 @@ export function tomlLocalDate(value) {
|
|
|
526
534
|
return value instanceof Date ? value.toISOString().slice(0, 10) : value;
|
|
527
535
|
}
|
|
528
536
|
|
|
529
|
-
// Bad debts written off (4005, sales code "o") and fixed-asset disposals
|
|
530
|
-
// (4006, code "fs") already carry their own sign in the sales code's own
|
|
531
|
-
// P&L formula (see LTD_SALES_CODE_MAP and the "o"/"fs" rows CONTEXT_SELF_
|
|
532
|
-
// EMPLOYED.md and CONTEXT_LIMITED_COMPANY.md describe), so negating a
|
|
533
|
-
// credit note against either account here would flip an already-correct
|
|
534
|
-
// figure a second time.
|
|
535
|
-
const SALES_ACCOUNTS_WITH_OWN_SIGN = new Set(["4005", "4006"]);
|
|
536
|
-
|
|
537
537
|
// A credit note reverses part of the sale or purchase it names. The schema
|
|
538
538
|
// fixes a line's amount at zero or more -- only documentType says which way
|
|
539
539
|
// it points -- so every total that sums a sales or purchases line's amount
|
|
540
540
|
// reads this instead of the raw field, or a refund inflates turnover (and a
|
|
541
|
-
// supplier's credit inflates spend) instead of reducing it.
|
|
541
|
+
// supplier's credit inflates spend) instead of reducing it. A bad debt
|
|
542
|
+
// written off (4005) is a credit note too, and every template books it as a
|
|
543
|
+
// negative sale: the Limited Company and Self Employed P&L rows read the
|
|
544
|
+
// sales tabs' "Bad Debts written off" column negated (Ltd TrialBalance row
|
|
545
|
+
// 81 = -Sales!T1, SE Profit & Loss row 29 = -Sales!U1), and the Basic Sole
|
|
546
|
+
// Trader's one gross sales column feeds turnover. Only a negative entry
|
|
547
|
+
// raises the expense, lowers trade debtors and lowers turnover.
|
|
542
548
|
export function signedAmount(line) {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
549
|
+
return line.documentType === "credit-note" ? -line.amount : line.amount;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// The bad debts written off expense a book's sales journal implies, net of
|
|
553
|
+
// the sheet's VAT divisor: every credit note on 4005 is a debt written off,
|
|
554
|
+
// so its stated amount is the expense. It reads the line's own amount, not
|
|
555
|
+
// signedAmount(), so a check holding the P&L's bad debts row to it is
|
|
556
|
+
// anchored to the book and fails whichever way the sign goes wrong.
|
|
557
|
+
export function writtenOffBadDebtsNet(salesLines, vatDivisor) {
|
|
558
|
+
const writtenOff = salesLines
|
|
559
|
+
.filter((line) => String(line.accountMainID) === "4005" && line.documentType === "credit-note")
|
|
560
|
+
.reduce((sum, line) => sum + line.amount, 0);
|
|
561
|
+
return Math.round(writtenOff / vatDivisor);
|
|
546
562
|
}
|
|
547
563
|
|
|
548
564
|
export function computeNetSales(salesLines) {
|
|
@@ -1407,6 +1423,7 @@ export function formatScenarioToml(metadata, grouped, expected) {
|
|
|
1407
1423
|
if (expected.total_motor_net !== undefined) parts.push(`total_motor_net = ${expected.total_motor_net}`);
|
|
1408
1424
|
if (expected.total_legal_net !== undefined) parts.push(`total_legal_net = ${expected.total_legal_net}`);
|
|
1409
1425
|
if (expected.total_premises_net !== undefined) parts.push(`total_premises_net = ${expected.total_premises_net}`);
|
|
1426
|
+
if (expected.total_bad_debts_net !== undefined) parts.push(`total_bad_debts_net = ${expected.total_bad_debts_net}`);
|
|
1410
1427
|
if (expected.vat_output_total !== undefined) parts.push(`vat_output_total = ${expected.vat_output_total}`);
|
|
1411
1428
|
if (expected.vat_input_total !== undefined) parts.push(`vat_input_total = ${expected.vat_input_total}`);
|
|
1412
1429
|
if (expected.fixed_asset_additions?.length) {
|
|
@@ -195,9 +195,10 @@ export async function applyCellWrites(xlsxBuffer, cellWrites, { refreshFormulaRe
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
// The formulas reading the written cells carry results computed from the
|
|
198
|
-
// values the cells held before
|
|
199
|
-
//
|
|
200
|
-
|
|
198
|
+
// values the cells held before, and the template caches some results its
|
|
199
|
+
// formulas do not produce; a workbook LibreOffice recalculates anyway can
|
|
200
|
+
// skip bringing them up to date.
|
|
201
|
+
if (refreshFormulaResults) await refreshZipCachedValues(zip);
|
|
201
202
|
|
|
202
203
|
return zip.generateAsync({
|
|
203
204
|
type: "uint8array",
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
LTD_SALES_CODE_MAP,
|
|
17
17
|
} from "./scenario-extractor.js";
|
|
18
18
|
import { calculateMileageAllowance } from "./tax/mileage.js";
|
|
19
|
+
import { ltdBankEntryOfRow } from "./ltd-layout.js";
|
|
19
20
|
import {
|
|
20
21
|
PAYSLIPS_ENTRY_COLUMNS,
|
|
21
22
|
PAYSLIPS_EMPLOYEE_START_DATE_OFFSET,
|
|
@@ -46,6 +47,18 @@ const VAT_RATE_CELLS = { se: "H2", ltd: "G2" };
|
|
|
46
47
|
* Build reverse code map: { code → accountMainID }.
|
|
47
48
|
* For codes that map from multiple accounts, uses the first (primary) account.
|
|
48
49
|
*/
|
|
50
|
+
// A sales or purchases row entered as a negative amount is a credit note: a
|
|
51
|
+
// refund, a supplier's credit, or a bad debt written off, which the writer
|
|
52
|
+
// puts on the sheet as a negative sale. The schema keeps a line's amount at
|
|
53
|
+
// zero or more and says which way it points through documentType.
|
|
54
|
+
function asCreditNoteWhenNegative(line) {
|
|
55
|
+
if (line.amount < 0) {
|
|
56
|
+
line.amount = -line.amount;
|
|
57
|
+
line.documentType = "credit-note";
|
|
58
|
+
}
|
|
59
|
+
return line;
|
|
60
|
+
}
|
|
61
|
+
|
|
49
62
|
export function buildReverseCodeMap(forwardMap) {
|
|
50
63
|
const reverse = {};
|
|
51
64
|
for (const [acctId, code] of Object.entries(forwardMap)) {
|
|
@@ -282,7 +295,7 @@ export async function extractBstTransactions(xlsxBuffer, extractionMap) {
|
|
|
282
295
|
|
|
283
296
|
const regionsFor = (journal) => BST_TRANSACTION_REGIONS.filter((region) => region.sourceJournalID === journal);
|
|
284
297
|
const push = (line, region, row) => {
|
|
285
|
-
lines.push(line);
|
|
298
|
+
lines.push(asCreditNoteWhenNegative(line));
|
|
286
299
|
if (extractionMap) extractionMap.recordLine(line, region, row, lines.length - 1);
|
|
287
300
|
};
|
|
288
301
|
|
|
@@ -756,7 +769,7 @@ export async function extractMultiFileTransactions(set, product, extractionMap)
|
|
|
756
769
|
}
|
|
757
770
|
const cisSuffered = numberAt(xml, `${salesCisColumn}${row}`, salesStrings);
|
|
758
771
|
if (cisSuffered) line["diya-gl:cisDeduction"] = cisSuffered;
|
|
759
|
-
lines.push(line);
|
|
772
|
+
lines.push(asCreditNoteWhenNegative(line));
|
|
760
773
|
if (extractionMap) extractionMap.recordLine(line, region, row, lines.length - 1, "Sales.xlsx");
|
|
761
774
|
}
|
|
762
775
|
}
|
|
@@ -829,7 +842,7 @@ export async function extractMultiFileTransactions(set, product, extractionMap)
|
|
|
829
842
|
if (description) line.lineItemComment = description;
|
|
830
843
|
const cisWithheld = numberAt(xml, `${purchasesCisColumn}${row}`, purchasesStrings);
|
|
831
844
|
if (cisWithheld) line["diya-gl:cisDeduction"] = cisWithheld;
|
|
832
|
-
lines.push(line);
|
|
845
|
+
lines.push(asCreditNoteWhenNegative(line));
|
|
833
846
|
if (extractionMap) extractionMap.recordLine(line, region, row, lines.length - 1, "Purchases.xlsx");
|
|
834
847
|
}
|
|
835
848
|
}
|
|
@@ -950,14 +963,15 @@ export async function extractBankTransactions(set, product, period, extractionMa
|
|
|
950
963
|
const code = readCellValue(xml, `E${row}`, sharedStrings) || "";
|
|
951
964
|
const codeStr = typeof code === "string" ? code : String(code);
|
|
952
965
|
|
|
966
|
+
const entry = product === "ltd" ? ltdBankEntryOfRow(true, codeStr, amount) : { debitCreditCode: "D", amount };
|
|
953
967
|
const line = {
|
|
954
968
|
"sourceJournalID": "bank",
|
|
955
969
|
"postingDate": excelSerialToDate(dateVal),
|
|
956
970
|
"accountMainID": accountID,
|
|
957
|
-
amount,
|
|
971
|
+
"amount": entry.amount,
|
|
958
972
|
"detailComment": typeof source === "string" ? source : "",
|
|
959
973
|
"diya-gl:bankCode": codeStr,
|
|
960
|
-
"debitCreditCode":
|
|
974
|
+
"debitCreditCode": entry.debitCreditCode,
|
|
961
975
|
"diya-gl:bankAccountID": accountID,
|
|
962
976
|
"entryNumber": nextEntryNumber("bank"),
|
|
963
977
|
};
|
|
@@ -980,14 +994,15 @@ export async function extractBankTransactions(set, product, period, extractionMa
|
|
|
980
994
|
const code = readCellValue(xml, `${payment.code}${row}`, sharedStrings) || "";
|
|
981
995
|
const codeStr = typeof code === "string" ? code : String(code);
|
|
982
996
|
|
|
997
|
+
const entry = product === "ltd" ? ltdBankEntryOfRow(false, codeStr, amount) : { debitCreditCode: "C", amount };
|
|
983
998
|
const line = {
|
|
984
999
|
"sourceJournalID": "bank",
|
|
985
1000
|
"postingDate": excelSerialToDate(dateVal),
|
|
986
1001
|
"accountMainID": accountID,
|
|
987
|
-
amount,
|
|
1002
|
+
"amount": entry.amount,
|
|
988
1003
|
"detailComment": typeof supplier === "string" ? supplier : "",
|
|
989
1004
|
"diya-gl:bankCode": codeStr,
|
|
990
|
-
"debitCreditCode":
|
|
1005
|
+
"debitCreditCode": entry.debitCreditCode,
|
|
991
1006
|
"diya-gl:bankAccountID": accountID,
|
|
992
1007
|
"entryNumber": nextEntryNumber("bank"),
|
|
993
1008
|
};
|
package/dist/app/products/ltd.js
CHANGED
|
@@ -34,7 +34,13 @@ import {
|
|
|
34
34
|
payslipsStartDate,
|
|
35
35
|
payslipsWagesPaidCell,
|
|
36
36
|
} from "../lib/payslips-layout.js";
|
|
37
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
BANK_ACCOUNT_FILES,
|
|
39
|
+
BANK_LAYOUTS,
|
|
40
|
+
OPENING_FIXED_ASSET_COLUMNS,
|
|
41
|
+
isLtdOpeningBankLine,
|
|
42
|
+
ltdBankPlacement,
|
|
43
|
+
} from "../lib/ltd-layout.js";
|
|
38
44
|
import { apportionCorporationTax, financialYearRatesFor } from "../lib/tax/corporation-tax.js";
|
|
39
45
|
import {
|
|
40
46
|
buildCategoryNetting,
|
|
@@ -1009,12 +1015,12 @@ export function cellWrites(scenario, targetStartYear, yearEndMonth) {
|
|
|
1009
1015
|
throw new Error(`Bank entry dated ${tx.date} (${tx.code} ${tx.amount}) has no direction`);
|
|
1010
1016
|
}
|
|
1011
1017
|
const layout = BANK_LAYOUTS[fileName];
|
|
1012
|
-
const
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
if (!analysedCodes.includes(tx.code)) {
|
|
1016
|
-
throw new Error(`${fileName} analyses no ${isReceipt ? "receipt" : "payment"} under code ${tx.code}`);
|
|
1018
|
+
const placement = ltdBankPlacement(fileName, tx.direction === "in", tx.code, tx.amount);
|
|
1019
|
+
if (!placement.analysed) {
|
|
1020
|
+
throw new Error(`${fileName} analyses no ${tx.direction === "in" ? "receipt" : "payment"} under code ${tx.code}`);
|
|
1017
1021
|
}
|
|
1022
|
+
const isReceipt = placement.receipt;
|
|
1023
|
+
const block = isReceipt ? layout.receipt : layout.payment;
|
|
1018
1024
|
|
|
1019
1025
|
const rowKey = `${fileName}:${tabName}`;
|
|
1020
1026
|
const rows = isReceipt ? receiptRows : paymentRows;
|
|
@@ -1025,7 +1031,7 @@ export function cellWrites(scenario, targetStartYear, yearEndMonth) {
|
|
|
1025
1031
|
if (tx.reference) sheet[`${block.reference}${row}`] = tx.reference;
|
|
1026
1032
|
if (tx.description) sheet[`${block.comment}${row}`] = tx.description;
|
|
1027
1033
|
sheet[`${block.code}${row}`] = tx.code;
|
|
1028
|
-
sheet[`${block.amount}${row}`] =
|
|
1034
|
+
sheet[`${block.amount}${row}`] = placement.amount;
|
|
1029
1035
|
}
|
|
1030
1036
|
}
|
|
1031
1037
|
}
|
|
@@ -2446,6 +2452,13 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
|
|
|
2446
2452
|
// else in this file reads that account.
|
|
2447
2453
|
check("Trial Balance: audit accuracy (EJ91)", results.TrialBalance.EJ91, 0);
|
|
2448
2454
|
|
|
2455
|
+
// The published balance sheet balances on its own: net assets against
|
|
2456
|
+
// shareholders' funds. EJ91 stays at 0 when the published sheet leaves a
|
|
2457
|
+
// trial balance row out, so the two tests are separate.
|
|
2458
|
+
if (results.PubBalSht) {
|
|
2459
|
+
check("Published balance sheet: net assets (F33) = shareholders' funds (F39)", num(results.PubBalSht.F33), num(results.PubBalSht.F39));
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2449
2462
|
// Opening balances. A balance sheet that never posts is still balanced, so
|
|
2450
2463
|
// EJ91 alone cannot tell an unposted opening from a posted one. The next
|
|
2451
2464
|
// two checks tie the sheet's own opening balance sheet and trial balance
|
|
@@ -2537,6 +2550,7 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
|
|
|
2537
2550
|
// Expense line totals (6f) — Ltd P&L keeps purchases at gross (same as SE)
|
|
2538
2551
|
if (expected.total_premises_net) check("Premises", pl.B21 || 0, expected.total_premises_net);
|
|
2539
2552
|
if (expected.total_legal_net) check("Legal & Professional", pl.B33 || 0, expected.total_legal_net);
|
|
2553
|
+
if (expected.total_bad_debts_net) check("Bad Debts written off", pl.B34 || 0, expected.total_bad_debts_net);
|
|
2540
2554
|
|
|
2541
2555
|
// Stock and debtors on the published balance sheet. Both are derived: stock
|
|
2542
2556
|
// comes from the physical count against the calculated value, debtors from
|
|
@@ -3501,16 +3515,15 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
|
|
|
3501
3515
|
|
|
3502
3516
|
// PubBalSht!E12 "Cash at bank and in hand" reproduces the sheet's own
|
|
3503
3517
|
// aggregation formula exactly (verified against the template: E12 =
|
|
3504
|
-
// IF(SUM(EJ22:EJ24)>0, SUM(EJ22:EJ24)+EJ25+EJ26
|
|
3505
|
-
// statement-book accounts (Current, Savings, Credit Card)
|
|
3506
|
-
//
|
|
3507
|
-
//
|
|
3508
|
-
//
|
|
3509
|
-
//
|
|
3510
|
-
//
|
|
3511
|
-
// the combined total, whether or not both legs were entered.
|
|
3518
|
+
// IF(SUM(EJ22:EJ24)>0, SUM(EJ22:EJ24), 0)+EJ25+EJ26). The three
|
|
3519
|
+
// statement-book accounts (Current, Savings, Credit Card) count only
|
|
3520
|
+
// when they are in credit together, and Cash and the Intra Cash & Bank
|
|
3521
|
+
// Transfers row are added on top either way. EJ26 nets out any receipt
|
|
3522
|
+
// or payment analysed under another account's transfer code, so a
|
|
3523
|
+
// transfer between the company's own bank accounts never moves the
|
|
3524
|
+
// combined total, whether or not both legs were entered.
|
|
3512
3525
|
const sumFirstThree = num(tb.EJ22) + num(tb.EJ23) + num(tb.EJ24);
|
|
3513
|
-
const expectedE12 = sumFirstThree
|
|
3526
|
+
const expectedE12 = Math.max(sumFirstThree, 0) + num(tb.EJ25) + num(tb.EJ26);
|
|
3514
3527
|
check("Published balance sheet: cash at bank = Trial Balance bank account aggregate", num(pubBalSht?.E12), expectedE12);
|
|
3515
3528
|
// E19 "Bank Overdraft" carries the same three accounts when they are
|
|
3516
3529
|
// overdrawn (E19 = IF(SUM(EJ22:EJ24)<0, -SUM(EJ22:EJ24), 0)), so an
|
package/dist/app/products/se.js
CHANGED
|
@@ -2522,6 +2522,7 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
|
|
|
2522
2522
|
);
|
|
2523
2523
|
}
|
|
2524
2524
|
if (expected.total_legal_net) check("Legal & Professional", pl.B28 || 0, expected.total_legal_net);
|
|
2525
|
+
if (expected.total_bad_debts_net) check("Bad Debts written off", pl.B29 || 0, expected.total_bad_debts_net);
|
|
2525
2526
|
|
|
2526
2527
|
// The mileage route. A mileage-log entry buys nothing: it states the miles
|
|
2527
2528
|
// and the sheet prices them. Each Purchases month tab pools its own column
|
|
@@ -682,6 +682,13 @@ The Company workbooks give PAYE, VAT, CIS and corporation tax a column each. Bot
|
|
|
682
682
|
Employed workbooks carry one HMRC Payments column between them, so a payment coded `RV`,
|
|
683
683
|
`RC` or `RT` lands in the `RP` column there.
|
|
684
684
|
|
|
685
|
+
The four Company bank workbooks analyse `DR` on receipts only and `CR` on payments only. A
|
|
686
|
+
refund to a customer (a payment, `debitCreditCode` `C`, coded `DR`) lands in the receipts
|
|
687
|
+
block under `DR` as a negative amount, which debits trade debtors and credits the bank. A
|
|
688
|
+
refund from a supplier (a receipt, `debitCreditCode` `D`, coded `CR`) lands in the payments
|
|
689
|
+
block under `CR` as a negative amount. Reading the workbook back gives the refund line again.
|
|
690
|
+
Both Self Employed workbooks have a column for each code on both sides.
|
|
691
|
+
|
|
685
692
|
**Bank transaction example:**
|
|
686
693
|
|
|
687
694
|
```json
|