204 lines
8.7 KiB
TypeScript
204 lines
8.7 KiB
TypeScript
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(' ')??'';}
|