jaz-cli 2.8.0 → 2.9.0
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/assets/skills/api/SKILL.md +1 -1
- package/assets/skills/conversion/SKILL.md +1 -1
- package/assets/skills/jobs/SKILL.md +58 -1
- package/assets/skills/jobs/references/sg-tax/add-backs-guide.md +354 -0
- package/assets/skills/jobs/references/sg-tax/capital-allowances-guide.md +343 -0
- package/assets/skills/jobs/references/sg-tax/data-extraction.md +408 -0
- package/assets/skills/jobs/references/sg-tax/enhanced-deductions.md +248 -0
- package/assets/skills/jobs/references/sg-tax/exemptions-and-rebates.md +197 -0
- package/assets/skills/jobs/references/sg-tax/form-cs-fields.md +191 -0
- package/assets/skills/jobs/references/sg-tax/ifrs16-tax-adjustment.md +194 -0
- package/assets/skills/jobs/references/sg-tax/losses-and-carry-forwards.md +269 -0
- package/assets/skills/jobs/references/sg-tax/overview.md +207 -0
- package/assets/skills/jobs/references/sg-tax/wizard-workflow.md +391 -0
- package/assets/skills/transaction-recipes/SKILL.md +1 -1
- package/dist/__tests__/jobs-audit-prep.test.js +3 -3
- package/dist/__tests__/jobs-bank-recon.test.js +5 -5
- package/dist/__tests__/jobs-credit-control.test.js +1 -1
- package/dist/__tests__/jobs-fa-review.test.js +1 -1
- package/dist/__tests__/jobs-payment-run.test.js +3 -3
- package/dist/__tests__/jobs-supplier-recon.test.js +6 -6
- package/dist/__tests__/tax-sg-capital-allowances.test.js +389 -0
- package/dist/__tests__/tax-sg-exemptions.test.js +232 -0
- package/dist/__tests__/tax-sg-form-cs.test.js +687 -0
- package/dist/__tests__/tax-validate.test.js +208 -0
- package/dist/commands/init.js +7 -2
- package/dist/commands/jobs.js +1 -1
- package/dist/commands/tax.js +195 -0
- package/dist/index.js +2 -0
- package/dist/jobs/audit-prep.js +4 -4
- package/dist/jobs/bank-recon.js +4 -4
- package/dist/jobs/credit-control.js +5 -5
- package/dist/jobs/fa-review.js +4 -4
- package/dist/jobs/gst-vat.js +3 -3
- package/dist/jobs/payment-run.js +4 -4
- package/dist/jobs/supplier-recon.js +3 -3
- package/dist/tax/format.js +18 -0
- package/dist/tax/sg/capital-allowances.js +160 -0
- package/dist/tax/sg/constants.js +63 -0
- package/dist/tax/sg/exemptions.js +76 -0
- package/dist/tax/sg/form-cs.js +349 -0
- package/dist/tax/sg/format-sg.js +134 -0
- package/dist/tax/types.js +9 -0
- package/dist/tax/validate.js +124 -0
- package/dist/utils/template.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { computeFormCs } from '../tax/sg/form-cs.js';
|
|
3
|
+
// ── Helper factory ──────────────────────────────────────────────
|
|
4
|
+
function makeInput(overrides = {}) {
|
|
5
|
+
return {
|
|
6
|
+
ya: 2026,
|
|
7
|
+
basisPeriodStart: '2025-01-01',
|
|
8
|
+
basisPeriodEnd: '2025-12-31',
|
|
9
|
+
revenue: 500_000,
|
|
10
|
+
accountingProfit: 100_000,
|
|
11
|
+
addBacks: {
|
|
12
|
+
depreciation: 0,
|
|
13
|
+
amortization: 0,
|
|
14
|
+
rouDepreciation: 0,
|
|
15
|
+
leaseInterest: 0,
|
|
16
|
+
generalProvisions: 0,
|
|
17
|
+
donations: 0,
|
|
18
|
+
entertainment: 0,
|
|
19
|
+
penalties: 0,
|
|
20
|
+
privateCar: 0,
|
|
21
|
+
capitalExpOnPnl: 0,
|
|
22
|
+
unrealizedFxLoss: 0,
|
|
23
|
+
otherNonDeductible: 0,
|
|
24
|
+
},
|
|
25
|
+
deductions: {
|
|
26
|
+
actualLeasePayments: 0,
|
|
27
|
+
unrealizedFxGain: 0,
|
|
28
|
+
exemptDividends: 0,
|
|
29
|
+
exemptIncome: 0,
|
|
30
|
+
otherDeductions: 0,
|
|
31
|
+
},
|
|
32
|
+
capitalAllowances: {
|
|
33
|
+
currentYearClaim: 0,
|
|
34
|
+
balanceBroughtForward: 0,
|
|
35
|
+
},
|
|
36
|
+
enhancedDeductions: {
|
|
37
|
+
rdExpenditure: 0,
|
|
38
|
+
rdMultiplier: 2.5,
|
|
39
|
+
ipRegistration: 0,
|
|
40
|
+
ipMultiplier: 2.0,
|
|
41
|
+
donations250Base: 0,
|
|
42
|
+
s14qRenovation: 0,
|
|
43
|
+
},
|
|
44
|
+
losses: { broughtForward: 0 },
|
|
45
|
+
donationsCarryForward: { broughtForward: 0 },
|
|
46
|
+
exemptionType: 'pte',
|
|
47
|
+
...overrides,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Merge nested addBacks while keeping all other defaults. */
|
|
51
|
+
function withAddBacks(partial, base) {
|
|
52
|
+
const input = makeInput(base);
|
|
53
|
+
return { ...input, addBacks: { ...input.addBacks, ...partial } };
|
|
54
|
+
}
|
|
55
|
+
/** Merge nested deductions while keeping all other defaults. */
|
|
56
|
+
function withDeductions(partial, base) {
|
|
57
|
+
const input = makeInput(base);
|
|
58
|
+
return { ...input, deductions: { ...input.deductions, ...partial } };
|
|
59
|
+
}
|
|
60
|
+
// ── Form type determination ─────────────────────────────────────
|
|
61
|
+
describe('Form type determination', () => {
|
|
62
|
+
it('1. revenue $200K -> formType C-S Lite', () => {
|
|
63
|
+
const r = computeFormCs(makeInput({ revenue: 200_000 }));
|
|
64
|
+
expect(r.formType).toBe('C-S Lite');
|
|
65
|
+
expect(r.eligible).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
it('2. revenue $500K -> formType C-S', () => {
|
|
68
|
+
const r = computeFormCs(makeInput({ revenue: 500_000 }));
|
|
69
|
+
expect(r.formType).toBe('C-S');
|
|
70
|
+
expect(r.eligible).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
it('3. revenue $5M -> formType C-S, eligible', () => {
|
|
73
|
+
const r = computeFormCs(makeInput({ revenue: 5_000_000 }));
|
|
74
|
+
expect(r.formType).toBe('C-S');
|
|
75
|
+
expect(r.eligible).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
it('4. revenue $5.1M -> not eligible', () => {
|
|
78
|
+
const r = computeFormCs(makeInput({ revenue: 5_100_000 }));
|
|
79
|
+
expect(r.formType).toBe('C-S');
|
|
80
|
+
expect(r.eligible).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
// ── Basic computation — simple profitable company ───────────────
|
|
84
|
+
describe('Basic computation — simple profitable company', () => {
|
|
85
|
+
it('5. profit $100K, PTE — chargeableIncome, exempt, taxable, grossTax, rebate', () => {
|
|
86
|
+
const r = computeFormCs(makeInput());
|
|
87
|
+
expect(r.chargeableIncome).toBeCloseTo(100_000, 2);
|
|
88
|
+
// PTE: 10K×75% = 7,500 + 90K×50% = 45,000 → 52,500 exempt
|
|
89
|
+
expect(r.exemptAmount).toBeCloseTo(52_500, 2);
|
|
90
|
+
expect(r.taxableIncome).toBeCloseTo(47_500, 2);
|
|
91
|
+
// 47,500 × 17% = 8,075
|
|
92
|
+
expect(r.grossTax).toBeCloseTo(8_075, 2);
|
|
93
|
+
// YA 2026: 40% capped at 40K → 8,075 × 0.40 = 3,230
|
|
94
|
+
expect(r.citRebate).toBeCloseTo(3_230, 2);
|
|
95
|
+
expect(r.netTaxPayable).toBeCloseTo(4_845, 2);
|
|
96
|
+
});
|
|
97
|
+
it('6. profit $100K, SUTE — exempt $75K, taxable $25K', () => {
|
|
98
|
+
const r = computeFormCs(makeInput({ exemptionType: 'sute' }));
|
|
99
|
+
// SUTE: 100K × 75% = 75,000 exempt
|
|
100
|
+
expect(r.exemptAmount).toBeCloseTo(75_000, 2);
|
|
101
|
+
expect(r.taxableIncome).toBeCloseTo(25_000, 2);
|
|
102
|
+
// 25,000 × 17% = 4,250
|
|
103
|
+
expect(r.grossTax).toBeCloseTo(4_250, 2);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
// ── Add-backs ───────────────────────────────────────────────────
|
|
107
|
+
describe('Add-backs', () => {
|
|
108
|
+
it('7. profit $100K + depreciation $15K -> adjustedProfit $115K', () => {
|
|
109
|
+
const input = withAddBacks({ depreciation: 15_000 });
|
|
110
|
+
const r = computeFormCs(input);
|
|
111
|
+
expect(r.totalAddBacks).toBeCloseTo(15_000, 2);
|
|
112
|
+
expect(r.adjustedProfit).toBeCloseTo(115_000, 2);
|
|
113
|
+
});
|
|
114
|
+
it('8. all add-back fields populated -> totalAddBacks is sum of all', () => {
|
|
115
|
+
const input = withAddBacks({
|
|
116
|
+
depreciation: 10_000,
|
|
117
|
+
amortization: 5_000,
|
|
118
|
+
rouDepreciation: 3_000,
|
|
119
|
+
leaseInterest: 2_000,
|
|
120
|
+
generalProvisions: 4_000,
|
|
121
|
+
donations: 1_000,
|
|
122
|
+
entertainment: 500,
|
|
123
|
+
penalties: 300,
|
|
124
|
+
privateCar: 200,
|
|
125
|
+
capitalExpOnPnl: 1_500,
|
|
126
|
+
unrealizedFxLoss: 800,
|
|
127
|
+
otherNonDeductible: 100,
|
|
128
|
+
});
|
|
129
|
+
const r = computeFormCs(input);
|
|
130
|
+
const expected = 10_000 + 5_000 + 3_000 + 2_000 + 4_000 + 1_000 + 500 + 300 + 200 + 1_500 + 800 + 100;
|
|
131
|
+
expect(r.totalAddBacks).toBeCloseTo(expected, 2);
|
|
132
|
+
expect(r.adjustedProfit).toBeCloseTo(100_000 + expected, 2);
|
|
133
|
+
});
|
|
134
|
+
it('9. only non-zero add-backs appear in schedule', () => {
|
|
135
|
+
const input = withAddBacks({ depreciation: 5_000, entertainment: 2_000 });
|
|
136
|
+
const r = computeFormCs(input);
|
|
137
|
+
const subItems = r.schedule.filter(s => s.indent === 1 && s.label !== '');
|
|
138
|
+
const labels = subItems.map(s => s.label);
|
|
139
|
+
expect(labels).toContain('Depreciation');
|
|
140
|
+
expect(labels).toContain('Entertainment');
|
|
141
|
+
expect(labels).not.toContain('Amortization');
|
|
142
|
+
expect(labels).not.toContain('Penalties & fines');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
// ── Deductions ──────────────────────────────────────────────────
|
|
146
|
+
describe('Deductions', () => {
|
|
147
|
+
it('10. deductions (actual lease $5K, unrealizedFxGain $2K) -> adjustedProfit $93K', () => {
|
|
148
|
+
const input = withDeductions({ actualLeasePayments: 5_000, unrealizedFxGain: 2_000 });
|
|
149
|
+
const r = computeFormCs(input);
|
|
150
|
+
expect(r.totalDeductions).toBeCloseTo(7_000, 2);
|
|
151
|
+
expect(r.adjustedProfit).toBeCloseTo(93_000, 2);
|
|
152
|
+
});
|
|
153
|
+
it('11. add-backs AND deductions combined', () => {
|
|
154
|
+
let input = withAddBacks({ depreciation: 20_000 });
|
|
155
|
+
input = { ...input, deductions: { ...input.deductions, actualLeasePayments: 10_000 } };
|
|
156
|
+
const r = computeFormCs(input);
|
|
157
|
+
// 100K + 20K - 10K = 110K
|
|
158
|
+
expect(r.adjustedProfit).toBeCloseTo(110_000, 2);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
// ── Capital allowances (set-off order) ──────────────────────────
|
|
162
|
+
describe('Capital allowances', () => {
|
|
163
|
+
it('12. adjusted profit $100K, current CA $30K -> remaining $70K', () => {
|
|
164
|
+
const input = makeInput({
|
|
165
|
+
capitalAllowances: { currentYearClaim: 30_000, balanceBroughtForward: 0 },
|
|
166
|
+
});
|
|
167
|
+
const r = computeFormCs(input);
|
|
168
|
+
expect(r.capitalAllowanceClaim).toBeCloseTo(30_000, 2);
|
|
169
|
+
expect(r.chargeableIncomeBeforeLosses).toBeCloseTo(70_000, 2);
|
|
170
|
+
});
|
|
171
|
+
it('13. adjusted profit $100K, current CA $30K + unabsorbed b/f CA $20K -> remaining $50K', () => {
|
|
172
|
+
const input = makeInput({
|
|
173
|
+
capitalAllowances: { currentYearClaim: 30_000, balanceBroughtForward: 20_000 },
|
|
174
|
+
});
|
|
175
|
+
const r = computeFormCs(input);
|
|
176
|
+
expect(r.capitalAllowanceClaim).toBeCloseTo(50_000, 2);
|
|
177
|
+
expect(r.chargeableIncomeBeforeLosses).toBeCloseTo(50_000, 2);
|
|
178
|
+
});
|
|
179
|
+
it('14. adjusted profit $50K, current CA $80K -> claim only $50K, carry forward $30K', () => {
|
|
180
|
+
const input = makeInput({
|
|
181
|
+
accountingProfit: 50_000,
|
|
182
|
+
capitalAllowances: { currentYearClaim: 80_000, balanceBroughtForward: 0 },
|
|
183
|
+
});
|
|
184
|
+
const r = computeFormCs(input);
|
|
185
|
+
expect(r.capitalAllowanceClaim).toBeCloseTo(50_000, 2);
|
|
186
|
+
expect(r.chargeableIncomeBeforeLosses).toBeCloseTo(0, 2);
|
|
187
|
+
expect(r.unabsorbedCapitalAllowances).toBeCloseTo(30_000, 2);
|
|
188
|
+
});
|
|
189
|
+
it('15. adjusted profit $0 (zero), CA $30K -> claim $0, carry forward $30K', () => {
|
|
190
|
+
const input = makeInput({
|
|
191
|
+
accountingProfit: 0,
|
|
192
|
+
capitalAllowances: { currentYearClaim: 30_000, balanceBroughtForward: 0 },
|
|
193
|
+
});
|
|
194
|
+
const r = computeFormCs(input);
|
|
195
|
+
expect(r.capitalAllowanceClaim).toBeCloseTo(0, 2);
|
|
196
|
+
expect(r.unabsorbedCapitalAllowances).toBeCloseTo(30_000, 2);
|
|
197
|
+
});
|
|
198
|
+
it('16. negative adjusted profit: no CA claimed, full CA carried forward', () => {
|
|
199
|
+
// profit = -20K, addBacks = 0, deductions = 0 -> adjustedProfit = -20K
|
|
200
|
+
const input = makeInput({
|
|
201
|
+
accountingProfit: -20_000,
|
|
202
|
+
capitalAllowances: { currentYearClaim: 30_000, balanceBroughtForward: 10_000 },
|
|
203
|
+
});
|
|
204
|
+
const r = computeFormCs(input);
|
|
205
|
+
expect(r.capitalAllowanceClaim).toBeCloseTo(0, 2);
|
|
206
|
+
expect(r.unabsorbedCapitalAllowances).toBeCloseTo(40_000, 2);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
// ── Enhanced deductions ─────────────────────────────────────────
|
|
210
|
+
describe('Enhanced deductions', () => {
|
|
211
|
+
it('17. R&D expenditure $10K at 2.5x -> enhanced portion = $15K', () => {
|
|
212
|
+
const input = makeInput({
|
|
213
|
+
enhancedDeductions: {
|
|
214
|
+
rdExpenditure: 10_000,
|
|
215
|
+
rdMultiplier: 2.5,
|
|
216
|
+
ipRegistration: 0,
|
|
217
|
+
ipMultiplier: 2.0,
|
|
218
|
+
donations250Base: 0,
|
|
219
|
+
s14qRenovation: 0,
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
const r = computeFormCs(input);
|
|
223
|
+
// Enhanced R&D = 10K × (2.5 - 1) = 15K
|
|
224
|
+
expect(r.enhancedDeductionTotal).toBeCloseTo(15_000, 2);
|
|
225
|
+
// CI before losses = 100K - 0 (CA) - 15K (enhanced) = 85K
|
|
226
|
+
expect(r.chargeableIncomeBeforeLosses).toBeCloseTo(85_000, 2);
|
|
227
|
+
});
|
|
228
|
+
it('18. R&D expenditure $10K at 4.0x -> enhanced portion = $30K', () => {
|
|
229
|
+
const input = makeInput({
|
|
230
|
+
enhancedDeductions: {
|
|
231
|
+
rdExpenditure: 10_000,
|
|
232
|
+
rdMultiplier: 4.0,
|
|
233
|
+
ipRegistration: 0,
|
|
234
|
+
ipMultiplier: 2.0,
|
|
235
|
+
donations250Base: 0,
|
|
236
|
+
s14qRenovation: 0,
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
const r = computeFormCs(input);
|
|
240
|
+
expect(r.enhancedDeductionTotal).toBeCloseTo(30_000, 2);
|
|
241
|
+
});
|
|
242
|
+
it('19. IP registration $5K at 2.0x -> enhanced = $5K', () => {
|
|
243
|
+
const input = makeInput({
|
|
244
|
+
enhancedDeductions: {
|
|
245
|
+
rdExpenditure: 0,
|
|
246
|
+
rdMultiplier: 2.5,
|
|
247
|
+
ipRegistration: 5_000,
|
|
248
|
+
ipMultiplier: 2.0,
|
|
249
|
+
donations250Base: 0,
|
|
250
|
+
s14qRenovation: 0,
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
const r = computeFormCs(input);
|
|
254
|
+
// IP enhanced = 5K × (2.0 - 1) = 5K
|
|
255
|
+
expect(r.enhancedDeductionTotal).toBeCloseTo(5_000, 2);
|
|
256
|
+
});
|
|
257
|
+
it('20. donations 250%: base $10K -> enhanced = $15K', () => {
|
|
258
|
+
const input = makeInput({
|
|
259
|
+
enhancedDeductions: {
|
|
260
|
+
rdExpenditure: 0,
|
|
261
|
+
rdMultiplier: 2.5,
|
|
262
|
+
ipRegistration: 0,
|
|
263
|
+
ipMultiplier: 2.0,
|
|
264
|
+
donations250Base: 10_000,
|
|
265
|
+
s14qRenovation: 0,
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
const r = computeFormCs(input);
|
|
269
|
+
// Donation enhanced = 10K × (2.5 - 1) = 15K
|
|
270
|
+
expect(r.enhancedDeductionTotal).toBeCloseTo(15_000, 2);
|
|
271
|
+
});
|
|
272
|
+
it('21. S14Q renovation $20K -> enhanced = $20K', () => {
|
|
273
|
+
const input = makeInput({
|
|
274
|
+
enhancedDeductions: {
|
|
275
|
+
rdExpenditure: 0,
|
|
276
|
+
rdMultiplier: 2.5,
|
|
277
|
+
ipRegistration: 0,
|
|
278
|
+
ipMultiplier: 2.0,
|
|
279
|
+
donations250Base: 0,
|
|
280
|
+
s14qRenovation: 20_000,
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
const r = computeFormCs(input);
|
|
284
|
+
expect(r.enhancedDeductionTotal).toBeCloseTo(20_000, 2);
|
|
285
|
+
});
|
|
286
|
+
it('22. enhanced claims capped at remaining CI', () => {
|
|
287
|
+
// profit $30K, CA $10K -> remaining $20K, but enhanced total = $30K
|
|
288
|
+
const input = makeInput({
|
|
289
|
+
accountingProfit: 30_000,
|
|
290
|
+
capitalAllowances: { currentYearClaim: 10_000, balanceBroughtForward: 0 },
|
|
291
|
+
enhancedDeductions: {
|
|
292
|
+
rdExpenditure: 20_000,
|
|
293
|
+
rdMultiplier: 2.5,
|
|
294
|
+
ipRegistration: 0,
|
|
295
|
+
ipMultiplier: 2.0,
|
|
296
|
+
donations250Base: 0,
|
|
297
|
+
s14qRenovation: 0,
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
const r = computeFormCs(input);
|
|
301
|
+
// Adjusted profit = 30K, after CA = 20K, enhanced total = 20K×(2.5-1) = 30K
|
|
302
|
+
// Capped at 20K
|
|
303
|
+
expect(r.enhancedDeductionTotal).toBeCloseTo(20_000, 2);
|
|
304
|
+
// chargeableIncomeBeforeLosses floored at 0 because remainingCI went to 0
|
|
305
|
+
// then prior CA = 0 claimed
|
|
306
|
+
expect(r.chargeableIncomeBeforeLosses).toBeCloseTo(0, 2);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
// ── Loss relief ─────────────────────────────────────────────────
|
|
310
|
+
describe('Loss relief', () => {
|
|
311
|
+
it('23. CI before losses $80K, losses b/f $50K -> lossRelief $50K, CI $30K', () => {
|
|
312
|
+
// adjustedProfit = $80K → chargeableIncomeBeforeLosses = $80K
|
|
313
|
+
const input = makeInput({
|
|
314
|
+
accountingProfit: 80_000,
|
|
315
|
+
losses: { broughtForward: 50_000 },
|
|
316
|
+
});
|
|
317
|
+
const r = computeFormCs(input);
|
|
318
|
+
expect(r.chargeableIncomeBeforeLosses).toBeCloseTo(80_000, 2);
|
|
319
|
+
expect(r.lossRelief).toBeCloseTo(50_000, 2);
|
|
320
|
+
expect(r.chargeableIncome).toBeCloseTo(30_000, 2);
|
|
321
|
+
});
|
|
322
|
+
it('24. CI before losses $30K, losses b/f $50K -> lossRelief $30K', () => {
|
|
323
|
+
const input = makeInput({
|
|
324
|
+
accountingProfit: 30_000,
|
|
325
|
+
losses: { broughtForward: 50_000 },
|
|
326
|
+
});
|
|
327
|
+
const r = computeFormCs(input);
|
|
328
|
+
expect(r.lossRelief).toBeCloseTo(30_000, 2);
|
|
329
|
+
expect(r.chargeableIncome).toBeCloseTo(0, 2);
|
|
330
|
+
expect(r.unabsorbedLosses).toBeCloseTo(20_000, 2);
|
|
331
|
+
});
|
|
332
|
+
it('25. CI before losses $0 -> no loss relief, full carry forward', () => {
|
|
333
|
+
const input = makeInput({
|
|
334
|
+
accountingProfit: 0,
|
|
335
|
+
losses: { broughtForward: 50_000 },
|
|
336
|
+
});
|
|
337
|
+
const r = computeFormCs(input);
|
|
338
|
+
expect(r.lossRelief).toBeCloseTo(0, 2);
|
|
339
|
+
expect(r.unabsorbedLosses).toBeCloseTo(50_000, 2);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
// ── Donation relief ─────────────────────────────────────────────
|
|
343
|
+
describe('Donation relief', () => {
|
|
344
|
+
it('26. CI after losses $50K, donations base $10K (x250% = $25K) -> relief $25K', () => {
|
|
345
|
+
const input = makeInput({
|
|
346
|
+
accountingProfit: 50_000,
|
|
347
|
+
enhancedDeductions: {
|
|
348
|
+
rdExpenditure: 0,
|
|
349
|
+
rdMultiplier: 2.5,
|
|
350
|
+
ipRegistration: 0,
|
|
351
|
+
ipMultiplier: 2.0,
|
|
352
|
+
donations250Base: 10_000,
|
|
353
|
+
s14qRenovation: 0,
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
const r = computeFormCs(input);
|
|
357
|
+
// Enhanced deduction from donations: 10K × (2.5-1) = 15K
|
|
358
|
+
// Adjusted profit = 50K, after enhanced = 50K - 15K = 35K (chargeableIncomeBeforeLosses)
|
|
359
|
+
// ciAfterLosses = 35K
|
|
360
|
+
// Donation relief: current donation 250% = 10K × 2.5 = 25K
|
|
361
|
+
// Relief = min(25K, 35K) = 25K
|
|
362
|
+
expect(r.donationRelief).toBeCloseTo(25_000, 2);
|
|
363
|
+
// chargeableIncome = 35K - 25K = 10K
|
|
364
|
+
expect(r.chargeableIncome).toBeCloseTo(10_000, 2);
|
|
365
|
+
});
|
|
366
|
+
it('27. CI after losses $20K, donations available $30K -> relief capped at $20K', () => {
|
|
367
|
+
const input = makeInput({
|
|
368
|
+
accountingProfit: 20_000,
|
|
369
|
+
enhancedDeductions: {
|
|
370
|
+
rdExpenditure: 0,
|
|
371
|
+
rdMultiplier: 2.5,
|
|
372
|
+
ipRegistration: 0,
|
|
373
|
+
ipMultiplier: 2.0,
|
|
374
|
+
donations250Base: 12_000, // 12K × 2.5 = 30K
|
|
375
|
+
s14qRenovation: 0,
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
const r = computeFormCs(input);
|
|
379
|
+
// Enhanced deduction: 12K × (2.5-1) = 18K, capped at 20K remaining = 18K
|
|
380
|
+
// chargeableIncomeBeforeLosses = 20K - 18K = 2K
|
|
381
|
+
// Donation relief: 12K × 2.5 = 30K, capped at 2K
|
|
382
|
+
expect(r.donationRelief).toBeCloseTo(2_000, 2);
|
|
383
|
+
expect(r.chargeableIncome).toBeCloseTo(0, 2);
|
|
384
|
+
});
|
|
385
|
+
it('28. prior donations b/f $5K + current $25K -> uses current first', () => {
|
|
386
|
+
const input = makeInput({
|
|
387
|
+
accountingProfit: 100_000,
|
|
388
|
+
enhancedDeductions: {
|
|
389
|
+
rdExpenditure: 0,
|
|
390
|
+
rdMultiplier: 2.5,
|
|
391
|
+
ipRegistration: 0,
|
|
392
|
+
ipMultiplier: 2.0,
|
|
393
|
+
donations250Base: 10_000, // 10K × 2.5 = 25K
|
|
394
|
+
s14qRenovation: 0,
|
|
395
|
+
},
|
|
396
|
+
donationsCarryForward: { broughtForward: 5_000 },
|
|
397
|
+
});
|
|
398
|
+
const r = computeFormCs(input);
|
|
399
|
+
// Enhanced deduction: 10K × (2.5-1) = 15K
|
|
400
|
+
// Adjusted profit = 100K, after enhanced = 85K
|
|
401
|
+
// Donation relief: current = 10K × 2.5 = 25K, + prior = 5K = 30K total
|
|
402
|
+
// CI after losses = 85K, donation relief = min(30K, 85K) = 30K
|
|
403
|
+
expect(r.donationRelief).toBeCloseTo(30_000, 2);
|
|
404
|
+
// Chargeable income = 85K - 30K = 55K
|
|
405
|
+
expect(r.chargeableIncome).toBeCloseTo(55_000, 2);
|
|
406
|
+
// All prior donations used
|
|
407
|
+
expect(r.unabsorbedDonations).toBeCloseTo(0, 2);
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
// ── Chargeable income floor ─────────────────────────────────────
|
|
411
|
+
describe('Chargeable income floor', () => {
|
|
412
|
+
it('29. all reliefs exceed income -> chargeableIncome = 0', () => {
|
|
413
|
+
const input = makeInput({
|
|
414
|
+
accountingProfit: 10_000,
|
|
415
|
+
capitalAllowances: { currentYearClaim: 5_000, balanceBroughtForward: 0 },
|
|
416
|
+
losses: { broughtForward: 20_000 },
|
|
417
|
+
});
|
|
418
|
+
const r = computeFormCs(input);
|
|
419
|
+
// Adjusted profit = 10K, CA = 5K, CI before losses = 5K
|
|
420
|
+
// Losses = 20K but capped at 5K
|
|
421
|
+
expect(r.chargeableIncome).toBeCloseTo(0, 2);
|
|
422
|
+
expect(r.grossTax).toBeCloseTo(0, 2);
|
|
423
|
+
expect(r.netTaxPayable).toBeCloseTo(0, 2);
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
// ── Tax computation ─────────────────────────────────────────────
|
|
427
|
+
describe('Tax computation', () => {
|
|
428
|
+
it('30. CI $200K, PTE -> exempt $102,500, taxable $97,500, grossTax $16,575', () => {
|
|
429
|
+
const input = makeInput({ accountingProfit: 200_000 });
|
|
430
|
+
const r = computeFormCs(input);
|
|
431
|
+
expect(r.chargeableIncome).toBeCloseTo(200_000, 2);
|
|
432
|
+
// PTE: 10K×75% = 7,500 + 190K×50% = 95,000 → 102,500
|
|
433
|
+
expect(r.exemptAmount).toBeCloseTo(102_500, 2);
|
|
434
|
+
expect(r.taxableIncome).toBeCloseTo(97_500, 2);
|
|
435
|
+
expect(r.grossTax).toBeCloseTo(16_575, 2);
|
|
436
|
+
});
|
|
437
|
+
it('31. CI $200K, SUTE -> exempt $125,000, taxable $75,000, grossTax $12,750', () => {
|
|
438
|
+
const input = makeInput({ accountingProfit: 200_000, exemptionType: 'sute' });
|
|
439
|
+
const r = computeFormCs(input);
|
|
440
|
+
expect(r.exemptAmount).toBeCloseTo(125_000, 2);
|
|
441
|
+
expect(r.taxableIncome).toBeCloseTo(75_000, 2);
|
|
442
|
+
expect(r.grossTax).toBeCloseTo(12_750, 2);
|
|
443
|
+
});
|
|
444
|
+
it('32. CI $200K, none -> exempt $0, taxable $200K, grossTax $34,000', () => {
|
|
445
|
+
const input = makeInput({ accountingProfit: 200_000, exemptionType: 'none' });
|
|
446
|
+
const r = computeFormCs(input);
|
|
447
|
+
expect(r.exemptAmount).toBeCloseTo(0, 2);
|
|
448
|
+
expect(r.taxableIncome).toBeCloseTo(200_000, 2);
|
|
449
|
+
expect(r.grossTax).toBeCloseTo(34_000, 2);
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
// ── CIT rebate integration ──────────────────────────────────────
|
|
453
|
+
describe('CIT rebate integration', () => {
|
|
454
|
+
it('33. YA 2026, grossTax $10,000 -> rebate $4,000 (40%)', () => {
|
|
455
|
+
// Need taxableIncome to produce grossTax ~ $10K: $10K / 0.17 = 58,823.53
|
|
456
|
+
// But easier: set CI high with 'none' exemption so taxable = CI
|
|
457
|
+
// $10K / 0.17 = 58,823.5294... Use exact: accountingProfit where taxable = profit
|
|
458
|
+
// With none exemption: grossTax = profit × 0.17
|
|
459
|
+
// Want grossTax = 10K → profit = 10K / 0.17 ≈ 58823.53 (not exact)
|
|
460
|
+
// Simpler: just verify the rebate in an end-to-end scenario
|
|
461
|
+
// Profit $100K PTE → grossTax = $8,075 → rebate = $8,075 × 0.40 = $3,230
|
|
462
|
+
const r = computeFormCs(makeInput({ ya: 2026 }));
|
|
463
|
+
expect(r.grossTax).toBeCloseTo(8_075, 2);
|
|
464
|
+
expect(r.citRebate).toBeCloseTo(3_230, 2);
|
|
465
|
+
});
|
|
466
|
+
it('34. YA 2024, grossTax $100K -> rebate $40,000 (50%, capped)', () => {
|
|
467
|
+
// profit $1M, none exemption → taxable = $1M, grossTax = $170K
|
|
468
|
+
// rebate = min(170K × 0.50, 40K) = 40K
|
|
469
|
+
const input = makeInput({
|
|
470
|
+
ya: 2024,
|
|
471
|
+
accountingProfit: 1_000_000,
|
|
472
|
+
exemptionType: 'none',
|
|
473
|
+
});
|
|
474
|
+
const r = computeFormCs(input);
|
|
475
|
+
expect(r.grossTax).toBeCloseTo(170_000, 2);
|
|
476
|
+
expect(r.citRebate).toBeCloseTo(40_000, 2);
|
|
477
|
+
});
|
|
478
|
+
it('35. YA 2021, grossTax $10,000 -> rebate $0', () => {
|
|
479
|
+
const input = makeInput({ ya: 2021, exemptionType: 'none' });
|
|
480
|
+
const r = computeFormCs(input);
|
|
481
|
+
expect(r.citRebate).toBeCloseTo(0, 2);
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
// ── Net tax payable ─────────────────────────────────────────────
|
|
485
|
+
describe('Net tax payable', () => {
|
|
486
|
+
it('36. gross tax $8,075 minus rebate -> final net tax', () => {
|
|
487
|
+
const r = computeFormCs(makeInput());
|
|
488
|
+
// grossTax = 8,075, rebate = 3,230
|
|
489
|
+
expect(r.netTaxPayable).toBeCloseTo(4_845, 2);
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
// ── Carry-forwards ──────────────────────────────────────────────
|
|
493
|
+
describe('Carry-forwards', () => {
|
|
494
|
+
it('37. current year loss: adjusted profit negative -> adds to unabsorbedLosses', () => {
|
|
495
|
+
const input = makeInput({
|
|
496
|
+
accountingProfit: -50_000,
|
|
497
|
+
losses: { broughtForward: 10_000 },
|
|
498
|
+
});
|
|
499
|
+
const r = computeFormCs(input);
|
|
500
|
+
// adjustedProfit = -50K (loss), currentYearLoss = 50K
|
|
501
|
+
// unabsorbedLosses = 10K (b/f, none used) + 50K (current) = 60K
|
|
502
|
+
expect(r.unabsorbedLosses).toBeCloseTo(60_000, 2);
|
|
503
|
+
});
|
|
504
|
+
it('38. excess CA -> unabsorbedCapitalAllowances', () => {
|
|
505
|
+
const input = makeInput({
|
|
506
|
+
accountingProfit: 20_000,
|
|
507
|
+
capitalAllowances: { currentYearClaim: 50_000, balanceBroughtForward: 15_000 },
|
|
508
|
+
});
|
|
509
|
+
const r = computeFormCs(input);
|
|
510
|
+
// Adjusted profit = 20K, current CA claim = 20K, excess = 30K
|
|
511
|
+
// Prior CA claim = 0 (remainingCI = 0), excess = 15K
|
|
512
|
+
// Total unabsorbed CA = 30K + 15K = 45K
|
|
513
|
+
expect(r.unabsorbedCapitalAllowances).toBeCloseTo(45_000, 2);
|
|
514
|
+
});
|
|
515
|
+
it('39. excess donations -> unabsorbedDonations', () => {
|
|
516
|
+
const input = makeInput({
|
|
517
|
+
accountingProfit: 10_000,
|
|
518
|
+
donationsCarryForward: { broughtForward: 20_000 },
|
|
519
|
+
enhancedDeductions: {
|
|
520
|
+
rdExpenditure: 0,
|
|
521
|
+
rdMultiplier: 2.5,
|
|
522
|
+
ipRegistration: 0,
|
|
523
|
+
ipMultiplier: 2.0,
|
|
524
|
+
donations250Base: 0,
|
|
525
|
+
s14qRenovation: 0,
|
|
526
|
+
},
|
|
527
|
+
});
|
|
528
|
+
const r = computeFormCs(input);
|
|
529
|
+
// CI = 10K, donation available = 0 (current) + 20K (b/f) = 20K
|
|
530
|
+
// Relief = min(20K, 10K) = 10K
|
|
531
|
+
// Current donation used = min(0, 10K) = 0
|
|
532
|
+
// Prior donation used = 10K - 0 = 10K
|
|
533
|
+
// Unabsorbed = 20K - 10K = 10K
|
|
534
|
+
expect(r.donationRelief).toBeCloseTo(10_000, 2);
|
|
535
|
+
expect(r.unabsorbedDonations).toBeCloseTo(10_000, 2);
|
|
536
|
+
});
|
|
537
|
+
it('40. no excess -> all carry-forwards zero', () => {
|
|
538
|
+
const r = computeFormCs(makeInput());
|
|
539
|
+
expect(r.unabsorbedLosses).toBeCloseTo(0, 2);
|
|
540
|
+
expect(r.unabsorbedCapitalAllowances).toBeCloseTo(0, 2);
|
|
541
|
+
expect(r.unabsorbedDonations).toBeCloseTo(0, 2);
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
// ── Full end-to-end scenarios ───────────────────────────────────
|
|
545
|
+
describe('Full end-to-end scenarios', () => {
|
|
546
|
+
it('41. simple SMB: revenue $800K, profit $120K, depreciation + entertainment, PTE, YA 2026', () => {
|
|
547
|
+
let input = withAddBacks({ depreciation: 15_000, entertainment: 3_000 }, { revenue: 800_000, accountingProfit: 120_000 });
|
|
548
|
+
const r = computeFormCs(input);
|
|
549
|
+
expect(r.formType).toBe('C-S');
|
|
550
|
+
expect(r.eligible).toBe(true);
|
|
551
|
+
// Add-backs: 15K + 3K = 18K
|
|
552
|
+
expect(r.totalAddBacks).toBeCloseTo(18_000, 2);
|
|
553
|
+
// Adjusted profit: 120K + 18K = 138K
|
|
554
|
+
expect(r.adjustedProfit).toBeCloseTo(138_000, 2);
|
|
555
|
+
// No CA, no enhanced, no losses, no donations
|
|
556
|
+
expect(r.chargeableIncome).toBeCloseTo(138_000, 2);
|
|
557
|
+
// PTE: 10K×75% = 7,500 + 128K×50% = 64,000 → 71,500 exempt
|
|
558
|
+
// (128K because min(128K, 190K) for second band)
|
|
559
|
+
expect(r.exemptAmount).toBeCloseTo(71_500, 2);
|
|
560
|
+
// Taxable: 138K - 71,500 = 66,500
|
|
561
|
+
expect(r.taxableIncome).toBeCloseTo(66_500, 2);
|
|
562
|
+
// Gross tax: 66,500 × 0.17 = 11,305
|
|
563
|
+
expect(r.grossTax).toBeCloseTo(11_305, 2);
|
|
564
|
+
// Rebate YA 2026: 11,305 × 0.40 = 4,522
|
|
565
|
+
expect(r.citRebate).toBeCloseTo(4_522, 2);
|
|
566
|
+
// Net tax: 11,305 - 4,522 = 6,783
|
|
567
|
+
expect(r.netTaxPayable).toBeCloseTo(6_783, 2);
|
|
568
|
+
});
|
|
569
|
+
it('42. loss-making company: revenue $300K, loss -$50K, depreciation $10K', () => {
|
|
570
|
+
let input = withAddBacks({ depreciation: 10_000 }, { revenue: 300_000, accountingProfit: -50_000 });
|
|
571
|
+
const r = computeFormCs(input);
|
|
572
|
+
// Adjusted profit: -50K + 10K = -40K
|
|
573
|
+
expect(r.adjustedProfit).toBeCloseTo(-40_000, 2);
|
|
574
|
+
// No CA claimed, no enhanced claimed
|
|
575
|
+
expect(r.capitalAllowanceClaim).toBeCloseTo(0, 2);
|
|
576
|
+
expect(r.chargeableIncomeBeforeLosses).toBeCloseTo(0, 2);
|
|
577
|
+
expect(r.chargeableIncome).toBeCloseTo(0, 2);
|
|
578
|
+
expect(r.grossTax).toBeCloseTo(0, 2);
|
|
579
|
+
expect(r.netTaxPayable).toBeCloseTo(0, 2);
|
|
580
|
+
// Current year loss = 40K
|
|
581
|
+
expect(r.unabsorbedLosses).toBeCloseTo(40_000, 2);
|
|
582
|
+
});
|
|
583
|
+
it('43. company with IFRS 16: ROU depr + lease interest add-back, actual payments deduction', () => {
|
|
584
|
+
let input = withAddBacks({ rouDepreciation: 20_000, leaseInterest: 5_000 }, { accountingProfit: 200_000 });
|
|
585
|
+
input = { ...input, deductions: { ...input.deductions, actualLeasePayments: 22_000 } };
|
|
586
|
+
const r = computeFormCs(input);
|
|
587
|
+
// Add-backs: 20K + 5K = 25K
|
|
588
|
+
expect(r.totalAddBacks).toBeCloseTo(25_000, 2);
|
|
589
|
+
// Deductions: 22K
|
|
590
|
+
expect(r.totalDeductions).toBeCloseTo(22_000, 2);
|
|
591
|
+
// Adjusted profit: 200K + 25K - 22K = 203K (net IFRS 16 adjustment = +3K)
|
|
592
|
+
expect(r.adjustedProfit).toBeCloseTo(203_000, 2);
|
|
593
|
+
});
|
|
594
|
+
it('44. full reliefs: profit $100K, depreciation $10K, CA $40K, losses b/f $30K, PTE, YA 2025', () => {
|
|
595
|
+
let input = withAddBacks({ depreciation: 10_000 }, {
|
|
596
|
+
ya: 2025,
|
|
597
|
+
accountingProfit: 100_000,
|
|
598
|
+
capitalAllowances: { currentYearClaim: 40_000, balanceBroughtForward: 0 },
|
|
599
|
+
losses: { broughtForward: 30_000 },
|
|
600
|
+
});
|
|
601
|
+
const r = computeFormCs(input);
|
|
602
|
+
// Adjusted profit: 100K + 10K = 110K
|
|
603
|
+
expect(r.adjustedProfit).toBeCloseTo(110_000, 2);
|
|
604
|
+
// CA: 40K claimed
|
|
605
|
+
expect(r.capitalAllowanceClaim).toBeCloseTo(40_000, 2);
|
|
606
|
+
// CI before losses: 110K - 40K = 70K
|
|
607
|
+
expect(r.chargeableIncomeBeforeLosses).toBeCloseTo(70_000, 2);
|
|
608
|
+
// Loss relief: min(30K, 70K) = 30K
|
|
609
|
+
expect(r.lossRelief).toBeCloseTo(30_000, 2);
|
|
610
|
+
// Chargeable income: 70K - 30K = 40K
|
|
611
|
+
expect(r.chargeableIncome).toBeCloseTo(40_000, 2);
|
|
612
|
+
// PTE: 10K×75% = 7,500 + 30K×50% = 15,000 → 22,500 exempt
|
|
613
|
+
expect(r.exemptAmount).toBeCloseTo(22_500, 2);
|
|
614
|
+
// Taxable: 40K - 22,500 = 17,500
|
|
615
|
+
expect(r.taxableIncome).toBeCloseTo(17_500, 2);
|
|
616
|
+
// Gross tax: 17,500 × 0.17 = 2,975
|
|
617
|
+
expect(r.grossTax).toBeCloseTo(2_975, 2);
|
|
618
|
+
// YA 2025 rebate: 50% capped at 40K → 2,975 × 0.50 = 1,487.50
|
|
619
|
+
expect(r.citRebate).toBeCloseTo(1_487.50, 2);
|
|
620
|
+
// Net tax: 2,975 - 1,487.50 = 1,487.50
|
|
621
|
+
expect(r.netTaxPayable).toBeCloseTo(1_487.50, 2);
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
// ── Output structure ────────────────────────────────────────────
|
|
625
|
+
describe('Output structure', () => {
|
|
626
|
+
it('45. schedule array has entries', () => {
|
|
627
|
+
const r = computeFormCs(makeInput());
|
|
628
|
+
expect(r.schedule.length).toBeGreaterThan(0);
|
|
629
|
+
for (const entry of r.schedule) {
|
|
630
|
+
expect(entry).toHaveProperty('label');
|
|
631
|
+
expect(entry).toHaveProperty('amount');
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
it('46. formFields for C-S has boxes 1-18 (IRAS spec)', () => {
|
|
635
|
+
const r = computeFormCs(makeInput({ revenue: 500_000 }));
|
|
636
|
+
expect(r.formType).toBe('C-S');
|
|
637
|
+
const boxes = r.formFields.map(f => f.box).sort((a, b) => a - b);
|
|
638
|
+
expect(boxes).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]);
|
|
639
|
+
});
|
|
640
|
+
it('47. formFields for C-S Lite has boxes 1-6 (IRAS spec)', () => {
|
|
641
|
+
const r = computeFormCs(makeInput({ revenue: 100_000 }));
|
|
642
|
+
expect(r.formType).toBe('C-S Lite');
|
|
643
|
+
const boxes = r.formFields.map(f => f.box).sort((a, b) => a - b);
|
|
644
|
+
expect(boxes).toEqual([1, 2, 3, 4, 5, 6]);
|
|
645
|
+
});
|
|
646
|
+
it('48. workings is a non-empty string', () => {
|
|
647
|
+
const r = computeFormCs(makeInput());
|
|
648
|
+
expect(typeof r.workings).toBe('string');
|
|
649
|
+
expect(r.workings.length).toBeGreaterThan(0);
|
|
650
|
+
expect(r.workings).toContain('TAX COMPUTATION');
|
|
651
|
+
expect(r.workings).toContain('NET TAX PAYABLE');
|
|
652
|
+
});
|
|
653
|
+
it('49. type is sg-form-cs', () => {
|
|
654
|
+
const r = computeFormCs(makeInput());
|
|
655
|
+
expect(r.type).toBe('sg-form-cs');
|
|
656
|
+
});
|
|
657
|
+
});
|
|
658
|
+
// ── Rounding ────────────────────────────────────────────────────
|
|
659
|
+
describe('Rounding', () => {
|
|
660
|
+
it('50. all amounts rounded to 2dp', () => {
|
|
661
|
+
const round2 = (n) => Math.round(n * 100) / 100;
|
|
662
|
+
// Use a scenario that produces fractional values
|
|
663
|
+
const input = makeInput({
|
|
664
|
+
accountingProfit: 33_333,
|
|
665
|
+
ya: 2025,
|
|
666
|
+
});
|
|
667
|
+
const r = computeFormCs(input);
|
|
668
|
+
expect(round2(r.accountingProfit)).toBe(r.accountingProfit);
|
|
669
|
+
expect(round2(r.totalAddBacks)).toBe(r.totalAddBacks);
|
|
670
|
+
expect(round2(r.totalDeductions)).toBe(r.totalDeductions);
|
|
671
|
+
expect(round2(r.adjustedProfit)).toBe(r.adjustedProfit);
|
|
672
|
+
expect(round2(r.capitalAllowanceClaim)).toBe(r.capitalAllowanceClaim);
|
|
673
|
+
expect(round2(r.enhancedDeductionTotal)).toBe(r.enhancedDeductionTotal);
|
|
674
|
+
expect(round2(r.chargeableIncomeBeforeLosses)).toBe(r.chargeableIncomeBeforeLosses);
|
|
675
|
+
expect(round2(r.lossRelief)).toBe(r.lossRelief);
|
|
676
|
+
expect(round2(r.donationRelief)).toBe(r.donationRelief);
|
|
677
|
+
expect(round2(r.chargeableIncome)).toBe(r.chargeableIncome);
|
|
678
|
+
expect(round2(r.exemptAmount)).toBe(r.exemptAmount);
|
|
679
|
+
expect(round2(r.taxableIncome)).toBe(r.taxableIncome);
|
|
680
|
+
expect(round2(r.grossTax)).toBe(r.grossTax);
|
|
681
|
+
expect(round2(r.citRebate)).toBe(r.citRebate);
|
|
682
|
+
expect(round2(r.netTaxPayable)).toBe(r.netTaxPayable);
|
|
683
|
+
expect(round2(r.unabsorbedLosses)).toBe(r.unabsorbedLosses);
|
|
684
|
+
expect(round2(r.unabsorbedCapitalAllowances)).toBe(r.unabsorbedCapitalAllowances);
|
|
685
|
+
expect(round2(r.unabsorbedDonations)).toBe(r.unabsorbedDonations);
|
|
686
|
+
});
|
|
687
|
+
});
|