timeman 26.300.0 Ломающее

Timeman 26.200.0–26.300.0: выключенный «Учёт рабочего времени» отключает REST timeman.*, перерывы за период отдаёт RecordProvider

2 мин чтения
Узнавайте о ломающих изменениях Битрикса раньше, чем они доедут до прода: свежие разборы — в канале @bxmax_updates

Ломающее обновление

Удалены или изменены публичные 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 раньше искалась на предыдущую дату. Смены с одинаковым временем начала при выборе больше не затирают друг друга.

Новая версия вышла — разбор уже в Telegram

В канале @bxmax_updates публикуем каждый свежий разбор: что сломается, что устарело и что появилось в API. Узнаете до того, как обновите прод.

Подписаться

Читайте дальше

timeman 26.100.0 Ломающее Свежее

Timeman 26.100.0: время считается по IANA-зоне сотрудника, у CTimeManEntry и REST другие значения

С timeman 26.100.0 учёт рабочего времени берёт зону сотрудника из IANA-идентификатора в `b_user.TIME_ZONE` и считает смещение UTC на момент каждого события, с переходами на летнее и зимнее время. Форматы полей и ответов остались прежними, поменялись значения: `TIME_START`, `TIME_FINISH` и `DURATION`…

3 мин
vote 26.300.0 Безопасность Свежее

Vote 26.300.0: список проголосовавших отдаётся только по подписи

В Vote 26.300.0 закрыли утечку списка проголосовавших через попап «Кто проголосовал», в том числе в анонимных опросах, и ужесточили права на удаление и сброс опросов в админском списке. Обновиться стоит всем, у кого на портале есть опросы. Если вы копировали к себе шаблон `.userfield` компонента `vo…

4 мин
translate 26.100.0 Безопасность Свежее

Translate 26.100.0: импорт CSV и редактор переводов больше не пишут за пределы разрешённых папок

В Translate 26.100.0 закрыли выход за пределы разрешённых папок при записи языковых файлов через редактор и CSV-импорт модуля «Перевод». Обновиться стоит всем, у кого модуль установлен. Публичный API не ломается, но CSV-импорт теперь пропускает файлы с путями вне разрешённых папок.

2 мин
tasks 26.350.0 Рутинное Свежее

Tasks 26.350.0: NEW_COMMENTS_COUNT читается точечно

В tasks 26.350.0 модуль иначе читает `NEW_COMMENTS_COUNT`, значения при этом прежние. Это касается `CTaskItem::fetchList()` и `fetchListArray()` с этим полем в select, а значит и `Manager\Task::getList()` с REST-методом `tasks.task.list`, и REST-метода `tasks.task.get`. Раньше ради этого поля `Count…

1 мин
Мы используем файлы cookie для улучшения работы сайта. Продолжая использовать сайт, вы соглашаетесь с нашей политикой конфиденциальности.
AI Домовой

AI Домовой История

на связи

пишет…
Нет истории чатов
AI Домовой

Нужна авторизация

Войдите, чтобы задавать вопросы AI Домовому.

Войти