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:
parent
e497e96274
commit
f10ee9dfcf
2 changed files with 146 additions and 0 deletions
22
index.json
22
index.json
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
Loading…
Reference in a new issue