Документы

Класс Bitrix\DocumentGenerator\Document создает файл по шаблону и данным исходного объекта. Используйте его, чтобы передать значения полей, проверить обязательные данные, сохранить результат и повторно сформировать файл после изменения исходного объекта.

Документ проходит несколько состояний:

шаблон и исходный объект
    -> объект Document без идентификатора
    -> проверка полей и обработка тела
    -> сохраненный документ и исходный файл
    -> преобразованные файлы, если преобразование включено

Подготовить данные

До создания документа подготовьте:

  • существующий шаблон с телом и классом основного провайдера,

  • исходное значение в формате провайдера, например идентификатор объекта,

  • идентификатор пользователя, от имени которого провайдер проверит доступ,

  • дополнительные значения для переопределения полей, если они нужны,

  • требуемый формат результата.

Подключите модуль documentgenerator и проверьте доступность генерации. Инструкции по подготовке шаблона и провайдера приведены в статьях Шаблоны, поля и форматирование и Провайдеры данных.

Метод hasAccess(?int $userId = null): bool проверяет доступ пользователя к данным основного провайдера. Он возвращает

  • true, если доступ разрешен,

  • false, если провайдер запретил чтение данных.

Если идентификатор $userId не передан, метод использует значение из setUserId(), а затем пользователя из Driver.

Эта проверка не заменяет разрешение на создание или изменение документов, которое устанавливает продукт или модуль точки интеграции. До работы с Document проверьте права на нужную операцию средствами этого модуля. Разделение проверок описано в статье Права доступа.

Создать и сохранить документ

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

  1. Загрузите шаблон.

  2. Создайте объект Document.

  3. Проверьте доступ к исходным данным.

  4. Получите файл.

Метод createByTemplate(Template $template, $value, array $data = []): ?Document создает по шаблону объект Document в памяти. Метод не сохраняет документ и не формирует файл.

Параметры метода:

  • $template — шаблон с телом и классом основного провайдера,

  • $value — исходное значение в формате основного провайдера,

  • $data — начальные данные документа. По умолчанию используется пустой массив.

Метод возвращает объект Document или null, если модуль не получил тело шаблона.

После создания объекта задайте пользователя и проверьте доступ методом hasAccess(). Затем передайте дополнительные значения, проверьте обязательные поля и вызовите getFile(false).

use Bitrix\DocumentGenerator\Document;
use Bitrix\DocumentGenerator\Driver;
use Bitrix\DocumentGenerator\Template;
use Bitrix\Main\Loader;

// Перед запуском подготовьте:
// $templateId — идентификатор существующего шаблона
// $providerClass — полное имя класса провайдера
// $sourceValue — исходное значение в формате провайдера
// $userId — идентификатор пользователя для проверки доступа

if (!Loader::includeModule('documentgenerator'))
{
    throw new \RuntimeException('Модуль documentgenerator не установлен');
}

if (!Driver::getInstance()->isEnabled())
{
    throw new \RuntimeException('Генератор документов недоступен');
}

// Загрузите шаблон и задайте основной провайдер
$template = Template::loadById($templateId);

if (!$template || $template->isDeleted())
{
    throw new \RuntimeException('Шаблон не найден');
}

$template->setSourceType($providerClass);

if (!$template->getSourceType())
{
    throw new \RuntimeException('Провайдер данных не поддерживается');
}

// Создайте документ и проверьте доступ к исходным данным
$document = Document::createByTemplate($template, $sourceValue);

if ($document === null)
{
    throw new \RuntimeException('Не удалось создать объект документа');
}

$document->setUserId($userId);

if (!$document->hasAccess())
{
    throw new \RuntimeException('Нет доступа к исходным данным');
}

// Передайте дополнительные значения и проверьте обязательные поля
$document->setValues([
    'DocumentTitle' => 'Счет для клиента',
    'Comment' => 'Оплатить в течение пяти рабочих дней',
]);

$emptyRequiredFields = $document->checkFields();

if ($emptyRequiredFields !== [])
{
    throw new \RuntimeException(
        'Не заполнены обязательные поля: '
        . implode(', ', array_keys($emptyRequiredFields))
    );
}

// Сформируйте исходный файл
$result = $document->getFile(false);

if (!$result->isSuccess())
{
    throw new \RuntimeException(implode('; ', $result->getErrorMessages()));
}

$documentData = $result->getData();
$documentId = (int)$documentData['id'];
$downloadUrl = $documentData['downloadUrl'];

Метод getFile(bool $sendToTransformation = true, bool $skipTransformationError = false): Result обрабатывает и сохраняет документ. В примере вызов getFile(false) формирует исходный файл без отправки на преобразование.

Параметры метода:

  • $sendToTransformation — отправлять ли исходный файл на преобразование. По умолчанию true,

  • $skipTransformationError — исключать ли ошибку преобразования из общего результата. По умолчанию false.

После успешного вызова в результате доступны идентификатор документа id и ссылка на файл downloadUrl.

Проверить результат

После сохранения данные Result содержат сведения о документе и доступных файлах:

Ключ

Тип

Описание

id

int

Идентификатор документа

downloadUrl

Bitrix\Main\Web\Uri

Ссылка на исходный файл

title

string

Название документа

number

string

Номер документа

createTime

Bitrix\Main\Type\DateTime

Дата и время создания

updateTime

Bitrix\Main\Type\DateTime

Дата и время последнего обновления

createdBy

int или null

Идентификатор пользователя, который создал документ

updatedBy

int или null

Идентификатор пользователя, который обновил документ

value

mixed

Исходное значение основного провайдера

values

array

Сохраненные дополнительные значения

isTransformationError

bool

Признак ошибки преобразования

pullTag

string

Тег для уведомления о результате преобразования. Доступен, если работают преобразователь и модуль pull

pdfUrl

Bitrix\Main\Web\Uri

Ссылка на PDF. Доступна после создания PDF

imageUrl

Bitrix\Main\Web\Uri

Ссылка на изображение. Доступна после создания изображения

printUrl

Bitrix\Main\Web\Uri

Ссылка на печать PDF. Доступна после создания PDF

transformationErrorMessage

string

Сообщение об ошибке. Доступно при ошибке преобразования

transformationErrorCode

mixed

Код ошибки. Доступен при ошибке преобразования

Проверяйте Result::isSuccess() до чтения данных. Метод checkFields() находит пустые обязательные поля, но не проверяет обработку тела, запись в хранилище и преобразование. Форматы результата и параметры преобразования описаны в статье Файлы, форматы и преобразование.

Получить преобразованный файл

У нового документа метод getFile() без аргументов сохраняет исходный файл и ставит в очередь задачу на создание PDF и изображения. У сохраненного документа этот вызов не пересоздает исходный файл, но может поставить недостающие форматы в очередь. Успешная постановка задачи не означает, что ключи pdfUrl и imageUrl уже появились в первом результате.

Чтобы убедиться, что преобразованные файлы созданы, дождитесь события onDocumentTransformationComplete или уведомления по тегу pullTag, если доступен модуль Push and Pull pull. Затем повторно загрузите документ и вызовите getFile(false). Готовые форматы появятся в данных результата как pdfUrl и imageUrl.

use Bitrix\DocumentGenerator\Document;

$document = Document::loadById($documentId);

if ($document === null)
{
    throw new \RuntimeException('Документ не найден');
}

$result = $document->getFile(false);

if (!$result->isSuccess())
{
    throw new \RuntimeException(implode('; ', $result->getErrorMessages()));
}

$documentData = $result->getData();
$pdfUrl = $documentData['pdfUrl'] ?? null;
$imageUrl = $documentData['imageUrl'] ?? null;

Если преобразование завершилось ошибкой, данные результата содержат признак isTransformationError и могут содержать сообщение и код ошибки. Не запускайте создание документа повторно. Исходный документ уже сохранен.

Передать дополнительные значения

Метод setValues(array $values): Document добавляет значения к данным провайдера или заменяет ранее переданные значения с тем же плейсхолдером. Ключ массива $values должен совпадать с кодом поля шаблона.

$document->setValues([
    'DocumentTitle' => 'Акт за август',
    'DocumentNumber' => 'A-2026-08',
    'ManagerComment' => 'Передать оригинал в бухгалтерию',
]);

Значения DocumentTitle и DocumentNumber задают название и номер документа. Если передать DocumentNumber, модуль использует это значение и не формирует номер автоматически.

Вызов setValues() не удаляет значения, которые были переданы ранее. Повторный вызов заменяет только ключи из нового массива.

Проверить обязательные поля

Метод checkFields(bool $requiredOnly = true): array возвращает обязательные поля с пустыми значениями. Поле считается обязательным, если в его описании установлено REQUIRED=Y.

$emptyRequiredFields = $document->checkFields();

foreach ($emptyRequiredFields as $placeholder => $field)
{
    $title = $field['TITLE'] ?? $placeholder;
    echo $placeholder . ': ' . $title . PHP_EOL;
}

Вызовите checkFields(false), чтобы дополнительно получить плейсхолдеры без рассчитанного значения. Такой режим помогает диагностировать шаблон, но не означает, что все найденные поля обязательны.

Учесть нумерацию

Нумератор — механизм, который формирует следующий номер документа по настройкам шаблона. Если значение DocumentNumber не передано, модуль использует нумератор шаблона.

Модуль использует тип нумератора DOCUMENT. Для него доступны базовые служебные слова {NUMBER}, {DAY}, {MONTH}, {YEAR}, {RANDOM} и {PREFIX}, а зарегистрированные генераторы могут добавить другие слова. Настройка шаблона номера и разработка собственных генераторов описаны в статье Нумератор.

У нового несохраненного документа без переданного значения DocumentNumber метод getNumber() возвращает следующий номер, но не резервирует его. При обработке документа модуль запрашивает следующий номер у нумератора и сохраняет результат в документе.

Если шаблон не содержит подходящего нумератора, модуль пытается использовать нумератор по умолчанию. Ошибка получения номера делает результат обработки неуспешным.

Модуль получает следующий номер до проверки шаблона, обязательных полей и обработки тела. Ошибка на последующем этапе может оставить пропуск в последовательности номеров. Не используйте предварительный просмотр номера как подтверждение успешного создания документа.

Методы update() и actualize() сохраняют номер загруженного документа. Чтобы задать другой номер, передайте значение DocumentNumber при обновлении и учтите правила нумерации приложения.

Загрузить сохраненный документ

Метод Document::loadById(int $documentId): ?Document возвращает сохраненный документ по положительному идентификатору $documentId или null, если запись не найдена.

use Bitrix\DocumentGenerator\Document;

$document = Document::loadById($documentId);

if ($document === null)
{
    throw new \RuntimeException('Документ не найден');
}

Загруженный объект содержит исходное значение провайдера, сохраненные дополнительные значения и идентификаторы файлов. Перед обновлением задайте пользователя и проверьте доступ к данным исходного объекта.

Обновить сохраненный документ

Выберите метод по источнику изменений:

  • вызовите update(), чтобы добавить или заменить дополнительные значения,

  • вызовите actualize(), если изменился исходный объект, а сохраненные дополнительные значения нужно использовать повторно.

Оба метода обновляют существующую запись и сохраняют идентификатор документа. Первый вызов getFile() создает новую запись, поэтому не используйте его вместо обновления сохраненного документа.

Изменить дополнительные значения

Метод update(array $values, bool $sendToTransformation = true, bool $skipTransformationError = false): Result повторно получает данные провайдера, обрабатывает тело и заменяет файл сохраненного документа. Метод работает только для документа с идентификатором.

Параметры метода:

  • $values — значения, которые нужно добавить или заменить,

  • $sendToTransformation — отправлять ли новый исходный файл на преобразование. По умолчанию true,

  • $skipTransformationError — исключать ли ошибку преобразования из общего результата. По умолчанию false.

Измените заголовок и комментарий, затем пересоздайте только исходный файл.

use Bitrix\DocumentGenerator\Document;

$document = Document::loadById($documentId);

if ($document === null)
{
    throw new \RuntimeException('Документ не найден');
}

$document->setUserId($userId);

if (!$document->hasAccess())
{
    throw new \RuntimeException('Нет доступа к исходным данным');
}

$result = $document->update(
    [
        'DocumentTitle' => 'Исправленный акт за август',
        'ManagerComment' => 'Повторная отправка клиенту',
    ],
    false
);

if (!$result->isSuccess())
{
    throw new \RuntimeException(implode('; ', $result->getErrorMessages()));
}

После успешного обновления проверьте данные результата. Ключ id должен совпасть с исходным идентификатором, downloadUrl должен вести на новый исходный файл, а updateTime — содержать время обновления.

Обновление заменяет исходный файл и сбрасывает сохраненные идентификаторы PDF и изображения. Если второй аргумент равен true, модуль повторно запускает преобразование.

Актуализировать данные провайдера

Метод actualize(?int $userId = null, bool $sendToTransformation = true, bool $skipTransformationError = false): Result повторно формирует документ с теми же дополнительными значениями. Используйте его после изменения исходного объекта, когда шаблон и ручные переопределения должны сохраниться.

Параметры метода:

  • $userId — идентификатор пользователя, который попадет в updatedBy. По умолчанию null,

  • $sendToTransformation — отправлять ли новый исходный файл на преобразование. По умолчанию true,

  • $skipTransformationError — исключать ли ошибку преобразования из общего результата. По умолчанию false.

use Bitrix\DocumentGenerator\Document;

$document = Document::loadById($documentId);

if ($document === null)
{
    throw new \RuntimeException('Документ не найден');
}

if (!$document->hasAccess($userId))
{
    throw new \RuntimeException('Нет доступа к исходным данным');
}

$result = $document->actualize($userId, false);

if (!$result->isSuccess())
{
    throw new \RuntimeException(implode('; ', $result->getErrorMessages()));
}

После успешной актуализации проверьте в данных результата идентификатор, ссылку downloadUrl и время updateTime. Идентификатор должен остаться прежним, а файл — содержать новые данные исходного объекта.

Прямой вызов actualize() обрабатывает документ в текущем запросе. Внутренние интеграции могут поставить актуализацию в очередь, но класс Document не переводит прямой вызов actualize() в фоновый режим.

Защитить повторный запуск от дублей

Метод createByTemplate() не проверяет, создавался ли документ для той же пары шаблона и исходного объекта. Каждый независимый сценарий создания может сохранить новый документ.

Для повторяемой операции храните идентификатор документа в данных приложения или в связи с исходным объектом. Перед генерацией попытайтесь загрузить документ по этому идентификатору:

  1. Если документ найден, вызовите update() или actualize().

  2. Если документ не найден, создайте его через createByTemplate() и сохраните идентификатор из результата getFile().

  3. Если несколько процессов могут запустить сценарий одновременно, защитите проверку и сохранение средствами приложения.

Класс Document не устанавливает блокировку для нескольких одновременных обновлений одного документа. Не запускайте параллельную актуализацию без координации. Завершившийся позже процесс может заменить результат предыдущего.

Не допустить рекурсию в обработчиках событий

При обработке модуль вызывает события onBeforeProcessDocument, onCreateDocument и onUpdateDocument. Если обработчик одного из этих событий повторно запускает актуализацию того же документа, может возникнуть рекурсивный цикл.

Перед повторным вызовом update() или actualize() из обработчика добавьте условие, которое остановит повторную обработку того же документа.

Обработать ошибки

Проверяйте отдельный Result после каждой операции, которая его возвращает.

Этап

Возможная проблема

Проверка

Создание объекта

Не удалось получить тело шаблона

createByTemplate() вернул null

Доступ к данным

Провайдер запретил чтение исходного объекта

hasAccess() вернул false

Проверка полей

Обязательное поле не содержит значения

checkFields() вернул непустой массив

Обработка и сохранение

Ошибка шаблона, провайдера или хранилища

getFile(), update() или actualize() вернул неуспешный Result

Преобразование

Не удалось получить PDF или изображение

Ошибки Result и данные isTransformationError

Сохранение файла и данных документа выполняется в несколько шагов. Если после сохранения файла не удалось записать данные документа, проверьте состояние хранилища отдельно. Ошибка генерации не отменяет изменения исходного объекта.

Способ повторного запуска зависит от того, был ли документ сохранен и на каком этапе произошла ошибка:

  • Если $document->ID больше нуля, документ уже сохранен. Устраните причину ошибки, повторно загрузите документ через Document::loadById() и вызовите update() или actualize().

  • Если не удалось только преобразование, загрузите сохраненный документ и повторите getFile() с включенным преобразованием. Не вызывайте update() только ради получения PDF или изображения.

  • Если идентификатора нет, документ не сохранен. Зафиксируйте ошибки Result, устраните причину и только после этого снова вызовите createByTemplate().

Не удаляйте напрямую из хранилища файл, который мог остаться после ошибки. Без записи документа класс Document не позволяет надежно определить все связанные данные.

Удалить документ

Класс Document не содержит метода удаления. Способ удаления зависит от продукта или модуля, который создал документ. Внутри модуль удаляет запись через модель данных, а обработчики очищают исходный файл, PDF, изображение и публичную ссылку. Не используйте ORM-класс модели как универсальный API приложения.

Также не удаляйте исходный файл напрямую через хранилище. Запись документа сохранит ссылку на отсутствующий файл, а PDF, изображение и публичная ссылка останутся связанными с документом. Используйте операцию удаления, которую предоставляет точка интеграции. Такая операция управляет жизненным циклом документа.

Связанные материалы