Files
Investidor-V2/resources/js/lib/fiscal-claim-events.ts
T

152 lines
5.2 KiB
TypeScript

import {
addFractions,
subtractFractions,
positiveFraction,
decimalFraction,
type FiscalFraction,
} from './fiscal-rational.ts';
export type FiscalClaimEvents = {
stage: 'preparation' | 'submitted';
requestReference: string;
requestDate: string;
decision: 'pending' | 'approved' | 'partial' | 'rejected';
decisionReference: string;
decisionDate: string;
decisionIssuer: string;
approvedAmount: string;
receipts: Array<{
id: string;
reference: string;
date: string;
amount: string;
}>;
};
export type FiscalClaimState = {
status: 'determined' | 'indeterminate';
reason: string;
approved: FiscalFraction | null;
received: FiscalFraction | null;
awaitingReceipt: FiscalFraction | null;
notGranted: FiscalFraction | null;
};
export function newFiscalClaimEvents(): FiscalClaimEvents {
return {
stage: 'preparation',
requestReference: '',
requestDate: '',
decision: 'pending',
decisionReference: '',
decisionDate: '',
decisionIssuer: '',
approvedAmount: '',
receipts: [],
};
}
export const fiscalClaimDate = (v: string) =>
/^20\d{2}-(0[1-9]|1[0-2])-\d{2}$/.test(v) &&
!Number.isNaN(Date.parse(v)) &&
new Date(v).toISOString().slice(0, 10) === v;
export function assessFiscalClaim(
row: FiscalClaimEvents,
requested: FiscalFraction,
duplicate: (p: FiscalClaimEvents['receipts'][number]) => boolean,
): FiscalClaimState {
const r: FiscalClaimState = {
status: 'indeterminate',
reason: '',
approved: null,
received: null,
awaitingReceipt: null,
notGranted: null,
},
stop = (reason: string) => ({ ...r, reason });
if (!row.requestReference.trim() || !fiscalClaimDate(row.requestDate))
return stop('Identifique referência/data do pedido ou preparação.');
if (
row.stage === 'preparation' &&
(row.decision !== 'pending' || row.receipts.length)
)
return stop(
'Preparação não é pedido apresentado;registe apresentação externa antes de decisão/recebimento.',
);
if (row.decision === 'pending') {
if (row.receipts.length)
return stop(
'Recebimento sem decisão identificada;conservar registo sem o presumir reconhecido.',
);
return {
...r,
status: 'determined',
reason: 'Pedido conservado;decisão e recebimento ainda não identificados.',
};
}
if (
!row.decisionReference.trim() ||
!row.decisionIssuer.trim() ||
!fiscalClaimDate(row.decisionDate) ||
row.decisionDate < row.requestDate
)
return stop(
'Identifique decisão recebida,número/data e entidade emitente;pedido não equivale a deferimento.',
);
let approved: FiscalFraction;
try {
approved = decimalFraction(row.approvedAmount);
} catch {
return stop(
'Montante reconhecido por identificar;indeferimento exigezero expresso,sem transformar desconhecido emzero.',
);
}
if (
BigInt(subtractFractions(approved, requested).numerator) > 0n ||
(row.decision === 'approved' &&
JSON.stringify(approved) !== JSON.stringify(requested)) ||
(row.decision === 'rejected' && BigInt(approved.numerator) !== 0n) ||
(row.decision === 'partial' &&
(BigInt(approved.numerator) <= 0n ||
BigInt(subtractFractions(requested, approved).numerator) <= 0n))
)
return stop(
'Montante reconhecido incompatível com decisão/pedido;conservar o âmbito exacto,sem generalizar o deferimento.',
);
r.approved = approved;
r.notGranted = subtractFractions(requested, approved);
const receipts: FiscalFraction[] = [];
for (const p of row.receipts) {
if (
!p.reference.trim() ||
!fiscalClaimDate(p.date) ||
p.date < row.decisionDate ||
duplicate(p)
)
return stop(
'Recebimento sem referência/data posterior à decisão ou já registado noutro pedido;sem duplicar caixa.',
);
try {
const value = decimalFraction(p.amount);
if (BigInt(value.numerator) <= 0n)
return stop(
'Recebimento exige montante positivo identificado.',
);
receipts.push(value);
} catch {
return stop(
'Montante recebido por identificar;decisão não cria recebimento.',
);
}
}
r.received = addFractions(receipts);
if (BigInt(subtractFractions(r.received, approved).numerator) > 0n)
return stop(
'Recebimentos excedem o montante reconhecido;reconcilie sem criar crédito ou caixa adicional.',
);
r.awaitingReceipt = positiveFraction(
subtractFractions(approved, r.received),
);
return {
...r,
status: 'determined',
reason: 'Pedido,decisão e recebimento separados;sem execução bancária ou sincronização silenciosa de caixa.',
};
}