# Controlo orçamental comum — entrega técnica em verificação **Actualização posterior de 29/10/2026:** extracção técnica aceite pela Tempo, com autorização de checkpoint exclusivamente local. Ver [aceitação e pendências preservadas](COMMON_BUDGET_ACCEPTANCE.md). A revisão visual de Compras continua pendente; SGF continua demonstrado apenas pelo adaptador QA, sem interface deste percurso; os dois testes MFA ignorados continuam pendentes. As menções abaixo a aceitação/checkpoint por realizar são históricas; não há activação operacional decorrente desta aceitação. 29/10/2026. `COMMON-BUDGET-SYNTHETIC-v1`. Implementação do primeiro incremento de [desenho e transição](PURCHASING_COMMON_CORE_PLAN.md). **Sem aceitação presumida ou checkpoint de encerramento.** Apenas provas isoladas; nenhuma migração, concessão ou operação nas bases de desenvolvimento/produção. Não houve publicação ou push. ## Resultado e fronteira Existe uma fonte única em `packages/budget-control`: `Tempo\Finance\Budget\BudgetControl`, contexto explícito `BudgetContext` e erro de domínio `BudgetFailure`. Foram retirados os algoritmos de movimentos/reforços/consumo de Compras; os percursos anteriores delegam neste núcleo. O núcleo grava dotação, aprovação, reforço, reserva, ajuste de adjudicação, compromisso, libertação permitida e consumo orçamental. Saldo/total aprovado também são calculados pelo núcleo. Não chama `auth()`, sessão ou contexto global de uma aplicação, nem depende de Sales, recebimentos, contas a pagar, pagamentos, contabilidade ou de um SGF remoto. Usa `Illuminate\Database\ConnectionInterface` já disponibilizada pelos hosts, contexto imutável, tabelas por prefixo e callbacks locais de autorização/bloqueio/auditoria. A autorização é revalidada antes de qualquer replay; a auditoria participa na transacção do efeito. O primeiro consumidor é Compras, nos seus percursos efectivos. O segundo é a composição reduzida de QA do SGF: bootstrap Laravel real, modelos de identidade/contexto/permissões reais e adaptador `app/Modules/Purchasing/BudgetAdapter.php`. O adaptador SGF recusa funcionar fora de `budget-testing` e de uma base descartável `budget_core_sgf_<12 hex>`, PostgreSQL local verificado. **Não se declara instalado o módulo completo de Compras no SGF, nova interface visual ou circuito de procurement SGF completo.** A origem SGF é uma referência sintética mapeada para testar o primeiro efeito comum; não é uma obrigação ou factura operacional. ## Dependências e valores - Mantidos manifests/lockfiles: Laravel 13.39.0 / Brick Math 1.0.0 de Compras e Laravel 13.38.0 / Brick Math 0.14.8 do SGF. Nenhuma instalação, actualização ou substituição de dependências. - O núcleo usa os inteiros em centavos já gravados por Compras. Não calcula IVA, não converte câmbio e não introduz float. MZN é o recorte suportado; o adaptador Compras verifica a moeda empresarial antes/depois do bloqueio. Outras moedas permanecem explicitamente não suportadas. - Conversão decimal de entrada continua no Brick Math da aplicação Compras, exactamente pelos caminhos anteriores. Quantidades em milésimos, arredondamento HalfUp das linhas e cálculo cumulativo de facturas parciais permanecem em `InvoiceWorkflow`/serviços anteriores. Política SGF de truncagem do IVA de vendas não é aplicada a compras. - Não se recalcularam documentos nem se reescreveram valores históricos. O total inicial permanece imutável no percurso normal; apenas reforços aprovados contam para a dotação total. - A chave histórica `(requisition_id, kind)` continua a identificar o efeito. Replay idêntico não grava; o núcleo reconstrói o alvo monetário da entrada histórica para recusar replay com montante diferente, sem exigir hash ou migração retroactiva. Esta recusa é um reforço explícito: antes, o percurso de movimentos devolvia silenciosamente o efeito existente mesmo quando o montante pedido diferia. Não altera a história nem cria nova versão de documentos. ## Adaptadores e responsabilidade | Ponto | Papel nesta entrega | | --- | --- | | Compras `app/Services/CommonBudget.php` | Confere sessão/actor, empresa, moeda e perfil actual; bloqueia empresa/actor; traduz erros; liga auditoria e elegibilidade do proponente | | Compras `BudgetWorkflow.php` | Conserva contrato dos chamadores, exigência de transacção exterior, vínculo da requisição e classificação corrente/investimento; delega o efeito | | Compras `BudgetReinforcementWorkflow.php` | Conserva conversão decimal/normalização de referência e retorno Eloquent; delega proposta/decisão | | Compras `BudgetController.php` | Conserva interface/validação/autorizações de origem; delega dotação, aprovação e saldos | | Compras `InvoiceWorkflow.php` | Conserva conferência de factura/preço/IVA/quantidades, actor distinto e auditoria `invoice.validated`; delega apenas consumo orçamental na mesma transacção | | Compras `Budget.php` | Mantém modelo/imutabilidade e usa a soma inteira comum do total aprovado | | SGF `app/Modules/Purchasing/BudgetAdapter.php` | Confere actor autenticado e contexto seleccionado, revalida membership/acções por unidade dentro do bloqueio e usa origens/centros explicitamente mapeados de QA | | `BudgetControl` | Único escritor do ledger/dotação/reforços dentro do recorte; não substitui elegibilidade de procurement ou conferência do documento | Compras continua a tratar o seu `tenant_id` como empresa. SGF usa separadamente tenant/empresa/unidade; o teste cria identificadores diferentes e um centro de compras próprio. Nenhum departamento ou lote industrial é convertido implicitamente em unidade/dotação. O orçamento industrial existente do SGF não foi alterado. ## Fonte única e distribuição autónoma O provider SGF carrega `packages/budget-control/bootstrap.php`. Compras procura o artefacto instalado em `packages/budget-control`, ou o caminho explícito `config/common-budget.php` / `COMMON_BUDGET_PATH`. Só em ambiente não produtivo existe fallback para a fonte canónica do checkout SGF vizinho. Não há chamadas remotas. A prova autónoma empacota temporariamente apenas bootstrap e os três ficheiros de produção do núcleo, fora de ambos os projectos, e arranca Compras usando esse artefacto destacado. O teste exige que a classe efectivamente carregada venha do artefacto. O SGF carrega a fonte canónica. Os SHA-256 e traços normalizados coincidem. O artefacto temporário é eliminado no final, não constitui uma segunda fonte mantida. Para distribuição comercial, o artefacto local versionado terá de acompanhar Compras; não basta enviar o checkout sem o pacote. Automatização completa de releases/licenciamento não está incluída. ## Provas executadas Comando dos dois hosts, a partir de Compras: ```powershell & 'C:\Users\macbook\.config\investmoz\php-8.5.11\php.exe' tests/Concurrency/run-common-budget.php ``` O coordenador usa uma ligação administrativa à base de manutenção `postgres` exclusivamente para criar/eliminar duas bases novas. Não consulta ou migra bases de negócio. Em Compras aplica as migrações existentes apenas a `compras_receipts_test_<12 hex>` e usa as fixtures sintéticas existentes; no SGF aplica a fundação de identidade e o schema de QA apenas a `budget_core_sgf_<12 hex>`. Bases eliminadas no final. Configurações privadas são consumidas em processo, sem imprimir/versionar credenciais. Não se fez comparação de dados activos nem se reivindica checksum desses dados. Evidência dirigida: [dois hosts e concorrência](proofs/common-budget-hosts.json). O relatório inclui hash do núcleo realmente carregado, igualdade dos traços, bases descartadas e ausência de revisão visual. | Sequência, em ambos os hosts | Resultado em centavos | | --- | --- | | Dotação aprovada 100000; reserva 30000 repetida | Reservado 30000; disponível 70000 | | Ajuste da adjudicação para 35000 repetido | Reservado 35000; disponível 65000 | | Compromisso 35000 repetido | Reservado 0; comprometido 35000; disponível 65000 | | Consumo parcial 10000 repetido | Comprometido 25000; consumido 10000; disponível 65000 | | Reforço 20000 proposto | Disponível permanece 65000 | | Aprovação distinta do reforço repetida | Dotação total 120000; disponível 85000 | Também passaram, nos dois hosts: recusa de aprovação própria; conflito de montante em replay; recusa de libertação já comprometida; isolamento de dotação entre empresas; libertação antes de compromisso uma única vez; base sem IVA usando subtotal original; falha SQL injectada na auditoria com rollback da reserva e retry sem duplicação; replay de decisão com permissão entretanto revogada. No SGF: origem de outra empresa recusada, unidade reservada recusada e contexto mudado após obter o serviço recusado. **Concorrência PostgreSQL: oito cenários aprovados, quatro por host.** Duas ligações/processos PHP independentes, com dois waits de bloqueio observados em cada cenário: duas reservas acima do saldo conjunto; repetição da mesma reserva; repetição do mesmo compromisso; repetição da mesma libertação. No excesso apenas um efeito de 6000 cabe na dotação 10000; nos replays ambos os pedidos obtêm sucesso e o saldo só muda uma vez. Não é uma prova de todas as corridas possíveis ou teste de carga. ## Regressões e compilação - Referência antes da alteração: 75 testes dirigidos / 317 asserções aprovados. Mesma bateria após extracção: 75 / 317, sem mudar expectativas existentes. - Regressão completa Compras: **498 aprovados, 2 ignorados, 4141 asserções**, incluindo percursos/autorização existentes. Os dois ignorados são as limitações MFA anteriormente documentadas; não se declarou cobertura nova desses casos. - PHPStan Compras: zero erros. Pint dos ficheiros de produção alterados: aprovado. Sintaxe PHP 8.4 do pacote/adaptador/fixtures SGF: aprovada. - Compilação Compras: `npm.cmd run build`, aprovada; compilação SGF: `scripts/sgf.ps1 npm run build`, aprovada. Sem alterações de dependências; geração Wayfinder ocorreu pelo build habitual. - A suite destrutiva SGF normal não foi executada sobre `sgf_tempo_test`, para preservar a demonstração da equipa. O delta SGF foi verificado no host/bootstrap real com base descartável e nas verificações acima. Não se atribui regressão integral de todos os módulos SGF a esta prova. - Uma tentativa inicial com outro PHP existente falhou por falta do driver SQLite, sem asserções ou alteração de base. A bateria válida usou o executável já instalado com SQLite. O primeiro comando `npm` foi recusado pela política PowerShell; usou-se `npm.cmd`, sem mudar a política. ## Ficheiros deste incremento **Fonte comum no SGF:** `packages/budget-control/bootstrap.php`, `README.md`, `src/BudgetContext.php`, `src/BudgetFailure.php`, `src/BudgetControl.php`; provas reproduzíveis em `tests/host-support.php`, `tests/run-host.php`, `tests/sgf-schema.php` dentro do mesmo pacote. **SGF:** `app/Providers/AppServiceProvider.php`, `app/Modules/Purchasing/BudgetAdapter.php`, este relatório, `docs/proofs/common-budget-hosts.json` e referências nos documentos/instruções existentes. **Compras:** `app/Providers/AppServiceProvider.php`, `config/common-budget.php`, `app/Services/CommonBudget.php`, `BudgetWorkflow.php`, `BudgetReinforcementWorkflow.php`, `InvoiceWorkflow.php`, `app/Models/Budget.php`, `app/Http/Controllers/BudgetController.php`, `tests/Concurrency/run-common-budget.php` e referências nos relatórios existentes. Alterações anteriores de oficinas/navegação, fontes e histórico SGF e documentação prévia de Compras foram preservadas, sem commit, reset ou checkout. Não são atribuídas a este incremento. ## Migrações para desenvolvimento/produção — separadas e não executadas **Compras:** o adaptador conserva as tabelas/colunas/índices existentes; esta extracção não necessita de migração SQL para os efeitos existentes. Não foram executadas migrações nessas bases. A distribuição tem de incluir o pacote e conservar os seus originais e lockfiles. **SGF:** o único schema executado é a fixture `packages/budget-control/tests/sgf-schema.php`, que tem guarda QA e não está registada em `database/migrations`. Documenta `cb_centres`, `cb_origins`, `cb_budgets`, `cb_budget_entries`, `cb_budget_consumptions`, `cb_budget_reinforcements`, `cb_audit` e as sete acções `purchasing_budget.*`, mais a extensão da constraint de permissões na base nova. Nenhuma concessão real foi instalada. A futura migração SGF terá de adaptar essas referências aos cadastros definitivos de Purchasing/Platform, completar constraints compostas de pertença e políticas de imutabilidade, conferir schema/permissões de destino e ser ensaiada com backup/restauro. Não apresentar esta fixture como migração operacional pronta nem retirar a guarda para apontar para `sgf_tempo_dev`. Não fundir tabelas/IDs/históricos existentes por igualdade numérica. ## Limitações e próximos passos Revisão visual não realizada; nenhum ecrã novo apresentado como aceite. SGF demonstra o efeito orçamental do primeiro incremento, não Compras integral instalado. O pacote não inclui obrigações, pagamentos ou contabilidade, emissão real, câmbio, novas reversões ou cancelamento de ordens já emitidas. A revogação/concessão de direitos reais permanece fora desta entrega. Próximo passo dentro desta entrega: revisão do desenho/código/provas e dificuldades que a equipa identificar, sem presumir aceitação. Depois, preparar o empacotamento e os pontos de montagem do módulo completo nos dois produtos com os cadastros definitivos. T2 (obrigações), T3 (pagamentos) e restantes passos do plano não foram iniciados. As capacidades inexistentes continuam pendentes, sem motores fictícios ou reaproveitamento indevido de recebimentos de clientes/orçamento industrial.