diff --git a/index.json b/index.json index 87b728a..d8ee757 100755 --- a/index.json +++ b/index.json @@ -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" } ] } diff --git a/records/plugin-dev/KB-PLUGIN-040-glpi11-unified-kanban-multi-itemtype.md b/records/plugin-dev/KB-PLUGIN-040-glpi11-unified-kanban-multi-itemtype.md new file mode 100644 index 0000000..e4e5cff --- /dev/null +++ b/records/plugin-dev/KB-PLUGIN-040-glpi11-unified-kanban-multi-itemtype.md @@ -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 + `@` 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 ` + `plugin:activate ` 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.