diff --git a/docs/SUPPLIER_PAYABLE_DELIVERY.md b/docs/SUPPLIER_PAYABLE_DELIVERY.md index e89a13f..a8bb08e 100644 --- a/docs/SUPPLIER_PAYABLE_DELIVERY.md +++ b/docs/SUPPLIER_PAYABLE_DELIVERY.md @@ -138,4 +138,6 @@ Próximo passo: a equipa testa as duas interfaces e relata função, origem/capt ## Retoma da interface — 30/10/2026 +Retoma automática posterior: [resultados consolidados SGF/Compras](SUPPLIER_SETTLEMENT_CONSOLIDATION_20261030.md). Verificações adicionais aprovadas sem alterar motor, permissões existentes ou dados persistentes; revisão visual separada e pendente. + A página de obrigações passou a mostrar afectações novas e saldo por liquidar da posição canónica, preservando o principal. Percurso HTTP/HTML repetido em Compras e SGF: 11 600 MZN → pagamento confirmado sem abatimento → afectação confirmada de 4 000 → saldo 7 600; vencimento/dias nulos. Provas anteriores preservadas. Resultados, correcções de navegação e bloqueio visual no [relatório de pagamentos](SUPPLIER_SETTLEMENT_DELIVERY.md), secção Consolidação do percurso. Sem habilitação pública, mudança de permissões ou encerramento presumido. diff --git a/docs/SUPPLIER_SETTLEMENT_CONSOLIDATION_20261030.md b/docs/SUPPLIER_SETTLEMENT_CONSOLIDATION_20261030.md new file mode 100644 index 0000000..09ae17d --- /dev/null +++ b/docs/SUPPLIER_SETTLEMENT_CONSOLIDATION_20261030.md @@ -0,0 +1,34 @@ +# Consolidação automática — SGF e Compras, 30/10/2026 + +Conferidos primeiro SUPPLIER_SETTLEMENT_DELIVERY.md, SUPPLIER_PAYABLE_DELIVERY.md e as respectivas provas. A sequência HTTP/HTML já estava aprovada nas duas composições. Concorrência, isolamento, revogação e recuperação têm provas existentes, reutilizadas sem repetir a bateria. O SHA-256 de SupplierSettlements.php continua igual ao da prova original: `156bd40f3080de66a73c0befd975990e9d7010c2a232a86d751d4db3af4fae93`. + +## Cobertura completada + +Acrescentadas asserções em `scripts/settlements-interface-check.mjs`: dívida inalterada enquanto o pagamento é proposto; repetição da confirmação com nova chave recupera a mesma decisão; actor de confirmação diferente do preparador tanto no pagamento como na afectação; exactamente quatro eventos, um por etapa, sem eventos adicionais na repetição; IDs do histórico preservados após sair e voltar a entrar. + +Estas verificações precisaram de uma nova sequência de preparação em bases descartáveis. Nenhum direito foi modificado nas instalações existentes: o instalador de fixture apenas preparou os acessos sintéticos das bases novas de prova. Nenhum comando financeiro foi executado nas demonstrações persistentes. + +| Resultado HTTP automático | Compras autónomo | SGF | +| --- | --- | --- | +| Principal reconhecido | 11 600 MZN | 11 600 MZN | +| Pagamento de 4 000 proposto e depois confirmado | Dívida mantém 11 600 MZN | Dívida mantém 11 600 MZN | +| Afectação proposta, ainda não confirmada | Dívida mantém 11 600 MZN | Dívida mantém 11 600 MZN | +| Afectação de 4 000 confirmada por outra identidade | Saldo 7 600 MZN | Saldo 7 600 MZN | +| Vencimento / dias de atraso | null / null; indeterminado | null / null; indeterminado | +| Repetição e reentrada | Um pagamento, uma afectação, quatro eventos | Um pagamento, uma afectação, quatro eventos | + +Evidência: [nova prova](proofs/supplier-settlements-consolidation-20261030.json). Preservadas as provas supplier-settlements.json e supplier-settlements-interface-20261030.json. + +Executados `settlements-interface-run.py`, sintaxe Node do verificador, regressão de navegação/página inicial (`home-navigation-check.mjs`) e `git diff --check`, todos aprovados. O runner terminou e removeu os seus processos, bases e roles. Nenhum novo defeito funcional foi encontrado; só o verificador e documentação foram alterados. Builds e regressões financeiras anteriores são reutilizados: não houve alteração de código de aplicação, motor, adaptadores ou dependências nesta retoma. + +## Limitações e encerramento + +**Revisão visual pendente**, sem tentativa de navegador nesta retoma. O diagnóstico anterior mais recente substitui o antigo bloqueio de pipe: Firefox interrompido com `browser URL policy enforcement is not yet supported for the current Windows browser`. Não se contornou o bloqueio. HTTP/HTML e VM não comprovam disposição, legibilidade, foco ou utilização por cliques. + +MFA continua com dois testes ignorados já documentados: `users with two factor enabled are redirected to two factor challenge` e `security page is displayed`. São cobertura de autenticação pendente, não prova do efeito monetário. + +## Aceitação técnica e checkpoint local — 30/10/2026 + +O responsável aceitou expressamente esta consolidação e autorizou o checkpoint exclusivamente local, preservando alterações alheias e excluindo credenciais e dados privados. Fica aceite o resultado técnico nas duas composições; a revisão visual continua pendente e não é declarada realizada ou aceite. Os dois testes MFA permanecem pendentes nos termos acima. + +O checkpoint inclui apenas o verificador, a prova sintética e a documentação desta consolidação. Não autoriza utilização real, publicação ou habilitação do percurso público. Não houve alteração de permissões existentes, operações bancárias, correcções de afectações ou novos módulos. A página inicial mantém-se limpa. Mantêm-se os restantes limites do relatório de entrega, incluindo históricos importados e ausência de reconciliação/contabilidade/stock/orçamento adicional. diff --git a/docs/SUPPLIER_SETTLEMENT_DELIVERY.md b/docs/SUPPLIER_SETTLEMENT_DELIVERY.md index be3cf55..c755086 100644 --- a/docs/SUPPLIER_SETTLEMENT_DELIVERY.md +++ b/docs/SUPPLIER_SETTLEMENT_DELIVERY.md @@ -4,6 +4,8 @@ Implementado o incremento seguinte às obrigações, nas duas composições, com ## Responsabilidade e código +Actualização de 30/10/2026: [consolidação automática sem navegador](SUPPLIER_SETTLEMENT_CONSOLIDATION_20261030.md). Complementa as provas abaixo, sem as substituir; cobertura adicional de repetição da confirmação, identidade distinta e eventos únicos aprovada nas duas composições. Revisão visual continua pendente. + O código comum reside em `packages/payables/src/SupplierSettlements.php`, `SettlementSchema.php`, `SettlementHttp.php` e `ui/settlements.php`. Carregamento e apresentação em `bootstrap.php` e `src/Workspace.php`. `src/Payables.php` conserva a responsabilidade única pelo saldo: principal menos histórico confirmado existente e afectações novas confirmadas. Não se escrevem pagamentos externos legados para representar novamente o mesmo facto. Os adaptadores são `app/Modules/Purchasing/SupplierPaymentsController.php` no SGF e `app/Http/Controllers/SupplierPaymentsController.php` em Compras, apoiados nos respectivos `SupplierPayables.php`. Contexto, pessoa, organização e direitos são revalidados no servidor pelos adaptadores existentes; o motor recebe contexto explícito. Rotas em `routes/web.php` de cada aplicação. A página anterior de obrigações apresenta a ligação quando disponível. No SGF, `resources/js/WorkspaceNavigation.jsx` e `app.jsx` incluem a entrada; nenhuma concessão real foi adicionada. diff --git a/docs/proofs/supplier-settlements-consolidation-20261030.json b/docs/proofs/supplier-settlements-consolidation-20261030.json new file mode 100644 index 0000000..bcb1c53 --- /dev/null +++ b/docs/proofs/supplier-settlements-consolidation-20261030.json @@ -0,0 +1,93 @@ +{ + "checked_at": "2026-10-30T21:28:42.382Z", + "core_unchanged": true, + "core_sha256": "156bd40f3080de66a73c0befd975990e9d7010c2a232a86d751d4db3af4fae93", + "hosts": [ + { + "host": "compras", + "passed": true, + "kind": "HTTP pages, form availability, submitted commands and JavaScript syntax; not browser execution", + "stages": [ + { + "stage": "recognised", + "debt_minor": 1160000 + }, + { + "stage": "payment_confirmed_before_allocation", + "debt_minor": 1160000, + "unallocated_minor": 400000 + }, + { + "stage": "allocation_confirmed", + "debt_minor": 760000, + "unallocated_minor": 0 + } + ], + "principal_minor": 1160000, + "due_on": null, + "days_overdue": null, + "payments": 1, + "allocations": 1, + "repeat_recovered": true, + "payment_confirmation_repeat_recovered": true, + "event_counts": { + "supplier_payment.proposed": 1, + "supplier_payment.confirmed": 1, + "supplier_allocation.proposed": 1, + "supplier_allocation.confirmed": 1 + }, + "distinct_validation_actors": true, + "history_after_reentry": true, + "canonical_balance_on_both_pages": true, + "role_destination_retained": true, + "external_redirect_refused": true, + "read_only_without_forms": true, + "visual_review": false + }, + { + "host": "sgf", + "passed": true, + "kind": "HTTP pages, form availability, submitted commands and JavaScript syntax; not browser execution", + "stages": [ + { + "stage": "recognised", + "debt_minor": 1160000 + }, + { + "stage": "payment_confirmed_before_allocation", + "debt_minor": 1160000, + "unallocated_minor": 400000 + }, + { + "stage": "allocation_confirmed", + "debt_minor": 760000, + "unallocated_minor": 0 + } + ], + "principal_minor": 1160000, + "due_on": null, + "days_overdue": null, + "payments": 1, + "allocations": 1, + "repeat_recovered": true, + "payment_confirmation_repeat_recovered": true, + "event_counts": { + "supplier_payment.proposed": 1, + "supplier_payment.confirmed": 1, + "supplier_allocation.proposed": 1, + "supplier_allocation.confirmed": 1 + }, + "distinct_validation_actors": true, + "history_after_reentry": true, + "canonical_balance_on_both_pages": true, + "role_destination_retained": true, + "external_redirect_refused": true, + "read_only_without_forms": true, + "visual_review": false + } + ], + "visual_blocker": "Previous Firefox attempt stopped: browser URL policy enforcement is not yet supported for the current Windows browser. Browser not retried in this consolidation.", + "public_enabled": false, + "bank_execution": false, + "accepted_proof_preserved": true +} diff --git a/scripts/settlements-interface-check.mjs b/scripts/settlements-interface-check.mjs index 1ff32c0..38a984a 100644 --- a/scripts/settlements-interface-check.mjs +++ b/scripts/settlements-interface-check.mjs @@ -41,8 +41,11 @@ for(const mode of ['compras','sgf']) { await checkFormFeedback(html); const paymentBody={obligation_id:obligation.id,source_revision:obligation.source_revision,amount:'4000.00',currency:'MZN',paid_on:new Date().toISOString().slice(0,10),fact_reference:'SIM-INTERFACE-PAY-001',evidence_reference:'SIM-INTERFACE-EVID-001',reason:'Pagamento sintético de 4 000 MZN sem execução bancária'}; const payment=await p.submit('/api/supplier-payments','payment-prepare',paymentBody);assert.equal((await p.submit('/api/supplier-payments','payment-prepare',paymentBody)).payment_id,payment.payment_id); + data=await q.position();assert.equal(data.obligations[0].balance_minor,1160000);assert.equal(data.payments[0].state,'proposed'); await v.enter('validacao','payments');await v.page('/supplier-payments','payment-decide');const decision={payment_id:payment.payment_id,decision:'confirmed',evidence_reference:'SIM-INTERFACE-CONF-001',reason:'Conferência independente de evidência artificial'}; - await v.submit('/api/supplier-payments','payment-decide',decision);data=await q.position();assert.equal(data.obligations[0].balance_minor,1160000);assert.equal(data.payments[0].unallocated_minor,400000);stages.push({stage:'payment_confirmed_before_allocation',debt_minor:1160000,unallocated_minor:400000}); + const paymentDecision=await v.submit('/api/supplier-payments','payment-decide',decision); + assert.equal((await v.submit('/api/supplier-payments','payment-decide',decision)).decision_id,paymentDecision.decision_id); + data=await q.position();assert.equal(data.obligations[0].balance_minor,1160000);assert.equal(data.payments[0].unallocated_minor,400000);stages.push({stage:'payment_confirmed_before_allocation',debt_minor:1160000,unallocated_minor:400000}); html=await q.page(obligations);assert(/Saldo por liquidar: 11 600\.00 MZN/.test(html)); await p.page('/supplier-payments','allocation-prepare');const plan={payment_id:payment.payment_id,reference:'SIM-INTERFACE-ALLOC-001',allocations:[{obligation_id:obligation.id,source_revision:obligation.source_revision,amount:'4000.00'}],reason:'Afectação explícita sem segundo efeito na obrigação'}; const allocation=await p.submit('/api/supplier-payments','allocation-prepare',plan);assert.equal((await q.position()).obligations[0].balance_minor,1160000); @@ -52,11 +55,19 @@ for(const mode of ['compras','sgf']) { html=await q.page('/supplier-payments');assert(html.includes('7 600.00 MZN'));assert(html.includes('Atraso indeterminado'));assert(html.includes('Dias de atraso: sem valor'));assert(!html.includes('data-action=')); html=await q.page(obligations);assert(/Saldo por liquidar: 7 600\.00 MZN/.test(html));assert(html.includes('11 600.00 MZN'));assert(html.includes('4 000.00 MZN'));assert(html.includes('Atraso indeterminado')); await p.page('/supplier-payments');assert(!(await p.page('/supplier-payments')).includes('data-action="allocation-prepare"'),'Exhausted payment should not be offered for new allocation'); - await q.leave();await q.enter('consulta','payments');data=await q.position();assert.equal(data.obligations[0].balance_minor,760000);assert.equal(data.allocations.length,1);assert(data.events.length>=4); + const eventCounts=Object.fromEntries([...new Set(data.events.map(e=>e.kind))].map(kind=>[kind,data.events.filter(e=>e.kind===kind).length])); + assert.equal(data.events.length,4);assert(Object.values(eventCounts).every(count=>count===1)); + const paymentEvent=data.events.find(e=>e.kind==='supplier_payment.confirmed'),allocationEvent=data.events.find(e=>e.kind==='supplier_allocation.confirmed'); + assert(paymentEvent);assert(allocationEvent); + assert.notEqual(String(data.payments[0].prepared_by),String(paymentEvent.actor_id)); + const allocationProposal=data.allocation_proposals.find(item=>Number(item.id)===allocation.proposal_id); + assert.notEqual(String(allocationProposal.prepared_by),String(allocationEvent.actor_id)); + const eventIds=data.events.map(e=>e.id); + await q.leave();await q.enter('consulta','payments');data=await q.position();assert.equal(data.obligations[0].balance_minor,760000);assert.equal(data.allocations.length,1);assert.deepEqual(data.events.map(e=>e.id),eventIds); await q.page('/demo?next=payments');const bad=await q.request('/demo/entrar','POST',{role:'consulta',next:'https://example.test'},true);assert.equal(bad.status,422); await p.leave();await v.leave();await q.leave(); - hosts.push({host:mode,passed:true,kind:'HTTP pages, form availability, submitted commands and JavaScript syntax; not browser execution',stages,principal_minor:1160000,due_on:null,days_overdue:null,payments:1,allocations:1,repeat_recovered:true,history_after_reentry:true,canonical_balance_on_both_pages:true,role_destination_retained:true,external_redirect_refused:true,read_only_without_forms:true,visual_review:false}); + hosts.push({host:mode,passed:true,kind:'HTTP pages, form availability, submitted commands and JavaScript syntax; not browser execution',stages,principal_minor:1160000,due_on:null,days_overdue:null,payments:1,allocations:1,repeat_recovered:true,payment_confirmation_repeat_recovered:true,event_counts:eventCounts,distinct_validation_actors:true,history_after_reentry:true,canonical_balance_on_both_pages:true,role_destination_retained:true,external_redirect_refused:true,read_only_without_forms:true,visual_review:false}); console.log(mode+': full synthetic sequence passed; single payment/allocation, debt 11600 before allocation and 7600 after.'); } const core=await fs.readFile('packages/payables/src/SupplierSettlements.php');const coreSha=createHash('sha256').update(core).digest('hex');const accepted=JSON.parse(await fs.readFile('docs/proofs/supplier-settlements.json','utf8'));assert(accepted.hosts.every(h=>h.core_sha256===coreSha),'Financial core changed since accepted proof'); -await fs.writeFile('docs/proofs/supplier-settlements-interface-20261030.json',JSON.stringify({checked_at:new Date().toISOString(),core_unchanged:true,core_sha256:coreSha,hosts,visual_blocker:'Computer Use native pipe is unavailable: failed to connect native pipe (OS error 2)',public_enabled:false,bank_execution:false,accepted_proof_preserved:true},null,2)+'\n'); +await fs.writeFile('docs/proofs/supplier-settlements-consolidation-20261030.json',JSON.stringify({checked_at:new Date().toISOString(),core_unchanged:true,core_sha256:coreSha,hosts,visual_blocker:'Previous Firefox attempt stopped: browser URL policy enforcement is not yet supported for the current Windows browser. Browser not retried in this consolidation.',public_enabled:false,bank_execution:false,accepted_proof_preserved:true},null,2)+'\n');