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
|
## 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.
|
**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
|
## 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.
|
**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
|
## 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.
|
**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),
|
acquisitionScope(d),
|
||||||
input,
|
input,
|
||||||
dependencies(d, !!b.investmentCredit),
|
dependencies(d, !!b.investmentCredit),
|
||||||
|
...(b.investmentCredit && b.creditCarry?.lots.length
|
||||||
|
? ['CBF15-credit-carry-combination/1']
|
||||||
|
: []),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
export function annualCurrent(d: Record<string, unknown>, b: AnnualBook) {
|
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)
|
if (b.investmentCredit && !investmentPreview)
|
||||||
pending.push('CréditoCBF15 exige colecta L6 recuperável.');
|
pending.push('CréditoCBF15 exige colecta L6 recuperável.');
|
||||||
if (investmentPreview) pending.push(...investmentPreview.pending);
|
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 (
|
if (
|
||||||
b.investmentCredit?.qualification.state === 'known' &&
|
b.investmentCredit?.qualification.state === 'known' &&
|
||||||
b.investmentCredit.qualification.eligible === 'yes' &&
|
b.investmentCredit.qualification.eligible === 'yes' &&
|
||||||
@@ -1462,7 +1455,7 @@ export function calculateAnnual(d: Record<string, unknown>, b: AnnualBook) {
|
|||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
if (carry) pending.push(...carry.pending);
|
if (carry) pending.push(...carry.pending);
|
||||||
if (investmentPreview?.projectCeiling && !pending.length) {
|
if (investmentPreview && !pending.length) {
|
||||||
const beforeInternational = economicPreview?.afterCredit ?? colecta;
|
const beforeInternational = economicPreview?.afterCredit ?? colecta;
|
||||||
const preview = b.internationalCredits
|
const preview = b.internationalCredits
|
||||||
? internationalDeduction(
|
? internationalDeduction(
|
||||||
@@ -1478,10 +1471,25 @@ export function calculateAnnual(d: Record<string, unknown>, b: AnnualBook) {
|
|||||||
carry.priorInternational,
|
carry.priorInternational,
|
||||||
)
|
)
|
||||||
: (preview?.afterCredit ?? beforeInternational);
|
: (preview?.afterCredit ?? beforeInternational);
|
||||||
if (!remaining || cmp(investmentPreview.projectCeiling, remaining) > 0n)
|
if (
|
||||||
|
investmentPreview.projectCeiling &&
|
||||||
|
(!remaining || cmp(investmentPreview.projectCeiling, remaining) > 0n)
|
||||||
|
)
|
||||||
pending.push(
|
pending.push(
|
||||||
'Quota residualCBF15 do projecto excede a colecta após créditos económicos/internacionais; reconcilie a atribuição antes de deduzir.',
|
'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,
|
const ready = pending.length === 0 && colecta !== null,
|
||||||
economic = b.economicCredits
|
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