fix(iva): report only applicable special event facts preserving drafts

This commit is contained in:
2026-10-25 01:43:06 +02:00
parent 7986d02405
commit c309215d0f
3 changed files with 7 additions and 2 deletions
+5 -1
View File
@@ -93,4 +93,8 @@ export function recordTravelEvents(draft:Record<string,unknown>,book:TravelEvent
const result:TravelEventCalculation={version:travelEventVersion,revision:(book.result?.revision??0)+1,calculatedAt:new Date().toISOString(),opportunity:acquisitionScope(draft),sourceKey:key(draft,book),source:travelEventSource,inputs:structuredClone(book.rows),rows:book.rows.map(row=>evaluateTravelEvent(draft,row))};
const next={...book,result,history:[...(book.history??[]),...(book.result?[book.result]:[])]};saveTravelEventBook(draft,next);return next;
}
export function travelEventReportRows(draft:Record<string,unknown>):Array<[string,string,string,string]>{try{const b=travelEventBook(draft),r=b.result;if(!r)return[];return r.inputs.map((row,i)=>{const line=r.rows[i];return['IVA mensal',`P133 — ${row.period} — ${row.id}`,`${row.description};${line.reason};facto gerador:${line.eventDate??'Não apurado/ainda não ocorrido'};momento:${line.eventTiming??'Indeterminado'};parcelas:${line.dueEvents.map(p=>p.date+':'+(p.amount??'Montante indeterminado')+' '+row.currency+' ('+p.reason+')').join('; ')||'Nenhuma apurada'};restante:${line.status==='indeterminate'?'Indeterminado':line.remainingPending?'Pendente':'Sem restante apurado'};base/IVA/dedução:Indeterminados`,`${r.opportunity};${travelEventCurrent(draft,b)?'Resultado actual':'Origem/entradas alteradas; resultado histórico conservado'};versão${r.revision};${r.calculatedAt};${r.source};${row.origin};operação${row.operationRef};âmbito${row.scope};documento${row.documentRef},${row.documentDate};conferência${row.reviewDate};${row.treatment==='suspensive_exit'?`regime14(1)${row.regimeClause};entrada${row.entryDate},${row.entryRef};situação${row.regimeStatus};cessação${row.exitDate},${row.exitRef},confirmada${row.cessationConfirmed}`:`origem viagem${row.travelRowId},versão${line.source?.revision??'Indeterminada'},${line.source?.scope??''};pagamento integral${row.fullPaymentState},${row.fullPaymentDate},${row.fullPaymentRef};início${row.startState},${row.startDate},${row.startRef};factura${row.invoiceRequired},${row.invoiceState},${row.invoiceDate},${row.invoiceRef};prazo${row.legalDeadline},${row.deadlineRef};recebimento${row.paymentState},${row.paymentDate},${row.paymentAmount},${row.paymentRef};total${row.amount}${row.currency}`};factos${row.evidence};sem lançamento fiscal, nova dedução/campo11 ou totaisModeloA.`];});}catch{return[['IVA mensal','P133','Dados não recuperáveis','Originais conservados; nenhum evento por defeito.']];}}
function eventFacts(row:TravelEventRow,line:TravelEventLine):string{
if(row.treatment==='suspensive_exit')return`regime14(1)${row.regimeClause};entrada${row.entryDate},${row.entryRef};situação${row.regimeStatus};${row.regimeStatus==='ceased'?`cessação${row.exitDate},${row.exitRef},confirmada${row.cessationConfirmed}`:'dados de cessação sem aplicação conservados no rascunho/histórico, não usados neste resultado'}`;
return`origem viagem${row.travelRowId},versão${line.source?.revision??'Indeterminada'},${line.source?.scope??''};pagamento${row.fullPaymentState}${row.fullPaymentState==='full'?','+row.fullPaymentDate+','+row.fullPaymentRef:''};início${row.startState}${row.startState==='started'?','+row.startDate+','+row.startRef:''};factura${row.invoiceRequired};${row.invoiceRequired==='yes'?`emissão${row.invoiceState}${row.invoiceState==='issued'?','+row.invoiceDate+','+row.invoiceRef:''};prazo${row.legalDeadline},${row.deadlineRef};`:''}recebimento${row.paymentState}${row.paymentState==='received'?','+row.paymentDate+','+row.paymentAmount+','+row.paymentRef:''};total${row.amount}${row.currency};referências sem aplicação conservadas no rascunho/histórico`;
}
export function travelEventReportRows(draft:Record<string,unknown>):Array<[string,string,string,string]>{try{const b=travelEventBook(draft),r=b.result;if(!r)return[];return r.inputs.map((row,i)=>{const line=r.rows[i];return['IVA mensal',`P133 — ${row.period} — ${row.id}`,`${row.description};${line.reason};facto gerador:${line.eventDate??'Não apurado/ainda não ocorrido'};momento:${line.eventTiming??'Indeterminado'};parcelas:${line.dueEvents.map(p=>p.date+':'+(p.amount??'Montante indeterminado')+' '+row.currency+' ('+p.reason+')').join('; ')||'Nenhuma apurada'};restante:${line.status==='indeterminate'?'Indeterminado':line.remainingPending?'Pendente':'Sem restante apurado'};base/IVA/dedução:Indeterminados`,`${r.opportunity};${travelEventCurrent(draft,b)?'Resultado actual':'Origem/entradas alteradas; resultado histórico conservado'};versão${r.revision};${r.calculatedAt};${r.source};${row.origin};operação${row.operationRef};âmbito${row.scope};documento${row.documentRef},${row.documentDate};conferência${row.reviewDate};${eventFacts(row,line)};factos${row.evidence};sem lançamento fiscal, nova dedução/campo11 ou totaisModeloA.`];});}catch{return[['IVA mensal','P133','Dados não recuperáveis','Originais conservados; nenhum evento por defeito.']];}}
File diff suppressed because one or more lines are too long
@@ -50,6 +50,7 @@ test('P133 classification dependency is guarded; unknown margin amounts do not b
test('Private save/recovery/history/report preserve123 delivered books, digital limitation and partial drafts',()=>{
const t=recordTravel(draft,travel),d={...draft,modelo_a_snapshot:saveTravelBook(draft,t)},a=recordTravelEvents(d,{rows:events});d.modelo_a_snapshot=saveTravelEventBook(d,a);const b=recordTravelEvents(d,a);d.modelo_a_snapshot=saveTravelEventBook(d,b);
assert.equal(t.history.length,1);assert.equal(b.history.length,1);assert.equal(travelReportRows(d).length,15);assert.equal(travelEventReportRows(d).length,12);assert.equal(travelCurrent(d,t),true);assert.equal(travelEventCurrent(d,b),true);
const report=travelEventReportRows(d);assert.doesNotMatch(report[3][3],/SYNTHETIC QA liquidação integral/);assert.doesNotMatch(report[3][3],/SYNTHETIC QA primeiro serviço/);assert.doesNotMatch(report[11][3],/SYNTHETIC QA cessação efectiva/);assert.equal(b.rows[3].fullPaymentRef,'SYNTHETIC QA liquidação integral');assert.equal(b.rows[11].exitRef,'SYNTHETIC QA cessação efectiva');
const store=JSON.parse(d.modelo_a_snapshot);delete store.travelOperations;delete store.travelSpecialEvents;assert.deepEqual(store,prior);assert.deepEqual(travelBook({...d,context_opportunity:'opportunity:361'}),{rows:[],periods:[]});assert.deepEqual(travelEventBook({...d,context_opportunity:'opportunity:361'}),{rows:[]});
assert.throws(()=>recordTravel(d,{rows:[trips[0],trips[0]],periods}));assert.throws(()=>recordTravelEvents(d,{rows:[events[0],events[0]]}));assert.throws(()=>travelBook({...d,modelo_a_snapshot:JSON.stringify({...store,travelOperations:[]})}));assert.throws(()=>travelEventBook({...d,modelo_a_snapshot:JSON.stringify({...store,travelSpecialEvents:[]})}));
const switched=switchTravelResources(trips[4],'own');assert.equal(switched.allocationRef,trips[4].allocationRef);assert.deepEqual(switched.costs,trips[4].costs);assert.equal(switched.conditions.singlePrice,'');assert.equal(switchTravelEvent(events[0],'suspensive_exit').fullPaymentRef,events[0].fullPaymentRef);