feat: add transactional approval workflow

This commit is contained in:
2026-08-04 16:36:15 +01:00
parent e667d05bc1
commit c2136bc250
18 changed files with 620 additions and 20 deletions

View File

@@ -33,6 +33,7 @@ Nginx ──► Laravel + Inertia/React
- `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.
- `app/Integrations/SnipeIt`: cliente HTTP, DTOs, mapper e exceções da API Snipe-IT.

View File

@@ -55,3 +55,10 @@ Consumir `/api/v1/hardware` por `offset`/`limit`, com páginas configuráveis at
**Estado:** aceite
Uma requisição submetida cria itens em `held` e bloqueia o período durante 48 horas, até decisão, cancelamento ou expiração. O próprio requisitante pode cancelar apenas enquanto a máquina de estados admite a transição. Um job único executado a cada cinco minutos expira pedidos `pending_approval` ou `awaiting_information` com prazo vencido. Cancelamento e expiração usam bloqueio transacional da requisição, libertam todos os itens bloqueantes e produzem eventos de auditoria; intervalos que se tocam continuam permitidos pela semântica `[início, fim)`.
## ADR-009 — Decisões com revalidação sob bloqueio
**Data:** 2026-08-04
**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.

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:** Fases 1, 2 e 3 concluídas; Fase 4 em curso
**Último agente:** Codex
## Resultado disponível
@@ -26,26 +26,29 @@
- Cancelamento pelo próprio requisitante, com validação de estado, libertação transacional dos itens e auditoria.
- Expiração de pré-reservas vencidas através do comando `reservations:expire` e job único agendado a cada cinco minutos.
- Calendário mensal pessoal, isolado por utilizador e com navegação entre meses.
- Á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`.
## 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.
- Aprovação, rejeição, entrega e devolução têm tabelas preparadas, mas ainda não possuem ações ou interface.
- As notificações ainda não foram implementadas.
- 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 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
Implementar a **Fase 4 — aprovações**:
Completar a **Fase 4 — aprovações**:
1. criar `app/Domain/Approvals/Services/ApprovalService.php` e policies para o perfil aprovador;
2. criar fila e detalhe de requisições pendentes com filtros por data, categoria e requisitante;
3. implementar aprovação com nova validação transacional de disponibilidade e promoção dos itens para `reserved`;
4. implementar rejeição e pedido de esclarecimentos, com justificação e registo em `approval_actions`/auditoria;
5. permitir substituição explícita por equipamento equivalente sem contornar a deteção de conflitos;
6. emitir notificações assíncronas ao requisitante e ao aprovador;
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;
7. atualizar este documento e o changelog.
## Testes da última sessão
@@ -53,13 +56,13 @@ Implementar 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: 102 ficheiros sem desvios;
- PHPStan/Larastan nível 7: 77 ficheiros, zero erros;
- Pest: 60 testes passaram, 270 asserções e 2 testes de registo ignorados por a funcionalidade estar deliberadamente desativada;
- 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;
- TypeScript (`tsc --noEmit`): passou;
- ESLint: passou;
- Prettier: passou;
- Vite: builds cliente e SSR concluídos, 2306 módulos cliente e 95 módulos SSR transformados;
- Vite: builds cliente e SSR concluídos, 2307 módulos cliente e 96 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;

View File

@@ -97,6 +97,10 @@ docker compose exec app php artisan schedule:list
Uma expiração ou cancelamento muda itens `held`, `reserved`, `ready` e `delivered` para `cancelled`, libertando o período. Confirmar os eventos `reservation.expired` ou `reservation.cancelled` em `audit_logs` quando existir uma divergência operacional.
## Aprovações
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.
## Produção
Antes de disponibilizar: