KB-PLUGIN-037: contrato de API do plugin redmine (GLPI -> Redmine)

O quê/como/por quê da integração + regras do Redmine de produção
(custom fields obrigatórios, atividade por projeto, identifier lowercase,
comentário obrigatório, projeto ativo) e gotchas de manutenção.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Gemini 2026-07-02 10:42:10 -03:00
parent 5b73169939
commit a39fc0f094
2 changed files with 138 additions and 1 deletions

View file

@ -1,6 +1,6 @@
{
"version": "1.0.0",
"last_updated": "2026-06-29",
"last_updated": "2026-07-02",
"records": [
{
"id": "KB-INFRA-001",
@ -698,6 +698,27 @@
"severity": "medium",
"path": "records/plugin-dev/KB-PLUGIN-036-glpi11-namespaces-twig.md",
"summary": "id: KB-PLUGIN-036"
},
{
"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",
"path": "records/plugin-dev/KB-PLUGIN-037-redmine-plugin-api-contract.md",
"summary": "id: KB-PLUGIN-037"
}
]
}

View file

@ -0,0 +1,116 @@
---
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:** cache `glpi_plugin_redmine_users` (sync) + auto-match por
**e-mail** + overrides manuais (`glpi_plugin_redmine_usermap`).
- **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.