@novha/calc-engines 1.3.3 → 1.4.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/dist/income-tax/domain/types.js +1 -1
- package/dist/income-tax/south-africa/SouthAfricaIncomeTaxService.js +3 -0
- package/dist/income-tax/south-africa/SouthAfricaIncomeTaxServiceImpl.js +79 -0
- package/dist/income-tax/south-africa/domain/types.js +3 -0
- package/dist/index.js +4 -2
- package/dist/types/income-tax/domain/types.d.ts +4 -0
- package/dist/types/income-tax/south-africa/SouthAfricaIncomeTaxService.d.ts +4 -0
- package/dist/types/income-tax/south-africa/SouthAfricaIncomeTaxServiceImpl.d.ts +14 -0
- package/dist/types/income-tax/south-africa/domain/types.d.ts +40 -0
- package/dist/types/index.d.ts +2 -0
- package/package.json +1 -1
- package/src/income-tax/domain/types.ts +5 -0
- package/src/income-tax/south-africa/SouthAfricaIncomeTaxService.ts +8 -0
- package/src/income-tax/south-africa/SouthAfricaIncomeTaxServiceImpl.ts +107 -0
- package/src/income-tax/south-africa/domain/types.ts +47 -0
- package/src/index.ts +5 -0
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvaW5jb21lLXRheC9kb21haW4vdHlwZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IFJ1bGVNZXRhIH0gZnJvbSBcIi4uLy4uL3NoYXJlZC9kb21haW4vdHlwZXNcIjtcblxuZXhwb3J0IGludGVyZmFjZSBSdWxlSW5wdXQge1xuICAgIG5hbWU6IHN0cmluZztcbiAgICB0eXBlOiAnbnVtYmVyJyB8ICdzZWxlY3QnIHwgJ3RleHQnO1xuICAgIHJlcXVpcmVkOiBib29sZWFuO1xuICAgIHVuaXQ/
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvaW5jb21lLXRheC9kb21haW4vdHlwZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IFJ1bGVNZXRhIH0gZnJvbSBcIi4uLy4uL3NoYXJlZC9kb21haW4vdHlwZXNcIjtcblxuZXhwb3J0IGludGVyZmFjZSBSdWxlSW5wdXQge1xuICAgIG5hbWU6IHN0cmluZztcbiAgICB0eXBlOiAnbnVtYmVyJyB8ICdzZWxlY3QnIHwgJ3RleHQnO1xuICAgIHJlcXVpcmVkOiBib29sZWFuO1xuICAgIHVuaXQ/OiBzdHJpbmc7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUnVsZU91dHB1dCB7XG4gICAgbmFtZTogc3RyaW5nO1xuICAgIHR5cGU6ICdudW1iZXInIHwgJ3N0cmluZyc7XG4gICAgdW5pdD86IHN0cmluZztcbn1cblxuZXhwb3J0IGludGVyZmFjZSBUYXhCcmFja2V0IHtcbiAgICBmcm9tOiBudW1iZXI7XG4gICAgdG86IG51bWJlciB8IG51bGw7XG4gICAgcmF0ZTogbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEluY29tZVRheENhbGN1bGF0b3JTY2hlbWE8VD4ge1xuICAgIG1ldGE6IFJ1bGVNZXRhO1xuICAgIGlucHV0czogUnVsZUlucHV0W107XG4gICAgb3V0cHV0czogUnVsZU91dHB1dFtdO1xuICAgIHJ1bGVzOiBUO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEFnZUJhc2VkUmViYXRlIHtcbiAgYWdlTWluOiBudW1iZXI7XG4gIGFtb3VudDogbnVtYmVyO1xufSJdfQ==
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU291dGhBZnJpY2FJbmNvbWVUYXhTZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2luY29tZS10YXgvc291dGgtYWZyaWNhL1NvdXRoQWZyaWNhSW5jb21lVGF4U2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29tcHV0ZWRJbmNvbWVUYXhWYWx1ZXMsIEluY29tZVRheFJ1bGVzIH0gZnJvbSBcIi4vZG9tYWluL3R5cGVzXCI7XG5cbmV4cG9ydCBpbnRlcmZhY2UgU291dGhBZnJpY2FJbmNvbWVUYXhTZXJ2aWNlIHtcbiAgICBjYWxjdWxhdGVOZXRJbmNvbWUoXG4gICAgICAgIGluY29tZTogbnVtYmVyLFxuICAgICAgICBydWxlczogSW5jb21lVGF4UnVsZXMsXG4gICAgKTogQ29tcHV0ZWRJbmNvbWVUYXhWYWx1ZXM7XG59Il19
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SouthAfricaIncomeTaxServiceImpl = void 0;
|
|
4
|
+
class SouthAfricaIncomeTaxServiceImpl {
|
|
5
|
+
constructor(income, age, rules) {
|
|
6
|
+
this._income = income;
|
|
7
|
+
this._age = age;
|
|
8
|
+
this._rules = rules;
|
|
9
|
+
}
|
|
10
|
+
calculateNetIncome() {
|
|
11
|
+
const grossIncome = this._income;
|
|
12
|
+
const threshold = this.getTaxThreshold(this._age);
|
|
13
|
+
if (this._income <= threshold) {
|
|
14
|
+
return {
|
|
15
|
+
grossIncome,
|
|
16
|
+
incomeTax: 0,
|
|
17
|
+
uif: 0,
|
|
18
|
+
totalDeductions: 0,
|
|
19
|
+
netIncome: grossIncome,
|
|
20
|
+
effectiveTaxRate: 0,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const grossTax = this.calculateBracketTax(this._income);
|
|
24
|
+
const rebate = this.getRebate(this._age);
|
|
25
|
+
const incomeTax = Math.max(0, grossTax - rebate);
|
|
26
|
+
const uif = this.calculateUif(this._income);
|
|
27
|
+
const totalDeductions = incomeTax + uif;
|
|
28
|
+
const netIncome = grossIncome - totalDeductions;
|
|
29
|
+
const effectiveTaxRate = incomeTax / grossIncome;
|
|
30
|
+
return {
|
|
31
|
+
grossIncome,
|
|
32
|
+
incomeTax: this.round(incomeTax),
|
|
33
|
+
uif: this.round(uif),
|
|
34
|
+
totalDeductions: this.round(totalDeductions),
|
|
35
|
+
netIncome: this.round(netIncome),
|
|
36
|
+
effectiveTaxRate: this.round(effectiveTaxRate, 4),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
calculateBracketTax(income) {
|
|
40
|
+
let tax = 0;
|
|
41
|
+
for (const bracket of this._rules.taxBrackets) {
|
|
42
|
+
if (income <= bracket.from)
|
|
43
|
+
break;
|
|
44
|
+
const upper = bracket.to ?? income;
|
|
45
|
+
const taxableAmount = Math.min(upper, income) - bracket.from;
|
|
46
|
+
if (taxableAmount > 0) {
|
|
47
|
+
tax += taxableAmount * bracket.rate;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return tax;
|
|
51
|
+
}
|
|
52
|
+
getTaxThreshold(age) {
|
|
53
|
+
const thresholds = this._rules.taxThresholds;
|
|
54
|
+
if (age >= 75)
|
|
55
|
+
return thresholds.age75Plus;
|
|
56
|
+
if (age >= 65)
|
|
57
|
+
return thresholds.age65To74;
|
|
58
|
+
return thresholds.under65;
|
|
59
|
+
}
|
|
60
|
+
getRebate(age) {
|
|
61
|
+
let rebate = this._rules.rebates.primary.amount;
|
|
62
|
+
if (age >= this._rules.rebates.secondary.ageMin) {
|
|
63
|
+
rebate += this._rules.rebates.secondary.amount;
|
|
64
|
+
}
|
|
65
|
+
if (age >= this._rules.rebates.tertiary.ageMin) {
|
|
66
|
+
rebate += this._rules.rebates.tertiary.amount;
|
|
67
|
+
}
|
|
68
|
+
return rebate;
|
|
69
|
+
}
|
|
70
|
+
calculateUif(income) {
|
|
71
|
+
const cappedIncome = Math.min(income, this._rules.uif.annualIncomeCap);
|
|
72
|
+
return Math.min(cappedIncome * this._rules.uif.rate, this._rules.uif.maxAnnualContribution);
|
|
73
|
+
}
|
|
74
|
+
round(value, decimals = 2) {
|
|
75
|
+
return Number(value.toFixed(decimals));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.SouthAfricaIncomeTaxServiceImpl = SouthAfricaIncomeTaxServiceImpl;
|
|
79
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU291dGhBZnJpY2FJbmNvbWVUYXhTZXJ2aWNlSW1wbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9pbmNvbWUtdGF4L3NvdXRoLWFmcmljYS9Tb3V0aEFmcmljYUluY29tZVRheFNlcnZpY2VJbXBsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUdBLE1BQWEsK0JBQStCO0lBS3hDLFlBQVksTUFBYyxFQUFFLEdBQVcsRUFBRSxLQUFxQjtRQUMxRCxJQUFJLENBQUMsT0FBTyxHQUFHLE1BQU0sQ0FBQztRQUN0QixJQUFJLENBQUMsSUFBSSxHQUFHLEdBQUcsQ0FBQztRQUNoQixJQUFJLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQztJQUN4QixDQUFDO0lBRUQsa0JBQWtCO1FBQ2QsTUFBTSxXQUFXLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQztRQUVqQyxNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUVsRCxJQUFJLElBQUksQ0FBQyxPQUFPLElBQUksU0FBUyxFQUFFLENBQUM7WUFDNUIsT0FBTztnQkFDSCxXQUFXO2dCQUNYLFNBQVMsRUFBRSxDQUFDO2dCQUNaLEdBQUcsRUFBRSxDQUFDO2dCQUNOLGVBQWUsRUFBRSxDQUFDO2dCQUNsQixTQUFTLEVBQUUsV0FBVztnQkFDdEIsZ0JBQWdCLEVBQUUsQ0FBQzthQUN0QixDQUFDO1FBQ04sQ0FBQztRQUVELE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUM7UUFFeEQsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDekMsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsUUFBUSxHQUFHLE1BQU0sQ0FBQyxDQUFDO1FBRWpELE1BQU0sR0FBRyxH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QUVPLEtBQUssQ0FBQyxLQUFhLEVBQUUsUUFBUSxHQUFHLENBQUM7UUFDckMsT0FBTyxNQUFNLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDO0lBQzNDLENBQUM7Q0FDSjtBQXZHRCwwRUF1R0MiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb21wdXRlZEluY29tZVRheFZhbHVlcywgSW5jb21lVGF4UnVsZXMgfSBmcm9tIFwiLi9kb21haW4vdHlwZXNcIjtcbmltcG9ydCB7IFNvdXRoQWZyaWNhSW5jb21lVGF4U2VydmljZSB9IGZyb20gXCIuL1NvdXRoQWZyaWNhSW5jb21lVGF4U2VydmljZVwiO1xuXG5leHBvcnQgY2xhc3MgU291dGhBZnJpY2FJbmNvbWVUYXhTZXJ2aWNlSW1wbCBpbXBsZW1lbnRzIFNvdXRoQWZyaWNhSW5jb21lVGF4U2VydmljZSB7XG4gICAgcHJpdmF0ZSBfaW5jb21lOiBudW1iZXI7XG4gICAgcHJpdmF0ZSBfYWdlOiBudW1iZXI7XG4gICAgcHJpdmF0ZSBfcnVsZXM6IEluY29tZVRheFJ1bGVzO1xuXG4gICAgY29uc3RydWN0b3IoaW5jb21lOiBudW1iZXIsIGFnZTogbnVtYmVyLCBydWxlczogSW5jb21lVGF4UnVsZXMpIHtcbiAgICAgICAgdGhpcy5faW5jb21lID0gaW5jb21lO1xuICAgICAgICB0aGlzLl9hZ2UgPSBhZ2U7XG4gICAgICAgIHRoaXMuX3J1bGVzID0gcnVsZXM7XG4gICAgfVxuXG4gICAgY2FsY3VsYXRlTmV0SW5jb21lKCk6IENvbXB1dGVkSW5jb21lVGF4VmFsdWVzIHtcbiAgICAgICAgY29uc3QgZ3Jvc3NJbmNvbWUgPSB0aGlzLl9pbmNvbWU7XG5cbiAgICAgICAgY29uc3QgdGhyZXNob2xkID0gdGhpcy5nZXRUYXhUaHJlc2hvbGQodGhpcy5fYWdlKTtcblxuICAgICAgICBpZiAodGhpcy5faW5jb21lIDw9IHRocmVzaG9sZCkge1xuICAgICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgICAgICBncm9zc0luY29tZSxcbiAgICAgICAgICAgICAgICBpbmNvbWVUYXg6IDAsXG4gICAgICAgICAgICAgICAgdWlmOiAwLFxuICAgICAgICAgICAgICAgIHRvdGFsRGVkdWN0aW9uczogMCxcbiAgICAgICAgICAgICAgICBuZXRJbmNvbWU6IGdyb3NzSW5jb21lLFxuICAgICAgICAgICAgICAgIGVmZmVjdGl2ZVRheFJhdGU6IDAsXG4gICAgICAgICAgICB9O1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgZ3Jvc3NUYXggPSB0aGlzLmNhbGN1bGF0ZUJyYWNrZXRUYXgodGhpcy5faW5jb21lKTtcblxuICAgICAgICBjb25zdCByZWJhdGUgPSB0aGlzLmdldFJlYmF0ZSh0aGlzLl9hZ2UpO1xuICAgICAgICBjb25zdCBpbmNvbWVUYXggPSBNYXRoLm1heCgwLCBncm9zc1RheCAtIHJlYmF0ZSk7XG5cbiAgICAgICAgY29uc3QgdWlmID0gdGhpcy5jYWxjdWxhdGVVaWYodGhpcy5faW5jb21lKTtcblxuICAgICAgICBjb25zdCB0b3RhbERlZHVjdGlvbnMgPSBpbmNvbWVUYXggKyB1aWY7XG4gICAgICAgIGNvbnN0IG5ldEluY29tZSA9IGdyb3NzSW5jb21lIC0gdG90YWxEZWR1Y3Rpb25zO1xuICAgICAgICBjb25zdCBlZmZlY3RpdmVUYXhSYXRlID0gaW5jb21lVGF4IC8gZ3Jvc3NJbmNvbWU7XG5cbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIGdyb3NzSW5jb21lLFxuICAgICAgICAgICAgaW5jb21lVGF4OiB0aGlzLnJvdW5kKGluY29tZVRheCksXG4gICAgICAgICAgICB1aWY6IHRoaXMucm91bmQodWlmKSxcbiAgICAgICAgICAgIHRvdGFsRGVkdWN0aW9uczogdGhpcy5yb3VuZCh0b3RhbERlZHVjdGlvbnMpLFxuICAgICAgICAgICAgbmV0SW5jb21lOiB0aGlzLnJvdW5kKG5ldEluY29tZSksXG4gICAgICAgICAgICBlZmZlY3RpdmVUYXhSYXRlOiB0aGlzLnJvdW5kKGVmZmVjdGl2ZVRheFJhdGUsIDQpLFxuICAgICAgICB9O1xuICAgIH1cblxuICAgIHByaXZhdGUgY2FsY3VsYXRlQnJhY2tldFRheChpbmNvbWU6IG51bWJlcik6IG51bWJlciB7XG4gICAgICAgIGxldCB0YXggPSAwO1xuXG4gICAgICAgIGZvciAoY29uc3QgYnJhY2tldCBvZiB0aGlzLl9ydWxlcy50YXhCcmFja2V0cykge1xuICAgICAgICAgICAgaWYgKGluY29tZSA8PSBicmFja2V0LmZyb20pIGJyZWFrO1xuXG4gICAgICAgICAgICBjb25zdCB1cHBlciA9IGJyYWNrZXQudG8gPz8gaW5jb21lO1xuICAgICAgICAgICAgY29uc3QgdGF4YWJsZUFtb3VudCA9IE1hdGgubWluKHVwcGVyLCBpbmNvbWUpIC0gYnJhY2tldC5mcm9tO1xuXG4gICAgICAgICAgICBpZiAodGF4YWJsZUFtb3VudCA+IDApIHtcbiAgICAgICAgICAgICAgICB0YXggKz0gdGF4YWJsZUFtb3VudCAqIGJyYWNrZXQucmF0ZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0YXg7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBnZXRUYXhUaHJlc2hvbGQoYWdlOiBudW1iZXIpOiBudW1iZXIge1xuICAgICAgICBjb25zdCB0aHJlc2hvbGRzID0gdGhpcy5fcnVsZXMudGF4VGhyZXNob2xkcztcblxuICAgICAgICBpZiAoYWdlID49IDc1KSByZXR1cm4gdGhyZXNob2xkcy5hZ2U3NVBsdXM7XG4gICAgICAgIGlmIChhZ2UgPj0gNjUpIHJldHVybiB0aHJlc2hvbGRzLmFnZTY1VG83NDtcblxuICAgICAgICByZXR1cm4gdGhyZXNob2xkcy51bmRlcjY1O1xuICAgIH1cblxuICAgIHByaXZhdGUgZ2V0UmViYXRlKGFnZTogbnVtYmVyKTogbnVtYmVyIHtcbiAgICAgICAgbGV0IHJlYmF0ZSA9IHRoaXMuX3J1bGVzLnJlYmF0ZXMucHJpbWFyeS5hbW91bnQ7XG5cbiAgICAgICAgaWYgKGFnZSA+PSB0aGlzLl9ydWxlcy5yZWJhdGVzLnNlY29uZGFyeS5hZ2VNaW4pIHtcbiAgICAgICAgICAgIHJlYmF0ZSArPSB0aGlzLl9ydWxlcy5yZWJhdGVzLnNlY29uZGFyeS5hbW91bnQ7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoYWdlID49IHRoaXMuX3J1bGVzLnJlYmF0ZXMudGVydGlhcnkuYWdlTWluKSB7XG4gICAgICAgICAgICByZWJhdGUgKz0gdGhpcy5fcnVsZXMucmViYXRlcy50ZXJ0aWFyeS5hbW91bnQ7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gcmViYXRlO1xuICAgIH1cblxuICAgIHByaXZhdGUgY2FsY3VsYXRlVWlmKGluY29tZTogbnVtYmVyKTogbnVtYmVyIHtcbiAgICAgICAgY29uc3QgY2FwcGVkSW5jb21lID0gTWF0aC5taW4oXG4gICAgICAgICAgICBpbmNvbWUsXG4gICAgICAgICAgICB0aGlzLl9ydWxlcy51aWYuYW5udWFsSW5jb21lQ2FwLFxuICAgICAgICApO1xuXG4gICAgICAgIHJldHVybiBNYXRoLm1pbihcbiAgICAgICAgICAgIGNhcHBlZEluY29tZSAqIHRoaXMuX3J1bGVzLnVpZi5yYXRlLFxuICAgICAgICAgICAgdGhpcy5fcnVsZXMudWlmLm1heEFubnVhbENvbnRyaWJ1dGlvbixcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICBwcml2YXRlIHJvdW5kKHZhbHVlOiBudW1iZXIsIGRlY2ltYWxzID0gMik6IG51bWJlciB7XG4gICAgICAgIHJldHVybiBOdW1iZXIodmFsdWUudG9GaXhlZChkZWNpbWFscykpO1xuICAgIH1cbn0iXX0=
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvaW5jb21lLXRheC9zb3V0aC1hZnJpY2EvZG9tYWluL3R5cGVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiIiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBBZ2VCYXNlZFJlYmF0ZSwgVGF4QnJhY2tldCB9IGZyb20gXCIuLi8uLi9kb21haW4vdHlwZXNcIjtcblxuZXhwb3J0IGludGVyZmFjZSBJbmNvbWVUYXhSdWxlcyB7XG4gIHRheEJyYWNrZXRzOiBUYXhCcmFja2V0W107XG4gIHJlYmF0ZXM6IFRheFJlYmF0ZXM7XG4gIHRheFRocmVzaG9sZHM6IFRheFRocmVzaG9sZHM7XG4gIG1lZGljYWxBaWRUYXhDcmVkaXQ6IE1lZGljYWxBaWRUYXhDcmVkaXQ7XG4gIHVpZjogVWlmUnVsZXM7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgVGF4UmViYXRlcyB7XG4gIHByaW1hcnk6IEFnZUJhc2VkUmViYXRlO1xuICBzZWNvbmRhcnk6IEFnZUJhc2VkUmViYXRlO1xuICB0ZXJ0aWFyeTogQWdlQmFzZWRSZWJhdGU7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgVGF4VGhyZXNob2xkcyB7XG4gIHVuZGVyNjU6IG51bWJlcjtcbiAgYWdlNjVUbzc0OiBudW1iZXI7XG4gIGFnZTc1UGx1czogbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIE1lZGljYWxBaWRUYXhDcmVkaXQge1xuICBtb250aGx5OiBNZWRpY2FsQWlkTW9udGhseUNyZWRpdDtcbiAgYW5udWFsTXVsdGlwbGllcjogbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIE1lZGljYWxBaWRNb250aGx5Q3JlZGl0IHtcbiAgdGF4cGF5ZXI6IG51bWJlcjtcbiAgZmlyc3REZXBlbmRhbnQ6IG51bWJlcjtcbiAgYWRkaXRpb25hbERlcGVuZGFudDogbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIFVpZlJ1bGVzIHtcbiAgcmF0ZTogbnVtYmVyO1xuICBhbm51YWxJbmNvbWVDYXA6IG51bWJlcjtcbiAgbWF4QW5udWFsQ29udHJpYnV0aW9uOiBudW1iZXI7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgQ29tcHV0ZWRJbmNvbWVUYXhWYWx1ZXMge1xuICBncm9zc0luY29tZTogbnVtYmVyO1xuICBpbmNvbWVUYXg6IG51bWJlcjtcbiAgdWlmOiBudW1iZXI7XG4gIHRvdGFsRGVkdWN0aW9uczogbnVtYmVyO1xuICBuZXRJbmNvbWU6IG51bWJlcjtcbiAgZWZmZWN0aXZlVGF4UmF0ZTogbnVtYmVyO1xufVxuIl19
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.SouthAfricaMortgageService = exports.FranceIncomeTaxService = exports.CanadaMortgageService = exports.CanadaIncomeTaxService = exports.CalculatorType = void 0;
|
|
17
|
+
exports.SouthAfricaMortgageService = exports.SouthAfricaIncomeTaxService = exports.FranceIncomeTaxService = exports.CanadaMortgageService = exports.CanadaIncomeTaxService = exports.CalculatorType = void 0;
|
|
18
18
|
var types_1 = require("./shared/domain/types");
|
|
19
19
|
Object.defineProperty(exports, "CalculatorType", { enumerable: true, get: function () { return types_1.CalculatorType; } });
|
|
20
20
|
var CanadaIncomeTaxServiceImpl_1 = require("./income-tax/canada/CanadaIncomeTaxServiceImpl");
|
|
@@ -23,7 +23,9 @@ var CanadaMortgageServiceImpl_1 = require("./mortgage/canada/CanadaMortgageServi
|
|
|
23
23
|
Object.defineProperty(exports, "CanadaMortgageService", { enumerable: true, get: function () { return CanadaMortgageServiceImpl_1.CanadaMortgageServiceImpl; } });
|
|
24
24
|
var FranceIncomeTaxServiceImpl_1 = require("./income-tax/france/FranceIncomeTaxServiceImpl");
|
|
25
25
|
Object.defineProperty(exports, "FranceIncomeTaxService", { enumerable: true, get: function () { return FranceIncomeTaxServiceImpl_1.FranceIncomeTaxServiceImpl; } });
|
|
26
|
+
var SouthAfricaIncomeTaxServiceImpl_1 = require("./income-tax/south-africa/SouthAfricaIncomeTaxServiceImpl");
|
|
27
|
+
Object.defineProperty(exports, "SouthAfricaIncomeTaxService", { enumerable: true, get: function () { return SouthAfricaIncomeTaxServiceImpl_1.SouthAfricaIncomeTaxServiceImpl; } });
|
|
26
28
|
var SouthAfricaMortgageServiceImpl_1 = require("./mortgage/south-africa/SouthAfricaMortgageServiceImpl");
|
|
27
29
|
Object.defineProperty(exports, "SouthAfricaMortgageService", { enumerable: true, get: function () { return SouthAfricaMortgageServiceImpl_1.SouthAfricaMortgageServiceImpl; } });
|
|
28
30
|
__exportStar(require("./income-tax/domain/types"), exports);
|
|
29
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
31
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSwrQ0FBdUQ7QUFBOUMsdUdBQUEsY0FBYyxPQUFBO0FBQ3ZCLDZGQUFzSDtBQUE3RyxvSUFBQSwwQkFBMEIsT0FBMEI7QUFDN0QseUZBQWlIO0FBQXhHLGtJQUFBLHlCQUF5QixPQUF5QjtBQUMzRCw2RkFBc0g7QUFBN0csb0lBQUEsMEJBQTBCLE9BQTBCO0FBQzdELDZHQUEySTtBQUFsSSw4SUFBQSwrQkFBK0IsT0FBK0I7QUFVdkUseUdBQXNJO0FBQTdILDRJQUFBLDhCQUE4QixPQUE4QjtBQWVyRSw0REFBMEMiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgeyBDYWxjdWxhdG9yVHlwZSB9IGZyb20gJy4vc2hhcmVkL2RvbWFpbi90eXBlcyc7XG5leHBvcnQgeyBDYW5hZGFJbmNvbWVUYXhTZXJ2aWNlSW1wbCBhcyBDYW5hZGFJbmNvbWVUYXhTZXJ2aWNlIH0gZnJvbSAnLi9pbmNvbWUtdGF4L2NhbmFkYS9DYW5hZGFJbmNvbWVUYXhTZXJ2aWNlSW1wbCc7XG5leHBvcnQgeyBDYW5hZGFNb3J0Z2FnZVNlcnZpY2VJbXBsIGFzIENhbmFkYU1vcnRnYWdlU2VydmljZSB9IGZyb20gJy4vbW9ydGdhZ2UvY2FuYWRhL0NhbmFkYU1vcnRnYWdlU2VydmljZUltcGwnO1xuZXhwb3J0IHsgRnJhbmNlSW5jb21lVGF4U2VydmljZUltcGwgYXMgRnJhbmNlSW5jb21lVGF4U2VydmljZSB9IGZyb20gJy4vaW5jb21lLXRheC9mcmFuY2UvRnJhbmNlSW5jb21lVGF4U2VydmljZUltcGwnO1xuZXhwb3J0IHsgU291dGhBZnJpY2FJbmNvbWVUYXhTZXJ2aWNlSW1wbCBhcyBTb3V0aEFmcmljYUluY29tZVRheFNlcnZpY2UgfSBmcm9tICcuL2luY29tZS10YXgvc291dGgtYWZyaWNhL1NvdXRoQWZyaWNhSW5jb21lVGF4U2VydmljZUltcGwnO1xuZXhwb3J0IHtcbiAgICBDb21wdXRlZEluY29tZVRheFZhbHVlcyBhcyBDYW5hZGFDb21wdXRlZEluY29tZVRheFZhbHVlcyxcbiAgICBJbmNvbWVUYXhSdWxlcyBhcyBDYW5hZGFJbmNvbWVUYXhSdWxlcyxcbn0gZnJvbSAnLi9pbmNvbWUtdGF4L2NhbmFkYS9kb21haW4vdHlwZXMnO1xuZXhwb3J0IHtcbiAgICBNb3J0Z2FnZVJ1bGVzIGFzIENhbmFkYU1vcnRnYWdlUnVsZXMsXG4gICAgTW9ydGdhZ2VDYWxjdWxhdGlvbklucHV0IGFzIENhbmFkYU1vcnRnYWdlQ2FsY3VsYXRpb25JbnB1dCxcbiAgICBNb3J0Z2FnZUNhbGN1bGF0aW9uUmVzdWx0IGFzIENhbmFkYU1vcnRnYWdlQ2FsY3VsYXRpb25SZXN1bHQsXG59IGZyb20gJy4vbW9ydGdhZ2UvY2FuYWRhL2RvbWFpbi90eXBlcyc7XG5leHBvcnQgeyBTb3V0aEFmcmljYU1vcnRnYWdlU2VydmljZUltcGwgYXMgU291dGhBZnJpY2FNb3J0Z2FnZVNlcnZpY2UgfSBmcm9tICcuL21vcnRnYWdlL3NvdXRoLWFmcmljYS9Tb3V0aEFmcmljYU1vcnRnYWdlU2VydmljZUltcGwnO1xuZXhwb3J0IHtcbiAgICBNb3J0Z2FnZVJ1bGVzIGFzIFNvdXRoQWZyaWNhTW9ydGdhZ2VSdWxlcyxcbiAgICBNb3J0Z2FnZUlucHV0IGFzIFNvdXRoQWZyaWNhTW9ydGdhZ2VJbnB1dCxcbiAgICBNb3J0Z2FnZU91dHB1dCBhcyBTb3V0aEFmcmljYU1vcnRnYWdlT3V0cHV0LFxufSBmcm9tICcuL21vcnRnYWdlL3NvdXRoLWFmcmljYS9kb21haW4vdHlwZXMnO1xuZXhwb3J0IHsgXG4gICAgQ29tcHV0ZWRJbmNvbWVUYXhWYWx1ZXMgYXMgRnJhbmNlQ29tcHV0ZWRJbmNvbWVUYXhWYWx1ZXMsXG4gICAgSW5jb21lVGF4UnVsZXMgYXMgRnJhbmNlSW5jb21lVGF4UnVsZXMsXG59IGZyb20gJy4vaW5jb21lLXRheC9mcmFuY2UvZG9tYWluL3R5cGVzJztcbmV4cG9ydCB7XG4gICAgQ29tcHV0ZWRJbmNvbWVUYXhWYWx1ZXMgYXMgU291dGhBZnJpY2FDb21wdXRlZEluY29tZVRheFZhbHVlcyxcbiAgICBJbmNvbWVUYXhSdWxlcyBhcyBTb3V0aEFmcmljYUluY29tZVRheFJ1bGVzLFxufSBmcm9tICcuL2luY29tZS10YXgvc291dGgtYWZyaWNhL2RvbWFpbi90eXBlcyc7XG5leHBvcnQgeyBJbmNvbWVUYXhDYWxjdWxhdG9yU2NoZW1hIH0gZnJvbSAnLi9pbmNvbWUtdGF4L2RvbWFpbi90eXBlcyc7XG5leHBvcnQgKiBmcm9tICcuL2luY29tZS10YXgvZG9tYWluL3R5cGVzJzsiXX0=
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ComputedIncomeTaxValues, IncomeTaxRules } from "./domain/types";
|
|
2
|
+
import { SouthAfricaIncomeTaxService } from "./SouthAfricaIncomeTaxService";
|
|
3
|
+
export declare class SouthAfricaIncomeTaxServiceImpl implements SouthAfricaIncomeTaxService {
|
|
4
|
+
private _income;
|
|
5
|
+
private _age;
|
|
6
|
+
private _rules;
|
|
7
|
+
constructor(income: number, age: number, rules: IncomeTaxRules);
|
|
8
|
+
calculateNetIncome(): ComputedIncomeTaxValues;
|
|
9
|
+
private calculateBracketTax;
|
|
10
|
+
private getTaxThreshold;
|
|
11
|
+
private getRebate;
|
|
12
|
+
private calculateUif;
|
|
13
|
+
private round;
|
|
14
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { AgeBasedRebate, TaxBracket } from "../../domain/types";
|
|
2
|
+
export interface IncomeTaxRules {
|
|
3
|
+
taxBrackets: TaxBracket[];
|
|
4
|
+
rebates: TaxRebates;
|
|
5
|
+
taxThresholds: TaxThresholds;
|
|
6
|
+
medicalAidTaxCredit: MedicalAidTaxCredit;
|
|
7
|
+
uif: UifRules;
|
|
8
|
+
}
|
|
9
|
+
export interface TaxRebates {
|
|
10
|
+
primary: AgeBasedRebate;
|
|
11
|
+
secondary: AgeBasedRebate;
|
|
12
|
+
tertiary: AgeBasedRebate;
|
|
13
|
+
}
|
|
14
|
+
export interface TaxThresholds {
|
|
15
|
+
under65: number;
|
|
16
|
+
age65To74: number;
|
|
17
|
+
age75Plus: number;
|
|
18
|
+
}
|
|
19
|
+
export interface MedicalAidTaxCredit {
|
|
20
|
+
monthly: MedicalAidMonthlyCredit;
|
|
21
|
+
annualMultiplier: number;
|
|
22
|
+
}
|
|
23
|
+
export interface MedicalAidMonthlyCredit {
|
|
24
|
+
taxpayer: number;
|
|
25
|
+
firstDependant: number;
|
|
26
|
+
additionalDependant: number;
|
|
27
|
+
}
|
|
28
|
+
export interface UifRules {
|
|
29
|
+
rate: number;
|
|
30
|
+
annualIncomeCap: number;
|
|
31
|
+
maxAnnualContribution: number;
|
|
32
|
+
}
|
|
33
|
+
export interface ComputedIncomeTaxValues {
|
|
34
|
+
grossIncome: number;
|
|
35
|
+
incomeTax: number;
|
|
36
|
+
uif: number;
|
|
37
|
+
totalDeductions: number;
|
|
38
|
+
netIncome: number;
|
|
39
|
+
effectiveTaxRate: number;
|
|
40
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -2,10 +2,12 @@ export { CalculatorType } from './shared/domain/types';
|
|
|
2
2
|
export { CanadaIncomeTaxServiceImpl as CanadaIncomeTaxService } from './income-tax/canada/CanadaIncomeTaxServiceImpl';
|
|
3
3
|
export { CanadaMortgageServiceImpl as CanadaMortgageService } from './mortgage/canada/CanadaMortgageServiceImpl';
|
|
4
4
|
export { FranceIncomeTaxServiceImpl as FranceIncomeTaxService } from './income-tax/france/FranceIncomeTaxServiceImpl';
|
|
5
|
+
export { SouthAfricaIncomeTaxServiceImpl as SouthAfricaIncomeTaxService } from './income-tax/south-africa/SouthAfricaIncomeTaxServiceImpl';
|
|
5
6
|
export { ComputedIncomeTaxValues as CanadaComputedIncomeTaxValues, IncomeTaxRules as CanadaIncomeTaxRules, } from './income-tax/canada/domain/types';
|
|
6
7
|
export { MortgageRules as CanadaMortgageRules, MortgageCalculationInput as CanadaMortgageCalculationInput, MortgageCalculationResult as CanadaMortgageCalculationResult, } from './mortgage/canada/domain/types';
|
|
7
8
|
export { SouthAfricaMortgageServiceImpl as SouthAfricaMortgageService } from './mortgage/south-africa/SouthAfricaMortgageServiceImpl';
|
|
8
9
|
export { MortgageRules as SouthAfricaMortgageRules, MortgageInput as SouthAfricaMortgageInput, MortgageOutput as SouthAfricaMortgageOutput, } from './mortgage/south-africa/domain/types';
|
|
9
10
|
export { ComputedIncomeTaxValues as FranceComputedIncomeTaxValues, IncomeTaxRules as FranceIncomeTaxRules, } from './income-tax/france/domain/types';
|
|
11
|
+
export { ComputedIncomeTaxValues as SouthAfricaComputedIncomeTaxValues, IncomeTaxRules as SouthAfricaIncomeTaxRules, } from './income-tax/south-africa/domain/types';
|
|
10
12
|
export { IncomeTaxCalculatorSchema } from './income-tax/domain/types';
|
|
11
13
|
export * from './income-tax/domain/types';
|
package/package.json
CHANGED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { ComputedIncomeTaxValues, IncomeTaxRules } from "./domain/types";
|
|
2
|
+
import { SouthAfricaIncomeTaxService } from "./SouthAfricaIncomeTaxService";
|
|
3
|
+
|
|
4
|
+
export class SouthAfricaIncomeTaxServiceImpl implements SouthAfricaIncomeTaxService {
|
|
5
|
+
private _income: number;
|
|
6
|
+
private _age: number;
|
|
7
|
+
private _rules: IncomeTaxRules;
|
|
8
|
+
|
|
9
|
+
constructor(income: number, age: number, rules: IncomeTaxRules) {
|
|
10
|
+
this._income = income;
|
|
11
|
+
this._age = age;
|
|
12
|
+
this._rules = rules;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
calculateNetIncome(): ComputedIncomeTaxValues {
|
|
16
|
+
const grossIncome = this._income;
|
|
17
|
+
|
|
18
|
+
const threshold = this.getTaxThreshold(this._age);
|
|
19
|
+
|
|
20
|
+
if (this._income <= threshold) {
|
|
21
|
+
return {
|
|
22
|
+
grossIncome,
|
|
23
|
+
incomeTax: 0,
|
|
24
|
+
uif: 0,
|
|
25
|
+
totalDeductions: 0,
|
|
26
|
+
netIncome: grossIncome,
|
|
27
|
+
effectiveTaxRate: 0,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const grossTax = this.calculateBracketTax(this._income);
|
|
32
|
+
|
|
33
|
+
const rebate = this.getRebate(this._age);
|
|
34
|
+
const incomeTax = Math.max(0, grossTax - rebate);
|
|
35
|
+
|
|
36
|
+
const uif = this.calculateUif(this._income);
|
|
37
|
+
|
|
38
|
+
const totalDeductions = incomeTax + uif;
|
|
39
|
+
const netIncome = grossIncome - totalDeductions;
|
|
40
|
+
const effectiveTaxRate = incomeTax / grossIncome;
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
grossIncome,
|
|
44
|
+
incomeTax: this.round(incomeTax),
|
|
45
|
+
uif: this.round(uif),
|
|
46
|
+
totalDeductions: this.round(totalDeductions),
|
|
47
|
+
netIncome: this.round(netIncome),
|
|
48
|
+
effectiveTaxRate: this.round(effectiveTaxRate, 4),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private calculateBracketTax(income: number): number {
|
|
53
|
+
let tax = 0;
|
|
54
|
+
|
|
55
|
+
for (const bracket of this._rules.taxBrackets) {
|
|
56
|
+
if (income <= bracket.from) break;
|
|
57
|
+
|
|
58
|
+
const upper = bracket.to ?? income;
|
|
59
|
+
const taxableAmount = Math.min(upper, income) - bracket.from;
|
|
60
|
+
|
|
61
|
+
if (taxableAmount > 0) {
|
|
62
|
+
tax += taxableAmount * bracket.rate;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return tax;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private getTaxThreshold(age: number): number {
|
|
70
|
+
const thresholds = this._rules.taxThresholds;
|
|
71
|
+
|
|
72
|
+
if (age >= 75) return thresholds.age75Plus;
|
|
73
|
+
if (age >= 65) return thresholds.age65To74;
|
|
74
|
+
|
|
75
|
+
return thresholds.under65;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private getRebate(age: number): number {
|
|
79
|
+
let rebate = this._rules.rebates.primary.amount;
|
|
80
|
+
|
|
81
|
+
if (age >= this._rules.rebates.secondary.ageMin) {
|
|
82
|
+
rebate += this._rules.rebates.secondary.amount;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (age >= this._rules.rebates.tertiary.ageMin) {
|
|
86
|
+
rebate += this._rules.rebates.tertiary.amount;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return rebate;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private calculateUif(income: number): number {
|
|
93
|
+
const cappedIncome = Math.min(
|
|
94
|
+
income,
|
|
95
|
+
this._rules.uif.annualIncomeCap,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
return Math.min(
|
|
99
|
+
cappedIncome * this._rules.uif.rate,
|
|
100
|
+
this._rules.uif.maxAnnualContribution,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private round(value: number, decimals = 2): number {
|
|
105
|
+
return Number(value.toFixed(decimals));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { AgeBasedRebate, TaxBracket } from "../../domain/types";
|
|
2
|
+
|
|
3
|
+
export interface IncomeTaxRules {
|
|
4
|
+
taxBrackets: TaxBracket[];
|
|
5
|
+
rebates: TaxRebates;
|
|
6
|
+
taxThresholds: TaxThresholds;
|
|
7
|
+
medicalAidTaxCredit: MedicalAidTaxCredit;
|
|
8
|
+
uif: UifRules;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TaxRebates {
|
|
12
|
+
primary: AgeBasedRebate;
|
|
13
|
+
secondary: AgeBasedRebate;
|
|
14
|
+
tertiary: AgeBasedRebate;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface TaxThresholds {
|
|
18
|
+
under65: number;
|
|
19
|
+
age65To74: number;
|
|
20
|
+
age75Plus: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface MedicalAidTaxCredit {
|
|
24
|
+
monthly: MedicalAidMonthlyCredit;
|
|
25
|
+
annualMultiplier: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface MedicalAidMonthlyCredit {
|
|
29
|
+
taxpayer: number;
|
|
30
|
+
firstDependant: number;
|
|
31
|
+
additionalDependant: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface UifRules {
|
|
35
|
+
rate: number;
|
|
36
|
+
annualIncomeCap: number;
|
|
37
|
+
maxAnnualContribution: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ComputedIncomeTaxValues {
|
|
41
|
+
grossIncome: number;
|
|
42
|
+
incomeTax: number;
|
|
43
|
+
uif: number;
|
|
44
|
+
totalDeductions: number;
|
|
45
|
+
netIncome: number;
|
|
46
|
+
effectiveTaxRate: number;
|
|
47
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { CalculatorType } from './shared/domain/types';
|
|
|
2
2
|
export { CanadaIncomeTaxServiceImpl as CanadaIncomeTaxService } from './income-tax/canada/CanadaIncomeTaxServiceImpl';
|
|
3
3
|
export { CanadaMortgageServiceImpl as CanadaMortgageService } from './mortgage/canada/CanadaMortgageServiceImpl';
|
|
4
4
|
export { FranceIncomeTaxServiceImpl as FranceIncomeTaxService } from './income-tax/france/FranceIncomeTaxServiceImpl';
|
|
5
|
+
export { SouthAfricaIncomeTaxServiceImpl as SouthAfricaIncomeTaxService } from './income-tax/south-africa/SouthAfricaIncomeTaxServiceImpl';
|
|
5
6
|
export {
|
|
6
7
|
ComputedIncomeTaxValues as CanadaComputedIncomeTaxValues,
|
|
7
8
|
IncomeTaxRules as CanadaIncomeTaxRules,
|
|
@@ -21,5 +22,9 @@ export {
|
|
|
21
22
|
ComputedIncomeTaxValues as FranceComputedIncomeTaxValues,
|
|
22
23
|
IncomeTaxRules as FranceIncomeTaxRules,
|
|
23
24
|
} from './income-tax/france/domain/types';
|
|
25
|
+
export {
|
|
26
|
+
ComputedIncomeTaxValues as SouthAfricaComputedIncomeTaxValues,
|
|
27
|
+
IncomeTaxRules as SouthAfricaIncomeTaxRules,
|
|
28
|
+
} from './income-tax/south-africa/domain/types';
|
|
24
29
|
export { IncomeTaxCalculatorSchema } from './income-tax/domain/types';
|
|
25
30
|
export * from './income-tax/domain/types';
|