121 lines
6.1 KiB
Markdown
121 lines
6.1 KiB
Markdown
---
|
||
id: KB-PLUGIN-037
|
||
title: "Plugin redmine — Contrato de API GLPI → Redmine (o quê, como e por quê)"
|
||
domain: plugin-dev
|
||
tags:
|
||
- redmine
|
||
- glpi11
|
||
- plugin
|
||
- api
|
||
- rest
|
||
- integration
|
||
- impersonation
|
||
- custom-fields
|
||
- time-tracking
|
||
- producao
|
||
status: active
|
||
severity: high
|
||
created_at: 2026-07-02
|
||
updated_at: 2026-07-02
|
||
applies_to:
|
||
- plugin redmine (GLPI 11.0.4+ prod / 11.0.7 dev)
|
||
- Redmine 5.x (redmine.mindtek.com.br / 10.10.100.6:8081)
|
||
related_records:
|
||
- KB-PLUGIN-031
|
||
- KB-PLUGIN-036
|
||
- KB-INFRA-004
|
||
---
|
||
|
||
# Plugin redmine — Contrato de API GLPI → Redmine
|
||
|
||
Registro de desenvolvedor do plugin `redmine` (marketplace Mindplace). Resume **o quê**
|
||
sai do GLPI, **como** entra no Redmine e **por quê** cada decisão existe. A referência
|
||
completa (payloads, exemplos HTTP) vive no repo do plugin: `docs/API.md`
|
||
(`servicedesk.mindtek.com.br/git/rodolpho.lopes/redmine`, espelho dev em
|
||
`onmind.mindtek.com.br/git/rodolpho.lopes/Redmine`).
|
||
|
||
## O quê (modelo)
|
||
|
||
| GLPI | Redmine | Quando |
|
||
|---|---|---|
|
||
| Projeto ou Entidade (vínculo manual na aba Redmine) | Projeto | vínculo 1:1 salvo em `glpi_plugin_redmine_projects` / `_entities` |
|
||
| Chamado | **1 issue** | criada no 1º lançamento de tempo (lazy) |
|
||
| TicketTask **Feito** com duração > 0 | **1 time entry** na issue | ao salvar a task (1× por task) |
|
||
| Status do chamado | status da issue | mapeado (`status_map` do formconfig) |
|
||
| Técnico atribuído / autor da task | autor da issue / do time entry | via impersonation |
|
||
|
||
**Por quê:** o chamado nasce e morre no GLPI; para o Redmine importam as horas e o
|
||
andamento. Tarefas não concluídas ficam só no GLPI (gate no `state = Feito`).
|
||
|
||
## Como (mecanismos-chave)
|
||
|
||
- **Auth:** header `X-Redmine-API-Key` com chave de **admin** (cifrada no banco via GLPIKey).
|
||
- **Autoria — impersonation:** header `X-Redmine-Switch-User: <login>`. Só funciona se o
|
||
usuário for **membro do projeto** com papel que permita a ação → o plugin faz
|
||
auto-membership (`POST /projects/:id/memberships.json`, papel = `default_role_id`).
|
||
Falhou (403/412)? Refaz como admin e loga — nunca perde o lançamento.
|
||
- **Mapeamento de usuários:** overrides manuais (`glpi_plugin_redmine_usermap`) →
|
||
auto-match por **e-mail** no cache `glpi_plugin_redmine_users` → **lookup ao vivo**
|
||
no Redmine (`/users.json?name=<email>`) com upsert no cache (self-healing, 1.6.0).
|
||
Sem par: lança como admin **com log** ("Sem par Redmine para o usuário GLPI #N").
|
||
Incidente real (2026-07-02): o cache ficou vazio em prod e os lançamentos saíram
|
||
como admin em silêncio — motivou o self-healing e o `syncUsers` por upsert
|
||
(nunca esvazia a tabela).
|
||
- **Idempotência:** `glpi_plugin_redmine_tickets` (1 issue por chamado) e
|
||
`glpi_plugin_redmine_tasks` (tempo 1× por task). Se a issue vinculada foi apagada no
|
||
Redmine, o plugin detecta (GET 404) e **recria** removendo o vínculo órfão (1.5.5+).
|
||
- **Tempo:** GLPI grava segundos → Redmine quer horas decimais → `actiontime/3600`,
|
||
2 casas (0h05 → 0.08 h; o Redmine exibe "0:05").
|
||
- **Sincronizar Metadados:** cacheia projects, trackers, statuses, priorities,
|
||
activities, roles, users e **project_custom_fields** em
|
||
`glpi_plugin_redmine_configs.metadata_cache`. Os formulários leem SÓ do cache — após
|
||
mudanças no Redmine (novos campos, projetos), é preciso re-sincronizar.
|
||
|
||
## Por quê — regras do Redmine de PRODUÇÃO que moldaram o contrato
|
||
|
||
Aprendidas em produção (2026-06-30 a 07-02); o dev era permissivo, o prod não:
|
||
|
||
1. **Custom fields de projeto OBRIGATÓRIOS**: "Tipo de Projeto" (cf id 4) e
|
||
"Tipo de Faturamento" (cf id 5) não podem ficar vazios na criação de projeto →
|
||
`POST /projects.json` leva `custom_field_values: {"4": "...", "5": "..."}`.
|
||
O form das abas Redmine renderiza esses campos **dinamicamente** a partir de
|
||
`metadata_cache.project_custom_fields` (dropdown p/ `field_format=list`) — se o
|
||
Redmine ganhar novos campos obrigatórios, basta re-sincronizar metadados.
|
||
2. **Identificador**: só minúsculas/números/traços → `createProjectFull()` normaliza
|
||
("GNA" → "gna"); senão 422 "Identificador não é válido".
|
||
3. **Atividade do time entry é POR PROJETO**: a `default_activity_id` do formconfig
|
||
precisa estar habilitada no projeto de destino, senão 422 "não está incluso na lista".
|
||
4. **Comentário do time entry é obrigatório**: task sem descrição → fallback
|
||
"Tempo lançado via GLPI (chamado #N)" (1.5.4+).
|
||
5. **Projeto de destino precisa estar ATIVO** (status 1): fechado/arquivado → 422/403.
|
||
O dropdown de vínculo filtra só ativos (1.5.3+).
|
||
6. **Tracker é por projeto**: o `default_tracker_id` global precisa existir no projeto;
|
||
vazio = o Redmine usa o default do projeto (mais seguro em multi-cliente).
|
||
|
||
## Endpoints usados (resumo)
|
||
|
||
Leitura (sync): `/projects.json`, `/trackers.json`, `/issue_statuses.json`,
|
||
`/enumerations/issue_priorities.json`, `/enumerations/time_entry_activities.json`,
|
||
`/roles.json`, `/users.json` (paginado), `/projects/:id/issue_categories.json`,
|
||
`/custom_fields.json` (filtra `customized_type=project`).
|
||
|
||
Escrita: `POST /projects.json` (criar projeto c/ custom fields), `POST /issues.json`
|
||
(issue do chamado), `PUT /issues/:id.json` (status), `POST /time_entries.json` (tempo),
|
||
`POST /projects/:id/memberships.json` (auto-membership).
|
||
|
||
## Gotchas de manutenção
|
||
|
||
- Dev = GLPI 11.0.7, prod = **11.0.4**: desenvolver mirando 11.0.0+ (sem
|
||
`fields.csrfField()`, sem `Toolbox::logError()`). Ver [KB-PLUGIN-036].
|
||
- Ambiente em `production`: mudou `.twig` → `cache:clear` obrigatório (o cache do GLPI
|
||
não é invalidado por atualização de plugin).
|
||
- **Twig `merge` renumera chaves inteiras**: nunca montar opções `{id: nome}` no Twig —
|
||
montar no PHP (bug real: vínculo gravava o índice em vez do ID do projeto, 1.5.3).
|
||
- Release ZIP: wrapper `redmine/` como **primeira entrada** (entrada de diretório),
|
||
senão o instalador do Mindplace extrai aninhado.
|
||
|
||
## Classificação
|
||
|
||
- Tipo: contrato de integração + lições de produção.
|
||
- Reutilização: obrigatória para quem for manter/evoluir o plugin redmine; útil para
|
||
qualquer integração GLPI → API externa.
|