@diy-accounting-uk/diya-gl 1.2.12 → 1.2.13

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.
@@ -545,6 +545,32 @@
545
545
  "scorecard": "101605 passed, 0 warnings, 0 failed"
546
546
  }
547
547
  }
548
+ },
549
+ {
550
+ "tag": "diya-gl-v1.2.12",
551
+ "date": "2026-09-14",
552
+ "formatVersion": "diya-gl/1",
553
+ "engineVersion": "1.2.12+8ad4ddf1",
554
+ "taxDataHash": "4ecdbe985d22",
555
+ "reconciledCommit": "c910fdeefbe4f2f71ae9ec5f5d4a713783a1c0c7",
556
+ "templates": {
557
+ "bst": {
558
+ "hash": "0d003e10e00c",
559
+ "scorecard": "812 passed, 0 warnings, 0 failed"
560
+ },
561
+ "taxi": {
562
+ "hash": "1ff69654d64d",
563
+ "scorecard": "686 passed, 0 warnings, 0 failed"
564
+ },
565
+ "se": {
566
+ "hash": "4decdcb12664",
567
+ "scorecard": "7952 passed, 0 warnings, 0 failed"
568
+ },
569
+ "ltd": {
570
+ "hash": "caa14632083b",
571
+ "scorecard": "101605 passed, 0 warnings, 0 failed"
572
+ }
573
+ }
548
574
  }
549
575
  ]
550
576
  }
@@ -461,5 +461,6 @@
461
461
  "section/journal-category-vat-netting/business-entertainment-memo-purchases-e/net": "The journal-category-vat-netting report is a reconciliation table (categoryNettingLines, products/se.js) explaining how a gross journal figure nets into a statement cell; no page view is this table.",
462
462
  "section/journal-category-vat-netting/business-entertainment-memo-purchases-e/residue": "The journal-category-vat-netting report is a reconciliation table (categoryNettingLines, products/se.js) explaining how a gross journal figure nets into a statement cell; no page view is this table.",
463
463
  "section/journal-category-vat-netting/business-entertainment-memo-purchases-e/vat": "The journal-category-vat-netting report is a reconciliation table (categoryNettingLines, products/se.js) explaining how a gross journal figure nets into a statement cell; no page view is this table.",
464
- "section/accounting-profit-to-tax-profit-bridge/add-disallowable-expenses-added-back-box-46": "The page has no profit-bridge view; the bridge from accounting profit to tax profit is not printed anywhere on the books page."
464
+ "section/accounting-profit-to-tax-profit-bridge/add-disallowable-expenses-added-back-box-46": "The page has no profit-bridge view; the bridge from accounting profit to tax profit is not printed anywhere on the books page.",
465
+ "section/accounting-profit-to-tax-profit-bridge/add-the-full-return-s-own-box-71-adjustment-for-change-of-accounting-practice": "The page has no profit-bridge view; the bridge from accounting profit to tax profit is not printed anywhere on the books page."
465
466
  }
@@ -605,14 +605,6 @@ export function buildSelfEmploymentAnnualSubmission(book, lines, taxData, option
605
605
  "SA103F box 54 has no formula behind it and the book states no tax.selfEmployment.allowances.electricChargePointAllowance; the box stays blank.",
606
606
  });
607
607
  }
608
- if (cellNumber("D210") !== 0) {
609
- warnings.push({
610
- field: primaryField(boxes, "71"),
611
- reason:
612
- "SE Full carries box 71 into box 77 (D219 = O179+E197+D210+P190) and not into box 73 (O194 = O174); HMRC computes the adjusted profit from the filed field, so the sheet's printed box 73 and its Income Tax sheet leave it out.",
613
- handComputed: round2(seFull.O174 + cellNumber("D210")),
614
- });
615
- }
616
608
  for (const { box: boxNumber, pick } of NO_SOURCE_ANNUAL_BOXES) {
617
609
  const entry = boxEntry(boxes, boxNumber);
618
610
  const field = fieldsOf(entry)[pick];
@@ -1175,15 +1175,18 @@ export function calculateSeCells(book, lines, taxData, scenario = {}) {
1175
1175
  const fromNetLoss = -seFull.O129 + seFull.D174 - seFull.O169;
1176
1176
  return fromNetLoss < 0 ? -fromNetLoss : 0;
1177
1177
  });
1178
- seFull.O194 = seFull.O174;
1178
+ // HMRC's SA103F working sheet for boxes 73 and 77 takes one figure, box
1179
+ // 64 less box 65 plus boxes 68, 71 and 72: box 73 is that figure when it
1180
+ // is positive and box 77 is its negation when it is not. The sheet reads
1181
+ // O174-O179+N(D197)+D210+N(O190); boxes 68 and 72 have no input cell on
1182
+ // this template, so their printed dash cells (D197, O190) contribute nil
1183
+ // and box 71 (D210) is the only adjustment that moves either figure.
1184
+ const adjustedProfitBeforeFloor = carry([seFull.O174, seFull.O179], () => seFull.O174 - seFull.O179 + sheetNumber(seFull.D210));
1185
+ seFull.O194 = carry([adjustedProfitBeforeFloor], () => Math.max(0, adjustedProfitBeforeFloor));
1179
1186
  seFull.O204 = pl.B11;
1180
1187
  seFull.O199 = carry([seFull.O194], () => (sheetNumber(seFull.D179) > 0 ? 0 : Math.min(seFull.O194 + seFull.O204, lossesBroughtForward)));
1181
1188
  seFull.O210 = carry([seFull.O194, seFull.O199], () => seFull.O194 - seFull.O199 + seFull.O204);
1182
- // Box 77 is O179-N(D197)-D210-N(O190): box 65 reduced by boxes 68, 71 and
1183
- // 72. Boxes 68 and 72 have no input cell on this template, so their
1184
- // printed dash cells (D197, O190) always contribute nil; box 71 (D210)
1185
- // is the only one that moves the figure.
1186
- seFull.D219 = carry([seFull.O179], () => seFull.O179 - sheetNumber(seFull.D210));
1189
+ seFull.D219 = carry([adjustedProfitBeforeFloor], () => Math.max(0, -adjustedProfitBeforeFloor));
1187
1190
  seFull.O224 = seFull.D219;
1188
1191
  seFull.D231 = contractorDeductions;
1189
1192
  seFull.J280 = admin.N20;
@@ -12,7 +12,7 @@
12
12
 
13
13
  export const PROVENANCE_DATA = {
14
14
  formatVersion: "diya-gl/1",
15
- engineVersion: "1.2.12+8ad4ddf1",
15
+ engineVersion: "1.2.13+f5b44cdf",
16
16
  taxDataHash: "4ecdbe985d22",
17
17
  reconciledCommit: "c910fdeefbe4f2f71ae9ec5f5d4a713783a1c0c7",
18
18
  templates: {
@@ -25,8 +25,8 @@ export const PROVENANCE_DATA = {
25
25
  scorecard: "686 passed, 0 warnings, 0 failed",
26
26
  },
27
27
  se: {
28
- hash: "4decdcb12664",
29
- scorecard: "7952 passed, 0 warnings, 0 failed",
28
+ hash: "15d3b753f7a4",
29
+ scorecard: "7956 passed, 0 warnings, 0 failed",
30
30
  },
31
31
  ltd: {
32
32
  hash: "caa14632083b",
@@ -230,10 +230,12 @@ export const DISALLOWABLE_PERCENT_CELLS = {
230
230
  // The SA103F boxes the trader states by hand, keyed by HMRC's own API field
231
231
  // names. Verified against the template: SE Full!O154 (box 57) reads
232
232
  // D139+D144+D147+D152+D156+D160+O139+O144+O149, so every allowance cell here
233
- // is a term of the total; O169 (box 63) reads O154+D179; D219 (box 77) reads
234
- // O179-N(D197)-D210-N(O190). Box 54's caption at N136 is "Electric
235
- // charge-point allowance" (L136 = 54); box 55's cell O144 is the small pools
236
- // formula, so the enhanced-allowance figure has no input cell of its own.
233
+ // is a term of the total; O169 (box 63) reads O154+D179; O194 (box 73) and
234
+ // D219 (box 77) each floor the working sheet's one figure,
235
+ // O174-O179+N(D197)+D210+N(O190), at nil from its own side. Box 54's caption
236
+ // at N136 is "Electric charge-point allowance" (L136 = 54); box 55's cell
237
+ // O144 is the small pools formula, so the enhanced-allowance figure has no
238
+ // input cell of its own.
237
239
  export const ANNUAL_ALLOWANCE_CELLS = {
238
240
  zeroEmissionsGoodsVehicleAllowance: "D152",
239
241
  zeroEmissionsCarAllowance: "D156",
@@ -1842,6 +1844,11 @@ export function profitBridge(results) {
1842
1844
  { label: "Less the full return's own Structures and Buildings Allowance (box 53)", cell: "SE Full!D160", value: -num(seFull?.D160) },
1843
1845
  { label: "Less the full return's own electric charge-point allowance (box 54)", cell: "SE Full!O139", value: -num(seFull?.O139) },
1844
1846
  { label: "Less the full return's own box 62 adjustment", cell: "SE Full!D179", value: -num(seFull?.D179) },
1847
+ {
1848
+ label: "Add the full return's own box 71 adjustment for change of accounting practice",
1849
+ cell: "SE Full!D210",
1850
+ value: num(seFull?.D210),
1851
+ },
1845
1852
  {
1846
1853
  label: "Add back the year's loss, carried forward rather than reducing tax below nil",
1847
1854
  cell: "SE Full!O179",
@@ -2278,13 +2285,14 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
2278
2285
  );
2279
2286
  // Income Tax!E5 reads 'SE Full'!O210, so the short return's box 31
2280
2287
  // reaches it only through the full return: the two agree exactly when
2281
- // no box the trader states on SE Full alone (52, 52.1, 53, 54, 62)
2282
- // carries a figure, and stand apart by those figures otherwise.
2288
+ // no box the trader states on SE Full alone (52, 52.1, 53, 54, 62 and
2289
+ // 71) carries a figure, and stand apart by those figures otherwise.
2283
2290
  const fullOnly = results["SE Full"] || {};
2284
- const fullReturnOnlyBoxes = num(fullOnly.D152) + num(fullOnly.D156) + num(fullOnly.D160) + num(fullOnly.O139) + num(fullOnly.D179);
2291
+ const fullReturnOnlyBoxes =
2292
+ num(fullOnly.D152) + num(fullOnly.D156) + num(fullOnly.D160) + num(fullOnly.O139) + num(fullOnly.D179) - num(fullOnly.D210);
2285
2293
  if (seShort.D106) {
2286
2294
  check(
2287
- "SA103S: Profit for tax (D106) less the SE Full-only boxes 52, 52.1, 53, 54 and 62 = Income Tax E5",
2295
+ "SA103S: Profit for tax (D106) less the SE Full-only boxes 52, 52.1, 53, 54 and 62 plus box 71 = Income Tax E5",
2288
2296
  Math.max(0, num(seShort.D106) - fullReturnOnlyBoxes),
2289
2297
  tax.E5,
2290
2298
  );
@@ -2507,7 +2515,21 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
2507
2515
  num(seFull.O174),
2508
2516
  taxProfitFromNetProfit > 0 ? taxProfitFromNetProfit : Math.max(0, taxProfitFromNetLoss),
2509
2517
  );
2510
- check("SA103F box 73 adjusted profit (O194) = box 64", num(seFull.O194), num(seFull.O174));
2518
+ // HMRC's working sheet for boxes 73 and 77 takes one figure, box 64 less
2519
+ // box 65 plus boxes 68, 71 and 72; box 73 is it when positive, box 77 its
2520
+ // negation when not. Boxes 68 (D197) and 72 (O190) print a dash on this
2521
+ // template and contribute nil, so box 71 is the only adjustment here.
2522
+ const adjustedProfitBeforeFloor = num(seFull.O174) - num(seFull.O179) + num(seFull.D210);
2523
+ check(
2524
+ "SA103F box 73 adjusted profit (O194) = box 64 less box 65 plus box 71, floored at nil",
2525
+ num(seFull.O194),
2526
+ Math.max(0, adjustedProfitBeforeFloor),
2527
+ );
2528
+ check(
2529
+ "SA103F box 77 adjusted loss (D219) = box 65 less box 64 and box 71, floored at nil",
2530
+ num(seFull.D219),
2531
+ Math.max(0, -adjustedProfitBeforeFloor),
2532
+ );
2511
2533
  check(
2512
2534
  "SA103F box 76 total taxable profits (O210) = box 73 less box 74 plus box 75",
2513
2535
  num(seFull.O210),
@@ -2604,22 +2626,19 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
2604
2626
  num(seFull.D174) - num(seFull.O122) - num(seFull.O160),
2605
2627
  ownUseStated,
2606
2628
  );
2607
- // Box 77's formula is O179-N(D197)-D210-N(O190): box 65 reduced by box
2608
- // 71, with the box 68 and 72 terms reading each printed dash (always
2609
- // nil -- neither box has an input cell on this template).
2629
+ // The same two boxes anchored on the figure the book states, so a sheet
2630
+ // whose box 71 cell and adjusted figures agree with each other but not
2631
+ // with the book fails here.
2632
+ const statedAccountingAdjustment = statedAdjustments.accountingAdjustment || 0;
2610
2633
  check(
2611
- "SA103F box 77 adjusted loss (D219) = box 65 less the box 71 figure the book states",
2612
- num(seFull.D219),
2613
- num(seFull.O179) - (statedAdjustments.accountingAdjustment || 0),
2634
+ "SA103F box 73 adjusted profit (O194) = box 64 less box 65 plus the box 71 figure the book states, floored at nil",
2635
+ num(seFull.O194),
2636
+ Math.max(0, num(seFull.O174) - num(seFull.O179) + statedAccountingAdjustment),
2614
2637
  );
2615
- // Box 73 reads box 64 alone (O194 = O174). HMRC's working sheet adds box
2616
- // 71 to the adjusted profit; the sheet carries box 71 into box 77 only.
2617
2638
  check(
2618
- "SA103F box 73 adjusted profit (O194) leaves out box 71 (D210), which HMRC's working sheet adds to box 64",
2619
- num(seFull.O194),
2620
- num(seFull.O174) + num(seFull.D210),
2621
- 0.01,
2622
- "warning",
2639
+ "SA103F box 77 adjusted loss (D219) = box 65 less box 64 and the box 71 figure the book states, floored at nil",
2640
+ num(seFull.D219),
2641
+ Math.max(0, num(seFull.O179) - num(seFull.O174) - statedAccountingAdjustment),
2623
2642
  );
2624
2643
 
2625
2644
  if (sa103s) {
@@ -2647,10 +2666,12 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
2647
2666
  num(seFull.O174),
2648
2667
  Math.max(0, num(sa103s.D99) - fullReturnOnlyDeductions),
2649
2668
  );
2669
+ // Box 71 is the one SE Full-only figure that adds to the taxable
2670
+ // profit; the short return has no box for it either.
2650
2671
  check(
2651
- "SA103F box 76 total taxable profits: full return (O210) = short return (D106) less the SE Full-only boxes 52, 52.1, 53, 54 and 62, with each return's own loss set-off",
2672
+ "SA103F box 76 total taxable profits: full return (O210) = short return (D106) less the SE Full-only boxes 52, 52.1, 53, 54 and 62 plus box 71, with each return's own loss set-off",
2652
2673
  num(seFull.O210),
2653
- Math.max(0, num(sa103s.D106) - fullReturnOnlyDeductions + num(sa103s.O94) - num(seFull.O199)),
2674
+ Math.max(0, num(sa103s.D106) - fullReturnOnlyDeductions + num(seFull.D210) + num(sa103s.O94) - num(seFull.O199)),
2654
2675
  );
2655
2676
  check(
2656
2677
  "SA103S box 28 net business profit for tax purposes (D99) leaves out the allowances and box 62 adjustment the trader states on SE Full alone",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diy-accounting-uk/diya-gl",
3
- "version": "1.2.12",
3
+ "version": "1.2.13",
4
4
  "type": "module",
5
5
  "description": "Recalculate, read and write DIYA-GL books from the command line",
6
6
  "license": "Apache-2.0",