Files
Investidor-V2/resources/js/lib/irpc-ppc-obligation.ts
T
2026-11-11 10:37:33 +02:00

169 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
decimalFraction,
fraction,
subtractFractions,
fractionPresentation,
type FiscalFraction,
} from './fiscal-rational.ts';
export const ppcObligationVersion = 'IRPC-PPC-obligation/1';
export const ppcObligationSource =
'RIRPC, Decreto 9/2008, arts.27(1)(a) e28(1–3), BR16/I,16-04-2008,exemplar preservado SHA256 fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee: imposto liquidado no exercício anterior nos termos21(1), líquido das retenções na fonte;80%, dividido em três montantes iguais arredondados por excesso ao milhar de meticais. Maio,Julho,Setembro. Recorte2026,residente geral,actividade comercial/industrial/agrícola,ano civil e exercício anterior completo2025,MZN. Períodos especiais,anualização28(3),dispensas,juros e guias separados.';
export type PpcObligation = {
version: 1;
scope: '' | 'ordinary_resident' | 'other';
subjectNuit: string;
year: string;
previousYear: string;
previousFullCalendarYear: '' | 'yes' | 'no';
previousTax: string;
previousWithholdings: string;
assessmentReference: string;
withholdingReference: string;
};
export const newPpcObligation = (
subjectNuit = '',
year = '',
): PpcObligation => ({
version: 1,
scope: '',
subjectNuit,
year,
previousYear: '',
previousFullCalendarYear: '',
previousTax: '',
previousWithholdings: '',
assessmentReference: '',
withholdingReference: '',
});
export function validatePpcObligation(p: PpcObligation) {
if (
!p ||
p.version !== 1 ||
Object.keys(newPpcObligation())
.filter((k) => k !== 'version')
.some(
(k) =>
typeof (p as unknown as Record<string, unknown>)[k] !==
'string',
) ||
!['', 'ordinary_resident', 'other'].includes(p.scope) ||
!['', 'yes', 'no'].includes(p.previousFullCalendarYear)
)
throw Error('Preparação PPC não recuperável; original preservado.');
}
export function calculatePpcObligation(
p: PpcObligation,
subjectNuit: string,
year: string,
assessment: string,
) {
validatePpcObligation(p);
const pending: string[] = [];
let base: FiscalFraction | null = null,
annualBeforeRounding: FiscalFraction | null = null,
instalment: FiscalFraction | null = null;
if (
p.scope !== 'ordinary_resident' ||
assessment !== 'normal' ||
p.previousFullCalendarYear !== 'yes' ||
year !== '2026' ||
p.year !== year ||
p.previousYear !== '2025' ||
!/^\d{9}$/.test(subjectNuit) ||
p.subjectNuit !== subjectNuit
)
pending.push(
'PPC: confirmar titular,2026,residente geral com actividade comercial/industrial/agrícola e exercício civil completo2025. Outros períodos/regimes e anualização permanecem separados.',
);
if (!p.assessmentReference.trim() || !p.withholdingReference.trim())
pending.push(
'PPC: falta suporte do imposto liquidado anterior21(1) e das retenções na fonte, incluindo zero conhecido. Colecta projectada corrente não substitui liquidação anterior.',
);
if (
![p.previousTax, p.previousWithholdings].every((v) =>
/^\d{1,18}(\.\d{1,2})?$/.test(v),
)
)
pending.push(
'PPC: montantes anteriores em MZN desconhecidos ou inválidos; zero deve ser explícito.',
);
if (!pending.length) {
base = subtractFractions(
decimalFraction(p.previousTax),
decimalFraction(p.previousWithholdings),
);
if (BigInt(base.numerator) < 0n)
pending.push(
'PPC: retenções superiores ao imposto anterior; reconciliar o caso antes de determinar a obrigação.',
);
else {
annualBeforeRounding = fraction(
BigInt(base.numerator) * 4n,
BigInt(base.denominator) * 5n,
);
const n = BigInt(annualBeforeRounding.numerator),
d = BigInt(annualBeforeRounding.denominator) * 3n * 1000n;
instalment = fraction(((n + d - 1n) / d) * 1000n);
}
}
return {
version: ppcObligationVersion,
source: ppcObligationSource,
pending,
base,
annualBeforeRounding,
instalment,
total: instalment
? fraction(
BigInt(instalment.numerator) * 3n,
BigInt(instalment.denominator),
)
: null,
schedule: instalment
? ['2026-05', '2026-07', '2026-09'].map((month) => ({
month,
amount: instalment,
}))
: [],
paymentExecuted: false,
annualDeductionCreated: false,
};
}
export function ppcObligationReport(
p: PpcObligation,
subjectNuit: string,
year: string,
assessment: string,
): Array<[string, string, string, string]> {
const m = calculatePpcObligation(p, subjectNuit, year, assessment),
show = (v: FiscalFraction | null) =>
v ? fractionPresentation(v).decimal + ' MZN' : 'Indeterminado';
return [
[
'IRPC — L8',
'PPC — estimativa fiscal',
m.pending.length
? m.pending.join(' ')
: 'Base líquida anterior: ' +
show(m.base) +
';80% antes de arredondar: ' +
show(m.annualBeforeRounding) +
'; cada prestação: ' +
show(m.instalment) +
'; total arredondado: ' +
show(m.total) +
'. ' +
m.schedule
.map((s) => s.month + ': ' + show(s.amount))
.join('; ') +
'. Previsão de obrigação; não é pagamento nem dedução anual.',
p.assessmentReference +
'; ' +
p.withholdingReference +
'; ' +
m.source,
],
];
}