Implement P131 P132 event timing and conditional P104 draft in QA
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
<?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 IvaCurrencyEventDraftTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function test_currencyevents_recover_privately_preserving_all_delivered_books(): 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('tests/frontend/fixtures/iva-currency-events.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']);
|
||||
$this->assertSame('opportunity:357', $recovered['context_opportunity']);
|
||||
$this->assertSame('Manica', $recovered['selection_labels']['province_id']);
|
||||
$store = json_decode($recovered['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR);
|
||||
$book = $store['currencyEvents']['opportunity:357'];
|
||||
$this->assertCount(15, $book['rows']);
|
||||
$this->assertSame('conditional',$book['result']['rows'][0]['status']);
|
||||
$this->assertSame('64500.00',$book['result']['rows'][0]['scenarioAmount']);
|
||||
$this->assertSame('2026-12-10',$book['result']['rows'][13]['eventDate']);
|
||||
$this->assertSame(['200.00','800.00'],array_column($book['result']['rows'][14]['dueEvents'],'amount'));
|
||||
foreach ($book['result']['rows'] as $row) {
|
||||
$this->assertNull($row['baseAmount']);$this->assertNull($row['vat']);$this->assertNull($row['rate']);
|
||||
}
|
||||
$this->assertSame($book['rows'], $book['result']['inputs']);
|
||||
$this->assertCount(1, $book['history']);
|
||||
$this->assertSame('64500.00',$book['history'][0]['rows'][0]['scenarioAmount']);
|
||||
$frozen = json_decode(file_get_contents(base_path('tests/frontend/fixtures/iva-currency-events-preserved.json')), true, flags: JSON_THROW_ON_ERROR);
|
||||
$prior = json_decode($frozen['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR);
|
||||
$this->assertSame($prior, array_diff_key($store, ['currencyEvents' => true]));
|
||||
$this->assertSame('1250.30', $store['publicAuthorityBases']['opportunity:339']['result']['rows'][1]['baseAmount']);
|
||||
$this->assertNull($store['publicAuthorityBases']['opportunity:339']['result']['rows'][1]['vat']);
|
||||
$this->assertSame('400.00', $store['importExemptions']['opportunity:338']['result']['rows'][1]['importVat']);
|
||||
$this->assertNull($store['books']['opportunity:329']['result']['periods'][0]['fields']['11']);
|
||||
file_put_contents(base_path('outputs/iva-l1-currency-events-20261025/recovered.json'), json_encode($recovered, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,40 @@
|
||||
import test from 'node:test';import assert from 'node:assert/strict';import fs from 'node:fs';
|
||||
import {newCurrencyEvent,currencyEventTreatments,evaluateCurrencyEvent,recordCurrencyEvents,saveCurrencyEventBook,currencyEventBook,currencyEventCurrent,currencyEventReportRows,switchCurrencyEvent} from '../../resources/js/lib/iva-currency-events.ts';
|
||||
const frozen=JSON.parse(fs.readFileSync('tests/frontend/fixtures/iva-currency-events-preserved.json','utf8'));
|
||||
const draft={...frozen,currency:'MZN',context_opportunity:'opportunity:357',project_title:'SYNTHETIC QA — Moeda e eventos / Manica'};
|
||||
function row(t){return{...newCurrencyEvent(t,'MZN'),treatment:t,description:'SYNTHETIC QA '+t,period:'2026-12',origin:'projected',operationRef:'SYNTHETIC QA operação existente',documentRef:'SYNTHETIC QA documento/pressuposto',documentDate:'2026-12-12',scope:'SYNTHETIC QA conjunto/parte/operação/período',evidence:'SYNTHETIC QA pressupostos expressos',eventDate:'2026-12-10',eventRef:'SYNTHETIC QA evento específico/artigo7,excepções próprias conferidas',otherDate:t==='consignment_deadline'?'2026-06-13':t==='contract_effects'?'2026-12-01':'2026-12-12',reviewDate:'2026-12-31',factsConfirmed:'yes',invoiceRequired:'yes',invoiceState:'issued',invoiceDate:'2026-12-12',invoiceRef:'SYNTHETIC QA factura',legalDeadline:'2026-12-17',deadlineRef:'SYNTHETIC QA prazo/disposição/calendário identificados,não regra vigente presumida',paymentState:t==='advance_payment'?'received':'none',paymentDate:'2026-12-09',paymentRef:'SYNTHETIC QA recebimento identificado',amount:'1000.00',paymentAmount:'200.00',sourceCurrency:'USD',fxRate:'64.5',fxDate:'2026-12-10',fxSource:'SYNTHETIC QA taxa/exemplar',fxRuleRef:'SYNTHETIC QA pressuposto da taxa/data,não base fiscal',fxDirection:'mzn_per_unit'};}
|
||||
for(const rule of currencyEventTreatments)test(rule.branch+' '+rule.id+' own event and noVAT/base',()=>{
|
||||
const r=row(rule.id),v=evaluateCurrencyEvent(r);assert.equal(v.status,rule.branch==='P104'?'conditional':'determined');assert.equal(v.baseAmount,null);assert.equal(v.vat,null);assert.equal(v.rate,null);
|
||||
if(rule.branch==='P104')assert.equal(v.scenarioAmount,'64500.00');else{assert.equal(v.dueEvents.at(-1).date,['import_clearance','import_auction'].includes(rule.id)?'2026-12-10':'2026-12-12');assert.equal(v.eventDate,rule.id==='advance_payment'?null:rule.id==='consignment_deadline'?'2026-12-10':'2026-12-10');}
|
||||
for(const f of ['operationRef','documentRef','scope','evidence'])assert.equal(evaluateCurrencyEvent({...r,[f]:''}).status,'indeterminate');assert.equal(evaluateCurrencyEvent({...r,factsConfirmed:''}).status,'indeterminate');assert.equal(evaluateCurrencyEvent({...r,factsConfirmed:'no'}).status,'not_applicable');
|
||||
});
|
||||
test('P131/P132 invoice intime/late/absent,partial payment and no duplicate remainder',()=>{
|
||||
const r=row('goods_available');let v=evaluateCurrencyEvent({...r,paymentState:'received'});assert.deepEqual(v.dueEvents.map(x=>[x.date,x.amount]),[['2026-12-09','200.00'],['2026-12-12','800.00']]);
|
||||
v=evaluateCurrencyEvent({...r,invoiceDate:'2026-12-20'});assert.equal(v.dueEvents[0].date,'2026-12-17');
|
||||
v=evaluateCurrencyEvent({...r,invoiceState:'not_issued'});assert.equal(v.dueEvents[0].date,'2026-12-17');
|
||||
v=evaluateCurrencyEvent({...r,invoiceState:'not_issued',reviewDate:'2026-12-11',paymentState:'received'});assert.equal(v.dueEvents.length,1);assert.equal(v.remainingPending,true);
|
||||
v=evaluateCurrencyEvent({...r,paymentState:'received',paymentDate:'2026-12-19',invoiceDate:'2026-12-20'});assert.deepEqual(v.dueEvents.map(x=>[x.date,x.amount]),[['2026-12-17','1000.00']]);
|
||||
v=evaluateCurrencyEvent({...r,paymentState:'received',paymentAmount:'1000'});assert.equal(v.dueEvents.length,1);assert.equal(v.dueEvents[0].amount,'1000.00');
|
||||
for(const patch of [{paymentState:''},{legalDeadline:''},{deadlineRef:''},{invoiceState:''},{paymentState:'received',paymentAmount:'1001'},{paymentState:'received',amount:''}])assert.equal(evaluateCurrencyEvent({...r,...patch}).status,'indeterminate');
|
||||
assert.equal(evaluateCurrencyEvent({...r,invoiceRequired:'no',amount:''}).dueEvents[0].amount,null);
|
||||
assert.equal(evaluateCurrencyEvent({...r,eventDate:'2026-12-20',reviewDate:'2026-12-11',invoiceState:'not_issued',legalDeadline:'2026-12-28',paymentState:'received'}).dueEvents[0].date,'2026-12-09');
|
||||
});
|
||||
test('own events,consignmentterm,servicecharge,contractexceptions and dates',()=>{
|
||||
assert.equal(evaluateCurrencyEvent({...row('consignment_deadline'),otherDate:''}).status,'indeterminate');
|
||||
assert.equal(evaluateCurrencyEvent({...row('consignment_deadline'),invoiceRequired:'no'}).dueEvents[0].date,'2026-12-10');
|
||||
assert.equal(evaluateCurrencyEvent({...row('service_early_charge'),otherDate:'2026-12-09'}).status,'indeterminate');
|
||||
assert.equal(evaluateCurrencyEvent({...row('contract_effects'),otherDate:'2026-12-11'}).status,'indeterminate');
|
||||
for(const field of ['reviewDate','eventDate','documentDate'])assert.equal(evaluateCurrencyEvent({...row('goods_available'),[field]:'2026-02-30'}).status,'indeterminate');
|
||||
const r=row('own_goods'),sw=switchCurrencyEvent(r,'foreign_scenario');assert.equal(sw.eventDate,r.eventDate);assert.equal(sw.fxSource,r.fxSource);assert.equal(sw.factsConfirmed,'');
|
||||
});
|
||||
test('P104 conditionalconversion direction/exactrounding/missingparams never fiscalbase',()=>{
|
||||
const r=row('foreign_scenario');assert.equal(evaluateCurrencyEvent({...r,fxDirection:'units_per_mzn',fxRate:'2',amount:'100'}).scenarioAmount,'50.00');
|
||||
assert.equal(evaluateCurrencyEvent({...r,amount:'0.000001',fxRate:'1.5'}).scenarioAmount,'0.000002');
|
||||
for(const patch of [{fxRate:'0'},{fxRate:'-1'},{fxSource:''},{fxDate:''},{fxRuleRef:''},{fxDirection:''},{sourceCurrency:'MZN'}])assert.equal(evaluateCurrencyEvent({...r,...patch}).status,'indeterminate');assert.equal(evaluateCurrencyEvent(r).baseAmount,null);
|
||||
});
|
||||
test('snapshot/history/report preserve every delivered book and contextual scope',()=>{
|
||||
const rows=currencyEventTreatments.map(t=>row(t.id)),a=recordCurrencyEvents(draft,{rows}),next={...draft,modelo_a_snapshot:saveCurrencyEventBook(draft,a)},b=recordCurrencyEvents(next,a);next.modelo_a_snapshot=saveCurrencyEventBook(next,b);
|
||||
assert.equal(b.history.length,1);assert.equal(currencyEventCurrent(next,b),true);assert.equal(currencyEventCurrent(next,{...b,rows:rows.slice(1)}),false);assert.equal(currencyEventReportRows(next).length,15);assert.deepEqual(currencyEventBook({...next,context_opportunity:'opportunity:358'}),{rows:[]});
|
||||
const store=JSON.parse(next.modelo_a_snapshot);delete store.currencyEvents;assert.deepEqual(store,JSON.parse(frozen.modelo_a_snapshot));assert.throws(()=>recordCurrencyEvents(next,{rows:[rows[0],rows[0]]}));assert.throws(()=>currencyEventBook({...next,modelo_a_snapshot:JSON.stringify({...store,currencyEvents:[]})}));
|
||||
if(process.env.IVA_CURRENCY_EVENTS_FIXTURE)fs.writeFileSync(process.env.IVA_CURRENCY_EVENTS_FIXTURE,JSON.stringify(next,null,2)+'\n');
|
||||
});
|
||||
Reference in New Issue
Block a user