feat(irpc): demonstrate ordinary economic credit and compatible carry in QA

This commit is contained in:
2026-10-28 14:29:55 +02:00
parent 6ededb597a
commit aae67c169d
21 changed files with 882 additions and 37 deletions
@@ -0,0 +1,83 @@
<?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 IrpcEconomicCreditDraftTest extends TestCase
{
use DatabaseTransactions;
public function test_economic_credit_and_compatible_carry_round_trip_without_overwriting_accepted_history(): void
{
$target = DB::selectOne('select current_database() as name,inet_server_port() as port');
$this->assertSame('proactive_investment_20261027_test', $target->name);
$this->assertSame(55441, (int) $target->port);
$snapshot = static fn (string $table): array => collect(DB::select('select id::text as id,md5(row_to_json(t)::text) as hash from public."'.$table.'" t order by id'))->pluck('hash', 'id')->all();
$protected = [];
foreach (['opportunities', 'documents', 'opportunity_documents', 'simulation_form_drafts', 'users', 'organizations', 'audit_events'] as $table) {
$protected[$table] = $snapshot($table);
}
config(['investor-path.enabled' => true, 'suitecrm.enabled' => false]);
$this->actingAs(User::findOrFail(1021));
$fixture = json_decode(file_get_contents(base_path('tests/frontend/fixtures/irpc-economic-preserved.json')), true, flags: JSON_THROW_ON_ERROR);
$this->assertTrue($fixture['synthetic']);
$this->assertFalse($fixture['actualTaxEvidence']);
$id = null;
$etag = null;
foreach ($fixture['cases'] as $name => $draft) {
$personal = ['person_type' => $draft['person_type']];
unset($draft['person_type']);
$payload = ['personal_snapshot' => $personal, 'economic_snapshot' => $draft];
if ($id === null) {
$id = $this->postJson('/api/v1/simulation-drafts', $payload, ['Idempotency-Key' => (string) Str::uuid()])->assertCreated()->json('data.id');
} else {
$this->putJson('/api/v1/simulation-drafts/'.$id, $payload, ['If-Match' => $etag])->assertOk();
}
$response = $this->getJson('/api/v1/simulation-drafts/'.$id)->assertOk();
$etag = $response->headers->get('ETag');
$back = $response->json('data.economic_snapshot');
$this->assertSame($draft['modelo_a_snapshot'], $back['modelo_a_snapshot']);
$this->assertSame($draft['business_plan_snapshot'], $back['business_plan_snapshot']);
$original = json_decode($draft['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR);
$recovered = json_decode($back['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR);
foreach (['irpcNormal', 'irpcWithholdings', 'books', 'currencyEvents', 'ispcQuarters'] as $map) {
$this->assertSame($original[$map], $recovered[$map]);
}
$book = $recovered['irpcAnnual'][$draft['context_opportunity']];
$model = $book['result']['model'];
$this->assertSame($original['irpcAnnual'][$draft['context_opportunity']], $book);
$this->assertCount(1, $book['history']);
$this->assertSame('IRPC-economic-credit/1', $model['economicCredit']['version']);
$this->assertFalse($model['economicCredit']['carryCreated']);
$this->assertFalse($model['economicCredit']['officialBalancesConsumed']);
$this->assertFalse($model['submitted']);
if (in_array($name, ['duplicate', 'missing-gross-up', 'article40', 'partial-parcel'], true)) {
$this->assertSame('indeterminate', $model['status']);
$this->assertNull($model['arithmeticBalance']);
$this->assertNull($model['economicCredit']['deducted']);
$this->assertNull($model['internationalCredit']['deducted']);
$this->assertNull($model['pecDeduction']['deducted']);
} else {
$expected = ['payable' => ['688', '1'], 'combined' => ['538', '1'], 'quota' => ['695', '1'], 'prior-use' => ['691', '1'], 'shared-paper' => ['688', '1'], 'fractional' => ['349997', '500'], 'recoverable' => ['-500', '1']][$name];
$this->assertEmpty($model['pending']);
$this->assertSame(['numerator' => $expected[0], 'denominator' => $expected[1]], $model['arithmeticBalance']);
}
if (getenv('INVESTMOZ_ECONOMIC_QA_OUTPUT') === '1') {
file_put_contents(base_path('outputs/irpc-l8-economic-20261028/recovered-'.$name.'.json'), json_encode($back, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
}
}
$preserved = [];
foreach ($protected as $table => $before) {
$this->assertSame($before, array_intersect_key($snapshot($table), $before), 'Existing records changed: '.$table);
$preserved[$table] = ['existing_rows' => count($before), 'unchanged' => true];
}
if (getenv('INVESTMOZ_ECONOMIC_QA_OUTPUT') === '1') {
file_put_contents(base_path('outputs/irpc-l8-economic-20261028/existing-records-preservation.json'), json_encode(['database' => $target->name, 'tables' => $preserved, 'transaction_rollback' => true], JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT));
}
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,25 @@
import {internationalCase} from './irpc-international-case.mjs';
import {carryCase} from './irpc-carry-case.mjs';
import {prepareEconomicBook} from '../../../resources/js/lib/irpc-annual-settlement.ts';
import {newEconomicPosition} from '../../../resources/js/lib/irpc-economic-credit.ts';
export function economicCase(withCarry = false) {
const c = withCarry ? carryCase() : internationalCase();
c.b = prepareEconomicBook(c.b);
Object.assign(c.b.economicCredits, {state: 'known', reference: 'SYNTHETIC QA full ordinary economic-credit inventory', rows: [{
...newEconomicPosition('economic-1'), subjectNuit: c.b.subjectNuit, year: c.b.year, assessmentKey: c.b.assessmentKey,
kind: 'ordinary_profit', payerNuit: '111222333', recipientResident: 'yes', payerResident: 'yes', payerIrpc: 'yes', payerExempt: 'no',
article40: 'no', qualificationReference: 'SYNTHETIC QA entities seated/effectively managed in MZ; payer subject nonexempt IRPC',
article40Reference: 'SYNTHETIC QA explicit nonapplication of40(1), including2009 participation/period/recipient conditions',
distributionDocument: 'SYNTHETIC-DIVIDEND2026', distributionParcel: 'distribution-1', distributionDate: '2026-09-30',
income: '100', incomeIncluded: 'yes', incomeReference: 'SYNTHETIC QA100 gross of beneficiary withholding already included in current L6; no actual dividend assertion',
taxIssuer: 'SYNTHETIC domestic IRPC issuer', taxDocument: 'SYNTHETIC-SOURCE-IRPC2026', taxParcel: 'underlying-tax-1',
parcelReference: 'SYNTHETIC QA underlying domestic IRPC parcel, distinct from recipient withholding or foreign income tax',
proofAmount: '20', taxAmount: '20', attributionReference: 'SYNTHETIC QA documented IRPC20 corresponding to included profits100, not inferred by applying a rate to dividends',
previouslyAttributed: '0', priorAttributionReference: 'SYNTHETIC QA actual previous attribution to other profits known zero',
grossUpIncluded: '12', grossUpReference: 'SYNTHETIC QA53 nominal credit12 already included in reconciled current base4400; no base mutation',
nationalLimit: '32', nationalLimitReference: 'SYNTHETIC QA documented proportional current collecta ceiling32 after credit addition; not an automatic fraction derivation',
previousUse: {state: 'known', amount: '0', reference: 'SYNTHETIC QA actual prior economic-credit use known zero'},
}]});
return c;
}
@@ -0,0 +1,141 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {economicCase} from './helpers/irpc-economic-case.mjs';
import {internationalCase} from './helpers/irpc-international-case.mjs';
import {calculateAnnual, prepareEconomicBook, annualBook, recordAnnual, saveAnnualBook, annualCurrent, annualHtml, annualReportRows} from '../../resources/js/lib/irpc-annual-settlement.ts';
import {createProjectDossierReport} from '../../resources/js/lib/project-dossier-report.ts';
import {irpcBook, recordIrpc, saveIrpcBook} from '../../resources/js/lib/irpc-withholdings.ts';
const number = v => v ? Number(v.numerator) / Number(v.denominator) : null;
const row = c => c.b.economicCredits.rows[0];
const foreign = c => c.b.internationalCredits.positions[0].payments[0];
const calc = c => calculateAnnual(c.d, c.b);
const pending = c => {const m = calc(c); assert.equal(m.status, 'indeterminate', JSON.stringify(m.pending)); assert.equal(m.arithmeticBalance, null); assert.equal(m.economicCredit.deducted, null); assert.equal(m.internationalCredit.deducted, null); assert.equal(m.pecDeduction.deducted, null); return m;};
test('economic60% of documented sourceIRPC20 yields12 before international8,PEC200 andPPC500:688', () => {
const m = calc(economicCase()); assert.deepEqual(m.pending, []); assert.equal(number(m.economicCredit.rows[0].nominal), 12);
assert.equal(number(m.economicCredit.deducted), 12); assert.equal(number(m.economicCredit.afterCredit), 1396);
assert.equal(number(m.internationalCredit.deducted), 8); assert.equal(number(m.internationalCredit.afterCredit), 1388); assert.equal(number(m.payable), 688);
assert.equal(m.economicCredit.carryCreated, false); assert.equal(m.economicCredit.officialBalancesConsumed, false); assert.equal(m.submitted, false);
});
test('documented proportional ceiling and prior actual use limit economic deduction,not a second tax rate on dividends', () => {
const c = economicCase(); row(c).nationalLimit = '5'; let m = calc(c); assert.equal(number(m.economicCredit.rows[0].nominal), 12); assert.equal(number(m.payable), 695);
row(c).previousUse.amount = '3'; m = calc(c); assert.equal(number(m.economicCredit.deducted), 2); assert.equal(number(m.payable), 698);
row(c).previousUse.amount = '6'; pending(c);
});
test('article40 exclusion, payer exemption/foreign residence and special distributions do not become international credit', () => {
for (const patch of [{article40: ''}, {article40: 'yes'}, {article40Reference: ''}, {payerResident: ''}, {payerResident: 'no'}, {recipientResident: 'no'}, {payerIrpc: 'no'}, {payerExempt: ''}, {payerExempt: 'yes'}, {kind: 'liquidation'}, {kind: 'association'}, {kind: ''}, {qualificationReference: ''}]) {
const c = economicCase(); Object.assign(row(c), patch); const m = pending(c); assert.equal(m.internationalCredit.positions.length, 1); assert.equal(number(m.internationalCredit.positions[0].eligible), 8);
if (patch.article40 === 'yes' || patch.kind === 'liquidation' || patch.kind === 'association') assert.equal(m.economicCredit.rows[0].status, 'outside_scope');
}
});
test('inclusion,credit gross-up and current assessment must be reconciled without silently changing L6', () => {
for (const patch of [{incomeIncluded: ''}, {incomeIncluded: 'no'}, {incomeReference: ''}, {assessmentKey: ''}, {assessmentKey: 'stale'}, {grossUpIncluded: ''}, {grossUpIncluded: '0'}, {grossUpIncluded: '11.99'}, {grossUpReference: ''}, {nationalLimit: ''}, {nationalLimit: '1409'}, {nationalLimitReference: ''}, {income: '0'}]) {
const c = economicCase(), before = c.d.modelo_a_snapshot; Object.assign(row(c), patch); pending(c); assert.equal(c.d.modelo_a_snapshot, before);
}
});
test('dividend/source tax identity,date,year,subject and actual allocated source amount require support', () => {
for (const patch of [{subjectNuit: '999999999'}, {payerNuit: ''}, {year: '2025'}, {distributionDate: '2026-02-30'}, {distributionDate: '2025-12-31'}, {distributionDocument: ''}, {distributionParcel: ''}, {taxIssuer: ''}, {taxDocument: ''}, {taxParcel: ''}, {parcelReference: ''}, {currency: 'USD'}, {proofAmount: ''}, {taxAmount: '21'}, {taxAmount: ''}, {attributionReference: ''}, {previouslyAttributed: ''}, {previouslyAttributed: '1'}, {priorAttributionReference: ''}]) {
const c = economicCase(); Object.assign(row(c), patch); pending(c);
}
});
test('same distributed profit parcel cannot be re-used by changing source-tax document,id or date', () => {
const c = economicCase(); c.b.economicCredits.rows.push({...structuredClone(row(c)), id: 'other', taxDocument: 'OTHER', distributionDate: '2026-10-01', distributionDocument: ' synthetic-dividend2026 ', distributionParcel: ' DISTRIBUTION-1 '});
assert.match(pending(c).pending.join(' '), /Mesma parcela de lucros/);
});
test('one source-tax parcel can support distinct documented distributions only within its actual available amount', () => {
const c = economicCase(), other = {...structuredClone(row(c)), id: 'second', distributionParcel: 'distribution-2'};
c.b.economicCredits.rows.push(other); assert.match(pending(c).pending.join(' '), /disponível/);
for (const r of c.b.economicCredits.rows) {r.taxAmount = '10'; r.grossUpIncluded = '6';}
let m = calc(c); assert.deepEqual(m.pending, []); assert.equal(number(m.economicCredit.deducted), 12);
row(c).previouslyAttributed = '1'; other.previouslyAttributed = '1'; pending(c);
row(c).taxAmount = '9'; other.taxAmount = '9'; row(c).grossUpIncluded = '5.4'; other.grossUpIncluded = '5.4'; m = calc(c); assert.deepEqual(m.pending, []); assert.equal(number(m.economicCredit.deducted), 10.8);
other.previouslyAttributed = '0'; assert.match(pending(c).pending.join(' '), /divergente/);
});
test('economic and international use of the same actual tax parcel is rejected even with changed receipt date', () => {
const c = economicCase(); Object.assign(foreign(c), {issuer: row(c).taxIssuer, document: row(c).taxDocument, parcel: ' UNDERLYING-TAX-1 ', parcelReference: 'SYNTHETIC same parcel', proofAmount: '20', date: '2026-10-01'});
assert.match(pending(c).pending.join(' '), /natureza|naturezas/);
});
test('an old proof with no parcel overlaps the whole document;new names require supported partition on both sides', () => {
const c = economicCase(); Object.assign(foreign(c), {issuer: row(c).taxIssuer, document: row(c).taxDocument, proofAmount: '20'}); pending(c);
foreign(c).parcel = 'foreign-tax-line'; pending(c); foreign(c).parcelReference = 'SYNTHETIC evidence of a distinct foreign-tax line in shared document';
const m = calc(c); assert.deepEqual(m.pending, []); assert.equal(number(m.payable), 688);
});
test('economic source IRPC is not beneficiary withholding,PPC or PEC;distinct supported parcels may share a document', () => {
for (const kind of ['ppc', 'pec']) {
const c = economicCase(), proof = kind === 'ppc' ? c.b.ppc.rows[0] : c.b.pecPayments.rows[0];
Object.assign(row(c), {taxIssuer: proof.issuer, taxDocument: proof.document, proofAmount: proof.proofAmount}); pending(c);
Object.assign(proof, {parcel: 'own-' + kind, parcelReference: 'SYNTHETIC distinct payment parcel'}); assert.deepEqual(calc(c).pending, []);
proof.parcel = row(c).taxParcel; pending(c);
}
});
test('underlying domestic IRPC and beneficiary withholding remain separate even when supported by the same paper', () => {
const c = internationalCase('10', '8', '200', '500', '300'); c.b = prepareEconomicBook(c.b); c.b.economicCredits = economicCase().b.economicCredits;
const withholding = c.b.withholdings.rows[0], proof = calc(c).claims.find(x => x.kind === 'withholding');
Object.assign(row(c), {taxIssuer: proof.issuer, taxDocument: proof.document, proofAmount: '300'}); pending(c);
Object.assign(withholding, {parcel: 'beneficiary-withholding', parcelReference: 'SYNTHETIC separate recipient-tax parcel'});
assert.equal(number(calc(c).payable), 388);
const other = structuredClone(c.b), s = JSON.parse(c.d.modelo_a_snapshot);
other.economicCredits.rows = []; other.economicCredits.state = 'absent'; other.internationalCredits.positions = []; other.internationalCredits.state = 'absent';
other.ppc.rows = []; other.ppc.state = 'absent'; other.pecPayments.rows = []; other.pecPayments.state = 'absent';
other.withholdings.rows[0].parcel = 'relabelled-same-L5-allocation';
s.irpcAnnual = {'opportunity:other': other}; s.irpcWithholdings['opportunity:other'] = structuredClone(Object.values(s.irpcWithholdings)[0]); c.d.modelo_a_snapshot = JSON.stringify(s);
c.d.modelo_a_snapshot = saveIrpcBook(c.d, recordIrpc(c.d, irpcBook(c.d), '2026-10-28T14:00:00+02:00'));
assert.match(pending(c).pending.join(' '), /calculada e reconciliada emL5|afectação L5/);
});
test('documented economic inclusion is not rejected merely because fiscal losses reduce total taxable base below dividends', () => {
const c = economicCase(); row(c).income = '5000';
const m = calc(c); assert.deepEqual(m.pending, []); assert.equal(number(m.payable), 688);
});
test('cross-context source capacity and distributed-income duplication cannot be evaded with new ids/documents', () => {
const c = economicCase(), other = structuredClone(c.b), s = JSON.parse(c.d.modelo_a_snapshot);
other.internationalCredits.positions = []; other.internationalCredits.state = 'absent'; other.ppc.rows = []; other.ppc.state = 'absent'; other.pecPayments.rows = []; other.pecPayments.state = 'absent';
other.economicCredits.rows[0].id = 'other-id'; other.economicCredits.rows[0].taxDocument = 'OTHER';
s.irpcAnnual = {'opportunity:other': other}; c.d.modelo_a_snapshot = JSON.stringify(s); assert.match(pending(c).pending.join(' '), /lucros económicos já/);
other.economicCredits.rows[0].distributionParcel = 'other-distribution'; other.economicCredits.rows[0].taxDocument = row(c).taxDocument; c.d.modelo_a_snapshot = JSON.stringify(s); pending(c);
other.economicCredits.rows[0].taxAmount = '10'; row(c).taxAmount = '10'; row(c).grossUpIncluded = '6'; c.d.modelo_a_snapshot = JSON.stringify(s);
assert.deepEqual(calc(c).pending, []);
});
test('economic and accepted carry increments compose:1408-12-8-50-200-100-500=538', () => {
const m = calc(economicCase(true)); assert.deepEqual(m.pending, []); assert.equal(number(m.payable), 538);
assert.equal(number(m.creditCarry.afterInternational), 1338); assert.equal(number(m.creditCarry.afterPec), 1038);
});
test('economic priority leaves prior explicit allocations intact when remaining collecta becomes insufficient', () => {
const c = economicCase(true); c.b.creditCarry.lots[0].eligible = '2000'; c.b.creditCarry.lots[0].selected = '1300'; c.b.creditCarry.currentPec = '0';
const before = structuredClone(c.b.creditCarry); assert.match(pending(c).pending.join(' '), /ultrapassam a colecta/); assert.deepEqual(c.b.creditCarry, before);
c.b.creditCarry.lots[0].selected = '1288'; const m = calc(c); assert.deepEqual(m.pending, []); assert.equal(number(m.recoverable), 500); assert.equal(m.economicCredit.carryCreated, false);
});
test('zero,absence and missing differ; earlier deliberate economic summary remains frozen and does not block detailed calculation', () => {
const c = economicCase(); c.b.internationalCredits.economic = {state: 'known', amount: '999', reference: 'SYNTHETIC previous deliberate summary'};
assert.equal(number(calc(c).payable), 688); assert.equal(c.b.internationalCredits.economic.amount, '999');
c.b.economicCredits.state = 'absent'; pending(c); c.b.economicCredits.rows = []; assert.equal(number(calc(c).payable), 700);
c.b.economicCredits.state = ''; pending(c);
const z = economicCase(); Object.assign(row(z), {taxAmount: '0', proofAmount: '0', grossUpIncluded: '0', income: '0', nationalLimit: '0'}); assert.equal(number(calc(z).payable), 700);
});
test('exact60% arithmetic and fractional quota never invent fiscal rounding', () => {
const c = economicCase(); Object.assign(row(c), {taxAmount: '0.01', proofAmount: '0.01', grossUpIncluded: '0.006'});
const m = calc(c); assert.deepEqual(m.economicCredit.deducted, {numerator: '3', denominator: '500'}); assert.deepEqual(m.payable, {numerator: '349997', denominator: '500'});
row(c).nationalLimit = '1/1000'; assert.deepEqual(calc(c).economicCredit.deducted, {numerator: '1', denominator: '1000'});
});
test('save/recover/history,HTML and dossier preserve current L6,original snapshots and accepted carry results', () => {
const c = economicCase(true), prior = structuredClone(c.b); delete prior.economicCredits;
c.d.modelo_a_snapshot = saveAnnualBook(c.d, recordAnnual(c.d, prior, '2026-10-28T12:00:00+02:00'));
const old = annualBook(c.d), frozen = structuredClone(old.result), base = JSON.parse(c.d.modelo_a_snapshot), plan = c.d.business_plan_snapshot;
const next = prepareEconomicBook(old); next.economicCredits = c.b.economicCredits;
c.d.modelo_a_snapshot = saveAnnualBook(c.d, recordAnnual(c.d, next, '2026-10-28T14:00:00+02:00'));
const back = annualBook(JSON.parse(JSON.stringify(c.d))); assert.equal(annualCurrent(c.d, back), true); assert.deepEqual(back.history, [frozen]); assert.equal(number(back.result.model.payable), 538);
const after = JSON.parse(c.d.modelo_a_snapshot); delete after.irpcAnnual; delete base.irpcAnnual; assert.deepEqual(after, base); assert.equal(c.d.business_plan_snapshot, plan);
assert.match(annualHtml(c.d), /Crédito económico — antes do internacional/); assert.match(annualHtml(c.d), /538.00/);
assert.match(JSON.stringify(createProjectDossierReport(c.d, {selectedSections: new Set(['finance'])})), /Crédito económico/);
back.economicCredits.rows[0].nationalLimit = '5'; c.d.modelo_a_snapshot = saveAnnualBook(c.d, back); assert.equal(annualCurrent(c.d, back), false); assert.deepEqual(back.result, annualBook(c.d).result); assert.throws(() => annualHtml(c.d), /fontes actuais/); assert.match(annualReportRows(c.d).flat().join(' '), /Histórico/);
});
test('malformed optional evidence and parcels are rejected without resetting snapshots', () => {
for (const mutate of [c => c.b.economicCredits.version = 2, c => row(c).previousUse = null, c => row(c).article40 = null, c => foreign(c).parcel = 1]) {
const c = economicCase(), before = c.d.modelo_a_snapshot; mutate(c); assert.throws(() => saveAnnualBook(c.d, c.b), /recuperável/); assert.equal(c.d.modelo_a_snapshot, before);
}
const c = internationalCase(); const once = prepareEconomicBook(c.b); assert.equal(prepareEconomicBook(once), once);
});
test('a partially identified document parcel remains recoverable and indeterminate rather than acquiring absent fields', () => {
const c = economicCase(); foreign(c).parcel = 'foreign-tax-line';
const model = pending(c); c.d.modelo_a_snapshot = saveAnnualBook(c.d, recordAnnual(c.d, c.b));
const back = annualBook(JSON.parse(JSON.stringify(c.d))); assert.deepEqual(calculateAnnual(c.d, back), back.result.model); assert.equal(back.result.model.status, model.status);
});