76 lines
9.2 KiB
JavaScript
76 lines
9.2 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import fs from 'node:fs';
|
|
import {operationTreatments,newSalesOperation,evaluateSalesOperation,recordSales,saveSalesBook,salesBook,salesCurrent} from '../../resources/js/lib/iva-operations.ts';
|
|
import {createProjectDossierReport} from '../../resources/js/lib/project-dossier-report.ts';
|
|
import {productionMinimumTermEnd,productionReturnEffective,productionOptionCheck} from '../../resources/js/lib/iva-production-option.ts';
|
|
const ids=['production_agriculture', 'production_forestry', 'production_forestry_opted', 'production_livestock', 'production_livestock_opted', 'production_fishing', 'production_fishing_opted'];
|
|
function seed(){return JSON.parse(fs.readFileSync(new URL('./fixtures/iva-listed.json',import.meta.url),'utf8'));}
|
|
function fixture(){const draft=seed();draft.context_opportunity='opportunity:318';draft.project_title='SYNTHETIC QA — G2I social';
|
|
let book={currency:'MZN',rows:ids.map((id,i)=>({...newSalesOperation(`g2i-${i}`,'2026-10','MZN'),description:`SYNTHETIC QA ${id}`,establishment:'SYNTHETIC QA equipamento Manica',amount:id.endsWith('_opted')&&i%3===2?'116':'100',basis:i%3===2?'included':'net',treatment:id,...(id.endsWith('_opted')?{taxOption:{mode:'annual',declarationDate:'2024-12-15',startupDate:'',returnStatus:'none',returnDeclarationDate:'',operationDate:'2026-10-15'}}:{}),confirmed:operationTreatments.find(t=>t.id===id).conditions.map((_,n)=>n),evidence:'SYNTHETIC QA — natureza, entidade, reconhecimento e condições cumulativas pressupostos documentados; sem certificação'})),periods:[{period:'2026-10',complete:true}]};book=recordSales(draft,book);draft.modelo_a_snapshot=saveSalesBook(draft,book);return {draft,book};}
|
|
test('G2I four production branches with seven exemption or opted treatments require their own operation and eligibility conditions',()=>{
|
|
const {book}=fixture();for(const row of book.rows){const rule=operationTreatments.find(t=>t.id===row.treatment),r=evaluateSalesOperation(row,'MZN');assert.equal(r.field,row.treatment.endsWith('_opted')?'01':row.treatment==='production_agriculture'?'05':'07');assert.equal(r.net,'100.00');assert.equal(r.vat,row.treatment.endsWith('_opted')?'16.00':'0.00');assert.equal(r.gross,row.treatment.endsWith('_opted')?'116.00':'100.00');assert.match(r.article,/9\(7\)/);
|
|
assert.ok(rule.conditions.length>=3);
|
|
for(const missing of row.confirmed)assert.equal(evaluateSalesOperation({...row,confirmed:row.confirmed.filter(n=>n!==missing)},'MZN').vat,null);
|
|
assert.equal(evaluateSalesOperation({...row,evidence:''},'MZN').vat,null);assert.equal(evaluateSalesOperation({...row,treatment:'listed_banana'},'MZN').vat,null); assert.equal(evaluateSalesOperation({...row,period:'2025-10'},'MZN').vat,null); assert.equal(evaluateSalesOperation(row,'USD').vat,null);
|
|
}
|
|
assert.equal(book.result.periods[0].fields['07'],'300.00');assert.equal(book.result.periods[0].fields['05'],'100.00');assert.equal(book.result.periods[0].fields['01'],'300.00');assert.equal(book.result.periods[0].fields['02'],'48.00');
|
|
});
|
|
test('new production context round-trips and reports only its operations, preserving nine accepted books without recalculation',()=>{
|
|
const {draft,book}=fixture(),original=seed();const old=JSON.parse(original.modelo_a_snapshot).salesOperations;
|
|
const recovered=JSON.parse(JSON.stringify(draft)),store=JSON.parse(recovered.modelo_a_snapshot).salesOperations;
|
|
for(const id of ['opportunity:309','opportunity:310','opportunity:311','opportunity:312','opportunity:313','opportunity:314','opportunity:315','opportunity:316','opportunity:317']){assert.deepEqual(store[id],old[id]);assert.equal(salesCurrent({...recovered,context_opportunity:id},store[id]),true);}
|
|
assert.equal(salesCurrent(recovered,salesBook(recovered)),true);
|
|
const model=JSON.stringify(createProjectDossierReport(recovered,{selectedSections:new Set(['finance'])}));
|
|
for(const token of [...ids,'48.00','opportunity:318','Manica','2026-10','MZN'])assert.ok(model.includes(token),token);
|
|
for(const token of ['financial_stamp','patient_transport','social_assistance_public','statutory_membership','author_rights','industrial_feed','special_tax_games','public_security','listed_maize'])assert.equal(model.includes(token),false);
|
|
assert.equal(book.result.rows.length,7);assert.equal(draft.currency,original.currency);assert.deepEqual(draft.selection_labels,original.selection_labels);
|
|
});
|
|
test('article11 distinguishes annual effects, startup, five-year minimum and effective return without automatic expiry',()=>{
|
|
const {draft,book}=fixture(),row=book.rows.find(r=>r.treatment==='production_forestry_opted');
|
|
const check=(patch)=>evaluateSalesOperation({...row,taxOption:{...row.taxOption,...('returnDeclarationDate' in patch?{returnStatus:patch.returnDeclarationDate?'declared':'none'}:{}),...patch}},'MZN');
|
|
assert.equal(check({declarationDate:'2026-01-01'}).vat,null);
|
|
assert.match(check({declarationDate:'2026-01-01'}).reason,/2027-01-01/);
|
|
assert.equal(check({mode:'startup',declarationDate:'2026-09-20',startupDate:'2026-10-01'}).vat,'16.00');
|
|
assert.equal(check({mode:'startup',declarationDate:'2026-10-02',startupDate:'2026-10-01'}).vat,null);
|
|
assert.equal(check({declarationDate:'2020-01-01',returnDeclarationDate:'2025-12-31'}).vat,null);
|
|
assert.equal(check({declarationDate:'2019-12-31',returnDeclarationDate:'2025-01-01'}).vat,null);
|
|
assert.match(check({declarationDate:'2019-12-31',returnDeclarationDate:'2025-01-01'}).reason,/2026-01-01/);
|
|
assert.equal(check({declarationDate:'2020-01-01',returnDeclarationDate:'2026-01-01'}).vat,'16.00');
|
|
assert.equal(check({declarationDate:'2019-12-31',returnDeclarationDate:''}).vat,'16.00');
|
|
for(const patch of [{mode:''},{returnStatus:''},{returnStatus:'declared',returnDeclarationDate:''},{returnStatus:'none',returnDeclarationDate:'2026-01-01'},{declarationDate:''},{declarationDate:'2026-99-99'},{declarationDate:'2024-02-30'},{operationDate:'2026-11-01'},{operationDate:''},{returnDeclarationDate:'not-a-date'}])assert.equal(check(patch).vat,null);
|
|
assert.equal(check({mode:'startup',declarationDate:'2020-02-28',startupDate:'2020-02-29',returnDeclarationDate:'2025-02-28'}).vat,null);
|
|
assert.match(check({mode:'startup',declarationDate:'2020-02-28',startupDate:'2020-02-29',returnDeclarationDate:'2025-02-28'}).reason,/regresso.*2026-01-01/);
|
|
assert.match(check({mode:'startup',declarationDate:'2020-02-28',startupDate:'2020-02-29',returnDeclarationDate:'2025-03-01'}).reason,/declaração tardia/);
|
|
assert.equal(check({mode:'startup',declarationDate:'2020-02-28',startupDate:'2020-02-29',returnDeclarationDate:'2026-03-01'}).vat,null);
|
|
assert.equal(operationTreatments.some(t=>t.id==='production_agriculture_opted'),false);
|
|
const changed={...book,rows:book.rows.map(r=>r.id===row.id?{...r,taxOption:{...r.taxOption,declarationDate:'2025-12-31'}}:r)};
|
|
assert.equal(salesCurrent(draft,changed),false);assert.deepEqual(changed.result,book.result);
|
|
const report=JSON.stringify(createProjectDossierReport(draft,{selectedSections:new Set(['finance'])}));
|
|
for(const token of ['2024-12-15','2025-01-01','2026-10-15','ausência de declaração de regresso'])assert.ok(report.includes(token),token);
|
|
});
|
|
test('Civil Code 279(c): calendar term at 24:00 is separate from article11 return effects',()=>{
|
|
assert.equal(productionMinimumTermEnd('2024-02-29'),'2029-02-28');
|
|
assert.equal(productionMinimumTermEnd('2023-03-01'),'2028-03-01');
|
|
assert.equal(productionMinimumTermEnd('2096-02-29'),'2101-02-28');
|
|
assert.equal(productionMinimumTermEnd('2024-12-31'),'2029-12-31');
|
|
assert.equal(productionReturnEffective('2029-02-28'),'2030-01-01');
|
|
const option={mode:'startup',declarationDate:'2024-02-28',startupDate:'2024-02-29',returnStatus:'declared',returnDeclarationDate:'2029-02-28',operationDate:'2029-02-28'};
|
|
// Schedule checks only: these dates do not extend the VAT calculator beyond its admitted 2026 period.
|
|
for(const date of ['2029-02-28','2029-03-01','2029-12-31']){
|
|
const result=productionOptionCheck({...option,operationDate:date},date.slice(0,7));
|
|
assert.equal(result.error,undefined);assert.match(result.summary,/24 horas de 2029-02-28/);assert.match(result.summary,/efeitos 2030-01-01/);
|
|
}
|
|
for(const date of ['2030-01-01','2030-01-02'])assert.match(productionOptionCheck({...option,operationDate:date},date.slice(0,7)).error,/cessou.*2030-01-01/);
|
|
assert.equal(productionOptionCheck({...option,returnDeclarationDate:'2029-02-27'},'2029-02').error,undefined);
|
|
assert.match(productionOptionCheck({...option,returnDeclarationDate:'2028-12-31'},'2029-02').error,/antes de terminado o prazo mínimo/);
|
|
assert.match(productionOptionCheck({...option,returnDeclarationDate:'2029-03-01'},'2029-02').error,/declaração tardia/);
|
|
const {draft,book}=fixture();
|
|
const row=book.rows.find(r=>r.treatment==='production_forestry_opted');
|
|
const leap={...row,taxOption:{mode:'startup',declarationDate:'2020-02-28',startupDate:'2020-02-29',returnStatus:'declared',returnDeclarationDate:'2025-02-28',operationDate:'2026-10-15'}};
|
|
assert.match(evaluateSalesOperation(leap,'MZN').reason,/regresso.*2026-01-01/);
|
|
const report=JSON.stringify(createProjectDossierReport(draft,{selectedSections:new Set(['finance'])}));
|
|
assert.ok(report.includes('24 horas de 2030-01-01'));assert.ok(report.includes('production-option/2'));
|
|
});
|
|
if(process.env.IVA_G2I_OUT)fs.writeFileSync(process.env.IVA_G2I_OUT,JSON.stringify(fixture().draft,null,2));
|