50 lines
5.3 KiB
JavaScript
50 lines
5.3 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import { onboardSupplyTreatments, supplyCategories, onboardSupplyIssue } from '../../resources/js/lib/iva-onboard-supplies.ts';
|
|
import { newSalesOperation, evaluateSalesOperation, recordSales, saveSalesBook, salesBook, salesCurrent } from '../../resources/js/lib/iva-operations.ts';
|
|
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
|
|
const frozen=JSON.parse(fs.readFileSync(new URL('./fixtures/iva-g4c-inputs.json',import.meta.url),'utf8'));
|
|
const positive=['provisions','fuel','beverages','cargo_products'];
|
|
const row=(rule,i)=>({...newSalesOperation(`g4d-${i}`,'2026-12','MZN'),description:`SYNTHETIC QA — ${rule.label}`,establishment:'SYNTHETIC QA estabelecimento',amount:'100',basis:i%2?'included':'net',treatment:rule.id,supplyCategory:positive[i],confirmed:rule.conditions.map((_,n)=>n),evidence:'SYNTHETIC QA — documento/pressuposto identifica bens postos a bordo, destino, embarcação/avião e actividade do operador; projecção, sem certificar despacho nem restituição'});
|
|
function fixture(){
|
|
const draft={...structuredClone(frozen),context_opportunity:'opportunity:326',project_title:'SYNTHETIC QA — G4D / Abastecimentos a bordo'};
|
|
const book=recordSales(draft,{currency:'MZN',rows:onboardSupplyTreatments.map(row),periods:[{period:'2026-12',complete:true}]});draft.modelo_a_snapshot=saveSalesBook(draft,book);return{draft,book};
|
|
}
|
|
test('four supply branches require each legal condition and identification; values with/without included VAT use existing motor',()=>{
|
|
assert.equal(onboardSupplyTreatments.length,4);
|
|
for(const[i,rule]of onboardSupplyTreatments.entries()){
|
|
const input=row(rule,i),result=evaluateSalesOperation(input,'MZN');assert.equal(result.field,'05');assert.equal(result.vat,'0.00');assert.equal(result.net,'100.00');assert.equal(result.gross,'100.00');
|
|
for(const missing of input.confirmed)assert.equal(evaluateSalesOperation({...input,confirmed:input.confirmed.filter(n=>n!==missing)},'MZN').vat,null);
|
|
for(const patch of [{amount:''},{evidence:''},{sourceCurrency:'USD'},{period:'2027-01'},{basis:''},{description:''},{establishment:''}])assert.equal(evaluateSalesOperation({...input,...patch},'MZN').vat,null);
|
|
}
|
|
});
|
|
test('article13 categories and exclusions are distinct: beverages do not cause automatic exemption or deduction',()=>{
|
|
for(const[i,rule]of onboardSupplyTreatments.entries())for(const[category]of supplyCategories){
|
|
const result=evaluateSalesOperation({...row(rule,i),supplyCategory:category},'MZN');
|
|
const excluded=i===1&&(category==='provisions'||category==='beverages'),refund=category==='beverages'&&(i===0||i===3);
|
|
if(excluded||refund){assert.equal(result.vat,null);assert.equal(result.net,null);assert.equal(result.field,null);assert.equal(result.rate,null);assert.match(result.reason,excluded?/excluídas/:/dedução ou restituição/);}
|
|
else assert.equal(result.vat,'0.00');
|
|
}
|
|
assert.match(onboardSupplyIssue('onboard_international_aircraft','beverages'),/20\(1\)\(d\)/);
|
|
assert.equal(onboardSupplyIssue('onboard_departing_warship','beverages'),null); // 13(2) lists c/d/h, not e.
|
|
for(const category of [undefined,'',null,'unknown'])for(const rule of onboardSupplyTreatments){const result=evaluateSalesOperation({...row(rule,0),supplyCategory:category},'MZN');assert.equal(result.vat,null);assert.match(result.reason,/Identifique a natureza/);}
|
|
});
|
|
test('changing category preserves price and historical result; unresolved beverage/provisions never turn into zero totals',()=>{
|
|
const{draft,book}=fixture();assert.equal(book.result.periods[0].fields['05'],'400.00');
|
|
for(const[index,category]of [[0,'beverages'],[1,'provisions'],[3,'']]){
|
|
const changed=recordSales(draft,{...book,rows:book.rows.map((r,i)=>i===index?{...r,supplyCategory:category,confirmed:[]}:r)});
|
|
assert.equal(changed.rows[index].amount,'100');assert.equal(changed.rows[index].sourceCurrency,'MZN');assert.equal(changed.rows[index].period,'2026-12');
|
|
assert.equal(changed.result.periods[0].fields['05'],null);assert.deepEqual(changed.history.at(-1),book.result);
|
|
const edited={...draft,modelo_a_snapshot:saveSalesBook(draft,{...book,rows:changed.rows})};assert.equal(salesCurrent(edited,salesBook(edited)),false);
|
|
}
|
|
});
|
|
test('QA snapshot and report retain category/context/source; all17 prior books stay identical/current without recalculation',()=>{
|
|
const{draft,book}=fixture();assert.ok(salesCurrent(draft,book));assert.deepEqual(salesBook(JSON.parse(JSON.stringify(draft))),JSON.parse(JSON.stringify(book)));
|
|
const before=JSON.parse(frozen.modelo_a_snapshot).salesOperations,after=JSON.parse(draft.modelo_a_snapshot).salesOperations;assert.equal(Object.keys(before).length,17);
|
|
for(const[context,original]of Object.entries(before)){assert.deepEqual(after[context],original);assert.ok(salesCurrent({...draft,context_opportunity:context},after[context]));}
|
|
const report=JSON.stringify(createProjectDossierReport(draft,{selectedSections:new Set(['finance']),version:'QA G4D'}));
|
|
for(const token of ['opportunity:326','Manica','2026-12','400.00','13(1)(c)','13(1)(d)','13(1)(e)','13(1)(h)','Natureza do abastecimento','Bebidas como provisões','QA G4D'])assert.ok(report.includes(token),token);
|
|
if(process.env.IVA_G4D_CALCULATED)fs.writeFileSync(process.env.IVA_G4D_CALCULATED,JSON.stringify(draft));
|
|
});
|