Preserve documented turnover corrections in PEC viability estimates
tests / ci (push) Canceled after 0s
tests / ci (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
<?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 IrpcPecCorrectionDraftTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function test_pec_obligation_and_calendar_survive_native_draft_without_new_paid_deductions(): 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);
|
||||
config(['investor-path.enabled' => true, 'suitecrm.enabled' => false]);
|
||||
$this->actingAs(User::findOrFail(1021));
|
||||
$id = null;
|
||||
$etag = null;
|
||||
foreach (['higher' => 138, 'lower' => 138, 'retained' => 138, 'changed_base' => 138, 'zero' => 138] as $name => $balance) {
|
||||
$snapshot = json_decode(file_get_contents(base_path('outputs/irpc-pec-correction-20261111/'.$name.'.json')), true, flags: JSON_THROW_ON_ERROR);
|
||||
$personal = ['person_type' => $snapshot['person_type']];
|
||||
unset($snapshot['person_type']);
|
||||
$payload = ['personal_snapshot' => $personal, 'economic_snapshot' => $snapshot];
|
||||
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($snapshot['modelo_a_snapshot'], $back['modelo_a_snapshot']);
|
||||
$book = json_decode($back['modelo_a_snapshot'], true, flags: JSON_THROW_ON_ERROR)['irpcAnnual'][$snapshot['context_opportunity']];
|
||||
$this->assertCount(1, $book['history']);
|
||||
$this->assertSame(['numerator' => '138', 'denominator' => '1'], $book['history'][0]['model']['arithmeticBalance']);
|
||||
$this->assertSame($balance === null ? null : ['numerator' => (string) $balance, 'denominator' => '1'], $book['result']['model']['arithmeticBalance']);
|
||||
$this->assertSame($book['pecObligation'], $book['result']['model']['input']['pecObligation']);
|
||||
$this->assertSame('SYNTHETIC corrected sales2025 reviewed2026', $book['pecObligation']['correction']['reference']);
|
||||
$this->assertSame('SYNTHETIC effective activity'.($name === 'start' ? '2026' : '2020'), $book['pecObligation']['activityReference']);
|
||||
file_put_contents(base_path('outputs/irpc-pec-correction-20261111/recovered-'.$name.'.json'), json_encode($back, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import { compileFunction } from 'node:vm';
|
||||
import { createRequire } from 'node:module';
|
||||
import React from 'react';
|
||||
import ts from 'typescript';
|
||||
import * as annual from '../../resources/js/lib/irpc-annual-settlement.ts';
|
||||
import * as investment from '../../resources/js/lib/irpc-pec-correction.ts';
|
||||
import * as rational from '../../resources/js/lib/fiscal-rational.ts';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
|
||||
const require = createRequire(import.meta.url),
|
||||
module = { exports: {} };
|
||||
const source = fs.readFileSync(
|
||||
new URL(
|
||||
'../../resources/js/components/simulation-irpc-pec-correction.tsx',
|
||||
import.meta.url,
|
||||
),
|
||||
'utf8',
|
||||
);
|
||||
const output = ts.transpileModule(source, {
|
||||
compilerOptions: {
|
||||
module: ts.ModuleKind.CommonJS,
|
||||
target: ts.ScriptTarget.ES2022,
|
||||
jsx: ts.JsxEmit.ReactJSX,
|
||||
},
|
||||
}).outputText;
|
||||
compileFunction(output, ['require', 'module', 'exports'])(
|
||||
(name) =>
|
||||
name === './simulation-irpc-pec-plan'
|
||||
? { SimulationIrpcPecPlan: () => null }
|
||||
: name === '@/lib/irpc-annual-settlement'
|
||||
? annual
|
||||
: name === '@/lib/irpc-pec-correction'
|
||||
? investment
|
||||
: name === '@/lib/fiscal-rational'
|
||||
? rational
|
||||
: name === './simulation-irpc-special'
|
||||
? { QualificationFields: () => null }
|
||||
: name === './fiscal-proof-parcel'
|
||||
? { FiscalProofParcelFields: () => null }
|
||||
: require(name),
|
||||
module,
|
||||
module.exports,
|
||||
);
|
||||
const Component = module.exports.SimulationIrpcPecCorrection;
|
||||
const text = (node) =>
|
||||
typeof node === 'string' || typeof node === 'number'
|
||||
? String(node)
|
||||
: React.isValidElement(node)
|
||||
? React.Children.toArray(node.props.children).map(text).join('')
|
||||
: '';
|
||||
const all = (node) =>
|
||||
React.isValidElement(node)
|
||||
? [node, ...React.Children.toArray(node.props.children).flatMap(all)]
|
||||
: [];
|
||||
test('actual correction form preserves original and revised values when retaining baseline', () => {
|
||||
let value;
|
||||
const render = () =>
|
||||
Component({
|
||||
value,
|
||||
original: '10000000',
|
||||
subjectNuit: '123456789',
|
||||
onChange: (next) => (value = next),
|
||||
});
|
||||
const click = (label) => {
|
||||
const n = all(render()).find(
|
||||
(x) => x.type === 'button' && text(x) === label,
|
||||
);
|
||||
assert.ok(n, label);
|
||||
n.props.onClick();
|
||||
};
|
||||
const change = (label, v) => {
|
||||
const n = all(render()).find((x) => x.props['aria-label'] === label);
|
||||
assert.ok(n, label);
|
||||
n.props.onChange({ target: { value: v } });
|
||||
};
|
||||
click('Rectificar volume na simulação PEC');
|
||||
assert.equal(value.originalTurnover, '10000000');
|
||||
assert.equal(value.subjectNuit, '123456789');
|
||||
change('Volume corrigido — PEC', '50000000');
|
||||
change(
|
||||
'Fonte e fundamento da rectificação PEC',
|
||||
'SYNTHETIC correction2025',
|
||||
);
|
||||
change('Aplicar rectificação PEC', 'yes');
|
||||
change('Aplicar rectificação PEC', 'no');
|
||||
assert.equal(value.revisedTurnover, '50000000');
|
||||
assert.equal(value.originalTurnover, '10000000');
|
||||
change('Aplicar rectificação PEC', 'yes');
|
||||
assert.match(renderToStaticMarkup(render()), /não submete declaração/);
|
||||
assert.match(text(render()), /original permanece/);
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
newPecCorrection,
|
||||
validatePecCorrection,
|
||||
} from '../../resources/js/lib/irpc-pec-correction.ts';
|
||||
import { calculatePecObligation } from '../../resources/js/lib/irpc-pec-obligation.ts';
|
||||
import {
|
||||
calculatePecPlan,
|
||||
newPecPlan,
|
||||
} from '../../resources/js/lib/irpc-pec-plan.ts';
|
||||
import { plan } from './helpers/irpc-pec-obligation-case.mjs';
|
||||
import { combinedCase } from './helpers/irpc-investment-combination-case.mjs';
|
||||
import {
|
||||
recordAnnual,
|
||||
annualCurrent,
|
||||
annualBook,
|
||||
saveAnnualBook,
|
||||
annualHtml,
|
||||
} from '../../resources/js/lib/irpc-annual-settlement.ts';
|
||||
const f = (n) => ({ numerator: String(n), denominator: '1' }),
|
||||
corrected = (p = plan()) => ({
|
||||
...p,
|
||||
correction: {
|
||||
...newPecCorrection(p.previousTurnover, p.subjectNuit),
|
||||
apply: 'yes',
|
||||
revisedTurnover: '50000000',
|
||||
reference: 'SYNTHETIC corrected sales2025 reviewed2026',
|
||||
},
|
||||
}),
|
||||
calc = (p) => calculatePecObligation(p, p.subjectNuit, p.year, 'normal');
|
||||
test('documented correction preserves original and recomputes percentage,cap and previous PPC once', () => {
|
||||
const p = corrected(),
|
||||
m = calc(p);
|
||||
assert.equal(p.previousTurnover, '10000000');
|
||||
assert.deepEqual(m.total, f(90000));
|
||||
assert.deepEqual(calc(plan()).total, f(40000));
|
||||
assert.equal(m.turnoverCorrection.applied, true);
|
||||
assert.equal(m.annualDeductionCreated, false);
|
||||
});
|
||||
test('explicitly retaining original leaves corrective values recoverable without using them', () => {
|
||||
const p = corrected();
|
||||
p.correction.apply = 'no';
|
||||
assert.deepEqual(calc(p).total, f(40000));
|
||||
assert.equal(p.correction.revisedTurnover, '50000000');
|
||||
const {d,b}=combinedCase();b.pecObligation={...p,subjectNuit:b.subjectNuit,correction:{...p.correction,subjectNuit:b.subjectNuit}};d.modelo_a_snapshot=saveAnnualBook(d,recordAnnual(d,b,'2026-11-11T12:05:00Z'));assert.match(annualHtml(d),/Estimativa original: 40000/);assert.match(annualHtml(d),/efeito na estimativa: 0/);
|
||||
p.correction.apply = '';
|
||||
assert.equal(calc(p).total, null);
|
||||
});
|
||||
test('changed original,titular,year and absent evidence cannot silently qualify correction;zero uses29(5)', () => {
|
||||
for (const patch of [
|
||||
{ originalTurnover: '1' },
|
||||
{ subjectNuit: '999999999' },
|
||||
{ turnoverYear: '2024' },
|
||||
{ reference: '' },
|
||||
{ revisedTurnover: '' },
|
||||
]) {
|
||||
const p = corrected();
|
||||
Object.assign(p.correction, patch);
|
||||
assert.equal(calc(p).total, null);
|
||||
}
|
||||
const p = corrected();
|
||||
p.correction.revisedTurnover = '0';
|
||||
assert.match(calc(p).pending.join(' '), /29\(5\)/);
|
||||
assert.throws(() =>
|
||||
validatePecCorrection({ ...p.correction, revisedTurnover: null }),
|
||||
);
|
||||
});
|
||||
test('correction invalidates old cash-flow allocation and annual snapshot;history/deductions remain intact', () => {
|
||||
const { d, b } = combinedCase();
|
||||
b.pecObligation = { ...plan(), subjectNuit: b.subjectNuit };
|
||||
let current = recordAnnual(d, b, '2026-11-11T12:00:00Z');
|
||||
current.pecObligation = corrected(current.pecObligation);
|
||||
current.pecObligation.plan = {
|
||||
...newPecPlan(),
|
||||
mode: 'three',
|
||||
reference: 'SYNTHETIC old cash hypothesis',
|
||||
june: '10000',
|
||||
august: '15000',
|
||||
october: '15000',
|
||||
};
|
||||
assert.equal(annualCurrent(d, current), false);
|
||||
const m = calc(current.pecObligation);
|
||||
assert.ok(
|
||||
calculatePecPlan(
|
||||
current.pecObligation.plan,
|
||||
m.total,
|
||||
m.notApplicable,
|
||||
'2026',
|
||||
).pending.length,
|
||||
);
|
||||
current = recordAnnual(d, current, '2026-11-11T12:01:00Z');
|
||||
d.modelo_a_snapshot = saveAnnualBook(d, current);
|
||||
const back = annualBook(d);
|
||||
assert.deepEqual(back.result.model.arithmeticBalance, f(138));
|
||||
assert.deepEqual(back.pecPayments, b.pecPayments);
|
||||
assert.equal(back.history.length, 1);
|
||||
assert.equal(annualCurrent(d, back), true);
|
||||
assert.match(annualHtml(d), /efeito na estimativa: 50000/);
|
||||
});
|
||||
@@ -30,7 +30,7 @@ const output = ts.transpileModule(source, {
|
||||
}).outputText;
|
||||
compileFunction(output, ['require', 'module', 'exports'])(
|
||||
(name) =>
|
||||
name === './simulation-irpc-pec-plan'
|
||||
name === './simulation-irpc-pec-correction' ? {SimulationIrpcPecCorrection:()=>null} : name === './simulation-irpc-pec-plan'
|
||||
? {SimulationIrpcPecPlan:()=>null}
|
||||
: name === '@/lib/irpc-annual-settlement'
|
||||
? annual
|
||||
|
||||
Reference in New Issue
Block a user