7sites - внедрение Битрикс24
Услуги
  • Лицензии 1С-Битрикс
    • Облачный сервис Битрикс24
    • Коробочная версия Битрикс24
    • Подписка Битрикс24.Маркет Плюс
  • Внедрение CRM
  • Доработка корпоративных порталов Битрикс24
  • Техническое сопровождение
  • Перенос Битрикс24 из облака в коробку
  • Сайты на 1С-Битрикс24
Проекты
  • Государственные организации
  • Управление бизнесом
  • Спорт
  • Автомобильный бизнес
Акции
Новости
Компания
  • О компании
  • Сертификаты
  • Карьера
  • Разработка ПО
Контакты
Ещё
    Задать вопрос
    +7 (495) 118-30-89
    Заказать звонок
    info@7sites.ru
    г. Пермь, ш. Космонавтов 111, к. 10
    • Вконтакте
    • Facebook
    • Twitter
    • Telegram
    +7 (495) 118-30-89
    Заказать звонок
    7sites - внедрение Битрикс24
    Услуги
    • Лицензии 1С-Битрикс
      • Облачный сервис Битрикс24 —
      • Коробочная версия Битрикс24 —
      • Подписка Битрикс24.Маркет Плюс
    • Внедрение CRM
    • Доработка корпоративных порталов Битрикс24
    • Техническое сопровождение
    • Перенос Битрикс24 из облака в коробку
    • Сайты на 1С-Битрикс24
    Проекты
    • Государственные организации
    • Управление бизнесом
    • Спорт
    • Автомобильный бизнес
    Акции
    Новости
    Компания
    • О компании
    • Сертификаты
    • Карьера
    • Разработка ПО
    Контакты
      7sites - внедрение Битрикс24
      Услуги
      • Лицензии 1С-Битрикс
        • Облачный сервис Битрикс24 —
        • Коробочная версия Битрикс24 —
        • Подписка Битрикс24.Маркет Плюс
      • Внедрение CRM
      • Доработка корпоративных порталов Битрикс24
      • Техническое сопровождение
      • Перенос Битрикс24 из облака в коробку
      • Сайты на 1С-Битрикс24
      Проекты
      • Государственные организации
      • Управление бизнесом
      • Спорт
      • Автомобильный бизнес
      Акции
      Новости
      Компания
      • О компании
      • Сертификаты
      • Карьера
      • Разработка ПО
      Контакты
        7sites - внедрение Битрикс24
        7sites - внедрение Битрикс24
        • Услуги
          • Назад
          • Услуги
          • Лицензии 1С-Битрикс
            • Назад
            • Лицензии 1С-Битрикс
            • Облачный сервис Битрикс24
            • Коробочная версия Битрикс24
            • Подписка Битрикс24.Маркет Плюс
          • Внедрение CRM
          • Доработка корпоративных порталов Битрикс24
          • Техническое сопровождение
          • Перенос Битрикс24 из облака в коробку
          • Сайты на 1С-Битрикс24
        • Проекты
          • Назад
          • Проекты
          • Государственные организации
          • Управление бизнесом
          • Спорт
          • Автомобильный бизнес
        • Акции
        • Новости
        • Компания
          • Назад
          • Компания
          • О компании
          • Сертификаты
          • Карьера
          • Разработка ПО
        • Контакты
        • +7 (495) 118-30-89
        info@7sites.ru
        г. Пермь, ш. Космонавтов 111, к. 10
        • Вконтакте
        • Facebook
        • Twitter
        • Telegram
        • Главная
        • Пресс-центр
        • Полезное
        • Автозакрытие рабочего дня в коробочном Битрикс24 через агента

        Автозакрытие рабочего дня в коробочном Битрикс24 через агента

        28 июля 2023
        Полезное

        Введение

        В коробочной версии Битрикс24 учёт рабочего времени ведётся модулем timeman. Если сотрудник забыл закрыть рабочий день, система продолжает считать его открытым — в отчётах появляются переработки вида «24ч 0м», а функционал, завязанный на статус рабочего дня (например, распределение звонков телефонии), работает некорректно. Со временем сотрудник и вовсе перестаёт открывать день.

        В этой статье разберём корректное программное закрытие рабочего дня через штатный метод CTimeManUser::CloseDay(), покажем подводные камни, с которыми приходится сталкиваться на практике, и расскажем, почему для регулярного запуска по расписанию лучше использовать агент Битрикса, а не cron-скрипт по URL.


        Область применения

        Задача автозакрытия рабочего дня возникает в следующих сценариях:

        • Массовое закрытие дней по отделу в конце рабочего дня (например, в 21:00).
        • Закрытие просроченных дней (статус EXPIRED) у сотрудников, которые забыли завершить день.
        • Закрытие дней за прошлые даты при разборе инцидентов и корректировке учёта.
        • Автоматизация учёта рабочего времени в распределённых командах.

        Штатный метод CloseDay

        За программное закрытие рабочего дня отвечает метод CTimeManUser::CloseDay() модуля timeman. Он выполняет весь цикл операций: фиксирует статус закрытия, рассчитывает длительность, проставляет метку завершения, снимает паузу, создаёт дневной отчёт.

        Сигнатура метода

        CloseDay($timestamp = false, $report = '', $bFieldsOnly = false)

        Параметры

        • $timestamp — время закрытия. Передаётся не Unix-время, а wall-time — секунды от начала дня. Для 19:00:00 это 19 * 3600 = 68400.
        • $report — текст отчёта или причина закрытия.
        • $bFieldsOnly — режим работы метода. Не отключает проверку прав.

        Базовый пример

        <?
        require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");
        CModule::IncludeModule("timeman");
        
        $userId = 371;
        $seconds = 19 * 3600; // 19:00:00 в wall-time
        
        $timeMan = new CTimeManUser($userId);
        $result = $timeMan->CloseDay($seconds, "Автозакрытие", false);
        

        Чтение данных о рабочем дне

        Перед закрытием дня нужно прочитать последнюю запись. Есть два инструмента:

        • CTimeManEntry::GetLast($userId) — возвращает последнюю запись рабочего дня из таблицы b_timeman_entries. Это штатный метод, без прямых SQL-запросов.
        • CTimeManUser::State() — возвращает текущее состояние: OPENED, CLOSED, PAUSED, EXPIRED.

        Пример чтения записи

        <?
        CModule::IncludeModule("timeman");
        
        $userId = 371;
        $entry = CTimeManEntry::GetLast($userId);
        
        if ($entry) {
        	echo "DATE_START:     " . $entry['DATE_START'] . "\n";
        	echo "DATE_FINISH:    " . ($entry['DATE_FINISH'] ?: "NULL") . "\n";
        	echo "CURRENT_STATUS: " . $entry['CURRENT_STATUS'] . "\n";
        }
        
        $timeMan = new CTimeManUser($userId);
        echo "State(): " . $timeMan->State() . "\n";
        

        Статусы рабочего дня

        Статус Что означает
        OPENED Рабочий день открыт сегодня
        CLOSED Рабочий день закрыт
        PAUSED Рабочий день на паузе
        EXPIRED Рабочий день открыт более 24 часов назад и не закрыт

        Подводный камень №1: wall-time, а не Unix-timestamp

        Самая частая ошибка — передать в CloseDay() абсолютный Unix-timestamp, полученный через strtotime() или MakeTimeStamp(). Метод ожидает секунды от начала дня, а не абсолютное время.

        Если передать, например, 1790949600 (это 02.10.2026 19:00:00 в Unix-времени), внутри модуля произойдёт сложение этого значения с полем TIME_START записи, и получится строка вида 2026-10-02 497486:00:00. Метод упадёт с ошибкой:

        [DateMalformedStringException]
        Failed to parse time string (2026-10-02 497486:00:00) at position 15 (8): Unexpected character (0)

        Правильно — считать секунды от начала дня:

        $closeTime = "19:00:00";
        list($hours, $minutes, $secondsPart) = explode(":", $closeTime);
        $seconds = $hours * 3600 + $minutes * 60 + $secondsPart; // 68400
        

        Подводный камень №2: CloseDay работает только под авторизацией

        Без прав администратора или пользователя с правом «Управление рабочим временем» метод молча возвращает false и ничего не пишет в базу. Исключение при этом может быть пустым — диагностировать причину сложно.

        Проверено на практике:

        • под авторизацией администратора CloseDay() возвращает массив с полями DATE_FINISH, TIME_FINISH, CURRENT_STATUS = 'CLOSED', DURATION, TIME_LEAKS и другими — день закрыт корректно;
        • без авторизации метод возвращает false, в базе ничего не меняется.

        Третий параметр $bFieldsOnly = false проверку прав не отключает. Он управляет только режимом работы метода.

        Подводный камень №3: прямое обновление b_timeman_entries

        Иногда вместо CloseDay() пытаются напрямую обновить запись в таблице b_timeman_entries:

        UPDATE b_timeman_entries SET
        	DATE_FINISH = '...',
        	TIME_FINISH = ...,
        	CURRENT_STATUS = 'CLOSED'
        WHERE ID = ...
        

        Так делать не стоит. Учёт рабочего времени — это набор связанных данных: длительность, метки старта и остановки, признаки пауз, перерывы, отчёты. Прямое обновление одного поля без остальных приводит к рассогласованию. В интерфейсе «Рабочее время» день может отображаться как «не завершён», а CloseDay() для такой записи в дальнейшем вернёт «Невозможно выполнить действие».

        Единственный корректный способ — штатный CloseDay().

        Почему агент, а не cron-скрипт по URL

        Рабочий день нужно закрывать регулярно — например, каждый день в 21:00. Первое, что приходит в голову, — повесить скрипт на cron и вызывать его по URL через curl.

        У этого подхода есть существенный недостаток: скрипт становится доступен извне по URL. Любой, кто узнает адрес, может его дёрнуть. Защита строится только на секретном токене, который легко утекает — из логов, из переписки, из истории браузера. Если токен утёк, злоумышленник получает возможность выполнить скрипт с админскими правами.

        Агент Битрикса лишён этого недостатка:

        • агент выполняется внутри серверного процесса через cron_events.php, недоступен извне по URL;
        • не нужен секретный токен и открытый эндпоинт;
        • авторизация администратора выполняется в памяти процесса, без создания сессии в браузере;
        • снаружи достучаться до агента невозможно — только через shell сервера.

        Как работает агент

        Агент срабатывает по cron раз в сутки в 21:00. Внутри агента:

        1. подключается модуль timeman;
        2. восстанавливается объект $USER (при запуске на cron он удаляется системой) и выполняется $USER->Authorize(1) — временная авторизация администратора на время выполнения агента;
        3. через CUser::GetList выбираются все активные сотрудники подразделения;
        4. для каждого сотрудника читается последняя запись через CTimeManEntry::GetLast($userId) и вызывается CloseDay(68400, "Автозакрытие", false);
        5. результат по каждому сотруднику пишется в лог.

        Важно: на cron $USER сбрасывается

        Перед запуском агента система удаляет объект $USER, чтобы не было дыр в безопасности. Поэтому внутри агента его надо создать заново:

        global $USER;
        if (!is_object($USER)) {
        	$USER = new CUser;
        }
        $USER->Authorize(1); // ID пользователя с правом «Управление рабочим временем»
        

        Код агента

        <?
        function AgentRabDen()
        {
        	if (!CModule::IncludeModule('timeman')) {
        		return "AgentRabDen();";
        	}
        	if (!CModule::IncludeModule('iblock')) {
        		return "AgentRabDen();";
        	}
        
        	global $USER, $APPLICATION;
        	if (!is_object($USER)) {
        		$USER = new CUser;
        	}
        	$USER->Authorize(1);
        
        	$departmentId = 42;
        	$filter = Array(
        		"ACTIVE" => "Y",
        		"UF_DEPARTMENT" => $departmentId,
        	);
        	$params = Array(
        		"SELECT" => Array("UF_DEPARTMENT"),
        	);
        
        	$rsUsers = CUser::GetList(($sort = "id"), ($order = "asc"), $filter, $params);
        	$rsUsers->NavStart(500);
        
        	$seconds = 19 * 3600; // 19:00:00 в wall-time
        
        	while ($user = $rsUsers->Fetch()) {
        		$userId = intval($user['ID']);
        
        		$entry = CTimeManEntry::GetLast($userId);
        		if (!$entry) {
        			continue;
        		}
        
        		$APPLICATION->ResetException();
        
        		$timeMan = new CTimeManUser($userId);
        		$result = $timeMan->CloseDay($seconds, "Автозакрытие", false);
        
        		$exception = $APPLICATION->GetException();
        		$exceptionText = $exception ? $exception->GetString() : "нет";
        
        		file_put_contents(
        			__DIR__ . "/agent_log.txt",
        			date("Y-m-d H:i:s") . " | ID=$userId | CloseDay=" . (is_array($result) ? "OK" : "false")
        				. " | EXCEPTION=" . $exceptionText . "\n",
        			FILE_APPEND
        		);
        	}
        
        	return "AgentRabDen();"; // агент не удаляется
        }
        ?>
        

        Подключение функции агента

        Агент в Битриксе вызывается по имени функции, поэтому перед регистрацией агента функцию нужно сделать доступной ядру. Для этого файл с функцией подключается в /local/php_interface/init.php:

        require_once($_SERVER["DOCUMENT_ROOT"] . "/local/php_interface/agents/close_workday.php");

        Вместо /local/php_interface/agents/close_workday.php укажите свой путь к файлу с функцией агента. Главное — чтобы файл подключался до того, как ядро начнёт искать функцию.

        init.php выполняется один раз при загрузке страницы, а require_once кэширует подключение — накладных расходов на производительность почти нет. Без этой строки при запуске агента ядро не найдёт функцию и выдаст ошибку:

        Fatal error: Call to undefined function AgentRabDen()

        Убирать подключение из init.php можно только после удаления агента из списка или его отключения.

        Регистрация агента

        Агент регистрируется через CAgent::AddAgent() или через интерфейс «Настройки → Инструменты → Агенты». Важно указать первый запуск в 21:00:

        CAgent::AddAgent(
        	"AgentRabDen();",
        	"main",
        	"N",
        	86400,                    // интервал — раз в сутки
        	date("d.m.Y 21:00:00"),   // первый запуск — сегодня в 21:00
        	"Y",
        	"",
        	100
        );

        Функция должна возвращать своё имя (return "AgentRabDen();";), иначе агент удалится после первого выполнения.

        Формат лога

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

        2026-10-03 14:47:01 | === ЗАКРЫТИЕ ДНЯ ПО ОТДЕЛУ 42 ===
          ID=101   | Иванов Иван       | ДО=CLOSED | CloseDay=OK | ПОСЛЕ=CLOSED | EXCEPTION=нет
          ID=102   | Петров Пётр       | ДО=CLOSED | CloseDay=OK | ПОСЛЕ=CLOSED | EXCEPTION=нет
          ID=103   | Сидоров Сидор     | ДО=OPENED | CloseDay=OK | ПОСЛЕ=CLOSED | EXCEPTION=нет
        ----------------------------------------
        

        Особенности и рекомендации

        1. Обязательное подключение модуля: перед вызовом любого метода необходим CModule::IncludeModule("timeman").
        2. Формат DATE_START: метод CTimeManEntry::GetLast() возвращает дату в формате портала ДД.ММ.ГГГГ ЧЧ:МИ:СС. strtotime() его разбирает корректно, а MakeTimeStamp() с константой FORMAT_DATETIME — нет. Для парсинга используйте strtotime().
        3. Проверка исключений: перед CloseDay() полезно вызывать $APPLICATION->ResetException(), а после — $APPLICATION->GetException(), чтобы поймать ошибку, если она есть.
        4. Повторный запуск безопасен: для уже закрытого дня CloseDay() под авторизацией вернёт массив, но реального изменения в базе не будет. Для просроченного дня запись будет дополнена корректными полями.
        5. Не обновляйте b_timeman_entries напрямую. Это ломает связи между полями и приводит к некорректному отображению в интерфейсе.
        6. Один агент — один отдел: если отделов несколько, регистрируйте отдельные агенты или передавайте подразделение параметром.
        7. Первый запуск: через интерфейс «Настройки → Инструменты → Агенты» можно вручную запустить агент, не дожидаясь cron.
        8. Логирование: файл лога удобно держать в том же каталоге, что и функцию агента. После отладки логи можно отключить, убрав file_put_contents.

        Заключение

        Программное закрытие рабочего дня в коробочном Битрикс24 выполняется штатным методом CTimeManUser::CloseDay(). Ключевые моменты: передавать wall-time (секунды от начала дня), а не Unix-timestamp; выполнять под авторизацией администратора или пользователя с правом «Управление рабочим временем»; не обновлять b_timeman_entries напрямую. Для регулярного запуска по расписанию используйте агент Битрикса — он выполняется внутри серверного процесса, недоступен извне и не требует секретных токенов в URL.

        Начните сотрудничество с индивидуальной консультации по подбору инструментов и услуг.
        Заказать услугу

        Подписаться на
        рассылку

        Будьте в курсе последних новостей отрасли

        Подписаться
        • Комментарии
        Загрузка комментариев...
        Назад к списку
        Услуги
        Лицензии 1С-Битрикс
        Внедрение CRM
        Доработка корпоративных порталов Битрикс24
        Техническое сопровождение
        Перенос Битрикс24 из облака в коробку
        Сайты на 1С-Битрикс24
        Компания
        О компании
        Сертификаты
        Карьера
        Разработка ПО
        Проекты
        Новости
        Контакты
        Подписка на рассылку
        +7 (495) 118-30-89
        Заказать звонок
        info@7sites.ru
        г. Пермь, ш. Космонавтов 111, к. 10
        • Вконтакте
        • Facebook
        • Twitter
        • Telegram
        Политика конфиденциальности
        © 2009-2026 ООО "7 САЙТОВ" (с) Все права защищены.
        Заказать звонок
        Написать сообщение
        Ближайший офис