Prepare exact beverage partitions, fiscal gap diagnostics and L3 form review in QA
This commit is contained in:
@@ -27218,5 +27218,11 @@
|
||||
"received": 7,
|
||||
"awaiting_receipt": 5
|
||||
}
|
||||
},
|
||||
"l3_e_additional_qa_20261025": {
|
||||
"status": "QA_demonstrated_not_installed",
|
||||
"installed_E": "7ad187f9",
|
||||
"report": "docs\\investmoz\\simulations\\l3-e-additional-coverage-qa-20261025.json",
|
||||
"L3": "open"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
{
|
||||
"status": "QA_demonstrated_not_installed",
|
||||
"installed_E": "7ad187f9f0745245268e3ed5317547a96b4700f3",
|
||||
"installation_receipt": "dbeaf886",
|
||||
"installed_example_MZN": {
|
||||
"documentary_VAT": 16,
|
||||
"deduction_this_route": 0,
|
||||
"recognised": 12,
|
||||
"received": 7,
|
||||
"awaiting_receipt": 5
|
||||
},
|
||||
"demonstrated_increment": {
|
||||
"partition": "Non-overlapping VAT intervals on the same documentary line;6+10=16;bounds/support required;remaining portion has no presumed destination.",
|
||||
"prior_deduction": "Positive effective deductions inventoried by context/revision/exact amount. Stable deduction6 assigned explicitly to[0,6[;E requests only[6,16[=10. Prior deduction preserved;no new deduction/reversal or AT submission. Exact rational boundaries also supported:prior16/3 and E32/3;rounded5.333333 boundary overlaps and is rejected.",
|
||||
"anti_duplication": "Overlap and legacy whole-line conflicts refused;effective cross-context uses and determined submitted histories retained. Editing/removing current preparation does not release committed portions. Invalid recalculation does not commit rejected boundaries;reference/decimal presentation edits do not create new allocations.",
|
||||
"shared_L2_delta": "Existing actual allocation22 can compose with the independently verified beverage link13(2);it still requires all allocation/documentary conditions. Removed technical exclusion of the specific link in the partial-evaluation branch.",
|
||||
"calculation_gaps": "Read-only inventory of applicable saved results:reason,missing monetary fields,required data and concrete resolution route. Frozen labels survive deliberate current edits;unrecoverable books produce visible errors.",
|
||||
"formal_preparation": "Identified fields19/20/21–27 from received exemplar prepared for review/HTML export by applicable A/B,C,D. E remains separate supporting dossier;no field27 assigned. Unknown values stay unknown;frozen inputs/results reused,not recalculated on export."
|
||||
},
|
||||
"verification": {
|
||||
"frontend_distinct_tests": 43,
|
||||
"frontend_proof": [
|
||||
"frontend-complete.txt:41 distinct passed",
|
||||
"form-fields.txt:1 additional passed",
|
||||
"fraction-test-final.txt:non-terminating exact fraction case passed;2 prior cases repeated only for changed parser"
|
||||
],
|
||||
"private_API": {
|
||||
"tool": "phpunit",
|
||||
"result": "passed",
|
||||
"tests": 1,
|
||||
"passed": 1,
|
||||
"assertions": 105,
|
||||
"duration_ms": 8525
|
||||
},
|
||||
"QA_database": "simulator_fiscal_delta_20261021_test:55441;DatabaseTransactions rollback;exact snapshot recovery",
|
||||
"types": "passed",
|
||||
"build": "passed",
|
||||
"UI": [
|
||||
"real component:5–16 overlaps previous0–6 and is rejected;restore6–16 gives10",
|
||||
"save→recover→report confirms10 and gap/form sections",
|
||||
"actual HTML download checked;C/D field mappings separately tested72,61,800000/200000/600000"
|
||||
],
|
||||
"UI_proof": "outputs\\iva-l3-e-partitions-20261025\\ui-reconciled-6-10.png",
|
||||
"sources_unchanged": 37,
|
||||
"prior_contexts_preserved_API_UI": true,
|
||||
"matrix_other_rows_unchanged": true
|
||||
},
|
||||
"runtime_delta": [
|
||||
{
|
||||
"path": "resources/js/components/acquisition-beverage-restitution.tsx",
|
||||
"sha256_LF": "6f44897b8b12569314551df0022c26da57a165634087942a724293e66837558d"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/fiscal-calculation-gaps.tsx",
|
||||
"sha256_LF": "1dab6ceb73756d4e023096b3b2ba767aaa843ff6cc69046ea630ab449a00b0dd"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/fiscal-l3-preparation.tsx",
|
||||
"sha256_LF": "e84a7b1ccadaa1b5bd78d778a3287f96ccaf530855eef404786cd7aa3d321b73"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/components/simulation-acquisitions.tsx",
|
||||
"sha256_LF": "c206d4cfe575d6ba007aa2b425b5f898efa97e4162b8710e358b6ecfd9458542"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-acquisition-bc.ts",
|
||||
"sha256_LF": "b0ef55e0917bdb79f14b53ba9e95d73c08153befeb7d4ce73f81e6fb13992225"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-beverage-allocation.ts",
|
||||
"sha256_LF": "32968d198c7fe0c53ca89641c22d5749be6636be2c79127b867e4e3afd0dd40d"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-beverage-restitution.ts",
|
||||
"sha256_LF": "5c650cbd9469d5f99001f56fc4ff99ac973eb368036d4e96c6d171f1b220bd9e"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/iva-calculation-gaps.ts",
|
||||
"sha256_LF": "2e7d84a0c196bd5b56fd67cc1a389d43d5041894184e675680a4de3cd10b31a2"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/l3-form-preparation.ts",
|
||||
"sha256_LF": "fc47f5fa5ec1420fbd847d0d081f00d99ebbb524164eef785b20877e0562f704"
|
||||
},
|
||||
{
|
||||
"path": "resources/js/lib/project-dossier.ts",
|
||||
"sha256_LF": "0d6f12b43a1137cbd8146e5d1beac960af48f4389f95c58dbd21505ca9635f46"
|
||||
}
|
||||
],
|
||||
"inventory_evidence": "outputs\\iva-l3-e-partitions-20261025\\calculation-inventory.json",
|
||||
"inventory_limits": "67 synthetic QA contexts,331 occurrences including deliberately incomplete/negative fixtures;not331newbusinessrequirements or a replacement matrix. Each record retains exact reason/data/resolution;no tax calculation activated.",
|
||||
"formalities_dependencies": {
|
||||
"C": "ModeloB/RCIVA15 versus substituteModeloA exemplar;need corresponding edition/instruction.",
|
||||
"D": "Regulatory exemplars/instructions Decretos78/2017,30/2022,52/2025;no new evidence,so previous research not repeated.",
|
||||
"E": "Specific model/procedure13(2) not identified;do not reuse general refund forms/thresholds/deadlines."
|
||||
},
|
||||
"remaining": [
|
||||
"Changing/reversing a prior deduction,especially whole-line16,requires supported sequential regularization and its effective fiscal treatment;not automated by choosingE or by a supplier-correction route requiring unchanged right.",
|
||||
"Divergent historical positive deductions require sequence reconciliation;no choice of a convenient revision and no release from an unexecuted correction.",
|
||||
"No central AT/cross-dossier account reconciliation beyond recovered maps. The unused part does not obtain a deduction automatically.",
|
||||
"Supply13(2),margin/travel/event VAT and integrations that remain indeterminate need their own rule,base,data and implementation;purchaseVAT16 is never substituted for outputVAT.",
|
||||
"Formalities remain delimited as above;HTML preparations do not certify current electronic edition or replace external submission.",
|
||||
"L1:P058/P084/P085 andP00105/06;L2:18(6)+mixed,successive/pluriennial,errors/baddebts remain in original matrix.",
|
||||
"Tempo request→decision→form procedure preserved;statistics/AI remain deferred."
|
||||
],
|
||||
"schema_changes": 0,
|
||||
"active_writes_for_increment": 0,
|
||||
"L3": "open"
|
||||
}
|
||||
@@ -5678,3 +5678,23 @@ Recibo: [cadastro, acessos e preservação no destino](legacy-editorial-installa
|
||||
E instalado em `7ad187f9`, candidata `47086625`; C/D `de36f028` e recibo `46944402` preservados. Backup novo e restauro real isolado confirmam174tabelas;369ficheiros/configuração e12ficheiros de execução conferidos. Manifesto/revisão,marcadoresE/C/D e simuladorHTTP200 confirmados. Apenas cache/sessions podem variar;sem migrações,importações ou recálculo de históricos. Pipeline:746testes frontend,59testesAPI/317asserções econtrolo1/3106 passaram.
|
||||
|
||||
Mantém16MZN deIVA documental,zero de dedução nesta via,12reconhecidos,7recebidos e5porreceber. Não éIVA do fornecimento nem pagamento bancário. Recibo:`docs/investmoz/simulations/l3-e-delivery-20261025.json`. L3permanece aberto;repartição/reconciliação,apuramentos eformalidades avançam separadamente emQA. L1conserva129fechos eP058/P084/P085/P001;L2conserva18(6)+mista,sucessivas/plurianuais,erros/incobráveis. ProcedimentoTempo intacto;estatísticas/IAadiadas.
|
||||
|
||||
## 25/10/2026 — inventário antes dos novos testes de parcelas/indeterminados/formalidades
|
||||
|
||||
E7ad187f9/recibodbeaf886 eC/Dde36f028/46944402 preservados;129fechosL1 eP058/P084/P085/P001,limitaçõesL2 eprocedimentoTempo permanecem na matriz existente. Sem perdas confirmadas no delta;provas aceites reutilizadas. Inventário completo das fontes fiscais eanexos acessíveis nas pastas autorizadas Notas/Modelos antes de seleccionar consulta:outputs/iva-l3-e-partitions-20261025/preservation-before-tests.json. Escolha técnica de representação:intervalos doIVA documental evitam sobreposição,sem criar requisito organizacional ou novo benefício. Desenvolvimento e novos testes apenasQA.
|
||||
|
||||
## 25/10/2026 — parcelas de E, diagnóstico e preparação formal em QA
|
||||
|
||||
E entregue mantém `7ad187f9`, recibo `dbeaf886`, com16MZNdocumentais/zero de dedução nesta via/12reconhecidos/7recebidos/5porreceber. Este incremento permanece emQA,sem instalação,migrações ou importações. C/D eprocedimentoTempo preservados;L3continua aberto.
|
||||
|
||||
Na restituição existente,escolher linha integral ou parcela documentada. Os limites representam partes doIVA documental:0–6 e6–16 não se sobrepõem num total16. A parte não utilizada não recebe destino/dedução automáticos. Para uma dedução efectiva anterior estável6,indicar a sua afectação documentada0–6;somente6–16=10 pode seguirE. O livro original de dedução mantém-se. Sem suporte,montantes históricos divergentes,sobreposição ou dedução integral16,não há restituição presumida. A anulação/redução da dedução exige fundamento,sequência e tratamento fiscal próprios ainda não automatizados nesta via;editar a aquisição ou preparar uma correcção não liberta a parcela. Não usar51(4),que exige manutenção do âmbito/direito,como regra automática de mudança paraE. Livros/históricos efectivos recuperados impedem duplicação;isto não é reconciliação central daAT.
|
||||
|
||||
O componente de aquisições mostra,por contexto,os cálculos guardados indeterminados:montantes em falta,motivo,dados necessários econdição de resolução. Base/margem,localização ou momento determinados não significamIVA liquidado. Fornecimento13(2),margens/viagens/eventos e integrações ainda limitadas conservam IVA próprio indeterminado;os16da aquisição não o substituem. Os mesmos diagnósticos seguem no relatório,com entradas congeladas preservadas. O inventário de67contextos/331ocorrências do corpusQA inclui provas negativas deliberadas,não331novos requisitos. Evidência:outputs/iva-l3-e-partitions-20261025/calculation-inventory.json;matriz existente mantémAUD-SIM-INT02.
|
||||
|
||||
Preparação dos quadros8–11 doVATReturn recebido:AB19/20/22–27;C21–24;D25/26/27. ExportaçãoHTML para conferência reutiliza apenas campos identificados,resultados congelados efracções exactas;campos05/06,assinaturas,meio de pagamento e uso dos serviços não são preenchidos por presunção. Egera dossier de apoio próprio,sem o transformar em campo27. A exportação não atesta edição electrónica nem submissãoAT. DependênciaC:ModeloB/RCIVA15 versus substituiçãoModeloA do exemplar;D:exemplares/instruções dosDecretos78/2017,30/2022,52/2025 identificados antes;E:modelo/procedimento específico13(2). Retomar cada dependência apenas com exemplar/instrução pertinente;sem pesquisas repetidas sem evidência nova e sem importar condições deD paraE.
|
||||
|
||||
Verificação:43testesfrontend dirigidos;API1teste/105asserções,QA55441 comrollback erecuperação exacta;TypeScript/build passaram. UIreal:rejeita5–16 sobre dedução0–6;repor6–16 recupera pedido10;gravar/recuperar/relatar preserva restantes mapas;HTML realmente descarregado econferido.37fontes/anexos acessíveis conservaram hashes. A baseQA estava parada;arranque erecuperação automática concluídos antes do teste privado,sem usar a baseactiva. ReciboQA:docs/investmoz/simulations/l3-e-additional-coverage-qa-20261025.json.
|
||||
|
||||
L1conserva129fechos,P058/P084/P085 eP00105/06. L2mantém18(6)+mista,sucessivas/plurianuais,erros/incobráveis. ProcedimentoTempo pedido→decisão→formulário intacto. Estatísticas/IAadiadas.
|
||||
|
||||
Limites das parcelas também aceitam fracções exactas,por exemplo16/3. Dedução anterior16/3 eparcelaE32/3 somam16sem aproximação;indicar5.333333 como início da restituição sobrepõe-se à dedução exacta e é recusado. Escolha técnica de representação,sem nova condição fiscal ou organizacional.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,7 @@
|
||||
import {useState} from 'react';
|
||||
import {beverageRestitutionBook,beverageRestitutionSources,newBeverageRestitution,saveBeverageRestitutionBook,recordBeverageRestitution,beverageRestitutionReportRows,type BeverageRestitutionBook,type BeverageRestitutionInput} from '@/lib/iva-beverage-restitution';
|
||||
import {beverageDeductionEvidence} from '@/lib/iva-beverage-allocation';
|
||||
import {fractionPresentation} from '@/lib/fiscal-rational';
|
||||
import {FiscalClaimEventsFields} from './fiscal-claim-events';
|
||||
export function AcquisitionBeverageRestitution({draft,onChange}:{draft:Record<string,unknown>;onChange:(field:string,value:string)=>void}){
|
||||
const[error,setError]=useState('');let b:BeverageRestitutionBook;try{b=beverageRestitutionBook(draft);}catch(e){return<p role="alert">{String(e)}</p>;}
|
||||
@@ -7,14 +9,17 @@ export function AcquisitionBeverageRestitution({draft,onChange}:{draft:Record<st
|
||||
const change=(id:string,p:Partial<BeverageRestitutionInput>)=>save({...b,rows:b.rows.map(r=>r.id===id?{...r,...p}:r)});
|
||||
return<details className="rounded border p-3"><summary>L3-E — restituição de bebidas13(2)</summary>
|
||||
<p>Reutiliza bebidas de abastecimento de bordo13(1)(c/d/h),com documento e vínculoL1/L2. Escolha restituição na aquisição e recalculeL2:esta linha não gera dedução nem crédito para reembolso geral. Não se aplica a qualquer despesa com bebidas,tabaco ou recepção.</p>
|
||||
<p>Âmbito:linha documental integral exclusivamente para restituição,2026/MZN. Parcelas mistas ou correcção de dedução efectiva anterior exigem reconciliação própria;históricos preservados. Pedido,decisão e recebimento são registos distintos. Procedimento/formulário específico e prazos não automatizados;submissão fora doInvesteMoz,sem condições importadas do reembolso geralD.</p>
|
||||
<p>Âmbito2026/MZN:linha integral ou parcelas de IVA documentadas,sem sobreposição. A parte não pedida fica sem destino presumido. Uma dedução efectiva anterior conserva montante e histórico;identifique a parcela já deduzida. A mudança ou anulação dessa dedução requer regularização com fundamento próprio,sem a libertar por simples edição. Pedido,decisão e recebimento são registos distintos. Procedimento/formulário específico e prazos não automatizados;submissão fora doInvesteMoz,sem condições importadas do reembolso geralD.</p>
|
||||
{error&&<p role="alert">{error}</p>}
|
||||
<button type="button" onClick={()=>save({...b,rows:[...b.rows,newBeverageRestitution(crypto.randomUUID(),sources.length===1?sources[0]:null,String(draft.fiscal_entity_ref??draft.organization_name??''))]})}>Preparar restituição de bebidas</button>
|
||||
{b.rows.map(r=><fieldset key={r.id} className="rounded border p-3"><legend>Restituição — {r.source?.period||'origem por escolher'}</legend>
|
||||
<label>Linha documental destinada à restituição<select value={sources.findIndex(s=>s.parcel===r.source?.parcel&&s.fingerprint===r.source?.fingerprint)} onChange={e=>{const s=sources[Number(e.target.value)];if(s)change(r.id,{source:structuredClone(s)});}}><option value={-1}>Escolha origem actualL1/L2</option>{sources.map((s,i)=><option key={s.parcel} value={i}>{s.documentRef} — linha{s.documentLine||'única'} — {s.period}</option>)}</select></label>
|
||||
{r.source&&<p>Documento:{r.source.documentRef};{r.source.documentDate};fornecedor{r.source.supplier};operaçãoL1{r.source.operationId};{r.source.operationRef}. Montante pedido reutiliza o IVA identificado da linha,sem taxa presumida.</p>}
|
||||
{r.source&&<p>Documento:{r.source.documentRef};{r.source.documentDate};fornecedor{r.source.supplier};operaçãoL1{r.source.operationId};{r.source.operationRef}. O total documental é reutilizado sem taxa presumida; repartição e IVA do fornecimento mantêm cálculos próprios.</p>}
|
||||
<label>Âmbito fiscal da entidade<input value={r.entityRef} onChange={e=>change(r.id,{entityRef:e.target.value})}/></label>
|
||||
<label>Linha integral exclusivamente para restituição,sem dedução da mesma parcela<select value={r.exclusive} onChange={e=>change(r.id,{exclusive:e.target.value as BeverageRestitutionInput['exclusive']})}><option value="">Por confirmar</option><option value="yes">Sim</option><option value="no">Não — reconciliar parcela</option></select></label>
|
||||
<label>Parcela exclusivamente para restituição,sem dedução da mesma parcela<select value={r.exclusive} onChange={e=>change(r.id,{exclusive:e.target.value as BeverageRestitutionInput['exclusive']})}><option value="">Por confirmar</option><option value="yes">Sim</option><option value="no">Não — reconciliar parcela</option></select></label>
|
||||
<label>Âmbito da restituição<select value={r.allocation?'partial':'full'} onChange={e=>change(r.id,{allocation:e.target.value==='partial'?{from:'',to:'',reference:'',deductionFrom:'',deductionTo:'',deductionReference:''}:undefined})}><option value="full">Linha integral</option><option value="partial">Parcela documentada da mesma linha</option></select></label>
|
||||
{r.allocation&&<fieldset><legend>Repartição do IVA documental em MZN</legend><p>Os limites identificam partes do IVA da linha: por exemplo, de 0 a 6 e de 6 a 16 são parcelas distintas de um total de 16. A parte restante não gera dedução automática. Pode indicar valores decimais ou fracções exactas,como16/3;sem arredondar a dedução anterior.</p>{([['from','Início da parcela'],['to','Fim da parcela'],['reference','Suporte da repartição']] as const).map(([field,label])=><label key={field}>{label}<input value={r.allocation![field]} onChange={e=>change(r.id,{allocation:{...r.allocation!,[field]:e.target.value}})}/></label>)}</fieldset>}
|
||||
{r.source&&(()=>{const evidence=beverageDeductionEvidence(draft,r.source!);return<fieldset><legend>Reconciliação da dedução anterior</legend><p>IVA documental: {fractionPresentation(r.source!.vat).decimal} MZN. Dedução efectiva positiva identificada: {evidence.amount?fractionPresentation(evidence.amount).decimal:'Indeterminada'} MZN. {evidence.reason}</p>{evidence.records.map((v,i)=><p key={i}>{v.context};revisão{v.revision};{v.calculatedAt};{fractionPresentation(v.amount).decimal} MZN;{v.documents.join(' | ')}</p>)}{r.allocation&&evidence.records.length>0&&([['deductionFrom','Início da parcela já deduzida'],['deductionTo','Fim da parcela já deduzida'],['deductionReference','Suporte da afectação da dedução anterior']] as const).map(([field,label])=><label key={field}>{label}<input value={r.allocation![field]} onChange={e=>change(r.id,{allocation:{...r.allocation!,[field]:e.target.value}})}/></label>)}</fieldset>;})()}
|
||||
<FiscalClaimEventsFields value={r} onChange={p=>change(r.id,p)}/>
|
||||
<button type="button" onClick={()=>save({...b,rows:b.rows.filter(q=>q.id!==r.id)})}>Remover restituição desta preparação</button>
|
||||
</fieldset>)}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import {fiscalCalculationGaps} from '@/lib/iva-calculation-gaps';
|
||||
export function FiscalCalculationGaps({draft}:{draft:Record<string,unknown>}){
|
||||
try{const gaps=fiscalCalculationGaps(draft);if(!gaps.length)return null;return<details className="rounded border p-3"><summary>Cálculos indeterminados e condições para resolver ({gaps.length})</summary><p>Contexto desta oportunidade. Cada cálculo conserva o motivo, os dados necessários e a condição de resolução. IVA documental de aquisição e IVA do fornecimento permanecem separados.</p>{gaps.map(g=><article key={g.id} className="border-t py-2"><h4>{g.period} — {g.calculation}</h4><p>{g.reason}</p><p>Dados necessários: {g.required.join('; ')}.</p><p>Para resolver: {g.resolution}</p></article>)}</details>;}catch(error){return<p role="alert">{String(error)}</p>;}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import {l3FormPreparations,l3FormPreparationHtml,l3FormalitiesDependencies,l3PreparationStatus} from '@/lib/l3-form-preparation';
|
||||
export function FiscalL3Preparation({draft}:{draft:Record<string,unknown>}){
|
||||
try{const forms=l3FormPreparations(draft);if(!forms.length)return null;return<details className="rounded border p-3"><summary>Preparação dos quadros L3 e formalidades</summary><p>Reutiliza entradas e resultados congelados. Os campos com correspondência identificada ficam preparados para conferência e exportação. As formalidades específicas por identificar conservam o seu âmbito.</p>{forms.map(p=><article key={p.id} className="border-t py-2"><h4>{p.branch} — {p.period} — {p.entity}</h4><p>{p.current?'Resultado correspondente às entradas':'Resultado histórico;rever entradas/origem'};{l3PreparationStatus(p.status)}</p><table><thead><tr><th>Campo</th><th>Finalidade</th><th>MZN</th></tr></thead><tbody>{p.fields.map((f,i)=><tr key={i}><td>{f.field}</td><td>{f.label}</td><td>{f.value}</td></tr>)}</tbody></table><p>{p.reference}</p><button type="button" onClick={()=>{const url=URL.createObjectURL(new Blob([l3FormPreparationHtml(p)],{type:'text/html;charset=utf-8'})),link=document.createElement('a');link.href=url;link.download=`preparacao-L3-${p.branch.replace('/','-')}-${p.period||'periodo'}.html`;link.click();setTimeout(()=>URL.revokeObjectURL(url),1000);}}>Exportar preparação {p.branch} para conferência</button></article>)}{l3FormalitiesDependencies.filter(dep=>forms.some(p=>p.branch===dep.branch)).map(dep=><p key={dep.branch}><strong>{dep.branch}:</strong> {dep.missing} Retomar com: {dep.trigger}</p>)}</details>;}catch(error){return<p role="alert">{String(error)}</p>;}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import {FiscalCalculationGaps} from './fiscal-calculation-gaps';
|
||||
import {FiscalL3Preparation} from './fiscal-l3-preparation';
|
||||
import { useState } from 'react';
|
||||
import {AcquisitionClosure} from './acquisition-closure';
|
||||
import {AcquisitionSubstitution} from './acquisition-substitution';
|
||||
@@ -91,6 +93,8 @@ export function SimulationAcquisitions({draft,onChange}:{draft:Record<string,unk
|
||||
<AcquisitionSubstitution draft={draft} onChange={onChange}/>
|
||||
<AcquisitionRefund draft={draft} onChange={onChange}/>
|
||||
<AcquisitionBeverageRestitution draft={draft} onChange={onChange}/>
|
||||
<FiscalCalculationGaps draft={draft}/>
|
||||
<FiscalL3Preparation draft={draft}/>
|
||||
{book.result&&<><p role="status">{acquisitionCurrent(draft,book)?'Resultado correspondente às entradas guardadas.':'Entradas ou origem alteradas: resultado histórico preservado; actualizar o cálculo.'}</p><dl>{acquisitionReportRows(draft).map(([,label,value,origin],i)=><div key={i}><dt className="font-semibold">{label}</dt><dd>{value}<small className="block">{origin}</small></dd></div>)}</dl></>}
|
||||
</section>;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export function evaluateAcquisitionBC(row:Acquisition,currency:string,registered
|
||||
if(row.allocation==='no_right'||row.integrity==='no'||row.exclusion==='excluded')return stop('Sem direito pela afectação,exclusão expressa ou impedimento18(3–5) declarado.','no_right',0);
|
||||
if(row.allocation==='mixed'&&!partial)return stop('Afectação parcial/pro rata depende deD;sem dedução integral presumida.');
|
||||
if(!partial&&(!row.deductionLink||row.deductionLink.scopeConfirmed!=='yes'||!row.deductionLink.allocationRef.trim()||row.allocation!=='taxable_only'))return stop('Confirme afectação exclusiva e referência à operação ligada.');
|
||||
if(!partial&&bc.expense==='beverages'&&bc.facts.beverageSpecial==='yes'&&['deduction','refund'].includes(bc.beveragePath)&&draft&&row.deductionLink)right=linkedBeverageDeductionRight(draft,row.deductionLink,bc.facts.notCoastalFishing==='yes');
|
||||
if(bc.expense==='beverages'&&bc.facts.beverageSpecial==='yes'&&['deduction','refund'].includes(bc.beveragePath)&&draft&&row.deductionLink)right=linkedBeverageDeductionRight(draft,row.deductionLink,bc.facts.notCoastalFishing==='yes');
|
||||
if(right.status==='no_right')return stop(right.reason,'no_right',0);
|
||||
if(right.status!=='eligible')return stop(right.reason);
|
||||
const noExcludedPayments=partial?.noExcludedPayments??row.deductionLink?.noExcludedPayments;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import type {AcquisitionBook} from './iva-acquisitions.ts';
|
||||
import {beverageParcel} from './iva-beverage-parcels.ts';
|
||||
import {addFractions,decimalFraction,fraction,fractionLabel,subtractFractions,type FiscalFraction} from './fiscal-rational.ts';
|
||||
|
||||
/** Technical representation of disjoint VAT portions, not an additional fiscal condition. */
|
||||
export type BeverageAllocation={from:string;to:string;reference:string;deductionFrom:string;deductionTo:string;deductionReference:string};
|
||||
type Source={parcel:string;origin:string;vat:FiscalFraction};
|
||||
export type BeverageRange={from:FiscalFraction;to:FiscalFraction;amount:FiscalFraction};
|
||||
function boundary(raw:string):FiscalFraction{const parts=/^(\d{1,40})\/(\d{1,40})$/.exec(raw);if(!parts)return decimalFraction(raw);if(BigInt(parts[2])===0n)throw Error('Denominador nulo nos limites da parcela.');return fraction(BigInt(parts[1]),BigInt(parts[2]));}
|
||||
export function beverageRange(source:Source,allocation?:BeverageAllocation):BeverageRange {
|
||||
const from=allocation?boundary(allocation.from):fraction(0n),to=allocation?boundary(allocation.to):source.vat,amount=subtractFractions(to,from);
|
||||
if(BigInt(amount.numerator)<=0n||BigInt(subtractFractions(source.vat,to).numerator)<0n)throw Error('Limites da parcela fora do IVA documental; indique início inferior ao fim e fim até ao total da linha.');
|
||||
if(allocation&&!allocation.reference.trim())throw Error('Identifique o suporte da repartição da linha.');
|
||||
return {from,to,amount};
|
||||
}
|
||||
export function beverageRangesOverlap(a:BeverageRange,b:BeverageRange):boolean{return BigInt(subtractFractions(a.to,b.from).numerator)>0n&&BigInt(subtractFractions(b.to,a.from).numerator)>0n;}
|
||||
export function beverageDeductionEvidence(d:Record<string,unknown>,source:Source):{amount:FiscalFraction|null;reason:string;records:Array<{context:string;revision:number;calculatedAt:string;amount:FiscalFraction;documents:string[]}>} {
|
||||
const store=JSON.parse(String(d.modelo_a_snapshot||'{}')),books=(store.books??{}) as Record<string,AcquisitionBook>,records:Array<{context:string;revision:number;calculatedAt:string;amount:FiscalFraction;documents:string[]}>=[];
|
||||
for(const[context,book]of Object.entries(books))for(const calc of [...(book.history??[]),...(book.result?[book.result]:[])]){
|
||||
const inputs=calc.inputs.filter(row=>row.origin==='effective'&&source.origin==='effective'&&beverageParcel(row)===source.parcel);
|
||||
const amounts:FiscalFraction[]=[];
|
||||
for(const input of inputs){const result=calc.rows.find(r=>r.id===input.id);if(!result||result.deductible===null){if(input.bc?.beveragePath==='deduction')return{amount:null,reason:'Dedução efectiva anterior indeterminada; identificar montante exacto e documento antes da repartição.',records};continue;}
|
||||
const amount=result.deductibleExact??decimalFraction(result.deductible);if(BigInt(amount.numerator)>0n)amounts.push(amount);
|
||||
}
|
||||
if(amounts.length)records.push({context,revision:calc.revision,calculatedAt:calc.calculatedAt,amount:addFractions(amounts),documents:inputs.map(row=>`${row.documentRef};linha${row.documentLine};${row.period}`)});
|
||||
}
|
||||
const unique=[...new Map(records.map(r=>[fractionLabel(r.amount),r.amount])).values()];
|
||||
if(unique.length>1)return{amount:null,reason:'Montantes de dedução efectiva divergentes nas versões recuperadas; reconciliar a sequência e o fundamento das regularizações. Uma correcção preparada não liberta IVA para E.',records};
|
||||
return{amount:unique[0]??fraction(0n),reason:records.length?'Dedução efectiva declarada preservada; a sua afectação à parcela exige suporte expresso.':'Sem dedução efectiva positiva identificada nos livros recuperados; não é consulta à conta corrente AT.',records};
|
||||
}
|
||||
export function reconcileBeverageAllocation(d:Record<string,unknown>,source:Source,allocation?:BeverageAllocation):{range:BeverageRange|null;prior:FiscalFraction|null;reason:string} {
|
||||
const evidence=beverageDeductionEvidence(d,source);if(!evidence.amount)return{range:null,prior:null,reason:evidence.reason};
|
||||
try{
|
||||
const range=beverageRange(source,allocation);
|
||||
if(BigInt(evidence.amount.numerator)>0n){
|
||||
if(!allocation||!allocation.deductionReference.trim())return{range:null,prior:evidence.amount,reason:'Dedução efectiva anterior identificada; indique limites e suporte da parcela já deduzida, sem a apagar ou repetir em restituição.'};
|
||||
const deduction=beverageRange(source,{...allocation,from:allocation.deductionFrom,to:allocation.deductionTo});
|
||||
if(BigInt(subtractFractions(deduction.amount,evidence.amount).numerator)!==0n||beverageRangesOverlap(range,deduction))return{range:null,prior:evidence.amount,reason:'Parcela da dedução não coincide com o montante efectivo anterior ou sobrepõe-se à restituição; benefício duplicado impedido. Se toda a linha foi deduzida, é necessária regularização com fundamento próprio, ainda não automatizada nesta via.'};
|
||||
}else if(allocation&&(allocation.deductionFrom||allocation.deductionTo||allocation.deductionReference))return{range:null,prior:evidence.amount,reason:'Foi indicada dedução anterior sem correspondência nos livros efectivos recuperados; resolver a divergência antes de calcular.'};
|
||||
return{range,prior:evidence.amount,reason:evidence.reason};
|
||||
}catch(error){return{range:null,prior:evidence.amount,reason:String(error instanceof Error?error.message:error)};}
|
||||
}
|
||||
@@ -3,37 +3,41 @@ import {beverageParcel} from './iva-beverage-parcels.ts';
|
||||
import {closureFingerprint} from './iva-closure.ts';
|
||||
import {assessFiscalClaim,newFiscalClaimEvents,fiscalClaimDate,type FiscalClaimEvents,type FiscalClaimState} from './fiscal-claim-events.ts';
|
||||
import {decimalFraction,fractionPresentation,type FiscalFraction} from './fiscal-rational.ts';
|
||||
export const beverageRestitutionSource='L3-E:CIVA13(1)(c/d/h),13(2–3),Lei13/2016 PDF12/p1426(270). Bebidas:dedução ou restituição,sem aplicar20(1)(d);não isenção directa. Provisões de pesca costeira excluídas. Linha documental integral afectada exclusivamente à restituição;sem dedução da mesma parcela. Procedimento/formulário específico não automatizado;sem importar limites/prazos do reembolso geral21.';
|
||||
import {beverageRange,beverageRangesOverlap,beverageDeductionEvidence,reconcileBeverageAllocation,type BeverageAllocation} from './iva-beverage-allocation.ts';
|
||||
export const beverageRestitutionSource='L3-E:CIVA13(1)(c/d/h),13(2–3),Lei13/2016 PDF12/p1426(270). Bebidas:dedução ou restituição,sem aplicar20(1)(d);não isenção directa. Provisões de pesca costeira excluídas. Linha integral ou parcelas documentadas sem sobreposição;dedução efectiva anterior reconciliada por montante e suporte,sem apagar históricos. Procedimento/formulário específico não automatizado;sem importar limites/prazos do reembolso geral21.';
|
||||
export type BeverageRestitutionSource={parcel:string;acquisitionId:string;scope:string;period:string;origin:'projected'|'effective';documentRef:string;documentDate:string;documentLine:string;supplier:string;vat:FiscalFraction;fingerprint:string;operationId:string;operationRef:string;calculatedAt:string};
|
||||
export type BeverageRestitutionInput=FiscalClaimEvents&{id:string;source:BeverageRestitutionSource|null;entityRef:string;exclusive:''|'yes'|'no'};
|
||||
export type BeverageRestitutionInput=FiscalClaimEvents&{id:string;source:BeverageRestitutionSource|null;entityRef:string;exclusive:''|'yes'|'no';allocation?:BeverageAllocation};
|
||||
export type BeverageRestitutionResult=FiscalClaimState&{id:string;inputKey:string;requested:FiscalFraction|null;source:string};
|
||||
type Calc={calculatedAt:string;key:string;inputs:BeverageRestitutionInput[];rows:BeverageRestitutionResult[]};
|
||||
export type BeverageRestitutionBook={version:1;rows:BeverageRestitutionInput[];result?:Calc;history?:Calc[]};
|
||||
const scope=(d:Record<string,unknown>)=>String(d.context_opportunity||'general'),norm=(s:string)=>s.trim().toLocaleLowerCase();
|
||||
function store(d:Record<string,unknown>):Record<string,unknown>{const s=JSON.parse(String(d.modelo_a_snapshot||'{"version":1,"books":{}}'));if(s.version!==1||!s.books)throw Error('Livros não recuperáveis;originais preservados.');return s;}
|
||||
function maps(d:Record<string,unknown>):Record<string,BeverageRestitutionBook>{const m=store(d).ivaBeverageRestitutions;if(m===undefined)return{};if(!m||typeof m!=='object'||Array.isArray(m))throw Error('Mapa de restituições não recuperável;originais preservados.');return m as Record<string,BeverageRestitutionBook>;}
|
||||
function validate(b:BeverageRestitutionBook):void{if(!b||b.version!==1||!Array.isArray(b.rows)||b.history!==undefined&&!Array.isArray(b.history)||b.result!==undefined&&(!Array.isArray(b.result.rows)||!Array.isArray(b.result.inputs)))throw Error('LivroE não recuperável;originais preservados.');for(const r of b.rows)if(!r||!['id','entityRef','requestReference','requestDate','decisionReference','decisionDate','decisionIssuer','approvedAmount'].every(k=>typeof r[k as keyof BeverageRestitutionInput]==='string')||!['','yes','no'].includes(r.exclusive)||!['preparation','submitted'].includes(r.stage)||!['pending','approved','partial','rejected'].includes(r.decision)||!Array.isArray(r.receipts)||r.receipts.some(p=>!p||!['id','reference','date','amount'].every(k=>typeof p[k as keyof typeof p]==='string')))throw Error('EntradasE não recuperáveis;sem apagar documentos.');}
|
||||
function validate(b:BeverageRestitutionBook):void{if(!b||b.version!==1||!Array.isArray(b.rows)||b.history!==undefined&&!Array.isArray(b.history)||b.result!==undefined&&(!Array.isArray(b.result.rows)||!Array.isArray(b.result.inputs)))throw Error('LivroE não recuperável;originais preservados.');for(const r of b.rows)if(!r||!['id','entityRef','requestReference','requestDate','decisionReference','decisionDate','decisionIssuer','approvedAmount'].every(k=>typeof r[k as keyof BeverageRestitutionInput]==='string')||r.allocation!==undefined&&(!r.allocation||!['from','to','reference','deductionFrom','deductionTo','deductionReference'].every(k=>typeof r.allocation![k as keyof BeverageAllocation]==='string'))||!['','yes','no'].includes(r.exclusive)||!['preparation','submitted'].includes(r.stage)||!['pending','approved','partial','rejected'].includes(r.decision)||!Array.isArray(r.receipts)||r.receipts.some(p=>!p||!['id','reference','date','amount'].every(k=>typeof p[k as keyof typeof p]==='string')))throw Error('EntradasE não recuperáveis;sem apagar documentos.');}
|
||||
export function beverageRestitutionBook(d:Record<string,unknown>):BeverageRestitutionBook{const b=maps(d)[scope(d)]??{version:1,rows:[]};validate(b);return b;}
|
||||
export function saveBeverageRestitutionBook(d:Record<string,unknown>,b:BeverageRestitutionBook):string{validate(b);const s=store(d);return JSON.stringify({...s,ivaBeverageRestitutions:{...maps(d),[scope(d)]:b}});}
|
||||
export function beverageRestitutionSources(d:Record<string,unknown>):BeverageRestitutionSource[]{const b=acquisitionBook(d);if(!acquisitionCurrent(d,b))return[];return b.rows.flatMap(row=>{const result=b.result!.rows.find(r=>r.id===row.id);if(row.bc?.expense!=='beverages'||row.bc.beveragePath!=='refund'||!result?.reason.startsWith('Via restituição13(2)')||result.deductible!=='0.00'||!result.vat||!row.deductionLink)return[];const vat=decimalFraction(result.vat);if(BigInt(vat.numerator)<=0n)return[];return[{parcel:beverageParcel(row),acquisitionId:row.id,scope:scope(d),period:row.period,origin:row.origin,documentRef:row.documentRef,documentDate:row.documentDate,documentLine:row.documentLine,supplier:row.supplier,vat,fingerprint:closureFingerprint([row,result]),operationId:row.deductionLink.operationId,operationRef:row.deductionLink.allocationRef,calculatedAt:b.result!.calculatedAt}];});}
|
||||
function deducted(d:Record<string,unknown>,s:BeverageRestitutionSource):boolean{const books=store(d).books as Record<string,ReturnType<typeof acquisitionBook>>;return Object.entries(books).some(([context,b])=>[...(b.history??[]),...(b.result?[b.result]:[])].some(calc=>calc.inputs.some(row=>beverageParcel(row)===s.parcel&&(row.origin==='effective'&&s.origin==='effective'||context===scope(d)&&calc===b.result)&&calc.rows.some(r=>r.id===row.id&&r.deductible!==null&&(r.deductibleExact?BigInt(r.deductibleExact.numerator)>0n:BigInt(decimalFraction(r.deductible).numerator)>0n)))));}
|
||||
export function newBeverageRestitution(id:string,source:BeverageRestitutionSource|null,entityRef=''):BeverageRestitutionInput{return{...newFiscalClaimEvents(),id,source:source?structuredClone(source):null,entityRef,exclusive:''};}
|
||||
function sameSource(d:Record<string,unknown>,r:BeverageRestitutionInput):boolean{return!!r.source&&beverageRestitutionSources(d).some(s=>s.scope===r.source!.scope&&s.parcel===r.source!.parcel&&s.fingerprint===r.source!.fingerprint)&&!deducted(d,r.source);}
|
||||
function sameSource(d:Record<string,unknown>,r:BeverageRestitutionInput):boolean{return!!r.source&&beverageRestitutionSources(d).some(s=>s.scope===r.source!.scope&&s.parcel===r.source!.parcel&&s.fingerprint===r.source!.fingerprint);}
|
||||
function allUses(d:Record<string,unknown>,b:BeverageRestitutionBook):BeverageRestitutionInput[]{return[...b.rows,...Object.entries(maps(d)).filter(([id])=>id!==scope(d)).flatMap(([,v])=>{validate(v);return v.rows.filter(r=>r.source?.origin==='effective');})];}
|
||||
function past(d:Record<string,unknown>,b:BeverageRestitutionBook):BeverageRestitutionInput[]{return Object.entries({...maps(d),[scope(d)]:b}).flatMap(([context,v])=>[...(v.history??[]),...(v.result?[v.result]:[])].flatMap(c=>c.inputs.filter(r=>context===scope(d)||r.source?.origin==='effective')));}
|
||||
function past(d:Record<string,unknown>,b:BeverageRestitutionBook):BeverageRestitutionInput[]{return Object.entries({...maps(d),[scope(d)]:b}).flatMap(([context,v])=>[...(v.history??[]),...(v.result?[v.result]:[])].flatMap(c=>c.inputs.filter(r=>(context===scope(d)||r.source?.origin==='effective')&&c.rows.some(v=>v.id===r.id&&v.status==='determined'&&v.requested&&BigInt(v.requested.numerator)>0n))));}
|
||||
function committedParcel(r:BeverageRestitutionInput):string{if(!r.source)return '';try{const range=beverageRange(r.source,r.allocation);return closureFingerprint([r.source.parcel,r.source.origin,r.source.vat,range.from,range.to]);}catch{return 'invalid';}}
|
||||
const receiptKey=(r:BeverageRestitutionInput,p:FiscalClaimEvents['receipts'][number])=>JSON.stringify([norm(r.entityRef),r.source?.origin,norm(p.reference)]);
|
||||
function key(d:Record<string,unknown>,b:BeverageRestitutionBook):string{return closureFingerprint([b.rows,b.rows.map(r=>sameSource(d,r)),allUses(d,b).map(r=>[r.id,r.source?.parcel,r.receipts.map(p=>receiptKey(r,p))]),past(d,b).filter(r=>r.stage==='submitted'||r.decision!=='pending'||r.receipts.length).map(r=>[r.id,r.source?.parcel,r.receipts.map(p=>receiptKey(r,p))])]);}
|
||||
function key(d:Record<string,unknown>,b:BeverageRestitutionBook):string{return closureFingerprint([b.rows,b.rows.map(r=>[sameSource(d,r),r.source?beverageDeductionEvidence(d,r.source):null]),allUses(d,b).map(r=>[r.id,r.source,r.allocation,r.receipts.map(p=>receiptKey(r,p))]),past(d,b).filter(r=>r.stage==='submitted'||r.decision!=='pending'||r.receipts.length).map(r=>[r.id,r.source,r.allocation,r.receipts.map(p=>receiptKey(r,p))])]);}
|
||||
export function beverageRestitutionCurrent(d:Record<string,unknown>,b:BeverageRestitutionBook):boolean{return!!b.result&&b.result.key===key(d,b)&&b.rows.length===b.result.rows.length&&b.rows.every(r=>b.result!.rows.some(v=>v.id===r.id&&v.inputKey===closureFingerprint(r)));}
|
||||
export function recordBeverageRestitution(d:Record<string,unknown>,b:BeverageRestitutionBook,now=new Date().toISOString()):BeverageRestitutionBook{
|
||||
validate(b);if(new Set(b.rows.map(r=>r.id)).size!==b.rows.length)throw Error('IdentidadeE repetida.');const uses=allUses(d,b),history=past(d,b);
|
||||
const rows=b.rows.map(row=>{const r:BeverageRestitutionResult={id:row.id,inputKey:closureFingerprint(row),status:'indeterminate',reason:'',requested:null,approved:null,received:null,awaitingReceipt:null,notGranted:null,source:beverageRestitutionSource},stop=(reason:string)=>({...r,reason}),s=row.source;
|
||||
if(!s||!sameSource(d,row)||d.currency!=='MZN'||s.scope!==scope(d))return stop('OrigemL1/L2 alterada,ausente ou parcela já deduzida em registo efectivo;sem restituição presumida.');
|
||||
if(!row.entityRef.trim()||row.exclusive!=='yes')return stop('Identifique entidade e confirme a linha integral exclusivamente para restituição;sem dupla utilização.');
|
||||
if(uses.filter(v=>v.source?.origin===s.origin&&v.source?.parcel===s.parcel).length>1||history.some(v=>v.id!==row.id&&v.source?.origin===s.origin&&v.source?.parcel===s.parcel&&(v.stage==='submitted'||v.decision!=='pending'||v.receipts.length)))return stop('Linha documental já ligada a outro pedido de restituição no âmbito recuperado;sem duplicar a parcela.');
|
||||
r.requested=s.vat;if(!fiscalClaimDate(row.requestDate)||row.requestDate<s.documentDate)return stop('Data do pedido/preparação anterior ao documento ou por identificar.');
|
||||
const events=assessFiscalClaim(row,s.vat,p=>uses.flatMap(v=>v.receipts.filter(q=>receiptKey(v,q)===receiptKey(row,p))).length>1||history.some(v=>v.id!==row.id&&v.receipts.some(q=>receiptKey(v,q)===receiptKey(row,p))));Object.assign(r,events);
|
||||
if(!s||!sameSource(d,row)||d.currency!=='MZN'||s.scope!==scope(d))return stop('OrigemL1/L2 alterada ou ausente;sem restituição presumida.');
|
||||
if(!row.entityRef.trim()||row.exclusive!=='yes')return stop('Identifique entidade e confirme a parcela exclusivamente para restituição;sem dupla utilização.');
|
||||
const reconciled=reconcileBeverageAllocation(d,s,row.allocation);if(!reconciled.range)return stop(reconciled.reason);
|
||||
const related=(v:BeverageRestitutionInput)=>v.source?.origin===s.origin&&v.source?.parcel===s.parcel;
|
||||
const overlaps=(v:BeverageRestitutionInput)=>{try{return beverageRangesOverlap(reconciled.range!,beverageRange(v.source!,v.allocation));}catch{return true;}};
|
||||
if(uses.some(v=>v!==row&&related(v)&&overlaps(v))||history.some(v=>related(v)&&(v.stage==='submitted'||v.decision!=='pending'||v.receipts.length)&&(v.id!==row.id?overlaps(v):committedParcel(v)!==committedParcel(row))))return stop('Parcela documental já ligada a pedido ou histórico comprometido;limites sobrepostos ou alterados. Sem duplicar nem libertar IVA por apagar a preparação.');
|
||||
r.requested=reconciled.range.amount;if(!fiscalClaimDate(row.requestDate)||row.requestDate<s.documentDate)return stop('Data do pedido/preparação anterior ao documento ou por identificar.');
|
||||
const events=assessFiscalClaim(row,reconciled.range.amount,p=>uses.flatMap(v=>v.receipts.filter(q=>receiptKey(v,q)===receiptKey(row,p))).length>1||history.some(v=>v.id!==row.id&&v.receipts.some(q=>receiptKey(v,q)===receiptKey(row,p))));Object.assign(r,events);
|
||||
if(r.status==='determined')r.reason+=' Linha13(2) fora da deduçãoL2 e do crédito geralD;pedido não é isenção directa nem concessão automática. Decisão e recebimento registados fora doInvesteMoz;procedimento/formulário específico e prazos não automatizados,sem os importar deD.';return r;
|
||||
});
|
||||
const result={calculatedAt:now,key:'',inputs:structuredClone(b.rows),rows};const next={...b,...(b.result?{history:[...(b.history??[]),structuredClone(b.result)]}:{}),result};result.key=key(d,next);return next;
|
||||
}
|
||||
export function beverageRestitutionReportRows(d:Record<string,unknown>):Array<[string,string,string,string]>{try{const b=beverageRestitutionBook(d);if(!b.rows.length)return[];const out:Array<[string,string,string,string]>=[['IVA mensal','L3-E — restituição de bebidas',beverageRestitutionCurrent(d,b)?'Resultado correspondente às entradas':'Histórico preservado;rever origem/parcela',beverageRestitutionSource]],fmt=(v:FiscalFraction|null)=>v?fractionPresentation(v).decimal:'Indeterminado';for(const r of b.result?.rows??[]){const i=b.result!.inputs.find(v=>v.id===r.id),s=i?.source;out.push(['IVA mensal',`Restituição de bebidas — ${s?.period??''}`,`ParcelaIVA:${fmt(r.requested)};dedução desta parcela:${r.requested?'0pela via exclusivaE':'Indeterminada;ver origem'};reconhecido:${fmt(r.approved)};recebido:${fmt(r.received)};aguarda recebimento:${fmt(r.awaitingReceipt)}`,`${i?.entityRef??''};${scope(d)};${s?.origin==='effective'?'Dados efectivos declarados':'Pressuposto de simulação'};${s?.documentRef??''};${s?.documentDate??''};linha${s?.documentLine??''};operaçãoL1${s?.operationId??''};${s?.operationRef??''};pedido${i?.requestReference??''},${i?.requestDate??''};decisão${i?.decisionReference??''},${i?.decisionDate??''},${i?.decisionIssuer??''};recibos:${i?.receipts.map(p=>`${p.reference};${p.date};${p.amount}MZN`).join(' | ')||'nenhum registado'};${r.reason}`]);}return out;}catch{return[['IVA mensal','L3-E','Restituição não recuperável','Originais preservados;sem dedução/restituição presumidas.']];}}
|
||||
export function beverageRestitutionReportRows(d:Record<string,unknown>):Array<[string,string,string,string]>{try{const b=beverageRestitutionBook(d);if(!b.rows.length)return[];const out:Array<[string,string,string,string]>=[['IVA mensal','L3-E — restituição de bebidas',beverageRestitutionCurrent(d,b)?'Resultado correspondente às entradas':'Histórico preservado;rever origem/parcela',beverageRestitutionSource]],fmt=(v:FiscalFraction|null)=>v?fractionPresentation(v).decimal:'Indeterminado';for(const r of b.result?.rows??[]){const i=b.result!.inputs.find(v=>v.id===r.id),s=i?.source;out.push(['IVA mensal',`Restituição de bebidas — ${s?.period??''}`,`IVA documental:${fmt(s?.vat??null)};parcelaIVA:${fmt(r.requested)};dedução desta parcela:${r.requested?'0pela via exclusivaE':'Indeterminada;ver origem'};reconhecido:${fmt(r.approved)};recebido:${fmt(r.received)};aguarda recebimento:${fmt(r.awaitingReceipt)}`,`${i?.entityRef??''};${scope(d)};${s?.origin==='effective'?'Dados efectivos declarados':'Pressuposto de simulação'};${s?.documentRef??''};${s?.documentDate??''};linha${s?.documentLine??''};operaçãoL1${s?.operationId??''};${s?.operationRef??''};pedido${i?.requestReference??''},${i?.requestDate??''};decisão${i?.decisionReference??''},${i?.decisionDate??''},${i?.decisionIssuer??''};recibos:${i?.receipts.map(p=>`${p.reference};${p.date};${p.amount}MZN`).join(' | ')||'nenhum registado'};${i?.allocation?`repartiçãoIVA[${i.allocation.from},${i.allocation.to}[;suporte${i.allocation.reference};dedução anterior[${i.allocation.deductionFrom},${i.allocation.deductionTo}[;${i.allocation.deductionReference};`:''}${r.reason}`]);}return out;}catch{return[['IVA mensal','L3-E','Restituição não recuperável','Originais preservados;sem dedução/restituição presumidas.']];}}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import {salesBook,salesCurrent} from './iva-operations.ts';
|
||||
import {acquisitionBook,acquisitionCurrent} from './iva-acquisitions.ts';
|
||||
import {travelBook,travelCurrent} from './iva-travel.ts';
|
||||
import {travelEventBook,travelEventCurrent} from './iva-travel-events.ts';
|
||||
import {secondhandBook,secondhandCurrent} from './iva-secondhand.ts';
|
||||
import {currencyEventBook,currencyEventCurrent} from './iva-currency-events.ts';
|
||||
export type FiscalCalculationGap={id:string;book:string;period:string;calculation:string;reason:string;required:string[];resolution:string;kind:'inputs_or_rule'|'implementation'|'stale'|'not_calculated';source:string};
|
||||
type Row=Record<string,unknown>;
|
||||
const fieldLabels:Record<string,string>={net:'Base líquida',vat:'IVA',gross:'Total com IVA',deductible:'IVA dedutível',baseAmount:'Base tributável',deduction:'Dedução',requested:'Pedido',approved:'Reconhecido',received:'Recebido',awaitingReceipt:'Por receber',delta:'Regularização',grossMargin:'Margem bruta',scenarioAmount:'Montante do cenário',outstanding:'Por liquidar'};
|
||||
const financial=new Set(['net','vat','gross','deductible','baseAmount','deduction','requested','approved','received','awaitingReceipt','delta','grossMargin','scenarioAmount','outstanding']);
|
||||
const details:Record<string,{required:string[];resolution:string}>={
|
||||
ivaBeverageRestitutions:{required:['origem e parcela13(2) actuais,sem sobreposição','referência/data do pedido e da decisão externa,quando exista','montante reconhecido e comprovativos dos recebimentos efectivamente ocorridos'],resolution:'Reconciliar a parcela ou completar apenas o acto externo em falta. Sem decisão/recebimento documentado,conservar esses estados pendentes;sem concessão ou caixa presumidas.'},
|
||||
ivaRefunds:{required:['afectação25/26/27 actual','fundamento e origem do crédito','pedido,decisão e comprovativos de recebimento próprios'],resolution:'Resolver a condição concreta do pedido ou registar o acto externo documentado;pedido não equivale a decisão ou recebimento.'},
|
||||
ivaSubstitutions:{required:['declaração anterior congelada e fonte actual','referência,motivo e data da substituição','juros e pagamentos adicionais próprios'],resolution:'Reconciliar anterior e actual,sem repetir créditos/pagamentos;guardar novo diferencial. A correspondência ModeloB/edição recebida permanece separada.'},
|
||||
|
||||
salesOperations:{required:['natureza e localização da operação','preço e indicação de IVA incluído/sem IVA','tratamento, condições e suporte próprios do fornecimento'],resolution:'Resolver o motivo da linha e calcular a operação de venda. O IVA documental da aquisição não é origem do IVA do fornecimento.'},
|
||||
books:{required:['IVA documental e documento legal','direito e afectação da aquisição','cobertura das parcelas do período e vendas/regularizações aplicáveis'],resolution:'Resolver as linhas/campos indicados e guardar novo apuramento; preservar versões e valores indeterminados até completar as parcelas.'},
|
||||
regulatedSecondhands:{required:['categoria, origem e regime do bem','venda, compra ou adjudicação e comissão documentadas','base da margem e regra de liquidação aplicável'],resolution:'A base/margem já demonstrada conserva-se. Implementar separadamente a liquidação e a integração nos totais com a regra e o âmbito próprios; base calculada não significa IVA calculado.'},
|
||||
travelOperations:{required:['operador, período e âmbito do registo global','contraprestação da parte especial e custos directos elegíveis de terceiros','excesso anterior, completude e enquadramento tributável'],resolution:'Completar o registo global e a base; implementar IVA devido, dedução43(d) e integração do período com correspondência própria. A taxa do divisor da base não preenche o IVA devido.'},
|
||||
travelSpecialEvents:{required:['origem da operação e natureza do evento','data e referência do pagamento integral/início ou cessação do regime','base, taxa e direito à dedução no cálculo próprio da operação'],resolution:'Resolver o momento do evento e ligá-lo ao apuramento próprio. Momento determinado ou diferimento não calcula base, IVA ou dedução.'},
|
||||
currencyEvents:{required:['natureza e âmbito do evento','datas de factura, prazo legal e recebimentos aplicáveis','moeda e câmbio documentado quando aplicável','base e regra fiscal da operação'],resolution:'Resolver os factos/câmbio indicados e implementar ou reutilizar o cálculo próprio da operação; o evento isolado não liquida imposto.'},
|
||||
};
|
||||
function record(value:unknown):value is Row{return!!value&&typeof value==='object'&&!Array.isArray(value);}
|
||||
/** Reads the applicable scope only; never recalculates or promotes a frozen result. */
|
||||
export function fiscalCalculationGaps(d:Record<string,unknown>):FiscalCalculationGap[]{
|
||||
const store=JSON.parse(String(d.modelo_a_snapshot||'{"version":1,"books":{}}'));if(store.version!==1||!record(store.books))throw Error('Livros não recuperáveis; conservar o original antes de diagnosticar.');
|
||||
const scope=String(d.context_opportunity||'general'),out:FiscalCalculationGap[]=[];
|
||||
const current:Record<string,()=>boolean>={salesOperations:()=>salesCurrent(d,salesBook(d)),books:()=>acquisitionCurrent(d,acquisitionBook(d)),travelOperations:()=>travelCurrent(d,travelBook(d)),travelSpecialEvents:()=>travelEventCurrent(d,travelEventBook(d)),regulatedSecondhands:()=>secondhandCurrent(d,secondhandBook(d)),currencyEvents:()=>currencyEventCurrent(d,currencyEventBook(d))};
|
||||
for(const[name,map]of Object.entries(store)){
|
||||
if(!record(map)||!(scope in map))continue;if(!record(map[scope]))throw Error(`Livro ${name} não recuperável;original preservado.`);const book=map[scope] as Row;if(book.result!==undefined&&!record(book.result))throw Error(`Resultado ${name} não recuperável;original preservado.`);const result=record(book.result)?book.result:null,inputs=(Array.isArray(book.rows)?book.rows:Array.isArray(book.events)?book.events:[]) as Row[],spec=details[name];
|
||||
if(!result){for(const row of inputs)out.push({id:`${name}:${row.id??out.length}:uncalculated`,book:name,period:String(row.period??''),calculation:String(row.description??row.id??name),reason:'Preparação sem resultado guardado; nenhum valor fiscal foi presumido.',required:spec?.required??['dados e condições específicos da preparação existente'],resolution:spec?.resolution??'Completar a preparação no componente existente e guardar o cálculo correspondente.',kind:'not_calculated',source:''});continue;}
|
||||
let fresh=true;try{fresh=current[name]?current[name]():!(Array.isArray(result.inputs)&&JSON.stringify(result.inputs)!==JSON.stringify(inputs));}catch{fresh=false;}
|
||||
if(!fresh)out.push({id:`${name}:stale`,book:name,period:'',calculation:'Actualidade do resultado',reason:'Entradas ou origem alteradas; resultado histórico conservado.',required:['reconciliação das entradas/origens alteradas'],resolution:'Rever apenas o delta e guardar nova versão antes de usar os valores como actuais.',kind:'stale',source:String(result.source??result.sources??'')});
|
||||
const visit=(value:unknown,path:string)=>{
|
||||
if(Array.isArray(value)){value.forEach((v,i)=>visit(v,`${path}[${i}]`));return;}if(!record(value)||value.status==='not_applicable')return;
|
||||
const missing=Object.entries(value).filter(([k,v])=>v===null&&financial.has(k)).map(([k])=>k);
|
||||
if(record(value.fields))for(const[k,v]of Object.entries(value.fields))if(v===null)missing.push(`campo${k}`);
|
||||
if(value.status==='indeterminate'||missing.length){
|
||||
const frozen=Array.isArray(result.inputs)?result.inputs as Row[]:inputs,input=frozen.find(i=>i.id===value.id),origin=record(input?.source)?input.source:null,prior=record(input?.prior)?input.prior:null,supply=name==='salesOperations'&&String(input?.treatment??'').startsWith('onboard_');
|
||||
const implementation=missing.length>0&&value.status!=='indeterminate'&&['regulatedSecondhands','travelOperations','travelSpecialEvents','currencyEvents'].includes(name);
|
||||
out.push({id:`${name}:${path}`,book:name,period:String(value.period??input?.period??origin?.period??prior?.period??''),calculation:`${String(input?.description??origin?.documentRef??value.id??(value.period?'Apuramento do período':'Apuramento'))}${missing.length?` — ${missing.map(f=>fieldLabels[f]??f.replace('campo','Campo ')).join(', ')}`:''}`,reason:String(value.reason??(Array.isArray(value.reasons)?value.reasons.join('; '):'Parcelas incompletas; consultar o apuramento existente.')),required:supply?['natureza do abastecimento e meio elegível','tratamento e montante liquidado do fornecimento, separados da aquisição','procedimento próprio de13(2), quando aplicável']:spec?.required??['dados, documentos e parâmetros indicados no motivo desta linha'],resolution:supply?'Identificar e implementar o tratamento do fornecimento e respectivo montante liquidado; conservar o IVA da aquisição separado. O acompanhamento E não resolve este cálculo.':spec?.resolution??'Resolver o motivo concreto no percurso de origem e guardar nova versão; campos em falta continuam indeterminados.',kind:implementation||supply?'implementation':'inputs_or_rule',source:String(value.source??result.source??result.sources??'')});
|
||||
}
|
||||
for(const[k,v]of Object.entries(value))if(['rows','periods','events'].includes(k))visit(v,`${path}.${k}`);
|
||||
};visit(result,'result');
|
||||
}
|
||||
return out;
|
||||
}
|
||||
export function fiscalCalculationGapReportRows(d:Record<string,unknown>):Array<[string,string,string,string]>{try{return fiscalCalculationGaps(d).map(g=>['IVA mensal',`Cálculo pendente — ${g.calculation}`,`${g.period};${g.kind};${g.reason};Dados necessários:${g.required.join('; ')};Condição:${g.resolution}`,`${g.book};${g.id};${g.source};sem inferir IVA do fornecimento a partir da aquisição.`]);}catch(error){return[['IVA mensal','Diagnóstico dos cálculos','Livros não recuperáveis; originais preservados',String(error)]];}}
|
||||
@@ -0,0 +1,27 @@
|
||||
import {closureBook,closureCurrent} from './iva-closure.ts';
|
||||
import {substitutionBook,substitutionCurrent} from './iva-substitution.ts';
|
||||
import {refundBook,refundCurrent} from './iva-refund.ts';
|
||||
import {beverageRestitutionBook,beverageRestitutionCurrent} from './iva-beverage-restitution.ts';
|
||||
import {fractionPresentation,fractionLabel,type FiscalFraction} from './fiscal-rational.ts';
|
||||
export const l3FormReference={file:'VAT Return.pdf',sha256:'9c0b99a810c7db53eb75d63c22ee22fb86a50ba57bd5509041ed80ee10f75741',page:2,scope:'Preparação dos campos identificados dos quadros8–11 do exemplar recebido;edição/aceitação electrónica não estabelecida.'};
|
||||
export const l3FormalitiesDependencies=[
|
||||
{branch:'A/B',identified:'Quadros8/10/11 e campos19/20/22–27 do exemplar recebido,PDF2;CIVA21(2/5)/23(1);RCIVA12/15.',missing:'Identificação da edição electrónica aplicável e dados de autenticação/meio de pagamento não inferidos dos recibos.',trigger:'Exemplar/instrução da edição electrónica ou dados do sujeito passivo;nenhuma assinatura ou submissão automática.'},
|
||||
{branch:'C',identified:'Quadro9/campo21 e transcrição positiva para22 do exemplar recebido,PDF2.',missing:'RCIVA15 refereModeloB;exemplar recebido assinala substituiçãoModeloA. Correspondência de edição/formulário regulamentar por resolver.',trigger:'ModeloB/instrução que esclareça a correspondência com a edição recebida. Preservar cálculo e preparação de21–24.'},
|
||||
{branch:'D',identified:'Quadro11:25=26+27;pedido27 não volta a19 sem comunicação. Remissãoart20 impressa conservada como divergência;regime2026 assenta em21.',missing:'Exemplares e instruções regulamentares específicos de reembolso:Decretos78/2017,30/2022 e52/2025 identificados na análise anterior;sem correspondência de campos demonstrada.',trigger:'Texto/modelo/instrução integral novo ou já fornecido;sem repetir a pesquisa anterior sem nova evidência. Preparação26/27 não equivale ao pedido regulamentar completo.'},
|
||||
{branch:'E',identified:'CIVA13(1)c/d/h e13(2–3);pedido,decisão e recebimento separados no dossier.',missing:'Modelo e procedimento específico de restituição de bebidas por identificar. Não usar27 como se fosse a restituição13(2).',trigger:'Exemplar ou instrução específica de13(2);sem importar limiares,prazos ou modelos do reembolso geralD.'},
|
||||
] as const;
|
||||
export type L3FormPreparation={id:string;branch:string;period:string;entity:string;current:boolean;reference:string;status:string;fields:Array<{field:string;label:string;value:string;exact:string}>;notes:string[]};
|
||||
const fmt=(v:FiscalFraction|null|undefined)=>v?fractionPresentation(v).decimal:'Indeterminado';
|
||||
const cell=(field:string,label:string,v:FiscalFraction|null|undefined)=>({field,label,value:fmt(v),exact:v?fractionLabel(v):'Indeterminado'});
|
||||
export function l3FormPreparations(d:Record<string,unknown>):L3FormPreparation[]{
|
||||
const out:L3FormPreparation[]=[],ab=closureBook(d),c=substitutionBook(d),rd=refundBook(d),e=beverageRestitutionBook(d);
|
||||
for(const r of ab.result?.rows??[]){const input=ab.result!.inputs.find(i=>i.id===r.id);out.push({id:`AB:${r.id}`,branch:'A/B',period:r.period,entity:r.entityRef,current:closureCurrent(d,ab),reference:`${l3FormReference.file};SHA256${l3FormReference.sha256};PDF2/quadros8,10,11`,status:r.status,fields:[['19','Excesso do período anterior'],['20','Créditos comunicados'],['22','IVA a entregar'],['23','Juros compensatórios'],['24','Importância a pagar'],['25','Crédito de imposto'],['26','Reporte seguinte'],['27','Pedido de reembolso']].map(([f,label])=>cell(f,label,r.fields[f])),notes:[`Versão congelada${ab.result!.calculatedAt};${input?.origin??''};fonteL2${input?.source?.revision??''}.`,'Cabeçalho refere18/19,mas caixas19/20;original não renumerado. Meio de pagamento,assinatura e uso dos serviços ficam por preencher.','27 é afectação a pedido geral,não decisão nem recebimento;05/06 permanecem fora desta preparaçãoL3.']});}
|
||||
for(const r of c.result?.rows??[]){const input=c.result!.inputs.find(i=>i.id===r.id);out.push({id:`C:${r.id}`,branch:'C',period:input?.prior?.period??'',entity:input?.prior?.entityRef??'',current:substitutionCurrent(d,c),reference:`${l3FormReference.file};PDF2/quadro9;${l3FormalitiesDependencies[1].missing}`,status:r.status,fields:[['21','Diferencial'],['22','Diferencial devedor'],['23','Juros adicionais'],['24','Importância adicional a pagar']].map(([f,label])=>cell(f,label,r.fields[f])),notes:[`Anterior${input?.priorReference??''};${input?.priorDate??''};substituição${input?.reference??''};${input?.date??''};${input?.stage??''}.`,r.reason,'Crédito credor aguarda comunicação20;pagamentos anteriores conservam-se e não voltam a ser inscritos como pagamento adicional.']});}
|
||||
for(const r of rd.result?.rows??[]){const input=rd.result!.inputs.find(i=>i.id===r.id);out.push({id:`D:${r.id}`,branch:'D',period:input?.source?.period??'',entity:input?.source?.entityRef??'',current:refundCurrent(d,rd),reference:`${l3FormReference.file};PDF2/quadro11;${l3FormalitiesDependencies[2].missing}`,status:r.status,fields:[cell('25','Crédito de imposto',input?.source?.credit),cell('26','Reporte separado',r.reported),cell('27','Pedido de reembolso',r.requested)],notes:[`Pedido${input?.requestReference??''};${input?.requestDate??''};${input?.stage??''}.`,r.reason,`Acompanhamento separado:reconhecido${fmt(r.approved)};recebido${fmt(r.received)};por receber${fmt(r.awaitingReceipt)}MZN. Estes valores não substituem27.`]});}
|
||||
for(const r of e.result?.rows??[]){const input=e.result!.inputs.find(i=>i.id===r.id);out.push({id:`E:${r.id}`,branch:'E',period:input?.source?.period??'',entity:input?.entityRef??'',current:beverageRestitutionCurrent(d,e),reference:'Dossier de apoio13(2);modelo oficial específico ainda não identificado.',status:r.status,fields:[cell('Dossier','IVA documental',input?.source?.vat),cell('Dossier','Parcela pedida',r.requested),{field:'Dossier',label:'Dedução desta parcela',value:r.requested?'0.00':'Indeterminado',exact:r.requested?'0/1':'Indeterminado'},cell('Dossier','Reconhecido',r.approved),cell('Dossier','Recebido',r.received),cell('Dossier','Por receber',r.awaitingReceipt)],notes:[`Documento${input?.source?.documentRef??''};linha${input?.source?.documentLine??''};operação${input?.source?.operationId??''};pedido${input?.requestReference??''};${input?.requestDate??''}.`,r.reason,'Sem campo27 nem formulárioD presumidos;IVA do fornecimento mantém cálculo próprio.']});}
|
||||
return out;
|
||||
}
|
||||
export const l3PreparationStatus=(status:string)=>status==='determined'?'Cálculo determinado':status==='indeterminate'?'Indeterminado':status;
|
||||
const escape=(s:string)=>s.replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]!));
|
||||
export function l3FormPreparationHtml(p:L3FormPreparation):string{return`<!doctype html><html lang="pt"><meta charset="utf-8"><title>Preparação L3 ${escape(p.branch)}</title><style>body{font:16px system-ui;max-width:960px;margin:32px auto;padding:0 24px;line-height:1.5}table{border-collapse:collapse;width:100%}th,td{border:1px solid #999;padding:8px;text-align:left}small{display:block}h1{font-size:26px}@media print{body{margin:0}}</style><h1>Preparação L3 ${escape(p.branch)} — ${escape(p.period)}</h1><p>${escape(p.entity)};${escape(l3PreparationStatus(p.status))};${p.current?'Correspondente às entradas':'Histórico preservado;rever entradas/origem'}. Submissão externa;preparação para conferência,não declaração AT submetida.</p><p>${escape(p.reference)}</p><table><thead><tr><th>Campo</th><th>Finalidade</th><th>MZN</th><th>Valor exacto</th></tr></thead><tbody>${p.fields.map(f=>`<tr><td>${escape(f.field)}</td><td>${escape(f.label)}</td><td>${escape(f.value)}</td><td>${escape(f.exact)}</td></tr>`).join('')}</tbody></table>${p.notes.map(n=>`<p>${escape(n)}</p>`).join('')}<h2>Formalidades ainda por completar</h2>${l3FormalitiesDependencies.filter(dep=>dep.branch===p.branch||p.branch==='A/B'&&dep.branch==='D').map(dep=>`<p>${escape(dep.missing)}<small>Condição de retoma:${escape(dep.trigger)}</small></p>`).join('')}<p>Fracções exactas conservadas;decimais são apresentação técnica,sem arredondamento fiscal final ou alteração de histórico.</p></html>`;}
|
||||
export function l3FormPreparationReportRows(d:Record<string,unknown>):Array<[string,string,string,string]>{try{return l3FormPreparations(d).flatMap(p=>[['IVA mensal',`Preparação formal L3${p.branch} — ${p.period}`,`${p.current?'Entradas correspondentes':'Histórico;rever origem'};${l3PreparationStatus(p.status)};${p.fields.map(f=>`${f.field}/${f.label}:${f.value}MZN;exacto${f.exact}`).join(' | ')}`,`${p.reference};${p.notes.join(' | ')}`]as[string,string,string,string]]);}catch(error){return[['IVA mensal','Preparação formalL3','Dados não recuperáveis;originais preservados',String(error)]];}}
|
||||
@@ -1,3 +1,5 @@
|
||||
import {fiscalCalculationGapReportRows} from './iva-calculation-gaps.ts';
|
||||
import {l3FormPreparationReportRows} from './l3-form-preparation.ts';
|
||||
import { remissionReportRows } from './iva-remission-imports.ts';
|
||||
import { digitalReportRows } from './iva-digital.ts';
|
||||
import { objectiveImportReportRows } from './iva-objective-imports.ts';
|
||||
@@ -683,6 +685,8 @@ export function buildProjectDossierRows(
|
||||
rows.push(...travelReportRows(draft));
|
||||
rows.push(...travelEventReportRows(draft));
|
||||
rows.push(...offerPreparationReportRows(draft));
|
||||
rows.push(...fiscalCalculationGapReportRows(draft));
|
||||
rows.push(...l3FormPreparationReportRows(draft));
|
||||
const operationRows = salesReportRows(draft);
|
||||
rows.push(...operationRows);
|
||||
if (draft.monthly_iva_snapshot) {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
namespace Tests\Feature;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Tests\TestCase;
|
||||
final class IvaBeveragePartitionDraftTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
public function test_partition_recovery_preserves_prior_effective_deduction_without_reusing_it_for_restitution(): void
|
||||
{
|
||||
$target=DB::selectOne('select current_database() as name,inet_server_port() as port');
|
||||
$this->assertSame('simulator_fiscal_delta_20261021_test',$target->name);$this->assertSame(55441,(int)$target->port);
|
||||
config(['investor-path.enabled'=>true,'suitecrm.enabled'=>false]);$this->actingAs(User::factory()->create(['email_verified_at'=>now()]));
|
||||
$draft=json_decode(file_get_contents(base_path('outputs/iva-l3-e-partitions-20261025/calculated.json')),true,flags:JSON_THROW_ON_ERROR);
|
||||
$id=$this->postJson('/api/v1/simulation-drafts',['personal_snapshot'=>[],'economic_snapshot'=>$draft],['Idempotency-Key'=>(string)Str::uuid()])->assertCreated()->json('data.id');
|
||||
$recovered=$this->getJson('/api/v1/simulation-drafts/'.$id)->assertOk()->json('data.economic_snapshot');
|
||||
$this->assertSame($draft['modelo_a_snapshot'],$recovered['modelo_a_snapshot']);
|
||||
$s=json_decode($recovered['modelo_a_snapshot'],true,flags:JSON_THROW_ON_ERROR);$book=$s['ivaBeverageRestitutions']['opportunity:375'];
|
||||
$this->assertSame(['numerator'=>'10','denominator'=>'1'],$book['result']['rows'][0]['requested']);
|
||||
$this->assertSame('determined',$book['result']['rows'][0]['status']);
|
||||
$this->assertNull($book['result']['rows'][0]['approved']);$this->assertNull($book['result']['rows'][0]['received']);
|
||||
$allocation=$book['result']['inputs'][0]['allocation'];
|
||||
foreach(['from'=>'6','to'=>'16','deductionFrom'=>'0','deductionTo'=>'6']as$key=>$value)$this->assertSame($value,$allocation[$key]);
|
||||
$this->assertNotEmpty($allocation['reference']);$this->assertNotEmpty($allocation['deductionReference']);
|
||||
$originalDeductions=[];foreach($s['books']['opportunity:375']['history']as$calc)foreach($calc['rows']as$row)if($row['id']==='beverage-purchase')$originalDeductions[]=$row['deductibleExact']??null;
|
||||
$this->assertContains(['numerator'=>'6','denominator'=>'1'],$originalDeductions);
|
||||
foreach(['iva-l3e-preserved','iva-l3ab-preserved']as$fixture){$accepted=json_decode(file_get_contents(base_path('tests/frontend/fixtures/'.$fixture.'.json')),true,flags:JSON_THROW_ON_ERROR);foreach(json_decode($accepted['modelo_a_snapshot'],true,flags:JSON_THROW_ON_ERROR)as$key=>$map){if($key==='version')continue;foreach($map as$context=>$original)$this->assertSame($original,$s[$key][$context]);}}
|
||||
$this->assertSame(['numerator'=>'72','denominator'=>'1'],$s['ivaClosures']['opportunity:373']['result']['rows'][2]['fields']['24']);
|
||||
$this->assertSame(['numerator'=>'450000','denominator'=>'1'],$s['ivaRefunds']['opportunity:374']['result']['rows'][0]['received']);
|
||||
file_put_contents(base_path('outputs/iva-l3-e-partitions-20261025/recovered.json'),json_encode($recovered,JSON_THROW_ON_ERROR|JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import fs from 'node:fs';
|
||||
import {newSalesOperation,recordSales,saveSalesBook,operationTreatments,salesBook} from '../../../resources/js/lib/iva-operations.ts';
|
||||
import {newAcquisition,recordAcquisitions,saveAcquisitionBook,acquisitionBook,acquisitionCurrent} from '../../../resources/js/lib/iva-acquisitions.ts';
|
||||
import {createAcquisitionRightLink} from '../../../resources/js/lib/iva-acquisition-right.ts';
|
||||
import {newAcquisitionBC} from '../../../resources/js/lib/iva-acquisition-bc.ts';
|
||||
import {newBeverageRestitution,beverageRestitutionSources,recordBeverageRestitution,saveBeverageRestitutionBook,beverageRestitutionBook,beverageRestitutionCurrent,beverageRestitutionReportRows} from '../../../resources/js/lib/iva-beverage-restitution.ts';
|
||||
const seed=JSON.parse(fs.readFileSync(new URL('./iva-l3e-preserved.json',import.meta.url),'utf8')),older=JSON.parse(fs.readFileSync(new URL('./iva-l3ab-preserved.json',import.meta.url),'utf8'));
|
||||
export function fixture(treatment='onboard_seagoing_ship'){const d=structuredClone(seed),s=JSON.parse(d.modelo_a_snapshot),old=JSON.parse(older.modelo_a_snapshot);for(const[k,v]of Object.entries(old))if(k!=='version')s[k]={...v,...s[k]};d.modelo_a_snapshot=JSON.stringify(s);d.context_opportunity='opportunity:375';const rule=operationTreatments.find(t=>t.id===treatment);const sale={...newSalesOperation('beverage-supply','2026-12','MZN'),description:'SYNTHETIC QA bebidas efectivamente postas a bordo',establishment:'Manica',amount:'100',basis:'net',treatment,supplyCategory:'beverages',confirmed:rule.conditions.map((_,i)=>i),evidence:'SYNTHETIC QA embarcação/avião,actividade,consumo e embarque'};d.modelo_a_snapshot=saveSalesBook(d,recordSales(d,{currency:'MZN',rows:[sale],periods:[{period:'2026-12',complete:true}]}));
|
||||
const row={...newAcquisition('beverage-purchase','2026-12','MZN'),net:'100',vat:'16',gross:'116',description:'SYNTHETIC QA parcela bebidas',documentType:'invoice',documentRef:'SYNTHETIC QA facturaBE1',documentDate:'2026-12-10',documentLine:'1-bebidas',supplier:'SYNTHETIC QA fornecedor',supplierRegistered:'yes',legalDocument:'yes',held:'yes',domestic:'yes',allocation:'taxable_only',exclusion:'none',integrity:'yes',deductionLink:{...createAcquisitionRightLink(d,'beverage-supply'),scopeConfirmed:'yes',allocationRef:'SYNTHETIC QA linha1 exclusivamente para abastecimento identificado',noExcludedPayments:'yes'},bc:{...newAcquisitionBC(),expense:'beverages',beveragePath:'refund',reducedSupply:'no',evidence:'SYNTHETIC QA natureza bebidas,não tabaco/recepção',facts:{beverageSpecial:'yes',notCoastalFishing:'yes'}}};
|
||||
const acq=recordAcquisitions(d,{currency:'MZN',rightVersion:'acquisition-operation-right/1',taxpayerRegistered:'yes',rows:[row],periods:[{period:'2026-12',complete:true,importsComplete:true,absent:['12','13']}]});d.modelo_a_snapshot=saveAcquisitionBook(d,acq);
|
||||
const claim={...newBeverageRestitution('beverage-claim',beverageRestitutionSources(d)[0]??null,'SYNTHETIC QA entidade375'),exclusive:'yes',stage:'submitted',requestReference:'SYNTHETIC QA pedidoBE1',requestDate:'2026-12-15',decision:'partial',decisionReference:'SYNTHETIC QA decisãoBE1',decisionDate:'2026-12-20',decisionIssuer:'SYNTHETIC QA entidade emitente declarada',approvedAmount:'12',receipts:[{id:'rec1',reference:'SYNTHETIC QA recebimentoBE1',date:'2026-12-23',amount:'7'}]};return{d,b:{version:1,rows:[claim]},acq};}
|
||||
@@ -0,0 +1,24 @@
|
||||
import test from 'node:test';import assert from 'node:assert/strict';
|
||||
import {fixture} from './fixtures/iva-beverage-fixture.mjs';
|
||||
import {recordBeverageRestitution,saveBeverageRestitutionBook,beverageRestitutionBook,beverageRestitutionSources,beverageRestitutionCurrent,beverageRestitutionReportRows} from '../../resources/js/lib/iva-beverage-restitution.ts';
|
||||
import {beverageDeductionEvidence} from '../../resources/js/lib/iva-beverage-allocation.ts';
|
||||
import {acquisitionBook,recordAcquisitions,saveAcquisitionBook} from '../../resources/js/lib/iva-acquisitions.ts';
|
||||
import {newAcquisitionAllocation} from '../../resources/js/lib/iva-acquisition-allocation.ts';
|
||||
import {fiscalCalculationGaps} from '../../resources/js/lib/iva-calculation-gaps.ts';
|
||||
import {l3FormPreparations,l3FormPreparationHtml,l3FormalitiesDependencies} from '../../resources/js/lib/l3-form-preparation.ts';
|
||||
const n=v=>v&&`${v.numerator}/${v.denominator}`;
|
||||
const allocation=(from,to,patch={})=>({from,to,reference:'SYNTHETIC QA repartição documento',deductionFrom:'',deductionTo:'',deductionReference:'',...patch});
|
||||
function pending(row,id,from,to){return{...structuredClone(row),id,decision:'pending',decisionReference:'',decisionDate:'',decisionIssuer:'',approvedAmount:'',receipts:[],allocation:allocation(from,to)};}
|
||||
function partialPrior(numerator='3',denominator='8',expected='6/1'){const f=fixture(),a=acquisitionBook(f.d);a.rows[0].origin='effective';a.rows[0].allocation='mixed';a.rows[0].bc.beveragePath='deduction';a.rows[0].bc.facts.onlyDeduction='yes';a.rows[0].d={...newAcquisitionAllocation('2026-12'),method:'actual',numerator,denominator,unit:'unidades físicas documentadas',scopeRef:'SYNTHETIC QA parte com direito',source:'SYNTHETIC QA critério e registos',communicationRef:'SYNTHETIC QA comunicação22(2)',noExcludedPayments:'yes',facts:{scopeConfirmed:'yes',rightBasis:'yes',actualScope:'yes',communication:'yes',conditionsMet:'yes'}};let actual=recordAcquisitions(f.d,a);assert.equal(n(actual.result.rows[0].deductibleExact),expected,actual.result.rows[0].reason);f.d.modelo_a_snapshot=saveAcquisitionBook(f.d,actual);actual.rows[0].bc.beveragePath='refund';actual.rows[0].allocation='taxable_only';delete actual.rows[0].d;actual=recordAcquisitions(f.d,actual);f.d.modelo_a_snapshot=saveAcquisitionBook(f.d,actual);f.b.rows[0].source=beverageRestitutionSources(f.d)[0];return f;}
|
||||
test('disjoint portions6+10 share one documentary VAT16 without overlap;save/recovery/report preserve prior maps',()=>{const{d,b}=fixture(),before=JSON.parse(d.modelo_a_snapshot);b.rows=[pending(b.rows[0],'portion6','0','6'),pending(b.rows[0],'portion10','6','16')];const book=recordBeverageRestitution(d,b);assert.deepEqual(book.result.rows.map(r=>[r.status,n(r.requested)]),[['determined','6/1'],['determined','10/1']]);d.modelo_a_snapshot=saveBeverageRestitutionBook(d,book);assert.deepEqual(beverageRestitutionBook(d),book);for(const[k,v]of Object.entries(before))assert.deepEqual(JSON.parse(d.modelo_a_snapshot)[k],v,k);assert.ok(beverageRestitutionCurrent(d,book));assert.match(beverageRestitutionReportRows(d).flat().join(' '),/IVA documental:16.00;parcelaIVA:6.00/);});
|
||||
test('overlap,negative/zero/outside bounds,missing support and duplicate legacy whole line are rejected',()=>{const{d,b}=fixture();for(const pair of [['0','0'],['-1','6'],['6','5'],['0','16.000001']]){const book={...b,rows:[pending(b.rows[0],'bad',...pair)]};assert.equal(recordBeverageRestitution(d,book).result.rows[0].requested,null);}let book={...b,rows:[pending(b.rows[0],'p1','0','10'),pending(b.rows[0],'p2','9','16')]};assert.ok(recordBeverageRestitution(d,book).result.rows.every(r=>!r.requested));book.rows=[pending(b.rows[0],'p1','0','6'),structuredClone(b.rows[0])];assert.ok(recordBeverageRestitution(d,book).result.rows.every(r=>!r.requested));book.rows=[pending(b.rows[0],'p1','0','6')];book.rows[0].allocation.reference='';assert.equal(recordBeverageRestitution(d,book).result.rows[0].requested,null);});
|
||||
test('effective deduction6 must be explicitly reconciled to its own interval;E10 uses only the disjoint remainder',()=>{const{d,b}=partialPrior();assert.equal(n(beverageDeductionEvidence(d,b.rows[0].source).amount),'6/1');b.rows=[pending(b.rows[0],'remainder','6','16')];assert.equal(recordBeverageRestitution(d,b).result.rows[0].requested,null);Object.assign(b.rows[0].allocation,{deductionFrom:'0',deductionTo:'6',deductionReference:'SYNTHETIC QA correspondência da dedução original'});let book=recordBeverageRestitution(d,b);assert.equal(n(book.result.rows[0].requested),'10/1');assert.equal(book.result.rows[0].status,'determined');assert.ok(acquisitionBook(d).history.some(c=>c.rows.some(r=>n(r.deductibleExact)==='6/1')));for(const patch of [{deductionTo:'5'},{deductionFrom:'4',deductionTo:'10'},{deductionReference:''}]){const changed=structuredClone(b);Object.assign(changed.rows[0].allocation,patch);assert.equal(recordBeverageRestitution(d,changed).result.rows[0].requested,null);}});
|
||||
test('a rounded tiny deduction,divergent historical amounts and editing away a committed portion never releaseVAT',()=>{const{d,b}=partialPrior(),a=acquisitionBook(d),old=structuredClone(a.history.find(c=>c.rows.some(r=>n(r.deductibleExact)==='6/1')));old.rows.find(r=>r.id==='beverage-purchase').deductibleExact={numerator:'1',denominator:'100000000'};old.rows.find(r=>r.id==='beverage-purchase').deductible='0.00';a.history.push(old);d.modelo_a_snapshot=saveAcquisitionBook(d,a);b.rows=[pending(b.rows[0],'x','0','16')];assert.equal(recordBeverageRestitution(d,b).result.rows[0].requested,null);assert.match(beverageDeductionEvidence(d,b.rows[0].source).reason,/divergentes/);const f=fixture();f.b.rows=[pending(f.b.rows[0],'committed','0','6')];let book=recordBeverageRestitution(f.d,f.b);f.d.modelo_a_snapshot=saveBeverageRestitutionBook(f.d,book);book=recordBeverageRestitution(f.d,{...book,rows:[]});f.d.modelo_a_snapshot=saveBeverageRestitutionBook(f.d,book);const overlap=pending(f.b.rows[0],'new','5','16');assert.equal(recordBeverageRestitution(f.d,{...book,rows:[overlap]}).result.rows[0].requested,null);const edited=pending(f.b.rows[0],'committed','0','7');assert.equal(recordBeverageRestitution(f.d,{...book,rows:[edited]}).result.rows[0].requested,null);});
|
||||
test('gap inventory identifies supplyVAT independently from purchase16 and includes implementation limits of margin/travel/events',()=>{const{d}=fixture(),gaps=fiscalCalculationGaps(d);const supply=gaps.find(g=>g.book==='salesOperations'&&g.reason.includes('Bebidas em13'));assert.ok(supply);assert.match(supply.resolution,/IVA da aquisição separado/);const store=JSON.parse(d.modelo_a_snapshot);store.regulatedSecondhands={...store.regulatedSecondhands,[d.context_opportunity]:{rows:[{id:'m',period:'2026-12'}],result:{inputs:[{id:'m',period:'2026-12'}],rows:[{id:'m',status:'determined',baseAmount:'100',vat:null,reason:'Base da margem calculada;IVA não calculado.'}]}}};d.modelo_a_snapshot=JSON.stringify(store);const margin=fiscalCalculationGaps(d).find(g=>g.book==='regulatedSecondhands'&&g.kind==='implementation');assert.ok(margin);assert.match(margin.resolution,/liquidação/);assert.ok(margin.required.length);assert.ok(fiscalCalculationGaps({...d,context_opportunity:'other-context'}).length===0);});
|
||||
test('formal preparation maps only identified L3 fields;E keeps16/0/12/7/5 outside27;HTML escapes documents and lists specific dependencies',()=>{const{d,b}=fixture();b.rows[0].entityRef='<script>alert(1)</script>';d.modelo_a_snapshot=saveBeverageRestitutionBook(d,recordBeverageRestitution(d,b));const forms=l3FormPreparations(d),e=forms.find(p=>p.branch==='E');assert.deepEqual(e.fields.map(f=>f.value),['16.00','16.00','0.00','12.00','7.00','5.00']);assert.ok(e.fields.every(f=>f.field==='Dossier'));const html=l3FormPreparationHtml(e);assert.ok(!html.includes('<script>'));assert.ok(html.includes('<script>'));assert.match(html,/modelo oficial específico ainda não identificado/);assert.match(html,/Condição de retoma/);assert.equal(l3FormalitiesDependencies.length,4);assert.ok(forms.filter(p=>p.branch==='A/B').every(p=>p.fields.every(f=>!['05','06'].includes(f.field))));});
|
||||
test('export reconciled6deducted/10requested plus gap inventory and preparations for isolated persistence and realUI',async()=>{const{d,b}=partialPrior();b.rows=[pending(b.rows[0],'remainder','6','16')];Object.assign(b.rows[0].allocation,{deductionFrom:'0',deductionTo:'6',deductionReference:'SYNTHETIC QA correspondência da dedução original'});let book=recordBeverageRestitution(d,b);book=recordBeverageRestitution(d,book);d.modelo_a_snapshot=saveBeverageRestitutionBook(d,book);assert.equal(n(book.result.rows[0].requested),'10/1');const fs=await import('node:fs');if(process.env.IVA_E_PARTITION_OUTPUT){fs.writeFileSync(process.env.IVA_E_PARTITION_OUTPUT,JSON.stringify(d));fs.writeFileSync(process.env.IVA_E_PARTITION_OUTPUT.replace('calculated.json','gaps.json'),JSON.stringify(fiscalCalculationGaps(d),null,2));fs.writeFileSync(process.env.IVA_E_PARTITION_OUTPUT.replace('calculated.json','preparations.json'),JSON.stringify(l3FormPreparations(d),null,2));}});
|
||||
test('a failed recalculation does not commit its rejected boundaries;correcting support or decimal presentation keeps the accepted reservation',()=>{const{d,b}=fixture();b.rows=[pending(b.rows[0],'stable','0','6')];let book=recordBeverageRestitution(d,b);d.modelo_a_snapshot=saveBeverageRestitutionBook(d,book);const bad=structuredClone(book);bad.rows[0].allocation.to='17';book=recordBeverageRestitution(d,bad);assert.equal(book.result.rows[0].requested,null);d.modelo_a_snapshot=saveBeverageRestitutionBook(d,book);book.rows[0].allocation.to='6.0';book.rows[0].allocation.reference='Updated documented support';book=recordBeverageRestitution(d,book);assert.equal(n(book.result.rows[0].requested),'6/1');assert.equal(book.result.rows[0].status,'determined');});
|
||||
test('malformed applicable books are visible errors rather than an empty list;frozen gap labels do not adopt deliberate new edits',()=>{const{d}=fixture(),store=JSON.parse(d.modelo_a_snapshot);store.salesOperations[d.context_opportunity].rows[0].description='Deliberate edited description';d.modelo_a_snapshot=JSON.stringify(store);const gaps=fiscalCalculationGaps(d);assert.ok(gaps.some(g=>g.book==='salesOperations'&&g.kind==='stale'));assert.ok(gaps.filter(g=>g.book==='salesOperations'&&g.kind!=='stale').every(g=>!g.calculation.includes('Deliberate edited description')));store.books[d.context_opportunity]='malformed';assert.throws(()=>fiscalCalculationGaps({...d,modelo_a_snapshot:JSON.stringify(store)}),/não recuperável/);});
|
||||
test('identified AB C D fields use their actual frozen books;general request600000 never becomes approval550000 or receipt450000',()=>{const{d}=fixture();const forms=l3FormPreparations({...d,context_opportunity:'opportunity:373'}),march=forms.find(p=>p.branch==='A/B'&&p.period==='2026-03'),sub=forms.find(p=>p.branch==='C');assert.ok(march);assert.equal(march.fields.find(f=>f.field==='24').value,'72.00');assert.ok(sub);assert.equal(sub.fields.find(f=>f.field==='24').value,'61.00');const refund=l3FormPreparations({...d,context_opportunity:'opportunity:374'}).find(p=>p.branch==='D');assert.ok(refund);assert.deepEqual(refund.fields.map(f=>[f.field,f.value]),[['25','800000.00'],['26','200000.00'],['27','600000.00']]);assert.match(refund.notes.join(' '),/reconhecido550000.00;recebido450000.00;por receber100000.00/);assert.ok(march.fields.every(f=>!['05','06'].includes(f.field)));assert.match(l3FormPreparationHtml(sub),/ModeloB/);const snapshot=JSON.parse(d.modelo_a_snapshot);snapshot.ivaClosures['opportunity:373'].rows.find(r=>r.period==='2026-03').entityRef='Deliberate edited entity';const retained=l3FormPreparations({...d,context_opportunity:'opportunity:373',modelo_a_snapshot:JSON.stringify(snapshot)}).find(p=>p.branch==='A/B'&&p.period==='2026-03');assert.equal(retained.current,false);assert.notEqual(retained.entity,'Deliberate edited entity');assert.equal(retained.fields.find(f=>f.field==='24').value,'72.00');});
|
||||
|
||||
test('non-terminating effective deduction16/3 is reconciled exactly and leaves32/3 forE without decimal approximation or excess',()=>{const{d,b}=partialPrior('1','3','16/3');b.rows=[pending(b.rows[0],'exact-remainder','16/3','16')];Object.assign(b.rows[0].allocation,{deductionFrom:'0',deductionTo:'16/3',deductionReference:'SYNTHETIC QA exact allocation'});let book=recordBeverageRestitution(d,b);assert.equal(book.result.rows[0].status,'determined',book.result.rows[0].reason);assert.equal(n(book.result.rows[0].requested),'32/3');const changed=structuredClone(b);changed.rows[0].allocation.from='5.333333';assert.equal(recordBeverageRestitution(d,changed).result.rows[0].requested,null);changed.rows[0].allocation.from='1/0';assert.equal(recordBeverageRestitution(d,changed).result.rows[0].requested,null);});
|
||||
@@ -4,11 +4,7 @@ import {newAcquisition,recordAcquisitions,saveAcquisitionBook,acquisitionBook,ac
|
||||
import {createAcquisitionRightLink} from '../../resources/js/lib/iva-acquisition-right.ts';
|
||||
import {newAcquisitionBC} from '../../resources/js/lib/iva-acquisition-bc.ts';
|
||||
import {newBeverageRestitution,beverageRestitutionSources,recordBeverageRestitution,saveBeverageRestitutionBook,beverageRestitutionBook,beverageRestitutionCurrent,beverageRestitutionReportRows} from '../../resources/js/lib/iva-beverage-restitution.ts';
|
||||
const seed=JSON.parse(fs.readFileSync(new URL('./fixtures/iva-l3e-preserved.json',import.meta.url),'utf8')),older=JSON.parse(fs.readFileSync(new URL('./fixtures/iva-l3ab-preserved.json',import.meta.url),'utf8'));
|
||||
function fixture(treatment='onboard_seagoing_ship'){const d=structuredClone(seed),s=JSON.parse(d.modelo_a_snapshot),old=JSON.parse(older.modelo_a_snapshot);for(const[k,v]of Object.entries(old))if(k!=='version')s[k]={...v,...s[k]};d.modelo_a_snapshot=JSON.stringify(s);d.context_opportunity='opportunity:375';const rule=operationTreatments.find(t=>t.id===treatment);const sale={...newSalesOperation('beverage-supply','2026-12','MZN'),description:'SYNTHETIC QA bebidas efectivamente postas a bordo',establishment:'Manica',amount:'100',basis:'net',treatment,supplyCategory:'beverages',confirmed:rule.conditions.map((_,i)=>i),evidence:'SYNTHETIC QA embarcação/avião,actividade,consumo e embarque'};d.modelo_a_snapshot=saveSalesBook(d,recordSales(d,{currency:'MZN',rows:[sale],periods:[{period:'2026-12',complete:true}]}));
|
||||
const row={...newAcquisition('beverage-purchase','2026-12','MZN'),net:'100',vat:'16',gross:'116',description:'SYNTHETIC QA parcela bebidas',documentType:'invoice',documentRef:'SYNTHETIC QA facturaBE1',documentDate:'2026-12-10',documentLine:'1-bebidas',supplier:'SYNTHETIC QA fornecedor',supplierRegistered:'yes',legalDocument:'yes',held:'yes',domestic:'yes',allocation:'taxable_only',exclusion:'none',integrity:'yes',deductionLink:{...createAcquisitionRightLink(d,'beverage-supply'),scopeConfirmed:'yes',allocationRef:'SYNTHETIC QA linha1 exclusivamente para abastecimento identificado',noExcludedPayments:'yes'},bc:{...newAcquisitionBC(),expense:'beverages',beveragePath:'refund',reducedSupply:'no',evidence:'SYNTHETIC QA natureza bebidas,não tabaco/recepção',facts:{beverageSpecial:'yes',notCoastalFishing:'yes'}}};
|
||||
const acq=recordAcquisitions(d,{currency:'MZN',rightVersion:'acquisition-operation-right/1',taxpayerRegistered:'yes',rows:[row],periods:[{period:'2026-12',complete:true,importsComplete:true,absent:['12','13']}]});d.modelo_a_snapshot=saveAcquisitionBook(d,acq);
|
||||
const claim={...newBeverageRestitution('beverage-claim',beverageRestitutionSources(d)[0]??null,'SYNTHETIC QA entidade375'),exclusive:'yes',stage:'submitted',requestReference:'SYNTHETIC QA pedidoBE1',requestDate:'2026-12-15',decision:'partial',decisionReference:'SYNTHETIC QA decisãoBE1',decisionDate:'2026-12-20',decisionIssuer:'SYNTHETIC QA entidade emitente declarada',approvedAmount:'12',receipts:[{id:'rec1',reference:'SYNTHETIC QA recebimentoBE1',date:'2026-12-23',amount:'7'}]};return{d,b:{version:1,rows:[claim]},acq};}
|
||||
import {fixture} from './fixtures/iva-beverage-fixture.mjs';
|
||||
const n=v=>v&&`${v.numerator}/${v.denominator}`;
|
||||
test('E reuses classified supply and invoice;VAT16 is not deducted,12recognised/7received/5waiting remain distinct',()=>{const{d,b,acq}=fixture();assert.equal(acq.result.rows[0].deductible,'0.00');assert.ok(b.rows[0].source);const r=recordBeverageRestitution(d,b).result.rows[0];assert.equal(r.status,'determined',r.reason);assert.equal(n(r.requested),'16/1');assert.equal(n(r.approved),'12/1');assert.equal(n(r.received),'7/1');assert.equal(n(r.awaitingReceipt),'5/1');assert.equal(n(r.notGranted),'4/1');assert.equal(b.rows[0].source.operationId,'beverage-supply');assert.equal(b.rows[0].source.documentLine,'1-bebidas');});
|
||||
test('c/d/h retain their distinct conditions;warships,coastal fishing,ordinary beverages and changedL1 are not inherited',()=>{for(const t of ['onboard_seagoing_ship','onboard_rescue_coastal_ship','onboard_international_aircraft'])assert.ok(fixture(t).b.rows[0].source,t);assert.equal(fixture('onboard_departing_warship').b.rows[0].source,null);const{d}=fixture('onboard_rescue_coastal_ship'),a=acquisitionBook(d);a.rows[0].bc.facts.notCoastalFishing='no';d.modelo_a_snapshot=saveAcquisitionBook(d,recordAcquisitions(d,a));assert.deepEqual(beverageRestitutionSources(d),[]);const f=fixture();const ops=salesBook(f.d);ops.rows[0].evidence='changed';f.d.modelo_a_snapshot=saveSalesBook(f.d,recordSales(f.d,ops));assert.equal(recordBeverageRestitution(f.d,f.b).result.rows[0].requested,null);});
|
||||
|
||||
Reference in New Issue
Block a user