feat: add Snipe-IT asset synchronization
This commit is contained in:
@@ -35,7 +35,7 @@ Nginx ──► Laravel + Inertia/React
|
||||
- `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).
|
||||
- `app/Integrations/SnipeIt`: cliente HTTP, DTOs, mapper e exceções da API Snipe-IT.
|
||||
|
||||
## Consistência temporal
|
||||
|
||||
@@ -52,11 +52,15 @@ Uma criação ou aprovação deve bloquear as linhas dos ativos, voltar a valida
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -41,3 +41,10 @@ Desenvolvimento e primeira instalação utilizam serviços separados para Nginx,
|
||||
**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.
|
||||
|
||||
## ADR-007 — Sincronização Snipe-IT convergente por execução
|
||||
|
||||
**Data:** 2026-08-04
|
||||
**Estado:** aceite
|
||||
|
||||
Consumir `/api/v1/hardware` por `offset`/`limit`, com páginas configuráveis até ao máximo oficial de 500 registos. Cada ativo visto recebe o ID da execução em `last_seen_sync_run_id`; a desativação de ausentes só ocorre depois de todas as páginas terminarem com sucesso. Upserts de páginas já válidas podem permanecer após uma falha, permitindo convergência na execução seguinte sem manter uma transação aberta durante chamadas HTTP. Repetir apenas falhas de ligação, HTTP 429 e HTTP 5xx. Usar lock distribuído, job único e token exclusivo de leitura.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 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
|
||||
**Fase:** Fases 1 e 2 concluídas; primeiro fluxo vertical da Fase 3 concluído
|
||||
**Último agente:** Codex
|
||||
|
||||
## Resultado disponível
|
||||
@@ -18,10 +18,13 @@
|
||||
- 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.
|
||||
- Cliente Snipe-IT autenticado com paginação, timeout, retry seletivo e validação de respostas.
|
||||
- Sincronização convergente de ativos com histórico, hash de conteúdo, lock, job único e desativação apenas após execução completa.
|
||||
- Comando `snipe:sync-assets`, execução horária e painel de diagnóstico exclusivo para administradores.
|
||||
|
||||
## Limites conhecidos
|
||||
|
||||
- A API real do Snipe-IT ainda não está implementada. O catálogo utiliza dados fictícios do seeder.
|
||||
- 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 e jobs de expiração ainda não foram implementados.
|
||||
@@ -30,14 +33,14 @@
|
||||
|
||||
## Próxima tarefa recomendada
|
||||
|
||||
Implementar a **Fase 2 — cliente e sincronização Snipe-IT**:
|
||||
Completar a **Fase 3 — motor de reservas**:
|
||||
|
||||
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;
|
||||
1. abrir `app/Domain/Reservations/Services/AvailabilityService.php` e `ReservationService.php`;
|
||||
2. acrescentar pesquisa do catálogo por período e apresentar disponibilidade real;
|
||||
3. implementar cancelamento pelo requisitante com transição explícita e auditoria;
|
||||
4. criar expiração automática de pré-reservas pendentes e libertação dos itens;
|
||||
5. adicionar uma vista de calendário pessoal;
|
||||
6. testar limites temporais, cancelamento, expiração e autorização;
|
||||
7. atualizar este documento e o changelog.
|
||||
|
||||
## Testes da última sessão
|
||||
@@ -45,18 +48,19 @@ Implementar a **Fase 2 — cliente e sincronização Snipe-IT**:
|
||||
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;
|
||||
- Laravel Pint: 97 ficheiros sem desvios;
|
||||
- PHPStan/Larastan nível 7: 75 ficheiros, zero erros;
|
||||
- Pest: 53 testes passaram, 201 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;
|
||||
- Vite: builds cliente e SSR concluídos, 2305 módulos cliente 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;
|
||||
- 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: 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.
|
||||
- verificação visual no browser integrado: a inspeção controlada continua impedida por uma política administrativa temporariamente indisponível; o painel administrativo foi validado por teste Inertia, TypeScript e builds cliente/SSR.
|
||||
|
||||
## Decisões pendentes do contexto institucional
|
||||
|
||||
|
||||
@@ -50,6 +50,36 @@ docker compose exec app php artisan optimize:clear
|
||||
docker compose restart queue scheduler
|
||||
```
|
||||
|
||||
## Integração Snipe-IT
|
||||
|
||||
Definir no ambiente, sem colocar tokens no repositório:
|
||||
|
||||
```dotenv
|
||||
SNIPE_IT_URL=https://inventario.exemplo.pt
|
||||
SNIPE_IT_READ_TOKEN=
|
||||
SNIPE_IT_TIMEOUT=10
|
||||
SNIPE_IT_PAGE_SIZE=100
|
||||
SNIPE_IT_RETRIES=3
|
||||
SNIPE_IT_RETRY_DELAY_MS=250
|
||||
```
|
||||
|
||||
O token deve pertencer a uma conta técnica com acesso apenas de leitura aos ativos. A aplicação acrescenta `/api/v1` automaticamente quando a URL não o contém.
|
||||
|
||||
Operações:
|
||||
|
||||
```bash
|
||||
# Sincronização imediata e resultado no terminal
|
||||
docker compose exec app php artisan snipe:sync-assets
|
||||
|
||||
# Colocar na fila
|
||||
docker compose exec app php artisan snipe:sync-assets --queue
|
||||
|
||||
# Confirmar o agendamento horário
|
||||
docker compose exec app php artisan schedule:list
|
||||
```
|
||||
|
||||
Administradores podem testar a ligação, iniciar uma execução e consultar as últimas dez sincronizações em `http://localhost:8000/administracao/snipe-it`. Uma execução falhada mantém o último catálogo válido e não desativa registos ausentes. Consultar `sync_runs`, os logs do worker e o painel antes de repetir.
|
||||
|
||||
## Produção
|
||||
|
||||
Antes de disponibilizar:
|
||||
|
||||
Reference in New Issue
Block a user