Vender pacotes de horas sem um saldo fiável é operar às escuras: o cliente questiona a fatura, o técnico não sabe quanto ainda resta e o fecho do mês vira arqueologia de folha de cálculo. O módulo Contract Hours do NexTool resolve isto dentro do próprio GLPI, cronometrando o tempo na linha de tempo do chamado e debitando do contrato certo. Este guia mostra como configurar, que campos mais confundem e o erro de operação que bloqueia a faturação inteira.
O que o GLPI nativo não entrega
O campo Duração da tarefa (armazenado em segundos na coluna actiontime) regista tempo, mas não conhece contrato, quota nem saldo. Introduz os minutos à mão, sem cronómetro, sem arredondamento faturável e sem responder à única pergunta que o cliente faz todos os meses: "quanto já usei e quanto ainda sobra?". O Contract Hours acrescenta essa camada por cima do campo nativo, sem o substituir.
Como o tempo é capturado
O técnico controla um cronómetro diretamente na linha de tempo do ticket:
- Start inicia a contagem; um crachá flutuante mantém-se visível em todas as páginas do GLPI enquanto houver uma medição ativa.
- Pause / Resume descontam as pausas (reunião, almoço) sem perder o tempo acumulado.
- Stop arredonda o tempo, cria uma TicketTask privada e debita o contrato associado.
Ao resolver ou fechar o chamado, um hook para o cronómetro automaticamente (auto-stop). É o detalhe que evita o cronómetro esquecido a correr a noite toda e a inflacionar o consumo.
Passo a passo: configurar um contrato
- Instale e ative o módulo em NexTool -> Módulos -> Contract Hours. A instalação cria as tabelas, a VIEW de resumo e regista três CronTasks (billing, cleanup, reconcile).
- Defina o mínimo faturável nas Configurações (predefinição 15 min). O arredondamento é em dois passos: primeiro para o múltiplo do mínimo, depois para múltiplo de 5 (compatibilidade com o
actiontimedo GLPI). - Crie o contrato no separador Contratos de Horas: nome, entidade (relação 1:1), Total de horas, dia de faturação, periodicidade e limiar de alerta.
- Configure os critérios no separador Condições: os grupos e/ou as categorias ITIL que fazem um chamado "cair" nesse contrato.
- Ajuste a visibilidade no separador Visibilidade: utilizadores, perfis e grupos que veem o contrato no painel do cliente. Uma lista vazia significa visível para todos com permissão de leitura.
O erro comum aqui: o campo "Total de horas" é armazenado em MINUTOS. Quem introduz "40" a pensar em 40 horas cria, na verdade, um contrato de 40 minutos e vê o saldo rebentar logo no primeiro chamado. Para 40 horas, indique 2400.
A regra de resolução é E, não OU
Decidir quando um chamado pertence a um contrato é onde mais vemos configuração errada. A lógica combina a entidade (sempre obrigatória, 1:1) com critérios opcionais de grupo e categoria:
| Critérios no contrato | Regra aplicada | O chamado entra no contrato quando |
|---|---|---|
| Nenhum | Só a entidade | Sempre (basta ser da entidade do contrato) |
| Só grupos | Pertença no tipo | Um grupo atribuído está na lista do contrato |
| Só categorias | Pertença no tipo | A categoria ITIL está nas regras do contrato |
| Grupos E categorias | AND entre os tipos | Grupo na lista E categoria nas regras (os dois) |
Ou seja: com grupo E categoria preenchidos, o ticket só entra no contrato se ambos corresponderem. Configurar os dois à espera de comportamento "OU" é o que atira metade dos chamados para o cartão "Sem Contrato".
actiontime é a fonte de verdade
A partir da versão 3.4.2 o módulo trata o actiontime da TicketTask como fonte primária. O billable_time do cronómetro é mantido sincronizado por três camadas: o hook onTicketTaskUpdated (em tempo real, quando edita a tarefa pelo GLPI nativo), o cron reconcile (a cada 4h) e o botão "Recalcular Contratos". Na prática, editar a duração da tarefa diretamente no GLPI reflete-se no saldo do contrato - desde que o cron esteja a correr.
O cron tem de estar em modo externo
Aqui está o erro de operação que bloqueia tudo. O Contract Hours depende de três CronTasks: billing (fecha o ciclo e repõe o consumo), cleanup (fecha cronómetros órfãos) e reconcile (sincroniza e volta a resolver). Só correm se as ações automáticas do GLPI estiverem em modo externo (CLI). No modo GLPI (despoletado por acesso web), um ambiente com pouco tráfego pode passar dias sem fechar o ciclo de faturação, enquanto cronómetros esquecidos vão somando horas fantasma.
Garanta o cron do sistema a apontar para o executor de ações automáticas do GLPI:
# /etc/cron.d/glpi - despoleta as acoes automaticas do GLPI a cada minuto (modo externo)
# Ajuste o caminho conforme a instalacao (em GLPI 11 o document root e public/)
* * * * * www-data /usr/bin/php /var/www/glpi/front/cron.php >/dev/null 2>&1
Depois confirme que as tarefas do módulo estão agendadas, em modo externo e com execução recente:
-- Estado das 3 CronTasks do Contract Hours
SELECT name, frequency, mode, state, lastrun
FROM glpi_crontasks
WHERE itemtype = 'PluginNextoolContracthoursCron';
-- mode=2 (externo/CLI), state=1 (agendada) e lastrun recente = saudavel.
Diagnóstico de saldo divergente
Na sustentação de ambientes de clientes, o padrão repete-se: o contrato é criado certo, os primeiros chamados debitam bem e, três semanas depois, o saldo "bloqueia". Em quase todos os casos a causa é a mesma: as ações automáticas estavam no modo web, não no CLI externo. Sem o cron externo, o billing nunca fecha o ciclo, o reconcile nunca sincroniza as edições feitas na tarefa e o cleanup nunca elimina os cronómetros órfãos de quem se esqueceu do Stop à sexta-feira. Por isso a primeira consulta que corremos num diagnóstico de Contract Hours é a de glpi_crontasks, não a do saldo: se o lastrun das três tarefas não é recente, o problema não é o módulo, é a infraestrutura do GLPI.
Confirmado o cron, o segundo suspeito é o billable_time dessincronizado do actiontime após uma edição retroativa. Meça a divergência antes de recalcular às cegas:
-- Cronometros cujo tempo faturado divergiu do actiontime da tarefa (fonte de verdade)
SELECT t.id, t.tickets_id,
t.billable_time/60 AS min_faturado,
tt.actiontime/60 AS min_tarefa,
(t.billable_time - tt.actiontime) AS diff_segundos
FROM glpi_plugin_nextool_contracthours_timers t
JOIN glpi_tickettasks tt ON tt.id = t.tickettasks_id
WHERE t.status = 'stopped'
AND t.billable_time != tt.actiontime
LIMIT 20;
Havendo divergências, o botão "Recalcular Contratos" (separador Dados) corre o sync e recalcula o consumo. Para confirmar o saldo, lembrando que os campos estão em minutos:
-- Saldo por contrato ativo (valores em minutos, convertidos para horas)
SELECT name, entities_id,
total_hours/60 AS quota_h,
used_hours/60 AS consumido_h,
(total_hours - used_hours)/60 AS saldo_h
FROM glpi_plugin_nextool_contracthours_contracts
WHERE is_active = 1
ORDER BY saldo_h;
Auditoria, alertas e cortesia
Toda a edição retroativa de tempo é registada - quem editou, quando e qual era o valor anterior - no separador Registos. É a rastreabilidade que agradece quando o cliente contesta uma hora lançada. Os alertas disparam por percentagem de consumo (limiar predefinido 80%) e por duas condições de contrato: expirado (data final no passado) e horas excedidas. Cada condição pode ser configurada como "não fazer nada", "alerta" (janela amarela que se pode fechar) ou "bloqueio". Aplica-se uma regra fina: o bloqueio vermelho só atua na criação de chamado pelo serviço de apoio (Service Catalog); um chamado já aberto nunca é bloqueado, apenas recebe o alerta. Desde a versão 3.6 existe ainda o interruptor "não faturável" (cortesia): o tempo é contabilizado mas não debita o saldo - útil para o atendimento que quer registar sem descontar a quota do cliente.
Transformar horas de chamado em faturação defensável - com saldo, extrato e um painel que o cliente entende - é exatamente o que o módulo Contract Hours do plugin NexTool resolve. Se sustenta GLPI e ainda fecha o mês na folha de cálculo, fale connosco.
Este conteúdo foi produzido com o auxílio de inteligência artificial e revisto pela equipa Nextool Solutions.