fix: expose document association changes in preparation impact

This commit is contained in:
2026-10-29 22:58:30 +02:00
parent 181a9883b3
commit a10dd83d8d
5 changed files with 174 additions and 18 deletions
@@ -2130,3 +2130,14 @@ No editor, os documentos ainda por associar mantêm-se visíveis; os já associa
Passaram oito testes frontend dirigidos: seis existentes e dois novos para encaminhamento conforme contexto, distinção entre associação/aprovação/indeterminação, controlos recuperados editáveis, retirada explícita, recuperação sem mutação do original e estado desactivado dos controlos. Tipos, lint, formatação e compilação QA passaram. Recibo e hashes no campo `adaptive_documents_increment` de `local-validation-20261029.json`; provas integrais aceites não foram repetidas.
**Visual continua pendente, execução pela equipa:** na candidata, conferir contagens e destino com/sem dados em falta; abrir documentos já associados, alterar/retirar uma associação, comparar impacto antes de confirmar e verificar recuperação. Incluir também âmbito/período não cobertos e exigências indeterminadas, sem interpretar associação como aprovação. Registar revisão, perfil, percurso, esperado/observado e evidência nesta matriz. Sem instalação ou avanço de main; requisitos transversais continuam parciais.
### 29/10/2026 — comparação de impacto: alterações das associações documentais
Continuação sobre `181a9883`, nos requisitos existentes V2-07/V2-10 e na decisão adaptativa de 23/10: explicar divergências e impacto antes da confirmação. Lacuna reproduzida na revisão anterior: a comparação mostrava apenas alterações de estado, omitindo a troca de documento quando ambos mantinham «associado». O texto de vínculo preservado também não distinguia substituição de conservação.
Corrigido no componente existente: comparação por estado e identificador documental, com substituição, associação e retirada explícitas; nomes anterior/novo quando disponíveis, nome indisponível identificado sem inventar o ficheiro, e vínculo preservado apenas quando o identificador permanece. A ausência de alterações é expressamente limitada às exigências/associações documentais. O plano anterior/posterior já fornecido pelo backend e a confirmação de impacto mantêm-se; não há escrita em dados, mudança de schema, permissões ou cálculo.
Dois novos casos falharam contra o código anterior e passaram na candidata. Dez testes frontend dirigidos passaram, cobrindo também troca com o mesmo nome, retirada, nova associação, nome indisponível, histórico sem aplicação, ausência de alterações e preservação da origem. Tipos, lint, formatação e compilação QA passaram. Provas históricas aceites reutilizadas, sem repetição integral. Recibo e hashes no campo `adaptive_document_impact_increment` de `local-validation-20261029.json`.
**Visual pendente pela equipa:** na candidata e num dossier autorizado, preparar troca/retirada de documento, comparar antes de confirmar e conferir nomes/estado anterior e posterior; confirmar que associação mantida não surge como substituição e que o histórico permanece. Não é necessário confirmar uma alteração real para observar a prévia. Registar revisão, percurso, perfil, esperado/observado e evidência nesta matriz. Nenhuma instalação ou avanço de main; o incremento não encerra o percurso adaptativo transversal.
@@ -1254,5 +1254,41 @@
"preservation": "Presentation and navigation only. Existing impact comparison, document data/history, backend, schema, permissions and fiscal rules unchanged. Historical accepted suites reused, not rerun in full.",
"visual_acceptance": "Pending team execution and recording on candidate; component tests do not constitute visual acceptance.",
"installation": "Not triggered; main and active release unchanged."
},
"adaptive_document_impact_increment": {
"recorded_at": "2026-10-29T20:58:29.863158+00:00",
"base_commit": "181a9883b383d11226a84ea0a7ff64844543982d",
"requirements": [
"V2-07",
"V2-10",
"Tempo adaptive journey decision 2026-10-23: explain divergences and impacts before confirmation"
],
"defect": "Impact comparison filtered only by requirement state, hiding a changed document association when both old and new states were associated; preservation text could misdescribe a replacement.",
"behavior": "Compare both state and document identifier. Expose replacement, attachment and detachment with prior/new filenames when available, explicit fallback when a linked name is unavailable, and preserved-link text only for unchanged identifiers. Explicitly report no requirement/association changes without implying no other context impact.",
"checks": {
"previous_revision_reproduced": true,
"new_frontend_cases": 2,
"directed_frontend_passed": 10,
"types": "passed",
"lint": "passed",
"format": "passed",
"qa_build": "passed"
},
"source_sha256": {
"resources/js/components/private-investment-preparation.tsx": "ec387317c8a4b0c82bf6339d3e3d898e9fdae3682419fbbc4b6a09c1b248a9f9",
"tests/frontend/investor-usability-components.test.mjs": "e6f4dcc4727f8ad8650cf50223c8d0aed1ac9f6b195568da3490e65cf93ecc50"
},
"evidence": {
"outputs/validation-20261029/adaptive-impact-red.log": "879cffff2d7b197b3e25caa80d41c203263c017823bb54ed1ff10e31dbf01d58",
"outputs/validation-20261029/adaptive-impact-tests.log": "e62231b8f3087a30781d59ebfab3255264a842ca205c5e627a448eb2000df566",
"outputs/validation-20261029/adaptive-impact-types.log": "82962651e47f564de653ad2883f8fd34e7dae4b4468e4ac459cb3331962a3879",
"outputs/validation-20261029/adaptive-impact-lint.log": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"outputs/validation-20261029/adaptive-impact-format-check.log": "69ac262099e4710ddfdef82edaac4b6cc0548322385ae66c10852453cee79eb3",
"outputs/validation-20261029/adaptive-impact-build.log": "21247f3e6ebce90ebd4c68d5877dbe9606eaaa4f26eb86c4e439755ca26cf61d",
"outputs/validation-20261029/adaptive-impact-build-exit.json": "124fd50cb6d223cdd18fedc166d00af7254199b6e6dedf9be82f71db87af6d01"
},
"preservation": "Existing before/after backend preview and confirmation flow retained; no data writes, schema, permissions, tax rules or backend changes. Historical accepted suites reused.",
"visual_acceptance": "Pending team execution and recording against candidate.",
"installation": "Not triggered; main and active release unchanged."
}
}
@@ -7667,3 +7667,14 @@ No editor, os documentos ainda por associar mantêm-se visíveis; os já associa
Passaram oito testes frontend dirigidos: seis existentes e dois novos para encaminhamento conforme contexto, distinção entre associação/aprovação/indeterminação, controlos recuperados editáveis, retirada explícita, recuperação sem mutação do original e estado desactivado dos controlos. Tipos, lint, formatação e compilação QA passaram. Recibo e hashes no campo `adaptive_documents_increment` de `local-validation-20261029.json`; provas integrais aceites não foram repetidas.
**Visual continua pendente, execução pela equipa:** na candidata, conferir contagens e destino com/sem dados em falta; abrir documentos já associados, alterar/retirar uma associação, comparar impacto antes de confirmar e verificar recuperação. Incluir também âmbito/período não cobertos e exigências indeterminadas, sem interpretar associação como aprovação. Registar revisão, perfil, percurso, esperado/observado e evidência nesta matriz. Sem instalação ou avanço de main; requisitos transversais continuam parciais.
### 29/10/2026 — comparação de impacto: alterações das associações documentais
Continuação sobre `181a9883`, nos requisitos existentes V2-07/V2-10 e na decisão adaptativa de 23/10: explicar divergências e impacto antes da confirmação. Lacuna reproduzida na revisão anterior: a comparação mostrava apenas alterações de estado, omitindo a troca de documento quando ambos mantinham «associado». O texto de vínculo preservado também não distinguia substituição de conservação.
Corrigido no componente existente: comparação por estado e identificador documental, com substituição, associação e retirada explícitas; nomes anterior/novo quando disponíveis, nome indisponível identificado sem inventar o ficheiro, e vínculo preservado apenas quando o identificador permanece. A ausência de alterações é expressamente limitada às exigências/associações documentais. O plano anterior/posterior já fornecido pelo backend e a confirmação de impacto mantêm-se; não há escrita em dados, mudança de schema, permissões ou cálculo.
Dois novos casos falharam contra o código anterior e passaram na candidata. Dez testes frontend dirigidos passaram, cobrindo também troca com o mesmo nome, retirada, nova associação, nome indisponível, histórico sem aplicação, ausência de alterações e preservação da origem. Tipos, lint, formatação e compilação QA passaram. Provas históricas aceites reutilizadas, sem repetição integral. Recibo e hashes no campo `adaptive_document_impact_increment` de `local-validation-20261029.json`.
**Visual pendente pela equipa:** na candidata e num dossier autorizado, preparar troca/retirada de documento, comparar antes de confirmar e conferir nomes/estado anterior e posterior; confirmar que associação mantida não surge como substituição e que o histórico permanece. Não é necessário confirmar uma alteração real para observar a prévia. Registar revisão, percurso, perfil, esperado/observado e evidência nesta matriz. Nenhuma instalação ou avanço de main; o incremento não encerra o percurso adaptativo transversal.
@@ -494,6 +494,22 @@ export function PreparationImpact({
before: Preparation;
after: Preparation;
}) {
const changedRequirements = after.requirements.filter((requirement) => {
const previous = before.requirements.find(
(item) => item.key === requirement.key,
);
return (
previous?.state !== requirement.state ||
(previous?.document_id ?? null) !== requirement.document_id
);
});
const documentName = (
requirement: Preparation['requirements'][number] | undefined,
) =>
requirement?.document_id
? (requirement.document?.file_name ??
'Documento associado — nome indisponível')
: 'Sem documento associado';
return (
<div
className="space-y-2 rounded border border-amber-400 p-3"
@@ -504,27 +520,36 @@ export function PreparationImpact({
{preparationMode(before.mode)} → {preparationMode(after.mode)}.
</p>
<p>Próximo passo: {after.next}.</p>
{changedRequirements.length === 0 && (
<p>Sem alterações às exigências ou associações documentais.</p>
)}
<ul className="list-disc pl-5">
{after.requirements
.filter(
(r) =>
before.requirements.find((b) => b.key === r.key)
?.state !== r.state,
)
.map((r) => (
<li key={r.key}>
{changedRequirements.map((r) => {
const previous = before.requirements.find(
(item) => item.key === r.key,
);
const documentChanged =
(previous?.document_id ?? null) !== r.document_id;
return (
<li key={r.key} className="space-y-1">
{r.label}:{' '}
{
states[
before.requirements.find(
(b) => b.key === r.key,
)?.state ?? 'indeterminate'
]
}{' '}
→ {states[r.state]}.{' '}
{r.document_id && 'Vínculo documental preservado.'}
{states[previous?.state ?? 'indeterminate']} →{' '}
{states[r.state]}.{' '}
{documentChanged ? (
<p>
{previous?.document_id && r.document_id
? 'Substituição de documento: '
: 'Documento: '}
{documentName(previous)} → {documentName(r)}
.
</p>
) : (
r.document_id &&
'Vínculo documental preservado.'
)}
</li>
))}
);
})}
</ul>
<p>{after.calculation_notice}</p>
<PreparationSummary data={after} />
@@ -396,6 +396,79 @@ function preparationFixture() {
],
};
}
void test('impact exposes document replacement even when its state and filename stay unchanged', () => {
const Impact = load(
'../../resources/js/components/private-investment-preparation.tsx',
preparationMocks,
'PreparationImpact',
);
const before = preparationFixture();
before.requirements = [before.requirements[1]];
before.requirements[0].document = { file_name: 'ORIGINAL.pdf' };
const after = structuredClone(before);
after.requirements[0].document_id = 'replacement-document';
const original = JSON.stringify({ before, after });
const html = renderToStaticMarkup(
React.createElement(Impact, { before, after }),
);
const impactList = html.slice(html.indexOf('<ul'), html.indexOf('</ul>'));
assert.match(impactList, /ASSOCIATED/);
assert.match(
impactList,
/Substituição de documento: ORIGINAL.pdf → ORIGINAL.pdf/,
);
assert.doesNotMatch(impactList, /Vínculo documental preservado/);
assert.doesNotMatch(html, /Sem alterações às exigências/);
assert.equal(JSON.stringify({ before, after }), original);
});
void test('impact distinguishes detachment, unavailable names, preserved history and unchanged associations', () => {
const Impact = load(
'../../resources/js/components/private-investment-preparation.tsx',
preparationMocks,
'PreparationImpact',
);
const before = preparationFixture();
before.requirements = [before.requirements[1]];
before.requirements[0].document = { file_name: 'ORIGINAL.pdf' };
const render = (after) =>
renderToStaticMarkup(React.createElement(Impact, { before, after }));
const detached = structuredClone(before);
detached.requirements[0] = {
...detached.requirements[0],
document_id: null,
document: null,
state: 'missing',
};
assert.match(
render(detached),
/Documento: ORIGINAL.pdf → Sem documento associado/,
);
const replaced = structuredClone(before);
replaced.requirements[0] = {
...replaced.requirements[0],
document_id: 'new',
document: null,
};
assert.match(
render(replaced),
/Substituição de documento: ORIGINAL.pdf → Documento associado — nome indisponível/,
);
const historical = structuredClone(before);
historical.requirements[0].state = 'not_applicable';
const historicalList = render(historical).split('</ul>')[0];
assert.match(historicalList, /Vínculo documental preservado/);
assert.doesNotMatch(historicalList, /Substituição de documento/);
assert.match(
render(structuredClone(before)),
/Sem alterações às exigências ou associações documentais/,
);
const associated = structuredClone(detached);
const added = renderToStaticMarkup(
React.createElement(Impact, { before: associated, after: before }),
);
assert.match(added, /Documento: Sem documento associado → ORIGINAL.pdf/);
assert.equal(before.requirements[0].document_id, 'saved-document');
});
const preparationMocks = {
'@inertiajs/react': {
Link: ({ href, children }) =>