5.1 KiB
| id | title | domain | tags | status | severity | created_at | updated_at | applies_to | related_records | |||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| KB-PLUGIN-041 | GLPI 11 — Ciclo projeto→task→chamado com sincronização (o ponto cego), modal de card componentizado e adaptador de tarefas | plugin-dev |
|
active | high | 2026-07-10 | 2026-07-10 |
|
|
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:
$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 emglpi_projecttasks_tickets), gravareal_start_dateda 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 viaProjectTask::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:
$(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.