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.', }; }