Prepare grouped L2 A B C with customs deduction exclusions and exact affected totals
This commit is contained in:
@@ -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);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user