UNPKG

saldo

Version:

Portuguese salary calculator library

279 lines (259 loc) 8.73 kB
export type SituationCodesT = | "SOLCAS2" | "SOLD" | "CAS1" | "SOLCAS2+DEF" | "SOLD+DEF" | "CAS2D+DEF" | "CAS1+DEF"; export type LocationT = "continente" | "acores" | "madeira"; export interface Condition { married: boolean; dependents: boolean; // True if has dependents False if not disabled: boolean; description: string; numberOfHolders?: number | null; // Number of holders, null if it's not applicable (e.g. not married) // Note: The Python __init__ logic for dependents based on numberOfDependents is handled differently in TS. // Consumers of this interface will need to ensure 'dependents' is set correctly. // We can add a constructor or factory function if complex initialization is needed. } export interface Situation { code: SituationCodesT; description: string; conditions: Condition[]; } export const Situations: { [key: string]: Situation } = { SOLCAS2: { code: "SOLCAS2", description: "Trabalho dependente - Não casado sem dependentes ou casado dois titulares", conditions: [ { description: "Não Casado sem dependentes", married: false, dependents: false, disabled: false, }, { description: "Casado, 2 titulares, sem dependentes", married: true, numberOfHolders: 2, dependents: false, disabled: false, }, { description: "Casado, 2 titulares, com dependentes", married: true, numberOfHolders: 2, dependents: true, disabled: false, }, ], }, SOLD: { code: "SOLD", description: "Trabalho dependente - Não casado com um ou mais dependentes", conditions: [ { description: "Não casado com um ou mais dependentes", married: false, dependents: true, disabled: false, }, ], }, CAS1: { code: "CAS1", description: "Trabalho dependente - Casado único titular", conditions: [ { description: "Casado único titular sem dependentes", married: true, numberOfHolders: 1, dependents: false, disabled: false, }, { description: "Casado único titular com dependentes", married: true, numberOfHolders: 1, dependents: true, disabled: false, }, ], }, SOLCAS2_DEF: { code: "SOLCAS2+DEF", description: "Trabalho dependente - Não casado ou casado dois titulares sem dependentes - deficiente", conditions: [ { description: "Não Casado sem dependentes - deficiente", married: false, dependents: false, disabled: true, }, { description: "Casado, 2 titulares, sem dependentes - deficiente", married: true, numberOfHolders: 2, dependents: false, disabled: true, }, ], }, SOLD_DEF: { code: "SOLD+DEF", description: "Trabalho dependente - Não casado, com um ou mais dependentes - deficiente", conditions: [ { description: "Não casado com um ou mais dependentes - deficiente", married: false, dependents: true, disabled: true, }, ], }, CAS2D_DEF: { code: "CAS2D+DEF", description: "Trabalho dependente - Casado dois titulares, com um ou mais dependentes - deficiente", conditions: [ { description: "Casado, 2 titulares, com dependentes - deficiente", married: true, numberOfHolders: 2, dependents: true, disabled: true, }, ], }, CAS1_DEF: { code: "CAS1+DEF", description: "Trabalho dependente - Casado único titular - deficiente", conditions: [ { description: "Casado único titular sem dependentes - deficiente", married: true, numberOfHolders: 1, dependents: false, // Python had None, translated to false based on Condition.__init__ disabled: true, }, { description: "Casado único titular com dependentes - deficiente", married: true, numberOfHolders: 1, dependents: true, disabled: true, }, ], }, }; // Utility functions for Situations export class SituationUtils { public static getSituationFromCode(code: string): Situation | undefined { return Object.keys(Situations) .map((key) => Situations[key]) .find((situation: Situation) => situation.code === code); } public static getSituation( married: boolean, disabled: boolean, numberOfHolders?: number | null, numberOfDependents?: number ): Situation | undefined { const inputCondition: Partial<Condition> & { married: boolean; disabled: boolean; dependents: boolean; } = { married, numberOfHolders: numberOfHolders, dependents: numberOfDependents !== undefined ? numberOfDependents > 0 : false, disabled, }; return SituationUtils.getSituationFromCondition( inputCondition as Condition ); } public static getSituationFromCondition( condition: Condition ): Situation | undefined { for (const key in Situations) { if (Object.prototype.hasOwnProperty.call(Situations, key)) { const situation = Situations[key]; for (const situationCondition of situation.conditions) { const marriedMatch = situationCondition.married === condition.married; let holdersMatch = true; if ( situationCondition.numberOfHolders !== undefined && situationCondition.numberOfHolders !== null ) { holdersMatch = situationCondition.numberOfHolders === condition.numberOfHolders; } // In Python, if situation_condition.number_of_holders is None, it implies a match. // Here, if situationCondition.numberOfHolders is undefined/null, we consider it a match only if condition.numberOfHolders is also undefined/null. // This might need adjustment based on exact Python None semantics vs TypeScript undefined/null. // For now, if schema has no numberOfHolders, it matches any. // If schema *has* numberOfHolders, it must match. let dependentsMatch = true; if (situationCondition.dependents !== undefined) { // Python code treated None for dependents as a wildcard / match dependentsMatch = situationCondition.dependents === condition.dependents; } const disabledMatch = situationCondition.disabled === condition.disabled; if (marriedMatch && holdersMatch && dependentsMatch && disabledMatch) { return situation; } } } } // In Python, this raised ValueError. In TS, we return undefined. // console.error(`Situation with condition ${JSON.stringify(condition)} not found.`); return undefined; } } export class RetentionPathsSchema { public identifier: string; // Renamed from yearPath, locationPath, etc. to a single identifier constructor( dateStart: Date, dateEnd: Date, location: LocationT, situationCode: SituationCodesT, // This is the code like "SOLD", "CAS1" year: number | string ) { const yearStr = String(year); const customPadStart = (str: string, targetLength: number, padString: string): string => { targetLength = targetLength >> 0; // floor if number or convert non-number to 0 padString = String(typeof padString !== 'undefined' ? padString : ' '); if (str.length > targetLength) { return String(str); } else { targetLength = targetLength - str.length; if (targetLength > padString.length) { padString += padString.repeat(targetLength / padString.length); // append to original to ensure we are longer than needed } return padString.slice(0, targetLength) + String(str); } }; const formatDate = (date: Date) => { const y = date.getFullYear(); const m = customPadStart(String(date.getMonth() + 1), 2, "0"); const d = customPadStart(String(date.getDate()), 2, "0"); return `${y}-${m}-${d}`; }; // Construct the identifier string matching the keys in taxTablesData // e.g., "2024/continente/2024-01-01_2024-12-31/SOLD" // Note: situationCode already comes without .json this.identifier = `${yearStr}/${location}/${formatDate(dateStart)}_${formatDate(dateEnd)}/${situationCode}`; } public get path(): string { // Keep 'path' getter name for compatibility if it's widely used, // but it now returns the identifier. return this.identifier; } }