From f86ecef44dfa7f71d76d81525ed143d96f5608ad Mon Sep 17 00:00:00 2001 From: nala Date: Wed, 28 Oct 2026 14:13:00 +0200 Subject: [PATCH] Valida concorrencia e aplica orcamento com recuperacao verificada --- README.md | 8 ++ app/Models/Budget.php | 14 ++ app/Services/BudgetWorkflow.php | 6 + docs/compatibilidade.md | 41 +++--- docs/desenvolvimento.md | 44 +++++++ docs/orcamento-concorrencia-postgresql.json | 44 +++++++ docs/orcamento-migracao-000009.json | 16 +++ resources/js/pages/budgets/index.tsx | 5 +- tests/Concurrency/budget-worker.php | 36 ++++++ tests/Concurrency/run-budgets.php | 135 ++++++++++++++++++++ tests/Feature/BudgetTest.php | 46 +++++++ tests/Support/apply-budget-migration.php | 105 +++++++++++++++ 12 files changed, 483 insertions(+), 17 deletions(-) create mode 100644 docs/orcamento-concorrencia-postgresql.json create mode 100644 docs/orcamento-migracao-000009.json create mode 100644 tests/Concurrency/budget-worker.php create mode 100644 tests/Concurrency/run-budgets.php create mode 100644 tests/Support/apply-budget-migration.php diff --git a/README.md b/README.md index 7043e10..1044e1a 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,14 @@ Crie um pedido, guarde o rascunho, confira os valores e submeta. Entre com as co ## Entrega actual +- Orçamentos por empresa, centro de custo e exercício, com montante e base + com/sem IVA explícitos, aprovação independente, reservas e compromissos auditados. + Migração 000009 aplicada após backup e restauro verificados; cinco cenários + concorrentes PostgreSQL aprovados. Sem dotações fictícias na base real. + Centros sem dotação mantêm o circuito de desenvolvimento sem controlo. + Facturas, reforços e pagamentos continuam pendentes; não constitui validação + operacional integral do orçamento. Interface em `/orcamentos`. + - Equipamentos e peças ligados aos artigos existentes, compatibilidades, criticidade configurada, mínimos explícitos e alertas, saídas rastreáveis e validação técnica. Finanças é obrigatória para equipamento em qualquer valor; a adjudicação/emissão de investimento continua bloqueada até definição do limite próprio. Migração 000008 aplicada no lote 9 após backup recuperável e verificação da preservação. Demonstração em `/consulta/equipamentos`. - Página inicial em `/`, demonstração pública por módulo em `/consulta` e navegação autenticada agrupada por área e perfil. - Recepções parciais/totais com qualidade, divergências, quantidades pendentes e segregação do emitente. Entradas de stock apenas para quantidades físicas aceites, sem duplicação; catálogo, armazéns, existências e histórico por empresa. diff --git a/app/Models/Budget.php b/app/Models/Budget.php index 99f1c09..9039fcf 100644 --- a/app/Models/Budget.php +++ b/app/Models/Budget.php @@ -16,6 +16,20 @@ class Budget extends Model protected $guarded = ['id']; + protected static function booted(): void + { + static::updating(function (Budget $budget) { + if ($budget->getRawOriginal('approved_by') !== null) { + throw new \LogicException('Uma dotação aprovada não pode ser reescrita.'); + } + }); + static::deleting(function (Budget $budget) { + if ($budget->approved_by !== null) { + throw new \LogicException('Preservar a dotação aprovada e os seus movimentos.'); + } + }); + } + protected function casts(): array { return ['includes_tax' => 'boolean']; diff --git a/app/Services/BudgetWorkflow.php b/app/Services/BudgetWorkflow.php index 38b5de2..d524154 100644 --- a/app/Services/BudgetWorkflow.php +++ b/app/Services/BudgetWorkflow.php @@ -6,6 +6,7 @@ use App\Models\Budget; use App\Models\BudgetEntry; use App\Models\Requisition; use App\Models\User; +use Illuminate\Support\Facades\DB; use Illuminate\Validation\ValidationException; class BudgetWorkflow @@ -15,6 +16,11 @@ class BudgetWorkflow */ public function record(User $user, Requisition $request, string $kind, int $amount = 0, ?int $subtotal = null): ?array { + abort_unless(auth()->id() === $user->id && $user->tenant_id !== null && $request->tenant_id === $user->tenant_id, 403); + if (DB::connection()->transactionLevel() === 0) { + throw new \LogicException('Movimentos de orçamento exigem transacção.'); + } + app(ConflictGuard::class)->lockCompany($user); $existing = BudgetEntry::query()->where('requisition_id', $request->id)->orderBy('id')->first(); $budget = $existing ? Budget::query()->lockForUpdate()->findOrFail($existing->budget_id) diff --git a/docs/compatibilidade.md b/docs/compatibilidade.md index 4585d27..5f42b5c 100644 --- a/docs/compatibilidade.md +++ b/docs/compatibilidade.md @@ -2,26 +2,37 @@ Estado actualizado em 28 de Outubro de 2026. Projecto activo: C:\laragon\www\compras, Laravel + React + PostgreSQL, http://127.0.0.1:8015. Preservar conta, dados, configuração, cookie e porta. SGF Tempo e Investmoz são independentes. A implementação Python separada fica apenas como referência; não importar contas, sessões ou bases experimentais. -| Área | Implementado no Laravel | Pendente | -| -------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | -| Acesso e organização | Fortify, conta, empresa, equipa, departamentos e centros de custo | MFA obrigatório e ciclo completo de acessos | -| Requisições e aprovações | Rascunhos, cálculos exactos, etapas, segregação e snapshots | Validação empresarial das regras provisórias | -| Fornecedores | Cadastro, edição, renovação, validação independente e anexos privados | Dados bancários com dupla validação | -| Cotações | Propostas, avaliações e matriz comparativa | Multimoeda e revisão das propostas | -| RF-AF-01 | Declarações pessoais, histórico e ConflictGuard no servidor | Política de periodicidade, validade e levantamento | -| Adjudicação | Escolha fundamentada, revalidação e aprovação do valor final | Excepções, revisão e cancelamento sem política ficam bloqueados | -| Ordens | Emissão única/idempotente, snapshots, consulta e impressão | Orçamento não validado; documentos de desenvolvimento | -| Recepção | Parcial/total, qualidade, divergências, pendentes, segregação do emitente e auditoria | Substituições e anexos; rejeições não autorizam receber acima do total | -| Stock | Artigos, armazéns, entradas, saídas fundamentadas, transferências atómicas, contagens e ajustes auditados por empresa | Revisão visual, reposição e conversões | -| Equipamentos e peças | Cadastro, compatibilidade, mínimos, alertas, validação técnica e rastreabilidade | Regra de investimento; adjudicação/emissão de equipamento bloqueadas | -| Orçamento, facturas e pagamentos | Ainda não implementados | Definir regras antes do próximo incremento | -| Início e navegação | Página inicial própria, menus por área/perfil e consulta pública por módulo | Revisão visual | -| Auditoria | Histórico das operações implementadas | Exportações, retenção e protecção contra SQL administrativo | +| Área | Implementado no Laravel | Pendente | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| Acesso e organização | Fortify, conta, empresa, equipa, departamentos e centros de custo | MFA obrigatório e ciclo completo de acessos | +| Requisições e aprovações | Rascunhos, cálculos exactos, etapas, segregação e snapshots | Validação empresarial das regras provisórias | +| Fornecedores | Cadastro, edição, renovação, validação independente e anexos privados | Dados bancários com dupla validação | +| Cotações | Propostas, avaliações e matriz comparativa | Multimoeda e revisão das propostas | +| RF-AF-01 | Declarações pessoais, histórico e ConflictGuard no servidor | Política de periodicidade, validade e levantamento | +| Adjudicação | Escolha fundamentada, revalidação e aprovação do valor final | Excepções, revisão e cancelamento sem política ficam bloqueados | +| Ordens | Emissão única/idempotente, snapshots, consulta e impressão | Orçamento não validado; documentos de desenvolvimento | +| Recepção | Parcial/total, qualidade, divergências, pendentes, segregação do emitente e auditoria | Substituições e anexos; rejeições não autorizam receber acima do total | +| Stock | Artigos, armazéns, entradas, saídas fundamentadas, transferências atómicas, contagens e ajustes auditados por empresa | Revisão visual, reposição e conversões | +| Equipamentos e peças | Cadastro, compatibilidade, mínimos, alertas, validação técnica e rastreabilidade | Regra de investimento; adjudicação/emissão de equipamento bloqueadas | +| Orçamento | Dotação por empresa/centro/exercício, aprovação independente, reservas e compromissos | Revisão visual, reforços e validação operacional integral | +| Facturas e pagamentos | Ainda não implementados | Conferência, consumo e liquidação | +| Início e navegação | Página inicial própria, menus por área/perfil e consulta pública por módulo | Revisão visual | +| Auditoria | Histórico das operações implementadas | Exportações, retenção e protecção contra SQL administrativo | Preservar enums, políticas, modelos relacionais, Brick Math e isolamento por empresa. As regras guardadas nos snapshots não são substituídas pela configuração actual. Ausência de conflito não anula conflito registado. Exemplos públicos fictícios não dão acesso aos dados de negócio. ## Verificação actual +Actualização de orçamento: cadastro e aprovação independente de dotações por +empresa/centro/exercício, com base de IVA explícita, reservas, libertações e +compromissos implementados. Migração 000009 aplicada no lote 10 após backup e +restauro verificados de 37 tabelas. Cinco cenários concorrentes PostgreSQL +aprovados; 14 testes de orçamento passaram com 40 asserções. Não foram criadas +dotações na base real. Facturas, reforços, pagamentos e validação operacional +integral continuam pendentes. Ver [relatório actual](desenvolvimento.md). + +### Referência anterior à aplicação do orçamento + Referência consolidada: `75ffdb3`, com **262 testes aprovados, 3 ignorados e 2.327 asserções**. Nova verificação dirigida de equipamentos, adjudicação, ordens e navegação: **87 testes aprovados, 1.163 asserções**. As onze migrações, diff --git a/docs/desenvolvimento.md b/docs/desenvolvimento.md index 11b4bdf..02c59c9 100644 --- a/docs/desenvolvimento.md +++ b/docs/desenvolvimento.md @@ -1,5 +1,49 @@ # Desenvolvimento do SGC +## Aplicação e validação PostgreSQL do orçamento + +- Migração 000009 aplicada à base actual no **lote 10**, sem migrações pendentes. + Backup custom PostgreSQL 17 de 206.615 bytes em armazenamento privado; + manifesto privado com hashes e instruções de recuperação. O dump e o manifesto + partilharam um snapshot consistente. Restauro verificado em base temporária: + **37 tabelas** coincidiram em contagens e hashes. Base de restauro eliminada. +- Registos de negócio anteriores e restrições preservados; `.env` inalterado. + Nenhuma conta, dotação ou movimento financeiro foi criado na base real. + As tabelas `budgets` e `budget_entries` permanecem vazias. Sessões, caches + e filas não integram a comparação de negócio, pois podem variar com os acessos. + [Evidência sem dados privados](orcamento-migracao-000009.json). +- **Cinco cenários concorrentes PostgreSQL aprovados**: excesso de reservas, + mesma reserva, mesmo compromisso, mesma libertação e excesso na adjudicação. + Duas transacções de serviço em processos PHP e ligações independentes por + cenário, observadas simultaneamente à espera de bloqueios. Saldos exactos e + ausência de duplicações confirmados. Base exclusiva de teste eliminada. + Isto não é teste HTTP, de carga ou de todos os fluxos financeiros. + [Evidência](orcamento-concorrencia-postgresql.json). +- Reforçada a autorização no serviço: autor autenticado, empresa do processo, + transacção obrigatória e bloqueio da empresa. Dotação já aprovada não admite + reescrita nem eliminação pelo modelo. Corrigida a legenda da dotação para + respeitar a base com/sem IVA escolhida pela empresa. +- **14 testes de orçamento aprovados, 40 asserções**, incluindo submissão real + sem saldo e rollback, submissão/reserva seguida de rejeição/libertação, + isolamento no serviço e imutabilidade da dotação aprovada. +- Suite completa concluída: **275 aprovados, 3 ignorados e 2.364 asserções**; + inclui os 13 casos de orçamento existentes no início dessa execução. O caso + adicional de imutabilidade foi validado depois no conjunto de 14 acima. + PHPStan, Pint, TypeScript, compilação e formatação/lint frontend aprovados. +- `/` e `/consulta/equipamentos` responderam HTTP 200; `/orcamentos` sem sessão + respondeu 302 para login. Nova tentativa de acesso ao navegador devolveu + `The browser access host is unavailable`. **Revisão visual pública, autenticada + e de impressão permanece pendente.** + +Cada empresa mantém dotações e base de IVA próprias; não existem valores +monetários de orçamento comuns a todos os clientes. Centros sem dotação continuam +no circuito de desenvolvimento sem controlo orçamental. Facturas, consumo validado, +reforços, pagamentos e regras próprias de investimento não estão implementados. +**A validação operacional integral do orçamento permanece pendente**, embora +as reservas e compromissos tenham sido testados e a migração esteja aplicada. + +## Histórico do primeiro incremento de orçamento + ## Primeiro incremento de orçamento por empresa Implementados cadastro de dotação anual por empresa/centro de custo, montantes diff --git a/docs/orcamento-concorrencia-postgresql.json b/docs/orcamento-concorrencia-postgresql.json new file mode 100644 index 0000000..6c0c224 --- /dev/null +++ b/docs/orcamento-concorrencia-postgresql.json @@ -0,0 +1,44 @@ +{ + "tested_at_utc": "2026-10-28T12:11:20+00:00", + "separate_database": true, + "transport": "Independent PHP service transactions and PostgreSQL connections", + "scenarios": [ + { + "name": "over_reserve", + "passed": true, + "overlapping_lock_waits": 2, + "reserved_minor": 6000, + "committed_minor": 0 + }, + { + "name": "same_reserve", + "passed": true, + "overlapping_lock_waits": 2, + "reserved_minor": 6000, + "committed_minor": 0 + }, + { + "name": "same_commit", + "passed": true, + "overlapping_lock_waits": 2, + "reserved_minor": 0, + "committed_minor": 6000 + }, + { + "name": "same_release", + "passed": true, + "overlapping_lock_waits": 2, + "reserved_minor": 0, + "committed_minor": 0 + }, + { + "name": "over_award", + "passed": true, + "overlapping_lock_waits": 2, + "reserved_minor": 8000, + "committed_minor": 0 + } + ], + "passed": true, + "test_database_dropped": true +} diff --git a/docs/orcamento-migracao-000009.json b/docs/orcamento-migracao-000009.json new file mode 100644 index 0000000..c56994e --- /dev/null +++ b/docs/orcamento-migracao-000009.json @@ -0,0 +1,16 @@ +{ + "verified_at_utc": "2026-10-28T12:09:22+00:00", + "migration": "2026_10_28_000009_create_budgets", + "batch": 10, + "backup_created": true, + "backup_bytes": 206615, + "restore_verified": true, + "restored_tables": 37, + "restore_database_dropped": true, + "business_records_unchanged": true, + "existing_constraints_preserved": true, + "env_unchanged": true, + "budgets_count": 0, + "budget_entries_count": 0, + "operational_validation": false +} diff --git a/resources/js/pages/budgets/index.tsx b/resources/js/pages/budgets/index.tsx index 8a25a98..7839acf 100644 --- a/resources/js/pages/budgets/index.tsx +++ b/resources/js/pages/budgets/index.tsx @@ -118,7 +118,7 @@ export default function Budgets({ />