feat: establish reservation portal foundation
This commit is contained in:
68
docs/ARCHITECTURE.md
Normal file
68
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Arquitetura
|
||||
|
||||
## Contexto
|
||||
|
||||
O portal é uma aplicação independente e complementar ao Snipe-IT. Usa um monólito modular para reduzir custo operacional, mas separa os domínios internamente para evitar acoplamento entre inventário, reservas, aprovações e operações.
|
||||
|
||||
```text
|
||||
Browser
|
||||
│ HTTPS
|
||||
▼
|
||||
Nginx ──► Laravel + Inertia/React
|
||||
├── PostgreSQL (estado do portal)
|
||||
├── Redis (cache, filas, locks)
|
||||
├── SMTP/Mailpit
|
||||
└── Snipe-IT REST API
|
||||
```
|
||||
|
||||
## Responsabilidade dos sistemas
|
||||
|
||||
| Informação/operação | Sistema oficial |
|
||||
|---|---|
|
||||
| Etiqueta, série, modelo, categoria e localização habitual | Snipe-IT |
|
||||
| Estado operacional e atribuição física atual | Snipe-IT |
|
||||
| Checkout e check-in efetivos | Snipe-IT via API |
|
||||
| Pedidos futuros e calendário | Portal |
|
||||
| Aprovação, rejeição, cancelamento e conflitos | Portal |
|
||||
| Entrega/devolução e evidência operacional | Portal, sincronizada com Snipe-IT |
|
||||
| Auditoria do fluxo de requisição | Portal |
|
||||
|
||||
## Módulos
|
||||
|
||||
- `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/Handovers`: entrega, devolução e incidentes (a criar).
|
||||
- `app/Domain/Audit`: logs imutáveis pela interface.
|
||||
- `app/Integrations/SnipeIt`: único ponto de comunicação com Snipe-IT (a criar).
|
||||
|
||||
## Consistência temporal
|
||||
|
||||
Os períodos usam `[starts_at, ends_at)`: uma reserva que termina às 11:00 não entra em conflito com outra que começa às 11:00.
|
||||
|
||||
A defesa ocorre em duas camadas:
|
||||
|
||||
1. `AvailabilityService` produz erros de negócio legíveis;
|
||||
2. PostgreSQL mantém a garantia final com uma restrição `EXCLUDE USING gist` nos estados bloqueantes.
|
||||
|
||||
Uma criação ou aprovação deve bloquear as linhas dos ativos, voltar a validar dentro da transação e tolerar corrida através da restrição da base de dados.
|
||||
|
||||
## Fluxo de dados Snipe-IT
|
||||
|
||||
1. Um job consulta a API paginada com token de leitura.
|
||||
2. Um mapper reduz a resposta aos campos documentados.
|
||||
3. `AssetSyncService` faz upsert por `snipe_asset_id` e calcula `content_hash`.
|
||||
4. Ativos ausentes numa sincronização completa ficam `active = false`; não são apagados para preservar histórico.
|
||||
5. Antes de aprovação ou entrega, o ativo é validado novamente.
|
||||
6. Checkout/check-in usam um token operacional separado e guardam apenas a resposta necessária para auditoria.
|
||||
|
||||
## Segurança
|
||||
|
||||
- Sem registo público.
|
||||
- Cookies seguros e HTTPS em produção.
|
||||
- Policies em cada ação de domínio.
|
||||
- Tokens e segredos apenas no ambiente.
|
||||
- Dois tokens Snipe-IT com menor privilégio.
|
||||
- MFA obrigatório a definir para perfis privilegiados antes do piloto.
|
||||
- Logs de auditoria não expostos a operações de update/delete.
|
||||
43
docs/DECISIONS.md
Normal file
43
docs/DECISIONS.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Registo de decisões técnicas
|
||||
|
||||
## ADR-001 — Monólito modular Laravel/Inertia
|
||||
|
||||
**Data:** 2026-08-04
|
||||
**Estado:** aceite
|
||||
|
||||
Usar Laravel 13 com React/TypeScript através de Inertia no mesmo projeto. Mantém autenticação, autorização, filas e transações numa única unidade operacional, sem impedir separação por domínio.
|
||||
|
||||
## ADR-002 — PostgreSQL como garantia de conflitos
|
||||
|
||||
**Data:** 2026-08-04
|
||||
**Estado:** aceite
|
||||
|
||||
Usar PostgreSQL e períodos semiabertos `[início, fim)`. A lógica aplicacional melhora a mensagem ao utilizador; uma restrição de exclusão GiST garante que concorrência ou um erro aplicacional não cria sobreposição bloqueante.
|
||||
|
||||
## ADR-003 — Cache local, sem replicação total do Snipe-IT
|
||||
|
||||
**Data:** 2026-08-04
|
||||
**Estado:** aceite
|
||||
|
||||
Guardar apenas os campos de ativos necessários ao catálogo e reservas. A sincronização desativa registos ausentes em vez de os apagar. A base de dados do Snipe-IT nunca é acedida diretamente.
|
||||
|
||||
## ADR-004 — Registo público desativado
|
||||
|
||||
**Data:** 2026-08-04
|
||||
**Estado:** aceite
|
||||
|
||||
Contas são criadas por administrador, convite ou identidade institucional verificada. Mantêm-se recuperação de palavra-passe, verificação de email, 2FA e passkeys do starter kit.
|
||||
|
||||
## ADR-005 — Ambiente reproduzível em Docker Compose
|
||||
|
||||
**Data:** 2026-08-04
|
||||
**Estado:** aceite
|
||||
|
||||
Desenvolvimento e primeira instalação utilizam serviços separados para Nginx, PHP-FPM, PostgreSQL, Redis, Vite, filas, scheduler e Mailpit. Produção deve usar imagens imutáveis, HTTPS e gestão externa de segredos, mantendo a mesma topologia lógica.
|
||||
|
||||
## ADR-006 — Português de Portugal na interface
|
||||
|
||||
**Data:** 2026-08-04
|
||||
**Estado:** aceite
|
||||
|
||||
Textos funcionais são escritos em português europeu. Nomes de código, eventos e tabelas permanecem em inglês para consistência técnica.
|
||||
69
docs/PROJECT_STATUS.md
Normal file
69
docs/PROJECT_STATUS.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Estado do projeto e handoff
|
||||
|
||||
**Atualizado em:** 4 de agosto de 2026
|
||||
**Fase:** Fase 1 concluída; primeiro fluxo vertical da Fase 3 concluído
|
||||
**Último agente:** Codex
|
||||
|
||||
## Resultado disponível
|
||||
|
||||
- Projeto oficial Laravel 13 React Starter Kit criado.
|
||||
- Autenticação local via Fortify, recuperação de palavra-passe, verificação de email, 2FA e passkeys.
|
||||
- Registo público desativado conforme o plano.
|
||||
- Estrutura relacional de utilizadores, identidades externas, perfis e permissões.
|
||||
- Cache local de ativos Snipe-IT.
|
||||
- Modelo completo inicial para reservas, itens, aprovações, bloqueios, entrega/devolução, sincronizações e auditoria.
|
||||
- Proteção PostgreSQL contra períodos sobrepostos para o mesmo equipamento através de `EXCLUDE USING gist` e intervalos `[início, fim)`.
|
||||
- Serviço de disponibilidade que considera estado local, atribuição, reservas e bloqueios.
|
||||
- Criação transacional de uma requisição pendente com vários equipamentos e registo de auditoria.
|
||||
- Dashboard pessoal, catálogo com pesquisa/filtros, formulário de nova requisição e histórico.
|
||||
- Docker Compose com Nginx, PHP-FPM, PostgreSQL, Redis, Node/Vite, filas, scheduler e Mailpit.
|
||||
- Dados fictícios e duas contas locais para desenvolvimento.
|
||||
|
||||
## Limites conhecidos
|
||||
|
||||
- A API real do Snipe-IT ainda não está implementada. 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 e jobs de expiração ainda não foram implementados.
|
||||
- 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.
|
||||
- A disponibilidade apresentada no catálogo ainda não recebe um período; a validação real ocorre ao submeter.
|
||||
|
||||
## Próxima tarefa recomendada
|
||||
|
||||
Implementar a **Fase 2 — cliente e sincronização Snipe-IT**:
|
||||
|
||||
1. abrir `docs/ARCHITECTURE.md` e `config/services.php`;
|
||||
2. criar `app/Integrations/SnipeIt/SnipeItClient.php`, DTOs, mapper e exceções;
|
||||
3. implementar paginação, timeout, retry limitado e token de leitura;
|
||||
4. criar `AssetSyncService`, job e comando `snipe:sync-assets`;
|
||||
5. testar com `Http::fake()` paginação, falhas e desativação de ativos ausentes;
|
||||
6. criar página administrativa de diagnóstico apenas para administradores;
|
||||
7. atualizar este documento e o changelog.
|
||||
|
||||
## Testes da última sessão
|
||||
|
||||
Validação concluída em 4 de agosto de 2026:
|
||||
|
||||
- `docker compose exec -T app composer test`: passou;
|
||||
- Laravel Pint: 78 ficheiros sem desvios;
|
||||
- PHPStan/Larastan nível 7: 59 ficheiros, zero erros;
|
||||
- Pest: 44 testes passaram, 157 asserções e 2 testes de registo ignorados por a funcionalidade estar deliberadamente desativada;
|
||||
- TypeScript (`tsc --noEmit`): passou;
|
||||
- ESLint: passou;
|
||||
- Prettier: passou;
|
||||
- Vite: build de produção concluído, 2304 módulos transformados;
|
||||
- PostgreSQL 17: `migrate:fresh --seed` concluído, incluindo extensão `btree_gist` e restrição de exclusão temporal;
|
||||
- smoke test HTTP: `/login` respondeu com estado 200 através do Nginx;
|
||||
- verificação visual no browser integrado: não executada devido a uma política administrativa temporariamente indisponível no browser desta sessão; não foi detetado erro da aplicação.
|
||||
|
||||
## Decisões pendentes do contexto institucional
|
||||
|
||||
Estas decisões não bloqueiam o desenvolvimento com defaults seguros, mas devem ser confirmadas antes do piloto:
|
||||
|
||||
- versão e URL da instância Snipe-IT;
|
||||
- categorias e estados requisitáveis;
|
||||
- tenant, client ID e redirect URI do Entra ID;
|
||||
- matriz final de aprovadores por categoria/departamento;
|
||||
- duração máxima, antecedência e margens por categoria;
|
||||
- domínio institucional e método de emissão TLS;
|
||||
- SMTP institucional.
|
||||
44
docs/REQUIREMENTS.md
Normal file
44
docs/REQUIREMENTS.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Requisitos e âmbito
|
||||
|
||||
Este documento resume o plano funcional fornecido em 4 de agosto de 2026. Alterações de âmbito devem ser registadas como decisão.
|
||||
|
||||
## Fluxo obrigatório do MVP
|
||||
|
||||
```text
|
||||
Consultar → Requisitar → Aprovar → Reservar → Entregar → Devolver → Concluir
|
||||
```
|
||||
|
||||
## Funcionalidades do MVP
|
||||
|
||||
1. Login local e Microsoft Entra ID.
|
||||
2. Perfis: requisitante, aprovador, operador, administrador e acesso de auditoria.
|
||||
3. Catálogo apenas com ativos requisitáveis do Snipe-IT.
|
||||
4. Pesquisa por nome, categoria, modelo, localização, etiqueta, estado e período.
|
||||
5. Requisição de um ou vários equipamentos com início, fim, finalidade, local e observações.
|
||||
6. Pré-reserva pendente com prazo de aprovação.
|
||||
7. Aprovação, rejeição, esclarecimentos e substituição por equivalente.
|
||||
8. Prevenção de conflitos e margens configuráveis.
|
||||
9. Entrega manual com checkout Snipe-IT.
|
||||
10. Devolução manual com check-in, estado, acessórios e incidentes.
|
||||
11. Notificações por email e internas.
|
||||
12. Bloqueios de manutenção, histórico e auditoria.
|
||||
13. Administração de utilizadores, regras e integrações.
|
||||
14. Interface responsiva para computador, tablet e telemóvel.
|
||||
|
||||
## Regras invariantes
|
||||
|
||||
- Uma requisição não se torna reserva sem aprovação.
|
||||
- Nenhum equipamento pode ter duas reservas bloqueantes sobrepostas.
|
||||
- O checkout nunca é executado automaticamente por ter chegado a hora.
|
||||
- Falhas Snipe-IT nunca produzem sucesso silencioso.
|
||||
- O último cache válido pode manter o catálogo, mas operações críticas exigem validação recente.
|
||||
- Ativos históricos não são apagados por deixarem de ser requisitáveis.
|
||||
- Todas as decisões e operações físicas ficam auditadas.
|
||||
|
||||
## Fora do primeiro MVP
|
||||
|
||||
Reservas recorrentes, kits, reserva por modelo, Teams, calendário Microsoft 365, assinatura digital, fotografias, PWA, push, lista de espera, aprovações multinível e previsão de procura.
|
||||
|
||||
## Critérios de aceitação
|
||||
|
||||
O MVP só está pronto quando todos os fluxos acima possuem testes automatizados, controlo de acesso, tratamento explícito de falhas, documentação técnica/utilizador, backups testados e validação responsiva.
|
||||
49
docs/RUNBOOK.md
Normal file
49
docs/RUNBOOK.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Runbook de desenvolvimento e operação
|
||||
|
||||
## Desenvolvimento local
|
||||
|
||||
1. Copiar `.env.example` para `.env`.
|
||||
2. Executar `docker compose build`.
|
||||
3. Gerar a chave com `docker compose run --rm app php artisan key:generate`.
|
||||
4. Iniciar com `docker compose up -d`.
|
||||
5. Preparar dados com `docker compose exec app php artisan migrate --seed`.
|
||||
6. Abrir o portal em `http://localhost:8000` e emails em `http://localhost:8025`.
|
||||
|
||||
## Diagnóstico
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
docker compose logs --tail=100 app
|
||||
docker compose logs --tail=100 queue
|
||||
docker compose exec app php artisan about
|
||||
docker compose exec app php artisan migrate:status
|
||||
docker compose exec postgres pg_isready -U reserva -d reserva
|
||||
docker compose exec redis redis-cli ping
|
||||
```
|
||||
|
||||
## Atualização de código
|
||||
|
||||
```bash
|
||||
docker compose exec app composer install
|
||||
docker compose exec node npm install
|
||||
docker compose exec app php artisan migrate --force
|
||||
docker compose exec app php artisan optimize:clear
|
||||
docker compose restart queue scheduler
|
||||
```
|
||||
|
||||
## Produção
|
||||
|
||||
Antes de disponibilizar:
|
||||
|
||||
- definir `APP_ENV=production`, `APP_DEBUG=false` e HTTPS;
|
||||
- substituir todas as palavras-passe do exemplo;
|
||||
- montar segredos fora do repositório;
|
||||
- remover exposição pública das portas PostgreSQL, Vite e Mailpit;
|
||||
- configurar volumes persistentes e backups cifrados;
|
||||
- executar `php artisan optimize` e frontend compilado;
|
||||
- definir health checks, alertas e rotação de logs;
|
||||
- testar recuperação de backup e falha Snipe-IT.
|
||||
|
||||
## Backups
|
||||
|
||||
Salvaguardar diariamente PostgreSQL e uploads. Manter retenção semanal/mensal e uma cópia fora do servidor. Segredos são salvaguardados através de procedimento separado. Executar um teste de restauro periódico e registar o resultado.
|
||||
Reference in New Issue
Block a user