saldo
Version:
Portuguese salary calculator library
128 lines (127 loc) • 5.85 kB
JavaScript
import { RetentionPathsSchema } from "../config/schemas";
import { taxTablesData } from "../data/retention-tax-tables-data"; // Import the pre-loaded JSON data
class TaxBracket {
signal;
limit;
max_marginal_rate;
deduction;
var1_deduction;
var2_deduction;
dependent_aditional_deduction;
effective_mensal_rate;
constructor(data) {
this.signal = data.signal;
this.limit = data.limit;
this.max_marginal_rate = data.max_marginal_rate;
this.deduction = data.deduction;
this.var1_deduction = data.var1_deduction;
this.var2_deduction = data.var2_deduction;
this.dependent_aditional_deduction = data.dependent_aditional_deduction;
this.effective_mensal_rate = data.effective_mensal_rate;
}
calculate_deductible(salary) {
// In Python, 0.0 is falsy. If var1_deduction or var2_deduction can be 0.0
// and that should prevent this path, this JS/TS equivalent is fine.
if (this.var1_deduction && this.var2_deduction) {
return this.deduction * this.var1_deduction * (this.var2_deduction - salary);
}
return this.deduction;
}
calculate_tax(taxable_income, twelfths_income, number_of_dependents = 0, extra_deduction = 0) {
const deduction = this.calculate_deductible(taxable_income);
let rate;
if (number_of_dependents >= 3) {
// https://diariodarepublica.pt/dr/detalhe/despacho/9971-a-2024-885806206#:~:text=h)%20Aos%20titulares,abater%20por%20dependente%3B
/*
h) Aos titulares de rendimentos de trabalho dependente com três ou mais dependentes
que se enquadrem nas tabelas previstas nas alíneas a) e b) do n.º 1, é aplicada uma
redução de um ponto percentual à taxa marginal máxima correspondente ao escalão em que
se integram, mantendo-se inalterada a parcela a abater e a parcela adicional a abater por dependente;
*/
rate = this.max_marginal_rate - 0.01;
}
else {
rate = this.max_marginal_rate;
}
const base_tax = taxable_income * rate -
deduction -
extra_deduction -
number_of_dependents * this.dependent_aditional_deduction;
// effective rate is the actual rate that is applied to the income after the deductions
// this is what we use to calculate the tax for the twelfths income
const effective_rate = taxable_income !== 0 ? base_tax / taxable_income : 0; // Avoid division by zero
const twelfths_tax = twelfths_income * effective_rate;
const tax = base_tax + twelfths_tax;
return Math.max(0, tax);
}
toJSON() {
return {
signal: this.signal,
limit: this.limit,
max_marginal_rate: this.max_marginal_rate,
deduction: this.deduction,
var1_deduction: this.var1_deduction,
var2_deduction: this.var2_deduction,
dependent_aditional_deduction: this.dependent_aditional_deduction,
effective_mensal_rate: this.effective_mensal_rate
};
}
}
class TaxRetentionTable {
region;
situation;
description;
tax_brackets; // Array of TaxBracket class instances
dependent_disabled_addition_deduction;
constructor(region, situation, description, tax_brackets_data, // Accepts raw data for brackets
dependent_disabled_addition_deduction) {
this.region = region;
this.situation = situation;
this.description = description;
this.tax_brackets = tax_brackets_data.map(b => new TaxBracket(b));
this.dependent_disabled_addition_deduction = dependent_disabled_addition_deduction;
}
find_bracket(salary) {
for (const bracket of this.tax_brackets) {
if (bracket.signal === "max" && salary <= bracket.limit) {
return bracket;
}
else if (bracket.signal === "min" && salary > bracket.limit) {
return bracket;
}
}
throw new Error(`No bracket found for salary ${salary}`);
}
// Renamed from load_from_file and modified to accept data directly
static from_data(region, data) {
if (!data) {
throw new Error(`Tax table data not provided.`);
}
return new TaxRetentionTable(region, // Region might need to be passed or inferred differently
data.situation, data.description, data.brackets, // Pass the raw bracket data
data.dependent_disabled_addition_deduction);
}
static load(period, location, situation_code, year) {
const yearStr = String(year);
const retentionTablePathGenerator = new RetentionPathsSchema(period, location, situation_code, yearStr);
const identifier = retentionTablePathGenerator.path; // This now returns the identifier string
const tableData = taxTablesData[identifier]; // Look up in the manifest
if (!tableData) {
throw new Error(`Tax table not found for identifier: ${identifier}. Ensure it's in retention-tax-tables-data.ts`);
}
// Assuming 'location' can be used as the region directly.
// The original load_from_file hardcoded "continent".
// We use the 'location' parameter now, which seems more correct.
return TaxRetentionTable.from_data(location, tableData);
}
toJSON() {
return {
situation: this.situation,
description: this.description,
brackets: this.tax_brackets.map(bracket => bracket.toJSON()),
dependent_disabled_addition_deduction: this.dependent_disabled_addition_deduction
};
}
}
// Export the classes if they are to be used by other modules
export { TaxBracket, TaxRetentionTable };