# DESIGN 2.0 — Templates de Payload (motor de mapeamento dinâmico) > Objetivo: transformar o plugin — hoje moldado ao ambiente Mindtek — em **produto**: > os payloads enviados ao Redmine passam a ser **templates editáveis** pelo admin, > com variáveis do GLPI e campos descobertos do Redmine de destino. > Linha estável (clientes atuais): branch `stable/1.x`. Evolução: `main` (2.0). ## Decisões fechadas (2026-07-02) | Decisão | Escolha | |---|---| | Idioma das tags | **PT-BR** (`{{ chamado.titulo }}`) — público Mindplace | | UX do editor | **Linha-a-linha** (campo → valor/tag → obrigatório) com toggle "ver JSON" | | Escopo do template | **Global por operação** na 2.0; override por vínculo na 2.1 | | Dry-run | **Preview de render sempre**; teste real opt-in contra alvo escolhido | | Sintaxe | Mustache-like (`{{ var \| filtro }}`), **sem Twig completo** (segurança) | ## Fronteira Motor × Template **Motor (fixo, não editável):** auth, impersonation + auto-membership + fallback admin, idempotência (1 issue/chamado, tempo 1×/task), recuperação de issue órfã, gates (Feito + duração>0), normalização de identifier, logs, self-healing de usuários. **Template (editável):** o corpo JSON de cada operação — quais campos vão e com quais valores (tags, valores fixos, custom fields do ambiente). O admin mapeia **dados**, nunca **lógica**. ## Operações e seeds 4 operações fixas: `create_project`, `create_issue`, `log_time`, `update_status`. O botão **"Gerar template"** produz o seed = comportamento atual do motor 1.x + custom fields obrigatórios descobertos via `/custom_fields.json` (`customized_type` project/issue/time_entry conforme a operação). Seed `create_issue` (exemplo): ```json { "issue": { "project_id": "{{ vinculo.projeto_redmine }}", "subject": "{{ chamado.titulo | truncar:255 }}", "description": "{{ chamado.descricao | sem_html }}", "tracker_id": "{{ config.tracker_padrao }}", "priority_id": "{{ config.prioridade_padrao }}", "status_id": "{{ map.status[chamado.status] }}", "category_id": "{{ vinculo.categoria_padrao }}", "watcher_user_ids": "{{ config.observadores }}" } } ``` ## Sintaxe das tags - `{{ namespace.campo }}` — resolução por caminho no contexto. - `{{ mapa[chave.dinamica] }}` — lookup indexado (ex.: `map.status[chamado.status]`). - Filtros em pipeline: `{{ var | filtro:arg | filtro2 }}`. - `truncar:N`, `sem_html`, `minusculo`, `identificador`, `padrao:'texto fixo'`. **Regra de tipos:** tag ocupando o **valor inteiro** da string → rende com o **tipo nativo** da variável (número/array/bool, sem aspas no JSON final). Tag **embutida** em texto → interpolação de string. Valores `null`/arrays vazios pós-render são **removidos** do payload (igual ao motor 1.x, que só inclui campos definidos). ## Contexto (paleta de variáveis) | Namespace | Campos | Origem | |---|---|---| | `chamado.*` | id, titulo, descricao, status, entidade | Ticket | | `tarefa.*` | id, horas, segundos, descricao, comentario*, data | TicketTask | | `vinculo.*` | projeto_redmine, issue_redmine, categoria_padrao | tabelas do plugin | | `config.*` | tracker_padrao, prioridade_padrao, atividade_padrao, observadores | formconfig | | `map.status` | mapa status GLPI→Redmine | formconfig | | `form.*` | campos do form de criar projeto (inclui `cf_`) | aba Redmine | \* `tarefa.comentario` = descrição sem HTML **ou** o fallback "Tempo lançado via GLPI (chamado #N)" — o fallback é responsabilidade do motor (contexto), não do template. ## Armazenamento e retrocompatibilidade Tabela `glpi_plugin_redmine_templates` (`operation` único, `template` MEDIUMTEXT, `is_active`). **Sem template ativo → motor 1.x intocado.** Template ativo com erro de **render** → log + motor 1.x (nunca perde lançamento). Template ativo válido → payload do template é o enviado (sem retry pela via legada em erro de API — respeita a escolha do admin e evita duplicação). ## Fases 1. **Motor (esta fase):** engine de render + tabela + seeds + wiring com fallback nas operações do hook (`create_issue`, `log_time`, `update_status`). Invisível na UI. 2. **UI:** editor linha-a-linha + paleta + "Gerar template" + wiring do `create_project` (contexto `form.*`). 3. **Validação (concluída):** preview de render + dry-run opcional com tradução de 422. 4. **2.1:** override por vínculo, custom fields de issue/time_entry na paleta, perfis múltiplos (um GLPI → vários Redmines). ## Referências - Caso de estudo: ambiente Mindtek (KB-PLUGIN-037) — nativo vs "o que o Redmine exigiu" (custom fields 4/5 de projeto, activity/comments obrigatórios no time entry, trackers por projeto). - Redmine **não tem** introspecção de schema: descoberta = esqueleto fixo por operação + `/custom_fields.json` + dry-run com tradução de erros 422.