From 181a9883b383d11226a84ea0a7ff64844543982d Mon Sep 17 00:00:00 2001 From: nala Date: Thu, 29 Oct 2026 22:56:02 +0200 Subject: [PATCH] feat: prioritise missing preparation documents and retain associations --- .../consolidated-review-matrix-2026-10-03.md | 11 ++ .../local-validation-20261029.json | 38 +++++ .../unification/traceability-and-manual.md | 11 ++ .../private-investment-preparation.tsx | 141 +++++++++++------ .../investor-usability-components.test.mjs | 144 ++++++++++++++++++ 5 files changed, 296 insertions(+), 49 deletions(-) diff --git a/docs/investmoz/consolidated-review-matrix-2026-10-03.md b/docs/investmoz/consolidated-review-matrix-2026-10-03.md index ba928663..052fdb55 100644 --- a/docs/investmoz/consolidated-review-matrix-2026-10-03.md +++ b/docs/investmoz/consolidated-review-matrix-2026-10-03.md @@ -2119,3 +2119,14 @@ Incremento de apresentação sobre `fb9a363e`, nos requisitos existentes V2-07/V Conferência dirigida: seis testes frontend existentes passaram; 22 verificações SSR cobriram os três modos, rótulos de dados em falta, preparação inactiva/ausente, ligação de continuação, histórico preservado e imutabilidade da origem. Tipos, formatação, lint e compilação QA passaram. Provas anteriores reutilizadas. Recibo no campo `adaptive_summary_increment` de `local-validation-20261029.json`. **Conferência visual pendente pela equipa:** na candidata, abrir o resumo de preparação activo, conferir situação/próxima acção/dados em falta sem expandir detalhes, continuar pelo formulário e consultar histórico/fontes; verificar também preparação inactiva e ausência de dados em falta. Registar revisão, perfil, percurso e resultado nesta matriz. SSR não comprova apresentação no navegador. Sem instalação ou avanço de main; este incremento não encerra os requisitos transversais. + + +### 29/10/2026 — percurso adaptativo: documentos em falta e recuperação das associações + +Continuação sobre `d662e634`, nos requisitos existentes V2-07/V2-10 e na decisão adaptativa da Tempo de 23/10. O resumo distingue documentos obrigatórios por associar, documentos associados cujo conteúdo continua por conferir e exigências documentais ainda por determinar; o histórico sem aplicação não é contado como pendência. Com dados completos e âmbito/período cobertos, a acção de continuação conduz à secção existente de documentos. Nos restantes estados mantém o destino do formulário. São decisões de apresentação/navegação; não criam novas condições de negócio. + +No editor, os documentos ainda por associar mantêm-se visíveis; os já associados ficam em «Documentos já associados — consultar ou alterar». A retirada deliberada permanece por confirmar na comparação de impacto e não é substituída pelo identificador guardado. O agrupamento usa o estado recuperado, conservando o controlo durante a edição. Dados, documentos originais, histórico, regras fiscais, backend, schema e permissões preservados. + +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. diff --git a/docs/investmoz/unification/local-validation-20261029.json b/docs/investmoz/unification/local-validation-20261029.json index a1ba5aa5..b48ee301 100644 --- a/docs/investmoz/unification/local-validation-20261029.json +++ b/docs/investmoz/unification/local-validation-20261029.json @@ -1216,5 +1216,43 @@ "baseline_evidence": "Reused accepted PHP/frontend baseline; no full-suite rerun.", "visual_acceptance": "Pending team execution and recording against candidate; SSR does not replace browser acceptance.", "installation": "Not triggered; main and active release unchanged." + }, + "adaptive_documents_increment": { + "recorded_at": "2026-10-29T20:56:01.348797+00:00", + "base_commit": "d662e6346942400e18bedffbfed0d92e017428f9", + "requirements": [ + "V2-07", + "V2-10", + "Tempo adaptive journey decision 2026-10-23" + ], + "behavior": [ + "Preparation summary separately counts missing required documents, associated required documents with content still to review, and requirements whose applicability remains undetermined; not-applicable history is excluded from these counts.", + "When enabled context is complete within its covered period and scope, missing required documents lead directly to the existing document section. Otherwise the existing question-section destination is retained.", + "Editor prioritises required documents still to associate and groups saved associated documents in editable details. Explicit null detachment overrides the saved link until impact comparison; no original association is mutated." + ], + "checks": { + "directed_frontend_tests": 8, + "new_cases": 2, + "existing_cases_reused": 6, + "types": "passed", + "lint": "passed", + "format": "passed", + "qa_build": "passed" + }, + "source_sha256": { + "resources/js/components/private-investment-preparation.tsx": "b1e55f3d80d0ae5cba4ab822ce9b564880973d2d8ebfc35bac0d91acaeea5d72", + "tests/frontend/investor-usability-components.test.mjs": "fc07a7ff63e122392250825f9825ec6f1e0e09b5c62c6fb900d63846d07f2d3d" + }, + "evidence": { + "outputs/validation-20261029/adaptive-documents-tests.log": "cbf08ccb0897a7432a9153ee6488490a1b5c2a59c80465f459c3786765c0ee6f", + "outputs/validation-20261029/adaptive-documents-types.log": "82962651e47f564de653ad2883f8fd34e7dae4b4468e4ac459cb3331962a3879", + "outputs/validation-20261029/adaptive-documents-lint.log": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "outputs/validation-20261029/adaptive-documents-format-check.log": "3996b5359cbd4c71d9610a4af35fdc8af9aa6fb4ab4bfcec9a15df2190b9c0cd", + "outputs/validation-20261029/adaptive-documents-build.log": "e76ce07b405e5dc28e9529f0d49b82bf8d4b9fa0e2ccf0574897b888369c4525", + "outputs/validation-20261029/adaptive-documents-build-exit.json": "124fd50cb6d223cdd18fedc166d00af7254199b6e6dedf9be82f71db87af6d01" + }, + "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." } } diff --git a/docs/investmoz/unification/traceability-and-manual.md b/docs/investmoz/unification/traceability-and-manual.md index 8636dacf..50492dd1 100644 --- a/docs/investmoz/unification/traceability-and-manual.md +++ b/docs/investmoz/unification/traceability-and-manual.md @@ -7656,3 +7656,14 @@ Incremento de apresentação sobre `fb9a363e`, nos requisitos existentes V2-07/V Conferência dirigida: seis testes frontend existentes passaram; 22 verificações SSR cobriram os três modos, rótulos de dados em falta, preparação inactiva/ausente, ligação de continuação, histórico preservado e imutabilidade da origem. Tipos, formatação, lint e compilação QA passaram. Provas anteriores reutilizadas. Recibo no campo `adaptive_summary_increment` de `local-validation-20261029.json`. **Conferência visual pendente pela equipa:** na candidata, abrir o resumo de preparação activo, conferir situação/próxima acção/dados em falta sem expandir detalhes, continuar pelo formulário e consultar histórico/fontes; verificar também preparação inactiva e ausência de dados em falta. Registar revisão, perfil, percurso e resultado nesta matriz. SSR não comprova apresentação no navegador. Sem instalação ou avanço de main; este incremento não encerra os requisitos transversais. + + +### 29/10/2026 — percurso adaptativo: documentos em falta e recuperação das associações + +Continuação sobre `d662e634`, nos requisitos existentes V2-07/V2-10 e na decisão adaptativa da Tempo de 23/10. O resumo distingue documentos obrigatórios por associar, documentos associados cujo conteúdo continua por conferir e exigências documentais ainda por determinar; o histórico sem aplicação não é contado como pendência. Com dados completos e âmbito/período cobertos, a acção de continuação conduz à secção existente de documentos. Nos restantes estados mantém o destino do formulário. São decisões de apresentação/navegação; não criam novas condições de negócio. + +No editor, os documentos ainda por associar mantêm-se visíveis; os já associados ficam em «Documentos já associados — consultar ou alterar». A retirada deliberada permanece por confirmar na comparação de impacto e não é substituída pelo identificador guardado. O agrupamento usa o estado recuperado, conservando o controlo durante a edição. Dados, documentos originais, histórico, regras fiscais, backend, schema e permissões preservados. + +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. diff --git a/resources/js/components/private-investment-preparation.tsx b/resources/js/components/private-investment-preparation.tsx index 97b1e6b8..59fc89f4 100644 --- a/resources/js/components/private-investment-preparation.tsx +++ b/resources/js/components/private-investment-preparation.tsx @@ -64,6 +64,23 @@ export function PreparationSummary({ caseId?: string; }) { if (!data) return null; + const missingDocuments = data.requirements.filter( + (requirement) => + requirement.required && requirement.state === 'missing', + ).length; + const associatedDocuments = data.requirements.filter( + (requirement) => + requirement.required && requirement.state === 'associated', + ).length; + const undeterminedDocuments = data.requirements.filter( + (requirement) => requirement.state === 'indeterminate', + ).length; + const continueWithDocuments = + data.enabled && + data.period_covered && + data.scope_covered && + data.missing_fields.length === 0 && + missingDocuments > 0; return (
)} + {missingDocuments > 0 && ( +

Documentos por associar: {missingDocuments}.

+ )} + {associatedDocuments > 0 && ( +

+ Documentos associados: {associatedDocuments} — + conteúdo por conferir. +

+ )} + {undeterminedDocuments > 0 && ( +

+ Exigências documentais por determinar:{' '} + {undeterminedDocuments}. +

+ )} )} {caseId && (

- Preencher ou continuar o formulário + {continueWithDocuments + ? 'Associar documentos do dossier' + : 'Preencher ou continuar o formulário'}

)} @@ -336,6 +370,39 @@ export function PreparationEditor({ data.mode !== 'authorization' || data.missing_fields.includes(q.key)), ); + const renderDocument = (r: Preparation['requirements'][number]) => ( + + ); return (
{data.requirements - .filter((r) => r.required) - .map((r) => ( - - ))} + .filter( + (r) => r.required && r.state !== 'associated', + ) + .map(renderDocument)} + {data.requirements.some( + (r) => r.required && r.state === 'associated', + ) && ( +
+ + Documentos já associados — consultar ou + alterar + +
+ {data.requirements + .filter( + (r) => + r.required && + r.state === 'associated', + ) + .map(renderDocument)} +
+
+ )} )} diff --git a/tests/frontend/investor-usability-components.test.mjs b/tests/frontend/investor-usability-components.test.mjs index 7580432b..07d62579 100644 --- a/tests/frontend/investor-usability-components.test.mjs +++ b/tests/frontend/investor-usability-components.test.mjs @@ -358,3 +358,147 @@ void test('investment stages expose readable next actions while internal codes a assert.doesNotMatch(html, /]*>S01/); assert.match(html, /Fonte e referência desta etapa/); }); + +function preparationFixture() { + const requirement = (key, state, required = true) => ({ + key, + label: key, + state, + required, + document_id: state === 'associated' ? 'saved-document' : null, + document: null, + reason: 'Synthetic QA', + source: { url: '/source', reference: 'Synthetic QA' }, + }); + return { + version: 'private-investment-preparation-2026-v1', + enabled: true, + mode: 'registration', + period_covered: true, + scope_covered: true, + missing_fields: [], + questions: [ + { + key: 'investment_preparation', + label: 'Preparar?', + source: 'Synthetic QA', + }, + ], + next: 'Preparar documentos', + reason: [], + authority_source: { url: '/source' }, + source: { url: '/source' }, + requirements: [ + requirement('MISSING', 'missing'), + requirement('ASSOCIATED', 'associated'), + requirement('UNDETERMINED', 'indeterminate', false), + requirement('HISTORICAL', 'not_applicable', false), + ], + }; +} +const preparationMocks = { + '@inertiajs/react': { + Link: ({ href, children }) => + React.createElement('a', { href }, children), + }, + '@/pages/investment/shared': { card: 'card', input: 'input' }, +}; +void test('preparation directs complete context to missing documents without treating association or uncertainty as approval', () => { + const Summary = load( + '../../resources/js/components/private-investment-preparation.tsx', + preparationMocks, + 'PreparationSummary', + ); + const data = preparationFixture(); + const before = JSON.stringify(data); + const render = (overrides = {}) => + renderToStaticMarkup( + React.createElement(Summary, { + data: { ...data, ...overrides }, + caseId: 'synthetic', + }), + ); + const visible = render().split('
')[0]; + assert.match(visible, /Documentos por associar: 1/); + assert.match(visible, /Documentos associados: 1 — conteúdo por conferir/); + assert.match(visible, /Exigências documentais por determinar: 1/); + assert.match(visible, /#preparation-documents/); + assert.match(visible, /Associar documentos do dossier/); + for (const overrides of [ + { missing_fields: ['currency'] }, + { scope_covered: false }, + { period_covered: false }, + { enabled: false }, + { + requirements: data.requirements.filter( + (r) => r.state !== 'missing', + ), + }, + ]) { + assert.match( + render(overrides).split('
')[0], + /#preparation-questions/, + ); + } + assert.equal(JSON.stringify(data), before); +}); +void test('associated document controls remain editable in details and deliberate detachment survives rerender', () => { + const Editor = load( + '../../resources/js/components/private-investment-preparation.tsx', + preparationMocks, + 'PreparationEditor', + ); + const data = preparationFixture(); + const links = {}; + const props = { + data, + fields: { investment_preparation: 'yes' }, + changes: {}, + documents: [ + { + document_id: 'saved-document', + file_name: 'ORIGINAL.pdf', + status: 'active', + }, + ], + links, + onChange() {}, + onLink(key, value) { + links[key] = value; + }, + disabled: false, + }; + const tree = Editor(props); + const details = flat(tree).find( + (node) => + node.type === 'details' && + flat(node).some( + (child) => + child.type === 'summary' && + child.props.children === + 'Documentos já associados — consultar ou alterar', + ), + ); + assert.ok(details); + assert.ok(!details.props.open); + const existingSelect = flat(details).find((node) => node.type === 'select'); + assert.equal(existingSelect.props.value, 'saved-document'); + assert.equal(flat(tree).filter((node) => node.type === 'select').length, 2); + existingSelect.props.onChange({ target: { value: '' } }); + assert.deepEqual(links, { ASSOCIATED: null }); + const rerender = Editor(props); + assert.equal( + flat(rerender) + .filter((node) => node.type === 'select') + .every((node) => node.props.value === ''), + true, + ); + assert.equal(data.requirements[1].document_id, 'saved-document'); + const disabled = Editor({ ...props, disabled: true }); + assert.equal( + flat(disabled) + .filter((node) => node.type === 'select') + .every((node) => node.props.disabled), + true, + ); +});