Completar combinação CBF15 com saldos anteriores e conferir PEC residual
tests / ci (push) Canceled after 0s
tests / ci (push) Canceled after 0s
This commit is contained in:
@@ -1,3 +1,13 @@
|
||||
## 11/11/2026 — combinação CBF15 com créditos económicos/internacionais e PEC em QA
|
||||
|
||||
Retoma da ordem de conclusão existente, sem nova auditoria ou repetição da publicação financeira:270instituições publicadas e revisão instalada4f3dbcf8 preservadas; visual financeiro/S08 permanece separado. Reconciliação delimitada: o transporte CBF15 básico(7476dc34) já integra a revisão instalada4f3dbcf8, pelo histórico Git e recibos aceites; a nota antiga «QA, não instalado» desse transporte foi superada por entregas posteriores. Não reinstalar esse recorte como se estivesse ausente.
|
||||
|
||||
**Implementado nesta passagem, ainda sem instalação:** retirada a recusa técnica genérica da combinação de CBF15 corrente/transportado com saldos anteriores, reutilizando as fontes e ordem de dedução existentes. Créditos económicos e internacionais/correspondentes transportes precedemCBF15; PEC corrente e anterior usam apenas a colecta residual seguinte, antes de retenções/PPC. Uma escolha explícita de PEC superior ao residual é recusada como indeterminada, sem reduzir silenciosamente parcelas ou fabricar saldo recuperável. Histórico e fontes de investimento/uso anterior continuam preservados; a chave da combinação inclui versão própria para exigir recálculo dos resultados anteriores desse ramo, sem reescrever históricos nem invalidar os recortes sem combinação.
|
||||
|
||||
Cinco cenários sintéticos: combinação ordinária138MZN; quota residual documental100→saldo438; PEC sobrealocado→saldo indeterminado; CBF15 esgota colecta1338 ePEC seleccionado expressamente0→saldo−500 proveniente dePPC, sem reembolso/consumo oficial; janelaCBF15 expirada→saldo538. Também provada a varianteCBF15 corrente e o limite de quota após créditos anteriores. Cálculo→POST/PUT do rascunho privado existente→GET→recálculo exacto→HTML/dossier aprovados no QA55441/proactive_investment_20261027_test, com rollback e sem novas bases/portas/migrations/direitos. Uma provaAPI/37asserções; cinco testes novos nas combinações e23casos afectados de compatibilidade aprovados, não multiplicados como funcionalidades. Tipos/lint aprovados; compilação QA e pacote registados no fecho. Provas `outputs/irpc-cbf15-combination-20261111/*-verification.json` e relatórios recuperados.
|
||||
|
||||
**Saldo:** implementação e percurso desta combinação concluídos emQA; instalação do novo delta e visual separados. Persistem repartição automática/múltiplos projectos, outros períodos/moedas/regimes/EP/cumulações/benefícios e documentos fora dos recortes demonstrados; L1–L7/restanteL8/L9–L16 não são encerrados. Os cinco programas anteriores privados do directório financeiro não são publicados por consequência desta entrega. Próximo trabalho: agrupar a entrega compatível deste delta e continuar o primeiro residual fiscal executável, preservando os fechos aceites.
|
||||
|
||||
## 10/11/2026 —270instituições publicadas e consultáveis na homologação
|
||||
|
||||
**Cadastro e disponibilização do documento fornecido concluídos em homologação.** Conferência de leitura às22:54:31Africa/Johannesburg(20:54:31UTC), revisão instalada `4f3dbcf86499180011fc79712d6b5be445e57e26`:270/270instituições/operadores do documento publicados, nenhuma referência do original em falta,270identidades únicas,393referências físicas e168contactos. Publicações confirmadas pelo fluxo editorial nativo do utilizador; zero escritas efectuadas pelo verificador. As275fichas totais incluem cinco referências anteriores de fundos/programas que permanecem privadas, sem serem publicadas por consequência deste lote. Fonte original/SHA/edição, incertezas e identidades preservadas.
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
## 11/11/2026 — combinação CBF15 com créditos económicos/internacionais e PEC em QA
|
||||
|
||||
Retoma da ordem de conclusão existente, sem nova auditoria ou repetição da publicação financeira:270instituições publicadas e revisão instalada4f3dbcf8 preservadas; visual financeiro/S08 permanece separado. Reconciliação delimitada: o transporte CBF15 básico(7476dc34) já integra a revisão instalada4f3dbcf8, pelo histórico Git e recibos aceites; a nota antiga «QA, não instalado» desse transporte foi superada por entregas posteriores. Não reinstalar esse recorte como se estivesse ausente.
|
||||
|
||||
**Implementado nesta passagem, ainda sem instalação:** retirada a recusa técnica genérica da combinação de CBF15 corrente/transportado com saldos anteriores, reutilizando as fontes e ordem de dedução existentes. Créditos económicos e internacionais/correspondentes transportes precedemCBF15; PEC corrente e anterior usam apenas a colecta residual seguinte, antes de retenções/PPC. Uma escolha explícita de PEC superior ao residual é recusada como indeterminada, sem reduzir silenciosamente parcelas ou fabricar saldo recuperável. Histórico e fontes de investimento/uso anterior continuam preservados; a chave da combinação inclui versão própria para exigir recálculo dos resultados anteriores desse ramo, sem reescrever históricos nem invalidar os recortes sem combinação.
|
||||
|
||||
Cinco cenários sintéticos: combinação ordinária138MZN; quota residual documental100→saldo438; PEC sobrealocado→saldo indeterminado; CBF15 esgota colecta1338 ePEC seleccionado expressamente0→saldo−500 proveniente dePPC, sem reembolso/consumo oficial; janelaCBF15 expirada→saldo538. Também provada a varianteCBF15 corrente e o limite de quota após créditos anteriores. Cálculo→POST/PUT do rascunho privado existente→GET→recálculo exacto→HTML/dossier aprovados no QA55441/proactive_investment_20261027_test, com rollback e sem novas bases/portas/migrations/direitos. Uma provaAPI/37asserções; cinco testes novos nas combinações e23casos afectados de compatibilidade aprovados, não multiplicados como funcionalidades. Tipos/lint aprovados; compilação QA e pacote registados no fecho. Provas `outputs/irpc-cbf15-combination-20261111/*-verification.json` e relatórios recuperados.
|
||||
|
||||
**Saldo:** implementação e percurso desta combinação concluídos emQA; instalação do novo delta e visual separados. Persistem repartição automática/múltiplos projectos, outros períodos/moedas/regimes/EP/cumulações/benefícios e documentos fora dos recortes demonstrados; L1–L7/restanteL8/L9–L16 não são encerrados. Os cinco programas anteriores privados do directório financeiro não são publicados por consequência desta entrega. Próximo trabalho: agrupar a entrega compatível deste delta e continuar o primeiro residual fiscal executável, preservando os fechos aceites.
|
||||
|
||||
## 10/11/2026 —270instituições publicadas e consultáveis na homologação
|
||||
|
||||
**Cadastro e disponibilização do documento fornecido concluídos em homologação.** Conferência de leitura às22:54:31Africa/Johannesburg(20:54:31UTC), revisão instalada `4f3dbcf86499180011fc79712d6b5be445e57e26`:270/270instituições/operadores do documento publicados, nenhuma referência do original em falta,270identidades únicas,393referências físicas e168contactos. Publicações confirmadas pelo fluxo editorial nativo do utilizador; zero escritas efectuadas pelo verificador. As275fichas totais incluem cinco referências anteriores de fundos/programas que permanecem privadas, sem serem publicadas por consequência deste lote. Fonte original/SHA/edição, incertezas e identidades preservadas.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,13 @@
|
||||
## 11/11/2026 — combinação CBF15 com créditos económicos/internacionais e PEC em QA
|
||||
|
||||
Retoma da ordem de conclusão existente, sem nova auditoria ou repetição da publicação financeira:270instituições publicadas e revisão instalada4f3dbcf8 preservadas; visual financeiro/S08 permanece separado. Reconciliação delimitada: o transporte CBF15 básico(7476dc34) já integra a revisão instalada4f3dbcf8, pelo histórico Git e recibos aceites; a nota antiga «QA, não instalado» desse transporte foi superada por entregas posteriores. Não reinstalar esse recorte como se estivesse ausente.
|
||||
|
||||
**Implementado nesta passagem, ainda sem instalação:** retirada a recusa técnica genérica da combinação de CBF15 corrente/transportado com saldos anteriores, reutilizando as fontes e ordem de dedução existentes. Créditos económicos e internacionais/correspondentes transportes precedemCBF15; PEC corrente e anterior usam apenas a colecta residual seguinte, antes de retenções/PPC. Uma escolha explícita de PEC superior ao residual é recusada como indeterminada, sem reduzir silenciosamente parcelas ou fabricar saldo recuperável. Histórico e fontes de investimento/uso anterior continuam preservados; a chave da combinação inclui versão própria para exigir recálculo dos resultados anteriores desse ramo, sem reescrever históricos nem invalidar os recortes sem combinação.
|
||||
|
||||
Cinco cenários sintéticos: combinação ordinária138MZN; quota residual documental100→saldo438; PEC sobrealocado→saldo indeterminado; CBF15 esgota colecta1338 ePEC seleccionado expressamente0→saldo−500 proveniente dePPC, sem reembolso/consumo oficial; janelaCBF15 expirada→saldo538. Também provada a varianteCBF15 corrente e o limite de quota após créditos anteriores. Cálculo→POST/PUT do rascunho privado existente→GET→recálculo exacto→HTML/dossier aprovados no QA55441/proactive_investment_20261027_test, com rollback e sem novas bases/portas/migrations/direitos. Uma provaAPI/37asserções; cinco testes novos nas combinações e23casos afectados de compatibilidade aprovados, não multiplicados como funcionalidades. Tipos/lint aprovados; compilação QA e pacote registados no fecho. Provas `outputs/irpc-cbf15-combination-20261111/*-verification.json` e relatórios recuperados.
|
||||
|
||||
**Saldo:** implementação e percurso desta combinação concluídos emQA; instalação do novo delta e visual separados. Persistem repartição automática/múltiplos projectos, outros períodos/moedas/regimes/EP/cumulações/benefícios e documentos fora dos recortes demonstrados; L1–L7/restanteL8/L9–L16 não são encerrados. Os cinco programas anteriores privados do directório financeiro não são publicados por consequência desta entrega. Próximo trabalho: agrupar a entrega compatível deste delta e continuar o primeiro residual fiscal executável, preservando os fechos aceites.
|
||||
|
||||
## 10/11/2026 —270instituições publicadas e consultáveis na homologação
|
||||
|
||||
**Cadastro e disponibilização do documento fornecido concluídos em homologação.** Conferência de leitura às22:54:31Africa/Johannesburg(20:54:31UTC), revisão instalada `4f3dbcf86499180011fc79712d6b5be445e57e26`:270/270instituições/operadores do documento publicados, nenhuma referência do original em falta,270identidades únicas,393referências físicas e168contactos. Publicações confirmadas pelo fluxo editorial nativo do utilizador; zero escritas efectuadas pelo verificador. As275fichas totais incluem cinco referências anteriores de fundos/programas que permanecem privadas, sem serem publicadas por consequência deste lote. Fonte original/SHA/edição, incertezas e identidades preservadas.
|
||||
|
||||
@@ -565,6 +565,9 @@ function key(d: Record<string, unknown>, b: AnnualBook) {
|
||||
acquisitionScope(d),
|
||||
input,
|
||||
dependencies(d, !!b.investmentCredit),
|
||||
...(b.investmentCredit && b.creditCarry?.lots.length
|
||||
? ['CBF15-credit-carry-combination/1']
|
||||
: []),
|
||||
]);
|
||||
}
|
||||
export function annualCurrent(d: Record<string, unknown>, b: AnnualBook) {
|
||||
@@ -1303,16 +1306,6 @@ export function calculateAnnual(d: Record<string, unknown>, b: AnnualBook) {
|
||||
if (b.investmentCredit && !investmentPreview)
|
||||
pending.push('CréditoCBF15 exige colecta L6 recuperável.');
|
||||
if (investmentPreview) pending.push(...investmentPreview.pending);
|
||||
if (
|
||||
investmentPreview?.available &&
|
||||
cmp(investmentPreview.available, zero()) > 0n &&
|
||||
(!investmentPreview.projectCeiling ||
|
||||
cmp(investmentPreview.projectCeiling, zero()) > 0n) &&
|
||||
b.creditCarry?.lots.length
|
||||
)
|
||||
pending.push(
|
||||
'CombinaçãoCBF15 com saldos anteriores exige correspondência própria; transportes anteriores preservados.',
|
||||
);
|
||||
if (
|
||||
b.investmentCredit?.qualification.state === 'known' &&
|
||||
b.investmentCredit.qualification.eligible === 'yes' &&
|
||||
@@ -1462,7 +1455,7 @@ export function calculateAnnual(d: Record<string, unknown>, b: AnnualBook) {
|
||||
)
|
||||
: null;
|
||||
if (carry) pending.push(...carry.pending);
|
||||
if (investmentPreview?.projectCeiling && !pending.length) {
|
||||
if (investmentPreview && !pending.length) {
|
||||
const beforeInternational = economicPreview?.afterCredit ?? colecta;
|
||||
const preview = b.internationalCredits
|
||||
? internationalDeduction(
|
||||
@@ -1478,10 +1471,25 @@ export function calculateAnnual(d: Record<string, unknown>, b: AnnualBook) {
|
||||
carry.priorInternational,
|
||||
)
|
||||
: (preview?.afterCredit ?? beforeInternational);
|
||||
if (!remaining || cmp(investmentPreview.projectCeiling, remaining) > 0n)
|
||||
if (
|
||||
investmentPreview.projectCeiling &&
|
||||
(!remaining || cmp(investmentPreview.projectCeiling, remaining) > 0n)
|
||||
)
|
||||
pending.push(
|
||||
'Quota residualCBF15 do projecto excede a colecta após créditos económicos/internacionais; reconcilie a atribuição antes de deduzir.',
|
||||
);
|
||||
if (!pending.length && carry?.pec && remaining) {
|
||||
const afterInvestment = deductInvestmentCredit(
|
||||
investmentPreview, remaining, true,
|
||||
).afterCredit;
|
||||
if (
|
||||
afterInvestment &&
|
||||
cmp(addFractions([carry.pec, carry.priorPec]), afterInvestment) > 0n
|
||||
)
|
||||
pending.push(
|
||||
'As parcelas de PEC corrente e anterior excedem a colecta residual após CBF15; reconcilie a escolha documentada sem reduzir valores automaticamente.',
|
||||
);
|
||||
}
|
||||
}
|
||||
const ready = pending.length === 0 && colecta !== null,
|
||||
economic = b.economicCredits
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<?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 IrpcInvestmentCombinationDraftTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function test_combination_and_explicit_pe_c_reconciliation_survive_private_draft_recovery(): 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 (['combined' => 138, 'quota' => 438, 'overallocated' => null, 'reconciled' => -500, 'expired' => 538] as $name => $balance) {
|
||||
$snapshot = json_decode(file_get_contents(base_path('outputs/irpc-cbf15-combination-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->assertFalse($book['result']['model']['investmentCredit']['officialBalancesConsumed']);
|
||||
file_put_contents(base_path('outputs/irpc-cbf15-combination-20261111/recovered-'.$name.'.json'), json_encode($back, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { economicCase } from "./irpc-economic-case.mjs";
|
||||
import { investmentCarryCase } from "./irpc-investment-carry-case.mjs";
|
||||
import {
|
||||
prepareInvestmentCarry,
|
||||
} from "../../../resources/js/lib/irpc-annual-settlement.ts";
|
||||
export function combinedCase() {
|
||||
const c = economicCase(true);
|
||||
c.b = prepareInvestmentCarry(c.d, c.b);
|
||||
c.b.investmentCredit = structuredClone(
|
||||
investmentCarryCase().b.investmentCredit,
|
||||
);
|
||||
c.b.investmentCredit.assessmentKey = c.b.assessmentKey;
|
||||
return c;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { investmentCase } from "./helpers/irpc-investment-case.mjs";
|
||||
import { combinedCase } from "./helpers/irpc-investment-combination-case.mjs";
|
||||
import {
|
||||
calculateAnnual,
|
||||
recordAnnual,
|
||||
annualCurrent,
|
||||
annualBook,
|
||||
saveAnnualBook,
|
||||
annualHtml,
|
||||
} from "../../resources/js/lib/irpc-annual-settlement.ts";
|
||||
const f = (n) => ({ numerator: String(n), denominator: "1" });
|
||||
test("CBF15 carry combines with economic/current and prior international/PEC once in statutory order", () => {
|
||||
const c = combinedCase(),
|
||||
m = calculateAnnual(c.d, c.b);
|
||||
assert.deepEqual(m.pending, []);
|
||||
assert.deepEqual(m.investmentCredit.deducted, f(400));
|
||||
assert.deepEqual(m.arithmeticBalance, f(138));
|
||||
});
|
||||
test("explicit current and prior PEC allocation cannot exceed collecta left after CBF15", () => {
|
||||
const c = combinedCase();
|
||||
Object.assign(c.b.investmentCredit.assets[0], {
|
||||
amount: "40000",
|
||||
proofAmount: "40000",
|
||||
});
|
||||
const m = calculateAnnual(c.d, c.b);
|
||||
assert.equal(
|
||||
m.arithmeticBalance,
|
||||
null,
|
||||
"An excessive PEC allocation must not manufacture a recoverable balance",
|
||||
);
|
||||
assert.match(m.pending.join(" "), /PEC.*CBF15/);
|
||||
});
|
||||
|
||||
test("documented residual quota and expired original window retain distinct unused credits", () => {
|
||||
const c = combinedCase();
|
||||
c.b.investmentCredit.soleProject = "no";
|
||||
c.b.investmentCredit.projectQuota = {
|
||||
amount: "100",
|
||||
reference:
|
||||
"SYNTHETIC residual collecta allocated to this project after prior credits",
|
||||
};
|
||||
let m = calculateAnnual(c.d, c.b);
|
||||
assert.deepEqual(m.pending, []);
|
||||
assert.deepEqual(m.arithmeticBalance, f(438));
|
||||
assert.deepEqual(m.investmentCredit.unused, f(300));
|
||||
c.b.investmentCredit.projectQuota.amount = "1339";
|
||||
assert.match(
|
||||
calculateAnnual(c.d, c.b).pending.join(" "),
|
||||
/Quota residualCBF15/,
|
||||
);
|
||||
c.b.investmentCredit.soleProject = "yes";
|
||||
c.b.investmentCredit.startDate = "2021-01-01";
|
||||
m = calculateAnnual(c.d, c.b);
|
||||
assert.deepEqual(m.pending, []);
|
||||
assert.deepEqual(m.investmentCredit.expired, f(400));
|
||||
assert.deepEqual(m.arithmeticBalance, f(538));
|
||||
});
|
||||
test("explicit zero PEC allocation after exhausted collecta preserves prior PEC and recovers only PPC", () => {
|
||||
const c = combinedCase();
|
||||
Object.assign(c.b.investmentCredit.assets[0], {
|
||||
amount: "40000",
|
||||
proofAmount: "40000",
|
||||
});
|
||||
c.b.creditCarry.currentPec = "0";
|
||||
c.b.creditCarry.lots[1].selected = "0";
|
||||
const m = calculateAnnual(c.d, c.b);
|
||||
assert.deepEqual(m.pending, []);
|
||||
assert.deepEqual(m.investmentCredit.deducted, f(1338));
|
||||
assert.deepEqual(m.pecDeduction.deducted, f(0));
|
||||
assert.deepEqual(m.creditCarry.priorPec, f(0));
|
||||
assert.deepEqual(m.arithmeticBalance, f(-500));
|
||||
assert.equal(m.creditCarry.officialBalancesConsumed, false);
|
||||
const b = recordAnnual(c.d, c.b, "2026-11-11T09:00:00+02:00"),
|
||||
d = { ...c.d, modelo_a_snapshot: saveAnnualBook(c.d, b) },
|
||||
back = annualBook(d);
|
||||
assert.equal(annualCurrent(d, back), true);
|
||||
assert.deepEqual(
|
||||
calculateAnnual(d, back).investmentCredit,
|
||||
m.investmentCredit,
|
||||
);
|
||||
assert.match(annualHtml(d), /CBF15/);
|
||||
});
|
||||
|
||||
test("current CBF15 and prior international/PEC obey the same residual collecta guard", () => {
|
||||
const c = combinedCase();
|
||||
c.b.version = 2;
|
||||
c.b.investmentCredit = structuredClone(investmentCase().b.investmentCredit);
|
||||
c.b.investmentCredit.assessmentKey = c.b.assessmentKey;
|
||||
let m = calculateAnnual(c.d, c.b);
|
||||
assert.deepEqual(m.pending, []);
|
||||
assert.deepEqual(m.arithmeticBalance, f(38));
|
||||
Object.assign(c.b.investmentCredit.assets[0], {
|
||||
amount: "40000",
|
||||
proofAmount: "40000",
|
||||
});
|
||||
m = calculateAnnual(c.d, c.b);
|
||||
assert.equal(m.arithmeticBalance, null);
|
||||
assert.match(m.pending.join(" "), /PEC.*CBF15/);
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
import fs from "node:fs";
|
||||
import assert from "node:assert/strict";
|
||||
import { combinedCase } from "../tests/frontend/helpers/irpc-investment-combination-case.mjs";
|
||||
import {
|
||||
calculateAnnual,
|
||||
recordAnnual,
|
||||
annualBook,
|
||||
annualCurrent,
|
||||
saveAnnualBook,
|
||||
annualHtml,
|
||||
} from "../resources/js/lib/irpc-annual-settlement.ts";
|
||||
import { createProjectDossierReport } from "../resources/js/lib/project-dossier-report.ts";
|
||||
const out = new URL(
|
||||
"../outputs/irpc-cbf15-combination-20261111/",
|
||||
import.meta.url,
|
||||
);
|
||||
fs.mkdirSync(out, { recursive: true });
|
||||
const expected = {
|
||||
combined: 138,
|
||||
quota: 438,
|
||||
overallocated: null,
|
||||
reconciled: -500,
|
||||
expired: 538,
|
||||
};
|
||||
for (const [name, balance] of Object.entries(expected)) {
|
||||
if (process.argv[2] === "recovered") {
|
||||
const d = JSON.parse(
|
||||
fs.readFileSync(
|
||||
new URL("recovered-" + name + ".json", out),
|
||||
"utf8",
|
||||
),
|
||||
),
|
||||
b = annualBook(d),
|
||||
m = calculateAnnual(d, b);
|
||||
assert.equal(annualCurrent(d, b), true);
|
||||
assert.deepEqual(m, b.result.model);
|
||||
assert.deepEqual(
|
||||
m.arithmeticBalance,
|
||||
balance === null
|
||||
? null
|
||||
: { numerator: String(balance), denominator: "1" },
|
||||
);
|
||||
const html = annualHtml(d),
|
||||
report = createProjectDossierReport(d, {
|
||||
selectedSections: new Set(["finance"]),
|
||||
generatedAt: "2026-11-11T09:00:00+02:00",
|
||||
});
|
||||
assert.match(html, /CBF15/);
|
||||
assert.match(JSON.stringify(report), /CBF15/);
|
||||
fs.writeFileSync(new URL(name + "-report.html", out), html);
|
||||
fs.writeFileSync(
|
||||
new URL(name + "-verification.json", out),
|
||||
JSON.stringify(
|
||||
{
|
||||
synthetic: true,
|
||||
apiRecovered: true,
|
||||
exactRecalculation: true,
|
||||
html: true,
|
||||
dossierReport: true,
|
||||
balance: m.arithmeticBalance,
|
||||
pending: m.pending,
|
||||
investment: m.investmentCredit,
|
||||
carry: m.creditCarry,
|
||||
officialBalancesConsumed: false,
|
||||
activeChanged: false,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
const c = combinedCase();
|
||||
c.b.history = [
|
||||
recordAnnual(c.d, structuredClone(c.b), "2026-11-11T08:00:00+02:00")
|
||||
.result,
|
||||
];
|
||||
if (name === "quota") {
|
||||
c.b.investmentCredit.soleProject = "no";
|
||||
c.b.investmentCredit.projectQuota = {
|
||||
amount: "100",
|
||||
reference: "SYNTHETIC documented residual project quota100",
|
||||
};
|
||||
}
|
||||
if (["overallocated", "reconciled"].includes(name))
|
||||
Object.assign(c.b.investmentCredit.assets[0], {
|
||||
amount: "40000",
|
||||
proofAmount: "40000",
|
||||
});
|
||||
if (name === "reconciled") {
|
||||
c.b.creditCarry.currentPec = "0";
|
||||
c.b.creditCarry.lots[1].selected = "0";
|
||||
}
|
||||
if (name === "expired") c.b.investmentCredit.startDate = "2021-01-01";
|
||||
const book = recordAnnual(c.d, c.b, "2026-11-11T09:00:00+02:00");
|
||||
assert.deepEqual(
|
||||
book.result.model.arithmeticBalance,
|
||||
balance === null
|
||||
? null
|
||||
: { numerator: String(balance), denominator: "1" },
|
||||
);
|
||||
fs.writeFileSync(
|
||||
new URL(name + ".json", out),
|
||||
JSON.stringify({
|
||||
...c.d,
|
||||
modelo_a_snapshot: saveAnnualBook(c.d, book),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
console.log(
|
||||
process.argv[2] === "recovered"
|
||||
? "Five API-recovered combinations recalculated and reported."
|
||||
: "Five synthetic combinations prepared.",
|
||||
);
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
$root = dirname(__DIR__);
|
||||
if (($argv[1] ?? '') !== 'test' || basename($root) !== 'portal-investidor') {
|
||||
throw new RuntimeException('Explicit local test operation required.');
|
||||
}
|
||||
foreach (['APP_ENV' => 'testing', 'APP_KEY' => 'base64:'.base64_encode(str_repeat('Q', 32)),
|
||||
'APP_CONFIG_CACHE' => $root.'/outputs/irpc-cbf15-combination-20261111/no-config.php',
|
||||
'DB_CONNECTION' => 'pgsql', 'DB_HOST' => '127.0.0.1', 'DB_PORT' => '55441',
|
||||
'DB_DATABASE' => 'proactive_investment_20261027_test', 'DB_USERNAME' => 'investmoz_test', 'DB_PASSWORD' => '',
|
||||
'DB_URL' => '', 'DATABASE_URL' => '', 'CACHE_STORE' => 'array', 'SESSION_DRIVER' => 'array',
|
||||
'QUEUE_CONNECTION' => 'sync', 'MAIL_MAILER' => 'array', 'SUITECRM_ENABLED' => 'false'] as $key => $value) {
|
||||
putenv($key.'='.$value);
|
||||
$_ENV[$key] = $_SERVER[$key] = $value;
|
||||
}
|
||||
$pdo = new PDO('pgsql:host=127.0.0.1;port=55441;dbname=proactive_investment_20261027_test', 'investmoz_test', '');
|
||||
$identity = $pdo->query('select current_database() as name,inet_server_port() as port')->fetch(PDO::FETCH_ASSOC);
|
||||
if ($identity['name'] !== 'proactive_investment_20261027_test' || (int) $identity['port'] !== 55441) {
|
||||
throw new RuntimeException('Isolated QA identity required.');
|
||||
}
|
||||
$argv = ['phpunit', '-c', 'phpunit.investmoz.xml', '--do-not-cache-result', 'tests/Feature/IrpcInvestmentCombinationDraftTest.php'];
|
||||
$argc = count($argv);
|
||||
$_SERVER['argv'] = $argv;
|
||||
$_SERVER['argc'] = $argc;
|
||||
require $root.'/vendor/autoload.php';
|
||||
require $root.'/vendor/phpunit/phpunit/phpunit';
|
||||
Reference in New Issue
Block a user