@novha/calc-engines 6.1.0 → 6.3.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.
@@ -20,47 +20,35 @@ class CanadaCapitalGainsServiceImpl {
20
20
  };
21
21
  }
22
22
  const taxableGain = gain * this._rules.inclusionRate;
23
- const otherIncome = this._input.totalTaxableIncome - gain;
24
- const { tax, breakdowns } = this.applyBrackets(taxableGain, otherIncome, this._rules.taxBrackets);
23
+ // income BEFORE the capital gain
24
+ const baseIncome = this._input.totalTaxableIncome;
25
+ const incomeWithGain = baseIncome + taxableGain;
26
+ const baseTax = this.calculateProgressiveTax(baseIncome, this._rules.taxBrackets);
27
+ const taxWithGain = this.calculateProgressiveTax(incomeWithGain, this._rules.taxBrackets);
28
+ const capitalGainTax = taxWithGain - baseTax;
25
29
  return {
26
30
  taxableGain,
27
- capitalGainTax: tax,
31
+ capitalGainTax,
28
32
  socialContributions: 0,
29
33
  netInvestmentIncomeTax: 0,
30
- totalTax: tax,
31
- effectiveRate: gain > 0 ? (tax / gain) * 100 : 0,
32
- breakdowns,
34
+ totalTax: capitalGainTax,
35
+ effectiveRate: gain > 0 ? (capitalGainTax / gain) * 100 : 0,
36
+ breakdowns: []
33
37
  };
34
38
  }
35
- applyBrackets(taxableGain, otherIncome, brackets) {
39
+ calculateProgressiveTax(income, brackets) {
36
40
  let tax = 0;
37
- const breakdowns = [];
38
- let remaining = taxableGain;
39
- let incomeUsed = otherIncome;
40
41
  for (const bracket of brackets) {
41
- if (remaining <= 0)
42
- break;
43
42
  const upper = bracket.to ?? Infinity;
44
- if (incomeUsed >= upper)
45
- continue;
46
- const bracketStart = Math.max(bracket.from, incomeUsed);
47
- const bracketSpace = upper - bracketStart;
48
- const taxable = Math.min(bracketSpace, remaining);
43
+ if (income <= bracket.from)
44
+ break;
45
+ const taxable = Math.min(income, upper) - bracket.from;
49
46
  if (taxable > 0) {
50
- const bracketTax = taxable * bracket.rate;
51
- tax += bracketTax;
52
- remaining -= taxable;
53
- incomeUsed += taxable;
54
- breakdowns.push({
55
- from: `${bracket.from}`,
56
- to: `${bracket.to ?? 'Above'}`,
57
- rate: bracket.rate,
58
- amount: bracketTax,
59
- });
47
+ tax += taxable * bracket.rate;
60
48
  }
61
49
  }
62
- return { tax, breakdowns };
50
+ return tax;
63
51
  }
64
52
  }
65
53
  exports.CanadaCapitalGainsServiceImpl = CanadaCapitalGainsServiceImpl;
66
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ2FuYWRhQ2FwaXRhbEdhaW5zU2VydmljZUltcGwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY2FwaXRhbC1nYWlucy9jYW5hZGEvQ2FuYWRhQ2FwaXRhbEdhaW5zU2VydmljZUltcGwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBSUEsTUFBYSw2QkFBNkI7SUFJdEMsWUFBWSxLQUFZLEVBQUUsS0FBWTtRQUNsQyxJQUFJLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQztRQUNwQixJQUFJLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQztJQUN4QixDQUFDO0lBRUQsU0FBUztRQUNMLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDO1FBRXJDLElBQUksSUFBSSxJQUFJLENBQUMsRUFBRSxDQUFDO1lBQ1osT0FBTztnQkFDSCxXQUFXLEVBQUUsQ0FBQztnQkFDZCxjQUFjLEVBQUUsQ0FBQztnQkFDakIsbUJBQW1CLEVBQUUsQ0FBQztnQkFDdEIsc0JBQXNCLEVBQUUsQ0FBQztnQkFDekIsUUFBUSxFQUFFLENBQUM7Z0JBQ1gsYUFBYSxFQUFFLENBQUM7Z0JBQ2hCLFVBQVUsRUFBRSxFQUFFO2FBQ2pCLENBQUM7UUFDTixDQUFDO1FBRUQsTUFBTSxXQUFXLEdBQUcsSUFBSSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsYUFBYSxDQUFDO1FBQ3JELE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsa0JBQWtCLEdBQUcsSUFBSSxDQUFDO1FBQzFELE1BQU0sRUFBRSxHQUFHLEVBQUUsVUFBVSxFQUFFLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxXQUFXLEVBQUUsV0FBVyxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLENBQUM7UUFFbEcsT0FBTztZQUNILFdBQVc7WUFDWCxjQUFjLEVBQUUsR0FBRztZQUNuQixtQkFBbUIsRUFBRSxDQUFDO1lBQ3RCLHNCQUFzQixFQUFFLENBQUM7WUFDekIsUUFBUSxFQUFFLEdBQUc7WUFDYixhQUFhLEVBQUUsSUFBSSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ2hELFVBQVU7U0FDYixDQUFDO0lBQ04sQ0FBQztJQUVPLGFBQWEsQ0FDakIsV0FBbUIsRUFDbkIsV0FBbUIsRUFDbkIsUUFBc0I7UUFFdEIsSUFBSSxHQUFHLEdBQUcsQ0FBQyxDQUFDO1FBQ1osTUFBTSxVQUFVLEdBQWdCLEVBQUUsQ0FBQztRQUNuQyxJQUFJLFNBQVMsR0FBRyxXQUFXLENBQUM7UUFDNUIsSUFBSSxVQUFVLEdBQUcsV0FBVyxDQUFDO1FBRTdCLEtBQUssTUFBTSxPQUFPLElBQUksUUFBUSxFQUFFLENBQUM7WUFDN0IsSUFBSSxTQUFTLElBQUksQ0FBQztnQkFBRSxNQUFNO1lBRTFCLE1BQU0sS0FBSyxHQUFHLE9BQU8sQ0FBQyxFQUFFLElBQUksUUFBUSxDQUFDO1lBRXJDLElBQUksVUFBVSxJQUFJLEtBQUs7Z0JBQUUsU0FBUztZQUVsQyxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsVUFBVSxDQUFDLENBQUM7WUFDeEQsTUFBTSxZQUFZLEdBQUcsS0FBSyxHQUFHLFlBQVksQ0FBQztZQUMxQyxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLFlBQVksRUFBRSxTQUFTLENBQUMsQ0FBQztZQUVsRCxJQUFJLE9BQU8sR0FBRyxDQUFDLEVBQUUsQ0FBQztnQkFDZCxNQUFNLFVBQVUsR0FBRyxPQUFPLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQztnQkFDMUMsR0FBRyxJQUFJLFVBQVUsQ0FBQztnQkFDbEIsU0FBUyxJQUFJLE9BQU8sQ0FBQztnQkFDckIsVUFBVSxJQUFJLE9BQU8sQ0FBQztnQkFFdEIsVUFBVSxDQUFDLElBQUksQ0FBQztvQkFDWixJQUFJLEVBQUUsR0FBRyxPQUFPLENBQUMsSUFBSSxFQUFFO29CQUN2QixFQUFFLEVBQUUsR0FBRyxPQUFPLENBQUMsRUFBRSxJQUFJLE9BQU8sRUFBRTtvQkFDOUIsSUFBSSxFQUFFLE9BQU8sQ0FBQyxJQUFJO29CQUNsQixNQUFNLEVBQUUsVUFBVTtpQkFDckIsQ0FBQyxDQUFDO1lBQ1AsQ0FBQztRQUNMLENBQUM7UUFFRCxPQUFPLEVBQUUsR0FBRyxFQUFFLFVBQVUsRUFBRSxDQUFDO0lBQy9CLENBQUM7Q0FDSjtBQTdFRCxzRUE2RUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBCcmVha2Rvd24sIFJlc3VsdCB9IGZyb20gXCIuLi9kb21haW4vdHlwZXNcIjtcbmltcG9ydCB7IENhbmFkYUNhcGl0YWxHYWluc1NlcnZpY2UgfSBmcm9tIFwiLi9DYW5hZGFDYXBpdGFsR2FpbnNTZXJ2aWNlXCI7XG5pbXBvcnQgeyBJbnB1dCwgUnVsZXMsIFRheEJyYWNrZXQgfSBmcm9tIFwiLi9kb21haW4vdHlwZXNcIjtcblxuZXhwb3J0IGNsYXNzIENhbmFkYUNhcGl0YWxHYWluc1NlcnZpY2VJbXBsIGltcGxlbWVudHMgQ2FuYWRhQ2FwaXRhbEdhaW5zU2VydmljZSB7XG4gICAgcHJpdmF0ZSBfaW5wdXQ6IElucHV0O1xuICAgIHByaXZhdGUgX3J1bGVzOiBSdWxlcztcblxuICAgIGNvbnN0cnVjdG9yKGlucHV0OiBJbnB1dCwgcnVsZXM6IFJ1bGVzKSB7XG4gICAgICAgIHRoaXMuX2lucHV0ID0gaW5wdXQ7XG4gICAgICAgIHRoaXMuX3J1bGVzID0gcnVsZXM7XG4gICAgfVxuXG4gICAgY2FsY3VsYXRlKCk6IFJlc3VsdCB7XG4gICAgICAgIGNvbnN0IGdhaW4gPSB0aGlzLl9pbnB1dC5jYXBpdGFsR2FpbjtcblxuICAgICAgICBpZiAoZ2FpbiA8PSAwKSB7XG4gICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICAgIHRheGFibGVHYWluOiAwLFxuICAgICAgICAgICAgICAgIGNhcGl0YWxHYWluVGF4OiAwLFxuICAgICAgICAgICAgICAgIHNvY2lhbENvbnRyaWJ1dGlvbnM6IDAsXG4gICAgICAgICAgICAgICAgbmV0SW52ZXN0bWVudEluY29tZVRheDogMCxcbiAgICAgICAgICAgICAgICB0b3RhbFRheDogMCxcbiAgICAgICAgICAgICAgICBlZmZlY3RpdmVSYXRlOiAwLFxuICAgICAgICAgICAgICAgIGJyZWFrZG93bnM6IFtdXG4gICAgICAgICAgICB9O1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgdGF4YWJsZUdhaW4gPSBnYWluICogdGhpcy5fcnVsZXMuaW5jbHVzaW9uUmF0ZTtcbiAgICAgICAgY29uc3Qgb3RoZXJJbmNvbWUgPSB0aGlzLl9pbnB1dC50b3RhbFRheGFibGVJbmNvbWUgLSBnYWluO1xuICAgICAgICBjb25zdCB7IHRheCwgYnJlYWtkb3ducyB9ID0gdGhpcy5hcHBseUJyYWNrZXRzKHRheGFibGVHYWluLCBvdGhlckluY29tZSwgdGhpcy5fcnVsZXMudGF4QnJhY2tldHMpO1xuXG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICB0YXhhYmxlR2FpbixcbiAgICAgICAgICAgIGNhcGl0YWxHYWluVGF4OiB0YXgsXG4gICAgICAgICAgICBzb2NpYWxDb250cmlidXRpb25zOiAwLFxuICAgICAgICAgICAgbmV0SW52ZXN0bWVudEluY29tZVRheDogMCxcbiAgICAgICAgICAgIHRvdGFsVGF4OiB0YXgsXG4gICAgICAgICAgICBlZmZlY3RpdmVSYXRlOiBnYWluID4gMCA/ICh0YXggLyBnYWluKSAqIDEwMCA6IDAsXG4gICAgICAgICAgICBicmVha2Rvd25zLFxuICAgICAgICB9O1xuICAgIH1cblxuICAgIHByaXZhdGUgYXBwbHlCcmFja2V0cyhcbiAgICAgICAgdGF4YWJsZUdhaW46IG51bWJlcixcbiAgICAgICAgb3RoZXJJbmNvbWU6IG51bWJlcixcbiAgICAgICAgYnJhY2tldHM6IFRheEJyYWNrZXRbXSxcbiAgICApOiB7IHRheDogbnVtYmVyOyBicmVha2Rvd25zOiBCcmVha2Rvd25bXSB9IHtcbiAgICAgICAgbGV0IHRheCA9IDA7XG4gICAgICAgIGNvbnN0IGJyZWFrZG93bnM6IEJyZWFrZG93bltdID0gW107XG4gICAgICAgIGxldCByZW1haW5pbmcgPSB0YXhhYmxlR2FpbjtcbiAgICAgICAgbGV0IGluY29tZVVzZWQgPSBvdGhlckluY29tZTtcblxuICAgICAgICBmb3IgKGNvbnN0IGJyYWNrZXQgb2YgYnJhY2tldHMpIHtcbiAgICAgICAgICAgIGlmIChyZW1haW5pbmcgPD0gMCkgYnJlYWs7XG5cbiAgICAgICAgICAgIGNvbnN0IHVwcGVyID0gYnJhY2tldC50byA/PyBJbmZpbml0eTtcblxuICAgICAgICAgICAgaWYgKGluY29tZVVzZWQgPj0gdXBwZXIpIGNvbnRpbnVlO1xuXG4gICAgICAgICAgICBjb25zdCBicmFja2V0U3RhcnQgPSBNYXRoLm1heChicmFja2V0LmZyb20sIGluY29tZVVzZWQpO1xuICAgICAgICAgICAgY29uc3QgYnJhY2tldFNwYWNlID0gdXBwZXIgLSBicmFja2V0U3RhcnQ7XG4gICAgICAgICAgICBjb25zdCB0YXhhYmxlID0gTWF0aC5taW4oYnJhY2tldFNwYWNlLCByZW1haW5pbmcpO1xuXG4gICAgICAgICAgICBpZiAodGF4YWJsZSA+IDApIHtcbiAgICAgICAgICAgICAgICBjb25zdCBicmFja2V0VGF4ID0gdGF4YWJsZSAqIGJyYWNrZXQucmF0ZTtcbiAgICAgICAgICAgICAgICB0YXggKz0gYnJhY2tldFRheDtcbiAgICAgICAgICAgICAgICByZW1haW5pbmcgLT0gdGF4YWJsZTtcbiAgICAgICAgICAgICAgICBpbmNvbWVVc2VkICs9IHRheGFibGU7XG5cbiAgICAgICAgICAgICAgICBicmVha2Rvd25zLnB1c2goe1xuICAgICAgICAgICAgICAgICAgICBmcm9tOiBgJHticmFja2V0LmZyb219YCxcbiAgICAgICAgICAgICAgICAgICAgdG86IGAke2JyYWNrZXQudG8gPz8gJ0Fib3ZlJ31gLFxuICAgICAgICAgICAgICAgICAgICByYXRlOiBicmFja2V0LnJhdGUsXG4gICAgICAgICAgICAgICAgICAgIGFtb3VudDogYnJhY2tldFRheCxcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB7IHRheCwgYnJlYWtkb3ducyB9O1xuICAgIH1cbn1cbiJdfQ==
54
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ2FuYWRhQ2FwaXRhbEdhaW5zU2VydmljZUltcGwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY2FwaXRhbC1nYWlucy9jYW5hZGEvQ2FuYWRhQ2FwaXRhbEdhaW5zU2VydmljZUltcGwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBSUEsTUFBYSw2QkFBNkI7SUFJdEMsWUFBWSxLQUFZLEVBQUUsS0FBWTtRQUNsQyxJQUFJLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQztRQUNwQixJQUFJLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQztJQUN4QixDQUFDO0lBRUQsU0FBUztRQUNMLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDO1FBRXJDLElBQUksSUFBSSxJQUFJLENBQUMsRUFBRSxDQUFDO1lBQ1osT0FBTztnQkFDSCxXQUFXLEVBQUUsQ0FBQztnQkFDZCxjQUFjLEVBQUUsQ0FBQztnQkFDakIsbUJBQW1CLEVBQUUsQ0FBQztnQkFDdEIsc0JBQXNCLEVBQUUsQ0FBQztnQkFDekIsUUFBUSxFQUFFLENBQUM7Z0JBQ1gsYUFBYSxFQUFFLENBQUM7Z0JBQ2hCLFVBQVUsRUFBRSxFQUFFO2FBQ2pCLENBQUM7UUFDTixDQUFDO1FBRUQsTUFBTSxXQUFXLEdBQUcsSUFBSSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsYUFBYSxDQUFDO1FBRXJELGlDQUFpQztRQUNqQyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLGtCQUFrQixDQUFDO1FBRWxELE1BQU0sY0FBYyxHQUFHLFVBQVUsR0FBRyxXQUFXLENBQUM7UUFFaEQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLHVCQUF1QixDQUFDLFVBQVUsRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxDQUFDO1FBQ2xGLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyx1QkFBdUIsQ0FBQyxjQUFjLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUUxRixNQUFNLGNBQWMsR0FBRyxXQUFXLEdBQUcsT0FBTyxDQUFDO1FBRTdDLE9BQU87WUFDSCxXQUFXO1lBQ1gsY0FBYztZQUNkLG1CQUFtQixFQUFFLENBQUM7WUFDdEIsc0JBQXNCLEVBQUUsQ0FBQztZQUN6QixRQUFRLEVBQUUsY0FBYztZQUN4QixhQUFhLEVBQUUsSUFBSSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxjQUFjLEdBQUcsSUFBSSxDQUFDLEdBQUcsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQzNELFVBQVUsRUFBRSxFQUFFO1NBQ2pCLENBQUM7SUFDTixDQUFDO0lBRU8sdUJBQXVCLENBQUMsTUFBYyxFQUFFLFFBQXNCO1FBQ2xFLElBQUksR0FBRyxHQUFHLENBQUMsQ0FBQztRQUVaLEtBQUssTUFBTSxPQUFPLElBQUksUUFBUSxFQUFFLENBQUM7WUFDN0IsTUFBTSxLQUFLLEdBQUcsT0FBTyxDQUFDLEVBQUUsSUFBSSxRQUFRLENBQUM7WUFFckMsSUFBSSxNQUFNLElBQUksT0FBTyxDQUFDLElBQUk7Z0JBQUUsTUFBTTtZQUVsQyxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLE1BQU0sRUFBRSxLQUFLLENBQUMsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDO1lBRXZELElBQUksT0FBTyxHQUFHLENBQUMsRUFBRSxDQUFDO2dCQUNkLEdBQUcsSUFBSSxPQUFPLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQztZQUNsQyxDQUFDO1FBQ0wsQ0FBQztRQUVELE9BQU8sR0FBRyxDQUFDO0lBQ2YsQ0FBQztDQUNKO0FBaEVELHNFQWdFQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IFJlc3VsdCB9IGZyb20gXCIuLi9kb21haW4vdHlwZXNcIjtcbmltcG9ydCB7IENhbmFkYUNhcGl0YWxHYWluc1NlcnZpY2UgfSBmcm9tIFwiLi9DYW5hZGFDYXBpdGFsR2FpbnNTZXJ2aWNlXCI7XG5pbXBvcnQgeyBJbnB1dCwgUnVsZXMsIFRheEJyYWNrZXQgfSBmcm9tIFwiLi9kb21haW4vdHlwZXNcIjtcblxuZXhwb3J0IGNsYXNzIENhbmFkYUNhcGl0YWxHYWluc1NlcnZpY2VJbXBsIGltcGxlbWVudHMgQ2FuYWRhQ2FwaXRhbEdhaW5zU2VydmljZSB7XG4gICAgcHJpdmF0ZSBfaW5wdXQ6IElucHV0O1xuICAgIHByaXZhdGUgX3J1bGVzOiBSdWxlcztcblxuICAgIGNvbnN0cnVjdG9yKGlucHV0OiBJbnB1dCwgcnVsZXM6IFJ1bGVzKSB7XG4gICAgICAgIHRoaXMuX2lucHV0ID0gaW5wdXQ7XG4gICAgICAgIHRoaXMuX3J1bGVzID0gcnVsZXM7XG4gICAgfVxuXG4gICAgY2FsY3VsYXRlKCk6IFJlc3VsdCB7XG4gICAgICAgIGNvbnN0IGdhaW4gPSB0aGlzLl9pbnB1dC5jYXBpdGFsR2FpbjtcblxuICAgICAgICBpZiAoZ2FpbiA8PSAwKSB7XG4gICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICAgIHRheGFibGVHYWluOiAwLFxuICAgICAgICAgICAgICAgIGNhcGl0YWxHYWluVGF4OiAwLFxuICAgICAgICAgICAgICAgIHNvY2lhbENvbnRyaWJ1dGlvbnM6IDAsXG4gICAgICAgICAgICAgICAgbmV0SW52ZXN0bWVudEluY29tZVRheDogMCxcbiAgICAgICAgICAgICAgICB0b3RhbFRheDogMCxcbiAgICAgICAgICAgICAgICBlZmZlY3RpdmVSYXRlOiAwLFxuICAgICAgICAgICAgICAgIGJyZWFrZG93bnM6IFtdXG4gICAgICAgICAgICB9O1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgdGF4YWJsZUdhaW4gPSBnYWluICogdGhpcy5fcnVsZXMuaW5jbHVzaW9uUmF0ZTtcblxuICAgICAgICAvLyBpbmNvbWUgQkVGT1JFIHRoZSBjYXBpdGFsIGdhaW5cbiAgICAgICAgY29uc3QgYmFzZUluY29tZSA9IHRoaXMuX2lucHV0LnRvdGFsVGF4YWJsZUluY29tZTtcblxuICAgICAgICBjb25zdCBpbmNvbWVXaXRoR2FpbiA9IGJhc2VJbmNvbWUgKyB0YXhhYmxlR2FpbjtcblxuICAgICAgICBjb25zdCBiYXNlVGF4ID0gdGhpcy5jYWxjdWxhdGVQcm9ncmVzc2l2ZVRheChiYXNlSW5jb21lLCB0aGlzLl9ydWxlcy50YXhCcmFja2V0cyk7XG4gICAgICAgIGNvbnN0IHRheFdpdGhHYWluID0gdGhpcy5jYWxjdWxhdGVQcm9ncmVzc2l2ZVRheChpbmNvbWVXaXRoR2FpbiwgdGhpcy5fcnVsZXMudGF4QnJhY2tldHMpO1xuXG4gICAgICAgIGNvbnN0IGNhcGl0YWxHYWluVGF4ID0gdGF4V2l0aEdhaW4gLSBiYXNlVGF4O1xuXG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICB0YXhhYmxlR2FpbixcbiAgICAgICAgICAgIGNhcGl0YWxHYWluVGF4LFxuICAgICAgICAgICAgc29jaWFsQ29udHJpYnV0aW9uczogMCxcbiAgICAgICAgICAgIG5ldEludmVzdG1lbnRJbmNvbWVUYXg6IDAsXG4gICAgICAgICAgICB0b3RhbFRheDogY2FwaXRhbEdhaW5UYXgsXG4gICAgICAgICAgICBlZmZlY3RpdmVSYXRlOiBnYWluID4gMCA/IChjYXBpdGFsR2FpblRheCAvIGdhaW4pICogMTAwIDogMCxcbiAgICAgICAgICAgIGJyZWFrZG93bnM6IFtdXG4gICAgICAgIH07XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBjYWxjdWxhdGVQcm9ncmVzc2l2ZVRheChpbmNvbWU6IG51bWJlciwgYnJhY2tldHM6IFRheEJyYWNrZXRbXSk6IG51bWJlciB7XG4gICAgICAgIGxldCB0YXggPSAwO1xuXG4gICAgICAgIGZvciAoY29uc3QgYnJhY2tldCBvZiBicmFja2V0cykge1xuICAgICAgICAgICAgY29uc3QgdXBwZXIgPSBicmFja2V0LnRvID8/IEluZmluaXR5O1xuXG4gICAgICAgICAgICBpZiAoaW5jb21lIDw9IGJyYWNrZXQuZnJvbSkgYnJlYWs7XG5cbiAgICAgICAgICAgIGNvbnN0IHRheGFibGUgPSBNYXRoLm1pbihpbmNvbWUsIHVwcGVyKSAtIGJyYWNrZXQuZnJvbTtcblxuICAgICAgICAgICAgaWYgKHRheGFibGUgPiAwKSB7XG4gICAgICAgICAgICAgICAgdGF4ICs9IHRheGFibGUgKiBicmFja2V0LnJhdGU7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdGF4O1xuICAgIH1cbn0iXX0=
@@ -6,5 +6,5 @@ export declare class CanadaCapitalGainsServiceImpl implements CanadaCapitalGains
6
6
  private _rules;
7
7
  constructor(input: Input, rules: Rules);
8
8
  calculate(): Result;
9
- private applyBrackets;
9
+ private calculateProgressiveTax;
10
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@novha/calc-engines",
3
- "version": "6.1.0",
3
+ "version": "6.3.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/types/index.d.ts",
6
6
  "scripts": {
@@ -1,4 +1,4 @@
1
- import { Breakdown, Result } from "../domain/types";
1
+ import { Result } from "../domain/types";
2
2
  import { CanadaCapitalGainsService } from "./CanadaCapitalGainsService";
3
3
  import { Input, Rules, TaxBracket } from "./domain/types";
4
4
 
@@ -27,56 +27,43 @@ export class CanadaCapitalGainsServiceImpl implements CanadaCapitalGainsService
27
27
  }
28
28
 
29
29
  const taxableGain = gain * this._rules.inclusionRate;
30
- const otherIncome = this._input.totalTaxableIncome - gain;
31
- const { tax, breakdowns } = this.applyBrackets(taxableGain, otherIncome, this._rules.taxBrackets);
30
+
31
+ // income BEFORE the capital gain
32
+ const baseIncome = this._input.totalTaxableIncome;
33
+
34
+ const incomeWithGain = baseIncome + taxableGain;
35
+
36
+ const baseTax = this.calculateProgressiveTax(baseIncome, this._rules.taxBrackets);
37
+ const taxWithGain = this.calculateProgressiveTax(incomeWithGain, this._rules.taxBrackets);
38
+
39
+ const capitalGainTax = taxWithGain - baseTax;
32
40
 
33
41
  return {
34
42
  taxableGain,
35
- capitalGainTax: tax,
43
+ capitalGainTax,
36
44
  socialContributions: 0,
37
45
  netInvestmentIncomeTax: 0,
38
- totalTax: tax,
39
- effectiveRate: gain > 0 ? (tax / gain) * 100 : 0,
40
- breakdowns,
46
+ totalTax: capitalGainTax,
47
+ effectiveRate: gain > 0 ? (capitalGainTax / gain) * 100 : 0,
48
+ breakdowns: []
41
49
  };
42
50
  }
43
51
 
44
- private applyBrackets(
45
- taxableGain: number,
46
- otherIncome: number,
47
- brackets: TaxBracket[],
48
- ): { tax: number; breakdowns: Breakdown[] } {
52
+ private calculateProgressiveTax(income: number, brackets: TaxBracket[]): number {
49
53
  let tax = 0;
50
- const breakdowns: Breakdown[] = [];
51
- let remaining = taxableGain;
52
- let incomeUsed = otherIncome;
53
54
 
54
55
  for (const bracket of brackets) {
55
- if (remaining <= 0) break;
56
-
57
56
  const upper = bracket.to ?? Infinity;
58
57
 
59
- if (incomeUsed >= upper) continue;
58
+ if (income <= bracket.from) break;
60
59
 
61
- const bracketStart = Math.max(bracket.from, incomeUsed);
62
- const bracketSpace = upper - bracketStart;
63
- const taxable = Math.min(bracketSpace, remaining);
60
+ const taxable = Math.min(income, upper) - bracket.from;
64
61
 
65
62
  if (taxable > 0) {
66
- const bracketTax = taxable * bracket.rate;
67
- tax += bracketTax;
68
- remaining -= taxable;
69
- incomeUsed += taxable;
70
-
71
- breakdowns.push({
72
- from: `${bracket.from}`,
73
- to: `${bracket.to ?? 'Above'}`,
74
- rate: bracket.rate,
75
- amount: bracketTax,
76
- });
63
+ tax += taxable * bracket.rate;
77
64
  }
78
65
  }
79
66
 
80
- return { tax, breakdowns };
67
+ return tax;
81
68
  }
82
- }
69
+ }