29 lines
10 KiB
JavaScript
29 lines
10 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import {irpcBook,newIrpcRow,recordIrpc,saveIrpcBook,reuseIrpcSources,irpcCurrent,irpcReportRows,irpcPreparationHtml} from '../../resources/js/lib/irpc-withholdings.ts';
|
|
import {acquisitionBook,newAcquisition,saveAcquisitionBook} from '../../resources/js/lib/iva-acquisitions.ts';
|
|
const seed=JSON.parse(fs.readFileSync(new URL('./fixtures/ispc-residual-preserved.json',import.meta.url),'utf8'));
|
|
const draft=()=>({...structuredClone(seed),fiscal_component:'irpc'});
|
|
const row=(id='digital')=>({...newIrpcRow(id),counterparty:'SYNTHETIC QA fornecedor não residente',documentRef:'SYNTHETIC-'+id,base:'100000',kind:'digital_services',recipientIrpc:'yes',payerIrpc:'yes',residence:'nonresident',permanentEstablishment:'no',mozambique:'yes',qualificationRef:'SYNTHETIC QA: SaaS utilizado em Moçambique, devedor nacional,sem EP,Lei12/2025 arts5(5/7)/62(5)b',specialRelief:'none',eventsComplete:'yes',events:[{kind:'cost',date:'2026-10-02',reference:'SYNTHETIC custo'},{kind:'payment',date:'2026-11-03',reference:'SYNTHETIC pagamento do rendimento'}],settlement:'none',settlementRef:'SYNTHETIC sem entrega'});
|
|
const book=(r=row())=>({version:1,rows:[r],payments:[]});
|
|
test('positive digital goods/services and e-money have exact 10 percent and first applicable cost event',()=>{for(const kind of ['digital_goods','digital_services','emoney_commission']){const r=recordIrpc(draft(),book({...row(),kind})).result.rows[0];assert.equal(r.status,'calculated');assert.deepEqual(r.tax,{numerator:'10000',denominator:'1'});assert.equal(r.eventDate,'2026-10-02');assert.equal(r.nature,'definitive');assert.equal(r.period,'2026-10');}});
|
|
test('unknown territory, recipient, payer, EP, event completeness, relief never become zero or nonapplicable',()=>{for(const patch of [{mozambique:''},{recipientIrpc:''},{payerIrpc:''},{permanentEstablishment:''},{eventsComplete:''},{specialRelief:''}]){const r=recordIrpc(draft(),book({...row(),...patch})).result.rows[0];assert.equal(r.status,'indeterminate');assert.equal(r.tax,null);}});
|
|
test('resident and income attributable to EP preserve concrete 68d mapping gap; no 10 or20 automatic',()=>{for(const patch of [{residence:'resident'},{permanentEstablishment:'yes',attributable:'yes'}]){const r=recordIrpc(draft(),book({...row(),...patch})).result.rows[0];assert.equal(r.tax,null);assert.match(r.reason,/68/);}});
|
|
test('nonresident with EP and explicitly nonattributable income has definitive positive branch',()=>assert.equal(recordIrpc(draft(),book({...row(),permanentEstablishment:'yes',attributable:'no'})).result.rows[0].status,'calculated'));
|
|
test('ordinary and exceptional income need specific amended rule; no legacy2002 rates',()=>{for(const kind of ['royalty','rent','capital','equipment','services','other']){const r=recordIrpc(draft(),book({...row(),kind})).result.rows[0];assert.equal(r.tax,null);assert.match(r.reason,/20\/2022/);}});
|
|
test('event before2026 is not moved to later payment; impossible dates rejected',()=>{for(const date of ['2025-12-31','2026-02-30']){const r=row();r.events[0].date=date;assert.equal(recordIrpc(draft(),book(r)).result.rows[0].status,'indeterminate');}});
|
|
test('foreign amount and missing base remain indeterminate, explicit zero is distinct',()=>{for(const patch of [{currency:'USD'},{base:''},{base:'-1'}])assert.equal(recordIrpc(draft(),book({...row(),...patch})).result.rows[0].tax,null);assert.deepEqual(recordIrpc(draft(),book({...row(),base:'0'})).result.rows[0].tax,{numerator:'0',denominator:'1'});});
|
|
test('partial remittance is separate from tax and recipient payment cannot settle tax',()=>{const r={...row(),settlement:'complete',allocations:[{paymentId:'p1',amount:'6000'}]};const b={...book(r),payments:[{id:'p1',kind:'remittance',reference:'SYNTHETIC-AT1',date:'2026-10-20',amount:'6000',currency:'MZN'}]};const out=recordIrpc(draft(),b).result.rows[0];assert.deepEqual(out.settled,{numerator:'6000',denominator:'1'});assert.deepEqual(out.outstanding,{numerator:'4000',denominator:'1'});b.payments[0].kind='beneficiary';assert.equal(recordIrpc(draft(),b).result.rows[0].status,'indeterminate');});
|
|
test('received withholding needs matching proof; no yearly credit or buyer debt generated',()=>{const r={...row(),direction:'received',settlement:'complete',allocations:[{paymentId:'p1',amount:'1500'}],base:'20000'};const b={...book(r),payments:[{id:'p1',kind:'withholding_proof',reference:'SYNTHETIC-PROOF1',date:'2026-10-20',amount:'1500',currency:'MZN'}]};const out=recordIrpc(draft(),b).result.rows[0];assert.equal(out.direction,'received');assert.deepEqual(out.outstanding,{numerator:'500',denominator:'1'});assert.equal('annualCredit'in out,false);});
|
|
test('unknown settlement stays null and no-payment with allocations is contradictory',()=>{let r=recordIrpc(draft(),book({...row(),settlement:''})).result.rows[0];assert.equal(r.settled,null);assert.equal(r.tax.numerator,'10000');r=recordIrpc(draft(),book({...row(),allocations:[{paymentId:'p',amount:'1'}]})).result.rows[0];assert.equal(r.status,'indeterminate');});
|
|
test('same remittance can be split but never consumed twice, including other opportunities',()=>{const p={id:'p',kind:'remittance',reference:'SYNTHETIC-SHARED',date:'2026-10-20',amount:'10000',currency:'MZN'},a={...row('a'),settlement:'complete',allocations:[{paymentId:'p',amount:'5000'}]},b={...row('b'),settlement:'complete',allocations:[{paymentId:'p',amount:'5000'}]};let bk={version:1,rows:[a,b],payments:[p]};assert.ok(recordIrpc(draft(),bk).result.rows.every(r=>r.status==='calculated'));bk.rows[1].allocations[0].amount='6000';assert.ok(recordIrpc(draft(),bk).result.rows.every(r=>r.status==='indeterminate'));const d=draft();d.modelo_a_snapshot=saveIrpcBook(d,{version:1,rows:[a],payments:[p]});d.context_opportunity='another';assert.equal(recordIrpc(d,{version:1,rows:[{...b,allocations:[{paymentId:'p',amount:'6000'}]}],payments:[p]}).result.rows[0].status,'indeterminate');});
|
|
test('duplicate document, duplicate proof ID or duplicate reference do not double totals',()=>{const r=row(),d=draft();assert.ok(recordIrpc(d,{version:1,rows:[r,{...r,id:'x'}],payments:[]}).result.rows.every(r=>r.status==='indeterminate'));const p={id:'p',kind:'remittance',reference:'SYNTHETIC-SAME',date:'2026-10-20',amount:'1',currency:'MZN'};assert.throws(()=>saveIrpcBook(d,{...book(),payments:[p,{...p,id:'p2'}]}),/já registado/);});
|
|
test('reuse is idempotent, inherits supplier/document/amount, preserves deliberate edits and detects source delta',()=>{const d=draft(),ab=acquisitionBook(d),a={...newAcquisition('L5-supplier','2026-10','MZN'),supplier:'SYNTHETIC saved supplier',documentRef:'SYNTHETIC invoice',documentLine:'1',net:'100000',description:'SaaS'};d.modelo_a_snapshot=saveAcquisitionBook(d,{...ab,rows:[...ab.rows,a]});let b=reuseIrpcSources(d,irpcBook(d));assert.equal(reuseIrpcSources(d,b).rows.length,b.rows.length);let r=b.rows.find(r=>r.source?.id==='acquisition:L5-supplier');assert.equal(r.counterparty,a.supplier);assert.equal(r.documentRef,'SYNTHETIC invoice / linha 1');r.base='90000';assert.equal(reuseIrpcSources(d,b).rows.find(v=>v.id===r.id).base,'90000');b={version:1,rows:[{...row(),id:r.id,source:r.source}],payments:[]};const current=acquisitionBook(d);d.modelo_a_snapshot=saveAcquisitionBook(d,{...current,rows:current.rows.map(v=>v.id===a.id?{...v,net:'120000'}:v)});assert.match(recordIrpc(d,b).result.rows[0].reason,/origem alterado/);});
|
|
test('save/recover preserves all preexisting maps, history, context separation and frozen report',()=>{const d=draft(),prior=JSON.parse(d.modelo_a_snapshot),b=recordIrpc(d,book());d.modelo_a_snapshot=saveIrpcBook(d,b);const now=JSON.parse(d.modelo_a_snapshot);for(const[k,v]of Object.entries(prior))assert.deepEqual(now[k],v);assert.deepEqual(irpcBook(d).result,b.result);assert.equal(irpcCurrent(d,b),true);b.rows[0].base='200000';assert.equal(irpcCurrent(d,b),false);assert.match(irpcReportRows({...d,modelo_a_snapshot:saveIrpcBook(d,b)})[0][2],/10000.00/);assert.equal(recordIrpc(d,b).history.length,1);d.context_opportunity='other';assert.equal(irpcBook(d).rows.length,0);});
|
|
test('malformed store remains recoverable as explicit error, never overwritten blank',()=>{assert.throws(()=>irpcBook({modelo_a_snapshot:'{"version":1,"books":{},"irpcWithholdings":[]}'}));const d=draft(),b=book();b.rows[0].events=[{kind:'madeup',date:'2026-10-01',reference:'x'}];assert.throws(()=>saveIrpcBook(d,b));});
|
|
test('export escapes private inputs, retains period/nature/gaps and excludes submission claims',()=>{const d=draft(),r={...row(),counterparty:'<script>alert(1)</script>'};d.modelo_a_snapshot=saveIrpcBook(d,recordIrpc(d,book(r)));const html=irpcPreparationHtml(d);assert.ok(!html.includes('<script>'));assert.match(html,/<script>/);assert.match(html,/sem submissão/);assert.match(html,/CER/);assert.match(html,/2026-10/);assert.match(html,/Retenção definitiva/);});
|
|
if(process.env.IRPC_FIXTURE_OUTPUT){const d=draft(),b=recordIrpc(d,{version:1,rows:[{...row(),settlement:'complete',allocations:[{paymentId:'p1',amount:'6000'}]},{...row('commission'),base:'20000',kind:'emoney_commission',direction:'received',settlement:'complete',allocations:[{paymentId:'p2',amount:'1500'}]}],payments:[{id:'p1',kind:'remittance',reference:'SYNTHETIC-AT1',date:'2026-10-20',amount:'6000',currency:'MZN'},{id:'p2',kind:'withholding_proof',reference:'SYNTHETIC-PROOF1',date:'2026-10-20',amount:'1500',currency:'MZN'}]});d.modelo_a_snapshot=saveIrpcBook(d,b);fs.writeFileSync(process.env.IRPC_FIXTURE_OUTPUT,JSON.stringify(d,null,2)+'\n');}
|
|
|
|
test("allocation changes in another opportunity mark previous result stale only for shared payments",()=>{const d=draft(),p={id:'p',kind:'remittance',reference:'SYNTHETIC-SHARED',date:'2026-10-20',amount:'10000',currency:'MZN'},r={...row(),settlement:'complete',allocations:[{paymentId:'p',amount:'6000'}]},b=recordIrpc(d,{...book(r),payments:[p]});d.modelo_a_snapshot=saveIrpcBook(d,b);assert.equal(irpcCurrent(d,b),true);const ctx=d.context_opportunity;d.context_opportunity='other';d.modelo_a_snapshot=saveIrpcBook(d,book(row('unrelated')));d.context_opportunity=ctx;assert.equal(irpcCurrent(d,b),true);d.context_opportunity='other';d.modelo_a_snapshot=saveIrpcBook(d,{...book({...row('shared'),settlement:'complete',allocations:[{paymentId:'p',amount:'1000'}]}),payments:[p]});d.context_opportunity=ctx;assert.equal(irpcCurrent(d,b),false);assert.match(irpcReportRows(d)[0][3],/Histórico/);});
|