@diy-accounting-uk/diya-gl 1.2.17 → 1.2.18
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/hmrc/form-layouts/se.json +16 -3
- package/dist/app/data/hmrc/sa103-mtd-mapping.json +3 -3
- package/dist/app/data/releases.json +26 -0
- package/dist/app/data/render-unrepresentable/se.json +19 -1
- package/dist/app/lib/anchors/se.js +15 -1
- package/dist/app/lib/calculators/se-derivations.js +41 -9
- package/dist/app/lib/calculators/se.js +95 -11
- package/dist/app/lib/diya-gl-loader.js +11 -1
- package/dist/app/lib/provenance-data.js +3 -3
- package/dist/app/lib/scenario-extractor.js +1 -0
- package/dist/app/lib/xlsx-exporter.js +35 -1
- package/dist/app/products/se.js +226 -16
- package/dist/web/spreadsheets.diyaccounting.co.uk/public/schema/diya-gl-book-v2.schema.json +22 -0
- package/package.json +1 -1
|
@@ -301,15 +301,28 @@
|
|
|
301
301
|
"boxes": [
|
|
302
302
|
{ "box": "66", "label": "Not in use", "cell": null, "notInUse": true },
|
|
303
303
|
{ "box": "67", "label": "Not in use", "cell": null, "notInUse": true },
|
|
304
|
-
{
|
|
305
|
-
|
|
304
|
+
{
|
|
305
|
+
"box": "68",
|
|
306
|
+
"label": "Adjustment where your accounting period was not 12 months long",
|
|
307
|
+
"cell": "Financialaccounts.xlsx!SE Full!D197"
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
"box": "69",
|
|
311
|
+
"label": "Not in use",
|
|
312
|
+
"cell": "Financialaccounts.xlsx!Business Details!D64",
|
|
313
|
+
"notInUse": true
|
|
314
|
+
},
|
|
306
315
|
{ "box": "70", "label": "Not in use", "cell": null, "notInUse": true },
|
|
307
316
|
{ "box": "71", "label": "Adjustment for change of accounting practice", "cell": "Financialaccounts.xlsx!SE Full!D210" },
|
|
308
317
|
{ "box": "72", "label": "Averaging adjustment", "cell": null },
|
|
309
318
|
{ "box": "73", "label": "Adjusted profit for 2025-26", "cell": "Financialaccounts.xlsx!SE Full!O194" },
|
|
310
319
|
{ "box": "73.1", "label": "Not in use", "cell": null, "notInUse": true },
|
|
311
320
|
{ "box": "73.2", "label": "Not in use", "cell": null, "notInUse": true },
|
|
312
|
-
{
|
|
321
|
+
{
|
|
322
|
+
"box": "73.3",
|
|
323
|
+
"label": "Spread of the transition profit treated as arising in this tax year",
|
|
324
|
+
"cell": "Financialaccounts.xlsx!SE Full!D201"
|
|
325
|
+
},
|
|
313
326
|
{ "box": "73.4", "label": "Loss brought forward set against this year's spread of the transition profit", "cell": null },
|
|
314
327
|
{
|
|
315
328
|
"box": "74",
|
|
@@ -574,7 +574,7 @@
|
|
|
574
574
|
"form": "SA103F",
|
|
575
575
|
"box": "68",
|
|
576
576
|
"label": "Adjustment where your accounting period ended before 31 March 2026 or where your accounting period was not 12 months long – if the adjustment needs to be taken off the profit figure, put a minus sign in the box",
|
|
577
|
-
"cell":
|
|
577
|
+
"cell": "SE Full!D197",
|
|
578
578
|
"field": "adjustments.basisAdjustment",
|
|
579
579
|
"route": "annual"
|
|
580
580
|
},
|
|
@@ -582,7 +582,7 @@
|
|
|
582
582
|
"form": "SA103F",
|
|
583
583
|
"box": "69",
|
|
584
584
|
"label": "Overlap relief used this year",
|
|
585
|
-
"cell":
|
|
585
|
+
"cell": "Business Details!D64",
|
|
586
586
|
"field": "adjustments.overlapReliefUsed",
|
|
587
587
|
"route": "annual",
|
|
588
588
|
"caveat": "the CSV lists box 69 (overlapReliefUsed) as live even though the 2026 SA103F prints boxes 69 and 70 as not in use; the field is dropped from the API schema from 2026-27 (see api.years['2026-27'].fieldsGone)"
|
|
@@ -621,7 +621,7 @@
|
|
|
621
621
|
"form": "SA103F",
|
|
622
622
|
"box": "73.3",
|
|
623
623
|
"label": "Spread of the transition profit treated as arising in this tax year",
|
|
624
|
-
"cell":
|
|
624
|
+
"cell": "SE Full!D201",
|
|
625
625
|
"field": "adjustments.transitionProfitAmount | adjustments.transitionProfitAccelerationAmount",
|
|
626
626
|
"route": "annual"
|
|
627
627
|
},
|
|
@@ -675,6 +675,32 @@
|
|
|
675
675
|
"scorecard": "101605 passed, 0 warnings, 0 failed"
|
|
676
676
|
}
|
|
677
677
|
}
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
"tag": "diya-gl-v1.2.17",
|
|
681
|
+
"date": "2026-09-15",
|
|
682
|
+
"formatVersion": "diya-gl/1",
|
|
683
|
+
"engineVersion": "1.2.17+67e3da11",
|
|
684
|
+
"taxDataHash": "3221a7c2f39c",
|
|
685
|
+
"reconciledCommit": "54e8731a91279975e61b1a232fbb67fda89515f0",
|
|
686
|
+
"templates": {
|
|
687
|
+
"bst": {
|
|
688
|
+
"hash": "0d003e10e00c",
|
|
689
|
+
"scorecard": "812 passed, 0 warnings, 0 failed"
|
|
690
|
+
},
|
|
691
|
+
"taxi": {
|
|
692
|
+
"hash": "1ff69654d64d",
|
|
693
|
+
"scorecard": "686 passed, 0 warnings, 0 failed"
|
|
694
|
+
},
|
|
695
|
+
"se": {
|
|
696
|
+
"hash": "107ff1b5c94a",
|
|
697
|
+
"scorecard": "7969 passed, 0 warnings, 0 failed"
|
|
698
|
+
},
|
|
699
|
+
"ltd": {
|
|
700
|
+
"hash": "caa14632083b",
|
|
701
|
+
"scorecard": "101605 passed, 0 warnings, 0 failed"
|
|
702
|
+
}
|
|
703
|
+
}
|
|
678
704
|
}
|
|
679
705
|
]
|
|
680
706
|
}
|
|
@@ -482,5 +482,23 @@
|
|
|
482
482
|
"cell/Fixedassets.xlsx!Schedule!G116": "The fixture's own Structures and Buildings Allowance claim row (the Freeport claim): its date, building, expenditure, postcode and tax site marker. The Fixed assets view has no per-claim table, and the K121 total those rows sum into is on the same view under its own cell key.",
|
|
483
483
|
"cell/Fixedassets.xlsx!Schedule!H116": "The fixture's own Structures and Buildings Allowance claim row (the Freeport claim): its rate, its days claimed and its allowance. The Fixed assets view has no per-claim table, and the K121 total those rows sum into is on the same view under its own cell key.",
|
|
484
484
|
"cell/Fixedassets.xlsx!Schedule!J116": "The fixture's own Structures and Buildings Allowance claim row (the Freeport claim): its rate, its days claimed and its allowance. The Fixed assets view has no per-claim table, and the K121 total those rows sum into is on the same view under its own cell key.",
|
|
485
|
-
"cell/Fixedassets.xlsx!Schedule!K116": "The fixture's own Structures and Buildings Allowance claim row (the Freeport claim): its rate, its days claimed and its allowance. The Fixed assets view has no per-claim table, and the K121 total those rows sum into is on the same view under its own cell key."
|
|
485
|
+
"cell/Fixedassets.xlsx!Schedule!K116": "The fixture's own Structures and Buildings Allowance claim row (the Freeport claim): its rate, its days claimed and its allowance. The Fixed assets view has no per-claim table, and the K121 total those rows sum into is on the same view under its own cell key.",
|
|
486
|
+
"cell/Financialaccounts.xlsx!Business Details!D59": "The basis period record's overlap profit brought forward (feeds box 69), entered by hand on Business Details' own ENTER block. No view renders that block; the book page has no basis period section.",
|
|
487
|
+
"cell/Financialaccounts.xlsx!Business Details!O59": "The basis period record's 2023-24 transition profit not yet treated as arising, entered by hand on Business Details' own ENTER block. No view renders that block.",
|
|
488
|
+
"cell/Financialaccounts.xlsx!Business Details!O69": "The basis period record's additional transition profit elected this year (feeds box 73.3), entered by hand on Business Details' own ENTER block. No view renders that block.",
|
|
489
|
+
"cell/Financialaccounts.xlsx!Business Details!D69": "The overlap balance carried forward, the sheet's own formula on Business Details' ENTER block. No view renders that block.",
|
|
490
|
+
"cell/Financialaccounts.xlsx!Business Details!O64": "The transition profit treated as arising this year before election (feeds box 73.3), the sheet's own formula on Business Details' ENTER block. No view renders that block.",
|
|
491
|
+
"cell/Financialaccounts.xlsx!Business Details!O74": "The transition profit carried forward to next year, the sheet's own formula on Business Details' ENTER block. No view renders that block.",
|
|
492
|
+
"cell/Financialaccounts.xlsx!Business Details!N27": "The book's own accounting period start date (box 8), printed on Business Details. No view renders the front page's boxes 8 and 9.",
|
|
493
|
+
"cell/Financialaccounts.xlsx!Business Details!N32": "The book's own accounting period end date (box 9), printed on Business Details. No view renders the front page's boxes 8 and 9.",
|
|
494
|
+
"cell/Financialaccounts.xlsx!Income Tax!E14": "The top-slice income tax on box 73.3's transition profit spread. The Income Tax view lists E5 to E12, E15 and E16, added before box 73.3 had a cell to read; E14 is not yet in that list.",
|
|
495
|
+
"section/business-details/overlap-profit-brought-forward-feeds-box-69": "The basis period record's own report row. No view renders Business Details' ENTER block.",
|
|
496
|
+
"section/business-details/transition-profit-not-yet-treated-as-arising": "The basis period record's own report row. No view renders Business Details' ENTER block.",
|
|
497
|
+
"section/business-details/additional-transition-profit-elected-this-year-feeds-box-73-3": "The basis period record's own report row. No view renders Business Details' ENTER block.",
|
|
498
|
+
"section/business-details/overlap-profit-carried-forward": "The basis period record's own report row. No view renders Business Details' ENTER block.",
|
|
499
|
+
"section/business-details/transition-profit-treated-as-arising-this-year-before-election-feeds-box-73-3": "The basis period record's own report row. No view renders Business Details' ENTER block.",
|
|
500
|
+
"section/business-details/transition-profit-carried-forward-to-next-year": "The basis period record's own report row. No view renders Business Details' ENTER block.",
|
|
501
|
+
"section/business-details/accounting-period-start-date-box-8": "The book's own accounting period start date. No view renders the front page's boxes 8 and 9.",
|
|
502
|
+
"section/business-details/accounting-period-end-date-box-9": "The book's own accounting period end date. No view renders the front page's boxes 8 and 9.",
|
|
503
|
+
"section/income-tax-calculation/income-tax-on-transition-profit-box-73-3-top-sliced": "Box 73.3's own top-slice income tax. The Income Tax view lists E5 to E12, E15 and E16, added before box 73.3 had a cell to read."
|
|
486
504
|
}
|
|
@@ -30,6 +30,9 @@ import {
|
|
|
30
30
|
ANNUAL_ALLOWANCE_CELLS,
|
|
31
31
|
ANNUAL_ADJUSTMENT_CELLS,
|
|
32
32
|
GOODS_FOR_OWN_USE_CELL,
|
|
33
|
+
BASIS_PERIOD_CELLS,
|
|
34
|
+
ACCOUNTING_PERIOD_START_CELL,
|
|
35
|
+
ACCOUNTING_PERIOD_END_CELL,
|
|
33
36
|
SPECIAL_RATE_POOL_MARKER_COLUMN,
|
|
34
37
|
SINGLE_ASSET_POOL_MARKER_COLUMN,
|
|
35
38
|
PRIVATE_USE_COLUMN,
|
|
@@ -418,8 +421,19 @@ function isVatInputCell(sheet, cellRef) {
|
|
|
418
421
|
// stock counts, the disallowable percentages and the SA103F boxes the book
|
|
419
422
|
// states by hand.
|
|
420
423
|
const SE_FULL_STATED_CELLS = new Set([...Object.values(ANNUAL_ALLOWANCE_CELLS), ...Object.values(ANNUAL_ADJUSTMENT_CELLS)]);
|
|
424
|
+
const BUSINESS_DETAILS_BASIS_PERIOD_CELLS = new Set([
|
|
425
|
+
...Object.values(BASIS_PERIOD_CELLS),
|
|
426
|
+
ACCOUNTING_PERIOD_START_CELL,
|
|
427
|
+
ACCOUNTING_PERIOD_END_CELL,
|
|
428
|
+
]);
|
|
421
429
|
function isHubInputCell(sheet, cellRef) {
|
|
422
|
-
if (sheet === "Business Details")
|
|
430
|
+
if (sheet === "Business Details")
|
|
431
|
+
return (
|
|
432
|
+
cellRef === "C5" ||
|
|
433
|
+
cellRef === BUSINESS_DESCRIPTION_CELL ||
|
|
434
|
+
cellRef === GOODS_FOR_OWN_USE_CELL ||
|
|
435
|
+
BUSINESS_DETAILS_BASIS_PERIOD_CELLS.has(cellRef)
|
|
436
|
+
);
|
|
423
437
|
if (sheet === "StockControl") return cellRef === STOCK_OPENING_COUNT_CELL || cellRef === STOCK_CLOSING_COUNT_CELL;
|
|
424
438
|
if (sheet === "VitalTax") return Object.values(DISALLOWABLE_PERCENT_CELLS).includes(cellRef);
|
|
425
439
|
if (sheet === "SE Full") return SE_FULL_STATED_CELLS.has(cellRef);
|
|
@@ -474,18 +474,13 @@ const BOOK_STATED_ANNUAL_BOXES = [
|
|
|
474
474
|
// Every annual field no cell and no book field can source, and a warning
|
|
475
475
|
// naming the box for each -- section 8 of the design records why each is
|
|
476
476
|
// out of reach today: no cell in the template, or a figure the book has no
|
|
477
|
-
// record for.
|
|
478
|
-
//
|
|
479
|
-
//
|
|
480
|
-
//
|
|
481
|
-
// they are filed as arrays below, one item per claim.
|
|
477
|
+
// record for. Boxes 53 and 53.1 are not here: the Schedule's SBA block
|
|
478
|
+
// computes them, and they are filed as arrays below, one item per claim.
|
|
479
|
+
// Boxes 68, 69 and 73.3 are not here either: the basis period record below
|
|
480
|
+
// computes and files them.
|
|
482
481
|
const NO_SOURCE_ANNUAL_BOXES = [
|
|
483
482
|
{ box: "55", pick: 1 }, // allowances.businessPremisesRenovationAllowance (shared with enhancedCapitalAllowance)
|
|
484
483
|
{ box: "59", pick: 1 }, // adjustments.balancingChargeBpra (shared with balancingChargeOther)
|
|
485
|
-
{ box: "68", pick: 0 }, // adjustments.basisAdjustment
|
|
486
|
-
{ box: "69", pick: 0 }, // adjustments.overlapReliefUsed -- gone from 2026-27
|
|
487
|
-
{ box: "73.3", pick: 0 }, // adjustments.transitionProfitAmount -- added in 2024-25
|
|
488
|
-
{ box: "73.3", pick: 1 }, // adjustments.transitionProfitAccelerationAmount -- added in 2024-25
|
|
489
484
|
];
|
|
490
485
|
|
|
491
486
|
// api.years in sa103-mtd-mapping.json is the source of which annual fields
|
|
@@ -624,6 +619,43 @@ export function buildSelfEmploymentAnnualSubmission(book, lines, taxData, option
|
|
|
624
619
|
});
|
|
625
620
|
}
|
|
626
621
|
|
|
622
|
+
// The basis period record (boxes 68, 69 and 73.3): the sheet computes
|
|
623
|
+
// every one of these from the book's stated basis period figures, so each
|
|
624
|
+
// files only when it carries a genuine adjustment -- box 68 is nil for
|
|
625
|
+
// almost every trader (s.7C, an accounting date of 31 March to 5 April),
|
|
626
|
+
// and a trader who has already used all of their overlap or transition
|
|
627
|
+
// profit states nothing further for it.
|
|
628
|
+
const businessDetails = rawResults["Business Details"] || {};
|
|
629
|
+
const businessDetailsNumber = (cell) => (typeof businessDetails[cell] === "number" ? businessDetails[cell] : 0);
|
|
630
|
+
const basisAdjustmentField = primaryField(boxes, "68");
|
|
631
|
+
if (!unavailableFields.has(basisAdjustmentField) && cellNumber("D197") !== 0) {
|
|
632
|
+
setPath(root, basisAdjustmentField, round2(cellNumber("D197")));
|
|
633
|
+
}
|
|
634
|
+
const overlapReliefField = primaryField(boxes, "69");
|
|
635
|
+
const overlapReliefUsed = businessDetailsNumber("D64");
|
|
636
|
+
if (!unavailableFields.has(overlapReliefField) && overlapReliefUsed > 0) {
|
|
637
|
+
setPath(root, overlapReliefField, round2(overlapReliefUsed));
|
|
638
|
+
}
|
|
639
|
+
const transitionProfitField = fieldsOf(boxEntry(boxes, "73.3"))[0];
|
|
640
|
+
const transitionAccelerationField = fieldsOf(boxEntry(boxes, "73.3"))[1];
|
|
641
|
+
const transitionProfitArising = businessDetailsNumber("O64");
|
|
642
|
+
const transitionAcceleration = businessDetailsNumber("O69");
|
|
643
|
+
if (!unavailableFields.has(transitionProfitField) && transitionProfitArising > 0) {
|
|
644
|
+
setPath(root, transitionProfitField, round2(transitionProfitArising));
|
|
645
|
+
}
|
|
646
|
+
if (!unavailableFields.has(transitionAccelerationField) && transitionAcceleration > 0) {
|
|
647
|
+
setPath(root, transitionAccelerationField, round2(transitionAcceleration));
|
|
648
|
+
}
|
|
649
|
+
// FA 2022 Sch 1 para 73(3): an election cannot exceed the untaxed balance
|
|
650
|
+
// left once this year's automatic slice (O64) is taken out of it.
|
|
651
|
+
const transitionUntaxedBalance = businessDetailsNumber("O59") - transitionProfitArising;
|
|
652
|
+
if (transitionAcceleration > transitionUntaxedBalance) {
|
|
653
|
+
warnings.push({
|
|
654
|
+
field: transitionAccelerationField,
|
|
655
|
+
reason: `the elected transition profit acceleration (${round2(transitionAcceleration)}) exceeds the untaxed balance (${round2(transitionUntaxedBalance)}); an election cannot exceed it (FA 2022 Sch 1 para 73(3)).`,
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
|
|
627
659
|
// Structures and Buildings Allowance (boxes 53 and 53.1): the Schedule's
|
|
628
660
|
// SBA block computes each claim's year allowance from the book's own
|
|
629
661
|
// inputs, so the claim files as one item per row, not a stated scalar. A
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import { MONTH_SHEETS, extractTaxYearStart } from "../scenario-loader.js";
|
|
18
18
|
import { shiftMonths, periodShiftMonths } from "../period-shift.js";
|
|
19
|
+
import { taxYearFileName } from "../tax-year.js";
|
|
19
20
|
import { standardReads, multiFileOptions, SE_YEAR_END_MONTH, SBA_CLAIM_ROWS } from "../../products/se.js";
|
|
20
21
|
import { generateAdminDates, seVatPaymentDueDate, generatePayslipsCalendar } from "../generator.js";
|
|
21
22
|
import { calculateIncomeTax } from "../tax/income-tax.js";
|
|
@@ -867,6 +868,35 @@ export function calculateSeCells(book, lines, taxData, scenario = {}) {
|
|
|
867
868
|
const paidOn = (entry) => excelSerial(shiftMonths(new Date(entry.date), monthOffset));
|
|
868
869
|
const admin = buildAdmin(taxData, dateSerials);
|
|
869
870
|
|
|
871
|
+
// Business Details boxes 8, 9, and the basis period record (boxes 68, 69,
|
|
872
|
+
// 73.3): the writer's own whole-year shift, never the month-level one
|
|
873
|
+
// postings move by, and the sheet's own D64/O64/D69/O74 formulas.
|
|
874
|
+
const basisPeriodInput = (value) => (value === undefined ? SHEET_BLANK : value);
|
|
875
|
+
const basisPeriod = scenario.basis_period || {};
|
|
876
|
+
const businessDetails = {
|
|
877
|
+
D59: basisPeriodInput(basisPeriod.overlapProfitBroughtForward),
|
|
878
|
+
O59: basisPeriodInput(basisPeriod.transitionProfitBroughtForward),
|
|
879
|
+
O69: basisPeriodInput(basisPeriod.transitionProfitAccelerationAmount),
|
|
880
|
+
D74: basisPeriodInput(basisPeriod.followingPeriodProfit),
|
|
881
|
+
};
|
|
882
|
+
if (scenario.period_covered_start && scenario.period_covered_end) {
|
|
883
|
+
const naturalStartYear = parseInt(taxYearFileName(new Date(scenario.period_covered_end), "se").split("-")[1], 10);
|
|
884
|
+
const yearShift = startYear - naturalStartYear;
|
|
885
|
+
const shiftYears = (d) => shiftMonths(new Date(d), yearShift * 12);
|
|
886
|
+
businessDetails.N27 = excelSerial(shiftYears(scenario.period_covered_start));
|
|
887
|
+
businessDetails.N32 = excelSerial(shiftYears(scenario.period_covered_end));
|
|
888
|
+
} else {
|
|
889
|
+
// A flat scenario TOML (not a diya-gl book) carries no documentInfo, so
|
|
890
|
+
// the printed boxes stay the blank the template itself would show.
|
|
891
|
+
businessDetails.N27 = SHEET_BLANK;
|
|
892
|
+
businessDetails.N32 = SHEET_BLANK;
|
|
893
|
+
}
|
|
894
|
+
const b17Year = dateFromExcelSerial(admin.B17).getUTCFullYear();
|
|
895
|
+
businessDetails.D64 = b17Year === 2024 ? sheetNumber(businessDetails.D59) : 0;
|
|
896
|
+
businessDetails.O64 = b17Year < 2024 ? 0 : sheetNumber(businessDetails.O59) / Math.max(1, 2029 - b17Year);
|
|
897
|
+
businessDetails.D69 = sheetNumber(businessDetails.D59) - businessDetails.D64;
|
|
898
|
+
businessDetails.O74 = sheetNumber(businessDetails.O59) - businessDetails.O64 - sheetNumber(businessDetails.O69);
|
|
899
|
+
|
|
870
900
|
const salesMonths = journalMonths(scenario.sales, rate, SALES_ANALYSIS_COLUMNS, "a");
|
|
871
901
|
const purchasesMonths = journalMonths(scenario.purchases, rate, PURCHASES_ANALYSIS_COLUMNS);
|
|
872
902
|
const mileage = mileageMonths(scenario, taxData?.mileage);
|
|
@@ -1329,19 +1359,43 @@ export function calculateSeCells(book, lines, taxData, scenario = {}) {
|
|
|
1329
1359
|
const fromNetLoss = -seFull.O129 + seFull.D174 - seFull.O169;
|
|
1330
1360
|
return fromNetLoss < 0 ? -fromNetLoss : 0;
|
|
1331
1361
|
});
|
|
1362
|
+
// Box 68 (D197), s.7A(2)-(3): nil under s.7C(1)(c), an accounting date of
|
|
1363
|
+
// 31 March to 5 April; otherwise this period's profit (box 64 less box
|
|
1364
|
+
// 65) times the share of its days that fall in the tax year, plus the
|
|
1365
|
+
// following period's profit (Business Details!D74) times the share of
|
|
1366
|
+
// the tax year its own twelve months (EDATE) cover, less this period's
|
|
1367
|
+
// whole profit.
|
|
1368
|
+
const periodProfit = seFull.O174 - seFull.O179;
|
|
1369
|
+
seFull.D197 = carry([seFull.O174, seFull.O179], () => {
|
|
1370
|
+
const periodEnd = businessDetails.N32;
|
|
1371
|
+
if (typeof periodEnd !== "number") return 0;
|
|
1372
|
+
const marchThirtyFirst = excelSerial(new Date(Date.UTC(b17Year, 2, 31)));
|
|
1373
|
+
if (periodEnd >= marchThirtyFirst) return 0;
|
|
1374
|
+
const periodStart = businessDetails.N27;
|
|
1375
|
+
const followingPeriodEnd = excelSerial(shiftMonths(dateFromExcelSerial(periodEnd), 12));
|
|
1376
|
+
const thisPeriodShare = (periodEnd - Math.max(periodStart, admin.B4) + 1) / (periodEnd - periodStart + 1);
|
|
1377
|
+
const nextPeriodShare = (admin.B17 - periodEnd) / (followingPeriodEnd - periodEnd);
|
|
1378
|
+
return thisPeriodShare * periodProfit + nextPeriodShare * sheetNumber(businessDetails.D74) - periodProfit;
|
|
1379
|
+
});
|
|
1332
1380
|
// HMRC's SA103F working sheet for boxes 73 and 77 takes one figure, box
|
|
1333
1381
|
// 64 less box 65 plus boxes 68, 71 and 72: box 73 is that figure when it
|
|
1334
1382
|
// is positive and box 77 is its negation when it is not. The sheet reads
|
|
1335
|
-
// O174-O179+N(D197)+D210+N(O190);
|
|
1336
|
-
//
|
|
1337
|
-
|
|
1338
|
-
|
|
1383
|
+
// O174-O179+N(D197)+D210+N(O190); box 72 has no input cell on this
|
|
1384
|
+
// template, so its printed dash cell (O190) contributes nil.
|
|
1385
|
+
const adjustedProfitBeforeFloor = carry(
|
|
1386
|
+
[seFull.O174, seFull.O179, seFull.D197],
|
|
1387
|
+
() => seFull.O174 - seFull.O179 + sheetNumber(seFull.D197) + sheetNumber(seFull.D210),
|
|
1388
|
+
);
|
|
1339
1389
|
seFull.O194 = carry([adjustedProfitBeforeFloor], () => Math.max(0, adjustedProfitBeforeFloor));
|
|
1340
1390
|
seFull.O204 = pl.B11;
|
|
1341
1391
|
seFull.O199 = carry([seFull.O194], () => (sheetNumber(seFull.D179) > 0 ? 0 : Math.min(seFull.O194 + seFull.O204, lossesBroughtForward)));
|
|
1342
1392
|
seFull.O210 = carry([seFull.O194, seFull.O199], () => seFull.O194 - seFull.O199 + seFull.O204);
|
|
1343
1393
|
seFull.D219 = carry([adjustedProfitBeforeFloor], () => Math.max(0, -adjustedProfitBeforeFloor));
|
|
1344
1394
|
seFull.O224 = seFull.D219;
|
|
1395
|
+
// Box 73.3 (D201), para 73(1)-(4): the transition profit treated as
|
|
1396
|
+
// arising this year, before election (O64) plus the elected addition
|
|
1397
|
+
// (O69).
|
|
1398
|
+
seFull.D201 = sheetNumber(businessDetails.O64) + sheetNumber(businessDetails.O69);
|
|
1345
1399
|
seFull.D231 = contractorDeductions;
|
|
1346
1400
|
seFull.J280 = admin.N20;
|
|
1347
1401
|
|
|
@@ -1354,10 +1408,32 @@ export function calculateSeCells(book, lines, taxData, scenario = {}) {
|
|
|
1354
1408
|
// The deduction line already holds the contractor deductions negated, and the
|
|
1355
1409
|
// total below it adds every line up.
|
|
1356
1410
|
const contractorDeductionLine = -contractorDeductions;
|
|
1357
|
-
|
|
1358
|
-
|
|
1411
|
+
// Class 4 NIC follows the income tax charge (para 72(3) makes the spread
|
|
1412
|
+
// chargeable under ITTOIA Ch 2 Pt 2; SSCBA 1992 s.15(1)), so both bands
|
|
1413
|
+
// run over the taxable profit plus box 73.3's own spread (D201), even
|
|
1414
|
+
// though box 73.3 sits outside net income and box 76 for income tax.
|
|
1415
|
+
const transitionProfitSpread = sheetNumber(seFull.D201);
|
|
1416
|
+
const profitWithSpread = taxableProfit + transitionProfitSpread;
|
|
1417
|
+
const niLower = carry([taxableProfit, seFull.D201], () =>
|
|
1418
|
+
profitWithSpread > admin.N20 ? (Math.min(profitWithSpread, admin.N23) - admin.N20) * admin.L20 : 0,
|
|
1419
|
+
);
|
|
1420
|
+
const niUpper = carry([taxableProfit, seFull.D201], () =>
|
|
1421
|
+
profitWithSpread > admin.N23 ? (profitWithSpread - admin.N23) * admin.L23 : 0,
|
|
1359
1422
|
);
|
|
1360
|
-
const
|
|
1423
|
+
const taxableIncome = chargedOn("taxableIncome");
|
|
1424
|
+
const totalIncomeTax = chargedOn("totalIncomeTax");
|
|
1425
|
+
// Box 73.3's own top-slice charge (para 75(2)-(3)): the personal
|
|
1426
|
+
// allowance taper is left alone (the spread joins after E7, not before
|
|
1427
|
+
// E6), and the three bands run again over taxable income plus the
|
|
1428
|
+
// spread, less the tax E11 already charges on taxable income alone.
|
|
1429
|
+
const transitionProfitTax = carry([charged, seFull.D201], () => {
|
|
1430
|
+
if (transitionProfitSpread <= 0) return 0;
|
|
1431
|
+
const spread = taxableIncome + transitionProfitSpread;
|
|
1432
|
+
const basic = spread < admin.M11 ? spread * admin.N6 : admin.M11 * admin.N6;
|
|
1433
|
+
const higher = spread > admin.M11 ? (Math.min(spread, admin.N13) - admin.M11) * admin.N7 : 0;
|
|
1434
|
+
const additional = spread > admin.N13 ? (spread - admin.N13) * admin.N8 : 0;
|
|
1435
|
+
return basic + higher + additional - totalIncomeTax;
|
|
1436
|
+
});
|
|
1361
1437
|
const incomeTax = {
|
|
1362
1438
|
E5: taxableProfit,
|
|
1363
1439
|
// E6 = IF(E5<=0,0,MAX(0,Admin!N$4-MAX(0,E5-Admin!N$5)/2)): a loss year
|
|
@@ -1365,7 +1441,7 @@ export function calculateSeCells(book, lines, taxData, scenario = {}) {
|
|
|
1365
1441
|
// this at nil rather than showing the allowance unused -- the same floor
|
|
1366
1442
|
// Profit Forecast!C40 already applies below.
|
|
1367
1443
|
E6: carry([taxableProfit, charged], () => (taxableProfit <= 0 ? 0 : charged.personalAllowance)),
|
|
1368
|
-
E7:
|
|
1444
|
+
E7: taxableIncome,
|
|
1369
1445
|
C8: admin.N11,
|
|
1370
1446
|
D8: admin.N6,
|
|
1371
1447
|
E8: chargedOn("basicRateTax"),
|
|
@@ -1375,14 +1451,18 @@ export function calculateSeCells(book, lines, taxData, scenario = {}) {
|
|
|
1375
1451
|
C10: admin.N13,
|
|
1376
1452
|
D10: admin.N8,
|
|
1377
1453
|
E10: chargedOn("additionalRateTax"),
|
|
1378
|
-
E11:
|
|
1454
|
+
E11: totalIncomeTax,
|
|
1379
1455
|
E12: contractorDeductionLine,
|
|
1380
1456
|
C13: dateSerials[21],
|
|
1457
|
+
E14: transitionProfitTax,
|
|
1381
1458
|
D15: admin.L20,
|
|
1382
1459
|
E15: niLower,
|
|
1383
1460
|
D16: admin.L23,
|
|
1384
1461
|
E16: niUpper,
|
|
1385
|
-
E18: carry(
|
|
1462
|
+
E18: carry(
|
|
1463
|
+
[charged, niLower, niUpper, transitionProfitTax],
|
|
1464
|
+
() => totalIncomeTax + contractorDeductionLine + transitionProfitTax + niLower + niUpper,
|
|
1465
|
+
),
|
|
1386
1466
|
};
|
|
1387
1467
|
|
|
1388
1468
|
// ── Profit forecast ──
|
|
@@ -1424,7 +1504,11 @@ export function calculateSeCells(book, lines, taxData, scenario = {}) {
|
|
|
1424
1504
|
|
|
1425
1505
|
// ── The results, keyed the way the reconciliation reads them ──
|
|
1426
1506
|
const results = {
|
|
1427
|
-
"Business Details": {
|
|
1507
|
+
"Business Details": {
|
|
1508
|
+
C5: businessName(scenario),
|
|
1509
|
+
O50: stated(annualAdjustments.goodsAndServicesOwnUse),
|
|
1510
|
+
...businessDetails,
|
|
1511
|
+
},
|
|
1428
1512
|
"Profit & Loss Account": pl,
|
|
1429
1513
|
"Income Tax": incomeTax,
|
|
1430
1514
|
"Profit Forecast": forecast,
|
|
@@ -435,6 +435,12 @@ export function diyaGlToScenario(book, lines, product) {
|
|
|
435
435
|
business,
|
|
436
436
|
period_start_month: new Date(periodStart).getUTCMonth() + 1,
|
|
437
437
|
period_start_year: new Date(periodStart).getUTCFullYear(),
|
|
438
|
+
// The book's own accounting period, carried through unshifted: cellWrites
|
|
439
|
+
// moves it onto the target package's own year the same way it moves
|
|
440
|
+
// every other date, when it prints boxes 8 and 9 and the box 68 formula
|
|
441
|
+
// reads it back off the printed cells (Business Details!N27, N32).
|
|
442
|
+
period_covered_start: periodStart,
|
|
443
|
+
period_covered_end: book.documentInfo?.periodCoveredEnd,
|
|
438
444
|
sales: grouped.sales,
|
|
439
445
|
purchases: grouped.purchases,
|
|
440
446
|
expected,
|
|
@@ -452,8 +458,12 @@ export function diyaGlToScenario(book, lines, product) {
|
|
|
452
458
|
// them the calculator adds back only the wholly disallowable pair and
|
|
453
459
|
// charges tax on a larger profit than the package's own sheet does.
|
|
454
460
|
if (book.tax?.selfEmployment) {
|
|
455
|
-
const { allowances, adjustments, ...disallowable } = book.tax.selfEmployment;
|
|
461
|
+
const { allowances, adjustments, basisPeriod, ...disallowable } = book.tax.selfEmployment;
|
|
456
462
|
if (Object.keys(disallowable).length > 0) scenario.disallowable = disallowable;
|
|
463
|
+
// The basis period record (SA103F boxes 68, 69 and 73.3): copied
|
|
464
|
+
// verbatim, since every figure in it is a stated amount the writer puts
|
|
465
|
+
// straight onto its Business Details cell.
|
|
466
|
+
if (basisPeriod) scenario.basis_period = { ...basisPeriod };
|
|
457
467
|
// The annual SA103F figures the trader states by hand, keyed by HMRC's
|
|
458
468
|
// own field names: the writer puts each on its SE Full box and the
|
|
459
469
|
// derivation files it from there.
|
|
@@ -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.18+80457d57",
|
|
16
16
|
taxDataHash: "3221a7c2f39c",
|
|
17
17
|
reconciledCommit: "54e8731a91279975e61b1a232fbb67fda89515f0",
|
|
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: "
|
|
29
|
-
scorecard: "
|
|
28
|
+
hash: "064605ae76ed",
|
|
29
|
+
scorecard: "7982 passed, 0 warnings, 0 failed",
|
|
30
30
|
},
|
|
31
31
|
ltd: {
|
|
32
32
|
hash: "caa14632083b",
|
|
@@ -1152,6 +1152,7 @@ export function formatScenarioToml(metadata, grouped, expected) {
|
|
|
1152
1152
|
for (const [table, figures] of [
|
|
1153
1153
|
["annual_allowances", expected.annual_allowances],
|
|
1154
1154
|
["annual_adjustments", expected.annual_adjustments],
|
|
1155
|
+
["basis_period", expected.basis_period],
|
|
1155
1156
|
]) {
|
|
1156
1157
|
if (!figures) continue;
|
|
1157
1158
|
parts.push(`[${table}]`);
|
|
@@ -1711,6 +1711,15 @@ const SE_ANNUAL_ADJUSTMENT_CELLS = {
|
|
|
1711
1711
|
includedNonTaxableProfits: "D179",
|
|
1712
1712
|
accountingAdjustment: "D210",
|
|
1713
1713
|
};
|
|
1714
|
+
// The basis period record (SA103F boxes 68, 69 and 73.3), the four stated
|
|
1715
|
+
// cells app/products/se.js writes from BASIS_PERIOD_CELLS, all on
|
|
1716
|
+
// Business Details.
|
|
1717
|
+
const SE_BASIS_PERIOD_CELLS = {
|
|
1718
|
+
overlapProfitBroughtForward: "D59",
|
|
1719
|
+
transitionProfitBroughtForward: "O59",
|
|
1720
|
+
transitionProfitAccelerationAmount: "O69",
|
|
1721
|
+
followingPeriodProfit: "D74",
|
|
1722
|
+
};
|
|
1714
1723
|
|
|
1715
1724
|
// Fixedassets.xlsx!Schedule rows 115 to 119, the Structures and Buildings
|
|
1716
1725
|
// Allowance claims below the register (SA103F boxes 53 and 53.1); the same
|
|
@@ -2206,6 +2215,23 @@ function dateAt(xml, cellRef, sharedStrings) {
|
|
|
2206
2215
|
return typeof value === "number" && value > 1 ? excelSerialToDate(value) : undefined;
|
|
2207
2216
|
}
|
|
2208
2217
|
|
|
2218
|
+
// The SE package's own accounting period, read straight off the printed
|
|
2219
|
+
// boxes 8 and 9 (Business Details!N27, N32) rather than guessed from the
|
|
2220
|
+
// journal's posting dates: periodCovered()'s guess is always the twelve
|
|
2221
|
+
// whole months the package's own tab grid runs, which is right for every
|
|
2222
|
+
// other product but wrong for a book whose true accounting date sits
|
|
2223
|
+
// outside 31 March to 5 April.
|
|
2224
|
+
async function seAccountingPeriodFromSheet(set) {
|
|
2225
|
+
const hubZip = await openWorkbook(set, "Financialaccounts.xlsx");
|
|
2226
|
+
if (!hubZip) return null;
|
|
2227
|
+
const sheet = await openSheet(hubZip, "Business Details");
|
|
2228
|
+
if (!sheet) return null;
|
|
2229
|
+
const start = dateAt(sheet.xml, "N27", sheet.sharedStrings);
|
|
2230
|
+
const end = dateAt(sheet.xml, "N32", sheet.sharedStrings);
|
|
2231
|
+
if (!start || !end) return null;
|
|
2232
|
+
return { start, end };
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2209
2235
|
function assign(target, key, value) {
|
|
2210
2236
|
if (value !== undefined && value !== "" && value !== 0) target[key] = value;
|
|
2211
2237
|
}
|
|
@@ -2602,6 +2628,10 @@ export async function extractBook(set, product, lines, cellMap, options = {}) {
|
|
|
2602
2628
|
}
|
|
2603
2629
|
if (Object.keys(allowances).length > 0) selfEmployment.allowances = allowances;
|
|
2604
2630
|
if (Object.keys(adjustments).length > 0) selfEmployment.adjustments = adjustments;
|
|
2631
|
+
if (entitySheet) {
|
|
2632
|
+
const basisPeriod = readStated(entitySheet, SE_BASIS_PERIOD_CELLS);
|
|
2633
|
+
if (Object.keys(basisPeriod).length > 0) selfEmployment.basisPeriod = basisPeriod;
|
|
2634
|
+
}
|
|
2605
2635
|
}
|
|
2606
2636
|
if (Object.keys(selfEmployment).length > 0) tax.selfEmployment = selfEmployment;
|
|
2607
2637
|
}
|
|
@@ -2629,7 +2659,11 @@ export async function extractBook(set, product, lines, cellMap, options = {}) {
|
|
|
2629
2659
|
}
|
|
2630
2660
|
}
|
|
2631
2661
|
|
|
2632
|
-
|
|
2662
|
+
// SE reads its true accounting period off the printed boxes, since a book
|
|
2663
|
+
// whose dates do not run April to March still has every posting written
|
|
2664
|
+
// onto the package's own April-March tab grid.
|
|
2665
|
+
const period =
|
|
2666
|
+
(product === "se" && (await seAccountingPeriodFromSheet(set))) || periodCovered(await extractPeriodStartMonth(set, product), lines);
|
|
2633
2667
|
const book = {
|
|
2634
2668
|
documentInfo: {
|
|
2635
2669
|
entriesType: "journal",
|
package/dist/app/products/se.js
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
import { toExcelSerial } from "../lib/spreadsheet-runner.js";
|
|
10
10
|
import { ACCOUNT_ID_COLUMN } from "../lib/xlsx-exporter.js";
|
|
11
|
-
import { parseDate, MONTH_SHEETS } from "../lib/scenario-loader.js";
|
|
11
|
+
import { parseDate, MONTH_SHEETS, extractTaxYearStart } from "../lib/scenario-loader.js";
|
|
12
12
|
import { shiftMonths, periodShiftMonths } from "../lib/period-shift.js";
|
|
13
|
+
import { taxYearFileName } from "../lib/tax-year.js";
|
|
13
14
|
import {
|
|
14
15
|
monthlyPayrollBlockRow,
|
|
15
16
|
PAYE_DUE_DAY,
|
|
@@ -251,6 +252,27 @@ export const ANNUAL_ADJUSTMENT_CELLS = {
|
|
|
251
252
|
// use" cell (label at N48), read by SE Full!D169 and SE Short!D94.
|
|
252
253
|
export const GOODS_FOR_OWN_USE_CELL = "O50";
|
|
253
254
|
|
|
255
|
+
// The basis period record behind SA103F boxes 68, 69 and 73.3, on
|
|
256
|
+
// Business Details rows 57 to 75 (verified against the template): D59 box 69
|
|
257
|
+
// (overlap profit brought forward), O59 the 2023-24 transition profit not
|
|
258
|
+
// yet treated as arising, O69 the additional amount elected this year
|
|
259
|
+
// (para 73(1)), D74 the following period's profit (only read when box 9 is
|
|
260
|
+
// outside 31 March to 5 April). The sheet computes D64, O64, D69 and O74
|
|
261
|
+
// itself; no book field carries them.
|
|
262
|
+
export const BASIS_PERIOD_CELLS = {
|
|
263
|
+
overlapProfitBroughtForward: "D59",
|
|
264
|
+
transitionProfitBroughtForward: "O59",
|
|
265
|
+
transitionProfitAccelerationAmount: "O69",
|
|
266
|
+
followingPeriodProfit: "D74",
|
|
267
|
+
};
|
|
268
|
+
// Business Details boxes 8 and 9 (verified against the template: N27 merged
|
|
269
|
+
// N27:Q27, N32 merged N32:Q32, N32 today the formula =Admin!B17), the
|
|
270
|
+
// accounting period the trader's own books cover -- the sheet has nowhere
|
|
271
|
+
// else to read it from, since every posting date is written onto the
|
|
272
|
+
// package's own April-March tab grid regardless of the book's true dates.
|
|
273
|
+
export const ACCOUNTING_PERIOD_START_CELL = "N27";
|
|
274
|
+
export const ACCOUNTING_PERIOD_END_CELL = "N32";
|
|
275
|
+
|
|
254
276
|
// Fixedassets.xlsx Schedule sheet -- verified against the template:
|
|
255
277
|
// Existing assets (bought before the year start): rows 8-10 land,
|
|
256
278
|
// 14-18 plant, 22-26 fixtures, 30-34 computers, 38-54 motor. Each row:
|
|
@@ -593,6 +615,38 @@ function composeWrites(scenario, targetStartYear) {
|
|
|
593
615
|
hubWrites["SE Full"][cell] = figures[field];
|
|
594
616
|
}
|
|
595
617
|
}
|
|
618
|
+
// The basis period record (boxes 68, 69 and 73.3): stated figures, each
|
|
619
|
+
// on its own Business Details cell.
|
|
620
|
+
if (scenario.basis_period) {
|
|
621
|
+
hubWrites["Business Details"] = hubWrites["Business Details"] || {};
|
|
622
|
+
for (const [field, cell] of Object.entries(BASIS_PERIOD_CELLS)) {
|
|
623
|
+
if (scenario.basis_period[field] === undefined) continue;
|
|
624
|
+
hubWrites["Business Details"][cell] = scenario.basis_period[field];
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
// Boxes 8 and 9: the book's own accounting period, moved forward by whole
|
|
628
|
+
// years only -- the gap between the package's own start year and the
|
|
629
|
+
// year the book's period would open in on its own, never the month-level
|
|
630
|
+
// shift that moves postings onto the package's April-March tab grid.
|
|
631
|
+
if (targetStartYear && scenario.period_covered_start && scenario.period_covered_end) {
|
|
632
|
+
const periodEnd = parseDate(scenario.period_covered_end);
|
|
633
|
+
const naturalStartYear = parseInt(taxYearFileName(periodEnd, "se").split("-")[1], 10);
|
|
634
|
+
const yearShift = targetStartYear - naturalStartYear;
|
|
635
|
+
const shiftYears = (d) => shiftMonths(d, yearShift * 12);
|
|
636
|
+
const periodStart = shiftYears(parseDate(scenario.period_covered_start));
|
|
637
|
+
const periodEndShifted = shiftYears(periodEnd);
|
|
638
|
+
hubWrites["Business Details"] = hubWrites["Business Details"] || {};
|
|
639
|
+
hubWrites["Business Details"][ACCOUNTING_PERIOD_START_CELL] = toExcelSerial(
|
|
640
|
+
periodStart.getUTCFullYear(),
|
|
641
|
+
periodStart.getUTCMonth() + 1,
|
|
642
|
+
periodStart.getUTCDate(),
|
|
643
|
+
);
|
|
644
|
+
hubWrites["Business Details"][ACCOUNTING_PERIOD_END_CELL] = toExcelSerial(
|
|
645
|
+
periodEndShifted.getUTCFullYear(),
|
|
646
|
+
periodEndShifted.getUTCMonth() + 1,
|
|
647
|
+
periodEndShifted.getUTCDate(),
|
|
648
|
+
);
|
|
649
|
+
}
|
|
596
650
|
|
|
597
651
|
// Payslips.xlsx employee details
|
|
598
652
|
const payslipsWrites = {};
|
|
@@ -971,6 +1025,16 @@ export const CELL_MAP = [
|
|
|
971
1025
|
// ── Business Details ──
|
|
972
1026
|
["Business Details", "C5", "Business Name", "entityInformation.organizationIdentifier", "Business Details", 0],
|
|
973
1027
|
["Business Details", "O50", "Value of goods and services for own use (box 24)", "tax.selfEmployment.adjustments.goodsAndServicesOwnUse", "Business Details", 1],
|
|
1028
|
+
["Business Details", "N27", "Accounting period start date (box 8)", "documentInfo.periodCoveredStart", "Business Details", 1],
|
|
1029
|
+
["Business Details", "N32", "Accounting period end date (box 9)", "documentInfo.periodCoveredEnd", "Business Details", 1],
|
|
1030
|
+
["Business Details", "D59", "Overlap profit brought forward (feeds box 69)", "tax.selfEmployment.basisPeriod.overlapProfitBroughtForward", "Business Details", 1],
|
|
1031
|
+
["Business Details", "D64", "Overlap relief used this year (box 69)", "gl-cor:amount (basisPeriod.overlapReliefUsed)", "Business Details", 1],
|
|
1032
|
+
["Business Details", "D69", "Overlap profit carried forward", "gl-cor:amount (basisPeriod.overlapCarriedForward)", "Business Details", 1],
|
|
1033
|
+
["Business Details", "D74", "Following period's profit (feeds box 68)", "tax.selfEmployment.basisPeriod.followingPeriodProfit", "Business Details", 1],
|
|
1034
|
+
["Business Details", "O59", "Transition profit not yet treated as arising", "tax.selfEmployment.basisPeriod.transitionProfitBroughtForward", "Business Details", 1],
|
|
1035
|
+
["Business Details", "O64", "Transition profit treated as arising this year, before election (feeds box 73.3)", "gl-cor:amount (basisPeriod.transitionArisingBeforeElection)", "Business Details", 1],
|
|
1036
|
+
["Business Details", "O69", "Additional transition profit elected this year (feeds box 73.3)", "tax.selfEmployment.basisPeriod.transitionProfitAccelerationAmount", "Business Details", 1],
|
|
1037
|
+
["Business Details", "O74", "Transition profit carried forward to next year", "gl-cor:amount (basisPeriod.transitionCarriedForward)", "Business Details", 1],
|
|
974
1038
|
// ── Profit & Loss Account ──
|
|
975
1039
|
["Profit & Loss Account", "B5", "Product A sales (code a)", "accounts.sales.4000", "Profit & Loss Account", 1],
|
|
976
1040
|
["Profit & Loss Account", "B6", "Product B sales (code b)", "accounts.sales.4001", "Profit & Loss Account", 1],
|
|
@@ -1012,6 +1076,7 @@ export const CELL_MAP = [
|
|
|
1012
1076
|
[TAX_SHEET, "E10", "Tax at Additional Rate (45%)", "tax.incomeTax.additionalRate", "Income Tax Calculation", 1],
|
|
1013
1077
|
[TAX_SHEET, "E11", "**Total Income Tax**", "tax.incomeTax (total)", "Income Tax Calculation", 0],
|
|
1014
1078
|
[TAX_SHEET, "E12", "Less: CIS Deducted", "diya-gl:cisDeduction (total)", "Income Tax Calculation", 1],
|
|
1079
|
+
[TAX_SHEET, "E14", "Income Tax on transition profit (box 73.3), top-sliced", "gl-cor:amount (transitionProfitTax)", "Income Tax Calculation", 1],
|
|
1015
1080
|
[TAX_SHEET, "E15", "NI Class 4 (lower band)", "tax.nationalInsurance.class4MainRate", "Income Tax Calculation", 1],
|
|
1016
1081
|
[TAX_SHEET, "E16", "NI Class 4 (upper band)", "tax.nationalInsurance.class4UpperRate","Income Tax Calculation", 1],
|
|
1017
1082
|
[TAX_SHEET, "E18", "**Total Tax + NI**", "gl-cor:taxAmount (totalTaxNI)", "Income Tax Calculation", 0],
|
|
@@ -1119,8 +1184,10 @@ export const CELL_MAP = [
|
|
|
1119
1184
|
["SE Full", "O169", "**Total deductions from net profit (box 63)**", "gl-cor:amount (sa103f.totalDeductions)", "Self Assessment (SA103F)", 0],
|
|
1120
1185
|
["SE Full", "O174", "**Net business profit for tax purposes (box 64)**", "gl-cor:amount (sa103f.taxableProfit)", "Self Assessment (SA103F)", 0],
|
|
1121
1186
|
["SE Full", "O179", "Net business loss for tax purposes (box 65)", "gl-cor:amount (sa103f.taxableLoss)", "Self Assessment (SA103F)", 1],
|
|
1187
|
+
["SE Full", "D197", "Adjustment where accounting period was not 12 months long (box 68)", "gl-cor:amount (sa103f.basisAdjustment)", "Self Assessment (SA103F)", 1],
|
|
1122
1188
|
["SE Full", "D210", "Adjustment for change of accounting practice (box 71)", "tax.selfEmployment.adjustments.accountingAdjustment", "Self Assessment (SA103F)", 1],
|
|
1123
1189
|
["SE Full", "O194", "**Adjusted profit (box 73)**", "gl-cor:amount (sa103f.adjustedProfit)", "Self Assessment (SA103F)", 0],
|
|
1190
|
+
["SE Full", "D201", "Spread of the transition profit treated as arising this year (box 73.3)", "gl-cor:amount (sa103f.transitionProfitSpread)", "Self Assessment (SA103F)", 1],
|
|
1124
1191
|
["SE Full", "O199", "Loss brought forward set against this year (box 74)", "gl-cor:amount (sa103f.lossBroughtForward)", "Self Assessment (SA103F)", 1],
|
|
1125
1192
|
["SE Full", "O204", "Other business income not in boxes 15, 16 or 60 (box 75)", "gl-cor:amount (sa103f.otherBusinessIncome)", "Self Assessment (SA103F)", 1],
|
|
1126
1193
|
["SE Full", "O210", "**Total taxable profits from this business (box 76)**", "gl-cor:amount (sa103f.profitForTax)", "Self Assessment (SA103F)", 0],
|
|
@@ -1892,6 +1959,8 @@ export function unitFor(sheet, cell) {
|
|
|
1892
1959
|
}
|
|
1893
1960
|
switch (sheet) {
|
|
1894
1961
|
case "Business Details":
|
|
1962
|
+
// Boxes 8 and 9, the book's own accounting period dates.
|
|
1963
|
+
if (cell === ACCOUNTING_PERIOD_START_CELL || cell === ACCOUNTING_PERIOD_END_CELL) return "date";
|
|
1895
1964
|
return "text";
|
|
1896
1965
|
case "Admin":
|
|
1897
1966
|
if (ADMIN_TAX_YEAR_LABEL_CELLS.has(cell)) return "text";
|
|
@@ -2461,16 +2530,45 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
|
|
|
2461
2530
|
const tax = results[TAX_SHEET];
|
|
2462
2531
|
const profit = tax.E5 || 0;
|
|
2463
2532
|
const expectedTax = calculateExpectedTax(profit, taxData);
|
|
2533
|
+
// Class 4 NIC follows the income tax charge onto box 73.3's own spread
|
|
2534
|
+
// (FA 2022 Sch 1 para 72(3), SSCBA 1992 s.15(1)), even though the spread
|
|
2535
|
+
// sits outside box 76 and net income for income tax purposes.
|
|
2536
|
+
const transitionProfitSpread = num(results["SE Full"]?.D201);
|
|
2537
|
+
const expectedTaxWithSpread = calculateExpectedTax(profit + transitionProfitSpread, taxData);
|
|
2538
|
+
// Box 73.3's own top-slice charge (para 75(2)-(3)): the three bands run
|
|
2539
|
+
// again over taxable income (E7) plus the spread, less the tax already
|
|
2540
|
+
// charged on taxable income alone -- the personal allowance taper is
|
|
2541
|
+
// left untouched, since the spread never reaches E5/E6.
|
|
2542
|
+
const expectedTransitionProfitTax = (() => {
|
|
2543
|
+
if (transitionProfitSpread <= 0) return 0;
|
|
2544
|
+
const it = taxData.income_tax;
|
|
2545
|
+
const spreadTop = (tax.E7 || 0) + transitionProfitSpread;
|
|
2546
|
+
const basic = spreadTop < it.basic_band_end ? spreadTop * it.basic_rate : it.basic_band_end * it.basic_rate;
|
|
2547
|
+
const higher = spreadTop > it.basic_band_end ? (Math.min(spreadTop, it.higher_band_end) - it.basic_band_end) * it.higher_rate : 0;
|
|
2548
|
+
const additional = spreadTop > it.higher_band_end ? (spreadTop - it.higher_band_end) * it.additional_rate : 0;
|
|
2549
|
+
return basic + higher + additional - expectedTax.income_tax;
|
|
2550
|
+
})();
|
|
2464
2551
|
|
|
2465
2552
|
check("Income Tax", tax.E11 || 0, expectedTax.income_tax, 0.01);
|
|
2466
|
-
check("
|
|
2553
|
+
check("Income Tax on transition profit (box 73.3)", tax.E14 || 0, expectedTransitionProfitTax, 0.01);
|
|
2554
|
+
check("NI Class 4 (lower)", tax.E15 || 0, expectedTaxWithSpread.ni_class4_lower, 0.01);
|
|
2555
|
+
check("NI Class 4 (upper)", tax.E16 || 0, expectedTaxWithSpread.ni_class4_upper, 0.01);
|
|
2467
2556
|
// E18 is the sheet's own SUM(E11:E17), and E12 (the CIS already deducted,
|
|
2468
2557
|
// carried negative) sits inside that range, so the sheet's total is the
|
|
2469
2558
|
// computed tax and NI less what the contractors have already paid over.
|
|
2470
2559
|
const cisSuffered = Object.values(expected.sales || {})
|
|
2471
2560
|
.flat()
|
|
2472
2561
|
.reduce((total, tx) => total + (tx.cis_deduction || 0), 0);
|
|
2473
|
-
check(
|
|
2562
|
+
check(
|
|
2563
|
+
"Total Tax + NI, less the CIS already deducted",
|
|
2564
|
+
tax.E18 || 0,
|
|
2565
|
+
expectedTax.income_tax +
|
|
2566
|
+
expectedTransitionProfitTax +
|
|
2567
|
+
expectedTaxWithSpread.ni_class4_lower +
|
|
2568
|
+
expectedTaxWithSpread.ni_class4_upper -
|
|
2569
|
+
cisSuffered,
|
|
2570
|
+
0.01,
|
|
2571
|
+
);
|
|
2474
2572
|
|
|
2475
2573
|
// The allowance the sheet hands out, not the headline one. Above 100,000
|
|
2476
2574
|
// of profit it falls by a pound for every two, and reaches nil at 125,140.
|
|
@@ -2499,7 +2597,11 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
|
|
|
2499
2597
|
// the sheet's own total is SUM(E11:E17), so the deduction line is added,
|
|
2500
2598
|
// not subtracted. Every fixture so far carries nil CIS, which is why
|
|
2501
2599
|
// subtracting it here passed.
|
|
2502
|
-
check(
|
|
2600
|
+
check(
|
|
2601
|
+
"Tax: Total = IT + transition profit tax + CIS deduction line + NI",
|
|
2602
|
+
tax.E18,
|
|
2603
|
+
(tax.E11 || 0) + (tax.E14 || 0) + (tax.E12 || 0) + (tax.E15 || 0) + (tax.E16 || 0),
|
|
2604
|
+
);
|
|
2503
2605
|
|
|
2504
2606
|
// SA103S cross-check (6g)
|
|
2505
2607
|
const seShort = results["SE Short"];
|
|
@@ -2782,16 +2884,16 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
|
|
|
2782
2884
|
);
|
|
2783
2885
|
// HMRC's working sheet for boxes 73 and 77 takes one figure, box 64 less
|
|
2784
2886
|
// box 65 plus boxes 68, 71 and 72; box 73 is it when positive, box 77 its
|
|
2785
|
-
// negation when not.
|
|
2786
|
-
//
|
|
2787
|
-
const adjustedProfitBeforeFloor = num(seFull.O174) - num(seFull.O179) + num(seFull.D210);
|
|
2887
|
+
// negation when not. Box 72 (O190) prints a dash on this template and
|
|
2888
|
+
// contributes nil.
|
|
2889
|
+
const adjustedProfitBeforeFloor = num(seFull.O174) - num(seFull.O179) + num(seFull.D197) + num(seFull.D210);
|
|
2788
2890
|
check(
|
|
2789
|
-
"SA103F box 73 adjusted profit (O194) = box 64 less box 65 plus
|
|
2891
|
+
"SA103F box 73 adjusted profit (O194) = box 64 less box 65 plus boxes 68 and 71, floored at nil",
|
|
2790
2892
|
num(seFull.O194),
|
|
2791
2893
|
Math.max(0, adjustedProfitBeforeFloor),
|
|
2792
2894
|
);
|
|
2793
2895
|
check(
|
|
2794
|
-
"SA103F box 77 adjusted loss (D219) = box 65 less box 64 and
|
|
2896
|
+
"SA103F box 77 adjusted loss (D219) = box 65 less box 64, 68 and 71, floored at nil",
|
|
2795
2897
|
num(seFull.D219),
|
|
2796
2898
|
Math.max(0, -adjustedProfitBeforeFloor),
|
|
2797
2899
|
);
|
|
@@ -2801,6 +2903,114 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
|
|
|
2801
2903
|
num(seFull.O194) - num(seFull.O199) + num(seFull.O204),
|
|
2802
2904
|
);
|
|
2803
2905
|
|
|
2906
|
+
// The basis period record (boxes 68, 69 and 73.3), anchored on the
|
|
2907
|
+
// fixture's own basisPeriod figures and the package's tax year --
|
|
2908
|
+
// independent of the Business Details cells the s.7A formula itself
|
|
2909
|
+
// reads, so a self-consistent-but-wrong sheet fails here.
|
|
2910
|
+
const businessDetails = results["Business Details"] || {};
|
|
2911
|
+
const basisPeriod = expected.basis_period || {};
|
|
2912
|
+
const bpOverlapBroughtForward = basisPeriod.overlapProfitBroughtForward || 0;
|
|
2913
|
+
const bpTransitionBroughtForward = basisPeriod.transitionProfitBroughtForward || 0;
|
|
2914
|
+
const bpTransitionAcceleration = basisPeriod.transitionProfitAccelerationAmount || 0;
|
|
2915
|
+
const bpFollowingPeriodProfit = basisPeriod.followingPeriodProfit || 0;
|
|
2916
|
+
check(
|
|
2917
|
+
"Business Details!D59 overlap profit brought forward = the fixture's own figure",
|
|
2918
|
+
num(businessDetails.D59),
|
|
2919
|
+
bpOverlapBroughtForward,
|
|
2920
|
+
);
|
|
2921
|
+
check(
|
|
2922
|
+
"Business Details!O59 transition profit not yet treated as arising = the fixture's own figure",
|
|
2923
|
+
num(businessDetails.O59),
|
|
2924
|
+
bpTransitionBroughtForward,
|
|
2925
|
+
);
|
|
2926
|
+
check(
|
|
2927
|
+
"Business Details!O69 additional transition profit elected this year = the fixture's own figure",
|
|
2928
|
+
num(businessDetails.O69),
|
|
2929
|
+
bpTransitionAcceleration,
|
|
2930
|
+
);
|
|
2931
|
+
check("Business Details!D74 following period's profit = the fixture's own figure", num(businessDetails.D74), bpFollowingPeriodProfit);
|
|
2932
|
+
|
|
2933
|
+
// The tax year this figure is computed for -- calculateSeCells' own
|
|
2934
|
+
// source (taxData.tax_year.start, falling back to the scenario's own
|
|
2935
|
+
// dates), not packageYearEnd: a caller checking a single computed
|
|
2936
|
+
// result rather than a generated package directory passes no year end.
|
|
2937
|
+
const basisPeriodStartYear = taxData?.tax_year?.start
|
|
2938
|
+
? new Date(taxData.tax_year.start).getUTCFullYear()
|
|
2939
|
+
: extractTaxYearStart(expected);
|
|
2940
|
+
const packageTaxYearEnd = basisPeriodStartYear ? basisPeriodStartYear + 1 : null;
|
|
2941
|
+
// s.7A(2)(a): overlap relief is used in full on the 2023-24 return only.
|
|
2942
|
+
const expectedOverlapReliefUsed = packageTaxYearEnd === 2024 ? bpOverlapBroughtForward : 0;
|
|
2943
|
+
check(
|
|
2944
|
+
"Business Details!D64 overlap relief used this year = the 2023-24 return uses it in full, else nil",
|
|
2945
|
+
num(businessDetails.D64),
|
|
2946
|
+
expectedOverlapReliefUsed,
|
|
2947
|
+
);
|
|
2948
|
+
check(
|
|
2949
|
+
"Business Details!D69 overlap profit carried forward = box 69's input less box 69's relief used",
|
|
2950
|
+
num(businessDetails.D69),
|
|
2951
|
+
bpOverlapBroughtForward - expectedOverlapReliefUsed,
|
|
2952
|
+
);
|
|
2953
|
+
// para 72(3)-(4): 1/(years left, this one included) of the untaxed
|
|
2954
|
+
// transition balance, nil before 2023-24.
|
|
2955
|
+
const expectedTransitionArising =
|
|
2956
|
+
packageTaxYearEnd && packageTaxYearEnd >= 2024 ? bpTransitionBroughtForward / Math.max(1, 2029 - packageTaxYearEnd) : 0;
|
|
2957
|
+
check(
|
|
2958
|
+
"Business Details!O64 transition profit treated as arising this year, before election = the fixture's balance divided by the years left",
|
|
2959
|
+
num(businessDetails.O64),
|
|
2960
|
+
expectedTransitionArising,
|
|
2961
|
+
);
|
|
2962
|
+
check(
|
|
2963
|
+
"Business Details!O74 transition profit carried forward = the fixture's balance less what this year and the election take",
|
|
2964
|
+
num(businessDetails.O74),
|
|
2965
|
+
bpTransitionBroughtForward - expectedTransitionArising - bpTransitionAcceleration,
|
|
2966
|
+
);
|
|
2967
|
+
// para 73(3): an election cannot exceed the untaxed balance left once
|
|
2968
|
+
// this year's automatic slice is taken out of it -- checked against the
|
|
2969
|
+
// sheet's own O69, not the fixture's, so a sheet that carries an
|
|
2970
|
+
// over-large election fails here even when the fixture itself is fine.
|
|
2971
|
+
check(
|
|
2972
|
+
"Business Details!O69 election does not exceed the untaxed transition balance (para 73(3))",
|
|
2973
|
+
Math.max(0, num(businessDetails.O69) - (bpTransitionBroughtForward - expectedTransitionArising)),
|
|
2974
|
+
0,
|
|
2975
|
+
);
|
|
2976
|
+
// Box 73.3 (D201): this year's automatic slice plus the election.
|
|
2977
|
+
check(
|
|
2978
|
+
"SA103F box 73.3 spread of the transition profit treated as arising (D201) = box O64 plus box O69",
|
|
2979
|
+
num(seFull.D201),
|
|
2980
|
+
expectedTransitionArising + bpTransitionAcceleration,
|
|
2981
|
+
);
|
|
2982
|
+
|
|
2983
|
+
// Box 68 (D197), s.7A: nil under s.7C for an accounting date of 31
|
|
2984
|
+
// March to 5 April. Both fixtures ship one, so the independent figure
|
|
2985
|
+
// is nil on every package year -- a book whose accounting date sits
|
|
2986
|
+
// outside that window is covered by the calc-tier basis period test.
|
|
2987
|
+
const periodProfit = num(seFull.O174) - num(seFull.O179);
|
|
2988
|
+
const periodCoveredStart = expected.period_covered_start ? parseDate(expected.period_covered_start) : null;
|
|
2989
|
+
const periodCoveredEnd = expected.period_covered_end ? parseDate(expected.period_covered_end) : null;
|
|
2990
|
+
let expectedBasisAdjustment = 0;
|
|
2991
|
+
if (periodCoveredStart && periodCoveredEnd && basisPeriodStartYear) {
|
|
2992
|
+
const naturalStartYear = parseInt(taxYearFileName(periodCoveredEnd, "se").split("-")[1], 10);
|
|
2993
|
+
const shiftYears = (d) => shiftMonths(d, (basisPeriodStartYear - naturalStartYear) * 12);
|
|
2994
|
+
const shiftedStart = shiftYears(periodCoveredStart);
|
|
2995
|
+
const shiftedEnd = shiftYears(periodCoveredEnd);
|
|
2996
|
+
const marchThirtyFirst = new Date(Date.UTC(basisPeriodStartYear + 1, 2, 31));
|
|
2997
|
+
if (shiftedEnd < marchThirtyFirst) {
|
|
2998
|
+
const dayMs = 24 * 60 * 60 * 1000;
|
|
2999
|
+
const taxYearStart = new Date(Date.UTC(basisPeriodStartYear, 3, 6));
|
|
3000
|
+
const taxYearEnd = new Date(Date.UTC(basisPeriodStartYear + 1, 3, 5));
|
|
3001
|
+
const followingPeriodEnd = shiftMonths(shiftedEnd, 12);
|
|
3002
|
+
const thisPeriodShare = (shiftedEnd - Math.max(shiftedStart, taxYearStart) + dayMs) / (shiftedEnd - shiftedStart + dayMs);
|
|
3003
|
+
const nextPeriodShare = (taxYearEnd - shiftedEnd) / (followingPeriodEnd - shiftedEnd);
|
|
3004
|
+
expectedBasisAdjustment = thisPeriodShare * periodProfit + nextPeriodShare * bpFollowingPeriodProfit - periodProfit;
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
check(
|
|
3008
|
+
"SA103F box 68 adjustment where the accounting period was not 12 months long (D197) = the s.7A apportionment",
|
|
3009
|
+
num(seFull.D197),
|
|
3010
|
+
expectedBasisAdjustment,
|
|
3011
|
+
0.01,
|
|
3012
|
+
);
|
|
3013
|
+
|
|
2804
3014
|
// The capital allowance boxes have no profit and loss source: they read
|
|
2805
3015
|
// the fixed asset schedule across the cross-file external link.
|
|
2806
3016
|
const returnSchedule = results["Fixedassets.xlsx!Schedule"];
|
|
@@ -2931,19 +3141,19 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
|
|
|
2931
3141
|
num(seFull.D174) - num(seFull.O122) - num(seFull.O160),
|
|
2932
3142
|
ownUseStated,
|
|
2933
3143
|
);
|
|
2934
|
-
// The same two boxes anchored on the
|
|
2935
|
-
// whose box 71 cell and adjusted figures agree with each other
|
|
2936
|
-
// with the book fails here.
|
|
3144
|
+
// The same two boxes anchored on the figures the book states, so a
|
|
3145
|
+
// sheet whose box 71 cell and adjusted figures agree with each other
|
|
3146
|
+
// but not with the book fails here.
|
|
2937
3147
|
const statedAccountingAdjustment = statedAdjustments.accountingAdjustment || 0;
|
|
2938
3148
|
check(
|
|
2939
|
-
"SA103F box 73 adjusted profit (O194) = box 64 less box 65 plus
|
|
3149
|
+
"SA103F box 73 adjusted profit (O194) = box 64 less box 65 plus boxes 68 and 71 the book states, floored at nil",
|
|
2940
3150
|
num(seFull.O194),
|
|
2941
|
-
Math.max(0, num(seFull.O174) - num(seFull.O179) + statedAccountingAdjustment),
|
|
3151
|
+
Math.max(0, num(seFull.O174) - num(seFull.O179) + expectedBasisAdjustment + statedAccountingAdjustment),
|
|
2942
3152
|
);
|
|
2943
3153
|
check(
|
|
2944
|
-
"SA103F box 77 adjusted loss (D219) = box 65 less box 64 and
|
|
3154
|
+
"SA103F box 77 adjusted loss (D219) = box 65 less box 64, 68 and 71 the book states, floored at nil",
|
|
2945
3155
|
num(seFull.D219),
|
|
2946
|
-
Math.max(0, num(seFull.O179) - num(seFull.O174) - statedAccountingAdjustment),
|
|
3156
|
+
Math.max(0, num(seFull.O179) - num(seFull.O174) - expectedBasisAdjustment - statedAccountingAdjustment),
|
|
2947
3157
|
);
|
|
2948
3158
|
|
|
2949
3159
|
if (sa103s) {
|
|
@@ -897,6 +897,28 @@
|
|
|
897
897
|
}
|
|
898
898
|
}
|
|
899
899
|
},
|
|
900
|
+
"basisPeriod": {
|
|
901
|
+
"type": "object",
|
|
902
|
+
"description": "The basis period record behind SA103F boxes 68, 69 and 73.3, for a book whose accounting date is not 31 March to 5 April. overlapProfitBroughtForward is deducted in full on the 2023-24 return (box 69). transitionProfitBroughtForward is the 2023-24 transition profit left after every amount already treated as arising; each year 1/(years left, this one included) of it is treated as arising (box 73.3), unless transitionProfitAccelerationAmount elects an additional amount that year. followingPeriodProfit is the profit (a loss negative) of the accounting period after this one, read only when box 9 is outside 31 March to 5 April, and feeds the box 68 apportionment.",
|
|
903
|
+
"additionalProperties": false,
|
|
904
|
+
"properties": {
|
|
905
|
+
"overlapProfitBroughtForward": {
|
|
906
|
+
"type": "number",
|
|
907
|
+
"minimum": 0
|
|
908
|
+
},
|
|
909
|
+
"transitionProfitBroughtForward": {
|
|
910
|
+
"type": "number",
|
|
911
|
+
"minimum": 0
|
|
912
|
+
},
|
|
913
|
+
"transitionProfitAccelerationAmount": {
|
|
914
|
+
"type": "number",
|
|
915
|
+
"minimum": 0
|
|
916
|
+
},
|
|
917
|
+
"followingPeriodProfit": {
|
|
918
|
+
"type": "number"
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
},
|
|
900
922
|
"costOfGoods": {
|
|
901
923
|
"type": "number",
|
|
902
924
|
"minimum": 0,
|