knowledge-base/records/plugin-dev/KB-PLUGIN-037-redmine-plugin-api-contract.md
Gemini af013d58d4 KB-PLUGIN-037: resolução de usuários self-healing (1.6.0) e incidente do cache vazio
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 12:58:30 -03:00

6.1 KiB
Raw Blame History

id title domain tags status severity created_at updated_at applies_to related_records
KB-PLUGIN-037 Plugin redmine — Contrato de API GLPI → Redmine (o quê, como e por quê) plugin-dev
redmine
glpi11
plugin
api
rest
integration
impersonation
custom-fields
time-tracking
producao
active high 2026-07-02 2026-07-02
plugin redmine (GLPI 11.0.4+ prod / 11.0.7 dev)
Redmine 5.x (redmine.mindtek.com.br / 10.10.100.6:8081)
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_userslookup 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 .twigcache: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.