Prepare grouped L2 A B C with customs deduction exclusions and exact affected totals

This commit is contained in:
2026-10-25 12:29:10 +02:00
parent 56c948ec45
commit fc7e590d4e
14 changed files with 511 additions and 22 deletions
@@ -25155,5 +25155,52 @@
"F": "Repetir sódelta dos totais afectados em cada incremento."
},
"active_writes": false
},
"l2_bc_acquisition_qa_20261025": {
"state": "L2-B/C concluídos em QA no âmbito demonstrado;preparação de entrega conjunta comA,sem instalação",
"report": "docs/investmoz/simulations/l2-bc-imports-exclusions-qa-20261025.json",
"group_manifest": "docs/investmoz/simulations/l2-abc-prepared-delivery-20261025.json",
"L1": {
"state": "operacional com limitações explícitas",
"delivered": 129,
"functional_pending": [
"P058",
"P084",
"P085"
],
"documentary_pending": "P001 — exclusivamente05/06",
"no_reopening": true
},
"F": {
"included_each_increment": true,
"example_MZN": {
"09": "24.00",
"11": "16.00",
"14": "1000.00",
"15": "40.00",
"16": "160.00",
"17": "120.00",
"18": "0.00"
},
"unknown_import": "11/15/17/18 indeterminados;semzero presumido.",
"exact_diesel_test": "Duas parcelas deIVA0.000001×50%=1/2000000 cada,total exacto1/1000000;sem dupla aproximação.",
"limit": "17/18 antes de créditos anteriores;19–27,restituição/pagamentoL3 fora deste incremento."
},
"validation": {
"frontend": "21testes dirigidos:16anteriores deA/compras/continuidade e5novosB/C,com múltiplos cenários por família",
"api": "1teste/80asserções,guardcurrent_database=simulator_fiscal_delta_20261021_test/port55441,DatabaseTransactions rollback;A e todos os mapas anteriores preservados integralmente",
"types": "npm.cmd run types:check passou",
"build": "npm.cmd run build passou após encerrar servidoresQA temporários e reduzir concorrência de memória",
"ui": "Componente real:origemL1 reutilizada sem sobrescrita;IVA desconhecido→11/totais indeterminados;reposição de montante identificado→calcular→gravar→recuperar→relatório dos totais",
"ui_proof": "outputs/iva-l2-bc-20261025/ui-recovered-totals.png",
"resource_incident": "Última repetição deAPI/build encontrou falta de memória do Windows;servidores de demonstraçãoA/B/C encerrados;API,tipos e build repetidos sequencialmente. Não alterou activo ou limites de memória/configuração do sistema."
},
"remaining": {
"D": "Afectação mista/pro rata e proporções específicas18(6) não implementadas neste conjunto. Gasóleo50% é regraC,sem fecharD.",
"E": "Activos/regularizações por evento e resultado original ainda seguem dependências próprias.",
"L3": "Bebidas por restituição,créditos anteriores,saldo final e formulários continuam no âmbitoL3.",
"L1": "129fechos e quatro residuais preservados;Estatísticas/IA adiadas."
},
"active_writes": false
}
}
@@ -0,0 +1,62 @@
{
"status": "prepared_not_installed",
"base_installed": "ada9629bb65ba186f54d970f0e41194888ebf53b",
"base_receipt": "7febb6f7",
"accepted_A_candidate": "56c948ec",
"coverage_complete_in_demonstrated_scope": [
"L2-A",
"L2-B",
"L2-C"
],
"F": "Totais afectados incluídos emA eB/C;provas individuais reutilizadas.",
"reports": [
"docs/investmoz/simulations/l2-a-acquisition-right-qa-20261025.json",
"docs/investmoz/simulations/l2-bc-imports-exclusions-qa-20261025.json"
],
"runtime_delta": [
{
"path": "resources/js/lib/iva-acquisitions.ts",
"sha256_LF": "5c75d86e3cffda929fffc56a2613cebf26031c0c0bd466fab33887354ffb8281"
},
{
"path": "resources/js/lib/iva-acquisition-right.ts",
"sha256_LF": "1df109ef6eea9e370fa3ecb33993296e260cfb77b6adc8e4f414e5bda655c895"
},
{
"path": "resources/js/lib/iva-acquisition-bc.ts",
"sha256_LF": "14035966df3c680df6ac1b03f2cef8083fa5f67533deb9d98905f51c0bc1110d"
},
{
"path": "resources/js/components/simulation-acquisitions.tsx",
"sha256_LF": "efdc16a84cc64f47b2c587651f966f9dcfd57f1fe534645725629973ce92e12e"
},
{
"path": "resources/js/components/acquisition-operation-link.tsx",
"sha256_LF": "e6894e4986a2293b0d2cd9743074f045dc8e5c50610cb9d09a625108b4561144"
},
{
"path": "resources/js/components/acquisition-imports-exclusions.tsx",
"sha256_LF": "8f84ffeeafc7afeefcf64cd99df7bf84c16a9cc92432e215f7b48feb24d5b7d1"
}
],
"preservation": {
"state": "operacional com limitações explícitas",
"delivered": 129,
"functional_pending": [
"P058",
"P084",
"P085"
],
"documentary_pending": "P001 — exclusivamente05/06",
"no_reopening": true
},
"excluded": {
"D": "Afectação mista/pro rata e proporções específicas18(6) não implementadas neste conjunto. Gasóleo50% é regraC,sem fecharD.",
"E": "Activos/regularizações por evento e resultado original ainda seguem dependências próprias.",
"L3": "Bebidas por restituição,créditos anteriores,saldo final e formulários continuam no âmbitoL3.",
"L1": "129fechos e quatro residuais preservados;Estatísticas/IA adiadas."
},
"delivery_process": "Uma integração/backup/restauro/conferência do delta pelo processoGit existente. Rever concorrência imediatamente antes da instalação. Não há instalação/backup novo efectuado nesta preparação.",
"schema_changes": false,
"active_writes": false
}
@@ -0,0 +1,103 @@
{
"status": "L2-B/C concluídos em QA no âmbito demonstrado;preparação de entrega conjunta comA,sem instalação",
"matrix_anchor": "AUD-SIM-INT02",
"base_installed": "ada9629bb65ba186f54d970f0e41194888ebf53b",
"base_receipt": "7febb6f7",
"included_A_candidate": "56c948ec",
"main_fetch": "git fetch origin main obtido com sucesso;main permaneceada9629b",
"L1": {
"state": "operacional com limitações explícitas",
"delivered": 129,
"functional_pending": [
"P058",
"P084",
"P085"
],
"documentary_pending": "P001 — exclusivamente05/06",
"no_reopening": true
},
"B": {
"coverage": "Documento aduaneiro próprio,campo11,titularidade/importador,bens/linha,fonte de IVA e exigibilidade no mês declarado;posse,forma legal,integridade,direito e afectação da aquisição. Não exige fornecedor estrangeiro inscrito no IVA interno nem regra17-A de factura interna.",
"amount": "IVA expresso no bilhete/pressuposto basta;líquido/bruto opcionais,conservados se conhecidos.Não infere IVA da diferença de custos nem da classificaçãoL1;IVA isento/zero só contribui0 com montante0 expresso.Montante desconhecido permanece indeterminado.Sem calcular direitosL13.",
"reuse": "Reutiliza importações dos livrosL1 da mesma oportunidade:descrição,importador,documento/data/período apenas se vazios;montantes e edições deliberadas conservados. Mudança da origem invalida cálculo actual e pede adopção expressa;histórico não é recalculado.",
"coverage_control": "Todas as importações do período precisam cobertura própria,distinta de08–10. Não declarar ausência11 quando há importações.",
"anti_duplication": "Documento/linha repetido entre efectivo/projecção impedido;IVA próprio dos bens separado do serviço incluído na base. OrigemL1 que seja serviço12(1)e não pode fornecer IVA próprio dos bens no campo11."
},
"C": {
"coverage": [
"20(1)a:veículos/bens abrangidos e excepção de venda/exploração20(3)a",
"20(1)b:combustíveis;gasóleo50%;integral nos quatro usos específicos confirmados;revenda com fundamento próprio",
"20(1)c:transportes/viagens do sujeito passivo/pessoal",
"20(1)d:alojamento/alimentação,bebidas/tabaco/recepção;excepção de viajante comercial por conta própria20(3)b restrita a alojamento/alimentação",
"20(1)e:comunicações telefónicas,excepto telefone fixo em nome do sujeito passivo",
"20(1)f:divertimento/luxo segundo natureza/montante e exploração normal declarados",
"20(2),redacçãoLei22/2022:IVA suportado em fornecimento17-A excluído,distinto do destino19(2)",
"Bebidas13(2):via de dedução ligada a origemL1 actualc/d/h,condições,afectação e parcela exclusivamente para dedução;d exige excluir provisões de pesca costeira. Não estende a e,tabaco ou recepção;sem isenção directa ou restituiçãoL3."
],
"precision": "Percentagens aplicadas à dedução,sem inventar taxaIVA/pro rata. Fracções exactas dedutível/não dedutível conservadas;totais não somam apresentações arredondadas.",
"unknown": "Facto/fonte/categoria por esclarecer permanece indeterminado;escolha de excepção não dispensa documento,direito ou afectação exclusiva.",
"source_cotejo": [
"Lei13/2016 PDF12,p1426(270),13(2–3);PDF14,p1426(272),18(1)b;PDF15,p1426(273),18–21",
"Lei22/2022 PDF3,p2252(27),20(2–3),renumeração das excepções",
"Lei10/2025 PDF13,p2180(13),18(2)/19(2)"
]
},
"F": {
"included_each_increment": true,
"example_MZN": {
"09": "24.00",
"11": "16.00",
"14": "1000.00",
"15": "40.00",
"16": "160.00",
"17": "120.00",
"18": "0.00"
},
"unknown_import": "11/15/17/18 indeterminados;semzero presumido.",
"exact_diesel_test": "Duas parcelas deIVA0.000001×50%=1/2000000 cada,total exacto1/1000000;sem dupla aproximação.",
"limit": "17/18 antes de créditos anteriores;19–27,restituição/pagamentoL3 fora deste incremento."
},
"validation": {
"frontend": "21testes dirigidos:16anteriores deA/compras/continuidade e5novosB/C,com múltiplos cenários por família",
"api": "1teste/80asserções,guardcurrent_database=simulator_fiscal_delta_20261021_test/port55441,DatabaseTransactions rollback;A e todos os mapas anteriores preservados integralmente",
"types": "npm.cmd run types:check passou",
"build": "npm.cmd run build passou após encerrar servidoresQA temporários e reduzir concorrência de memória",
"ui": "Componente real:origemL1 reutilizada sem sobrescrita;IVA desconhecido→11/totais indeterminados;reposição de montante identificado→calcular→gravar→recuperar→relatório dos totais",
"ui_proof": "outputs/iva-l2-bc-20261025/ui-recovered-totals.png",
"resource_incident": "Última repetição deAPI/build encontrou falta de memória do Windows;servidores de demonstraçãoA/B/C encerrados;API,tipos e build repetidos sequencialmente. Não alterou activo ou limites de memória/configuração do sistema."
},
"remaining": {
"D": "Afectação mista/pro rata e proporções específicas18(6) não implementadas neste conjunto. Gasóleo50% é regraC,sem fecharD.",
"E": "Activos/regularizações por evento e resultado original ainda seguem dependências próprias.",
"L3": "Bebidas por restituição,créditos anteriores,saldo final e formulários continuam no âmbitoL3.",
"L1": "129fechos e quatro residuais preservados;Estatísticas/IA adiadas."
},
"active_writes": false,
"schema_changes": false,
"runtime_delta": [
{
"path": "resources/js/lib/iva-acquisitions.ts",
"sha256_LF": "5c75d86e3cffda929fffc56a2613cebf26031c0c0bd466fab33887354ffb8281"
},
{
"path": "resources/js/lib/iva-acquisition-right.ts",
"sha256_LF": "1df109ef6eea9e370fa3ecb33993296e260cfb77b6adc8e4f414e5bda655c895"
},
{
"path": "resources/js/lib/iva-acquisition-bc.ts",
"sha256_LF": "14035966df3c680df6ac1b03f2cef8083fa5f67533deb9d98905f51c0bc1110d"
},
{
"path": "resources/js/components/simulation-acquisitions.tsx",
"sha256_LF": "efdc16a84cc64f47b2c587651f966f9dcfd57f1fe534645725629973ce92e12e"
},
{
"path": "resources/js/components/acquisition-operation-link.tsx",
"sha256_LF": "e6894e4986a2293b0d2cd9743074f045dc8e5c50610cb9d09a625108b4561144"
},
{
"path": "resources/js/components/acquisition-imports-exclusions.tsx",
"sha256_LF": "8f84ffeeafc7afeefcf64cd99df7bf84c16a9cc92432e215f7b48feb24d5b7d1"
}
]
}
@@ -1,5 +1,25 @@
# Rastreabilidade, utilização e formação
## 25/10/2026 — L2-B/C emQA e entrega A/B/C preparada
L2-A56c948ec permanece aceite emQA.B/C concluídos emQA no âmbito demonstrado,comF,preparados para uma única entrega compatível comA.Homologação/main obtido porfetch:ada9629b/recibo7febb6f7,sem instalação nova.L1 operacional com limitações:129entregues+P058/P084/P085+P001=133;linhas dos129fechos e quatro residuais comparadas sem alterações.Nenhuma nova matriz/backlog.
**B — importações/campo11.** No livro de compras,abrir «Preparar importação — B/campo11».Conserva valores e documento;reutilizar origemL1 preenche apenas descrição/importador/documento/data/período vazios,sem copiar montante aduaneiro comoIVA.Indicar bilhete de despacho,importador/titularidade,bens/linha,posse/forma legal,IVA próprio devido e sua fonte;confirmar exigibilidade no período declarado21(1),sem inferir só da emissão do bilhete.Não exige fornecedor estrangeiro inscrito emIVA interno nem questão17-A própria da factura interna.IVA identificado no documento basta;custos líquido/bruto opcionais,montantes anteriores preservados.Não depende de direitosL13 nem da conclusão de outros regimes deL1.Isento/zero exige montantezero expresso;desconhecido não vira0.Confirme cobertura de todas as importações,distinta de08–10.Mesma linha repetida entre projecção/efectivo é impedida;serviço incluído12(1)e não fornece IVA próprio dos bens e não é somado novamente.
**C — exclusões/excepções.** Escolher a natureza da despesa e completar apenas condições aplicáveis,com factos/referência.Veículos20(1)a:abrangência própria e venda/exploração como actividade20(3)a.Combustíveis20(1)b:gasóleo50%,ou integral nos quatro usos específicos confirmados;revenda tem fundamento próprio.Transportes/viagens20(1)c,alojamento/alimentação/bebidas/tabaco/recepção20(1)d,telefone20(1)e e luxo20(1)f conservam diferenças.Excepções:viajante comercial por conta própria20(3)b apenas alojamento/alimentação;telefone fixo em nome do sujeito passivo.O IVA suportado17-A é excluído20(2),redacção2022,independentemente do destino19(2).Mudar a categoria conserva respostas anteriores,sem aplicar factos de outro tratamento.
**Bebidas13(2).** Via de dedução com origemL1 actualc/d/h,condições,afectação e parcela documentada exclusivamente para dedução;d não abrange provisões de pesca costeira.Não estender à alíneae,tabaco ou recepção;não conceder isenção directa.Benefício conhecido não dispensa documento/direito/condições próprios.A escolha de restituição permanece emL3 e não produz dedução nesta via,nem duplica a mesma parcela.Não altera classificação ou resultadosL1 aceites.
**Gravação eF.** Calcular e guardar versão;gravação privada/recuperação/relatório conservam condições/fontes,montantes,fracções exactas e histórico.Origens alteradas pedem adopção expressa sem sobrescrever edições.Livros deA e todos os mapas anteriores preservados.ExemploMZN:09=24,00;11=16,00;14=1000,00;15=40,00;16=160,00;17=120,00;18=0,00.IVA importado desconhecido deixa11/15/17/18 indeterminados.Gasóleo50% usa fracções exactas,sem somar aproximações;17/18 continuam antes dos créditos anteriores,sem ordem de pagamento/reembolso.
Validação:21testesfrontend dirigidos,API1teste/80asserções naQA55441 com rollback,tipos/build,componente real→gravar→recuperar→relatório.Tentativa final deAPI/build encontrou falta de memória doWindows;demonstrações temporárias encerradas e verificações repetidas em sequência,sem alterar configuração ou activo.D segue afectação mista/pro rata/proporções18(6);E,eventos/histórico originais.F acompanha cada incremento.Restituição/créditos finais permanecemL3.
Relatório:docs/investmoz/simulations/l2-bc-imports-exclusions-qa-20261025.json.Manifesto único preparado:docs/investmoz/simulations/l2-abc-prepared-delivery-20261025.json.Provas anteriores válidas reutilizadas;instalação futura requer o processoGit existente,com uma integração/backup/restauro/conferência do delta e nova verificação de concorrência.
## 25/10/2026 — preparação QA de L2-B/C: aquisição e direito à dedução
Inventário antes dos testes:129fechos e quatro residuais L1 preservados;L1 operacional com limitações explícitas,homologação ada9629b/recibo7febb6f7.L2-B/C estende os livros de compras/classificação existentes,sem reconstruir motor e sem alteração no activo.Cotejo restrito de18/19 para mapear direito da operação versus condições da aquisição;17-A não confere direito apesar de existir imposto.Não calcular pro rata,proporções18(6),direitosL13 ou regularizaçõesD/E.Provas anteriores reutilizadas e delta novo dirigido.
## 25/10/2026 — L2-A em QA: aquisição → direito à dedução
L1 permanece operacional com limitações explícitas:129entregues+P058/P084/P085+P001=133,sem reabertura dos fechos.Homologaçãoada9629b/recibo7febb6f7 inalterada.L2-A está preparado emQA,no âmbito de aquisição internaMZN/2026,afectação exclusiva e dedução no mês do documento;não representa conclusão deL2.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,29 @@
import type {Acquisition} from '@/lib/iva-acquisitions';
import {acquisitionImportSources,expenseOptions,newAcquisitionBC,reuseAcquisitionImport,type AcquisitionBC} from '@/lib/iva-acquisition-bc';
export function AcquisitionImportsExclusions({draft,row,onChange}:{draft:Record<string,unknown>;row:Acquisition;onChange:(patch:Partial<Acquisition>)=>void}){
const b=row.bc;const update=(patch:Partial<AcquisitionBC>)=>onChange({bc:{...(b??newAcquisitionBC()),...patch}});
const choice=(key:'reducedSupply'|'belongs'|'ownImportVat',label:string)=><label className="block">{label}<select value={b?.[key]??''} onChange={e=>update({[key]:e.target.value})}><option value="">Por indicar</option><option value="yes">Sim</option><option value="no">Não</option></select></label>;
const fact=(key:string,label:string)=><label className="block" key={key}>{label}<select value={b?.facts[key]??''} onChange={e=>update({facts:{...b?.facts,[key]:e.target.value as ''|'yes'|'no'}})}><option value="">Por indicar</option><option value="yes">Sim</option><option value="no">Não</option></select></label>;
if(!b)return <div><button type="button" onClick={()=>onChange({bc:newAcquisitionBC()})}>Analisar exclusões e excepções desta aquisição — C</button><button type="button" onClick={()=>onChange({bc:newAcquisitionBC(),field:'11',domestic:'no',documentType:'customs'})}>Preparar importação — B/campo11 (conserva valores e documento)</button></div>;
const imported=row.domestic==='no';const sources=acquisitionImportSources(draft);
return <fieldset className="space-y-2 rounded border p-3"><legend>Importações e exclusões — B/C</legend>
<p>Dados existentes conservados;as respostas são factos declarados ou pressupostos de projecção. Informação em falta fica indeterminada. Alterar a natureza da despesa conserva respostas anteriores sem as aplicar a outro tratamento.</p>
{imported&&<><p>Campo11:IVA devido na importação de bens pelo documento aduaneiro. Não calcula direitosL13,não copia o valor aduaneiro como IVA e não exige fornecedor estrangeiro inscrito no IVA interno.</p>
<label className="block">Reutilizar registo de importação existente<select value="" onChange={e=>{const source=sources.find(s=>s.map+'/'+s.row.id===e.target.value);if(source)onChange(reuseAcquisitionImport(draft,row,source.map,source.row.id));}}><option value="">Escolher correspondência;apenas preenche dados vazios</option>{sources.map(s=><option key={s.map+'/'+s.row.id} value={s.map+'/'+s.row.id}>{s.label}</option>)}</select></label>
{b.source&&<p>Origem conservada:{b.source.map}/{b.source.id}. Reutilizar novamente adopta a origem actual,sem substituir edições.</p>}
{(['importer','goodsRef','vatRef'] as const).map((key,i)=><label className="block" key={key}>{['Importador identificado','Bens/operação/linha abrangidos','Fonte do montante de IVA devido — bilhete/pressuposto'][i]}<input value={b[key]} onChange={e=>update({[key]:e.target.value})}/></label>)}
{choice('belongs','Bilhete na titularidade do sujeito passivo/importador desta aquisição')}
{fact('exigibleInPeriod','IVA aduaneiro tornou-se exigível no período de dedução indicado,conforme a fonte/documento21(1);não basta a data de emissão do bilhete')}
{choice('ownImportVat','Montante é apenas IVA próprio devido nesta importação,sem somar novamente IVA de serviço incluído na base dos bens')}
<label className="block">Situação do IVA da importação<select value={b.vatBasis} onChange={e=>update({vatBasis:e.target.value as AcquisitionBC['vatBasis']})}><option value="">Por identificar</option><option value="due">IVA devido identificado no documento/pressuposto</option><option value="exempt">Importação isenta — IVAzero expressamente indicado</option><option value="zero">Montantezero expresso,com fundamento</option><option value="unknown">Montante desconhecido</option></select></label>
<p>IVA isento/zero não é montante desconhecido. Para ausência de imposto a deduzir,indique zero expressamente no IVA;isençãoL1 não preenche esse montante.</p></>}
{!imported&&choice('reducedSupply','O IVA suportado respeita a fornecimento abrangido pelo artigo17-A (20(2))')}
<label className="block">Natureza da despesa para dedução<select value={b.expense} onChange={e=>update({expense:e.target.value})}><option value="">Por identificar</option>{expenseOptions.map(([v,l])=><option key={v} value={v}>{l}</option>)}</select></label>
{b.expense==='vehicle'&&<>{fact('tourism','Bem abrangido20(1)(a):viatura de turismo,barco de recreio,helicóptero,avião,moto/motociclo. Viatura:inclui reboque;não destinada unicamente a mercadorias/uso agrícola,comercial ou industrial,ou mista/passageiros até9lugares incluindo condutor')}{b.facts.tourism==='yes'&&fact('saleOrExploit','Venda ou exploração deste bem constitui objecto da actividade20(3)(a)')}</>}
{b.expense==='fuel'&&<>{fact('resale','Combustível adquirido para revenda,com actividade e destino identificados20(3)(a)')}{b.facts.resale==='no'&&<><label>Combustível<select value={b.fuelKind} onChange={e=>update({fuelKind:e.target.value as AcquisitionBC['fuelKind']})}><option value="">Por identificar</option><option value="diesel">Gasóleo</option><option value="other">Outro combustível normalmente utilizável em viaturas</option></select></label>{b.fuelKind==='diesel'&&<><label>Utilização do gasóleo<select value={b.dieselUse} onChange={e=>update({dieselUse:e.target.value as AcquisitionBC['dieselUse']})}><option value="">Por identificar</option><option value="heavy_passengers">Veículo pesado de passageiros</option><option value="public_passengers">Veículo licenciado para transporte público de passageiros,excepto rent a car</option><option value="machine">Máquina consumidora de gasóleo que não é veículo matriculado</option><option value="agriculture">Tractor com emprego exclusivo/predominante em operações inerentes à actividade agrícola</option><option value="other">Outro uso — dedução50%</option></select></label>{b.dieselUse&&b.dieselUse!=='other'&&fact('dieselScope','O uso e as características correspondem integralmente à categoria20(1)(b)(i–iv) seleccionada')}</>}</>}</>}
{b.expense==='lodging_food'&&fact('commercialTraveller','Alojamento/alimentação de viajante comercial,agindo por conta própria,no quadro da actividade profissional20(3)(b)')}
{b.expense==='telephone'&&fact('ownLandline','Serviço de telefone fixo em nome do sujeito passivo20(1)(e)')}
{b.expense==='beverages'&&<>{fact('beverageSpecial','Bebidas de abastecimento de bordo nas alíneas13(1)(c/d/h),com origemL1 identificada;tabaco e recepção não estão abrangidos por esta excepção')}{b.facts.beverageSpecial==='yes'&&<><label>Via13(2) para esta parcela<select value={b.beveragePath} onChange={e=>update({beveragePath:e.target.value as AcquisitionBC['beveragePath']})}><option value="">Por escolher</option><option value="deduction">Dedução nesta aquisição — L2</option><option value="refund">Restituição — percursoL3,sem apuramento aqui</option></select></label>{fact('onlyDeduction','Parcela identificada exclusivamente para dedução;sem pedido/restituição da mesma parcela')}{fact('notCoastalFishing','Se origem13(1)(d):embarcação de salvamento/assistência marítima,sem ser pesca costeira cujas provisões ficam excluídas')}</>}</>}
<label className="block">Factos e referência documental da classificação/excepção<input value={b.evidence} onChange={e=>update({evidence:e.target.value})}/></label>
</fieldset>;
}
@@ -1,11 +1,11 @@
import {acquisitionOperationChoices,createAcquisitionRightLink,linkedOperationRight,type AcquisitionRightLink} from '@/lib/iva-acquisition-right';
import {acquisitionOperationChoices,createAcquisitionRightLink,linkedOperationRight,linkedBeverageDeductionRight,type AcquisitionRightLink} from '@/lib/iva-acquisition-right';
import type {Acquisition} from '@/lib/iva-acquisitions';
export function AcquisitionOperationLink({draft,row,onChange}:{draft:Record<string,unknown>;row:Acquisition;onChange:(patch:Partial<Acquisition>)=>void}){
const choices=acquisitionOperationChoices(draft),link=row.deductionLink;
const adopt=(id:string)=>onChange({deductionLink:createAcquisitionRightLink(draft,id)});
const patch=(value:Partial<AcquisitionRightLink>)=>{if(link)onChange({deductionLink:{...link,...value}});};
const result=link?linkedOperationRight(draft,link):null;
const result=link?(row.bc?.expense==='beverages'&&row.bc.facts.beverageSpecial==='yes'&&row.bc.beveragePath==='deduction'?linkedBeverageDeductionRight(draft,link,row.bc.facts.notCoastalFishing==='yes'):linkedOperationRight(draft,link)):null;
return <fieldset className="space-y-2 rounded border p-3"><legend>Aquisição → direito à dedução — L2-A</legend>
<p>Reutilize a classificação existente. A ligação conserva montantes, documento e escolhas da aquisição; não cria outra compra. Importações, despesas especiais e afectação parcial conservam enquadramento próprio.</p>
{choices.length===1&&!link?<button type="button" onClick={()=>adopt(choices[0].id)}>Reutilizar operação existente: {choices[0].label}</button>:<label>Operação de destino<select value={link?.operationId??''} onChange={e=>e.target.value?adopt(e.target.value):onChange({deductionLink:undefined})}><option value="">Sem vínculo L2-A</option>{choices.map(c=><option key={c.id} value={c.id}>{c.label}</option>)}</select></label>}
@@ -1,5 +1,6 @@
import { useState } from 'react';
import { AcquisitionOperationLink } from './acquisition-operation-link';
import { AcquisitionImportsExclusions } from './acquisition-imports-exclusions';
import { acquisitionRightVersion } from '@/lib/iva-acquisition-right';
import { acquisitionBook, acquisitionCurrent, acquisitionExcludedFields, acquisitionPlanReferences, acquisitionReportRows, recordAcquisitions, newAcquisition, reuseAcquisitions, saveAcquisitionBook, type Acquisition, type AcquisitionBook } from '@/lib/iva-acquisitions';
import { acquisitionTreatmentIssue, continueWithoutFormerExploration, formerExplorationSource, type AcquisitionTreatment } from '@/lib/iva-acquisition-continuity';
@@ -22,7 +23,7 @@ export function SimulationAcquisitions({draft,onChange}:{draft:Record<string,unk
<p>Oportunidade em tratamento: {String(draft.context_opportunity||'entrada geral')}. Moeda de origem: {book.currency||'por indicar'}. A compra registada não gera automaticamente IVA dedutível.</p>
{book.currency && draft.currency!==book.currency && <p role="alert">As compras conservam {book.currency}; o projecto está em {String(draft.currency||'moeda por indicar')}. Reveja a moeda no projecto. Os montantes e o resultado anterior não foram convertidos ou reinterpretados.</p>}
{!book.currency && !!draft.currency && <button type="button" className="underline" onClick={()=>save({...book,currency:String(draft.currency),rows:book.rows.map(row=>row.sourceCurrency?row:{...row,sourceCurrency:String(draft.currency)})})}>Confirmar explicitamente {String(draft.currency)} como moeda de origem dos valores sem moeda, conservando os montantes</button>}
<p>L2-A: aquisições internas em MZN e afectação exclusiva a uma operação classificada, com direito, sem direito ou indeterminado. Documento e dedução no mesmo período de2026; importações, exclusões especiais, afectação parcial e regularizações conservam percursos próprios. Nas projecções, cada confirmação é um pressuposto. Indique dois valores entre líquido, IVA e bruto; o terceiro é obtido sem taxa de compra presumida.</p>
<p>Aquisições em MZN/2026,ligadas à operação de destino:com direito,sem direito ou indeterminadas. A/B/C distinguem compras internas,importações de bens e exclusões/excepções. Documento e dedução no mesmo mês;afectação mista,proporções18(6),dedução posterior e regularizações conservam percursos próprios. Nas projecções,cada confirmação é um pressuposto. Compras internas:indique dois valores entre líquido,IVA e bruto. Importações:IVA expressamente identificado no bilhete;custos líquido/bruto são opcionais,sem calcular direitos ou presumir taxa.</p>
<label className="block">Adquirente sujeito passivo de IVA no regime normal (efectivo ou pressuposto da projecção)<select className="block rounded border p-2" value={book.taxpayerRegistered??''} onChange={e=>save({...book,taxpayerRegistered:e.target.value as AcquisitionBook['taxpayerRegistered']})}><option value="">Por confirmar</option><option value="yes">Sim</option><option value="no">Não / outro enquadramento — fora deste lote</option></select></label>
{error&&<p role="alert">{error}</p>}
<details className="rounded border p-2"><summary>Indicar mês da aquisição pelo documento ou pressuposto da projecção</summary>
@@ -34,6 +35,7 @@ export function SimulationAcquisitions({draft,onChange}:{draft:Record<string,unk
<button type="button" className="ml-3 underline" disabled={!book.periods.length} onClick={()=>save({...book,rightVersion:acquisitionRightVersion,rows:[...book.rows,newAcquisition(crypto.randomUUID(),book.periods[0]?.period??'',book.currency)]})}>Acrescentar aquisição em falta</button>
{book.rows.map(row=><details key={row.id} className="rounded border p-3"><summary>{row.description||'Aquisição adicional'} — {row.period} — campo{row.field}</summary>
<AcquisitionOperationLink draft={draft} row={row} onChange={patch=>change(row.id,patch)}/>
<AcquisitionImportsExclusions draft={draft} row={row} onChange={patch=>change(row.id,patch)}/>
<details className="rounded border p-2"><summary>Enquadramento especial da aquisição / referência anterior</summary>
<label>Tratamento da aquisição<select value={row.acquisitionTreatment??''} onChange={e=>row.acquisitionTreatment==='former_exploration_exemption'&&e.target.value!=='former_exploration_exemption'?run(()=>save({...book,rows:book.rows.map(item=>item.id===row.id?continueWithoutFormerExploration(item):item)})):change(row.id,{acquisitionTreatment:e.target.value as AcquisitionTreatment})}><option value="">Enquadramento normal; sem benefício especial indicado</option><option value="former_exploration_exemption">Antiga isenção de prospecção/pesquisa —14(2)(c),eliminada</option><option value="normal">Continuidade pelo enquadramento normal</option></select></label>
{acquisitionTreatmentIssue(row.acquisitionTreatment)&&<p role="status">{acquisitionTreatmentIssue(row.acquisitionTreatment)}</p>}
@@ -44,19 +46,19 @@ export function SimulationAcquisitions({draft,onChange}:{draft:Record<string,unk
<div className="grid gap-3 md:grid-cols-2">
<label>Descrição<input className="block rounded border p-2" value={row.description} onChange={e=>change(row.id,{description:e.target.value})}/></label>
{row.sourceMonth===null && <label>Reutilizar valor existente, sem presumir que o orçamento é uma factura<select className="block rounded border p-2" value="" onChange={e=>run(()=>{const ref=references.find(r=>r.key===e.target.value);if(ref)change(row.id,{sourceMonth:ref.month,sourcePeriod:ref.period,sourceField:ref.kind,sourceAmount:ref.amount,sourceCurrency:ref.currency,...(ref.kind==='asset'?{}:{period:ref.period})});})}><option value="">Escolher apenas se corresponde a esta aquisição</option>{references.filter(ref=>ref.kind===(row.field==='08'?'asset':row.field==='10'?'operating':'inventory')).map(ref=><option key={ref.key} value={ref.key}>{ref.label}: {ref.amount??'sem informação'} {ref.currency}</option>)}</select></label>}
{select(row,'field','Categoria',[['08','08 — Imobilizado'],['09','09 — Existências'],['10','10 — Outros bens e serviços']])}
{select(row,'field','Categoria',[['08','08 — Imobilizado'],['09','09 — Existências'],['10','10 — Outros bens e serviços'],...(row.bc?[['11','11 — Importações de bens'] as [string,string]]:[])])}
{select(row,'origin','Natureza dos dados',[['projected','Projectados / pressupostos'],['effective','Efectivos declarados']])}
<label>Período de dedução<input className="block rounded border p-2" type="month" value={row.period} onChange={e=>change(row.id,{period:e.target.value})}/></label>
{row.sourceMonth!==null && <label>O valor de origem corresponde a<select className="block rounded border p-2" value={row.sourceBasis} onChange={e=>{const basis=e.target.value as Acquisition['sourceBasis'];change(row.id,{sourceBasis:basis,...(basis==='net'&&!row.net?{net:row.sourceAmount??''}:basis==='gross'&&!row.gross?{gross:row.sourceAmount??''}:{})});}}><option value="">Por classificar</option><option value="net">Valor sem IVA</option><option value="gross">Valor com IVA</option></select></label>}
{(['net','vat','gross'] as const).map((field,index)=><label key={field}>{['Valor líquido','IVA indicado','Valor bruto'][index]} ({row.sourceCurrency})<input className="block rounded border p-2" inputMode="decimal" value={row[field]} onChange={e=>change(row.id,{[field]:e.target.value})}/></label>)}
{select(row,'documentType','Tipo de documento',[['invoice','Factura'],['equivalent','Documento equivalente']])}
{select(row,'documentType','Tipo de documento',row.domestic==='no'&&row.bc?[['customs','Bilhete de despacho de importação']]:[['invoice','Factura'],['equivalent','Documento equivalente']])}
<label>Referência do documento / previsão<input className="block rounded border p-2" value={row.documentRef} onChange={e=>change(row.id,{documentRef:e.target.value})}/></label>
<label>Linha do documento, se discriminado em várias aquisições<input className="block rounded border p-2" value={row.documentLine} onChange={e=>change(row.id,{documentLine:e.target.value})}/></label>
<label>Data do documento / prevista<input className="block rounded border p-2" type="date" value={row.documentDate} onChange={e=>change(row.id,{documentDate:e.target.value})}/></label>
<label>Fornecedor / previsto<input className="block rounded border p-2" value={row.supplier} onChange={e=>change(row.id,{supplier:e.target.value})}/></label>
{!(row.bc&&row.domestic==='no')&&<label>Fornecedor / previsto<input className="block rounded border p-2" value={row.supplier} onChange={e=>change(row.id,{supplier:e.target.value})}/></label>}
{select(row,'domestic','Aquisição interna',yesNo)}
{row.acquisitionTreatment!=='former_exploration_exemption'&&<>
{select(row,'supplierRegistered','Fornecedor inscrito no IVA',yesNo)}
{!(row.bc&&row.domestic==='no')&&select(row,'supplierRegistered','Fornecedor inscrito no IVA',yesNo)}
{select(row,'legalDocument','Documento emitido em forma legal',yesNo)}
{select(row,'held','Documento na posse do adquirente / previsto na projecção',yesNo)}
{select(row,'allocation','Afectação',[['taxable_only',row.deductionLink?'Exclusiva à operação ligada':'Exclusiva a operações tributadas'],['mixed','Mista / parcial — fora deste lote'],['no_right','Operações sem direito à dedução']])}
@@ -64,11 +66,12 @@ export function SimulationAcquisitions({draft,onChange}:{draft:Record<string,unk
{select(row,'integrity','Operação real / pressuposto regular, sem simulação, margem de segunda mão ou impedimentos do art.18(3–5)',yesNo)}
</>}
</div>
<p>Fundamento: CIVA arts.18–21, fontes e versão no resultado. Não se calcula dedução parcial, excepção de despesa ou dedução posterior neste lote.</p>
<p>Fundamento:CIVA18–21,fontes e versão no resultado. {row.bc?'Aplicam-se apenas as exclusões/excepções documentadas escolhidas;gasóleo50% conserva fracção exacta. Sem pro rata,proporções18(6) ou regularização neste incremento.':'Afectação parcial,despesa especial e dedução posterior exigem percurso próprio;use a análiseC quando aplicável.'}</p>
{row.sourceMonth===null && <button type="button" className="mr-3 underline" onClick={()=>save({...book,rows:book.rows.filter(r=>r.id!==row.id)})}>Remover esta aquisição introduzida directamente</button>}
{row.sourceMonth!==null && <button type="button" className="underline" onClick={()=>run(()=>{const ref=acquisitionPlanReferences(draft).find(r=>r.month===row.sourceMonth && r.kind===(row.sourceField??'inventory'));if(!ref)throw new Error('Origem não disponível; conserve o registo e reveja o plano.');if(draft.currency!==row.sourceCurrency)throw new Error('Moeda divergente; não adoptar montantes de outra moeda.');change(row.id,{sourceAmount:ref.amount,sourcePeriod:ref.period,...(row.sourceBasis==='net'?{net:ref.amount??'',gross:''}:row.sourceBasis==='gross'?{gross:ref.amount??'',net:''}:{})});})}>Adoptar explicitamente o valor actual do plano (conserva histórico)</button>}
</details>)}
{book.periods.map(period=><details key={period.period} className="rounded border p-3"><summary>{period.period} — cobertura das parcelas — {period.complete && period.absent.length===acquisitionExcludedFields.length?'confirmada':'por completar'}</summary>
{book.rows.some(r=>r.bc)&&<label className="block"><input type="checkbox" checked={period.importsComplete??false} onChange={e=>save({...book,periods:book.periods.map(p=>p.period===period.period?{...p,importsComplete:e.target.checked}:p)})}/> Incluí todas as importações de bens/campo11 deste período,mesmo isentas ou sem direito;sem somar novamente IVA de serviços incluídos na base.</label>}
<label className="block"><input type="checkbox" checked={period.complete} onChange={e=>save({...book,periods:book.periods.map(p=>p.period===period.period?{...p,complete:e.target.checked}:p)})}/> Incluí todas as aquisições08–10 deste período, incluindo as sem direito à dedução; uma lista vazia significa que não houve essas aquisições.</label>
<p>Campos fora deste lote: assinale apenas quando não existiram as respectivas operações. Uma parcela não assinalada permanece indeterminada.</p>
{acquisitionExcludedFields.map(field=><label className="block" key={field}><input type="checkbox" checked={period.absent.includes(field)} onChange={e=>save({...book,periods:book.periods.map(p=>p.period===period.period?{...p,absent:e.target.checked?[...p.absent,field]:p.absent.filter(f=>f!==field)}:p)})}/> Sem operações no campo{field} — {labels[field]}</label>)}
+98
View File
@@ -0,0 +1,98 @@
import type {Acquisition,AcquisitionResult} from './iva-acquisitions.ts';
import {acquisitionScope,readAcquisitionStore} from './iva-acquisitions.ts';
import {linkedOperationRight,linkedBeverageDeductionRight} from './iva-acquisition-right.ts';
import {acquisitionTreatmentIssue} from './iva-acquisition-continuity.ts';
import {decimalFraction,fraction,fractionPresentation,subtractFractions} from './fiscal-rational.ts';
export const acquisitionBCVersion='acquisition-imports-exclusions/1';
export const acquisitionBCSource='L2-B/C: CIVA18(1)(a/b),18(2),19,20(1)(a–f)/20(2)/20(3)(a/b),21. Lei13/2016 PDF14–15,p1426(272–273);Lei22/2022 PDF3,p2252(27),art20(2–3);Lei10/2025 PDF13,p2180(13),18(2)/19(2). Bebidas13(2):Lei13/2016 PDF12,p1426(270). MZN/2026,mesmo mês documental;sem pro rata18(6),regularizações ou restituiçãoL3.';
type Choice=''|'yes'|'no';
export const expenseOptions=[['ordinary','Despesa sem exclusão especial'],['vehicle','Viatura de turismo,barco de recreio,helicóptero,avião,moto ou outro veículo'],['fuel','Combustível normalmente utilizável em viaturas automóveis'],['travel','Transportes e viagens do sujeito passivo/pessoal'],['lodging_food','Alojamento e alimentação'],['beverages','Bebidas,tabaco e recepção'],['telephone','Comunicações telefónicas'],['luxury','Divertimento e luxo'],['other','Outro caso — por enquadrar']] as const;
export type AcquisitionBC={version:1;expense:string;reducedSupply:Choice;evidence:string;facts:Record<string,Choice>;fuelKind:''|'diesel'|'other';dieselUse:''|'heavy_passengers'|'public_passengers'|'machine'|'agriculture'|'other';beveragePath:''|'deduction'|'refund';importer:string;belongs:Choice;ownImportVat:Choice;vatBasis:''|'due'|'exempt'|'zero'|'unknown';vatRef:string;goodsRef:string;source?:{map:string;id:string;fingerprint:string}};
export function newAcquisitionBC():AcquisitionBC{return{version:1,expense:'',reducedSupply:'',evidence:'',facts:{},fuelKind:'',dieselUse:'',beveragePath:'',importer:'',belongs:'',ownImportVat:'',vatBasis:'',vatRef:'',goodsRef:''};}
export function acquisitionBCDetails(b:AcquisitionBC):string{
const labels:Record<string,string>={exigibleInPeriod:'IVA aduaneiro exigível no período de exercício confirmado pelo documento',tourism:'Bem abrangido20(1)(a)',saleOrExploit:'Venda/exploração como objecto da actividade',resale:'Combustível para revenda',dieselScope:'Âmbito específico do uso de gasóleo confirmado',commercialTraveller:'Viajante comercial por conta própria',ownLandline:'Telefone fixo em nome do sujeito passivo',beverageSpecial:'Bebidas no âmbito13(2)',onlyDeduction:'Parcela exclusivamente para dedução',notCoastalFishing:'Sem provisões de pesca costeira'};
const keys:Record<string,string[]>={vehicle:['tourism',...(b.facts.tourism==='yes'?['saleOrExploit']:[])],fuel:['resale',...(b.fuelKind==='diesel'&&b.dieselUse!=='other'?['dieselScope']:[])],lodging_food:['commercialTraveller'],telephone:['ownLandline'],beverages:['beverageSpecial',...(b.facts.beverageSpecial==='yes'?['onlyDeduction','notCoastalFishing']:[])]};
return [...(b.facts.exigibleInPeriod!==undefined?['exigibleInPeriod']:[]),...(keys[b.expense]??[])].map(k=>`${labels[k]}:${b.facts[k]==='yes'?'Sim':b.facts[k]==='no'?'Não':'Por indicar'}`).concat(b.expense==='fuel'?[`Combustível:${b.fuelKind==='diesel'?'Gasóleo':b.fuelKind==='other'?'Outro':'Por indicar'}`,`Utilização:${({heavy_passengers:'Pesado de passageiros',public_passengers:'Transporte público licenciado,excepto rent a car',machine:'Máquina,não veículo matriculado',agriculture:'Tractor agrícola,emprego exclusivo/predominante',other:'Outro uso50%','':'Por indicar'})[b.dieselUse]}`]:[],b.expense==='beverages'&&b.facts.beverageSpecial==='yes'?[`Via:${b.beveragePath==='deduction'?'DeduçãoL2':b.beveragePath==='refund'?'RestituiçãoL3':'Por indicar'}`]:[]).join('; ');
}
export function validAcquisitionBC(v:unknown):v is AcquisitionBC{
if(!v||typeof v!=='object'||Array.isArray(v))return false;const b=v as AcquisitionBC;
return b.version===1&&['expense','evidence','vatRef','goodsRef','importer','fuelKind','dieselUse','beveragePath','vatBasis'].every(k=>typeof b[k as keyof AcquisitionBC]==='string')&&[b.reducedSupply,b.belongs,b.ownImportVat].every(v=>['','yes','no'].includes(v))&&!!b.facts&&typeof b.facts==='object'&&!Array.isArray(b.facts)&&Object.values(b.facts).every(v=>['','yes','no'].includes(v))&&(!b.source||['map','id','fingerprint'].every(k=>typeof b.source?.[k as keyof typeof b.source]==='string'));
}
type ImportSource={id:string;treatment?:string;description?:string;documentRef?:string;documentDate?:string;period?:string;importDate?:string;importer?:string};
const importMaps=['importExemptions','objectiveImports','remissionImports'];
export function acquisitionImportSources(draft:Record<string,unknown>){const store=readAcquisitionStore(draft.modelo_a_snapshot) as unknown as Record<string,Record<string,{rows:ImportSource[]}>>;return importMaps.flatMap(map=>(store[map]?.[acquisitionScope(draft)]?.rows??[]).map(row=>({map,row,label:`${row.description||row.id} — ${row.period??''} — ${row.documentRef||'documento por indicar'}`})));}
export function reuseAcquisitionImport(draft:Record<string,unknown>,row:Acquisition,map:string,id:string):Acquisition{
const src=acquisitionImportSources(draft).find(v=>v.map===map&&v.row.id===id);if(!src)throw Error('Origem da importação indisponível;dados conservados.');
const bc=row.bc??newAcquisitionBC();return {...row,description:row.description||src.row.description||'',documentRef:row.documentRef||src.row.documentRef||'',documentDate:row.documentDate||src.row.documentDate||src.row.importDate||'',period:row.period||src.row.period||'',bc:{...bc,importer:bc.importer||src.row.importer||'',source:{map,id,fingerprint:JSON.stringify(src.row)}}};
}
export function acquisitionBCSourceKey(draft:Record<string,unknown>,rows:Acquisition[]):string{return rows.some(r=>r.bc)?JSON.stringify({version:acquisitionBCVersion,sources:rows.filter(r=>r.bc?.source).map(r=>acquisitionImportSources(draft).find(s=>s.map===r.bc!.source!.map&&s.row.id===r.bc!.source!.id)?.row??null)}):'';}
function validDate(value:string){const d=new Date(value+'T00:00:00Z');return /^\d{4}-\d{2}-\d{2}$/.test(value)&&Number.isFinite(d.getTime())&&d.toISOString().slice(0,10)===value;}
export function evaluateAcquisitionBC(row:Acquisition,currency:string,registered:Choice,draft:Record<string,unknown>|undefined,raw:AcquisitionResult):AcquisitionResult{
const bc=row.bc!;let right=row.deductionLink&&draft?linkedOperationRight(draft,row.deductionLink):{status:'indeterminate' as const,reason:'Vínculo à operação de destino em falta.',source:acquisitionBCSource};
const stop=(reason:string,status:'eligible'|'no_right'|'indeterminate'='indeterminate',percent?:number):AcquisitionResult=>{
const exact=percent!==undefined&&raw.vat!==null?decimalFraction(raw.vat):null;
const deductibleExact=exact?fraction(BigInt(exact.numerator)*BigInt(percent!),BigInt(exact.denominator)*100n):undefined;
return {...raw,reason,deductible:deductibleExact?fractionPresentation(deductibleExact).decimal:null,...(deductibleExact?{deductibleExact,nonDeductibleExact:subtractFractions(exact!,deductibleExact)}:{}),eligibility:{...right,status,reason,source:acquisitionBCSource+'; '+right.source}};
};
if(!validAcquisitionBC(bc))return stop('CondiçõesB/C não recuperáveis;sem regra por defeito.');
if(currency!=='MZN'||row.sourceCurrency!==currency||!/^2026-(0[1-9]|1[0-2])$/.test(row.period)||raw.vat===null)return stop(raw.reason||'IVA/moeda/período em falta.');
if(row.domestic==='no'){
if(!/^\d{1,15}(\.\d{1,6})?$/.test(row.vat.trim()))return stop('Indique expressamente o IVA do bilhete;diferença entre custo líquido/bruto não constitui montante aduaneiro identificado.');
if(row.net.trim()&&raw.net===null||row.gross.trim()&&raw.gross===null)return stop('Valores de custo preenchidos inválidos;conservar e resolver a divergência.');
}else if(raw.net===null||raw.gross===null)return stop(raw.reason);
if(raw.net!==null&&raw.gross!==null&&fractionPresentation(subtractFractions(decimalFraction(raw.gross),decimalFraction(raw.net))).decimal!==raw.vat)return stop('Líquido + IVA diverge do bruto declarado;sem substituir valores.');
const treatmentIssue=acquisitionTreatmentIssue(row.acquisitionTreatment);if(treatmentIssue)return stop(treatmentIssue);
if(!['projected','effective'].includes(row.origin))return stop('Indique dados efectivos ou pressupostos de projecção.');
if(row.sourceMonth!==null&&(!row.sourceBasis||row.sourceAmount===null||(row.sourceBasis==='net'?raw.net:raw.gross)===null||JSON.stringify(decimalFraction(row.sourceAmount))!==JSON.stringify(decimalFraction((row.sourceBasis==='net'?raw.net:raw.gross)!))))return stop('Valor de origem divergente ou por classificar;sem sobrescrita.');
if(row.domestic==='no'&&row.field!=='11'||row.domestic==='yes'&&row.field==='11'||!['yes','no'].includes(row.domestic))return stop('Separe aquisição interna08–10 da importação de bens11.');
if(bc.source){const src=draft?acquisitionImportSources(draft).find(s=>s.map===bc.source!.map&&s.row.id===bc.source!.id):undefined;if(!src||JSON.stringify(src.row)!==bc.source.fingerprint)return stop('OrigemL1 da importação alterada:adopte explicitamente os dados actuais,conservando edições e histórico.');if(['service_in_customs_value','accessory_service_first_destination'].includes(src.row.treatment??''))return stop('Origem é serviço incluído na base aduaneira12(1)(e),não IVA próprio da importação de bens;identifique o bilhete dos bens,sem somar novamente IVA do serviço.');}
if(registered!=='yes')return stop('Enquadramento do adquirente no regime normal por confirmar.');
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')return stop('Afectação parcial/pro rata depende deD;sem dedução integral presumida.');
if(!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(bc.expense==='beverages'&&bc.facts.beverageSpecial==='yes'&&bc.beveragePath==='deduction'&&draft)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);
if(row.deductionLink.noExcludedPayments==='no')return stop('Pagamentos15(6)(c)/19(2) excluídos.','no_right',0);
if(row.deductionLink.noExcludedPayments!=='yes')return stop('Exclusão19(2) por esclarecer.');
if(!validDate(row.documentDate)||row.documentDate.slice(0,7)!==row.period)return stop('Documento e exercício no mesmo mês neste incremento;dedução posterior não presumida.');
if(!row.documentRef.trim()||row.legalDocument!=='yes'||row.held!=='yes'||row.integrity!=='yes')return stop('Referência,forma legal,posse e integridade do documento por completar.');
if(row.domestic==='no'){
if(row.documentType!=='customs'||!bc.importer.trim()||!bc.goodsRef.trim()||!bc.vatRef.trim()||bc.belongs!=='yes'||bc.ownImportVat!=='yes')return stop('Bilhete de despacho,importador/titularidade,bens/linha e IVA próprio devido na importação por completar;fornecedor inscrito no IVA interno não é exigido.');
if(bc.facts.exigibleInPeriod!=='yes')return stop('Exigibilidade do IVA aduaneiro no período de exercício por confirmar pelo documento21(1);não inferida só da emissão do bilhete.Dedução posterior conserva percurso próprio.');
if(!['due','exempt','zero'].includes(bc.vatBasis))return stop('IVA da importação desconhecido;não inferir montante da classificaçãoL1 ou calcular direitosL13.');
if(['exempt','zero'].includes(bc.vatBasis)&&decimalFraction(raw.vat).numerator!=='0')return stop('IVA positivo incompatível com isenção/zero declarado;resolver divergência sem substituir montante.');
}else if(!['invoice','equivalent'].includes(row.documentType)||!row.supplier.trim()||row.supplierRegistered!=='yes')return stop('Factura/documento equivalente e fornecedor interno inscrito por completar.');
if(row.domestic==='yes'&&bc.reducedSupply==='yes')return stop('IVA suportado em fornecimento17-A:exclusão20(2),Lei22/2022;distinta do destino19(2).','no_right',0);
if(row.domestic==='yes'&&bc.reducedSupply!=='no')return stop('Indique se o IVA suportado respeita a fornecimento17-A;sem inferir pela taxa ou valor.');
if(!expenseOptions.some(([id])=>id===bc.expense)||bc.expense==='other'||!bc.evidence.trim())return stop('Natureza da despesa e fundamento factual/documental por identificar.');
const f=bc.facts;
switch(bc.expense){
case 'ordinary':return stop('Elegível pelas condições declaradas18/19;sem exclusão especial20 identificada.','eligible',100);
case 'vehicle':
if(f.tourism==='')return stop('Classificação do veículo na definição20(1)(a) em falta.');
if(f.tourism==='no')return stop('Veículo fora da definição20(1)(a),segundo características/utilização documentadas.','eligible',100);
if(f.tourism!=='yes')return stop('Indique se o bem está abrangido por20(1)(a).');
if(f.saleOrExploit==='yes')return stop('Excepção20(3)(a):venda/exploração do bem constitui objecto da actividade;combustível conserva20(1)(b).','eligible',100);
return f.saleOrExploit==='no'?stop('Bem abrangido20(1)(a),sem excepção20(3)(a).','no_right',0):stop('Venda/exploração como objecto da actividade por esclarecer.');
case 'fuel':
if(f.resale==='yes')return stop('Combustível adquirido para revenda,conforme ressalva20(3)(a);fundamento específico documentado.','eligible',100);
if(f.resale!=='no')return stop('Destino de revenda do combustível por esclarecer.');
if(bc.fuelKind==='other')return stop('Combustível normalmente utilizável em viaturas,fora da excepção gasóleo20(1)(b).','no_right',0);
if(bc.fuelKind!=='diesel')return stop('Natureza do combustível por indicar.');
if(bc.dieselUse==='other')return stop('Gasóleo20(1)(b):dedução50%,não taxa de IVA nem pro rata geral.','eligible',50);
if(['heavy_passengers','public_passengers','machine','agriculture'].includes(bc.dieselUse)){
if(f.dieselScope!=='yes')return stop('Confirme o âmbito específico20(1)(b)(i–iv);não basta escolher a categoria.');
return stop('Gasóleo20(1)(b)(i–iv):dedução integral no uso específico confirmado;transporte público exclui rent a car,máquina não é veículo matriculado,tractor agrícola tem emprego exclusivo/predominante.','eligible',100);
}return stop('Utilização do gasóleo por indicar.');
case 'travel':return stop('Transportes/viagens do sujeito passivo e pessoal excluídos20(1)(c).','no_right',0);
case 'lodging_food':return f.commercialTraveller==='yes'?stop('Excepção20(3)(b):viajante comercial por conta própria,no quadro da actividade profissional.','eligible',100):f.commercialTraveller==='no'?stop('Alojamento/alimentação excluídos20(1)(d),sem excepção20(3)(b).','no_right',0):stop('Âmbito de viajante comercial por conta própria por esclarecer.');
case 'telephone':return f.ownLandline==='yes'?stop('Telefone fixo em nome do sujeito passivo:excepção expressa20(1)(e).','eligible',100):f.ownLandline==='no'?stop('Comunicações telefónicas fora da excepção20(1)(e).','no_right',0):stop('Telefone fixo e titularidade por esclarecer.');
case 'luxury':return stop('Despesa de divertimento/luxo20(1)(f),segundo natureza/montante e exploração normal declarados.','no_right',0);
case 'beverages':
if(bc.facts.beverageSpecial==='no')return stop('Bebidas/tabaco/recepção excluídos20(1)(d),fora do fundamento especial13(2).','no_right',0);
if(bc.facts.beverageSpecial==='yes'&&bc.beveragePath==='deduction'&&bc.facts.onlyDeduction==='yes'&&right.reason.startsWith('Via de dedução13(2)'))return stop(right.reason+' IVA suportado identificado neste documento;mesma parcela não segue restituiçãoL3.','eligible',100);
return stop('Via especial13(2) por delimitar:dedução ou restituiçãoL3,sem dupla contagem;tabaco/recepção não herdam a excepção de bebidas.');
}
return stop('Tratamento não implementado;rascunho preservado.');
}
@@ -6,6 +6,11 @@ export type AcquisitionRightLink={version:1;scopeId:string;operationId:string;fi
export type AcquisitionEligibility={status:'eligible'|'no_right'|'indeterminate';reason:string;source:string;operationId?:string;operationPeriod?:string;operationTreatment?:string;operationDescription?:string;operationArticle?:string;scopeId?:string};
export function validAcquisitionRightLink(value:unknown):value is AcquisitionRightLink{if(!value||typeof value!=='object'||Array.isArray(value))return false;const v=value as AcquisitionRightLink;return v.version===1&&['scopeId','operationId','fingerprint','allocationRef'].every(k=>typeof v[k as keyof AcquisitionRightLink]==='string')&&['','yes','no'].includes(v.scopeConfirmed)&&['','yes','no'].includes(v.noExcludedPayments);}
function fingerprint(row:SalesOperation,result:SalesLineResult|undefined){return JSON.stringify({version:acquisitionRightVersion,input:row,classification:result?{article:result.article,source:result.source,status:result.status}:null});}
export function linkedBeverageDeductionRight(draft:Record<string,unknown>,link:AcquisitionRightLink,notCoastalFishing:boolean):AcquisitionEligibility{
const base=linkedOperationRight(draft,link),b=salesBook(draft),row=b.rows.find(r=>r.id===link.operationId),line=b.result?.rows.find(r=>r.id===link.operationId),rule=operationTreatments.find(t=>t.id===row?.treatment);
if(link.scopeId!==acquisitionScope(draft)||!salesCurrent(draft,b)||!row||!line||!rule||link.fingerprint!==fingerprint(row,line)||row.supplyCategory!=='beverages'||!['onboard_seagoing_ship','onboard_international_aircraft','onboard_rescue_coastal_ship'].includes(row.treatment)||!row.description.trim()||!row.establishment.trim()||!row.evidence.trim()||rule.conditions.some((_,i)=>!row.confirmed.includes(i))||!/^2026-(0[1-9]|1[0-2])$/.test(row.period)||row.sourceCurrency!=='MZN'||!/^\d{1,15}(\.\d{1,6})?$/.test(row.amount)||!['net','included'].includes(row.basis)||row.treatment==='onboard_rescue_coastal_ship'&&!notCoastalFishing)return {...base,status:'indeterminate',reason:'Correspondência própria13(2) por demonstrar em origemL1 actual:c/d/h,bebidas,condições e documento;pesca costeira conserva exclusão de provisões.'};
return {...base,status:'eligible',reason:'Via de dedução13(2) de bebidas em13(1)(c/d/h),nas condições demonstradas;20(1)(d) não se aplica para este efeito. Não concede isenção directa nem restituição.',source:base.source+';CIVA13(2–3),Lei13/2016 PDF12/p1426(270)'};
}
export function acquisitionOperationChoices(draft:Record<string,unknown>){const book=salesBook(draft);return book.rows.map(row=>({id:row.id,label:`${row.description||row.id} — ${row.period} — ${operationTreatments.find(t=>t.id===row.treatment)?.label??row.treatment}`,row,current:salesCurrent(draft,book)}));}
export function createAcquisitionRightLink(draft:Record<string,unknown>,operationId:string):AcquisitionRightLink{const b=salesBook(draft),row=b.rows.find(r=>r.id===operationId);if(!row)throw Error('Operação de origem não encontrada nesta oportunidade;dados preservados.');return{version:1,scopeId:acquisitionScope(draft),operationId,fingerprint:fingerprint(row,b.result?.rows.find(r=>r.id===operationId)),scopeConfirmed:'',allocationRef:'',noExcludedPayments:''};}
export function linkedOperationRight(draft:Record<string,unknown>,link:AcquisitionRightLink):AcquisitionEligibility{
+24 -13
View File
@@ -2,6 +2,7 @@ import { modeloAPurchaseReferences } from './iva-modelo-a.ts';
import { monthlyIvaInput, monthlyIvaKey, readMonthlyIvaRecord, readMonthlyIvaSettings } from './monthly-iva.ts';
import { restoreBusinessPlanSnapshot } from './business-financial-plan.ts';
import { salesBook, salesCurrent as salesOperationsCurrent } from './iva-operations.ts';
import {evaluateAcquisitionBC,validAcquisitionBC,acquisitionBCSource,acquisitionBCSourceKey,acquisitionBCDetails,expenseOptions,type AcquisitionBC} from './iva-acquisition-bc.ts';
import { linkedOperationRight, validAcquisitionRightLink, acquisitionRightVersion, acquisitionRightSource, type AcquisitionRightLink, type AcquisitionEligibility } from './iva-acquisition-right.ts';
import { acquisitionTreatmentIssue, acquisitionContinuityVersion, formerExplorationSource, type AcquisitionTreatment } from './iva-acquisition-continuity.ts';
import { addFractions, decimalFraction, fractionLabel, fractionPresentation, positiveFraction, subtractFractions, type FiscalFraction } from './fiscal-rational.ts';
@@ -11,18 +12,19 @@ export const acquisitionBasis = 'CIVA arts.18(1)(a),18(2) (Lei10/2025),18(3–5)
export const acquisitionSources = 'Modelo A DOCX 62c198e9 / v1 63d45fa8; VAT Return.pdf SHA256 9c0b99a810c7db53eb75d63c22ee22fb86a50ba57bd5509041ed80ee10f75741, p.1; CIVA republicado Lei13/2016, PDF14–16; Lei22/2022, art.20; Lei10/2025, arts.18/19, PDF13.';
type Choice = '' | 'yes' | 'no';
export type Acquisition = {
id: string; field: '08'|'09'|'10'; period: string; origin: 'projected'|'effective';
id: string; field: '08'|'09'|'10'|'11'; period: string; origin: 'projected'|'effective';
sourceMonth: number|null; sourcePeriod?:string; sourceField?:'inventory'|'asset'|'operating'; sourceAmount: string|null; sourceCurrency: string; sourceBasis: ''|'net'|'gross';
net: string; vat: string; gross: string; documentType: ''|'invoice'|'equivalent'; documentRef: string; documentDate: string; documentLine:string;
net: string; vat: string; gross: string; documentType: ''|'invoice'|'equivalent'|'customs'; documentRef: string; documentDate: string; documentLine:string;
supplier: string; supplierRegistered: Choice; legalDocument: Choice; held: Choice; domestic: Choice;
allocation: ''|'taxable_only'|'mixed'|'no_right'; exclusion: ''|'none'|'excluded'|'special';
integrity: Choice; description: string;
acquisitionTreatment?: AcquisitionTreatment;
deductionLink?: AcquisitionRightLink;
bc?:AcquisitionBC;
acquisitionTreatmentOrigin?: {id:'former_exploration_exemption';source:string};
};
export type AcquisitionResult = {id:string; net:string|null; vat:string|null; gross:string|null; deductible:string|null; reason:string; eligibility?:AcquisitionEligibility};
type PeriodInput = {period:string; complete:boolean; absent:string[]};
export type AcquisitionResult = {id:string; net:string|null; vat:string|null; gross:string|null; deductible:string|null; reason:string; eligibility?:AcquisitionEligibility;deductibleExact?:FiscalFraction;nonDeductibleExact?:FiscalFraction};
type PeriodInput = {period:string; complete:boolean; absent:string[];importsComplete?:boolean};
export type AcquisitionBook = {currency:string; rightVersion?:string; taxpayerRegistered?:Choice; rows:Acquisition[]; periods:PeriodInput[]; result?:AcquisitionCalculation;history?:AcquisitionCalculation[]};
export type AcquisitionCalculation = {version:string; revision:number; calculatedAt:string; opportunity:string; location:string; currency:string; sourceKey:string; basis:string; sources:string;
declarations?:{taxpayerRegistered:Choice;periods:PeriodInput[]}; rows:AcquisitionResult[]; inputs:Acquisition[]; periods:Array<{period:string; fields:Record<string,string|null>; exactFields?:Record<string,FiscalFraction|null>; reasons:string[]}>};
@@ -41,6 +43,7 @@ export function readAcquisitionStore(raw:unknown):Store {
const value=JSON.parse(String(raw));
if (value?.version!==1 || !value.books || typeof value.books!=='object' || Array.isArray(value.books)) throw new Error('Compras fiscais guardadas não recuperáveis; dados preservados.');
for(const book of Object.values(value.books) as AcquisitionBook[]) {
if(book?.rows?.some(row=>row.bc!==undefined&&!validAcquisitionBC(row.bc))||book?.periods?.some(p=>p.importsComplete!==undefined&&typeof p.importsComplete!=='boolean'))throw new Error('CondiçõesB/C ou cobertura de importações inválidas;originais preservados.');
if(book?.rightVersion!==undefined&&book.rightVersion!==acquisitionRightVersion)throw new Error('Versão do direito da aquisição não recuperável; original preservado.');
if(book?.rows?.some(row=>row.deductionLink!==undefined&&!validAcquisitionRightLink(row.deductionLink)))throw new Error('Vínculo da aquisição não recuperável; original preservado.');
if(book?.history!==undefined&&!Array.isArray(book.history))throw new Error('Histórico de aquisições não recuperável; originais preservados, sem substituição.');
@@ -114,6 +117,7 @@ function evaluateAcquisitionBase(row:Acquisition,currency:string,taxpayerRegiste
}
export function evaluateAcquisition(row:Acquisition,currency:string,taxpayerRegistered:Choice='',draft?:Record<string,unknown>):AcquisitionResult {
const result=evaluateAcquisitionBase(row,currency,taxpayerRegistered);
if(row.bc)return evaluateAcquisitionBC(row,currency,taxpayerRegistered,draft,result);
if(!row.deductionLink)return result;
const right=draft?linkedOperationRight(draft,row.deductionLink):{status:'indeterminate' as const,reason:'Contexto da operação em falta.',source:acquisitionRightSource};
const finish=(status:AcquisitionEligibility['status'],reason:string,deductible:string|null=null):AcquisitionResult=>({...result,deductible,reason,eligibility:{...right,status,reason,source:right.source+'; '+acquisitionBasis}});
@@ -133,7 +137,7 @@ function sourceKey(draft:Record<string,unknown>,book:AcquisitionBook):string {
// Change detection only, never an authorization or authenticity token.
let hash=14695981039346656037n;for(let i=0;i<raw.length;i++)hash=BigInt.asUintN(64,(hash^BigInt(raw.charCodeAt(i)))*1099511628211n);
const operations=(readAcquisitionStore(draft.modelo_a_snapshot) as Store & {salesOperations?:Record<string,unknown>}).salesOperations?.[acquisitionScope(draft)];
return `${raw.length}:${hash.toString(16)}${operations?`:${JSON.stringify(operations)}`:''}${book.rows.some(r=>r.acquisitionTreatment||r.acquisitionTreatmentOrigin)?`:${acquisitionContinuityVersion}`:''}${book.rightVersion||book.rows.some(r=>r.deductionLink)?`:${acquisitionRightVersion}`:''}`;
return `${raw.length}:${hash.toString(16)}${operations?`:${JSON.stringify(operations)}`:''}${book.rows.some(r=>r.acquisitionTreatment||r.acquisitionTreatmentOrigin)?`:${acquisitionContinuityVersion}`:''}${book.rightVersion||book.rows.some(r=>r.deductionLink)?`:${acquisitionRightVersion}`:''}${book.rows.some(r=>r.bc)?`:${acquisitionBCSourceKey(draft,book.rows)}`:''}`;
}
function location(draft:Record<string,unknown>):string {
const labels=draft.selection_labels as Record<string,unknown>|undefined;
@@ -147,14 +151,14 @@ export function calculateAcquisitions(draft:Record<string,unknown>,book:Acquisit
if(new Set(book.rows.map(r=>r.id)).size!==book.rows.length) throw new Error('Existem compras com identidade repetida.');
const documents=book.rows.filter(r=>r.documentRef.trim() && r.supplier.trim()).map(r=>JSON.stringify([r.origin,r.supplier.trim().toLowerCase(),r.documentRef.trim().toLowerCase(),r.documentDate,r.documentLine?.trim()??'']));
if(new Set(documents).size!==documents.length) throw new Error('Documento/linha repetido: discrimine linhas distintas ou remova a duplicação antes de deduzir.');
if(book.rows.some(r=>!['08','09','10'].includes(r.field) || !dates.includes(r.period))) throw new Error('Classifique cada aquisição num campo08–10 e num mês incluído na cobertura declarada das aquisições.');
if(book.rows.some(r=>!['08','09','10','11'].includes(r.field) || r.field==='11'&&!r.bc || !dates.includes(r.period))) throw new Error('Classifique cada aquisição num campo08–10 ou importaçãoB no campo11 e num mês incluído na cobertura declarada.');
const references=acquisitionPlanReferences(draft);
for(const row of book.rows.filter(r=>r.sourceMonth!==null)) {
const current=references.find(p=>p.month===row.sourceMonth && p.kind===(row.sourceField??'inventory'));
if(!current || current.amount!==row.sourceAmount || (current.kind!=='asset' && row.sourcePeriod && current.period!==row.sourcePeriod)) throw new Error('As compras ou orçamentos do plano mudaram. Resolva a divergência de origem antes de calcular; histórico preservado.');
}
if(book.rightVersion||book.rows.some(r=>r.deductionLink)){
const identities=book.rows.filter(r=>r.documentRef.trim()&&r.supplier.trim()).map(r=>JSON.stringify([r.supplier.trim().toLowerCase(),r.documentRef.trim().toLowerCase(),r.documentDate,r.documentLine.trim()]));
if(book.rightVersion||book.rows.some(r=>r.deductionLink||r.bc)){
const identities=book.rows.filter(r=>r.documentRef.trim()&&(r.supplier.trim()||r.field==='11')).map(r=>JSON.stringify([r.field==='11'?'customs':r.supplier.trim().toLowerCase(),r.documentRef.trim().toLowerCase(),r.documentDate,r.documentLine.trim()]));
if(new Set(identities).size!==identities.length)throw new Error('Documento/linha repetido entre projecção e efectivo; sem dupla dedução.');
}
const rows=book.rows.map(row=>{
@@ -191,6 +195,11 @@ export function calculateAcquisitions(draft:Record<string,unknown>,book:Acquisit
reasons.push(`Campo${field}: operações reduzidas/isentas/outras exigem afectar cada aquisição; não reutilizar dedução exclusiva geral como se fosse universal (L2).`);
}
}
if(book.rows.some(r=>r.bc)||input?.importsComplete!==undefined){
const imported=book.rows.filter(r=>r.period===period&&r.field==='11');
if(imported.length||input?.importsComplete)fields['11']=input?.importsComplete?sum(imported.map(r=>rows.find(x=>x.id===r.id)!.deductible)):null;
if(imported.length&&!input?.importsComplete)reasons.push('Campo11:confirme a cobertura de todas as importações do período,distinta das compras08–10.');
}
if(!input?.complete) reasons.push('Aquisições08–10: confirme que todas as compras do período foram incluídas, mesmo quando não existam.');
for(const row of book.rows.filter(r=>r.period===period)) {const r=rows.find(x=>x.id===row.id)!;if(r.deductible===null) reasons.push(`${row.description||row.id}: ${r.reason}`);}
for(const field of excluded) if(fields[field]===null) reasons.push(`Campo${field}: fora deste lote, sem declaração expressa de inexistência de operações.`);
@@ -201,9 +210,10 @@ export function calculateAcquisitions(draft:Record<string,unknown>,book:Acquisit
fields['17']=state===null||taxpayer===null?null:money(state>taxpayer?state-taxpayer:0n);
fields['18']=state===null||taxpayer===null?null:money(taxpayer>state?taxpayer-state:0n);
const classified=salesOperationsCurrent(draft,operations)?operations.result?.periods.find(p=>p.period===period):null;
if(classified?.exactFields) {
if(classified?.exactFields||book.rows.some(r=>r.bc)) {
const exactFields:Record<string,FiscalFraction|null>={};
for(const f of ['01','02','03','04','05','06','07','08','09','10','11','12','13'])exactFields[f]=classified.exactFields[f]??(fields[f]===null?null:decimalFraction(fields[f]!));
for(const f of ['01','02','03','04','05','06','07','08','09','10','11','12','13'])exactFields[f]=classified?.exactFields?.[f]??(fields[f]===null?null:decimalFraction(fields[f]!));
for(const f of ['08','09','10','11'])if(fields[f]!==null){const matching=book.rows.filter(r=>r.period===period&&r.field===f).map(r=>rows.find(v=>v.id===r.id)!);exactFields[f]=addFractions(matching.map(r=>r.deductibleExact??decimalFraction(r.deductible!)));}
for(const [f,parts] of [['14',['01','03','05','06','07']],['15',['08','09','10','11','12']],['16',['02','04','13']]] as const)exactFields[f]=parts.some(k=>exactFields[k]===null)?null:addFractions(parts.map(k=>exactFields[k]!));
exactFields['17']=exactFields['16']&&exactFields['15']?positiveFraction(subtractFractions(exactFields['16'],exactFields['15'])):null;
exactFields['18']=exactFields['16']&&exactFields['15']?positiveFraction(subtractFractions(exactFields['15'],exactFields['16'])):null;
@@ -213,7 +223,7 @@ export function calculateAcquisitions(draft:Record<string,unknown>,book:Acquisit
}
return {period,fields,reasons};
});
return {version:acquisitionVersion,revision:(book.result?.revision??0)+1,calculatedAt:now,opportunity:scope,location:location(draft),currency:book.currency,sourceKey:sourceKey(draft,book),basis:acquisitionBasis,sources:acquisitionSources+(book.rows.some(r=>r.acquisitionTreatment==='former_exploration_exemption'||r.acquisitionTreatmentOrigin)?`; ${formerExplorationSource}`:''),declarations:{taxpayerRegistered:book.taxpayerRegistered??'',periods:structuredClone(book.periods)},rows,inputs:structuredClone(book.rows),periods:calculated};
return {version:acquisitionVersion,revision:(book.result?.revision??0)+1,calculatedAt:now,opportunity:scope,location:location(draft),currency:book.currency,sourceKey:sourceKey(draft,book),basis:book.rows.some(r=>r.bc)?acquisitionBCSource:acquisitionBasis,sources:acquisitionSources+(book.rows.some(r=>r.acquisitionTreatment==='former_exploration_exemption'||r.acquisitionTreatmentOrigin)?`; ${formerExplorationSource}`:''),declarations:{taxpayerRegistered:book.taxpayerRegistered??'',periods:structuredClone(book.periods)},rows,inputs:structuredClone(book.rows),periods:calculated};
}
export function recordAcquisitions(draft:Record<string,unknown>,book:AcquisitionBook,now=new Date().toISOString()):AcquisitionBook {
const result=calculateAcquisitions(draft,book,now);
@@ -228,16 +238,17 @@ export function acquisitionReportRows(draft:Record<string,unknown>):Array<[strin
const out:Array<[string,string,string,string]>=[['IVA mensal','Apuramento parcial Modelo A',acquisitionCurrent(draft,book)?'Versão correspondente às entradas':'Histórico desactualizado; não aplicar à projecção actual',context],['IVA mensal','Fundamento das aquisições',result.basis,result.sources]];
for(const row of result.inputs) {
const r=result.rows.find(x=>x.id===row.id)!;
if(row.bc)out.push(['IVA mensal',`CondiçõesB/C — ${row.id}`,`${row.field==='11'?'Bilhete de despacho de importação;importador '+row.bc.importer+';bens '+row.bc.goodsRef+';IVA '+({due:'devido identificado',exempt:'isento',zero:'zero expresso',unknown:'desconhecido','':'por indicar'}[row.bc.vatBasis])+';fonte '+row.bc.vatRef:'Despesa '+(expenseOptions.find(([id])=>id===row.bc!.expense)?.[1]??'por identificar')+';IVA suportado17-A:'+({yes:'Sim',no:'Não','':'Por indicar'}[row.bc.reducedSupply])};dedutível exacto:${r.deductibleExact?fractionLabel(r.deductibleExact):'Indeterminado'};não dedutível:${r.nonDeductibleExact?fractionLabel(r.nonDeductibleExact):'Indeterminado'}`,`${row.bc.evidence};${acquisitionBCDetails(row.bc)};origem${row.bc.source?'registo de importaçãoL1 '+row.bc.source.id:'introdução directa'};${acquisitionBCSource}`]);
if(r.eligibility)out.push(['IVA mensal',`Direito da aquisição ${row.id}`,`${{eligible:'Elegível',no_right:'Sem direito',indeterminate:'Indeterminado'}[r.eligibility.status]}: ${r.eligibility.reason}`,`${r.eligibility.source}; operação ${r.eligibility.operationId??'por identificar'}; ${r.eligibility.operationPeriod??''}; ${r.eligibility.operationArticle??''}; afectação ${row.deductionLink?.allocationRef??''}`]);
const source=row.sourceMonth===null?'Introduzido pelo utilizador':`Plano (${row.sourceField==='asset'?'orçamento de activos':row.sourceField==='operating'?'orçamento operacional':'compras/encomendas'}), mês ${row.sourceMonth}, ${row.sourcePeriod??'período de origem não indicado'}, ${row.sourceAmount??'valor em falta'} ${row.sourceCurrency}; ${row.sourceBasis==='net'?'sem IVA':row.sourceBasis==='gross'?'com IVA':'base por classificar'}`;
const allocation={taxable_only:row.deductionLink?'exclusiva à operação ligada':'exclusiva a operações tributadas',mixed:'mista',no_right:'sem direito à dedução','':'por indicar'}[row.allocation];
out.push(['IVA mensal',`Aquisição ${row.id} — campo${row.field} — ${row.period}`,`${row.description}; líquido ${r.net??'Indeterminado'}; IVA ${r.vat??'Indeterminado'}; bruto ${r.gross??'Indeterminado'}; dedutível ${r.deductible??'Indeterminado'}`,`${context}; ${row.origin==='projected'?'Projecção':'Dados efectivos declarados'}; ${source}; ${row.documentType==='invoice'?'Factura':row.documentType==='equivalent'?'Documento equivalente':'Documento por indicar'} ${row.documentRef}, ${row.documentDate}, linha ${row.documentLine||'única'}; fornecedor ${row.supplier}; afectação ${allocation}; ${r.reason}`]);
out.push(['IVA mensal',`Aquisição ${row.id} — campo${row.field} — ${row.period}`,`${row.description}; líquido ${r.net??'Indeterminado'}; IVA ${r.vat??'Indeterminado'}; bruto ${r.gross??'Indeterminado'}; dedutível ${r.deductible??'Indeterminado'}`,`${context}; ${row.origin==='projected'?'Projecção':'Dados efectivos declarados'}; ${source}; ${row.documentType==='invoice'?'Factura':row.documentType==='equivalent'?'Documento equivalente':row.documentType==='customs'?'Bilhete de despacho de importação':'Documento por indicar'} ${row.documentRef}, ${row.documentDate}, linha ${row.documentLine||'única'}; fornecedor ${row.supplier}; afectação ${allocation}; ${r.reason}`]);
if(row.acquisitionTreatment==='former_exploration_exemption'||row.acquisitionTreatmentOrigin)out.push(['IVA mensal',`Continuidade14(2)(c) — ${row.id}`,row.acquisitionTreatment==='former_exploration_exemption'?'Antiga isenção eliminada; dedução indeterminada por esta referência':'Continuidade normal expressamente escolhida; montantes e documento conservados; condições de dedução da aquisição, sem aplicar antiga isenção',`${context}; ${formerExplorationSource}; origem conservada ${row.acquisitionTreatmentOrigin?.id??'former_exploration_exemption'}`]);
}
for(const period of result.periods) {
const entries=Object.entries(period.fields).sort(([a],[b])=>Number(a)-Number(b));
const declaration=result.declarations?.periods.find(p=>p.period===period.period);
const declarationOrigin=declaration?`Aquisições08–10 ${declaration.complete?'declaradas completas':'por completar'}; ausência de operações expressamente declarada nos campos ${declaration.absent.join(', ')||'nenhum'}.`:'Declarações detalhadas não recuperáveis nesta versão histórica.';
const declarationOrigin=declaration?`Aquisições08–10 ${declaration.complete?'declaradas completas':'por completar'};${declaration.importsComplete!==undefined?`importações11 ${declaration.importsComplete?'declaradas completas':'por completar'};`:''} ausência de operações expressamente declarada nos campos ${declaration.absent.join(', ')||'nenhum'}.`:'Declarações detalhadas não recuperáveis nesta versão histórica.';
out.push(['IVA mensal',`Modelo A — ${period.period} — parcelas01–13`,entries.filter(([field])=>Number(field)<=13).map(([field,value])=>`${field}: ${value??'Indeterminado'}`).join('; '),`${context}; ${declarationOrigin}`]);
out.push(['IVA mensal',`Modelo A — ${period.period} — totais14–18`,entries.filter(([field])=>Number(field)>=14).map(([field,value])=>`${field}: ${value??'Indeterminado'}`).join('; '),context]);
if(period.exactFields)out.push(['IVA mensal',`Modelo A — ${period.period} — precisão exacta`,Object.entries(period.exactFields).map(([f,v])=>`${f}: ${v?fractionLabel(v):'Indeterminado'}`).join('; '),'Fracções em MZN; apresentação decimal técnica, sem regra declarativa presumida.']);
@@ -0,0 +1,42 @@
<?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 IvaAcquisitionBCDraftTest extends TestCase
{
use DatabaseTransactions;
public function test_customs_exclusions_recover_with_sources_totals_history_and_preserved_A(): 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-l2-bc-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']);
$store = json_decode($recovered['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR);
$book = $store['books']['opportunity:369'];
$this->assertCount(4, $book['rows']);
$this->assertSame(['eligible','eligible','no_right','eligible'], array_column(array_column($book['result']['rows'], 'eligibility'), 'status'));
$this->assertSame('40.00', $book['result']['periods'][0]['fields']['15']);
$this->assertCount(1, $book['history']);
$this->assertSame('16.00', $book['result']['periods'][0]['fields']['11']);
$this->assertSame('120.00', $book['result']['periods'][0]['fields']['17']);
$this->assertSame('customs', $book['rows'][0]['documentType']);
$this->assertSame('yes', $book['rows'][0]['bc']['belongs']);
$frozen = json_decode(file_get_contents(base_path('tests/frontend/fixtures/iva-l2bc-preserved.json')), true, flags: JSON_THROW_ON_ERROR);
foreach (json_decode($frozen['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR) as $key => $map) {
if ($key === 'version') continue;
foreach ($map as $context => $original) $this->assertSame($original, $store[$key][$context]);
}
file_put_contents(base_path('outputs/iva-l2-bc-20261025/recovered.json'), json_encode($recovered, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,68 @@
import test from 'node:test';import assert from 'node:assert/strict';import fs from 'node:fs';
import {newSalesOperation,recordSales,saveSalesBook,operationTreatments,salesBook} from '../../resources/js/lib/iva-operations.ts';
import {newAcquisition,evaluateAcquisition,recordAcquisitions,saveAcquisitionBook,acquisitionBook,acquisitionCurrent,acquisitionReportRows,readAcquisitionStore} from '../../resources/js/lib/iva-acquisitions.ts';
import {createAcquisitionRightLink} from '../../resources/js/lib/iva-acquisition-right.ts';
import {newAcquisitionBC,reuseAcquisitionImport,acquisitionImportSources} from '../../resources/js/lib/iva-acquisition-bc.ts';
import {newImportExemption,saveImportExemptionBook} from '../../resources/js/lib/iva-import-exemptions.ts';
const frozen=JSON.parse(fs.readFileSync(new URL('./fixtures/iva-l2bc-preserved.json',import.meta.url),'utf8'));
export function bcFixture(){
const draft={...structuredClone(frozen),context_opportunity:'opportunity:369',project_title:'SYNTHETIC QA — L2-B/C importações e exclusões'};
const sale={...newSalesOperation('sale','2026-12','MZN'),description:'SYNTHETIC QA venda geral',establishment:'Manica',amount:'1000',basis:'net',treatment:'general',confirmed:operationTreatments.find(t=>t.id==='general').conditions.map((_,i)=>i),evidence:'SYNTHETIC QA condições'};
draft.modelo_a_snapshot=saveSalesBook(draft,recordSales(draft,{currency:'MZN',rows:[sale],periods:[{period:'2026-12',complete:true}]}));
const link={...createAcquisitionRightLink(draft,'sale'),scopeConfirmed:'yes',allocationRef:'SYNTHETIC QA afectação exclusiva',noExcludedPayments:'yes'};
const row=(id,patch={},bc={})=>({...newAcquisition(id,'2026-12','MZN'),net:'100',vat:'16',gross:'116',description:'SYNTHETIC QA '+id,documentType:'invoice',documentRef:'SYNTHETIC QA '+id,documentDate:'2026-12-10',supplier:'SYNTHETIC QA fornecedor',supplierRegistered:'yes',legalDocument:'yes',held:'yes',domestic:'yes',allocation:'taxable_only',exclusion:'none',integrity:'yes',deductionLink:structuredClone(link),bc:{...newAcquisitionBC(),expense:'ordinary',reducedSupply:'no',evidence:'SYNTHETIC QA natureza e âmbito',...bc},...patch});
const imported=row('import-due',{field:'11',domestic:'no',documentType:'customs',supplier:'',supplierRegistered:''},{importer:'SYNTHETIC QA importador',belongs:'yes',ownImportVat:'yes',vatBasis:'due',goodsRef:'SYNTHETIC QA bens/linha1',vatRef:'SYNTHETIC QA bilhete de despacho',facts:{exigibleInPeriod:'yes'}});
const book={currency:'MZN',rightVersion:'acquisition-operation-right/1',taxpayerRegistered:'yes',rows:[imported,row('diesel',{}, {expense:'fuel',fuelKind:'diesel',dieselUse:'other',facts:{resale:'no'}}),row('travel',{}, {expense:'travel'}),row('fixed-phone',{}, {expense:'telephone',facts:{ownLandline:'yes'}})],periods:[{period:'2026-12',complete:true,importsComplete:true,absent:['12','13']}]};
return{draft,book,row,imported};
}
test('B customs document has own conditions, unknown is not exempt/zero and supplier domestic registration is irrelevant',()=>{
const{draft,imported}=bcFixture();const run=(patch={},bc={})=>evaluateAcquisition({...imported,...patch,bc:{...imported.bc,...bc}},'MZN','yes',draft);
assert.equal(run().deductible,'16.00');assert.equal(run().eligibility.status,'eligible');
assert.equal(run({net:'',gross:''},{reducedSupply:''}).deductible,'16.00');assert.equal(run({vat:''}).deductible,null);
assert.equal(run({acquisitionTreatment:'former_exploration_exemption'}).deductible,null);assert.equal(run({acquisitionTreatment:'unknown'}).deductible,null);
for(const bc of [{belongs:''},{importer:''},{goodsRef:''},{vatRef:''},{ownImportVat:''},{vatBasis:'unknown'},{facts:{exigibleInPeriod:''}},{facts:{exigibleInPeriod:'no'}}])assert.equal(run({},bc).deductible,null,JSON.stringify(bc));
assert.equal(run({documentType:'invoice'}).deductible,null);assert.equal(run({held:''}).deductible,null);assert.equal(run({documentDate:'2026-11-10'}).deductible,null);
assert.equal(run({vat:'0',gross:'100'},{vatBasis:'exempt'}).deductible,'0.00');assert.equal(run({vat:'0',gross:'100'},{vatBasis:'zero'}).deductible,'0.00');assert.equal(run({vat:'',gross:''},{vatBasis:'exempt'}).deductible,null);assert.equal(run({},{vatBasis:'exempt'}).deductible,null);
});
test('C exclusions/exceptions retain their own scope and unknown facts never imply no exclusion',()=>{
const{draft,row}=bcFixture();const run=(bc,patch={})=>evaluateAcquisition(row('case',patch,bc),'MZN','yes',draft);
const cases=[
[{expense:'vehicle',facts:{tourism:'yes',saleOrExploit:'no'}},'0.00'],[{expense:'vehicle',facts:{tourism:'yes',saleOrExploit:'yes'}},'16.00'],[{expense:'vehicle',facts:{tourism:'no'}},'16.00'],[{expense:'vehicle',facts:{}},null],
[{expense:'fuel',facts:{resale:'no'},fuelKind:'other'},'0.00'],[{expense:'fuel',facts:{resale:'yes'}},'16.00'],[{expense:'fuel',facts:{resale:'no'},fuelKind:'diesel',dieselUse:'other'},'8.00'],
[{expense:'travel'},'0.00'],[{expense:'lodging_food',facts:{commercialTraveller:'yes'}},'16.00'],[{expense:'lodging_food',facts:{commercialTraveller:'no'}},'0.00'],[{expense:'lodging_food',facts:{}},null],
[{expense:'beverages',facts:{beverageSpecial:'no'}},'0.00'],[{expense:'beverages',facts:{}},null],
[{expense:'telephone',facts:{ownLandline:'yes'}},'16.00'],[{expense:'telephone',facts:{ownLandline:'no'}},'0.00'],[{expense:'telephone',facts:{}},null],
[{expense:'luxury'},'0.00'],[{expense:'ordinary',reducedSupply:'yes'},'0.00'],[{expense:'ordinary',reducedSupply:''},null],
];for(const[bc,expected]of cases){const r=run(bc);assert.equal(r.deductible,expected,JSON.stringify(bc));assert.ok(r.eligibility.reason);assert.match(r.eligibility.source,/20\(3\)/);}
for(const dieselUse of ['heavy_passengers','public_passengers','machine','agriculture']){assert.equal(run({expense:'fuel',fuelKind:'diesel',dieselUse,facts:{resale:'no',dieselScope:'yes'}}).deductible,'16.00');assert.equal(run({expense:'fuel',fuelKind:'diesel',dieselUse,facts:{resale:'no',dieselScope:''}}).deductible,null);}
for(const patch of [{held:''},{legalDocument:''},{documentDate:'2026-11-01'},{allocation:'mixed'},{integrity:''}])assert.equal(run({expense:'telephone',facts:{ownLandline:'yes'}},patch).deductible,null);
});
test('F includes import11, excludes denied purchases and preserves exact diesel fractions across totals',()=>{
const{draft,book}=bcFixture();const result=recordAcquisitions(draft,book).result;assert.deepEqual(['09','11','14','15','16','17','18'].map(k=>result.periods[0].fields[k]),['24.00','16.00','1000.00','40.00','160.00','120.00','0.00']);
const partial=structuredClone(book);partial.periods[0].importsComplete=false;assert.equal(recordAcquisitions(draft,partial).result.periods[0].fields['11'],null);assert.equal(recordAcquisitions(draft,partial).result.periods[0].fields['15'],null);
partial.periods[0].absent.push('11');assert.throws(()=>recordAcquisitions(draft,partial),/importações/);
const precision=structuredClone(book);precision.rows=[0,1].map(i=>({...book.rows[1],id:'exact-'+i,documentLine:String(i),net:'1',vat:'0.000001',gross:'1.000001'}));const exact=recordAcquisitions(draft,precision).result;assert.deepEqual(exact.rows[0].deductibleExact,{numerator:'1',denominator:'2000000'});assert.equal(exact.periods[0].fields['09'],'0.000001');assert.deepEqual(exact.periods[0].exactFields['15'],{numerator:'1',denominator:'1000000'});
const duplicate=structuredClone(book);duplicate.rows.push({...book.rows[0],id:'duplicate',origin:'effective'});assert.throws(()=>recordAcquisitions(draft,duplicate),/dupla dedução/);
});
test('import reuse fills only missing data, detects source drift and preserves A plus every prior map and frozen history',()=>{
let{draft,book,imported}=bcFixture();const origin={...newImportExemption('source','MZN'),description:'SYNTHETIC QA origemL1',period:'2026-12',importer:'SYNTHETIC QA origem importador',documentRef:'ORIGINAL',documentDate:'2026-12-12'};draft.modelo_a_snapshot=saveImportExemptionBook(draft,{rows:[origin]});assert.equal(acquisitionImportSources(draft).length,1);
const reused=reuseAcquisitionImport(draft,imported,'importExemptions','source');assert.equal(reused.documentRef,imported.documentRef);assert.equal(reused.documentDate,imported.documentDate);assert.equal(reused.bc.importer,imported.bc.importer);assert.equal(reused.vat,'16');book.rows[0]=reused;
book=recordAcquisitions(draft,book);draft.modelo_a_snapshot=saveAcquisitionBook(draft,book);const first=structuredClone(book.result);book=recordAcquisitions(draft,book);draft.modelo_a_snapshot=saveAcquisitionBook(draft,book);const recovered=JSON.parse(JSON.stringify(draft));assert.ok(acquisitionCurrent(recovered,acquisitionBook(recovered)));assert.deepEqual(book.history,[first]);
const before=JSON.parse(frozen.modelo_a_snapshot),after=JSON.parse(draft.modelo_a_snapshot);for(const k of Object.keys(before).filter(k=>k!=='version'))for(const[id,value]of Object.entries(before[k]))assert.deepEqual(after[k][id],value,k+id);
assert.ok(acquisitionCurrent({...recovered,context_opportunity:'opportunity:368'},before.books['opportunity:368']));
const report=JSON.stringify(acquisitionReportRows(recovered));for(const v of ['CondiçõesB/C','40.00','120.00','importador','20(3)','8/1'])assert.ok(report.includes(v),v);
draft.modelo_a_snapshot=saveImportExemptionBook(draft,{rows:[{...origin,description:'Editado'}]});assert.equal(acquisitionCurrent(draft,book),false);assert.equal(evaluateAcquisition(book.rows[0],'MZN','yes',draft).deductible,null);assert.deepEqual(book.history[0],first);
draft.modelo_a_snapshot=saveImportExemptionBook(draft,{rows:[{...origin,treatment:'service_in_customs_value'}]});const service=reuseAcquisitionImport(draft,book.rows[0],'importExemptions','source');assert.equal(evaluateAcquisition(service,'MZN','yes',draft).deductible,null);assert.match(evaluateAcquisition(service,'MZN','yes',draft).reason,/não IVA próprio/);
const broken=JSON.parse(recovered.modelo_a_snapshot);broken.books['opportunity:369'].rows[0].bc.facts={bad:'maybe'};assert.throws(()=>readAcquisitionStore(JSON.stringify(broken)),/CondiçõesB\/C/);
if(process.env.IVA_L2BC_CALCULATED)fs.writeFileSync(process.env.IVA_L2BC_CALCULATED,JSON.stringify(recovered));
});
test('beverages13(2) uses current c/d/h source and deduction-only scope without granting direct exemption or refund',()=>{
for(const treatment of ['onboard_seagoing_ship','onboard_international_aircraft','onboard_rescue_coastal_ship','onboard_departing_warship']){
const{draft,row}=bcFixture();const ops=salesBook(draft);const rule=operationTreatments.find(t=>t.id===treatment);ops.rows.push({...newSalesOperation('beverages','2026-12','MZN'),description:'SYNTHETIC QA bebidas de bordo',establishment:'Manica',amount:'100',basis:'net',treatment,supplyCategory:'beverages',confirmed:rule.conditions.map((_,i)=>i),evidence:'SYNTHETIC QA meio,destino,utilização e embarque'});draft.modelo_a_snapshot=saveSalesBook(draft,recordSales(draft,ops));
const r=row('beverages',{}, {expense:'beverages',beveragePath:'deduction',facts:{beverageSpecial:'yes',onlyDeduction:'yes',notCoastalFishing:'yes'}});r.deductionLink={...createAcquisitionRightLink(draft,'beverages'),scopeConfirmed:'yes',allocationRef:'SYNTHETIC QA parcela e operação',noExcludedPayments:'yes'};
assert.equal(evaluateAcquisition(r,'MZN','yes',draft).deductible,treatment==='onboard_departing_warship'?null:'16.00',treatment);
assert.equal(evaluateAcquisition({...r,bc:{...r.bc,beveragePath:'refund'}},'MZN','yes',draft).deductible,null);
assert.equal(evaluateAcquisition({...r,bc:{...r.bc,facts:{...r.bc.facts,onlyDeduction:''}}},'MZN','yes',draft).deductible,null);
if(treatment==='onboard_rescue_coastal_ship')assert.equal(evaluateAcquisition({...r,bc:{...r.bc,facts:{...r.bc.facts,notCoastalFishing:'no'}}},'MZN','yes',draft).deductible,null);
}
});