ActualTime и Contract Hours: учёт времени в GLPI

ActualTime измеряет фактическое время специалиста; Contract Hours контролирует остаток по договору и выставляет счета. Сравнение двух плагинов учёта времени в GLPI, компромисс общего поля actiontime, вердикт по сценариям и способ измерить разрыв из-за округления.

В этой статьеРазделов: 8

Измерить время заявки и выставить за это время счёт — две разные задачи. Одна касается продуктивности команды: сколько фактического времени потратил специалист. Другая — договора: сколько часов клиент уже израсходовал и сколько у него осталось. Путать их — самая частая ошибка, которую мы видим в сопровождении, и выбор между ActualTime и Contract Hours начинается именно с этого.

Проблема: три плоскости «времени» в GLPI

Прежде чем выбирать плагин, стоит разделить три вещи, которые GLPI обрабатывает по-разному:

  • Плановое время — длительность, которую вы запланировали на задачу.
  • Фактическое время — сколько специалист реально потратил, по секундомеру.
  • Тарифицируемое время — то, что идёт в счёт по договору, уже округлённое и с учётом коммерческих правил.

Стандартное поле задачи Длительность (хранится в секундах в actiontime) решает базовую задачу, но требует ручного ввода и ничего не знает о договоре. Здесь и появляются два плагина.

ActualTime (TICGAL)

ActualTime — бесплатный плагин с открытым исходным кодом от TICGAL. Он добавляет в задачу заявки кнопки запуска, паузы и остановки и засекает фактическое время, которое специалист на неё тратит. При остановке измеренное время записывается в длительность задачи. Фокус честный и узкий: превратить «кажется, я потратил минут 40» в измеренное число. У него нет понятия договора, остатка или выставления счетов — и оно ему не нужно, потому что его цель — метрика трудозатрат, а не тарификация.

Contract Hours (NexTool)

Contract Hours — лицензируемый модуль NexTool для тех, кто продаёт пакеты часов. Таймер находится в ленте заявки, но результат другой: при остановке время округляется до настроенного минимального тарифицируемого интервала (по умолчанию 15 минут), превращается в автоматическую приватную TicketTask и списывается с остатка договора, привязанного к этой заявке. В нём есть то, чего нет у ActualTime: месячная квота по договору, выписка расхода, панель «Расход часов» для клиента в упрощённом интерфейсе, оповещения об истёкшем или превышенном договоре и, начиная с версии 3.6, понятие нетарифицируемых (бесплатных) часов — времени, которое регистрируется, но не списывается с остатка.

Сравнение

КритерийСтандартная длительность (вручную)ActualTime (TICGAL)Contract Hours (NexTool)
Как фиксируется времяРучной вводТаймер в задачеТаймер в ленте заявки
Измеряет фактическое времяНет (что введёте)ДаДа
Тарифицируемое округлениеНетНетДа (настраиваемый минимум, кратный 5)
Привязка к договоруНетНетДа (по организации; группа И категория)
Остаток / квота часовНетНетДа (месячная квота + оповещения)
Нетарифицируемые (бесплатные) часыНетНетДа (переключатель в задаче)
Выписка / отчёт о расходеСтандартный (ограниченный)БазовыйПодробный по циклу / периоду
Панель для клиентаНетНетДа («Расход часов»)
Куда записывается времяactiontimeactiontimeactiontime (источник истины) + остаток
ЛицензияСтандартная функцияБесплатно (GPL)Лицензируемый
Поддерживаемый GLPI10 и 1110 и 11Только 11

Компромисс, который важен в эксплуатации

В сопровождении мы использовали оба: ActualTime у клиентов, где болью была внутренняя продуктивность, Contract Hours — там, где договор на часы был сердцем выставления счетов. Деталь, которая проявляется только в работе: оба в итоге затрагивают одно и то же стандартное поле — actiontime задачи. ActualTime записывает туда измеренное фактическое время; Contract Hours считает actiontime задачи источником истины (начиная с 3.4.2) и создаёт приватную задачу с уже округлённым временем. Если запускать оба в одной заявке без правила, кто «владеет» задачей, возникают две конкурирующие длительности — сырое измерение специалиста спорит с округлённой тарифицируемой записью. Поэтому мы решили разделить плоскости: ActualTime как внутренняя метрика (никогда не тарифицируется) и Contract Hours как договорная плоскость, и никогда не позволять обоим создавать задачу в одной заявке. Причина проста: число, которое попадает в счёт клиента, должно быть округлённым и учитывать договор, а не сырыми секундами — а сырые секунды как раз нужны руководству для планирования мощностей. Две аудитории, два числа, два инструмента.

Типичная ошибка — ожидать, что ActualTime ответит на вопрос «сколько часов осталось по договору?». У него нет такого понятия. Каждый клиент, который пытался собрать это, выгружая данные ActualTime в таблицу, через несколько недель видел, как число расходится с реальным. Есть ещё и разрыв из-за округления: Contract Hours округляет вверх до минимального тарифицируемого интервала (по умолчанию 15 минут, кратно 5 для совместимости с GLPI). Задача на 3 минуты тарифицируется как 15. В месяц с большим количеством коротких заявок разница между фактическим итогом (ActualTime) и выставленным итогом (Contract Hours) существенна — и именно эту разницу вы обсуждаете с клиентом или поглощаете как бесплатные часы через переключатель нетарифицируемого времени.

Измерьте разрыв сами

Поскольку оба плагина пишут в стандартное поле actiontime (в секундах), записанное время можно посчитать прямо в базе данных, не завися от таблиц плагинов. Просуммируйте по специалистам за месяц и сравните с выставленным итогом в выписке Contract Hours:

-- Horas lançadas por técnico no mês (campo nativo actiontime, em segundos)
SELECT u.name AS tecnico,
       ROUND(SUM(tt.actiontime) / 3600, 2) AS horas_lancadas
FROM glpi_tickettasks tt
JOIN glpi_users u ON u.id = tt.users_id_tech
WHERE tt.date BETWEEN '2026-06-01' AND '2026-06-30 23:59:59'
  AND tt.actiontime IS NOT NULL
GROUP BY tt.users_id_tech
ORDER BY horas_lancadas DESC;

Если этот итог совпадает с измеренным фактическим временем, но расходится с выставленным, разница — это округление плюс бесплатные часы, и это информация для переговоров, а не ошибка.

Вердикт по сценариям

  • Используйте ActualTime, когда вопрос звучит как «продуктивна ли моя команда?». Вы хотите измерять реальные трудозатраты, питать внутренние показатели и не берёте плату за час. Нулевая стоимость, простая установка.
  • Используйте Contract Hours, когда вы продаёте пакеты часов и клиент спрашивает «сколько я уже использовал и сколько осталось?». Нужны остаток, выписка, тарифицируемое округление, панель клиента и оповещение о превышении. Это вложение, но оно окупается на первом же договоре, по которому вы перестанете недовыставлять счета.
  • Используйте оба, когда вам нужно и внутреннее фактическое время, И внешнее тарифицируемое, при условии разделения плоскостей: ActualTime как метрика (не тарифицируется) и Contract Hours для выставления счетов, с чётким правилом, что задачу в заявке создаёт только один из них.
  • Оставайтесь на стандартном поле, когда объём небольшой и ручной длительности уже достаточно. Не устанавливайте плагин ради проблемы, которой у вас нет.

Стоимость и трудозатраты

ActualTime бесплатен (GPL): затраты обычные для любого плагина — проверить сборку под вашу версию GLPI и включить его в окно обновления. Contract Hours лицензируется, но реальные трудозатраты внедрения связаны не с таймером, а с моделью договора: определить квоту, сопоставить группу и категорию, которые обслуживают каждый договор (правило — И, а не ИЛИ), настроить минимальный тарифицируемый интервал и видимость того, кто видит каждый договор на панели. Это полчаса кликов и несколько часов коммерческих решений. Отдача видна при первом закрытии месяца, когда выписка готова для клиента без всяких таблиц.

В NexTool мы внедряем и сопровождаем обе модели в GLPI B2B-клиентов. Если ваше узкое место — превращать часы по заявкам в обоснованные счета, с остатком, выпиской и панелью, понятной клиенту, — модуль Contract Hours плагина NexTool решает именно эту задачу. Если вы ещё не определились, какая плоскость подходит вашей работе, свяжитесь с нами.


Проверено командой NexTool Solutions.

Часто задаваемые вопросы

Да, оба в итоге пишут в стандартное поле actiontime задачи. Они могут сосуществовать, но в разных плоскостях: ActualTime — для внутренней метрики (не для выставления счетов), Contract Hours — для тарификации. Никогда не позволяйте обоим создавать задачу в одной и той же заявке, иначе вы сложите сырое измерение с округлённой записью и завысите часы.

Потому что Contract Hours округляет вверх до минимального тарифицируемого интервала (по умолчанию 15 минут, кратно 5). Задача на 3 минуты тарифицируется как 15. Разница между фактическим и выставленным временем — это округление плюс часы, отмеченные как нетарифицируемые (бесплатные). Это ожидаемо и именно это вы обсуждаете с клиентом.

Нет. ActualTime только измеряет фактическое время, затраченное на задачу; у него нет понятия договора, квоты или остатка. Чтобы ответить на вопрос «сколько уже использовано и сколько осталось», нужен Contract Hours или внешний учёт, который со временем начинает расходиться с реальностью.

Нет. Модуль Contract Hours работает только в GLPI 11. В GLPI 10 для измерения времени используется ActualTime или стандартное поле длительности; учёт по договорам придётся решать другим способом до перехода на 11.

В стандартном GLPI у задачи есть поле «Длительность» (actiontime, в секундах). Его можно суммировать по специалистам через SQL или через поиск. Для небольшого объёма это работает, но вручную и без какого-либо понятия о договоре или тарифицируемом округлении.

Нужна помощь?