# 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/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. ## 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. `AvailabilityService::availableAssets()` aplica as mesmas condições em SQL para alimentar catálogo e formulário sem carregar todo o inventário em memória. O período consultado é propagado até à criação da requisição; a validação transacional continua a ser a garantia aplicacional final. 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. ## 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`, calcula `content_hash` e marca cada ativo com `last_seen_sync_run_id`. 4. Apenas após todas as páginas serem recebidas com sucesso, ativos cujo marcador não corresponde à execução atual 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. Uma falha intermédia pode deixar upserts válidos das páginas anteriores, mas nunca executa a desativação global. A sincronização seguinte converge o cache. Um lock distribuído e um job único impedem duas sincronizações simultâneas. Referências oficiais usadas para o contrato: [autenticação da API](https://snipe-it.readme.io/reference/authenticating-with-the-api), [listagem de hardware](https://snipe-it.readme.io/reference/hardware-list) e [limites/paginação](https://snipe-it.readme.io/reference/api-throttling). ## 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.