@diy-accounting-uk/diya-gl 1.2.4 → 1.2.5
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/ltd.json +1 -1
- package/dist/app/data/ltd-2020.toml +7 -2
- package/dist/app/data/ltd-2021.toml +7 -2
- package/dist/app/data/ltd-2022.toml +7 -2
- package/dist/app/data/ltd-2023.toml +7 -2
- package/dist/app/data/ltd-2024.toml +7 -2
- package/dist/app/data/ltd-2025.toml +7 -2
- package/dist/app/data/ltd-2026.toml +7 -2
- package/dist/app/data/ltd-2027.toml +7 -2
- package/dist/app/data/releases.json +26 -0
- package/dist/app/lib/calculators/ltd.js +25 -21
- package/dist/app/lib/generator.js +18 -24
- package/dist/app/lib/provenance-data.js +3 -3
- package/dist/app/lib/tax/corporation-tax.js +63 -35
- package/dist/app/products/ltd.js +82 -51
- package/package.json +1 -1
|
@@ -485,7 +485,7 @@
|
|
|
485
485
|
{
|
|
486
486
|
"cell": "Financialaccounts.xlsx!CT600!AA126",
|
|
487
487
|
"test": "lt",
|
|
488
|
-
"against": "Financialaccounts.xlsx!Admin!
|
|
488
|
+
"against": "Financialaccounts.xlsx!Admin!R6"
|
|
489
489
|
}
|
|
490
490
|
],
|
|
491
491
|
"why": "the form ticks 329 for marginal relief or the small profits rate; the sheet prints neither tick"
|
|
@@ -17,10 +17,15 @@ main_rate_limit = 0
|
|
|
17
17
|
marginal_relief_fraction = 0
|
|
18
18
|
|
|
19
19
|
# Financial year 2019, the year before this one. An accounting period that
|
|
20
|
-
# reaches back over 1 April charges its earlier days at that year's
|
|
21
|
-
#
|
|
20
|
+
# reaches back over 1 April charges its earlier days at that year's own rates,
|
|
21
|
+
# limits and relief fraction, which the working sheet's first tax row carries
|
|
22
|
+
# in Admin P6, R6, S6, T6 and U6.
|
|
22
23
|
[corporation_tax_previous_financial_year]
|
|
24
|
+
main_rate = 0.19
|
|
23
25
|
small_profits_rate = 0.19
|
|
26
|
+
small_profits_limit = 0
|
|
27
|
+
main_rate_limit = 0
|
|
28
|
+
marginal_relief_fraction = 0
|
|
24
29
|
|
|
25
30
|
[capital_allowances]
|
|
26
31
|
annual_investment_allowance = 1.00
|
|
@@ -17,10 +17,15 @@ main_rate_limit = 0
|
|
|
17
17
|
marginal_relief_fraction = 0
|
|
18
18
|
|
|
19
19
|
# Financial year 2020, the year before this one. An accounting period that
|
|
20
|
-
# reaches back over 1 April charges its earlier days at that year's
|
|
21
|
-
#
|
|
20
|
+
# reaches back over 1 April charges its earlier days at that year's own rates,
|
|
21
|
+
# limits and relief fraction, which the working sheet's first tax row carries
|
|
22
|
+
# in Admin P6, R6, S6, T6 and U6.
|
|
22
23
|
[corporation_tax_previous_financial_year]
|
|
24
|
+
main_rate = 0.19
|
|
23
25
|
small_profits_rate = 0.19
|
|
26
|
+
small_profits_limit = 0
|
|
27
|
+
main_rate_limit = 0
|
|
28
|
+
marginal_relief_fraction = 0
|
|
24
29
|
|
|
25
30
|
[capital_allowances]
|
|
26
31
|
annual_investment_allowance = 1.00
|
|
@@ -17,10 +17,15 @@ main_rate_limit = 0
|
|
|
17
17
|
marginal_relief_fraction = 0
|
|
18
18
|
|
|
19
19
|
# Financial year 2021, the year before this one. An accounting period that
|
|
20
|
-
# reaches back over 1 April charges its earlier days at that year's
|
|
21
|
-
#
|
|
20
|
+
# reaches back over 1 April charges its earlier days at that year's own rates,
|
|
21
|
+
# limits and relief fraction, which the working sheet's first tax row carries
|
|
22
|
+
# in Admin P6, R6, S6, T6 and U6.
|
|
22
23
|
[corporation_tax_previous_financial_year]
|
|
24
|
+
main_rate = 0.19
|
|
23
25
|
small_profits_rate = 0.19
|
|
26
|
+
small_profits_limit = 0
|
|
27
|
+
main_rate_limit = 0
|
|
28
|
+
marginal_relief_fraction = 0
|
|
24
29
|
|
|
25
30
|
[capital_allowances]
|
|
26
31
|
annual_investment_allowance = 1.00
|
|
@@ -17,10 +17,15 @@ main_rate_limit = 250000
|
|
|
17
17
|
marginal_relief_fraction = 0.015
|
|
18
18
|
|
|
19
19
|
# Financial year 2022, the year before this one. An accounting period that
|
|
20
|
-
# reaches back over 1 April charges its earlier days at that year's
|
|
21
|
-
#
|
|
20
|
+
# reaches back over 1 April charges its earlier days at that year's own rates,
|
|
21
|
+
# limits and relief fraction, which the working sheet's first tax row carries
|
|
22
|
+
# in Admin P6, R6, S6, T6 and U6.
|
|
22
23
|
[corporation_tax_previous_financial_year]
|
|
24
|
+
main_rate = 0.19
|
|
23
25
|
small_profits_rate = 0.19
|
|
26
|
+
small_profits_limit = 0
|
|
27
|
+
main_rate_limit = 0
|
|
28
|
+
marginal_relief_fraction = 0
|
|
24
29
|
|
|
25
30
|
[capital_allowances]
|
|
26
31
|
annual_investment_allowance = 1.00
|
|
@@ -17,10 +17,15 @@ main_rate_limit = 250000
|
|
|
17
17
|
marginal_relief_fraction = 0.015
|
|
18
18
|
|
|
19
19
|
# Financial year 2023, the year before this one. An accounting period that
|
|
20
|
-
# reaches back over 1 April charges its earlier days at that year's
|
|
21
|
-
#
|
|
20
|
+
# reaches back over 1 April charges its earlier days at that year's own rates,
|
|
21
|
+
# limits and relief fraction, which the working sheet's first tax row carries
|
|
22
|
+
# in Admin P6, R6, S6, T6 and U6.
|
|
22
23
|
[corporation_tax_previous_financial_year]
|
|
24
|
+
main_rate = 0.25
|
|
23
25
|
small_profits_rate = 0.19
|
|
26
|
+
small_profits_limit = 50000
|
|
27
|
+
main_rate_limit = 250000
|
|
28
|
+
marginal_relief_fraction = 0.015
|
|
24
29
|
|
|
25
30
|
[capital_allowances]
|
|
26
31
|
annual_investment_allowance = 1.00
|
|
@@ -17,10 +17,15 @@ main_rate_limit = 250000
|
|
|
17
17
|
marginal_relief_fraction = 0.015
|
|
18
18
|
|
|
19
19
|
# Financial year 2024, the year before this one. An accounting period that
|
|
20
|
-
# reaches back over 1 April charges its earlier days at that year's
|
|
21
|
-
#
|
|
20
|
+
# reaches back over 1 April charges its earlier days at that year's own rates,
|
|
21
|
+
# limits and relief fraction, which the working sheet's first tax row carries
|
|
22
|
+
# in Admin P6, R6, S6, T6 and U6.
|
|
22
23
|
[corporation_tax_previous_financial_year]
|
|
24
|
+
main_rate = 0.25
|
|
23
25
|
small_profits_rate = 0.19
|
|
26
|
+
small_profits_limit = 50000
|
|
27
|
+
main_rate_limit = 250000
|
|
28
|
+
marginal_relief_fraction = 0.015
|
|
24
29
|
|
|
25
30
|
[capital_allowances]
|
|
26
31
|
annual_investment_allowance = 1.00
|
|
@@ -21,10 +21,15 @@ main_rate_limit = 250000
|
|
|
21
21
|
marginal_relief_fraction = 0.015 # 3/200
|
|
22
22
|
|
|
23
23
|
# Financial year 2025, the year before this one. An accounting period that
|
|
24
|
-
# reaches back over 1 April charges its earlier days at that year's
|
|
25
|
-
#
|
|
24
|
+
# reaches back over 1 April charges its earlier days at that year's own rates,
|
|
25
|
+
# limits and relief fraction, which the working sheet's first tax row carries
|
|
26
|
+
# in Admin P6, R6, S6, T6 and U6.
|
|
26
27
|
[corporation_tax_previous_financial_year]
|
|
28
|
+
main_rate = 0.25
|
|
27
29
|
small_profits_rate = 0.19
|
|
30
|
+
small_profits_limit = 50000
|
|
31
|
+
main_rate_limit = 250000
|
|
32
|
+
marginal_relief_fraction = 0.015
|
|
28
33
|
|
|
29
34
|
[capital_allowances]
|
|
30
35
|
annual_investment_allowance = 1.00
|
|
@@ -19,10 +19,15 @@ main_rate_limit = 250000
|
|
|
19
19
|
marginal_relief_fraction = 0.015 # 3/200
|
|
20
20
|
|
|
21
21
|
# Financial year 2026, the year before this one. An accounting period that
|
|
22
|
-
# reaches back over 1 April charges its earlier days at that year's
|
|
23
|
-
#
|
|
22
|
+
# reaches back over 1 April charges its earlier days at that year's own rates,
|
|
23
|
+
# limits and relief fraction, which the working sheet's first tax row carries
|
|
24
|
+
# in Admin P6, R6, S6, T6 and U6.
|
|
24
25
|
[corporation_tax_previous_financial_year]
|
|
26
|
+
main_rate = 0.25
|
|
25
27
|
small_profits_rate = 0.19
|
|
28
|
+
small_profits_limit = 50000
|
|
29
|
+
main_rate_limit = 250000
|
|
30
|
+
marginal_relief_fraction = 0.015
|
|
26
31
|
|
|
27
32
|
[capital_allowances]
|
|
28
33
|
annual_investment_allowance = 1.00
|
|
@@ -337,6 +337,32 @@
|
|
|
337
337
|
"scorecard": "101279 passed, 0 warnings, 0 failed"
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
"tag": "diya-gl-v1.2.4",
|
|
343
|
+
"date": "2026-09-11",
|
|
344
|
+
"formatVersion": "diya-gl/1",
|
|
345
|
+
"engineVersion": "1.2.4+f5483192",
|
|
346
|
+
"taxDataHash": "9b8294b452fb",
|
|
347
|
+
"reconciledCommit": "320b4b8a1585e16fa170c2d312c14df38d0bcf5b",
|
|
348
|
+
"templates": {
|
|
349
|
+
"bst": {
|
|
350
|
+
"hash": "0d003e10e00c",
|
|
351
|
+
"scorecard": "810 passed, 0 warnings, 0 failed"
|
|
352
|
+
},
|
|
353
|
+
"taxi": {
|
|
354
|
+
"hash": "1ff69654d64d",
|
|
355
|
+
"scorecard": "686 passed, 0 warnings, 0 failed"
|
|
356
|
+
},
|
|
357
|
+
"se": {
|
|
358
|
+
"hash": "e78c4aab0660",
|
|
359
|
+
"scorecard": "7679 passed, 0 warnings, 0 failed"
|
|
360
|
+
},
|
|
361
|
+
"ltd": {
|
|
362
|
+
"hash": "4dc8932fa566",
|
|
363
|
+
"scorecard": "101279 passed, 0 warnings, 0 failed"
|
|
364
|
+
}
|
|
365
|
+
}
|
|
340
366
|
}
|
|
341
367
|
]
|
|
342
368
|
}
|
|
@@ -25,12 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
import { toExcelSerial } from "../spreadsheet-runner.js";
|
|
27
27
|
import { BANK_ACCOUNT_FILES, BANK_LAYOUTS, OPENING_FIXED_ASSET_COLUMNS, isLtdOpeningBankLine } from "../ltd-layout.js";
|
|
28
|
-
import {
|
|
29
|
-
apportionCorporationTax,
|
|
30
|
-
financialYearsInPeriod,
|
|
31
|
-
financialYearNumber,
|
|
32
|
-
smallProfitsRatePercentFor,
|
|
33
|
-
} from "../tax/corporation-tax.js";
|
|
28
|
+
import { apportionCorporationTax, financialYearsInPeriod, financialYearNumber, financialYearRatesFor } from "../tax/corporation-tax.js";
|
|
34
29
|
import { calculateCapitalAllowances } from "../tax/capital-allowances.js";
|
|
35
30
|
import {
|
|
36
31
|
monthlyPayrollBlockRow,
|
|
@@ -878,7 +873,6 @@ export function calculateLtdCells(book, lines, taxData, scenario) {
|
|
|
878
873
|
// at 31 March: L6 is the period start, N6 the earlier of the first financial
|
|
879
874
|
// year's end and the period end, L7 the day after and N7 the period end.
|
|
880
875
|
function buildAdmin(taxData, period, associatedCompanies) {
|
|
881
|
-
const corporationTax = taxData.corporation_tax || {};
|
|
882
876
|
const capitalAllowances = taxData.capital_allowances || {};
|
|
883
877
|
const depreciation = taxData.depreciation || {};
|
|
884
878
|
const mileage = taxData.mileage || {};
|
|
@@ -889,19 +883,27 @@ function buildAdmin(taxData, period, associatedCompanies) {
|
|
|
889
883
|
const financialYears = financialYearsInPeriod(period.start, period.yearEnd);
|
|
890
884
|
const fileFinancialYear = financialYearNumber(period.yearEnd);
|
|
891
885
|
|
|
886
|
+
// What each tax row's own financial year charges by. The two rows differ
|
|
887
|
+
// whenever the period reaches back over 1 April into a year that charged
|
|
888
|
+
// differently.
|
|
889
|
+
const rowRates = financialYears.years.map((financialYear) =>
|
|
890
|
+
financialYearRatesFor(taxData, financialYear.year, fileFinancialYear, financialYear.days),
|
|
891
|
+
);
|
|
892
|
+
|
|
892
893
|
return {
|
|
893
894
|
B9: periodStartSerial,
|
|
894
895
|
B32: yearEndSerial,
|
|
895
896
|
F21: yearEndSerial,
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
897
|
+
P6: rowRates[0].smallProfitsRatePercent,
|
|
898
|
+
R6: rowRates[0].mainRatePercent,
|
|
899
|
+
S6: rowRates[0].marginalReliefFraction,
|
|
900
|
+
T6: rowRates[0].lowerLimit,
|
|
901
|
+
U6: rowRates[0].upperLimit,
|
|
902
|
+
P7: rowRates[1].smallProfitsRatePercent,
|
|
903
|
+
R7: rowRates[1].mainRatePercent,
|
|
904
|
+
S7: rowRates[1].marginalReliefFraction,
|
|
905
|
+
T7: rowRates[1].lowerLimit,
|
|
906
|
+
U7: rowRates[1].upperLimit,
|
|
905
907
|
P14: associatedCompanies,
|
|
906
908
|
G5: Math.round(capitalAllowances.annual_investment_allowance * 100),
|
|
907
909
|
G6: Math.round(capitalAllowances.writing_down_allowance_main * 100),
|
|
@@ -1804,11 +1806,13 @@ function buildCorporationTax({ admin, trialBalance, blocks, publishedPl, openAcc
|
|
|
1804
1806
|
|
|
1805
1807
|
const financialYears = financialYearsInPeriod(fromSerial(admin.L6), fromSerial(admin.N7));
|
|
1806
1808
|
const charge = apportionCorporationTax(sheet.K28, financialYears.years, financialYears.totalDays, {
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1809
|
+
perYear: [6, 7].map((adminRow) => ({
|
|
1810
|
+
smallProfitsRatePercent: admin[`P${adminRow}`],
|
|
1811
|
+
mainRatePercent: admin[`R${adminRow}`],
|
|
1812
|
+
marginalReliefFraction: admin[`S${adminRow}`],
|
|
1813
|
+
lowerLimit: admin[`T${adminRow}`],
|
|
1814
|
+
upperLimit: admin[`U${adminRow}`],
|
|
1815
|
+
})),
|
|
1812
1816
|
associatedCompanies: admin.P14,
|
|
1813
1817
|
frankedInvestmentIncome: sheet.K29,
|
|
1814
1818
|
});
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
payrollYearStart,
|
|
22
22
|
} from "./payslips-layout.js";
|
|
23
23
|
import { rollPayslipsCachedDateChain } from "./payslips-date-chain.js";
|
|
24
|
-
import {
|
|
24
|
+
import { financialYearRatesFor, financialYearNumber } from "./tax/corporation-tax.js";
|
|
25
25
|
|
|
26
26
|
// ── Deterministic zip output ───────────────────────────────────────────────
|
|
27
27
|
//
|
|
@@ -499,37 +499,31 @@ export function buildLtdCellEdits(taxData, yearEndSerial) {
|
|
|
499
499
|
const ca = taxData.capital_allowances;
|
|
500
500
|
const dep = taxData.depreciation;
|
|
501
501
|
const mil = taxData.mileage;
|
|
502
|
-
const ct = taxData.corporation_tax;
|
|
503
502
|
|
|
504
503
|
const numericEdits = {};
|
|
505
504
|
|
|
506
505
|
// Year-end date — the ONE date cell. All others are formula-driven from F21.
|
|
507
506
|
numericEdits.F21 = yearEndSerial;
|
|
508
507
|
|
|
509
|
-
// Corporation Tax rates
|
|
510
|
-
//
|
|
511
|
-
//
|
|
512
|
-
//
|
|
508
|
+
// Corporation Tax rates. Rows 6 and 7 are the two tax rows' own financial
|
|
509
|
+
// years, and each carries every figure its year charges by: the small
|
|
510
|
+
// profits rate and the main rate as whole-number percentages, the marginal
|
|
511
|
+
// relief fraction, and the two limits the relief tapers between. The rows
|
|
512
|
+
// differ whenever the period reaches back over 1 April into a year that
|
|
513
|
+
// charged differently.
|
|
513
514
|
const financialYearRows = ltdAdminFinancialYearRows(yearEndSerial);
|
|
514
515
|
const fileFinancialYear = financialYearNumber(fromExcelSerial(yearEndSerial));
|
|
515
|
-
|
|
516
|
-
taxData,
|
|
517
|
-
financialYearRows.
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
);
|
|
527
|
-
numericEdits.P8 = Math.round(ct.main_rate * 100);
|
|
528
|
-
|
|
529
|
-
// Marginal relief: the fraction and the two profit limits it tapers between
|
|
530
|
-
numericEdits.P9 = ct.marginal_relief_fraction;
|
|
531
|
-
numericEdits.P12 = ct.small_profits_limit;
|
|
532
|
-
numericEdits.P13 = ct.main_rate_limit;
|
|
516
|
+
const rowRates = [
|
|
517
|
+
financialYearRatesFor(taxData, financialYearRows.K6, fileFinancialYear, financialYearRows.N6 - financialYearRows.L6 + 1),
|
|
518
|
+
financialYearRatesFor(taxData, financialYearRows.K7, fileFinancialYear, financialYearRows.N7 - financialYearRows.L7 + 1),
|
|
519
|
+
];
|
|
520
|
+
for (const [index, row] of [6, 7].entries()) {
|
|
521
|
+
numericEdits[`P${row}`] = rowRates[index].smallProfitsRatePercent;
|
|
522
|
+
numericEdits[`R${row}`] = rowRates[index].mainRatePercent;
|
|
523
|
+
numericEdits[`S${row}`] = rowRates[index].marginalReliefFraction;
|
|
524
|
+
numericEdits[`T${row}`] = rowRates[index].lowerLimit;
|
|
525
|
+
numericEdits[`U${row}`] = rowRates[index].upperLimit;
|
|
526
|
+
}
|
|
533
527
|
|
|
534
528
|
// Capital allowances (stored as whole-number percentages)
|
|
535
529
|
numericEdits.G5 = Math.round(ca.annual_investment_allowance * 100);
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
export const PROVENANCE_DATA = {
|
|
14
14
|
formatVersion: "diya-gl/1",
|
|
15
|
-
engineVersion: "1.2.
|
|
16
|
-
taxDataHash: "
|
|
15
|
+
engineVersion: "1.2.5+adcbb86f",
|
|
16
|
+
taxDataHash: "84585062cad8",
|
|
17
17
|
reconciledCommit: "320b4b8a1585e16fa170c2d312c14df38d0bcf5b",
|
|
18
18
|
templates: {
|
|
19
19
|
bst: {
|
|
@@ -29,7 +29,7 @@ export const PROVENANCE_DATA = {
|
|
|
29
29
|
scorecard: "7679 passed, 0 warnings, 0 failed",
|
|
30
30
|
},
|
|
31
31
|
ltd: {
|
|
32
|
-
hash: "
|
|
32
|
+
hash: "81ffdc956e5a",
|
|
33
33
|
scorecard: "101279 passed, 0 warnings, 0 failed",
|
|
34
34
|
},
|
|
35
35
|
},
|
|
@@ -92,56 +92,87 @@ export function financialYearsInPeriod(periodStart, periodEnd) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
|
-
* The
|
|
96
|
-
*
|
|
95
|
+
* The five figures a financial year charges corporation tax by, as the
|
|
96
|
+
* working sheet's rate rows carry them: whole-number percentages for the two
|
|
97
|
+
* rates, a fraction for the relief and pounds for the two limits.
|
|
98
|
+
*
|
|
99
|
+
* @param {Object} table - a corporation_tax table out of an ltd-<FY>.toml
|
|
100
|
+
* @returns {{ smallProfitsRatePercent: number, mainRatePercent: number, marginalReliefFraction: number, lowerLimit: number, upperLimit: number }}
|
|
101
|
+
*/
|
|
102
|
+
function ratesFromTable(table) {
|
|
103
|
+
return {
|
|
104
|
+
smallProfitsRatePercent: Math.round(table.small_profits_rate * 100),
|
|
105
|
+
mainRatePercent: Math.round(table.main_rate * 100),
|
|
106
|
+
marginalReliefFraction: table.marginal_relief_fraction,
|
|
107
|
+
lowerLimit: table.small_profits_limit,
|
|
108
|
+
upperLimit: table.main_rate_limit,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// The fields a tax year file's previous financial year table has to state, so
|
|
113
|
+
// a row charging that year has every figure it needs from the one file.
|
|
114
|
+
const PREVIOUS_FINANCIAL_YEAR_FIELDS = [
|
|
115
|
+
"small_profits_rate",
|
|
116
|
+
"main_rate",
|
|
117
|
+
"marginal_relief_fraction",
|
|
118
|
+
"small_profits_limit",
|
|
119
|
+
"main_rate_limit",
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* What one of the working sheet's two tax rows charges: its own financial
|
|
124
|
+
* year's small profits rate, main rate, relief fraction and two limits, the
|
|
125
|
+
* figures Admin rows 6 and 7 carry a cell each for.
|
|
97
126
|
*
|
|
98
127
|
* A package is generated from one app/data/ltd-<FY>.toml, named for the
|
|
99
128
|
* financial year its own year end falls in. An accounting period that reaches
|
|
100
|
-
* back over 1 April charges its earlier days at the year before's
|
|
101
|
-
* that file states
|
|
102
|
-
* no days charges nothing, so it takes the file's own
|
|
103
|
-
* no file has.
|
|
104
|
-
*
|
|
105
|
-
* Only the small profits rate has a cell per financial year. The main rate,
|
|
106
|
-
* the relief fraction and the two limits are one figure each for the whole
|
|
107
|
-
* period, so a period straddling a change in those still charges both rows at
|
|
108
|
-
* the year end's figures.
|
|
129
|
+
* back over 1 April charges its earlier days at the year before's figures,
|
|
130
|
+
* and that file states them as corporation_tax_previous_financial_year. A row
|
|
131
|
+
* with no days charges nothing, so it takes the file's own figures rather
|
|
132
|
+
* than figures no file has.
|
|
109
133
|
*
|
|
110
134
|
* @param {Object} taxData - the parsed ltd-<FY>.toml
|
|
111
135
|
* @param {number} rowFinancialYear - the financial year this row charges
|
|
112
136
|
* @param {number} fileFinancialYear - the financial year taxData is named for
|
|
113
137
|
* @param {number} rowDays - days the row charges
|
|
114
|
-
* @returns {number
|
|
138
|
+
* @returns {{ smallProfitsRatePercent: number, mainRatePercent: number, marginalReliefFraction: number, lowerLimit: number, upperLimit: number }}
|
|
115
139
|
*/
|
|
116
|
-
export function
|
|
140
|
+
export function financialYearRatesFor(taxData, rowFinancialYear, fileFinancialYear, rowDays) {
|
|
117
141
|
// Tax data with no corporation tax table at all reaches here from callers
|
|
118
142
|
// that want the period dates off the Admin sheet and nothing else. Every
|
|
119
|
-
// other rate cell comes out NaN for them, so
|
|
120
|
-
// demanding
|
|
121
|
-
if (!taxData.corporation_tax)
|
|
122
|
-
|
|
123
|
-
|
|
143
|
+
// other rate cell comes out NaN for them, so these do too rather than
|
|
144
|
+
// demanding rates the caller never had.
|
|
145
|
+
if (!taxData.corporation_tax) {
|
|
146
|
+
return { smallProfitsRatePercent: NaN, mainRatePercent: NaN, marginalReliefFraction: NaN, lowerLimit: NaN, upperLimit: NaN };
|
|
147
|
+
}
|
|
148
|
+
if (rowDays <= 0 || rowFinancialYear === fileFinancialYear) return ratesFromTable(taxData.corporation_tax);
|
|
124
149
|
if (rowFinancialYear !== fileFinancialYear - 1) {
|
|
125
150
|
throw new Error(
|
|
126
|
-
`financial year ${rowFinancialYear} is neither ${fileFinancialYear} nor the year before it, so no tax year file states
|
|
151
|
+
`financial year ${rowFinancialYear} is neither ${fileFinancialYear} nor the year before it, so no tax year file states what it charged`,
|
|
127
152
|
);
|
|
128
153
|
}
|
|
129
154
|
const previous = taxData.corporation_tax_previous_financial_year;
|
|
130
|
-
|
|
155
|
+
const missing = PREVIOUS_FINANCIAL_YEAR_FIELDS.filter((field) => previous?.[field] === undefined);
|
|
156
|
+
if (missing.length > 0) {
|
|
131
157
|
throw new Error(
|
|
132
|
-
`the financial year ${fileFinancialYear} tax data states no corporation_tax_previous_financial_year.
|
|
158
|
+
`the financial year ${fileFinancialYear} tax data states no corporation_tax_previous_financial_year.${missing.join(", no corporation_tax_previous_financial_year.")}, so the ${rowDays} days in financial year ${rowFinancialYear} have no figures of their own`,
|
|
133
159
|
);
|
|
134
160
|
}
|
|
135
|
-
return
|
|
161
|
+
return ratesFromTable(previous);
|
|
136
162
|
}
|
|
137
163
|
|
|
138
164
|
/**
|
|
139
165
|
* Corporation tax charged financial year by financial year, the way the
|
|
140
166
|
* working sheet charges it: the chargeable profit is split across the two
|
|
141
|
-
* years by day count, each share meets its own year's
|
|
142
|
-
* relief applies to a share that sits between
|
|
143
|
-
* limits apportioned by the same day count and then divided by one plus
|
|
144
|
-
* number of associated companies.
|
|
167
|
+
* years by day count, each share meets its own year's rates, and marginal
|
|
168
|
+
* relief applies to a share that sits between that year's two limits, with
|
|
169
|
+
* both limits apportioned by the same day count and then divided by one plus
|
|
170
|
+
* the number of associated companies.
|
|
171
|
+
*
|
|
172
|
+
* Every figure the charge turns on belongs to the row's own financial year,
|
|
173
|
+
* so a period straddling 1 April into a year that charged differently -- as
|
|
174
|
+
* FY2022 and FY2023 did, 19% flat against 25% with relief between 50,000 and
|
|
175
|
+
* 250,000 -- charges each part at what that part's year charged.
|
|
145
176
|
*
|
|
146
177
|
* Which band a row falls in is decided on its share of augmented profits --
|
|
147
178
|
* the chargeable profit plus the exempt distributions received
|
|
@@ -154,11 +185,7 @@ export function smallProfitsRatePercentFor(taxData, rowFinancialYear, fileFinanc
|
|
|
154
185
|
* @param {Array<{ year: number, days: number }>} financialYears - two entries, the second possibly nil
|
|
155
186
|
* @param {number} totalDays
|
|
156
187
|
* @param {Object} rates
|
|
157
|
-
* @param {number
|
|
158
|
-
* @param {number} rates.mainRatePercent
|
|
159
|
-
* @param {number} rates.marginalReliefFraction
|
|
160
|
-
* @param {number} rates.lowerLimit
|
|
161
|
-
* @param {number} rates.upperLimit
|
|
188
|
+
* @param {Array<{ smallProfitsRatePercent: number, mainRatePercent: number, marginalReliefFraction: number, lowerLimit: number, upperLimit: number }>} rates.perYear - what each financial year charges, in the same order as financialYears
|
|
162
189
|
* @param {number} [rates.associatedCompanies] - default 0
|
|
163
190
|
* @param {number} [rates.frankedInvestmentIncome] - default 0
|
|
164
191
|
* @returns {{ rows: Array<{ year, days, profitShare, augmentedShare, ratePercent, taxBeforeRelief, marginalRelief, tax }>, tax: number, marginalRelief: number, taxBeforeRelief: number, augmentedProfits: number }}
|
|
@@ -167,16 +194,17 @@ export function apportionCorporationTax(profitChargeable, financialYears, totalD
|
|
|
167
194
|
const associatedDivisor = 1 + (rates.associatedCompanies ?? 0);
|
|
168
195
|
const augmentedProfits = profitChargeable + (rates.frankedInvestmentIncome ?? 0);
|
|
169
196
|
const rows = financialYears.map((financialYear, index) => {
|
|
197
|
+
const yearRates = rates.perYear[index];
|
|
170
198
|
const share = totalDays > 0 ? financialYear.days / totalDays : 0;
|
|
171
199
|
const profitShare = profitChargeable > 0 ? profitChargeable * share : 0;
|
|
172
200
|
const augmentedShare = augmentedProfits * share;
|
|
173
|
-
const lowerLimit = (
|
|
174
|
-
const upperLimit = (
|
|
175
|
-
const ratePercent = augmentedShare <= lowerLimit ?
|
|
201
|
+
const lowerLimit = (yearRates.lowerLimit * share) / associatedDivisor;
|
|
202
|
+
const upperLimit = (yearRates.upperLimit * share) / associatedDivisor;
|
|
203
|
+
const ratePercent = augmentedShare <= lowerLimit ? yearRates.smallProfitsRatePercent : yearRates.mainRatePercent;
|
|
176
204
|
const taxBeforeRelief = (profitShare * ratePercent) / 100;
|
|
177
205
|
const marginalRelief =
|
|
178
206
|
augmentedShare > lowerLimit && augmentedShare < upperLimit
|
|
179
|
-
? ((upperLimit - augmentedShare) * profitShare *
|
|
207
|
+
? ((upperLimit - augmentedShare) * profitShare * yearRates.marginalReliefFraction) / augmentedShare
|
|
180
208
|
: 0;
|
|
181
209
|
return {
|
|
182
210
|
year: financialYear.year,
|
package/dist/app/products/ltd.js
CHANGED
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
payslipsWagesPaidCell,
|
|
36
36
|
} from "../lib/payslips-layout.js";
|
|
37
37
|
import { BANK_ACCOUNT_FILES, BANK_LAYOUTS, OPENING_FIXED_ASSET_COLUMNS, isLtdOpeningBankLine } from "../lib/ltd-layout.js";
|
|
38
|
-
import {
|
|
38
|
+
import { apportionCorporationTax, financialYearRatesFor } from "../lib/tax/corporation-tax.js";
|
|
39
39
|
import {
|
|
40
40
|
buildCategoryNetting,
|
|
41
41
|
buildProfitBridge,
|
|
@@ -1475,14 +1475,21 @@ const PL_ROW_CAPTIONS = {
|
|
|
1475
1475
|
// Admin cells the generator injects from the tax-year TOML, and the TOML
|
|
1476
1476
|
// path each one carries. Whole-number percentages where the sheet holds a
|
|
1477
1477
|
// percentage, fractions where it holds a fraction.
|
|
1478
|
-
//
|
|
1479
|
-
//
|
|
1480
|
-
//
|
|
1478
|
+
// Rows 6 and 7 of the Admin rate table are not here: every corporation tax
|
|
1479
|
+
// figure on them belongs to the financial year that row names, so each is
|
|
1480
|
+
// checked against its own year rather than against one figure from the file.
|
|
1481
|
+
// Row 6 is the working sheet's first tax row and row 7 its second; each
|
|
1482
|
+
// column holds one of the five figures its financial year charges by.
|
|
1483
|
+
const ADMIN_FINANCIAL_YEAR_ROWS = [6, 7];
|
|
1484
|
+
const ADMIN_RATE_COLUMNS = {
|
|
1485
|
+
smallProfitsRatePercent: "P",
|
|
1486
|
+
mainRatePercent: "R",
|
|
1487
|
+
marginalReliefFraction: "S",
|
|
1488
|
+
lowerLimit: "T",
|
|
1489
|
+
upperLimit: "U",
|
|
1490
|
+
};
|
|
1491
|
+
|
|
1481
1492
|
const ADMIN_TAX_DATA_CELLS = [
|
|
1482
|
-
["P8", "corporation tax main rate", (t) => Math.round(t.corporation_tax.main_rate * 100)],
|
|
1483
|
-
["P9", "marginal relief fraction", (t) => t.corporation_tax.marginal_relief_fraction],
|
|
1484
|
-
["P12", "marginal relief lower limit", (t) => t.corporation_tax.small_profits_limit],
|
|
1485
|
-
["P13", "marginal relief upper limit", (t) => t.corporation_tax.main_rate_limit],
|
|
1486
1493
|
["G5", "annual investment allowance", (t) => Math.round(t.capital_allowances.annual_investment_allowance * 100)],
|
|
1487
1494
|
["G7", "annual investment allowance (new assets)", (t) => Math.round(t.capital_allowances.annual_investment_allowance * 100)],
|
|
1488
1495
|
["G6", "writing down allowance", (t) => Math.round(t.capital_allowances.writing_down_allowance_main * 100)],
|
|
@@ -1638,10 +1645,11 @@ export function standardReads() {
|
|
|
1638
1645
|
for (const cell of CT600_CELLS) add("CT600", cell);
|
|
1639
1646
|
|
|
1640
1647
|
for (const [cell] of ADMIN_TAX_DATA_CELLS) add("Admin", cell);
|
|
1641
|
-
// The two tax rows'
|
|
1642
|
-
// because each row takes its own financial year's
|
|
1643
|
-
|
|
1644
|
-
|
|
1648
|
+
// The two tax rows' own rates, limits and relief fraction, which are not in
|
|
1649
|
+
// that table because each row takes its own financial year's figures.
|
|
1650
|
+
for (const row of ADMIN_FINANCIAL_YEAR_ROWS) {
|
|
1651
|
+
for (const column of Object.values(ADMIN_RATE_COLUMNS)) add("Admin", `${column}${row}`);
|
|
1652
|
+
}
|
|
1645
1653
|
add("Admin", "P14");
|
|
1646
1654
|
add("Admin", "F21");
|
|
1647
1655
|
add("Admin", "B9");
|
|
@@ -2070,7 +2078,7 @@ const DATE_CELLS = {
|
|
|
2070
2078
|
|
|
2071
2079
|
// Cells that hold a rate or a percentage, whichever way the sheet writes it.
|
|
2072
2080
|
const RATE_CELLS = {
|
|
2073
|
-
Admin: ["P6", "P7", "
|
|
2081
|
+
Admin: ["P6", "P7", "R6", "R7", "S6", "S7", "M19", "M21", "G5", "G6", "G7", "G8", "G15", "G16", "G17", "G18", "G19", "O16", "O17"],
|
|
2074
2082
|
CorporationTax: ["G33", "G34"],
|
|
2075
2083
|
CT600: ["AA126", "W137"],
|
|
2076
2084
|
PubNotes: ["B27", "B28", "B29", "B30", "B31"],
|
|
@@ -4206,83 +4214,106 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
|
|
|
4206
4214
|
check("CT: charge for the year = the two tax rows", num(ct.K35), num(ct.I33) + num(ct.I34));
|
|
4207
4215
|
|
|
4208
4216
|
// Which band a row falls in is decided on augmented profits: the
|
|
4209
|
-
// chargeable profit plus the exempt distributions received.
|
|
4210
|
-
//
|
|
4211
|
-
//
|
|
4212
|
-
// tapers the gap up to the upper
|
|
4217
|
+
// chargeable profit plus the exempt distributions received. The row's
|
|
4218
|
+
// own financial year states both limits, which are shared out over the
|
|
4219
|
+
// period the same way the profit is and then divided by one plus the
|
|
4220
|
+
// number of associated companies. Relief tapers the gap up to the upper
|
|
4221
|
+
// limit at that year's own fraction and is then scaled by the row's
|
|
4213
4222
|
// taxable share over its augmented share, so distributions take relief
|
|
4214
4223
|
// away without ever being taxed.
|
|
4215
4224
|
const admin = results.Admin;
|
|
4216
4225
|
if (admin && days > 0) {
|
|
4217
4226
|
const associatedDivisor = 1 + num(admin.P14);
|
|
4218
|
-
const
|
|
4219
|
-
|
|
4227
|
+
const sheetRates = (adminRow) =>
|
|
4228
|
+
Object.fromEntries(Object.entries(ADMIN_RATE_COLUMNS).map(([figure, column]) => [figure, num(admin[`${column}${adminRow}`])]));
|
|
4229
|
+
const lowerLimit = (rates, rowDays) => (rates.lowerLimit * rowDays) / days / associatedDivisor;
|
|
4230
|
+
const upperLimit = (rates, rowDays) => (rates.upperLimit * rowDays) / days / associatedDivisor;
|
|
4220
4231
|
const augmentedShare = (rowDays) => (num(ct.K30) * rowDays) / days;
|
|
4221
|
-
const reliefFor = (rowProfit, rowDays) => {
|
|
4232
|
+
const reliefFor = (rates, rowProfit, rowDays) => {
|
|
4222
4233
|
const augmented = augmentedShare(rowDays);
|
|
4223
|
-
if (!(augmented > lowerLimit(rowDays) && augmented < upperLimit(rowDays))) return 0;
|
|
4224
|
-
return ((upperLimit(rowDays) - augmented) * rowProfit *
|
|
4234
|
+
if (!(augmented > lowerLimit(rates, rowDays) && augmented < upperLimit(rates, rowDays))) return 0;
|
|
4235
|
+
return ((upperLimit(rates, rowDays) - augmented) * rowProfit * rates.marginalReliefFraction) / augmented;
|
|
4225
4236
|
};
|
|
4226
|
-
const rateFor = (
|
|
4237
|
+
const rateFor = (rates, rowDays) =>
|
|
4238
|
+
augmentedShare(rowDays) <= lowerLimit(rates, rowDays) ? rates.smallProfitsRatePercent : rates.mainRatePercent;
|
|
4227
4239
|
|
|
4228
4240
|
check(
|
|
4229
4241
|
"CT: first tax row rate = the rate its share of the augmented profits falls in",
|
|
4230
4242
|
num(ct.G33),
|
|
4231
|
-
rateFor(
|
|
4243
|
+
rateFor(sheetRates(6), num(ct.A33)),
|
|
4232
4244
|
0,
|
|
4233
4245
|
);
|
|
4234
4246
|
check(
|
|
4235
4247
|
"CT: second tax row rate = the rate its share of the augmented profits falls in",
|
|
4236
4248
|
num(ct.G34),
|
|
4237
|
-
rateFor(
|
|
4249
|
+
rateFor(sheetRates(7), num(ct.A34)),
|
|
4238
4250
|
0,
|
|
4239
4251
|
);
|
|
4240
4252
|
check(
|
|
4241
4253
|
"CT: first tax row marginal relief = its share of the augmented profits against its share of the limits",
|
|
4242
4254
|
num(ct.L33),
|
|
4243
|
-
reliefFor(num(ct.F33), num(ct.A33)),
|
|
4255
|
+
reliefFor(sheetRates(6), num(ct.F33), num(ct.A33)),
|
|
4244
4256
|
);
|
|
4245
4257
|
check(
|
|
4246
4258
|
"CT: second tax row marginal relief = its share of the augmented profits against its share of the limits",
|
|
4247
4259
|
num(ct.L34),
|
|
4248
|
-
reliefFor(num(ct.F34), num(ct.A34)),
|
|
4260
|
+
reliefFor(sheetRates(7), num(ct.F34), num(ct.A34)),
|
|
4249
4261
|
);
|
|
4250
4262
|
|
|
4251
|
-
//
|
|
4252
|
-
//
|
|
4253
|
-
//
|
|
4254
|
-
//
|
|
4263
|
+
// Every figure a row charges by is its own financial year's. The year
|
|
4264
|
+
// the package was generated for supplies its own; a row whose year is
|
|
4265
|
+
// the one before it takes that year's figures from the same file, and
|
|
4266
|
+
// a row with no days charges nothing either way.
|
|
4255
4267
|
// The package is generated from the tax year file named for the
|
|
4256
4268
|
// financial year its year end falls in: the second row's year when
|
|
4257
4269
|
// the period straddles 1 April, and the first row's when the year
|
|
4258
4270
|
// ends on 31 March and the period lies wholly inside one.
|
|
4259
4271
|
const packageFinancialYear = num(ct.A34) > 0 ? num(admin.K7) : num(admin.K6);
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
0,
|
|
4272
|
+
const ROW_NAMES = { 6: "first", 7: "second" };
|
|
4273
|
+
const FIGURE_NAMES = {
|
|
4274
|
+
smallProfitsRatePercent: "small profits rate",
|
|
4275
|
+
mainRatePercent: "main rate",
|
|
4276
|
+
marginalReliefFraction: "marginal relief fraction",
|
|
4277
|
+
lowerLimit: "marginal relief lower limit",
|
|
4278
|
+
upperLimit: "marginal relief upper limit",
|
|
4279
|
+
};
|
|
4280
|
+
const statedRates = ADMIN_FINANCIAL_YEAR_ROWS.map((adminRow, index) =>
|
|
4281
|
+
financialYearRatesFor(taxData, num(admin[`K${adminRow}`]), packageFinancialYear, num(index === 0 ? ct.A33 : ct.A34)),
|
|
4271
4282
|
);
|
|
4283
|
+
for (const [index, adminRow] of ADMIN_FINANCIAL_YEAR_ROWS.entries()) {
|
|
4284
|
+
for (const [figure, column] of Object.entries(ADMIN_RATE_COLUMNS)) {
|
|
4285
|
+
check(
|
|
4286
|
+
`CT: ${ROW_NAMES[adminRow]} tax row ${FIGURE_NAMES[figure]} = what its own financial year charged`,
|
|
4287
|
+
num(admin[`${column}${adminRow}`]),
|
|
4288
|
+
statedRates[index][figure],
|
|
4289
|
+
0,
|
|
4290
|
+
);
|
|
4291
|
+
}
|
|
4292
|
+
}
|
|
4293
|
+
|
|
4294
|
+
// The charge the period's profit actually bears, against the
|
|
4295
|
+
// statutory computation worked from the tax year file rather than
|
|
4296
|
+
// from the rate cells the sheet charged by: each financial year's
|
|
4297
|
+
// share of the profit at the figures that year's own table states.
|
|
4298
|
+
const statutory = apportionCorporationTax(
|
|
4299
|
+
profit,
|
|
4300
|
+
[
|
|
4301
|
+
{ year: num(admin.K6), days: num(ct.A33) },
|
|
4302
|
+
{ year: num(admin.K7), days: num(ct.A34) },
|
|
4303
|
+
],
|
|
4304
|
+
days,
|
|
4305
|
+
{
|
|
4306
|
+
perYear: statedRates,
|
|
4307
|
+
associatedCompanies: expected.business?.associated_companies ?? 0,
|
|
4308
|
+
frankedInvestmentIncome: expected.business?.franked_investment_income ?? 0,
|
|
4309
|
+
},
|
|
4310
|
+
).tax;
|
|
4311
|
+
check("CT: charge for the year = the statutory computation with marginal relief", num(ct.K35), statutory, 1);
|
|
4272
4312
|
}
|
|
4273
4313
|
|
|
4274
4314
|
// Tax outstanding is the charge less any income tax already deducted
|
|
4275
4315
|
// at source from bank interest received.
|
|
4276
4316
|
check("CT: Tax outstanding = CT less tax deducted at source", num(ct.K39), num(ct.K35) - num(ct.K37));
|
|
4277
|
-
|
|
4278
|
-
// The charge the period's profit actually bears, against the statutory
|
|
4279
|
-
// computation worked independently of the sheet.
|
|
4280
|
-
const statutory = calculateCorporationTax(profit, {
|
|
4281
|
-
...taxData.corporation_tax,
|
|
4282
|
-
associated_companies: expected.business?.associated_companies ?? 0,
|
|
4283
|
-
franked_investment_income: expected.business?.franked_investment_income ?? 0,
|
|
4284
|
-
}).corporationTax;
|
|
4285
|
-
check("CT: charge for the year = the statutory computation with marginal relief", num(ct.K35), statutory, 1);
|
|
4286
4317
|
}
|
|
4287
4318
|
}
|
|
4288
4319
|
|