Timeman 26.200.0–26.300.0: выключенный «Учёт рабочего времени» отключает REST timeman.*, перерывы за период отдаёт RecordProvider
Ломающее обновление
Удалены или изменены публичные API — прикладной код может перестать работать.
Timeman 26.200.0 и 26.300.0 пришли в коробочный Битрикс24 в один прогон. В 26.200.0 модуль начал слушаться настройки портала: если инструмент «Учёт рабочего времени» выключен, перестают работать REST timeman.*, контроллеры и компоненты модуля, а уведомления не уходят. В 26.300.0 появился публичный метод для интервалов перерывов, а компонент отчёта за день перестал отдавать <br />. Разбор для тех, кто интегрируется с учётом времени по REST, правил шаблоны компонентов timeman или ведёт сменные графики. Предыдущий разбор: timeman 26.100.0.
Новое
Считайте перерывы без своего SQL
Bitrix\Timeman\V2\Public\Provider\RecordProvider::getPauseIntervals(int $userId, int $dateFrom, int $dateTo) (26.300.0) возвращает перерывы сотрудника за период списком ['from' => int, 'to' => ?int]. Модуль собирает их из пар PAUSE/CONTINUE журнала событий, каждую запись отдельно, поэтому перерыв первой смены не растянется на вторую. Запись, начатая до периода, тоже попадает в выборку, интервалы обрезаются по границам периода, обе границы включены. У перерыва, который ещё идёт, to равен null.
Права метод проверяет сам. Если текущему пользователю нельзя читать учёт сотрудника, вернётся пустой массив, как у дня без перерывов, и отличить одно от другого можно только через canReadWorktime($userId). Если записи периода у вас уже прочитаны, отдайте их в getPauseIntervalsOfRecords(), записи другого сотрудника он отбросит. В ядре метод пока никто не вызывает.
<?php declare(strict_types=1);
use Bitrix\Main\Loader;
use Bitrix\Timeman\Helper\TimeHelper;
use Bitrix\Timeman\V2\Public\Provider\RecordProvider;
function pausedSecondsToday(int $userId): ?int
{
Loader::requireModule('timeman');
$provider = new RecordProvider();
if (!$provider->canReadWorktime($userId)) {
return null;
}
$zone = TimeHelper::getInstance()->getUserDateTimeZone($userId);
$dayStart = new \DateTimeImmutable('today', $zone);
$from = $dayStart->getTimestamp();
$to = $dayStart->modify('+1 day')->getTimestamp() - 1;
$seconds = 0;
foreach ($provider->getPauseIntervals($userId, $from, $to) as $interval) {
$seconds += ($interval['to'] ?? time()) - $interval['from'];
}
return $seconds;
}
Журнал событий нескольких записей одним запросом
Bitrix\Timeman\Repository\Worktime\WorktimeRepository::findAllWorktimeEventsByRecordIds(array $recordIds, array $eventTypes = []) (26.300.0) читает журнал событий нескольких записей одним запросом. Типы событий можно отфильтровать, сортировка идёт по ACTUAL_TIMESTAMP, затем по ID. Сам репозиторий теперь зарегистрирован в .settings.php модуля и достаётся из ServiceLocator по имени класса:
<?php declare(strict_types=1);
use Bitrix\Main\DI\ServiceLocator;
use Bitrix\Main\Loader;
use Bitrix\Timeman\Model\Worktime\EventLog\WorktimeEventTable;
use Bitrix\Timeman\Repository\Worktime\WorktimeRepository;
Loader::requireModule('timeman');
$events = ServiceLocator::getInstance()
->get(WorktimeRepository::class)
->findAllWorktimeEventsByRecordIds(
$recordIds,
[WorktimeEventTable::EVENT_TYPE_PAUSE, WorktimeEventTable::EVENT_TYPE_CONTINUE],
);
Выключайте свои эндпоинты вместе с инструментом
Модуль закрывает свои контроллеры префильтром Bitrix\Timeman\V2\Infrastructure\Controller\ActionFilter\ToolEnabledFilter (26.200.0). Поставьте его в свой контроллер поверх данных timeman, и при выключенном инструменте ваши экшены вернут ту же ошибку TIMEMAN_TOOL_DISABLED, что и штатные.
<?php declare(strict_types=1);
namespace Vendor\Timesheet\Infrastructure\Controller;
use Bitrix\Main\Engine\Controller;
use Bitrix\Main\Loader;
use Bitrix\Timeman\V2\Infrastructure\Controller\ActionFilter\ToolEnabledFilter;
final class Overtime extends Controller
{
protected function getDefaultPreFilters(): array
{
$filters = parent::getDefaultPreFilters();
if (Loader::includeModule('timeman')) {
array_unshift($filters, new ToolEnabledFilter());
}
return $filters;
}
}
В агентах и CLI флаг проверяет isToolAvailable('worktime') у Bitrix\Timeman\Integration\Intranet\Settings. С 26.200.0 он читает состояние инструмента напрямую, в обход кеша меню, который зависит от текущего пользователя, поэтому отвечает верно и там, где пользователя нет.
Новые возвращаемые поля триггеров БП
Триггеры бизнес-процессов получили возвращаемые поля (26.200.0):
- «Запущен рабочий день»:
WORKDAY_START; - «Завершён рабочий день»:
WORKDAY_END; - «Отчёт отправлен руководителю»:
PERIOD_FROM,PERIOD_TO,EventDateTime; - «Пора формировать отчёт»:
EventDateTime; - «Добавлен отпуск в график отсутствия» и «Добавлен больничный в график»:
ABSENCE_TYPE,EventDateTime.
«Завершён рабочий день» и «Добавлен отпуск...» переехали в группы STARTER, TEAM_MANAGEMENT и HR, где уже стояли остальные триггеры модуля. Их описание требует в bizproc групп team_management и hr, на старом bizproc триггер пропадёт из дизайнера. Обновляйте bizproc вместе с timeman.
Что сломается
Ловите TIMEMAN_TOOL_DISABLED в интеграциях
С 26.200.0 выключенный в настройках портала «Учёт рабочего времени» выключает модуль целиком:
- REST
timeman.settings,timeman.status,timeman.open,timeman.close,timeman.pause,timeman.networkrange.*иtimeman.timecontrol.*отвечают ошибкойTIMEMAN_TOOL_DISABLED; - ту же ошибку отдают контроллеры модуля, все V2-контроллеры и REST v3 записей учёта, а старый
tools/timeman.phpотвечаетerror_id: 'TIMEMAN_TOOL_DISABLED'; - компоненты показывают заглушку выключенного инструмента;
- не уходят уведомления в чат и живую ленту по рабочему дню и отчётам, уведомления агента нарушений
period_time_lackиshift_missed, pull-события.
Интеграция, которая открывает и закрывает день по REST на портале со скрытым инструментом, теперь получит ошибку. Повтор запроса не поможет, ошибка уйдёт только после включения инструмента в настройках портала.
Положите tool-disabled.php в свои шаблоны
При выключенном инструменте компоненты bitrix:timeman, timeman.monitor.report, timeman.report, timeman.schedule.edit, timeman.schedule.list, timeman.schedule.shift.edit, timeman.schedule.shiftplan, timeman.settings, timeman.settings.permissions, timeman.worktime и timeman.worktime.record.report подключают страницу шаблона tool-disabled (26.200.0). Она есть только в системном .default. Ядро ищет страницу в папках с тем же именем шаблона, и шаблон со своим именем её не найдёт. Скопируйте tool-disabled.php из .default в каждый свой шаблон этих компонентов.
Выдайте право на запись тем, кто прикрепляет файлы к чужим отчётам
В 26.200.0 закрыта дыра в правах на вложения рабочего отчёта. Раньше для чужого отчёта хватало права на чтение учёта. Теперь добавлять и удалять файлы может автор отчёта или тот, чьё право WRITE из CTimeMan::GetAccess() распространяется на автора: на всех ('*') или на этого сотрудника. Руководители с правом только на чтение больше не прикрепят файл к отчёту подчинённого: загрузка пропускает файлы, удаление не выполняется. Правило вынесено в Bitrix\Timeman\Security\ReportAttachmentPermission::canManage().
Разбирайте даты из контекста триггера как строку
С 26.200.0 даты в контекст триггеров БП уходят строкой в формате бизнес-процессов, например 31.07.2026 09:00:00 [10800], в скобках смещение в секундах. До этого StopWorktimeTrigger::FIELD_WORKDAY_START передавался объектом \DateTime. Код, который ждёт там объект, получит строку. Модуль сам читает такие даты через CBPHelper::makeTimestamp():
<?php declare(strict_types=1);
use Bitrix\Timeman\Integration\Bizproc\StopWorktimeTrigger;
function workdayStartTimestamp(array $context): ?int
{
$value = $context[StopWorktimeTrigger::FIELD_WORKDAY_START] ?? null;
return match (true) {
$value instanceof \DateTimeInterface => $value->getTimestamp(), // timeman до 26.200.0
is_string($value) && $value !== '' => (int)\CBPHelper::makeTimestamp($value),
default => null,
};
}
Пропустите текст отчёта через парсер
Компонент timeman.worktime.record.report в 26.300.0 перестал прогонять отчёт через nl2br(). В $arResult['WORKTIME_REPORT']['REPORT'] теперь экранированный текст с BB-кодами и обычными переводами строк: HTML из редактора модуль приводит к BB-кодам. Штатный шаблон отдаёт его в CTextParser с allow['NL2BR'] = 'Y', и переносы там больше не удваиваются. Кастомный шаблон, который выводит REPORT как есть, потеряет переносы и покажет BB-коды текстом. Возьмите блок вывода отчёта из штатного templates/.default/template.php.
Предупредите сотрудников со сменными графиками
В 26.200.0 поменялись правила кнопки «Начать рабочий день». Новый день открывается только после закрытой записи: если время окончания проставлено, а статус всё ещё «открыт» или «на паузе», запись сначала нужно закрыть. Если график записи убрали из активных графиков сотрудника, старт не ждёт конца смены этой записи и ищет смену в текущих графиках.
Появился потолок в 18 часов. Запись, у которой не стало следующей смены (её график убрали из набора или рядом добавили свободный график), через 18 часов от начала считается просроченной, когда сотрудник продолжает день и когда модуль проверяет истечение записи. Окно редактирования считается по старому порогу.
Изменения заметят сотрудники со сменными графиками и те, кому меняли график посреди дня.
Мелочи и находки
- В 26.300.0 пересобран бандл
timeman.work-time-report. Кнопка «Обсудить отчёт» по старомуdistсама искала или создавала чат черезim.chat.getиim.chat.add, теперь она вызывает серверныйtimeman.V2.ReportDiscussion.discuss, а ошибку показывает уведомлением. timeman.V2.Record.getCurrentRecord(26.200.0) отдаёт сотруднику его текущую запись, если он может вести свой рабочий день, даже когда роль не даёт права читать учёт.- Панель статуса (26.200.0) у закрытого дня, который нельзя открыть заново, показывает сохранённые длительность и перерывы вместо нулей.
- План смен (26.200.0) ищется по местной дате начала смены, а не по UTC: у сотрудника в UTC+3 смена с началом в 01:00 раньше искалась на предыдущую дату. Смены с одинаковым временем начала при выборе больше не затирают друг друга.