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

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.
@@ -320,6 +320,7 @@ part = 3
320
320
  partName = "Section 1 Part 3: Accounting Disallowable Expenditure Addback"
321
321
  label = "Entertaining"
322
322
  xbrl = "AdjustmentsEntertaining"
323
+ sheetCell = "Financialaccounts.xlsx!CorporationTax!I9"
323
324
 
324
325
  [[line]]
325
326
  section = 1
@@ -183,6 +183,9 @@
183
183
  {
184
184
  "line": "1.3/Trading debits in respect of intangible fixed assets"
185
185
  },
186
+ {
187
+ "line": "1.3/Entertaining"
188
+ },
186
189
  {
187
190
  "line": "1.3/Donations",
188
191
  "outsideTotal": true,
@@ -444,7 +444,7 @@
444
444
  "form": "SA103F",
445
445
  "box": "51",
446
446
  "label": "Capital allowances at 6% on equipment, including cars with higher CO2 emissions",
447
- "cell": null,
447
+ "cell": "SE Full!D147",
448
448
  "field": "allowances.capitalAllowanceSpecialRatePool | allowances.capitalAllowanceSingleAssetPool",
449
449
  "route": "annual"
450
450
  },
@@ -452,7 +452,7 @@
452
452
  "form": "SA103F",
453
453
  "box": "52",
454
454
  "label": "Zero-emission goods vehicle allowance",
455
- "cell": null,
455
+ "cell": "SE Full!D152",
456
456
  "field": "allowances.zeroEmissionsGoodsVehicleAllowance",
457
457
  "route": "annual"
458
458
  },
@@ -460,7 +460,7 @@
460
460
  "form": "SA103F",
461
461
  "box": "52.1",
462
462
  "label": "Zero-emission car allowance",
463
- "cell": null,
463
+ "cell": "SE Full!D156",
464
464
  "field": "allowances.zeroEmissionsCarAllowance",
465
465
  "route": "annual"
466
466
  },
@@ -468,7 +468,7 @@
468
468
  "form": "SA103F",
469
469
  "box": "53",
470
470
  "label": "The Structures and Buildings Allowance",
471
- "cell": null,
471
+ "cell": "SE Full!D160",
472
472
  "field": "allowances.structuredBuildingAllowance",
473
473
  "route": "annual"
474
474
  },
@@ -484,7 +484,7 @@
484
484
  "form": "SA103F",
485
485
  "box": "54",
486
486
  "label": "Electric charge-point allowance",
487
- "cell": null,
487
+ "cell": "SE Full!O139",
488
488
  "field": "allowances.enhancedCapitalAllowance | allowances.electricChargePointAllowance",
489
489
  "route": "annual"
490
490
  },
@@ -545,7 +545,7 @@
545
545
  "form": "SA103F",
546
546
  "box": "62",
547
547
  "label": "Income, receipts and other profits included in business income or expenses but not taxable as business profits",
548
- "cell": null,
548
+ "cell": "SE Full!D179",
549
549
  "field": "adjustments.includedNonTaxableProfits",
550
550
  "route": "annual"
551
551
  },
@@ -598,7 +598,7 @@
598
598
  "form": "SA103F",
599
599
  "box": "71",
600
600
  "label": "Adjustment for change of accounting practice",
601
- "cell": null,
601
+ "cell": "SE Full!D210",
602
602
  "field": "adjustments.accountingAdjustment",
603
603
  "route": "annual"
604
604
  },
@@ -493,6 +493,58 @@
493
493
  "scorecard": "101565 passed, 0 warnings, 0 failed"
494
494
  }
495
495
  }
496
+ },
497
+ {
498
+ "tag": "diya-gl-v1.2.10",
499
+ "date": "2026-09-13",
500
+ "formatVersion": "diya-gl/1",
501
+ "engineVersion": "1.2.10+2c047a2a",
502
+ "taxDataHash": "84585062cad8",
503
+ "reconciledCommit": "1839cf9537c130410e2a180626e7729aca82ffc9",
504
+ "templates": {
505
+ "bst": {
506
+ "hash": "0d003e10e00c",
507
+ "scorecard": "812 passed, 0 warnings, 0 failed"
508
+ },
509
+ "taxi": {
510
+ "hash": "1ff69654d64d",
511
+ "scorecard": "686 passed, 0 warnings, 0 failed"
512
+ },
513
+ "se": {
514
+ "hash": "185e5ad6b61d",
515
+ "scorecard": "7692 passed, 0 warnings, 0 failed"
516
+ },
517
+ "ltd": {
518
+ "hash": "81ffdc956e5a",
519
+ "scorecard": "101565 passed, 0 warnings, 0 failed"
520
+ }
521
+ }
522
+ },
523
+ {
524
+ "tag": "diya-gl-v1.2.11",
525
+ "date": "2026-09-13",
526
+ "formatVersion": "diya-gl/1",
527
+ "engineVersion": "1.2.11+32b8b8c8",
528
+ "taxDataHash": "4ecdbe985d22",
529
+ "reconciledCommit": "c910fdeefbe4f2f71ae9ec5f5d4a713783a1c0c7",
530
+ "templates": {
531
+ "bst": {
532
+ "hash": "0d003e10e00c",
533
+ "scorecard": "812 passed, 0 warnings, 0 failed"
534
+ },
535
+ "taxi": {
536
+ "hash": "1ff69654d64d",
537
+ "scorecard": "686 passed, 0 warnings, 0 failed"
538
+ },
539
+ "se": {
540
+ "hash": "2d5629384e82",
541
+ "scorecard": "7952 passed, 0 warnings, 0 failed"
542
+ },
543
+ "ltd": {
544
+ "hash": "caa14632083b",
545
+ "scorecard": "101605 passed, 0 warnings, 0 failed"
546
+ }
547
+ }
496
548
  }
497
549
  ]
498
550
  }
@@ -20,5 +20,5 @@
20
20
  "cell/SE Short!D94": "The SA103S view's box table (sa103s, books/products/bst.js) does not print this box.",
21
21
  "section/self-assessment-sa103s/other-tax-adjustments": "The SA103S view's box table (sa103s, books/products/bst.js) does not print this box.",
22
22
  "cell/SE Short!D106": "The SA103S view's box table (sa103s, books/products/bst.js) does not print this box.",
23
- "section/self-assessment-sa103s/net-profit-for-tax-calc": "The SA103S view's box table (sa103s, books/products/bst.js) does not print this box."
23
+ "section/self-assessment-sa103s/net-profit-for-tax-calc-box-31": "The SA103S view's box table (sa103s, books/products/bst.js) does not print this box."
24
24
  }
@@ -684,6 +684,7 @@
684
684
  "cell/expensesform.xlsx!Month 10!C30": "Expense form monthly entries; the admin view shows rates only.",
685
685
  "cell/expensesform.xlsx!Month 11!C30": "Expense form monthly entries; the admin view shows rates only.",
686
686
  "cell/expensesform.xlsx!Month 12!C30": "Expense form monthly entries; the admin view shows rates only.",
687
+ "section/accounting-profit-to-tax-profit-bridge/add-back-business-entertainment": "The page has no profit-bridge view; the bridge from accounting profit to tax profit is not printed anywhere on the books page.",
687
688
  "section/accounting-profit-to-tax-profit-bridge/add-back-depreciation-charged-in-the-year": "The page has no profit-bridge view; the bridge from accounting profit to tax profit is not printed anywhere on the books page.",
688
689
  "section/accounting-profit-to-tax-profit-bridge/add-back-goodwill-written-off": "The page has no profit-bridge view; the bridge from accounting profit to tax profit is not printed anywhere on the books page.",
689
690
  "section/accounting-profit-to-tax-profit-bridge/add-gross-bank-interest-received": "The page has no profit-bridge view; the bridge from accounting profit to tax profit is not printed anywhere on the books page.",
@@ -695,11 +696,11 @@
695
696
  "section/accounting-profit-to-tax-profit-bridge/tax-profit-the-bridge-computes": "The page has no profit-bridge view; the bridge from accounting profit to tax profit is not printed anywhere on the books page.",
696
697
  "section/accounting-profit-to-tax-profit-bridge/tax-profit-the-sheet-carries": "The page has no profit-bridge view; the bridge from accounting profit to tax profit is not printed anywhere on the books page.",
697
698
  "section/ct600-as-filed/box-395-tax": "Box 395 tax is the CT600 straddle-period figure (an accounting period spanning two financial years); like its sibling straddle boxes, the CT600 form carries no box for it.",
698
- "section/journal-category-vat-netting/advertising-promotion-purchases-a/downstream": "The journal-category-vat-netting report is a reconciliation table (categoryNettingLines, products/ltd.js) explaining how a gross journal figure nets into a statement cell; no page view is this table.",
699
- "section/journal-category-vat-netting/advertising-promotion-purchases-a/gross": "The journal-category-vat-netting report is a reconciliation table (categoryNettingLines, products/ltd.js) explaining how a gross journal figure nets into a statement cell; no page view is this table.",
700
- "section/journal-category-vat-netting/advertising-promotion-purchases-a/net": "The journal-category-vat-netting report is a reconciliation table (categoryNettingLines, products/ltd.js) explaining how a gross journal figure nets into a statement cell; no page view is this table.",
701
- "section/journal-category-vat-netting/advertising-promotion-purchases-a/residue": "The journal-category-vat-netting report is a reconciliation table (categoryNettingLines, products/ltd.js) explaining how a gross journal figure nets into a statement cell; no page view is this table.",
702
- "section/journal-category-vat-netting/advertising-promotion-purchases-a/vat": "The journal-category-vat-netting report is a reconciliation table (categoryNettingLines, products/ltd.js) explaining how a gross journal figure nets into a statement cell; no page view is this table.",
699
+ "section/journal-category-vat-netting/advertising-promotion-entertainment-purchases-a/downstream": "The journal-category-vat-netting report is a reconciliation table (categoryNettingLines, products/ltd.js) explaining how a gross journal figure nets into a statement cell; no page view is this table.",
700
+ "section/journal-category-vat-netting/advertising-promotion-entertainment-purchases-a/gross": "The journal-category-vat-netting report is a reconciliation table (categoryNettingLines, products/ltd.js) explaining how a gross journal figure nets into a statement cell; no page view is this table.",
701
+ "section/journal-category-vat-netting/advertising-promotion-entertainment-purchases-a/net": "The journal-category-vat-netting report is a reconciliation table (categoryNettingLines, products/ltd.js) explaining how a gross journal figure nets into a statement cell; no page view is this table.",
702
+ "section/journal-category-vat-netting/advertising-promotion-entertainment-purchases-a/residue": "The journal-category-vat-netting report is a reconciliation table (categoryNettingLines, products/ltd.js) explaining how a gross journal figure nets into a statement cell; no page view is this table.",
703
+ "section/journal-category-vat-netting/advertising-promotion-entertainment-purchases-a/vat": "The journal-category-vat-netting report is a reconciliation table (categoryNettingLines, products/ltd.js) explaining how a gross journal figure nets into a statement cell; no page view is this table.",
703
704
  "section/journal-category-vat-netting/bad-debts-written-off-sales-o/downstream": "The journal-category-vat-netting report is a reconciliation table (categoryNettingLines, products/ltd.js) explaining how a gross journal figure nets into a statement cell; no page view is this table.",
704
705
  "section/journal-category-vat-netting/bad-debts-written-off-sales-o/gross": "The journal-category-vat-netting report is a reconciliation table (categoryNettingLines, products/ltd.js) explaining how a gross journal figure nets into a statement cell; no page view is this table.",
705
706
  "section/journal-category-vat-netting/bad-debts-written-off-sales-o/net": "The journal-category-vat-netting report is a reconciliation table (categoryNettingLines, products/ltd.js) explaining how a gross journal figure nets into a statement cell; no page view is this table.",
@@ -338,6 +338,10 @@
338
338
  "section/accounting-profit-to-tax-profit-bridge/less-other-capital-allowances-box-25": "The page has no profit-bridge view; the bridge from accounting profit to tax profit is not printed anywhere on the books page.",
339
339
  "section/accounting-profit-to-tax-profit-bridge/less-small-balance-allowance-box-24": "The page has no profit-bridge view; the bridge from accounting profit to tax profit is not printed anywhere on the books page.",
340
340
  "section/accounting-profit-to-tax-profit-bridge/less-the-full-return-s-own-box-62-adjustment": "The page has no profit-bridge view; the bridge from accounting profit to tax profit is not printed anywhere on the books page.",
341
+ "section/accounting-profit-to-tax-profit-bridge/less-the-full-return-s-own-electric-charge-point-allowance-box-54": "The page has no profit-bridge view; the bridge from accounting profit to tax profit is not printed anywhere on the books page.",
342
+ "section/accounting-profit-to-tax-profit-bridge/less-the-full-return-s-own-structures-and-buildings-allowance-box-53": "The page has no profit-bridge view; the bridge from accounting profit to tax profit is not printed anywhere on the books page.",
343
+ "section/accounting-profit-to-tax-profit-bridge/less-the-full-return-s-own-zero-emission-car-allowance-box-52-1": "The page has no profit-bridge view; the bridge from accounting profit to tax profit is not printed anywhere on the books page.",
344
+ "section/accounting-profit-to-tax-profit-bridge/less-the-full-return-s-own-zero-emission-goods-vehicle-allowance-box-52": "The page has no profit-bridge view; the bridge from accounting profit to tax profit is not printed anywhere on the books page.",
341
345
  "section/accounting-profit-to-tax-profit-bridge/profit-before-tax-per-the-profit-and-loss-account": "The page has no profit-bridge view; the bridge from accounting profit to tax profit is not printed anywhere on the books page.",
342
346
  "section/accounting-profit-to-tax-profit-bridge/residue": "The page has no profit-bridge view; the bridge from accounting profit to tax profit is not printed anywhere on the books page.",
343
347
  "section/accounting-profit-to-tax-profit-bridge/tax-profit-the-bridge-computes": "The page has no profit-bridge view; the bridge from accounting profit to tax profit is not printed anywhere on the books page.",
@@ -457,18 +461,5 @@
457
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.",
458
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.",
459
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.",
460
- "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.",
461
- "cell/Financialaccounts.xlsx!Profit & Loss Account!B49": "The Profit & Loss view renders the statement itself, B5 to B39. Row 49 sits below it as the memo the sheet keeps for disallowable entertainment, so the add-back has a figure to read; it is not a statement line and no page view prints it.",
462
- "cell/Financialaccounts.xlsx!Profit & Loss Account!C49": "The Profit & Loss view renders the statement itself, B5 to B39. Row 49 sits below it as the memo the sheet keeps for disallowable entertainment, so the add-back has a figure to read; it is not a statement line and no page view prints it.",
463
- "cell/Financialaccounts.xlsx!Profit & Loss Account!D49": "The Profit & Loss view renders the statement itself, B5 to B39. Row 49 sits below it as the memo the sheet keeps for disallowable entertainment, so the add-back has a figure to read; it is not a statement line and no page view prints it.",
464
- "cell/Financialaccounts.xlsx!Profit & Loss Account!E49": "The Profit & Loss view renders the statement itself, B5 to B39. Row 49 sits below it as the memo the sheet keeps for disallowable entertainment, so the add-back has a figure to read; it is not a statement line and no page view prints it.",
465
- "cell/Financialaccounts.xlsx!Profit & Loss Account!F49": "The Profit & Loss view renders the statement itself, B5 to B39. Row 49 sits below it as the memo the sheet keeps for disallowable entertainment, so the add-back has a figure to read; it is not a statement line and no page view prints it.",
466
- "cell/Financialaccounts.xlsx!Profit & Loss Account!G49": "The Profit & Loss view renders the statement itself, B5 to B39. Row 49 sits below it as the memo the sheet keeps for disallowable entertainment, so the add-back has a figure to read; it is not a statement line and no page view prints it.",
467
- "cell/Financialaccounts.xlsx!Profit & Loss Account!H49": "The Profit & Loss view renders the statement itself, B5 to B39. Row 49 sits below it as the memo the sheet keeps for disallowable entertainment, so the add-back has a figure to read; it is not a statement line and no page view prints it.",
468
- "cell/Financialaccounts.xlsx!Profit & Loss Account!I49": "The Profit & Loss view renders the statement itself, B5 to B39. Row 49 sits below it as the memo the sheet keeps for disallowable entertainment, so the add-back has a figure to read; it is not a statement line and no page view prints it.",
469
- "cell/Financialaccounts.xlsx!Profit & Loss Account!J49": "The Profit & Loss view renders the statement itself, B5 to B39. Row 49 sits below it as the memo the sheet keeps for disallowable entertainment, so the add-back has a figure to read; it is not a statement line and no page view prints it.",
470
- "cell/Financialaccounts.xlsx!Profit & Loss Account!K49": "The Profit & Loss view renders the statement itself, B5 to B39. Row 49 sits below it as the memo the sheet keeps for disallowable entertainment, so the add-back has a figure to read; it is not a statement line and no page view prints it.",
471
- "cell/Financialaccounts.xlsx!Profit & Loss Account!L49": "The Profit & Loss view renders the statement itself, B5 to B39. Row 49 sits below it as the memo the sheet keeps for disallowable entertainment, so the add-back has a figure to read; it is not a statement line and no page view prints it.",
472
- "cell/Financialaccounts.xlsx!Profit & Loss Account!M49": "The Profit & Loss view renders the statement itself, B5 to B39. Row 49 sits below it as the memo the sheet keeps for disallowable entertainment, so the add-back has a figure to read; it is not a statement line and no page view prints it.",
473
- "cell/Financialaccounts.xlsx!Profit & Loss Account!N49": "The Profit & Loss view renders the statement itself, B5 to B39. Row 49 sits below it as the memo the sheet keeps for disallowable entertainment, so the add-back has a figure to read; it is not a statement line and no page view prints it."
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."
474
465
  }
@@ -279,8 +279,8 @@
279
279
  },
280
280
  {
281
281
  "path": "tax.capitalAllowances.specialRateWDA",
282
- "products": ["bst", "taxi", "se"],
283
- "reason": "The self-employment year files declare one writing_down_allowance rate. The special rate pool is a Company distinction their capital allowance block does not draw."
282
+ "products": ["bst", "taxi"],
283
+ "reason": "The Basic Sole Trader and Taxi Driver Fixed Assets sheets keep one writing down pool at Admin's single rate. The Self Employed Schedule keeps a special rate pool at Admin!G6, which the year files state as writing_down_allowance_special."
284
284
  },
285
285
  {
286
286
  "path": "tax.incomeTax.additionalRate",
@@ -35,6 +35,7 @@ class2_small_profits_threshold = 6475
35
35
  [capital_allowances]
36
36
  annual_investment_allowance = 1.00
37
37
  writing_down_allowance = 0.18
38
+ writing_down_allowance_special = 0.06
38
39
 
39
40
  [depreciation]
40
41
  land_and_property = 0.00
@@ -35,6 +35,7 @@ class2_small_profits_threshold = 6515
35
35
  [capital_allowances]
36
36
  annual_investment_allowance = 1.00
37
37
  writing_down_allowance = 0.18
38
+ writing_down_allowance_special = 0.06
38
39
 
39
40
  [depreciation]
40
41
  land_and_property = 0.00
@@ -38,6 +38,7 @@ class2_small_profits_threshold = 6725
38
38
  [capital_allowances]
39
39
  annual_investment_allowance = 1.00
40
40
  writing_down_allowance = 0.18
41
+ writing_down_allowance_special = 0.06
41
42
 
42
43
  [depreciation]
43
44
  land_and_property = 0.00
@@ -37,6 +37,7 @@ class2_small_profits_threshold = 6725
37
37
  [capital_allowances]
38
38
  annual_investment_allowance = 1.00
39
39
  writing_down_allowance = 0.18
40
+ writing_down_allowance_special = 0.06
40
41
 
41
42
  [depreciation]
42
43
  land_and_property = 0.00
@@ -34,6 +34,7 @@ class2_small_profits_threshold = 6725
34
34
  [capital_allowances]
35
35
  annual_investment_allowance = 1.00
36
36
  writing_down_allowance = 0.18
37
+ writing_down_allowance_special = 0.06
37
38
 
38
39
  [depreciation]
39
40
  land_and_property = 0.00
@@ -34,6 +34,7 @@ class2_small_profits_threshold = 6845
34
34
  [capital_allowances]
35
35
  annual_investment_allowance = 1.00
36
36
  writing_down_allowance = 0.18
37
+ writing_down_allowance_special = 0.06
37
38
 
38
39
  [depreciation]
39
40
  land_and_property = 0.00
@@ -34,6 +34,7 @@ class2_small_profits_threshold = 7105
34
34
  [capital_allowances]
35
35
  annual_investment_allowance = 1.00
36
36
  writing_down_allowance = 0.14
37
+ writing_down_allowance_special = 0.06
37
38
 
38
39
  [depreciation]
39
40
  land_and_property = 0.00
@@ -27,6 +27,10 @@ import {
27
27
  STOCK_CLOSING_COUNT_CELL,
28
28
  BUSINESS_DESCRIPTION_CELL,
29
29
  DISALLOWABLE_PERCENT_CELLS,
30
+ ANNUAL_ALLOWANCE_CELLS,
31
+ ANNUAL_ADJUSTMENT_CELLS,
32
+ GOODS_FOR_OWN_USE_CELL,
33
+ SPECIAL_RATE_POOL_MARKER_COLUMN,
30
34
  SALESINVOICE_VAT_REG_CELL,
31
35
  SALESINVOICE_TELEPHONE_CELL,
32
36
  SALESINVOICE_PRODUCT_DETAILS_COLUMNS,
@@ -352,13 +356,13 @@ function isPayslipsInputCell(sheet, cellRef) {
352
356
  }
353
357
 
354
358
  // Schedule: an opening asset's description/cost/acc-dep/tax-wdv (C, E, F, O)
355
- // on its existing-asset row, or an in-year disposal's date/proceeds (U, V) on
356
- // that same row; a new purchase's date/supplier/cost (B, C, E) on a New
357
- // Plant & Machinery row. See app/products/se.js's cellWrites() Schedule
358
- // writer.
359
+ // and its special rate pool marker (AB) on its existing-asset row, or an
360
+ // in-year disposal's date/proceeds (U, V) on that same row; a new purchase's
361
+ // date/supplier/cost (B, C, E) on a New Plant & Machinery row. See
362
+ // app/products/se.js's cellWrites() Schedule writer.
359
363
  const EXISTING_SCHEDULE_ROWS = new Set([...EXISTING_ASSET_ROWS.motor, ...EXISTING_ASSET_ROWS.computer]);
360
364
  const NEW_SCHEDULE_ROWS = new Set(NEW_PLANT_ROWS);
361
- const EXISTING_SCHEDULE_COLUMNS = ["C", "E", "F", "O", "U", "V"];
365
+ const EXISTING_SCHEDULE_COLUMNS = ["C", "E", "F", "O", "U", "V", SPECIAL_RATE_POOL_MARKER_COLUMN];
362
366
  const NEW_SCHEDULE_COLUMNS = ["B", "C", "E"];
363
367
  const HP_ROWS = new Set(HP_AGREEMENT_ROWS);
364
368
  const HP_COLUMNS = ["B", "C", "D", "E", "F", "G", "H", "L"];
@@ -389,11 +393,15 @@ function isVatInputCell(sheet, cellRef) {
389
393
  return false;
390
394
  }
391
395
 
392
- // The hub's own business name and description, and the two stock counts.
396
+ // The hub's own business name, description and goods for own use, the two
397
+ // stock counts, the disallowable percentages and the SA103F boxes the book
398
+ // states by hand.
399
+ const SE_FULL_STATED_CELLS = new Set([...Object.values(ANNUAL_ALLOWANCE_CELLS), ...Object.values(ANNUAL_ADJUSTMENT_CELLS)]);
393
400
  function isHubInputCell(sheet, cellRef) {
394
- if (sheet === "Business Details") return cellRef === "C5" || cellRef === BUSINESS_DESCRIPTION_CELL;
401
+ if (sheet === "Business Details") return cellRef === "C5" || cellRef === BUSINESS_DESCRIPTION_CELL || cellRef === GOODS_FOR_OWN_USE_CELL;
395
402
  if (sheet === "StockControl") return cellRef === STOCK_OPENING_COUNT_CELL || cellRef === STOCK_CLOSING_COUNT_CELL;
396
403
  if (sheet === "VitalTax") return Object.values(DISALLOWABLE_PERCENT_CELLS).includes(cellRef);
404
+ if (sheet === "SE Full") return SE_FULL_STATED_CELLS.has(cellRef);
397
405
  return false;
398
406
  }
399
407
 
@@ -60,7 +60,10 @@ const VAT_RATE = 0.2;
60
60
  // Sales and Purchases analysis columns, code letter by column letter, in the
61
61
  // order row 5 of each month tab tests them. Column AK of a Purchases month
62
62
  // tab is the CIS certificates column, which the writer fills from a
63
- // sub-contractor purchase rather than from a code letter.
63
+ // sub-contractor purchase rather than from a code letter. Column AJ, business
64
+ // entertainment, sits after the fixed asset column: it is the one expense the
65
+ // working sheet adds back in full, so it has to be analysed apart from the
66
+ // advertising it shares a profit and loss line with.
64
67
  export const SALES_ANALYSIS_COLUMNS = { a: "O", b: "P", c: "Q", d: "R", g: "S", o: "T", fs: "U" };
65
68
  export const PURCHASE_ANALYSIS_COLUMNS = {
66
69
  s: "O",
@@ -84,6 +87,7 @@ export const PURCHASE_ANALYSIS_COLUMNS = {
84
87
  y: "AG",
85
88
  z: "AH",
86
89
  fa: "AI",
90
+ e: "AJ",
87
91
  };
88
92
  const SALES_CIS_COLUMN = "V";
89
93
  const PURCHASES_CIS_COLUMN = "AK";
@@ -106,7 +110,15 @@ const SALES_ROW_OFFSET = 49;
106
110
  const SALES_BAD_DEBT_ROW = 34;
107
111
 
108
112
  // The management P&L's expense rows and the trial balance row each reads.
113
+ // Row 27 also reads the entertainment row, TRIAL_BALANCE_ENTERTAINMENT_ROW.
109
114
  const EXPENSE_PL_ROWS = { 21: 68, 22: 69, 23: 70, 24: 71, 25: 72, 26: 73, 27: 74, 28: 75, 29: 76, 30: 77, 31: 78, 32: 79, 33: 80 };
115
+ const ADVERTISING_PL_ROW = 27;
116
+
117
+ // The trial balance row business entertainment lands on. The profit and loss
118
+ // account shows it with advertising; the Corporation Tax working sheet reads
119
+ // it back out of the trial balance as a third add-back beside goodwill and
120
+ // depreciation, the way HMRC disallows client entertaining in full.
121
+ const TRIAL_BALANCE_ENTERTAINMENT_ROW = 90;
110
122
 
111
123
  // WagesInterface holds a month a row in two blocks, employees above
112
124
  // directors, twelve rows each.
@@ -275,6 +287,7 @@ const TRIAL_BALANCE_READS = [
275
287
  "EJ40",
276
288
  "EJ48",
277
289
  "EJ66",
290
+ "EJ90",
278
291
  "EJ91",
279
292
  "L34",
280
293
  ];
@@ -284,7 +297,7 @@ const TRIAL_BALANCE_READS = [
284
297
  const AUDIT_ROWS = [
285
298
  6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 22, 23, 24, 25, 26, 28, 29, 30, 31, 32, 33, 34, 35, 37, 39, 40, 42, 43, 44, 47, 48,
286
299
  49, 53, 54, 55, 56, 57, 58, 60, 61, 62, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87,
287
- 88, 89,
300
+ 88, 89, 90,
288
301
  ];
289
302
 
290
303
  // ── Small helpers ──────────────────────────────────────────────────────────
@@ -1541,6 +1554,7 @@ function buildTrialBalance(input) {
1541
1554
  monthly[87] = zeroMonths().map(() => blocks.whole.I / 12);
1542
1555
  monthly[88] = negated(receipts("X"));
1543
1556
  monthly[89] = payments("X");
1557
+ monthly[TRIAL_BALANCE_ENTERTAINMENT_ROW] = purchasesMonthly(PURCHASE_ANALYSIS_COLUMNS.e);
1544
1558
  monthly[19] = stock.movements;
1545
1559
  for (const [row, values] of Object.entries(monthly)) tb[`EJ${row}`] = sum(values) + (tb[`D${row}`] || 0);
1546
1560
 
@@ -1651,7 +1665,13 @@ function buildMonthlyProfitAndLoss(tb, tabs) {
1651
1665
  pl.B18 = tb.EJ64 + tb.EJ65;
1652
1666
  pl.B19 = tb.EJ66;
1653
1667
  pl.B20 = tb.EJ67;
1654
- for (const [row, source] of Object.entries(EXPENSE_PL_ROWS)) setRow(row, tb.monthly[source]);
1668
+ for (const [row, source] of Object.entries(EXPENSE_PL_ROWS)) {
1669
+ const values =
1670
+ Number(row) === ADVERTISING_PL_ROW
1671
+ ? tb.monthly[source].map((value, index) => value + tb.monthly[TRIAL_BALANCE_ENTERTAINMENT_ROW][index])
1672
+ : tb.monthly[source];
1673
+ setRow(row, values);
1674
+ }
1655
1675
  setRow(SALES_BAD_DEBT_ROW, tb.monthly[81]);
1656
1676
  pl.B35 = tb.EJ82;
1657
1677
  pl.B36 = tb.EJ83 + tb.EJ88 + tb.EJ89;
@@ -1781,6 +1801,7 @@ function buildDirectorsReport(publishedPl, publishedBalanceSheet, companySecreta
1781
1801
  function buildCorporationTax({ admin, trialBalance, blocks, publishedPl, openAccounts }) {
1782
1802
  const goodwill = trialBalance.EJ85 > 0 ? trialBalance.EJ85 : 0;
1783
1803
  const depreciation = trialBalance.EJ87 > 0 ? trialBalance.EJ87 : 0;
1804
+ const entertainment = trialBalance[`EJ${TRIAL_BALANCE_ENTERTAINMENT_ROW}`] > 0 ? trialBalance[`EJ${TRIAL_BALANCE_ENTERTAINMENT_ROW}`] : 0;
1784
1805
  const netBalancingCharge = blocks.whole.W > 0 ? blocks.whole.Z - blocks.whole.Y : 0;
1785
1806
 
1786
1807
  const sheet = {
@@ -1794,7 +1815,8 @@ function buildCorporationTax({ admin, trialBalance, blocks, publishedPl, openAcc
1794
1815
  };
1795
1816
  if (goodwill > 0) sheet.I7 = goodwill;
1796
1817
  if (depreciation > 0) sheet.I8 = depreciation;
1797
- sheet.K10 = goodwill + depreciation;
1818
+ if (entertainment > 0) sheet.I9 = entertainment;
1819
+ sheet.K10 = goodwill + depreciation + entertainment;
1798
1820
  sheet.K12 = sheet.K5 + sheet.K10;
1799
1821
  sheet.K20 = sheet.I15 + sheet.I16 + sheet.I17 + sheet.I18;
1800
1822
  sheet.K22 = sheet.K12 - sheet.K20;
@@ -63,17 +63,24 @@ function primaryField(boxes, boxNumber) {
63
63
  // rather than the object this call meant to build. Every path here comes from
64
64
  // sa103-mtd-mapping.json's own "field" column, so a segment like this is a
65
65
  // defect in that mapping file and must fail loudly rather than land quietly
66
- // on some other object.
67
- const UNSAFE_PATH_SEGMENTS = new Set(["__proto__", "constructor", "prototype"]);
68
-
66
+ // on some other object. The check sits inside the walk, as literal
67
+ // comparisons, and each intermediate node is only reused when it is the
68
+ // object's own property: the shape static analysis recognises as guarded.
69
69
  export function setPath(target, path, value) {
70
70
  const parts = path.split(".");
71
- for (const part of parts) {
72
- if (UNSAFE_PATH_SEGMENTS.has(part)) throw new Error(`setPath: "${part}" is not a field name, in path "${path}"`);
73
- }
74
71
  let node = target;
75
- for (let i = 0; i < parts.length - 1; i++) node = node[parts[i]] ||= {};
76
- node[parts[parts.length - 1]] = value;
72
+ for (let i = 0; i < parts.length; i++) {
73
+ const part = parts[i];
74
+ if (part === "__proto__" || part === "constructor" || part === "prototype") {
75
+ throw new Error(`setPath: "${part}" is not a field name, in path "${path}"`);
76
+ }
77
+ if (i === parts.length - 1) {
78
+ node[part] = value;
79
+ return;
80
+ }
81
+ if (!Object.hasOwn(node, part) || node[part] === null || typeof node[part] !== "object") node[part] = {};
82
+ node = node[part];
83
+ }
77
84
  }
78
85
 
79
86
  function round2(value) {
@@ -452,27 +459,32 @@ export function buildSelfEmploymentQuarterlyUpdates(book, lines, taxData, option
452
459
  };
453
460
  }
454
461
 
455
- // Every annual field this book can source, and a warning naming the box for
456
- // every one it cannot -- section 8 of the design records why each is out of
457
- // reach today: no cell in the template, a cell the customer fills by hand,
458
- // or a box the schema has no field for. Three of these boxes (52, 54, 69)
459
- // carry a field that also moves in and out of HMRC's own schema by tax
462
+ // The annual boxes with no formula behind them that the book can state
463
+ // (tax.selfEmployment.allowances / .adjustments, keyed by the API field
464
+ // name): the writer puts each figure on its SE Full cell and the derivation
465
+ // reads it back from there, so the payload carries what the return prints.
466
+ // A box the book does not state stays blank and warns.
467
+ const BOOK_STATED_ANNUAL_BOXES = [
468
+ { box: "52", cell: "D152", table: "allowances" }, // zeroEmissionsGoodsVehicleAllowance -- gone from 2025-26
469
+ { box: "52.1", cell: "D156", table: "allowances" }, // zeroEmissionsCarAllowance
470
+ { box: "53", cell: "D160", table: "allowances" }, // structuredBuildingAllowance
471
+ { box: "62", cell: "D179", table: "adjustments" }, // includedNonTaxableProfits
472
+ { box: "71", cell: "D210", table: "adjustments" }, // accountingAdjustment
473
+ ];
474
+
475
+ // Every annual field no cell and no book field can source, and a warning
476
+ // naming the box for each -- section 8 of the design records why each is
477
+ // out of reach today: no cell in the template, or a figure the book has no
478
+ // record for. Box 69's field also moves out of HMRC's own schema by tax
460
479
  // year -- annualFieldsUnavailableForYear() below is what keeps a year that
461
480
  // no longer accepts a field from warning about it as if it still did.
462
481
  const NO_SOURCE_ANNUAL_BOXES = [
463
- { box: "51", pick: 0 }, // allowances.capitalAllowanceSpecialRatePool
464
482
  { box: "51", pick: 1 }, // allowances.capitalAllowanceSingleAssetPool (shared with box 50)
465
- { box: "52", pick: 0 }, // allowances.zeroEmissionsGoodsVehicleAllowance -- gone from 2025-26
466
- { box: "52.1", pick: 0 }, // allowances.zeroEmissionsCarAllowance
467
- { box: "53", pick: 0 }, // allowances.structuredBuildingAllowance
468
483
  { box: "53.1", pick: 0 }, // allowances.enhancedStructuredBuildingAllowance
469
- { box: "54", pick: 1 }, // allowances.electricChargePointAllowance -- gone from 2025-26 (shared with box 55's enhancedCapitalAllowance)
470
484
  { box: "55", pick: 1 }, // allowances.businessPremisesRenovationAllowance (shared with enhancedCapitalAllowance)
471
485
  { box: "59", pick: 1 }, // adjustments.balancingChargeBpra (shared with balancingChargeOther)
472
- { box: "62", pick: 0 }, // adjustments.includedNonTaxableProfits
473
486
  { box: "68", pick: 0 }, // adjustments.basisAdjustment
474
487
  { box: "69", pick: 0 }, // adjustments.overlapReliefUsed -- gone from 2026-27
475
- { box: "71", pick: 0 }, // adjustments.accountingAdjustment
476
488
  { box: "73.3", pick: 0 }, // adjustments.transitionProfitAmount -- added in 2024-25
477
489
  { box: "73.3", pick: 1 }, // adjustments.transitionProfitAccelerationAmount -- added in 2024-25
478
490
  ];
@@ -527,9 +539,16 @@ export function buildSelfEmploymentAnnualSubmission(book, lines, taxData, option
527
539
  // ...), so both branches land on one shared root and fall out of it rather
528
540
  // than being built separately and then walked into again.
529
541
  const root = {};
542
+ const cellNumber = (cell) => (typeof seFull[cell] === "number" ? seFull[cell] : 0);
530
543
  setPath(root, primaryField(boxes, "49"), round2(seFull.D139));
531
544
  setPath(root, primaryField(boxes, "50"), round2(seFull.D144));
532
- setPath(root, primaryField(boxes, "55"), round2(seFull.O144));
545
+ // Box 51 is the schedule's special rate pool (column AC, the rows marked S).
546
+ setPath(root, primaryField(boxes, "51"), round2(seFull.D147));
547
+ // HMRC's mapping files box 54 and box 55 under one field: the small pools
548
+ // write-off the sheet computes at O144 and the charge-point figure the book
549
+ // states at O139 both reach it.
550
+ const enhancedCapitalAllowanceField = primaryField(boxes, "55");
551
+ setPath(root, enhancedCapitalAllowanceField, round2(seFull.O144 + cellNumber("O139")));
533
552
  setPath(root, primaryField(boxes, "56"), round2(seFull.O149));
534
553
 
535
554
  setPath(root, primaryField(boxes, "59"), round2(seFull.O160));
@@ -537,21 +556,63 @@ export function buildSelfEmploymentAnnualSubmission(book, lines, taxData, option
537
556
  setPath(root, ownUseField, round2(seFull.D169));
538
557
  setPath(root, primaryField(boxes, "75"), round2(seFull.O204));
539
558
 
540
- const allowances = root.allowances || {};
541
- const adjustments = root.adjustments || {};
542
-
543
- const enhancedCapitalAllowanceField = primaryField(boxes, "55");
544
559
  const warnings = [
545
- {
546
- field: ownUseField,
547
- reason: "Business Details!O50 is a customer input cell the book schema has no field for; the template always carries nil here.",
548
- },
549
560
  {
550
561
  field: enhancedCapitalAllowanceField,
551
562
  reason:
552
563
  "SA103F box 55 is the small pools write-off (Schedule S1, once the pool balance and the writing down allowance together are under £1,000), not a genuine 100% or enhanced capital allowance; HMRC's own field mapping still files it here.",
553
564
  },
554
565
  ];
566
+ if (typeof rawResults["Business Details"]?.O50 !== "number") {
567
+ warnings.push({
568
+ field: ownUseField,
569
+ reason: "the book states no tax.selfEmployment.adjustments.goodsAndServicesOwnUse, so Business Details!O50 and box 60 carry nil.",
570
+ });
571
+ }
572
+ for (const { box: boxNumber, cell, table } of BOOK_STATED_ANNUAL_BOXES) {
573
+ const field = primaryField(boxes, boxNumber);
574
+ const leaf = field.split(".").pop();
575
+ if (unavailableFields.has(field)) {
576
+ if (cellNumber(cell) !== 0) {
577
+ warnings.push({
578
+ field,
579
+ reason: `the book states ${cellNumber(cell)} for SA103F box ${boxNumber}, which the sheet totals, but HMRC's schema for ${taxYear} no longer accepts the field; nothing is filed for it.`,
580
+ handComputed: round2(cellNumber(cell)),
581
+ });
582
+ }
583
+ continue;
584
+ }
585
+ if (typeof seFull[cell] === "number") {
586
+ setPath(root, field, round2(seFull[cell]));
587
+ continue;
588
+ }
589
+ warnings.push({
590
+ field,
591
+ reason: `SA103F box ${boxNumber} has no formula behind it and the book states no tax.selfEmployment.${table}.${leaf}; the box stays blank.`,
592
+ });
593
+ }
594
+ const electricChargePointField = fieldsOf(boxEntry(boxes, "54"))[1];
595
+ if (cellNumber("O139") !== 0) {
596
+ warnings.push({
597
+ field: electricChargePointField,
598
+ reason: `SA103F box 54 (SE Full!O139) is filed under ${enhancedCapitalAllowanceField}, HMRC's primary field for the box, together with box 55.`,
599
+ handComputed: round2(cellNumber("O139")),
600
+ });
601
+ } else if (!unavailableFields.has(electricChargePointField)) {
602
+ warnings.push({
603
+ field: electricChargePointField,
604
+ reason:
605
+ "SA103F box 54 has no formula behind it and the book states no tax.selfEmployment.allowances.electricChargePointAllowance; the box stays blank.",
606
+ });
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
+ }
555
616
  for (const { box: boxNumber, pick } of NO_SOURCE_ANNUAL_BOXES) {
556
617
  const entry = boxEntry(boxes, boxNumber);
557
618
  const field = fieldsOf(entry)[pick];
@@ -562,6 +623,9 @@ export function buildSelfEmploymentAnnualSubmission(book, lines, taxData, option
562
623
  });
563
624
  }
564
625
 
626
+ const allowances = root.allowances || {};
627
+ const adjustments = root.adjustments || {};
628
+
565
629
  const result = { taxYear };
566
630
  if (Object.keys(allowances).length > 0) result.allowances = allowances;
567
631
  if (Object.keys(adjustments).length > 0) result.adjustments = adjustments;