knowledge-base/records/plugin-dev/KB-PLUGIN-037-redmine-plugin-api-contract.md
2026-07-03 09:18:29 -03:00

7.9 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).

Motor de Templates de Payload (2.0)

A partir da 2.0 o corpo de cada operação (create_issue, log_time, update_status, create_project) pode vir de um template editável em vez de ser montado no código — o objetivo é o plugin virar produto ambiente-agnóstico (cada Redmine tem seus campos obrigatórios/personalizados). Guia do usuário: docs/TEMPLATES.md; contrato de dev: docs/API.md.

  • PluginRedmineTemplateengine (inc/templateengine.class.php): tabela glpi_plugin_redmine_templates, renderizador mustache-like ({{ var | filtro }}; tag ocupando o valor inteiro → tipo nativo; lookup indexado map.status[chamado.status]; filtros truncar/sem_html/minusculo/identificador/padrao; prune de nulos), seeds equivalentes ao 1.x, descoberta de custom fields e os contextos (buildContext, buildFormContext, sampleContext).
  • Precedência: template ativo → renderiza e envia; erro de render → log + fallback 1.x; sem template ativo → comportamento 1.x idêntico. Ou seja, atualizar da 1.x para a 2.0 não muda nada até ativar um template.
  • UI (config): editor linha-a-linha "DE → PARA" (paleta de variáveis GLPI via autocomplete + dicionário dos campos que o Redmine espera, com obrigatoriedade e valores), botão "Gerar template" (local, sem reload) e testador (ajax/template_test.php): preview do render + dry-run que cria/apaga objeto de teste e traduz os 422 em dicas.
  • NÃO virou template (é motor, não payload): impersonation, idempotência, membership, gates (Feito/duração), recuperação de issue órfã.
  • Decisão de escopo (2026-07-03): não implementar override por vínculo nem perfis multi-Redmine sem caso real (YAGNI) — o template global cobre o cenário atual.

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.