@diy-accounting-uk/diya-gl 1.2.39 → 1.2.40
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/bin/export.js +1 -0
- package/dist/app/bin/report.js +6 -2
- package/dist/app/data/ltd-2026.toml +2 -1
- package/dist/app/data/ltd-2027.toml +2 -1
- package/dist/app/data/releases.json +26 -0
- package/dist/app/data/se-2026-2027.toml +2 -1
- package/dist/app/lib/diya-gl-loader.js +3 -3
- package/dist/app/lib/provenance-data.js +4 -4
- package/dist/app/lib/report-generator.js +4 -4
- package/dist/app/lib/report-serializer.js +3 -2
- package/dist/app/lib/scenario-extractor.js +2 -2
- package/dist/app/lib/tax/mileage.js +21 -7
- package/dist/app/products/bst.js +5 -2
- package/dist/app/products/se.js +15 -9
- package/package.json +1 -1
package/dist/app/bin/export.js
CHANGED
package/dist/app/bin/report.js
CHANGED
|
@@ -146,7 +146,7 @@ async function main() {
|
|
|
146
146
|
const resolvedOutputDir = resolve(outputDir);
|
|
147
147
|
mkdirSync(resolvedOutputDir, { recursive: true });
|
|
148
148
|
|
|
149
|
-
const sectionReports = generateSectionReports(results, productMod, mergedScenario, checks);
|
|
149
|
+
const sectionReports = generateSectionReports(results, productMod, mergedScenario, checks, taxData);
|
|
150
150
|
for (const [filename, content] of Object.entries(sectionReports)) {
|
|
151
151
|
writeFileSync(resolve(resolvedOutputDir, filename), content);
|
|
152
152
|
console.log(` Written: ${filename}`);
|
|
@@ -158,6 +158,7 @@ async function main() {
|
|
|
158
158
|
results,
|
|
159
159
|
productMod,
|
|
160
160
|
scenario: mergedScenario,
|
|
161
|
+
taxData,
|
|
161
162
|
checks,
|
|
162
163
|
scenarioName: book.documentInfo?.entriesComment,
|
|
163
164
|
yearEnd,
|
|
@@ -245,6 +246,7 @@ async function main() {
|
|
|
245
246
|
// publishes the same compliance verdicts the JS run does, against the
|
|
246
247
|
// sheet's own figures. Without it the report is the values alone.
|
|
247
248
|
let excelScenario;
|
|
249
|
+
let excelTaxData;
|
|
248
250
|
let excelChecks = [];
|
|
249
251
|
if (dataDir) {
|
|
250
252
|
const { book, lines } = loadDiyaGlData(resolve(dataDir), offset);
|
|
@@ -261,12 +263,13 @@ async function main() {
|
|
|
261
263
|
} else {
|
|
262
264
|
taxData = extractTaxDataFromBook(book, packageName);
|
|
263
265
|
}
|
|
266
|
+
excelTaxData = taxData;
|
|
264
267
|
if (typeof productMod.checkCompliance === "function") {
|
|
265
268
|
excelChecks = productMod.checkCompliance({ ...results }, excelScenario, taxData, calculateExpectedTax, yearEnd);
|
|
266
269
|
}
|
|
267
270
|
}
|
|
268
271
|
|
|
269
|
-
const sectionReports = generateSectionReports(results, productMod, excelScenario, excelChecks);
|
|
272
|
+
const sectionReports = generateSectionReports(results, productMod, excelScenario, excelChecks, excelTaxData);
|
|
270
273
|
for (const [filename, content] of Object.entries(sectionReports)) {
|
|
271
274
|
writeFileSync(resolve(resolvedOutputDir, filename), content);
|
|
272
275
|
console.log(` Written: ${filename}`);
|
|
@@ -278,6 +281,7 @@ async function main() {
|
|
|
278
281
|
results,
|
|
279
282
|
productMod,
|
|
280
283
|
scenario: excelScenario,
|
|
284
|
+
taxData: excelTaxData,
|
|
281
285
|
checks: excelChecks,
|
|
282
286
|
yearEnd,
|
|
283
287
|
});
|
|
@@ -44,9 +44,10 @@ fixtures_and_fittings = 0.20
|
|
|
44
44
|
computer_equipment = 0.33
|
|
45
45
|
motor_vehicles = 0.25
|
|
46
46
|
|
|
47
|
+
# Approved mileage rates: gov.uk "Travel - mileage and fuel rates and allowances", cars and vans, tax year 2026 to 2027: 55p first 10,000 miles, 25p after.
|
|
47
48
|
[mileage]
|
|
48
49
|
higher_rate_limit = 10000
|
|
49
|
-
higher_rate_pence = 0.
|
|
50
|
+
higher_rate_pence = 0.55
|
|
50
51
|
lower_rate_start = 10001
|
|
51
52
|
lower_rate_pence = 0.25
|
|
52
53
|
|
|
@@ -42,9 +42,10 @@ fixtures_and_fittings = 0.20
|
|
|
42
42
|
computer_equipment = 0.33
|
|
43
43
|
motor_vehicles = 0.25
|
|
44
44
|
|
|
45
|
+
# Approved mileage rates: gov.uk "Travel - mileage and fuel rates and allowances", cars and vans, tax year 2026 to 2027: 55p first 10,000 miles, 25p after.
|
|
45
46
|
[mileage]
|
|
46
47
|
higher_rate_limit = 10000
|
|
47
|
-
higher_rate_pence = 0.
|
|
48
|
+
higher_rate_pence = 0.55
|
|
48
49
|
lower_rate_start = 10001
|
|
49
50
|
lower_rate_pence = 0.25
|
|
50
51
|
|
|
@@ -1247,6 +1247,32 @@
|
|
|
1247
1247
|
"scorecard": "101605 passed, 0 warnings, 0 failed"
|
|
1248
1248
|
}
|
|
1249
1249
|
}
|
|
1250
|
+
},
|
|
1251
|
+
{
|
|
1252
|
+
"tag": "diya-gl-v1.2.39",
|
|
1253
|
+
"date": "2026-09-30",
|
|
1254
|
+
"formatVersion": "diya-gl/1",
|
|
1255
|
+
"engineVersion": "1.2.37+907683df0",
|
|
1256
|
+
"taxDataHash": "3221a7c2f39c",
|
|
1257
|
+
"reconciledCommit": "54e8731a91279975e61b1a232fbb67fda89515f0",
|
|
1258
|
+
"templates": {
|
|
1259
|
+
"bst": {
|
|
1260
|
+
"hash": "0d003e10e00c",
|
|
1261
|
+
"scorecard": "812 passed, 0 warnings, 0 failed"
|
|
1262
|
+
},
|
|
1263
|
+
"taxi": {
|
|
1264
|
+
"hash": "1ff69654d64d",
|
|
1265
|
+
"scorecard": "686 passed, 0 warnings, 0 failed"
|
|
1266
|
+
},
|
|
1267
|
+
"se": {
|
|
1268
|
+
"hash": "064605ae76ed",
|
|
1269
|
+
"scorecard": "7982 passed, 0 warnings, 0 failed"
|
|
1270
|
+
},
|
|
1271
|
+
"ltd": {
|
|
1272
|
+
"hash": "5bb7be9b6d97",
|
|
1273
|
+
"scorecard": "101605 passed, 0 warnings, 0 failed"
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1250
1276
|
}
|
|
1251
1277
|
]
|
|
1252
1278
|
}
|
|
@@ -45,9 +45,10 @@ fixtures_and_fittings = 0.20
|
|
|
45
45
|
computer_equipment = 0.33
|
|
46
46
|
motor_vehicles = 0.25
|
|
47
47
|
|
|
48
|
+
# Approved mileage rates: gov.uk "Travel - mileage and fuel rates and allowances", cars and vans, tax year 2026 to 2027: 55p first 10,000 miles, 25p after.
|
|
48
49
|
[mileage]
|
|
49
50
|
higher_rate_limit = 10000
|
|
50
|
-
higher_rate_pence = 0.
|
|
51
|
+
higher_rate_pence = 0.55
|
|
51
52
|
lower_rate_start = 10001
|
|
52
53
|
lower_rate_pence = 0.25
|
|
53
54
|
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
deriveStraddlingEntries,
|
|
34
34
|
signedAmount,
|
|
35
35
|
} from "./scenario-extractor.js";
|
|
36
|
-
import { totalBusinessMiles, calculateMileageAllowance,
|
|
36
|
+
import { totalBusinessMiles, calculateMileageAllowance, FIXTURE_CAR_MILEAGE_RATES } from "./tax/mileage.js";
|
|
37
37
|
import { compareLines } from "./diya-gl-canonical.js";
|
|
38
38
|
|
|
39
39
|
// The Taxi Driver masters keep their own chart of accounts (fuel at 5100,
|
|
@@ -385,7 +385,7 @@ export function diyaGlToScenario(book, lines, product) {
|
|
|
385
385
|
const code = purchaseCodeMap[l.accountMainID];
|
|
386
386
|
if (code) bstByCode[code] = (bstByCode[code] || 0) + signedAmount(l);
|
|
387
387
|
});
|
|
388
|
-
if (businessMiles) bstByCode.m = (bstByCode.m || 0) + calculateMileageAllowance(businessMiles,
|
|
388
|
+
if (businessMiles) bstByCode.m = (bstByCode.m || 0) + calculateMileageAllowance(businessMiles, FIXTURE_CAR_MILEAGE_RATES);
|
|
389
389
|
|
|
390
390
|
const stockPurchases = bstByCode.s || 0;
|
|
391
391
|
const openingStock = book.stock?.openingValue ?? 0;
|
|
@@ -417,7 +417,7 @@ export function diyaGlToScenario(book, lines, product) {
|
|
|
417
417
|
.filter((l) => !(product === "se" && l.measurableUnitOfMeasure === "miles" && typeof l.measurableQuantity === "number"))
|
|
418
418
|
.filter((l) => purchaseCodeMap[l.accountMainID] === "v")
|
|
419
419
|
.reduce((sum, l) => sum + signedAmount(l), 0);
|
|
420
|
-
const mileageClaim = product === "se" ? calculateMileageAllowance(businessMiles,
|
|
420
|
+
const mileageClaim = product === "se" ? calculateMileageAllowance(businessMiles, FIXTURE_CAR_MILEAGE_RATES) : 0;
|
|
421
421
|
expected.total_motor_net = Math.round(cashMotor / vatDivisor + mileageClaim);
|
|
422
422
|
expected.total_legal_net = Math.round((byCode.l || 0) / vatDivisor);
|
|
423
423
|
if (product === "ltd") {
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
export const PROVENANCE_DATA = {
|
|
14
14
|
formatVersion: "diya-gl/1",
|
|
15
|
-
engineVersion: "1.2.
|
|
16
|
-
taxDataHash: "
|
|
15
|
+
engineVersion: "1.2.40+57de8f55b",
|
|
16
|
+
taxDataHash: "89424bd36c0d",
|
|
17
17
|
reconciledCommit: "54e8731a91279975e61b1a232fbb67fda89515f0",
|
|
18
18
|
templates: {
|
|
19
19
|
bst: {
|
|
@@ -22,11 +22,11 @@ export const PROVENANCE_DATA = {
|
|
|
22
22
|
},
|
|
23
23
|
taxi: {
|
|
24
24
|
hash: "1ff69654d64d",
|
|
25
|
-
scorecard: "
|
|
25
|
+
scorecard: "700 passed, 0 warnings, 0 failed",
|
|
26
26
|
},
|
|
27
27
|
se: {
|
|
28
28
|
hash: "064605ae76ed",
|
|
29
|
-
scorecard: "
|
|
29
|
+
scorecard: "8014 passed, 0 warnings, 0 failed",
|
|
30
30
|
},
|
|
31
31
|
ltd: {
|
|
32
32
|
hash: "5bb7be9b6d97",
|
|
@@ -219,7 +219,7 @@ export function complianceChecksLines(checks) {
|
|
|
219
219
|
return lines;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
export function generateReport(packageName, scenarioName, results, checks, productMod, scenario) {
|
|
222
|
+
export function generateReport(packageName, scenarioName, results, checks, productMod, scenario, taxData) {
|
|
223
223
|
const hasFail = checks.some((c) => !c.pass && c.severity !== "warning");
|
|
224
224
|
const hasWarning = checks.some((c) => !c.pass && c.severity === "warning");
|
|
225
225
|
const status = hasFail ? "ANOMALYDETECTED" : hasWarning ? "RECONCILES (with warnings)" : "RECONCILES";
|
|
@@ -242,7 +242,7 @@ export function generateReport(packageName, scenarioName, results, checks, produ
|
|
|
242
242
|
// Then the netting, which explains the other difference the checks only
|
|
243
243
|
// prove correct: a journal figure and the statement figure it becomes.
|
|
244
244
|
if (typeof productMod.categoryNetting === "function") {
|
|
245
|
-
lines.push(...categoryNettingLines(productMod.categoryNetting(results, scenario)));
|
|
245
|
+
lines.push(...categoryNettingLines(productMod.categoryNetting(results, scenario, taxData)));
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
// Formatted accounting statements (if product module provides them)
|
|
@@ -299,7 +299,7 @@ export function generateReport(packageName, scenarioName, results, checks, produ
|
|
|
299
299
|
* Generate individual report files, one per reportSections() section.
|
|
300
300
|
* Returns { "filename.md": content } map.
|
|
301
301
|
*/
|
|
302
|
-
export function generateSectionReports(results, productMod, scenario, checks) {
|
|
302
|
+
export function generateSectionReports(results, productMod, scenario, checks, taxData) {
|
|
303
303
|
const reports = {};
|
|
304
304
|
|
|
305
305
|
if (typeof productMod.reportSections !== "function") return reports;
|
|
@@ -341,7 +341,7 @@ export function generateSectionReports(results, productMod, scenario, checks) {
|
|
|
341
341
|
}
|
|
342
342
|
|
|
343
343
|
if (typeof productMod.categoryNetting === "function") {
|
|
344
|
-
const nettingLines = categoryNettingLines(productMod.categoryNetting(results, scenario));
|
|
344
|
+
const nettingLines = categoryNettingLines(productMod.categoryNetting(results, scenario, taxData));
|
|
345
345
|
if (nettingLines.length > 0) {
|
|
346
346
|
reports["journal-category-vat-netting.md"] = [`# ${CATEGORY_NETTING_TITLE}`, ...nettingLines.slice(2), ""].join("\n");
|
|
347
347
|
}
|
|
@@ -323,12 +323,13 @@ function checkEntries(checks) {
|
|
|
323
323
|
* @param {Object} options.results - the cell read map, sheet -> cell -> value
|
|
324
324
|
* @param {Object} options.productMod - the product module (app/products/<name>.js)
|
|
325
325
|
* @param {Object} [options.scenario] - the merged scenario, where the run has one
|
|
326
|
+
* @param {Object} [options.taxData] - the tax year's data, where the run has it
|
|
326
327
|
* @param {Array} [options.checks] - checkCompliance() output, where the run has one
|
|
327
328
|
* @param {string} [options.scenarioName]
|
|
328
329
|
* @param {string} [options.yearEnd] - YYYY-MM-DD
|
|
329
330
|
* @returns {Object} the R document, entries sorted by key
|
|
330
331
|
*/
|
|
331
|
-
export function buildReportDocument({ packageName, engine, results, productMod, scenario, checks, scenarioName, yearEnd }) {
|
|
332
|
+
export function buildReportDocument({ packageName, engine, results, productMod, scenario, taxData, checks, scenarioName, yearEnd }) {
|
|
332
333
|
const multiFile = Boolean(productMod.MULTI_FILE);
|
|
333
334
|
const labels = typeof productMod.cellLabels === "function" ? productMod.cellLabels() : {};
|
|
334
335
|
const cellEntries = collectCellEntries(results, multiFile);
|
|
@@ -349,7 +350,7 @@ export function buildReportDocument({ packageName, engine, results, productMod,
|
|
|
349
350
|
values.push(...bridgeEntries(productMod.profitBridge(results), multiFile));
|
|
350
351
|
}
|
|
351
352
|
if (typeof productMod.categoryNetting === "function") {
|
|
352
|
-
values.push(...nettingEntries(productMod.categoryNetting(results, scenario), multiFile));
|
|
353
|
+
values.push(...nettingEntries(productMod.categoryNetting(results, scenario, taxData), multiFile));
|
|
353
354
|
}
|
|
354
355
|
values.push(...checkEntries(checks));
|
|
355
356
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// scenario-extractor.js — Pure functions for extracting test scenario data
|
|
5
5
|
// from Precision Code Ltd master data.
|
|
6
6
|
|
|
7
|
-
import { totalBusinessMiles, calculateMileageAllowance,
|
|
7
|
+
import { totalBusinessMiles, calculateMileageAllowance, FIXTURE_CAR_MILEAGE_RATES } from "./tax/mileage.js";
|
|
8
8
|
import { generateTaxYearWeeks, groupWeeksIntoMonths } from "./generator.js";
|
|
9
9
|
|
|
10
10
|
// ============================================================================
|
|
@@ -948,7 +948,7 @@ export function bstExpectedFigures(lines, stock, purchaseCodeMap = BST_PURCHASE_
|
|
|
948
948
|
const byCode = totalsByCode(cashPurchaseLines, purchaseCodeMap);
|
|
949
949
|
const businessMiles = totalBusinessMiles(purchaseLines);
|
|
950
950
|
if (businessMiles) {
|
|
951
|
-
byCode.m = Math.round(((byCode.m || 0) + calculateMileageAllowance(businessMiles,
|
|
951
|
+
byCode.m = Math.round(((byCode.m || 0) + calculateMileageAllowance(businessMiles, FIXTURE_CAR_MILEAGE_RATES)) * 100) / 100;
|
|
952
952
|
}
|
|
953
953
|
|
|
954
954
|
const stockAdjustment = stock ? stock.openingValue - stock.closingValue : 0;
|
|
@@ -4,13 +4,27 @@
|
|
|
4
4
|
// mileage.js — the HMRC approved mileage allowance, shared by the Basic
|
|
5
5
|
// Sole Trader and Taxi Driver engines.
|
|
6
6
|
|
|
7
|
-
// HMRC's approved mileage rate for a car
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
export const
|
|
7
|
+
// HMRC's approved mileage rate for a car in tax year 2025-26 and every year
|
|
8
|
+
// back to 2011-12: 45p to the first 10,000 business miles, 25p beyond. The
|
|
9
|
+
// committed scenario fixtures and example books are dated in 2025-26, so their
|
|
10
|
+
// expected motoring figures are worked out at these rates. Tax year 2026-27
|
|
11
|
+
// pays 55p (app/data/se-2026-2027.toml), so a check against a package built
|
|
12
|
+
// for another year restates the fixture's claim at that year's rates.
|
|
13
|
+
export const FIXTURE_CAR_MILEAGE_RATES = { higher_rate_limit: 10000, higher_rate_pence: 0.45, lower_rate_pence: 0.25 };
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* How much more the mileage claim is at a tax year's rates than at the
|
|
17
|
+
* fixture's. A fixture figure that has the claim in it moves by this amount
|
|
18
|
+
* when the package under test was built for another year: added for a cost,
|
|
19
|
+
* taken off for a profit.
|
|
20
|
+
*
|
|
21
|
+
* @param {number} totalMiles - the business miles the fixture's claim was worked out from
|
|
22
|
+
* @param {Object} yearRates - the package's tax year [mileage] table
|
|
23
|
+
* @returns {number} the year's claim less the fixture's
|
|
24
|
+
*/
|
|
25
|
+
export function mileageClaimChangeFromFixture(totalMiles, yearRates) {
|
|
26
|
+
return calculateMileageAllowance(totalMiles, yearRates) - calculateMileageAllowance(totalMiles, FIXTURE_CAR_MILEAGE_RATES);
|
|
27
|
+
}
|
|
14
28
|
|
|
15
29
|
/**
|
|
16
30
|
* The allowance a year's business miles claim: the first band of miles at
|
package/dist/app/products/bst.js
CHANGED
|
@@ -11,7 +11,7 @@ import { parseDate, MONTH_SHEETS, fixedAssetAdditions } from "../lib/scenario-lo
|
|
|
11
11
|
import { shiftMonths, periodShiftMonths } from "../lib/period-shift.js";
|
|
12
12
|
import { MONTH_ORDER } from "../lib/scenario-extractor.js";
|
|
13
13
|
import { buildProfitBridge, PROFIT_BRIDGE_CHECK } from "../lib/report-generator.js";
|
|
14
|
-
import { calculateMileageAllowance } from "../lib/tax/mileage.js";
|
|
14
|
+
import { calculateMileageAllowance, mileageClaimChangeFromFixture } from "../lib/tax/mileage.js";
|
|
15
15
|
import { canonicalForUnit } from "../lib/canonical-report-value.js";
|
|
16
16
|
|
|
17
17
|
export const PRODUCT = {
|
|
@@ -450,7 +450,10 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
|
|
|
450
450
|
const pl = results["Profit & Loss Acc"];
|
|
451
451
|
if (expected.total_sales !== undefined) check("Total Sales", pl.C4, expected.total_sales);
|
|
452
452
|
if (expected.gross_profit !== undefined) check("Gross Profit", pl.C9, expected.gross_profit);
|
|
453
|
-
if (expected.net_profit !== undefined)
|
|
453
|
+
if (expected.net_profit !== undefined) {
|
|
454
|
+
const mileageChange = taxData && expected.total_mileage ? mileageClaimChangeFromFixture(expected.total_mileage, taxData.mileage) : 0;
|
|
455
|
+
check("Net Profit", pl.C24, expected.net_profit - mileageChange);
|
|
456
|
+
}
|
|
454
457
|
if (expected.total_premises !== undefined) check("Premises Costs", pl.C12, expected.total_premises);
|
|
455
458
|
if (expected.total_gen_admin !== undefined) check("Gen Admin", pl.C14, expected.total_gen_admin);
|
|
456
459
|
if (expected.total_legal !== undefined) check("Legal & Professional", pl.C18, expected.total_legal);
|
package/dist/app/products/se.js
CHANGED
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
vatCycleRows,
|
|
40
40
|
vatReturnCoverage,
|
|
41
41
|
} from "../lib/report-generator.js";
|
|
42
|
-
import { calculateMileageAllowance,
|
|
42
|
+
import { calculateMileageAllowance, mileageClaimChangeFromFixture, FIXTURE_CAR_MILEAGE_RATES } from "../lib/tax/mileage.js";
|
|
43
43
|
import { checkForecastTaxAndNi } from "../lib/tax/income-tax.js";
|
|
44
44
|
import { canonicalForUnit } from "../lib/canonical-report-value.js";
|
|
45
45
|
|
|
@@ -2259,7 +2259,7 @@ function mileageClaimsByMonth(scenario, mileageRates) {
|
|
|
2259
2259
|
|
|
2260
2260
|
// One row per journal category that crosses into another statement, so the
|
|
2261
2261
|
// gross-to-net step is stated where it happens rather than only in total.
|
|
2262
|
-
export function categoryNetting(results, scenario) {
|
|
2262
|
+
export function categoryNetting(results, scenario, taxData) {
|
|
2263
2263
|
// With no journal there is nothing to net: every row would compare a nil
|
|
2264
2264
|
// against whatever the sheet holds and read as a category that lost its
|
|
2265
2265
|
// whole value on the way.
|
|
@@ -2277,7 +2277,7 @@ export function categoryNetting(results, scenario) {
|
|
|
2277
2277
|
// Motor Expenses because there was no VAT on it to strip.
|
|
2278
2278
|
const businessMiles = journalMiles(scenario.sales) + journalMiles(scenario.purchases);
|
|
2279
2279
|
if (businessMiles) {
|
|
2280
|
-
const claim = calculateMileageAllowance(businessMiles,
|
|
2280
|
+
const claim = calculateMileageAllowance(businessMiles, taxData?.mileage || FIXTURE_CAR_MILEAGE_RATES);
|
|
2281
2281
|
purchases.gross.v = (purchases.gross.v || 0) + claim;
|
|
2282
2282
|
purchases.net.v = (purchases.net.v || 0) + claim;
|
|
2283
2283
|
}
|
|
@@ -2387,10 +2387,9 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
|
|
|
2387
2387
|
const blank = (v) => String(v ?? "").trim();
|
|
2388
2388
|
|
|
2389
2389
|
// The approved rates the generator injected into the Admin sheet, which is
|
|
2390
|
-
// what the Purchases sheets band their running mileage total by.
|
|
2391
|
-
//
|
|
2392
|
-
|
|
2393
|
-
const mileageRates = taxData?.mileage || HMRC_CAR_MILEAGE_RATES;
|
|
2390
|
+
// what the Purchases sheets band their running mileage total by. A book
|
|
2391
|
+
// checked without a tax year's data is priced at the fixture year's rates.
|
|
2392
|
+
const mileageRates = taxData?.mileage || FIXTURE_CAR_MILEAGE_RATES;
|
|
2394
2393
|
const monthlyMileageClaims = mileageClaimsByMonth(expected, mileageRates);
|
|
2395
2394
|
|
|
2396
2395
|
// The rate cell itself, month by month on both journals. A non-registered
|
|
@@ -2452,7 +2451,14 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
|
|
|
2452
2451
|
}
|
|
2453
2452
|
|
|
2454
2453
|
// Expense line totals (6f)
|
|
2455
|
-
if (expected.total_motor_net)
|
|
2454
|
+
if (expected.total_motor_net) {
|
|
2455
|
+
const fixtureMotorNet = expected.total_motor_net;
|
|
2456
|
+
check(
|
|
2457
|
+
"Motor Expenses",
|
|
2458
|
+
pl.B25 || 0,
|
|
2459
|
+
fixtureMotorNet + (expected.total_mileage ? mileageClaimChangeFromFixture(expected.total_mileage, mileageRates) : 0),
|
|
2460
|
+
);
|
|
2461
|
+
}
|
|
2456
2462
|
if (expected.total_legal_net) check("Legal & Professional", pl.B28 || 0, expected.total_legal_net);
|
|
2457
2463
|
|
|
2458
2464
|
// The mileage route. A mileage-log entry buys nothing: it states the miles
|
|
@@ -4264,7 +4270,7 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
|
|
|
4264
4270
|
// monthly ties above prove each month landed in the right column; these
|
|
4265
4271
|
// prove the year's gross figure reaches the statement with the VAT taken
|
|
4266
4272
|
// off and nothing else lost on the way.
|
|
4267
|
-
const netting = categoryNetting(results, expected);
|
|
4273
|
+
const netting = categoryNetting(results, expected, taxData);
|
|
4268
4274
|
for (const row of netting?.rows || []) check(categoryNettingCheckName(row), row.residue, 0, 0.01);
|
|
4269
4275
|
|
|
4270
4276
|
// ── The customer-facing invoice against the tax year's own VAT rate ──────
|