Documentgenerator 26.400.0: часовой пояс и сдвиг дат в шаблонах, очередь актуализации удалена
Ломающее обновление
Удалены или изменены публичные API — прикладной код может перестать работать.
В documentgenerator 26.400.0 у документов появился часовой пояс: его задают порталу и отдельному шаблону, а дату в плейсхолдере можно сдвинуть на N дней или вывести в другом поясе прямо в docx. Заодно из модуля вырезали очередь актуализации и Document::actualize(). CRM 26.1750.0 на нашем эталоне коробочного Битрикс24 по-прежнему пытается ставить документы в эту очередь, и документы смарт-счетов после правки счёта больше не перегенерируются. Если вы сами ставили документы в очередь или наследуете классы модуля, код придётся поправить.
Новое
Задайте документам часовой пояс
Дата и время в документе форматируются в поясе, который берётся по цепочке: поле TIMEZONE шаблона, затем опция модуля document_timezone, затем пояс сервера. Опцию выставляет новая настройка «Часовой пояс» в настройках генератора документов (компонент bitrix:documentgenerator.config). Пояс работает за фича-флагом Feature\Timezone, он включён по умолчанию. Пока ни опция, ни поле шаблона не заданы, документы выглядят как раньше, сдвиг от пояса сервера нулевой.
Пояс отдельного шаблона выбирают в форме шаблона в поле «Часовой пояс», пункт «Как в настройках модуля» его сбрасывает. Из кода пояс задают через контроллер или REST: documentgenerator.template.add и update (и crm.documentgenerator.template.* через прокси) принимают timezone, а get и list его возвращают. Нужен канонический идентификатор IANA вроде Asia/Yekaterinburg, на остальное придёт ошибка TIMEZONE_INVALID. Пустая строка сбрасывает поле в null, и шаблон снова берёт пояс из опции.
Из PHP можно писать в ORM напрямую, проверив пояс тем же Timezone::isValid():
<?php
declare(strict_types=1);
use Bitrix\DocumentGenerator\Model\TemplateTable;
use Bitrix\DocumentGenerator\Timezone;
use Bitrix\Main\Config\Option;
use Bitrix\Main\Error;
use Bitrix\Main\Loader;
use Bitrix\Main\Result;
Loader::requireModule('documentgenerator');
// пояс по умолчанию для всех шаблонов, то же, что настройка «Часовой пояс»
Option::set('documentgenerator', Timezone::OPTION_NAME, 'Europe/Moscow');
function setTemplateTimezone(int $templateId, ?string $timezone): Result
{
// null вернёт шаблон к поясу из опции
if ($timezone !== null && !Timezone::isValid($timezone)) {
return (new Result())->addError(new Error("Неизвестный пояс {$timezone}", 'TIMEZONE_INVALID'));
}
return TemplateTable::update($templateId, ['TIMEZONE' => $timezone]);
}
$result = setTemplateTimezone(12, 'Asia/Yekaterinburg');
Для своего выпадающего списка возьмите Timezone::getItems(). Он отдаёт пункты вида «(UTC+03:00) Europe/Moscow», отсортированные по смещению.
Считайте срок оплаты прямо в шаблоне
У плейсхолдеров дат два новых модификатора. add= сдвигает дату, timezone= выводит её в указанном поясе:
{DocumentCreateTime~d.m.Y,add=+10 days}
{DocumentCreateTime~d.m.Y H:i,timezone=Asia/Novosibirsk}
{DocumentCreateTime~d.m.Y,add=+1 month,timezone=Asia/Vladivostok}
Знак у add= обязателен, а между числом и единицей нужен пробел: add=10 days и add=+10days не сработают. Число до шести цифр, единицы year, month, day, hour, minute и second, можно с окончанием s. Часы, минуты и секунды к полю с чистой датой без времени не применяются, а два add= в одном плейсхолдере отменяют сдвиг совсем. timezone= ставьте последним среди остальных сегментов, add= может стоять где угодно. Сдвиг работает, только пока включён флаг Feature\Timezone.
Ошибка в модификаторе генерацию не ломает. Дата выйдет без сдвига или в унаследованном поясе, а в логгер documentgenerator.Default уйдёт предупреждение, не чаще раза на вид ошибки за генерацию. Как вывести этот логгер в отдельный файл, мы показывали в разборе 26.300.0.
Шаблон названия документа за выключенным флагом
Флаг Feature\DocumentTitlePattern по умолчанию выключен. С включённым флагом в форме шаблона появляется поле для шаблона названия (расширение documentgenerator.title-pattern), а template.add и update принимают titlePattern до 255 символов: {DocumentNumber}, {DocumentCreateTime} и скалярные поля, которые есть и в теле шаблона, и у всех провайдеров. Шаблон применяется только к новым документам без явного DocumentTitle. Если шаблон названия не отрендерился, документ получит прежнее название «имя шаблона + номер».
Что сломается
Замените Document::actualize()
Удалены Service\ActualizeQueue, Service\ActualizeQueue\Task, ORM-сущность Model\ActualizeQueueTable и сервис documentgenerator.service.actualizeQueue. ServiceLocator по коду этого сервиса теперь бросает ServiceNotFoundException. Вместе с очередью ушёл публичный Document::actualize().
Внутри actualize() были два вызова: setUserId() и update($this->getExternalValues(true)). Второй вызов снаружи не повторить, getExternalValues() объявлен protected. Но loadById() уже кладёт в документ сохранённые ручные значения, а setValues() внутри update() их только дополняет. Поэтому update([]) на загруженном документе делает то же самое: провайдеры отдают свежие данные, ручные правки остаются, файл пересобирается.
<?php
declare(strict_types=1);
namespace Vendor\Docs;
use Bitrix\Crm\Activity\Provider\Document as DocumentActivity;
use Bitrix\DocumentGenerator\Document;
use Bitrix\Main\Error;
use Bitrix\Main\Loader;
use Bitrix\Main\Result;
final class DocumentActualizer
{
public function actualize(int $documentId, ?int $userId = null): Result
{
Loader::requireModule('documentgenerator');
$document = Document::loadById($documentId);
if ($document === null) {
return (new Result())->addError(new Error("Документ {$documentId} не найден"));
}
if ($userId !== null) {
$document->setUserId($userId);
}
return $document->update([]);
}
/** Документы незавершённых дел смарт-счёта, как их раньше брал CRM */
public function actualizeSmartInvoice(int $invoiceId, int $userId): Result
{
Loader::requireModule('crm');
$result = new Result();
$activities = \CCrmActivity::GetList([], [
'OWNER_TYPE_ID' => \CCrmOwnerType::SmartInvoice,
'OWNER_ID' => $invoiceId,
'COMPLETED' => 'N',
'STATUS' => \CCrmActivityStatus::Waiting,
'TYPE_ID' => \CCrmActivityType::Provider,
'PROVIDER_ID' => DocumentActivity::getId(),
'PROVIDER_TYPE_ID' => DocumentActivity::PROVIDER_TYPE_ID_DOCUMENT,
], false, false, ['ID', 'ASSOCIATED_ENTITY_ID']);
while ($activity = $activities->Fetch()) {
$update = $this->actualize((int)$activity['ASSOCIATED_ENTITY_ID'], $userId);
if (!$update->isSuccess()) {
$result->addErrors($update->getErrors());
}
}
return $result;
}
}
Документы смарт-счетов перестали обновляться сами
Очередью пользовался CRM. В CRM 26.1750.0, который стоит на эталоне вместе с этой версией, код постановки в очередь остался, но DocumentGeneratorManager::getActualizeQueue() ловит ObjectNotFoundException и возвращает null. В итоге молча ничего не делают действие ActualizeDocuments после сохранения смарт-счёта и saveAction карточки crm.invoice.details. Актуализацию при открытии документа вырезали из ViewComponent самого documentgenerator.
Пользователь поправил счёт, а документ из запланированного дела остался со старыми данными. Если документы должны обновляться, вызывайте actualizeSmartInvoice() из примера выше там, где вы уже обрабатываете сохранение счёта. ID документа CRM хранит в ASSOCIATED_ENTITY_ID дела.
Проверьте наследников
Три protected-метода сменили сигнатуру:
Controller\Template::add()получил пятый параметр?\CRestServer $restServer = null;Document::getValues()получил второй параметр по ссылке?array &$rawValueKinds = null;- в
DataProviderManager::getValueByType()параметр$formatпереименован в$options.
Наследник, который переопределяет первые два метода со старой сигнатурой, упадёт с fatal error о несовместимом объявлении. Переименование фатала не даёт и мешает только вызову с именованными аргументами.
Deprecated
Константа Body::DO_NOT_INSERT_VALUE_MODIFIER ('__SystemDeletePlaceholder') помечена @deprecated, а её обработку удалили. Если в docx-шаблоне стоял ~__SystemDeletePlaceholder, значение теперь печатается, и строки таблиц с этим модификатором больше не исключаются при размножении. Замены нет. В модулях эталона модификатор не используется, так что проверять стоит только свои шаблоны.
БД
- В
b_documentgenerator_templateдобавлены колонкиTIMEZONE varchar(255) NULLиTITLE_PATTERN varchar(255) NULL, вTemplateTableпоявились одноимённые поля. ПустойTIMEZONEORM сохраняет какnull. - Таблица
b_documentgenerator_actualize_queueосталась в схеме с пометкой@deprecated. ORM-класса у неё больше нет, и писать в неё некому. - Новая опция
documentgenerator/document_timezone. - Агент
\Bitrix\DocumentGenerator\Service\ActualizeQueue::process(5);убран изinstall/migrations/agents.php. Удаляет ли обновление уже зарегистрированную запись, по диффу не видно. Если запись осталась, сама она не удалится. Агент будет падать на отсутствующем классе примерно раз в 10 минут и каждый раз писать ошибку в лог исключений. Проверьте:
SELECT ID, NAME, NEXT_EXEC FROM b_agent WHERE NAME LIKE '%ActualizeQueue::process%';
Найденную запись удалите в списке агентов (/bitrix/admin/agent_list.php).
Мелочи и находки
- Из
DocxXml::processMultiplyingBlock()убрали отладочныйdie. Блок{<список>.BLOCK_START~index=2}с ненулевым индексом обрывал PHP-процесс посреди генерации, если элемент с таким индексом в списке был. В истории эталона этотdieживёт с самого первого снапшота. - Если на странице несколько превью документов, картинку по push теперь получает каждое. Раньше глобальный флаг привязывал обработчик только к первому.
Устаревшие и удалённые API в этой версии
| Символ | Статус | Чем заменять |
|---|---|---|
Bitrix\DocumentGenerator\Body::DO_NOT_INSERT_VALUE_MODIFIER
|
Deprecated | — |
Bitrix\DocumentGenerator\Service\ActualizeQueue
|
Удалено | — |
Bitrix\DocumentGenerator\Service\ActualizeQueue\Task
|
Удалено | — |
Bitrix\DocumentGenerator\Model\ActualizeQueueTable
|
Удалено | — |
Bitrix\DocumentGenerator\Document::actualize()
|
Удалено |
Bitrix\DocumentGenerator\Document::update()
|