KB-PLUGIN-041: ciclo projeto->task->chamado (sync do ponto cego), card componentizado, adaptador de tarefas e flatpickr em modal

Aprendizado do mindscrum 0.5.0.

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

View file

@ -785,6 +785,28 @@
"severity": "high",
"path": "records/plugin-dev/KB-PLUGIN-040-glpi11-unified-kanban-multi-itemtype.md",
"summary": "id: KB-PLUGIN-040"
},
{
"id": "KB-PLUGIN-041",
"title": "\"GLPI 11 — Ciclo projeto→task→chamado com sincronização (o ponto cego), modal de card componentizado e adaptador de tarefas\"",
"domain": "plugin-dev",
"tags": [
"glpi11",
"plugin",
"project",
"projecttask",
"ticket",
"tickettask",
"lifecycle",
"hooks",
"sync",
"flatpickr",
"mindscrum"
],
"status": "active",
"severity": "high",
"path": "records/plugin-dev/KB-PLUGIN-041-glpi11-project-ticket-lifecycle-sync-and-componentized-card.md",
"summary": "id: KB-PLUGIN-041"
}
]
}

View file

@ -0,0 +1,124 @@
---
id: KB-PLUGIN-041
title: "GLPI 11 — Ciclo projeto→task→chamado com sincronização (o ponto cego), modal de card componentizado e adaptador de tarefas"
domain: plugin-dev
tags:
- glpi11
- plugin
- project
- projecttask
- ticket
- tickettask
- lifecycle
- hooks
- sync
- flatpickr
- 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.5.0
related_records:
- KB-PLUGIN-039
- KB-PLUGIN-040
- KB-PLUGIN-010
- KB-PLUGIN-036
---
# GLPI 11 — Ciclo projeto→task→chamado, sincronização e card componentizado
Modelo de domínio validado no mindscrum 0.5.0. O **card do kanban é o CHAMADO**;
a task de projeto é a *origem* (não é card). Fluxo:
```
Projeto (datas planejadas) → tasks do projeto (subtarefas)
→ [iniciar task] cria um chamado a partir dela (nativo) → grava real_start_date
→ chamado é o card; técnico executa (ticket_tasks) e soluciona
→ [ao solucionar] fecha a task 1:1 e grava real_end_date ← ponto cego do GLPI resolvido
```
## Criar chamado a partir de uma task (spawn nativo)
`Ticket::add(['name'=>…, 'content'=>…, 'entities_id'=>…, '_projecttasks_id'=>$taskId])`.
O core cria o vínculo `ProjectTask_Ticket` sozinho em `Ticket::post_addItem`
(campo mágico `_projecttasks_id`). Atribuir o técnico responsável depois com
`Ticket_User::add(['tickets_id'=>…, 'users_id'=>…, 'type'=>CommonITILActor::ASSIGN])`.
## As duas automações (hooks) — fecham o ponto cego
Registro em `setup.php`:
```php
$PLUGIN_HOOKS['item_add']['mindscrum'] = ['Ticket' => 'plugin_mindscrum_ticket_add'];
$PLUGIN_HOOKS['item_update']['mindscrum'] = ['Ticket' => 'plugin_mindscrum_ticket_update'];
```
- **item_add**: se o chamado nasce ligado a uma task (`$ticket->input['_projecttasks_id']`
ou vínculo em `glpi_projecttasks_tickets`), grava `real_start_date` da task (se vazio).
- **item_update**: se `in_array('status', $ticket->updates)` e status ∈ {5 Solucionado,
6 Fechado}, fecha a(s) task(s) vinculada(s): `projectstates_id` = estado is_finished,
`percent_done` = 100, `real_end_date` = agora. Tudo via `ProjectTask::update()` (nunca SQL
direto — preserva Log/regras). **O core NÃO faz nada disso** (verificado: zero referência a
ProjectTask no fluxo de solução do CommonITILObject).
Hooks disparam em CLI também (Kernel bootstrap) — dá pra testar E2E o ciclo inteiro.
Gotcha de teste CLI: `$_SESSION['glpigroups']` precisa existir (`[]`), senão
`TicketTask::add()` estoura em `Reminder::getVisibilityCriteria` (o navegador sempre popula).
## Modal de card componentizado — um render, esquema normalizado
Em vez de um modal por itemtype, o backend (`CardDetail::get`) devolve um **esquema
único** e o JS tem um `renderCard()` só:
```
{ itemtype, id, type_label, title, native_url,
properties: [ {key, label, kind:'text'|'priority'|'dates', value/…, editable} ],
attachments: [...],
tasklist: { label, action:'spawn'|'toggle', can_add, items:[…] },
notes: { items:[…], can_post },
progress: { done, total } }
```
Cada itemtype só preenche esse contrato. `tasklist.action` decide o comportamento da
bolinha (projeto=spawn de chamado; chamado=marca feito). Notas: chamado→`ITILFollowup`,
projeto→tabela própria `cardnotes`.
## Adaptador de tarefas — ProjectTask ↔ TicketTask
Uma classe `Tasks` com a mesma interface (`listFor/create/toggle`) para os dois. Mapa dos
campos comuns (fonte: schemas reais):
| Comum | ProjectTask (`glpi_projecttasks`) | TicketTask (`glpi_tickettasks`) |
|---|---|---|
| Descrição | `name`/`content` | `content` (sem título) |
| Início previsto | `plan_start_date` | `begin` |
| Fim previsto | `plan_end_date` | `end` |
| Estado a fazer/feito | `projectstates_id`/`percent_done` | `state` (`Planning::TODO=1`/`DONE=2`) |
| Responsável | `users_id` | `users_id_tech` |
| Tempo real | `effective_duration` | `actiontime` |
Só do projeto (o plugin preenche via automação, não digitado): `real_start_date`,
`real_end_date`, `percent_done`, `planned_duration`, `is_milestone`.
## Datepicker nativo (flatpickr) dentro de modal injetado por JS
`Html::header` **já carrega** flatpickr (JS+CSS) e o plugin de botões
(`js/flatpickr_buttons_plugin.js`, que expõe `CustomFlatpickrButtons()`) em toda página
central — não precisa `requireJs`. Para usar num modal montado via innerHTML, inicializar
depois de inserir no DOM:
```js
$(el).flatpickr({
altInput: true, altFormat: 'd/m/Y H:i', dateFormat: 'Y-m-d H:i:S',
enableTime: true, time_24hr: true, allowInput: true,
plugins: [ CustomFlatpickrButtons() ] // botão "agora"/"hoje"
});
```
`$` (jQuery) é global; guardar com fallback para `<input type=text>` se indisponível. O
input original mantém o valor `Y-m-d H:i:S` para enviar ao backend (o backend deve aceitar
datetime completo, não só `Y-m-d`).
## Regra para agentes
O card é o chamado; a task de projeto vira chamado por spawn. Nunca sincronizar
projeto↔chamado por SQL — sempre `update()` nos models, dentro de hooks `item_add`/
`item_update`. Para UI de data/hora em modal de plugin, reusar o flatpickr que o core já
carregou; não embutir picker de terceiro.