KB-PLUGIN-040: Kanban multi-itemtype em plugin (agregacao, mapa coluna<->estado, update() nativo)

Aprendizado do mindscrum 0.2.0. Inclui o achado de que update(status=5) resolve
o chamado sem exigir a solucao do formulario ITIL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Gemini 2026-07-10 15:33:17 -03:00
parent c51827a57c
commit e497e96274
2 changed files with 124 additions and 0 deletions

View file

@ -763,6 +763,28 @@
"severity": "high",
"path": "records/plugin-dev/KB-PLUGIN-039-glpi11-project-ticket-pm-substrate.md",
"summary": "id: KB-PLUGIN-039"
},
{
"id": "KB-PLUGIN-040",
"title": "\"GLPI 11 — Kanban multi-itemtype num plugin (Chamados + Tarefas de Projeto): agregação, mapa coluna↔estado e escrita via update() nativo\"",
"domain": "plugin-dev",
"tags": [
"glpi11",
"plugin",
"kanban",
"board",
"ticket",
"projecttask",
"projectstate",
"drag-drop",
"ajax",
"twig",
"mindscrum"
],
"status": "active",
"severity": "high",
"path": "records/plugin-dev/KB-PLUGIN-040-glpi11-unified-kanban-multi-itemtype.md",
"summary": "id: KB-PLUGIN-040"
}
]
}

View file

@ -0,0 +1,102 @@
---
id: KB-PLUGIN-040
title: "GLPI 11 — Kanban multi-itemtype num plugin (Chamados + Tarefas de Projeto): agregação, mapa coluna↔estado e escrita via update() nativo"
domain: plugin-dev
tags:
- glpi11
- plugin
- kanban
- board
- ticket
- projecttask
- projectstate
- drag-drop
- ajax
- twig
- mindscrum
status: active
severity: high
created_at: 2026-07-10
updated_at: 2026-07-10
applies_to:
- GLPI 11.0.8 dev
- plugin mindscrum 0.2.0
related_records:
- KB-PLUGIN-039
- KB-PLUGIN-010
- KB-PLUGIN-028
- KB-PLUGIN-036
---
# GLPI 11 — Kanban multi-itemtype num plugin (Chamados + Tarefas de Projeto)
O Kanban nativo é **single-itemtype** (KB-PLUGIN-039). Um board que mistura
`Ticket` + `ProjectTask` na mesma tela é viável num plugin **agregando os dados
e roteando o drop para o `update()` do itemtype certo** — sem tocar no motor.
Validado no mindscrum 0.2.0 (E2E via bootstrap do Kernel, KB-PLUGIN-031).
## Arquitetura que funcionou
- **Colunas unificadas fixas** (PoC): Novo / Em andamento / Pendente / Concluído.
- **`ColumnMap`** — o miolo da regra, reconcilia dois modelos:
- `Ticket.status` (enum 1..6) ↔ coluna;
- `ProjectTask.projectstates_id` (`ProjectState`: 1=New, 2=Processing, 3=Closed) ↔ coluna.
- Dois sentidos: leitura `estado → coluna` (montar o board) e escrita
`coluna → estado` (aplicar no drop).
- **`CardProvider`** — agrega via `$DB->request()`: tickets com
`getEntitiesRestrictCriteria('glpi_tickets')`; project tasks com LEFT JOIN em
`glpi_projects` para o nome do projeto.
- **Render server-side**: `TemplateRenderer::getInstance()->display('@mindscrum/board.html.twig', [...])`
a partir de `front/board.php` (sem `inc/includes.php` — KB-PLUGIN-028; namespace
`@<key>` auto — KB-PLUGIN-036).
- **Drop → `ajax/move.php`**: `fetch` POST com header `X-Glpi-Csrf-Token` +
`X-Requested-With: XMLHttpRequest` (KB-PLUGIN-010). O endpoint **não** chama
`checkCSRF` (o kernel já fez), só `checkLoginUser` + `haveRight` + `canUpdateItem`,
e delega para o model.
- **Drag-drop sem dependência**: HTML5 DnD nativo (o core tem `sortable.js` em
`public/lib/`, mas o nativo dispensa registro de asset e resolve o PoC).
## Regra de escrita — SEMPRE pelo model nativo
```php
$item = new $itemtype(); // 'Ticket' | 'ProjectTask' (allowlist!)
$item->getFromDB($items_id);
$input = ['id' => $items_id];
$itemtype === 'Ticket'
? $input['status'] = ColumnMap::ticketStatusForColumn($col)
: $input['projectstates_id'] = ColumnMap::projectStateForColumn($col);
$ok = $item->update($input); // preserva histórico (Log), date_mod e regras
```
Nunca `UPDATE` direto no banco: perderia histórico e validações. Validado:
`Ticket.status 2→4` e `2→5`, `ProjectTask.projectstates_id 1→3` — todos
`update()=true`, refletidos no banco e reversíveis.
## ⚠️ Achado — `update(['status' => 5])` NÃO exige solução
Colocar um chamado em **Resolvido (5)** ou **Fechado (6)** via `$ticket->update()`
**funciona sem a solução** que o formulário ITIL de solução normalmente exige
(`update()=true`, `status=5` no banco, sem mensagem de bloqueio).
Ou seja, `update(status)` **bypassa a regra de negócio da solução**. Implicações:
- Para um Kanban, mover para "Concluído" muda o status limpo — ótimo para UX.
- Mas se o processo exige registrar solução, o plugin precisa **impor isso na sua
camada** (pedir texto e gravar via `ITILSolution`) em vez de só `update(status)`.
- Não confiar que "mudar status = disparar as regras do formulário ITIL".
## Gotchas confirmados
- **Bump de versão** (0.1.0→0.2.0) marca o plugin como "Para atualizar"; rodar
`bin/console plugin:install <key>` + `plugin:activate <key>` para reativar
(KB-PLUGIN-013). Install/rights são idempotentes.
- **Twig cacheia em produção**: template novo compila na 1ª vez, mas ao **editar**
um `.twig` limpar `/var/glpi/files/_cache/*-production/templates`.
- **Entidade em ProjectTask**: `glpi_projecttasks` não tem `entities_id` próprio
(herda do projeto). O PoC não restringe tasks por entidade — pendência: filtrar
via join com `glpi_projects.entities_id`.
## Próxima evolução
Colunas **configuráveis** (criar/renomear, mapeadas a estados nativos, incl.
`ProjectState` customizados) — hoje são fixas no `ColumnMap`. É o requisito de
"budgets herdam estados nativos" levado adiante.