Files
Investidor-V2/resources/js/lib/irpc-pec-obligation.ts
T
2026-11-11 10:55:53 +02:00

204 lines
8.7 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 {validatePecCorrection,correctedPecTurnover,pecCorrectionSource,type PecCorrection} from './irpc-pec-correction.ts';
import {validatePecPlan,pecPlanReport,type PecPlan} from './irpc-pec-plan.ts';
import {
decimalFraction,
fraction,
subtractFractions,
fractionPresentation,
type FiscalFraction,
} from './fiscal-rational.ts';
export const pecObligationVersion = 'IRPC-PEC-obligation/1';
export const pecObligationSource =
'RIRPC,Decreto9/2008,art29(1–5),BR16/I16-04-2008,original SHA256 fc373ff5637edf75ec6a9595dec8763a8503d4ee786e3b7d8f1c742eae9d80ee.0,5% do volume de negócios anterior,limites30.000/100.000MZN,menos PPC efectivamente pagos no ano anterior.Junho/Agosto/Outubro;opção de pagamento único prevista,sem data ou repartição inventada. Ano de início excluído29(4);sem volume anterior exige último montante29(5). Recorte2026,residente geral comercial/industrial/agrícola,ano civil,MZN. Regimes/períodos especiais,rectificação29(3),base de anos anteriores29(5),guias/juros separados.';
export type PecObligation = {
version: 1;
plan?: PecPlan;
correction?: PecCorrection;
scope: '' | 'ordinary_resident' | 'other';
subjectNuit: string;
year: string;
activityStartYear: string;
activityReference: string;
previousYear: string;
previousTurnover: string;
turnoverReference: string;
previousPpcPaid: string;
ppcReference: string;
};
export const newPecObligation = (
subjectNuit = '',
year = '',
): PecObligation => ({
version: 1,
scope: '',
subjectNuit,
year,
activityStartYear: '',
activityReference: '',
previousYear: '',
previousTurnover: '',
turnoverReference: '',
previousPpcPaid: '',
ppcReference: '',
});
export function validatePecObligation(p: PecObligation) {
if(p?.correction!==undefined)validatePecCorrection(p.correction);
if(p?.plan!==undefined)validatePecPlan(p.plan);
if (
!p ||
p.version !== 1 ||
Object.keys(newPecObligation())
.filter((k) => k !== 'version')
.some(
(k) =>
typeof (p as unknown as Record<string, unknown>)[k] !==
'string',
) ||
!['', 'ordinary_resident', 'other'].includes(p.scope)
)
throw Error(
'Preparação da estimativa PEC não recuperável;original preservado.',
);
}
export function calculatePecObligation(
p: PecObligation,
subjectNuit: string,
year: string,
assessment: string,
) {
validatePecObligation(p);
const pending: string[] = [];
const turnover=correctedPecTurnover(p.correction,p.previousTurnover,p.subjectNuit,p.previousYear);
let percentage: FiscalFraction | null = null,
limited: FiscalFraction | null = null,
total: FiscalFraction | null = null,
notApplicable = false;
if (
p.scope !== 'ordinary_resident' ||
assessment !== 'normal' ||
year !== '2026' ||
p.year !== year ||
p.subjectNuit !== subjectNuit ||
!/^\d{9}$/.test(subjectNuit)
)
pending.push(
'PEC: confirmar titular2026,residente geral com actividade comercial/industrial/agrícola,ano civil e MZN;outros regimes/períodos separados.',
);
if (
!/^\d{4}$/.test(p.activityStartYear) ||
Number(p.activityStartYear) < 1 ||
Number(p.activityStartYear) > 2026 ||
!p.activityReference.trim()
)
pending.push(
'PEC: falta ano de início efectivo da actividade e suporte;constituição da empresa não substitui início.',
);
if (!pending.length && p.activityStartYear === '2026') {
notApplicable = true;
total = fraction(0n);
} else if (!pending.length) {
pending.push(...turnover.pending);
if (
p.previousYear !== '2025' ||
!p.turnoverReference.trim() ||
!p.ppcReference.trim()
)
pending.push(
'PEC: falta exercício anterior2025 e fontes do volume de negócios e PPC efectivamente pagos,mesmo quando zero.',
);
if (
![turnover.value, p.previousPpcPaid].every((v) =>
/^\d{1,18}(\.\d{1,2})?$/.test(v),
)
)
pending.push(
'PEC: valores anteriores desconhecidos/inválidos;zero deve ser explícito.',
);
if (!pending.length) {
const v = decimalFraction(turnover.value),
ppc = decimalFraction(p.previousPpcPaid);
if (BigInt(v.numerator) === 0n)
pending.push(
'PEC29(5):sem volume de negócios em2025,recuperar último montante dos anos anteriores;zero não determina automaticamente o mínimo.',
);
else {
percentage = fraction(
BigInt(v.numerator),
BigInt(v.denominator) * 200n,
);
const n = BigInt(percentage.numerator),
d = BigInt(percentage.denominator);
limited =
n < 30000n * d
? fraction(30000n)
: n > 100000n * d
? fraction(100000n)
: percentage;
const residual = subtractFractions(limited, ppc);
if (BigInt(residual.numerator) < 0n)
pending.push(
'PEC:PPC anteriores excedem o montante limitado;caso separado para reconciliação,não gera estimativa negativa ou reembolso.',
);
else total = residual;
}
}
}
return {
version: pecObligationVersion,
source: p.correction ? pecObligationSource.replace('rectificação29(3),','')+' '+pecCorrectionSource : pecObligationSource,
pending,
percentage,
limited,
total,
notApplicable,
turnoverCorrection: p.correction ? {original:p.correction.originalTurnover,revised:p.correction.revisedTurnover,applied:!notApplicable&&turnover.applied,pending:turnover.pending,reference:p.correction.reference,source:pecCorrectionSource}:null,
months:
total && !notApplicable ? ['2026-06', '2026-08', '2026-10'] : [],
instalmentAmountsDetermined: false,
paymentExecuted: false,
annualDeductionCreated: false,
};
}
export function pecObligationReport(
p: PecObligation,
subjectNuit: string,
year: string,
assessment: string,
): Array<[string, string, string, string]> {
const m = calculatePecObligation(p, subjectNuit, year, assessment),
show = (v: FiscalFraction | null) =>
v ? fractionPresentation(v).decimal + ' MZN' : 'Indeterminado';
const originalEstimate=p.correction ? calculatePecObligation({...p,correction:undefined},subjectNuit,year,assessment).total:null;
const correctionEffect=originalEstimate&&m.total ? fractionPresentation(subtractFractions(m.total,originalEstimate)).decimal+' MZN':'por determinar';
return [
...(p.correction ? [['IRPC — L8','PEC — rectificação do volume','Original: '+p.correction.originalTurnover+' MZN;corrigido: '+p.correction.revisedTurnover+' MZN;'+(m.turnoverCorrection?.applied?'aplicada à estimativa.':'conservada sem aplicação à estimativa.')+' Estimativa original: '+show(originalEstimate)+';efeito na estimativa: '+correctionEffect+'. '+turnoverCorrectionNotice(m.turnoverCorrection),p.correction.reference+'; '+pecCorrectionSource] as [string,string,string,string]]:[]),
...(p.plan ? pecPlanReport(p.plan,m.total,m.notApplicable,year) : []),
[
'IRPC — L8',
'PEC — estimativa fiscal',
m.pending.length
? m.pending.join(' ')
: m.notApplicable
? 'Não aplicável no ano de início da actividade29(4);total0 MZN.'
: '0,5%: ' +
show(m.percentage) +
';limitado antes de PPC: ' +
show(m.limited) +
';total estimado: ' +
show(m.total) +
';meses: ' +
m.months.join(', ') +
(p.plan ? '. Plano explícito apresentado separadamente;prazo legal não validado.' : '. Montantes por prestação e opção de pagamento único não determinados neste recorte.') + ' Preparação não é pagamento nem dedução anual.',
p.activityReference +
'; ' +
p.turnoverReference +
'; ' +
p.ppcReference +
'; ' +
m.source,
],
];
}
function turnoverCorrectionNotice(c:{pending:string[]}|null){return c?.pending.join(' ')??'';}