feat: complete approval workflow

This commit is contained in:
2026-08-04 16:51:51 +01:00
parent c2136bc250
commit 809eab9d4a
26 changed files with 1156 additions and 41 deletions

View File

@@ -32,7 +32,6 @@ Nginx ──► Laravel + Inertia/React
- `app/Domain/Identity`: perfis, permissões e identidades externas.
- `app/Domain/Inventory`: cache local de ativos e consultas do catálogo.
- `app/Domain/Reservations`: disponibilidade, estados, regras e requisições.
- `app/Domain/Approvals`: decisões e concorrência de aprovação (a criar).
- `app/Domain/Approvals`: decisões, histórico e concorrência de aprovação.
- `app/Domain/Handovers`: entrega, devolução e incidentes (a criar).
- `app/Domain/Audit`: logs imutáveis pela interface.
@@ -53,6 +52,10 @@ Uma criação ou aprovação deve bloquear as linhas dos ativos, voltar a valida
Pré-reservas ficam bloqueantes em `held` enquanto aguardam decisão. `ReservationService` centraliza criação, cancelamento e expiração: todas as transições bloqueiam a requisição, atualizam os itens bloqueantes e criam um `AuditLog`. Um job único procura prazos vencidos a cada cinco minutos. A restrição GiST continua a proteger contra corridas entre estes ciclos de vida.
`ApprovalService` bloqueia a requisição e os ativos antes de aprovar ou substituir, revalida a disponibilidade e regista cada passo em `approval_actions` e `audit_logs`. Uma substituição cancela o item original e cria um novo `held`, apenas na mesma categoria. Pedidos e respostas de esclarecimento conservam o histórico e renovam explicitamente o prazo.
Notificações implementam `ShouldQueue`, usam `afterCommit` e seguem dois canais: email e tabela `notifications`. A caixa interna consulta exclusivamente a relação polimórfica do utilizador autenticado, evitando acesso horizontal a notificações de terceiros.
## Fluxo de dados Snipe-IT
1. Um job consulta a API paginada com token de leitura.

View File

@@ -62,3 +62,10 @@ Uma requisição submetida cria itens em `held` e bloqueia o período durante 48
**Estado:** aceite
A autorização HTTP e a verificação no serviço exigem perfil de aprovador ou administrador ativo. Antes de aprovar, o serviço bloqueia a requisição e os ativos por ordem estável, ignora apenas os itens da própria pré-reserva e volta a validar estado, atribuição, bloqueios e conflitos externos. A decisão altera requisição e itens na mesma transação e escreve tanto `approval_actions` como `audit_logs`. Rejeição liberta os itens; um pedido de esclarecimentos mantém a pré-reserva e renova o prazo por 48 horas.
## ADR-010 — Notificações após commit e substituição equivalente
**Data:** 2026-08-04
**Estado:** aceite
Notificações funcionais são colocadas na fila apenas depois da confirmação da transação e enviadas por email e caixa interna. A submissão e uma resposta a esclarecimentos notificam aprovadores ativos; decisões e substituições notificam o requisitante. Uma substituição não altera automaticamente a decisão: exige justificação, equipamento livre da mesma categoria, bloqueio de ambas as linhas, novo item `held` e preservação do item original como `cancelled` para auditoria.

View File

@@ -1,7 +1,7 @@
# Estado do projeto e handoff
**Atualizado em:** 4 de agosto de 2026
**Fase:** Fases 1, 2 e 3 concluídas; Fase 4 em curso
**Fase:** Fases 1, 2, 3 e 4 concluídas
**Último agente:** Codex
## Resultado disponível
@@ -29,26 +29,31 @@
- Área de aprovação protegida por perfil, com pesquisa por referência/requisitante.
- Aprovação, rejeição e pedido de esclarecimentos transacionais, registados em `approval_actions` e `audit_logs`.
- Aprovação revalida disponibilidade e estado operacional do ativo antes de promover itens `held` para `reserved`.
- Notificações assíncronas por email e base de dados para submissões, decisões, esclarecimentos e substituições.
- Caixa interna de notificações, contador de não lidas, marcação individual/coletiva e isolamento por utilizador.
- Resposta auditada do requisitante a esclarecimentos, com regresso a `pending_approval` e novo prazo de 48 horas.
- Substituição de equipamento apenas por ativo livre da mesma categoria, mantendo a pré-reserva e todo o histórico.
- Fila de aprovação com filtros por referência, requisitante, categoria e período, incluindo histórico de ações.
## Limites conhecidos
- A integração está implementada e testada contra respostas simuladas; falta configurar e validar URL, versão e token da instância institucional. Até lá, o catálogo utiliza dados fictícios do seeder.
- Microsoft Entra ID ainda não está implementado; as variáveis de ambiente já estão definidas.
- Entrega e devolução têm tabelas preparadas, mas ainda não possuem ações ou interface.
- As notificações de decisão, a resposta do requisitante aos esclarecimentos e a substituição por equipamento equivalente ainda não foram implementadas.
- As notificações estão implementadas com Mailpit em desenvolvimento; falta validar SMTP institucional antes do piloto.
- As políticas de autorização finas ainda não foram ligadas às rotas; nesta fase todas as rotas funcionais exigem utilizador autenticado, verificado e ativo.
- O calendário atual é a vista mensal pessoal do MVP; vistas diária, semanal, global e por equipamento/categoria/localização ficam para as áreas operacional e administrativa.
## Próxima tarefa recomendada
Completar a **Fase 4 — aprovações**:
Implementar a **Fase 5 — entrega e devolução**:
1. abrir `app/Domain/Approvals/Services/ApprovalService.php` e criar notificações assíncronas para cada decisão;
2. permitir ao requisitante responder ao pedido de esclarecimentos e voltar a colocar o pedido em `pending_approval`;
3. acrescentar filtros da fila por data e categoria e uma vista de detalhe com histórico;
4. permitir substituição explícita por equipamento equivalente sem contornar a deteção de conflitos;
5. testar notificações, resposta, substituição e concorrência em PostgreSQL;
6. concluir a documentação da Fase 4;
1. abrir `app/Integrations/SnipeIt/SnipeItClient.php` e acrescentar checkout/check-in com o token operacional separado;
2. criar `app/Domain/Handovers/Services/HandoverService.php` com entrega e devolução transacionais;
3. criar área do operador para reservas do dia, pesquisa por referência/utilizador e confirmação do estado físico;
4. tratar falhas parciais da API Snipe-IT sem declarar entrega/devolução concluída;
5. implementar incidentes, atrasos e bloqueios associados a dano;
6. testar API simulada, autorização, idempotência e auditoria;
7. atualizar este documento e o changelog.
## Testes da última sessão
@@ -56,20 +61,21 @@ Completar a **Fase 4 — aprovações**:
Validação concluída em 4 de agosto de 2026:
- `docker compose exec -T app composer test`: passou;
- Laravel Pint: 106 ficheiros sem desvios;
- PHPStan/Larastan nível 7: 80 ficheiros, zero erros;
- Pest: 65 testes passaram, 289 asserções e 2 testes de registo ignorados por a funcionalidade estar deliberadamente desativada;
- Laravel Pint: 111 ficheiros sem desvios;
- PHPStan/Larastan nível 7: 84 ficheiros, zero erros;
- Pest: 70 testes passaram, 340 asserções e 2 testes de registo ignorados por a funcionalidade estar deliberadamente desativada;
- TypeScript (`tsc --noEmit`): passou;
- ESLint: passou;
- Prettier: passou;
- Vite: builds cliente e SSR concluídos, 2307 módulos cliente e 96 módulos SSR transformados;
- Vite: builds cliente e SSR concluídos, 2308 módulos cliente e 97 módulos SSR transformados;
- PostgreSQL 17: `migrate:fresh --seed` concluído, incluindo extensão `btree_gist` e restrição de exclusão temporal;
- PostgreSQL 17: migration incremental do marcador `last_seen_sync_run_id` aplicada com sucesso;
- PostgreSQL 17: nenhuma migration pendente; comando de expiração executado e agendamentos horário/a cada cinco minutos confirmados;
- PostgreSQL 17: migration `notifications` aplicada com sucesso;
- smoke test HTTP: `/login` respondeu com estado 200 através do Nginx;
- Vite em Docker: URL pública, HMR e CORS verificados em `localhost:5173`; corrigida a página em branco causada pela publicação anterior de `0.0.0.0:5173`;
- Inertia SSR: renderização do login validada através do serviço `node`; corrigido o componente de mensagens flash que usava `usePage()` fora do contexto Inertia;
- verificação visual no browser integrado: a inspeção controlada de `localhost` continua impedida por uma política administrativa temporariamente indisponível; catálogo, cancelamento e calendário foram validados por testes Inertia, TypeScript e builds cliente/SSR.
- verificação visual no browser integrado: a inspeção controlada de `localhost` continua impedida por uma política administrativa temporariamente indisponível; aprovações, esclarecimentos e notificações foram validados por testes Inertia, TypeScript e builds cliente/SSR.
## Decisões pendentes do contexto institucional

View File

@@ -101,6 +101,8 @@ Uma expiração ou cancelamento muda itens `held`, `reserved`, `ready` e `delive
Utilizadores com perfil `approver` ou `administrator` acedem a `http://localhost:8000/aprovacoes`. Uma decisão válida deve criar uma linha em `approval_actions` e outra em `audit_logs`. Se a aprovação indicar que a disponibilidade mudou, confirmar primeiro a atribuição/estado sincronizado do ativo e conflitos entretanto criados; não alterar o estado diretamente na base de dados.
As decisões geram jobs de notificação. Em desenvolvimento, confirmar o email em `http://localhost:8025` e a notificação interna em `http://localhost:8000/notificacoes`. Se uma mensagem não chegar, consultar primeiro `docker compose logs --tail=100 queue` e a tabela `failed_jobs`; não repetir a decisão já confirmada.
## Produção
Antes de disponibilizar: