Files
Investidor-V2/tests/frontend/iva-acquisition-bc.test.mjs
T

69 lines
12 KiB
JavaScript

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,treatment==='onboard_departing_warship'?null:'0.00');
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);
}
});