18 lines
3.8 KiB
TypeScript
18 lines
3.8 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.'};
|
|
}
|