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:
parent
5b73169939
commit
a39fc0f094
2 changed files with 138 additions and 1 deletions
23
index.json
23
index.json
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
116
records/plugin-dev/KB-PLUGIN-037-redmine-plugin-api-contract.md
Normal file
116
records/plugin-dev/KB-PLUGIN-037-redmine-plugin-api-contract.md
Normal 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.
|
||||
Loading…
Reference in a new issue