Files
Investidor-V2/tests/frontend/iva-production.test.mjs
T

426 lines
14 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 };
}
void 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');
});
void 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);
});
void 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);
});
void 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),
);