diff --git a/index.json b/index.json index 40c9928..f983cf7 100755 --- a/index.json +++ b/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" } ] } diff --git a/records/plugin-dev/KB-PLUGIN-037-redmine-plugin-api-contract.md b/records/plugin-dev/KB-PLUGIN-037-redmine-plugin-api-contract.md new file mode 100644 index 0000000..9e39c3f --- /dev/null +++ b/records/plugin-dev/KB-PLUGIN-037-redmine-plugin-api-contract.md @@ -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: `. 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.