@diy-accounting-uk/diya-gl 1.2.5 → 1.2.6
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/releases.json +26 -0
- package/dist/app/lib/anchors/se.js +2 -0
- package/dist/app/lib/calculators/se-derivations.js +3 -1
- package/dist/app/lib/calculators/se.js +159 -68
- package/dist/app/lib/provenance-data.js +6 -6
- package/dist/app/lib/scenario-extractor.js +12 -1
- package/dist/app/products/se.js +78 -13
- package/package.json +1 -1
|
@@ -363,6 +363,32 @@
|
|
|
363
363
|
"scorecard": "101279 passed, 0 warnings, 0 failed"
|
|
364
364
|
}
|
|
365
365
|
}
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
"tag": "diya-gl-v1.2.5",
|
|
369
|
+
"date": "2026-09-11",
|
|
370
|
+
"formatVersion": "diya-gl/1",
|
|
371
|
+
"engineVersion": "1.2.5+adcbb86f",
|
|
372
|
+
"taxDataHash": "84585062cad8",
|
|
373
|
+
"reconciledCommit": "320b4b8a1585e16fa170c2d312c14df38d0bcf5b",
|
|
374
|
+
"templates": {
|
|
375
|
+
"bst": {
|
|
376
|
+
"hash": "0d003e10e00c",
|
|
377
|
+
"scorecard": "810 passed, 0 warnings, 0 failed"
|
|
378
|
+
},
|
|
379
|
+
"taxi": {
|
|
380
|
+
"hash": "1ff69654d64d",
|
|
381
|
+
"scorecard": "686 passed, 0 warnings, 0 failed"
|
|
382
|
+
},
|
|
383
|
+
"se": {
|
|
384
|
+
"hash": "e78c4aab0660",
|
|
385
|
+
"scorecard": "7679 passed, 0 warnings, 0 failed"
|
|
386
|
+
},
|
|
387
|
+
"ltd": {
|
|
388
|
+
"hash": "81ffdc956e5a",
|
|
389
|
+
"scorecard": "101279 passed, 0 warnings, 0 failed"
|
|
390
|
+
}
|
|
391
|
+
}
|
|
366
392
|
}
|
|
367
393
|
]
|
|
368
394
|
}
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
STOCK_OPENING_COUNT_CELL,
|
|
27
27
|
STOCK_CLOSING_COUNT_CELL,
|
|
28
28
|
BUSINESS_DESCRIPTION_CELL,
|
|
29
|
+
DISALLOWABLE_PERCENT_CELLS,
|
|
29
30
|
SALESINVOICE_VAT_REG_CELL,
|
|
30
31
|
SALESINVOICE_TELEPHONE_CELL,
|
|
31
32
|
SALESINVOICE_PRODUCT_DETAILS_COLUMNS,
|
|
@@ -392,6 +393,7 @@ function isVatInputCell(sheet, cellRef) {
|
|
|
392
393
|
function isHubInputCell(sheet, cellRef) {
|
|
393
394
|
if (sheet === "Business Details") return cellRef === "C5" || cellRef === BUSINESS_DESCRIPTION_CELL;
|
|
394
395
|
if (sheet === "StockControl") return cellRef === STOCK_OPENING_COUNT_CELL || cellRef === STOCK_CLOSING_COUNT_CELL;
|
|
396
|
+
if (sheet === "VitalTax") return Object.values(DISALLOWABLE_PERCENT_CELLS).includes(cellRef);
|
|
395
397
|
return false;
|
|
396
398
|
}
|
|
397
399
|
|
|
@@ -205,7 +205,9 @@ function quarterlyFromMonthlyGrid(pl) {
|
|
|
205
205
|
// adjustment mixed in, read off app/lib/scenario-extractor.js's own account
|
|
206
206
|
// maps by way of the code letters scenario transactions already carry.
|
|
207
207
|
const SALES_QUARTERLY_ROW = { a: 5, b: 6, c: 7, d: 8 };
|
|
208
|
-
|
|
208
|
+
// Codes a (advertising) and e (business entertainment) both feed row 27 --
|
|
209
|
+
// box 24's own combined figure -- so both land on it here too.
|
|
210
|
+
const PURCHASES_QUARTERLY_ROW = { s: 14, c: 15, o: 16, w: 21, p: 22, m: 23, g: 24, v: 25, h: 26, a: 27, e: 27, l: 28, y: 32 };
|
|
209
211
|
|
|
210
212
|
const MONTH_KEYS = ["apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec", "jan", "feb", "mar"];
|
|
211
213
|
const MONTH_SHEET_NAMES = {
|
|
@@ -83,6 +83,7 @@ export const PURCHASES_ANALYSIS_COLUMNS = {
|
|
|
83
83
|
l: "Z",
|
|
84
84
|
y: "AA",
|
|
85
85
|
fa: "AB",
|
|
86
|
+
e: "AC",
|
|
86
87
|
};
|
|
87
88
|
|
|
88
89
|
// Bank.xlsx and Cash.xlsx analyse a receipt and a payment under different code
|
|
@@ -803,7 +804,9 @@ export function calculateSeCells(book, lines, taxData, scenario = {}) {
|
|
|
803
804
|
pl[`${col}24`] = purchasesCode(index, "g");
|
|
804
805
|
pl[`${col}25`] = purchasesCode(index, "v");
|
|
805
806
|
pl[`${col}26`] = purchasesCode(index, "h");
|
|
806
|
-
|
|
807
|
+
// Advertising and business entertainment share one Purchases analysis
|
|
808
|
+
// column each, but the same Purchases row: box 24 is the combined figure.
|
|
809
|
+
pl[`${col}27`] = purchasesCode(index, "a") + purchasesCode(index, "e");
|
|
807
810
|
pl[`${col}28`] = purchasesCode(index, "l");
|
|
808
811
|
pl[`${col}29`] = -salesCode(index, "o");
|
|
809
812
|
pl[`${col}30`] = bankPayments(bank, index, "J");
|
|
@@ -815,9 +818,12 @@ export function calculateSeCells(book, lines, taxData, scenario = {}) {
|
|
|
815
818
|
pl[`${col}37`] = pl[`${col}19`] - pl[`${col}35`];
|
|
816
819
|
pl[`${col}38`] = bankReceipts(bank, index, "K");
|
|
817
820
|
pl[`${col}39`] = pl[`${col}37`] + pl[`${col}38`];
|
|
821
|
+
// Row 49: a memo of the entertainment share of row 27, outside the B35
|
|
822
|
+
// administrative-expenses total the way the template's own row 49 is.
|
|
823
|
+
pl[`${col}49`] = purchasesCode(index, "e");
|
|
818
824
|
});
|
|
819
825
|
const yearTotal = (row) => sheetSum(MONTH_COLS.map((col) => pl[`${col}${row}`]));
|
|
820
|
-
for (const row of [5, 6, 7, 8, 11, 14, 15, 16, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 38]) {
|
|
826
|
+
for (const row of [5, 6, 7, 8, 11, 14, 15, 16, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 38, 49]) {
|
|
821
827
|
pl[`B${row}`] = yearTotal(row);
|
|
822
828
|
}
|
|
823
829
|
pl.B9 = pl.B5 + pl.B6 + pl.B7 + pl.B8;
|
|
@@ -827,6 +833,119 @@ export function calculateSeCells(book, lines, taxData, scenario = {}) {
|
|
|
827
833
|
pl.B37 = pl.B19 - pl.B35;
|
|
828
834
|
pl.B39 = pl.B37 + pl.B38;
|
|
829
835
|
|
|
836
|
+
// ── VitalTax ──
|
|
837
|
+
// The summary quotes the three product sales rows and the two direct cost
|
|
838
|
+
// rows, then a quarterly grid of fifteen expense categories: an allowable
|
|
839
|
+
// row read straight off the P&L beside a disallowable row that is a
|
|
840
|
+
// customer-set percentage of that same allowable row -- the source boxes
|
|
841
|
+
// 32 to 45 (SE Full's O66 to O118) read. Built ahead of the two self
|
|
842
|
+
// assessment returns because both read it.
|
|
843
|
+
const quarterSum = (row, quarter) => sheetSum(MONTH_COLS.slice(quarter * 3, quarter * 3 + 3).map((col) => pl[`${col}${row}`]));
|
|
844
|
+
const quarterSumRows = (rows, quarter) => sheetSum(rows.map((row) => quarterSum(row, quarter)));
|
|
845
|
+
const productSales = (quarter) => quarterSum(5, quarter) + quarterSum(6, quarter) + quarterSum(7, quarter);
|
|
846
|
+
const directCosts = (quarter) => quarterSum(14, quarter) + quarterSum(16, quarter);
|
|
847
|
+
const vitalTax = {
|
|
848
|
+
C5: productSales(0),
|
|
849
|
+
D5: productSales(1),
|
|
850
|
+
E5: productSales(2),
|
|
851
|
+
F5: productSales(3),
|
|
852
|
+
C7: directCosts(0),
|
|
853
|
+
D7: directCosts(1),
|
|
854
|
+
E7: directCosts(2),
|
|
855
|
+
F7: directCosts(3),
|
|
856
|
+
};
|
|
857
|
+
vitalTax.G5 = vitalTax.C5 + vitalTax.D5 + vitalTax.E5 + vitalTax.F5;
|
|
858
|
+
vitalTax.G7 = vitalTax.C7 + vitalTax.D7 + vitalTax.E7 + vitalTax.F7;
|
|
859
|
+
|
|
860
|
+
const QUARTER_COLS = [
|
|
861
|
+
["C", 0],
|
|
862
|
+
["D", 1],
|
|
863
|
+
["E", 2],
|
|
864
|
+
["F", 3],
|
|
865
|
+
];
|
|
866
|
+
|
|
867
|
+
// Every allowable row the disallowable percentages multiply, keyed by its
|
|
868
|
+
// own row number, reading the P&L row (or rows) its caption names.
|
|
869
|
+
const ALLOWABLE_PL_ROWS = {
|
|
870
|
+
12: [21], // Wages and staff costs
|
|
871
|
+
13: [22], // Premises running costs
|
|
872
|
+
14: [23], // Repairs and renewals
|
|
873
|
+
15: [24], // General administrative expenses
|
|
874
|
+
16: [31], // Finance charges (HP interest, lease, bank charges)
|
|
875
|
+
17: [25, 26], // Car, van and travel expenses
|
|
876
|
+
18: [27], // Advertising (now the combined advertising + entertainment row)
|
|
877
|
+
19: [28], // Professional fees
|
|
878
|
+
20: [29], // Bad debts
|
|
879
|
+
21: [30], // Interest on bank and other loans
|
|
880
|
+
22: [32], // Other expenses
|
|
881
|
+
24: [15], // Payments to subcontractors
|
|
882
|
+
25: [49], // Business entertainment (the memo row)
|
|
883
|
+
26: [33, 34], // Loss on disposal + depreciation
|
|
884
|
+
};
|
|
885
|
+
for (const [row, plRows] of Object.entries(ALLOWABLE_PL_ROWS)) {
|
|
886
|
+
for (const [col, quarter] of QUARTER_COLS) vitalTax[`${col}${row}`] = quarterSumRows(plRows, quarter);
|
|
887
|
+
vitalTax[`G${row}`] = vitalTax[`C${row}`] + vitalTax[`D${row}`] + vitalTax[`E${row}`] + vitalTax[`F${row}`];
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// The fifteen disallowable rows behind boxes 32 to 45. Thirteen are a
|
|
891
|
+
// customer-set percentage of the allowable row beside them; business
|
|
892
|
+
// entertainment (row 44) is wholly disallowable, so it echoes row 25
|
|
893
|
+
// outright, and depreciation (row 49) already has its own source on SE
|
|
894
|
+
// Full (O114 = P&L row 34), so it echoes that P&L row rather than taking a
|
|
895
|
+
// percentage of anything.
|
|
896
|
+
const DISALLOWABLE_ROWS = {
|
|
897
|
+
36: { allowableRow: 7, key: "costOfGoods" },
|
|
898
|
+
37: { allowableRow: 24, key: "paymentsToSubcontractors" },
|
|
899
|
+
38: { allowableRow: 12, key: "wagesAndStaffCosts" },
|
|
900
|
+
39: { allowableRow: 17, key: "carVanTravelExpenses" },
|
|
901
|
+
40: { allowableRow: 13, key: "premisesRunningCosts" },
|
|
902
|
+
41: { allowableRow: 14, key: "maintenanceCosts" },
|
|
903
|
+
42: { allowableRow: 15, key: "adminCosts" },
|
|
904
|
+
43: { allowableRow: 18, key: "advertisingCosts" },
|
|
905
|
+
45: { allowableRow: 21, key: "interestOnBankOtherLoans" },
|
|
906
|
+
46: { allowableRow: 16, key: "financeCharges" },
|
|
907
|
+
47: { allowableRow: 20, key: "irrecoverableDebts" },
|
|
908
|
+
48: { allowableRow: 19, key: "professionalFees" },
|
|
909
|
+
50: { allowableRow: 22, key: "otherExpenses" },
|
|
910
|
+
};
|
|
911
|
+
const disallowablePercent = scenario.disallowable || {};
|
|
912
|
+
for (const [row, { allowableRow, key }] of Object.entries(DISALLOWABLE_ROWS)) {
|
|
913
|
+
const percent = disallowablePercent[key] || 0;
|
|
914
|
+
for (const [col] of QUARTER_COLS) vitalTax[`${col}${row}`] = vitalTax[`${col}${allowableRow}`] * percent;
|
|
915
|
+
vitalTax[`G${row}`] = vitalTax[`C${row}`] + vitalTax[`D${row}`] + vitalTax[`E${row}`] + vitalTax[`F${row}`];
|
|
916
|
+
}
|
|
917
|
+
for (const [col] of QUARTER_COLS) vitalTax[`${col}44`] = vitalTax[`${col}25`];
|
|
918
|
+
vitalTax.G44 = vitalTax.C44 + vitalTax.D44 + vitalTax.E44 + vitalTax.F44;
|
|
919
|
+
for (const [col, quarter] of QUARTER_COLS) vitalTax[`${col}49`] = quarterSum(34, quarter);
|
|
920
|
+
vitalTax.G49 = vitalTax.C49 + vitalTax.D49 + vitalTax.E49 + vitalTax.F49;
|
|
921
|
+
|
|
922
|
+
// Row 29: the grand total of allowable expenses. Entertainment (row 25) is
|
|
923
|
+
// left out -- it is already inside row 18's P&L!C27:E27 read, which now
|
|
924
|
+
// carries the combined advertising-and-entertainment figure.
|
|
925
|
+
const ROW29_ROWS = [7, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 26];
|
|
926
|
+
for (const [col] of QUARTER_COLS) vitalTax[`${col}29`] = sheetSum(ROW29_ROWS.map((row) => vitalTax[`${col}${row}`]));
|
|
927
|
+
vitalTax.G29 = sheetSum(ROW29_ROWS.map((row) => vitalTax[`G${row}`]));
|
|
928
|
+
|
|
929
|
+
// The thirteen SE Full disallowable boxes (32 to 45 minus 44, which shares
|
|
930
|
+
// box 39 with advertising) read straight off VitalTax's annual column.
|
|
931
|
+
const seFullDisallowable = {
|
|
932
|
+
O66: vitalTax.G36,
|
|
933
|
+
O70: vitalTax.G37,
|
|
934
|
+
O74: vitalTax.G38,
|
|
935
|
+
O78: vitalTax.G39,
|
|
936
|
+
O82: vitalTax.G40,
|
|
937
|
+
O86: vitalTax.G41,
|
|
938
|
+
O90: vitalTax.G42,
|
|
939
|
+
O94: vitalTax.G43 + vitalTax.G44,
|
|
940
|
+
O98: vitalTax.G45,
|
|
941
|
+
O102: vitalTax.G46,
|
|
942
|
+
O106: vitalTax.G47,
|
|
943
|
+
O110: vitalTax.G48,
|
|
944
|
+
O118: vitalTax.G50,
|
|
945
|
+
};
|
|
946
|
+
// Box 46, boxes 32 to 45 summed. seShort.O64 (box 20) reads the same total.
|
|
947
|
+
const boxes32to45Total = sheetSum(Object.values(seFullDisallowable)) + pl.B34;
|
|
948
|
+
|
|
830
949
|
// ── The fixed asset workbook ──
|
|
831
950
|
const scheduleCells = { E57: schedule.existing.E, E110: schedule.additions.E };
|
|
832
951
|
for (const column of SCHEDULE_TOTAL_COLUMNS) scheduleCells[`${column}1`] = schedule.totals[column];
|
|
@@ -907,16 +1026,27 @@ export function calculateSeCells(book, lines, taxData, scenario = {}) {
|
|
|
907
1026
|
seShort.D38 > admin.F26
|
|
908
1027
|
? `SELF-EMPLOYMENT FULL RETURN REQUIRED AS TURNOVER EXCEEDS £${admin.F26} VAT threshold`
|
|
909
1028
|
: `Business income - if your annual turnover was below £${admin.F26} VAT threshold`;
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
seShort.
|
|
916
|
-
seShort.
|
|
917
|
-
seShort.
|
|
918
|
-
seShort.
|
|
919
|
-
seShort.
|
|
1029
|
+
// Boxes 11 to 19 go net of their own share of boxes 32 to 45: SA103S has no
|
|
1030
|
+
// disallowable column of its own, so each allowable figure drops the
|
|
1031
|
+
// matching SE Full box before it reaches the short return. Box 33
|
|
1032
|
+
// (subcontractor payments) has no short-return box of its own -- SA103S
|
|
1033
|
+
// folds it into box 11 -- so D46 drops both box 32's and box 33's shares.
|
|
1034
|
+
seShort.D46 = analysed(pl.B17 - seFullDisallowable.O66 - seFullDisallowable.O70);
|
|
1035
|
+
seShort.O46 = analysed(pl.B28 - seFullDisallowable.O110);
|
|
1036
|
+
seShort.D51 = analysed(pl.B25 + pl.B26 - seFullDisallowable.O78);
|
|
1037
|
+
seShort.O51 = analysed(pl.B30 + pl.B31 - seFullDisallowable.O98 - seFullDisallowable.O102);
|
|
1038
|
+
seShort.D55 = analysed(pl.B21 - seFullDisallowable.O74);
|
|
1039
|
+
seShort.O55 = analysed(pl.B24 - seFullDisallowable.O90);
|
|
1040
|
+
seShort.D60 = analysed(pl.B22 - seFullDisallowable.O82);
|
|
1041
|
+
// Box 19's base (B27+B29+B32+B33) never includes depreciation (P&L row 34
|
|
1042
|
+
// sits outside it, in row 33's disposal loss instead), so nothing here
|
|
1043
|
+
// reads box 44's O114 -- box 20 (O64) is where depreciation's disallowable
|
|
1044
|
+
// share leaves the total. O94 already carries the whole of box 39's
|
|
1045
|
+
// entertainment share (VitalTax row 44 echoes row 25, the entertainment
|
|
1046
|
+
// memo's own annual total), so box 19 does not also subtract P&L!B49.
|
|
1047
|
+
seShort.O60 = analysed(pl.B27 + pl.B29 + pl.B32 + pl.B33 - seFullDisallowable.O94 - seFullDisallowable.O106 - seFullDisallowable.O118);
|
|
1048
|
+
seShort.D64 = analysed(pl.B23 - seFullDisallowable.O86);
|
|
1049
|
+
seShort.O64 = pl.B17 + pl.B35 - boxes32to45Total;
|
|
920
1050
|
const shortNetProfit = seShort.D38 + seShort.O38 - seShort.O64;
|
|
921
1051
|
seShort.D71 = shortNetProfit >= 0 ? shortNetProfit : 0;
|
|
922
1052
|
seShort.O71 = shortNetProfit < 0 ? -shortNetProfit : 0;
|
|
@@ -969,43 +1099,23 @@ export function calculateSeCells(book, lines, taxData, scenario = {}) {
|
|
|
969
1099
|
seFull.D118 = pl.B32;
|
|
970
1100
|
seFull.D122 = pl.B17 + pl.B35;
|
|
971
1101
|
seFull.O114 = pl.B34;
|
|
972
|
-
// Boxes 32 to 45, the disallowable side of each expense category
|
|
973
|
-
//
|
|
974
|
-
|
|
975
|
-
seFull.
|
|
976
|
-
seFull.
|
|
977
|
-
seFull.
|
|
978
|
-
seFull.
|
|
979
|
-
seFull.
|
|
980
|
-
seFull.
|
|
981
|
-
seFull.
|
|
982
|
-
seFull.
|
|
983
|
-
seFull.
|
|
984
|
-
seFull.
|
|
985
|
-
seFull.
|
|
986
|
-
seFull.
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
// read of the depreciation figure box 44 already carries. The sum stays
|
|
990
|
-
// equal to box 44 alone until the boxes above gain a source.
|
|
991
|
-
seFull.O122 = carry([seFull.O114], () =>
|
|
992
|
-
sheetSum([
|
|
993
|
-
seFull.O66,
|
|
994
|
-
seFull.O70,
|
|
995
|
-
seFull.O74,
|
|
996
|
-
seFull.O78,
|
|
997
|
-
seFull.O82,
|
|
998
|
-
seFull.O86,
|
|
999
|
-
seFull.O90,
|
|
1000
|
-
seFull.O94,
|
|
1001
|
-
seFull.O98,
|
|
1002
|
-
seFull.O102,
|
|
1003
|
-
seFull.O106,
|
|
1004
|
-
seFull.O110,
|
|
1005
|
-
seFull.O114,
|
|
1006
|
-
seFull.O118,
|
|
1007
|
-
]),
|
|
1008
|
-
);
|
|
1102
|
+
// Boxes 32 to 45, the disallowable side of each expense category, sourced
|
|
1103
|
+
// from the VitalTax percentages built earlier.
|
|
1104
|
+
seFull.O66 = seFullDisallowable.O66;
|
|
1105
|
+
seFull.O70 = seFullDisallowable.O70;
|
|
1106
|
+
seFull.O74 = seFullDisallowable.O74;
|
|
1107
|
+
seFull.O78 = seFullDisallowable.O78;
|
|
1108
|
+
seFull.O82 = seFullDisallowable.O82;
|
|
1109
|
+
seFull.O86 = seFullDisallowable.O86;
|
|
1110
|
+
seFull.O90 = seFullDisallowable.O90;
|
|
1111
|
+
seFull.O94 = seFullDisallowable.O94;
|
|
1112
|
+
seFull.O98 = seFullDisallowable.O98;
|
|
1113
|
+
seFull.O102 = seFullDisallowable.O102;
|
|
1114
|
+
seFull.O106 = seFullDisallowable.O106;
|
|
1115
|
+
seFull.O110 = seFullDisallowable.O110;
|
|
1116
|
+
seFull.O118 = seFullDisallowable.O118;
|
|
1117
|
+
// Box 46's own caption calls it the total of boxes 32 to 45.
|
|
1118
|
+
seFull.O122 = carry([seFull.O114], () => boxes32to45Total);
|
|
1009
1119
|
const fullNetProfit = seFull.D55 + seFull.O55 - seFull.D122;
|
|
1010
1120
|
seFull.D129 = fullNetProfit >= 0 ? fullNetProfit : 0;
|
|
1011
1121
|
seFull.O129 = fullNetProfit < 0 ? -fullNetProfit : 0;
|
|
@@ -1127,25 +1237,6 @@ export function calculateSeCells(book, lines, taxData, scenario = {}) {
|
|
|
1127
1237
|
sheetSum([forecast.C42, forecast.C43, forecast.C44, forecast.C45]),
|
|
1128
1238
|
);
|
|
1129
1239
|
|
|
1130
|
-
// ── VitalTax ──
|
|
1131
|
-
// The summary quotes the three product sales rows and the two direct cost
|
|
1132
|
-
// rows, nothing else.
|
|
1133
|
-
const quarterSum = (row, quarter) => sheetSum(MONTH_COLS.slice(quarter * 3, quarter * 3 + 3).map((col) => pl[`${col}${row}`]));
|
|
1134
|
-
const productSales = (quarter) => quarterSum(5, quarter) + quarterSum(6, quarter) + quarterSum(7, quarter);
|
|
1135
|
-
const directCosts = (quarter) => quarterSum(14, quarter) + quarterSum(16, quarter);
|
|
1136
|
-
const vitalTax = {
|
|
1137
|
-
C5: productSales(0),
|
|
1138
|
-
D5: productSales(1),
|
|
1139
|
-
E5: productSales(2),
|
|
1140
|
-
F5: productSales(3),
|
|
1141
|
-
C7: directCosts(0),
|
|
1142
|
-
D7: directCosts(1),
|
|
1143
|
-
E7: directCosts(2),
|
|
1144
|
-
F7: directCosts(3),
|
|
1145
|
-
};
|
|
1146
|
-
vitalTax.G5 = vitalTax.C5 + vitalTax.D5 + vitalTax.E5 + vitalTax.F5;
|
|
1147
|
-
vitalTax.G7 = vitalTax.C7 + vitalTax.D7 + vitalTax.E7 + vitalTax.F7;
|
|
1148
|
-
|
|
1149
1240
|
// ── The results, keyed the way the reconciliation reads them ──
|
|
1150
1241
|
const results = {
|
|
1151
1242
|
"Business Details": { C5: businessName(scenario) },
|
|
@@ -12,25 +12,25 @@
|
|
|
12
12
|
|
|
13
13
|
export const PROVENANCE_DATA = {
|
|
14
14
|
formatVersion: "diya-gl/1",
|
|
15
|
-
engineVersion: "1.2.
|
|
15
|
+
engineVersion: "1.2.6+8f36b1c6",
|
|
16
16
|
taxDataHash: "84585062cad8",
|
|
17
|
-
reconciledCommit: "
|
|
17
|
+
reconciledCommit: "08ad8a551c51a0f9a7bdb24a381b06a810b7c15e",
|
|
18
18
|
templates: {
|
|
19
19
|
bst: {
|
|
20
20
|
hash: "0d003e10e00c",
|
|
21
|
-
scorecard: "
|
|
21
|
+
scorecard: "812 passed, 0 warnings, 0 failed",
|
|
22
22
|
},
|
|
23
23
|
taxi: {
|
|
24
24
|
hash: "1ff69654d64d",
|
|
25
25
|
scorecard: "686 passed, 0 warnings, 0 failed",
|
|
26
26
|
},
|
|
27
27
|
se: {
|
|
28
|
-
hash: "
|
|
29
|
-
scorecard: "
|
|
28
|
+
hash: "185e5ad6b61d",
|
|
29
|
+
scorecard: "7692 passed, 0 warnings, 0 failed",
|
|
30
30
|
},
|
|
31
31
|
ltd: {
|
|
32
32
|
hash: "81ffdc956e5a",
|
|
33
|
-
scorecard: "
|
|
33
|
+
scorecard: "101289 passed, 0 warnings, 0 failed",
|
|
34
34
|
},
|
|
35
35
|
},
|
|
36
36
|
};
|
|
@@ -37,6 +37,7 @@ export const LTD_PURCHASE_CODE_MAP = {
|
|
|
37
37
|
5401: "u",
|
|
38
38
|
5500: "a",
|
|
39
39
|
5501: "g",
|
|
40
|
+
5502: "a", // business entertainment -> Advertising, the same category Ltd's own book.toml column folds it into
|
|
40
41
|
5600: "h",
|
|
41
42
|
5601: "v",
|
|
42
43
|
5700: "n",
|
|
@@ -74,7 +75,7 @@ export const BST_PURCHASE_CODE_MAP = {
|
|
|
74
75
|
5900: "f", // Fixed assets
|
|
75
76
|
};
|
|
76
77
|
|
|
77
|
-
// SE purchases: accountMainID -> SE code letter (
|
|
78
|
+
// SE purchases: accountMainID -> SE code letter (22 codes)
|
|
78
79
|
export const SE_PURCHASE_CODE_MAP = {
|
|
79
80
|
5000: "s",
|
|
80
81
|
5001: "c",
|
|
@@ -88,6 +89,7 @@ export const SE_PURCHASE_CODE_MAP = {
|
|
|
88
89
|
5401: "y", // general shopping -> Other Expenses
|
|
89
90
|
5500: "a",
|
|
90
91
|
5501: "g",
|
|
92
|
+
5502: "e", // business entertainment -> its own Purchases analysis column
|
|
91
93
|
5600: "h",
|
|
92
94
|
5601: "v",
|
|
93
95
|
5700: "y", // insurance -> Other Expenses (no insurance column)
|
|
@@ -1135,6 +1137,15 @@ export function formatScenarioToml(metadata, grouped, expected) {
|
|
|
1135
1137
|
parts.push("");
|
|
1136
1138
|
}
|
|
1137
1139
|
|
|
1140
|
+
// The share of each SA103F expense category (32 to 45) that is private use
|
|
1141
|
+
// or otherwise disallowable, as a fraction (0.1 = 10%). A book setting, not
|
|
1142
|
+
// a transaction: no journal line carries it.
|
|
1143
|
+
if (expected.disallowable) {
|
|
1144
|
+
parts.push("[disallowable]");
|
|
1145
|
+
for (const [key, percent] of Object.entries(expected.disallowable)) parts.push(`${key} = ${percent}`);
|
|
1146
|
+
parts.push("");
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1138
1149
|
// Charges and debentures registered over the company's assets (Ltd). Each
|
|
1139
1150
|
// one secures a creditor falling due after more than one year.
|
|
1140
1151
|
if (expected.charges) {
|
package/dist/app/products/se.js
CHANGED
|
@@ -206,6 +206,27 @@ export const BUSINESS_DESCRIPTION_CELL = "C17";
|
|
|
206
206
|
export const STOCK_OPENING_COUNT_CELL = "AB6";
|
|
207
207
|
export const STOCK_CLOSING_COUNT_CELL = "AB30";
|
|
208
208
|
|
|
209
|
+
// VitalTax column I, rows 36 to 50: the customer's own percentage of each
|
|
210
|
+
// expense category that is private use or otherwise disallowable, the input
|
|
211
|
+
// behind SE Full's boxes 32 to 45. Verified against the template -- row 44
|
|
212
|
+
// (business entertainment) and row 49 (depreciation) take no percentage, so
|
|
213
|
+
// they carry no key here. See PLAN_SE_TEMPLATE_GAPS.md section 3.1.
|
|
214
|
+
export const DISALLOWABLE_PERCENT_CELLS = {
|
|
215
|
+
costOfGoods: "I36",
|
|
216
|
+
paymentsToSubcontractors: "I37",
|
|
217
|
+
wagesAndStaffCosts: "I38",
|
|
218
|
+
carVanTravelExpenses: "I39",
|
|
219
|
+
premisesRunningCosts: "I40",
|
|
220
|
+
maintenanceCosts: "I41",
|
|
221
|
+
adminCosts: "I42",
|
|
222
|
+
advertisingCosts: "I43",
|
|
223
|
+
interestOnBankOtherLoans: "I45",
|
|
224
|
+
financeCharges: "I46",
|
|
225
|
+
irrecoverableDebts: "I47",
|
|
226
|
+
professionalFees: "I48",
|
|
227
|
+
otherExpenses: "I50",
|
|
228
|
+
};
|
|
229
|
+
|
|
209
230
|
// Fixedassets.xlsx Schedule sheet -- verified against the template:
|
|
210
231
|
// Existing assets (bought before the year start): rows 8-10 land,
|
|
211
232
|
// 14-18 plant, 22-26 fixtures, 30-34 computers, 38-54 motor. Each row:
|
|
@@ -472,6 +493,14 @@ function composeWrites(scenario, targetStartYear) {
|
|
|
472
493
|
if (scenario.stock.opening !== undefined) hubWrites.StockControl[STOCK_OPENING_COUNT_CELL] = scenario.stock.opening;
|
|
473
494
|
if (scenario.stock.closing !== undefined) hubWrites.StockControl[STOCK_CLOSING_COUNT_CELL] = scenario.stock.closing;
|
|
474
495
|
}
|
|
496
|
+
// VitalTax's disallowable percentages: a book setting per category, not a
|
|
497
|
+
// transaction, so nothing here moves the trial balance.
|
|
498
|
+
if (scenario.disallowable) {
|
|
499
|
+
hubWrites.VitalTax = {};
|
|
500
|
+
for (const [key, cell] of Object.entries(DISALLOWABLE_PERCENT_CELLS)) {
|
|
501
|
+
if (scenario.disallowable[key] !== undefined) hubWrites.VitalTax[cell] = scenario.disallowable[key];
|
|
502
|
+
}
|
|
503
|
+
}
|
|
475
504
|
if (scenario.business || scenario.metadata) {
|
|
476
505
|
hubWrites["Business Details"] = {};
|
|
477
506
|
const bd = hubWrites["Business Details"];
|
|
@@ -1250,7 +1279,11 @@ const SALES_MONTHLY_TIE_ROWS = { a: 5, b: 6, c: 7, d: 8, g: 11 };
|
|
|
1250
1279
|
// negated -- a template quirk, not a naming error; verified against the
|
|
1251
1280
|
// formula (`C29 = -[2]Apr!$U$1`).
|
|
1252
1281
|
const SALES_BAD_DEBT_ROW = 29;
|
|
1253
|
-
|
|
1282
|
+
// Row 27 combines codes a (advertising) and e (business entertainment) -- box
|
|
1283
|
+
// 24's own caption -- so both places below that read this map special-case
|
|
1284
|
+
// code a to add code e's figure, rather than the map carrying two codes on
|
|
1285
|
+
// one row.
|
|
1286
|
+
const PURCHASES_MONTHLY_TIE_ROWS = { c: 15, o: 16, p: 22, m: 23, g: 24, v: 25, h: 26, a: 27, l: 28, y: 32, e: 49 };
|
|
1254
1287
|
|
|
1255
1288
|
// The P&L's own caption for each tied row, taken from column A of the
|
|
1256
1289
|
// template. The netting table names a category the way the statement it
|
|
@@ -1269,10 +1302,11 @@ const PL_ROW_CAPTIONS = {
|
|
|
1269
1302
|
24: "General Administrative Expenses",
|
|
1270
1303
|
25: "Motor Expenses",
|
|
1271
1304
|
26: "Travel Hotel & Subsistence",
|
|
1272
|
-
27: "Advertising &
|
|
1305
|
+
27: "Advertising Promotion & Entertainment",
|
|
1273
1306
|
28: "Legal & Professional Fees",
|
|
1274
1307
|
29: "Bad Debts written off",
|
|
1275
1308
|
32: "Other Expenses",
|
|
1309
|
+
49: "Business Entertainment (memo)",
|
|
1276
1310
|
};
|
|
1277
1311
|
|
|
1278
1312
|
// Wagesinterface and Payslips!Payment both hold one row per month, Apr at
|
|
@@ -1720,7 +1754,11 @@ export function profitBridge(results) {
|
|
|
1720
1754
|
// figures free to diverge from it if one of them is wrong.
|
|
1721
1755
|
const rows = [
|
|
1722
1756
|
{ label: "Profit before tax per the profit and loss account", cell: "Profit & Loss Account!B39", value: num(pl.B39) },
|
|
1723
|
-
|
|
1757
|
+
// Depreciation was the only disallowable expense while every other
|
|
1758
|
+
// category's percentage sat at nil; now the whole of box 46 (boxes 32
|
|
1759
|
+
// to 45, disallowable percentages included) is added back, not
|
|
1760
|
+
// depreciation alone.
|
|
1761
|
+
{ label: "Add disallowable expenses added back (box 46)", cell: "SE Full!O122", value: num(seFull?.O122) },
|
|
1724
1762
|
{ label: "Less grants, taxed as other business income below", cell: "Profit & Loss Account!B11", value: -num(pl.B11) },
|
|
1725
1763
|
{ label: "Less annual investment allowance (box 23)", cell: "SE Short!D80", value: -num(seShort.D80) },
|
|
1726
1764
|
{ label: "Less small-balance allowance (box 24)", cell: "SE Short!D85", value: -num(seShort.D85) },
|
|
@@ -1850,7 +1888,14 @@ export function categoryNetting(results, scenario) {
|
|
|
1850
1888
|
|
|
1851
1889
|
for (const [code, row] of Object.entries(SALES_MONTHLY_TIE_ROWS)) plRow("sales", sales, code, row);
|
|
1852
1890
|
plRow("sales", sales, "o", SALES_BAD_DEBT_ROW, -1);
|
|
1853
|
-
|
|
1891
|
+
// Box 24 combines advertising and business entertainment in one row, so
|
|
1892
|
+
// the "a" netting row carries both codes' combined gross and net.
|
|
1893
|
+
const purchasesRow27 = {
|
|
1894
|
+
gross: { a: (purchases.gross.a || 0) + (purchases.gross.e || 0) },
|
|
1895
|
+
net: { a: (purchases.net.a || 0) + (purchases.net.e || 0) },
|
|
1896
|
+
};
|
|
1897
|
+
for (const [code, row] of Object.entries(PURCHASES_MONTHLY_TIE_ROWS))
|
|
1898
|
+
plRow("purchases", code === "a" ? purchasesRow27 : purchases, code, row);
|
|
1854
1899
|
|
|
1855
1900
|
// Stock-coded purchases reach the materials line together with the year's
|
|
1856
1901
|
// stock movement, so the movement comes off the line before the two sides
|
|
@@ -2127,11 +2172,15 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
|
|
|
2127
2172
|
// the whole of the difference between the two profits. Both are exact
|
|
2128
2173
|
// identities; the profit was previously compared to a rebuilt figure
|
|
2129
2174
|
// with a one per cent tolerance.
|
|
2130
|
-
|
|
2175
|
+
// Box 20 (O64) reads 'SE Full'!O122 now, boxes 32 to 45 summed -- the
|
|
2176
|
+
// depreciation charge plus every other disallowable category's own
|
|
2177
|
+
// percentage. Depreciation alone was the whole of that total before
|
|
2178
|
+
// any category had a percentage.
|
|
2179
|
+
const sa103fBoxesTotal = num(results["SE Full"]?.O122);
|
|
2131
2180
|
check(
|
|
2132
2181
|
"SA103S: total expenses = cost of sales + admin expenses less depreciation",
|
|
2133
2182
|
num(seShort.O64),
|
|
2134
|
-
num(pl.B17) + num(pl.B35) -
|
|
2183
|
+
num(pl.B17) + num(pl.B35) - sa103fBoxesTotal,
|
|
2135
2184
|
);
|
|
2136
2185
|
// D71 only ever carries a profit (verified against the template: D71 =
|
|
2137
2186
|
// IF((D38+O38-O64)>=0,D38+O38-O64,0)) -- a loss-making year floors it
|
|
@@ -2417,12 +2466,6 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
|
|
|
2417
2466
|
const sa103fCounterparts = [
|
|
2418
2467
|
["D55", "D38", "box 15 turnover"],
|
|
2419
2468
|
["O55", "O38", "box 16 other business income"],
|
|
2420
|
-
["D74", "D55", "box 19 wages, salaries and staff costs"],
|
|
2421
|
-
["D78", "D51", "box 20 car, van and travel expenses"],
|
|
2422
|
-
["D82", "D60", "box 21 rent, rates, power and insurance"],
|
|
2423
|
-
["D86", "D64", "box 22 repairs and maintenance"],
|
|
2424
|
-
["D90", "O55", "box 23 phone, stationery and office costs"],
|
|
2425
|
-
["D110", "O46", "box 28 accountancy, legal and professional fees"],
|
|
2426
2469
|
["O129", "O71", "box 48 net loss"],
|
|
2427
2470
|
["D139", "D80", "box 49 annual investment allowance"],
|
|
2428
2471
|
["O144", "D85", "box 55 100% and other enhanced capital allowances"],
|
|
@@ -2445,6 +2488,26 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
|
|
|
2445
2488
|
check(`SA103F ${caption}: full return (${fullCell}) = short return (${shortCell})`, num(seFull[fullCell]), num(sa103s[shortCell]));
|
|
2446
2489
|
}
|
|
2447
2490
|
|
|
2491
|
+
// Boxes 19 to 23 and 28: SA103S has no disallowable column, so each of
|
|
2492
|
+
// these six drops its own share of boxes 32 to 45 before it reaches
|
|
2493
|
+
// the short return.
|
|
2494
|
+
const sa103fNetOfDisallowable = [
|
|
2495
|
+
["D74", "D55", "O74", "box 19 wages, salaries and staff costs"],
|
|
2496
|
+
["D78", "D51", "O78", "box 20 car, van and travel expenses"],
|
|
2497
|
+
["D82", "D60", "O82", "box 21 rent, rates, power and insurance"],
|
|
2498
|
+
["D86", "D64", "O86", "box 22 repairs and maintenance"],
|
|
2499
|
+
["D90", "O55", "O90", "box 23 phone, stationery and office costs"],
|
|
2500
|
+
["D110", "O46", "O110", "box 28 accountancy, legal and professional fees"],
|
|
2501
|
+
];
|
|
2502
|
+
for (const [fullCell, shortCell, disallowableCell, caption] of sa103fNetOfDisallowable) {
|
|
2503
|
+
if (!shortAnalysisShown) continue;
|
|
2504
|
+
check(
|
|
2505
|
+
`SA103F ${caption}: short return (${shortCell}) = full return (${fullCell}) less its own disallowable share (${disallowableCell})`,
|
|
2506
|
+
num(sa103s[shortCell]),
|
|
2507
|
+
num(seFull[fullCell]) - num(seFull[disallowableCell]),
|
|
2508
|
+
);
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2448
2511
|
// Where the two forms differ by design. The full return has a
|
|
2449
2512
|
// disallowable column, so it totals expenses before the add-back and
|
|
2450
2513
|
// carries a net profit that much lower; the short return has no such
|
|
@@ -2695,7 +2758,9 @@ export function checkCompliance(results, expected, taxData, calculateExpectedTax
|
|
|
2695
2758
|
}
|
|
2696
2759
|
|
|
2697
2760
|
for (const [code, row] of Object.entries(PURCHASES_MONTHLY_TIE_ROWS)) {
|
|
2698
|
-
|
|
2761
|
+
let net = netOfVat(byCode[code] || 0, rate) + (code === "v" ? monthlyMileageClaims[MONTH_KEYS[i]] || 0 : 0);
|
|
2762
|
+
// Box 24 combines advertising and business entertainment in one row.
|
|
2763
|
+
if (code === "a") net += netOfVat(byCode.e || 0, rate);
|
|
2699
2764
|
check(`P&L ${MONTH_KEYS[i]} col ${col}${row} = Purchases.xlsx ${code}-coded net`, pl[`${col}${row}`] || 0, net);
|
|
2700
2765
|
}
|
|
2701
2766
|
}
|