Разработка генераторов документов

Перед разработкой настройте плагин Document. Для заполнения шаблонов используются контекстные переменные.

Генерация документа

Это описание способа генерирования документов с использованием билдеров и контекстных переменных с описанием методов получения каждого поля.

Для генерирования документа используется ContentBaseDocumentGenerator либо DocumentGenerator. Если вы хотите использовать этот способ генерации, в конфигурации паттерна документа в параметре выбора скрипта необходимо указать:

document:pattern.N.script=ru.bgcrm.plugin.document.docgen.ContentBaseDocumentGenerator
Подробнее о том, как создать свой билдер и дополнять методы для получения данных описано в документации к Билдерам полей.
Документация по DocumentGenerator.

Описание работы

Генератор использует подход, при котором описываются данные для записи в документ, т.е. единицей является информация, заполняемая в, заранее подготовленные, интерактивные поля документа.

Шаги работы:

  • Из шаблона, указанного в конфиге, берется список интерактивных полей для заполнения. Получение происходит через метод интерфейса ru.bgcrm.plugin.document.docgen.DocBuilder#getFieldNames

  • По имени поля сначала ищется соответствующая контекстная переменная, причём происходит комбинированный поиск. Так, для сущности процесса ("process"), происходит поиск не только по собственным переменным, но и по переменным первой найденной связанной сущности единого договора ("bgbilling-commonContract"), а также первой найденной сущности контрагента ("customer"). Похожая логика работает в отношении единого договора - к его контекстным переменным добавляются переменные контрагента.

  • Если для данного поля находится подходящая контекстная переменная, её значение записывается в Map - отношение имени поля, к его значению.

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

  • Данный метод вызывается, а полученное значение преобразовывается в String

  • Значение записываются в Map - отношение имени поля, к его значению.

  • По полученному отношению заполняется шаблон документа. Для заполнения испльзуется интерфейс DocBuilder#writeValues, который имеет имплементации для DOCX и PDF.

  • Сгенерированный документ записывается в объект DocumentGenerateEvent в виде байтов.

Билдеры полей

Билдер полей представляет из себя класс содержащий методы получения данных конкретного поля. Как правило класс насле́дуется от класса FieldDataBuilder в котором есть общие методы по работе с получением данных для докуметов. Новый класс билдера следует помещать в пакет ru.bgcrm.dyn.ufanet.document.builder.

Создание билдера:

  • Создать класс в пакете ru.bgcrm.dyn.ufanet.document.builder

  • Наследовать от FieldDataBuilder

  • Добавить новый класс в конфигурацию "Плагин Document" в параметр handler.{@inc:id}={class path}

public class TmcFieldsBuilder extends FieldDataBuilder
{}

Объект билдер создается тогда, когда требуется получить значение поле у одного из его методов, описанных с помощью аннотации @FieldName("field_name"). За создание объекта билдера отвечает класс DocMapper. Какой конкретно объект билдера нужно создать определяется по имени очередного поля - по нему получается Class объекта из DocHandlersRegister.getBuilderMethod("field_name").

После создания, объект кешируется в DocMapper до конца генерации всего документа. При генерации следующего документа, билдер будет создан заново.

При создании, билдеру поступает контекст. Контекст состоит из DocumentGenerateEvent и Connection.

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

Вызовы методов билдера

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

Создание метода обработчика

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

@FieldName("serv_city")
public String getCity(){
    return getAddress()
            .map(this::extractCity)
            .orElse(null);
}

Метод для получения значения по имени поля строится по следующим правилам:

  • метод должен быть помечен аннотацией FieldName;

  • метод не имеет входных параметров.

Возвращаемые значения

Метод может возвращать любые значения. Не обязательно (даже не рекомендуется) приводить их к String самостоятельно. Например для методов возвращающих стоимость (чье лучшее представление BigDecimal) или состояния чекбокса (представленное boolean). Полученные значения приводятся к String с помощью FieldDataMapper.getValue. С актуальным списком классов для преобразования лучше ознакамливаться в данном методе.

Методы билдеров должны заботиться лишь о данных, а не об их представлении.

Возвращаемые коллекции

Метод может возвращать любого наследника Collection. Это может быть полезно при:

  • Заполнении таблиц. Имена полей в таблицах (списках) формируются с индексами через нижнее подчеркивание (field_1, field_2). В таком случае в качестве названия поля следует использовать имя поля без индекса (field) и возвращать коллекцию.

Пример:

@FieldName("monthly_price")
public List<RentCost> monthlyPrices() throws BGException {
    List<StorageLeftoverMoving> tmcList = getSpecTmc();
    return defineRentCosts(tmcList);
}

В данном примере возвращается список арендных плат для полей "monthly_price_n", где n индекс поля.

  • когда поле может состоять из нескольких составляющих и они все объединяются в одно поле.

Пример:

@FieldName(value = "fio_si", fillingMode = SINGLE_FIELD)
public Set<String> companyRepresentatives() throws BGException {
    Process parentProcess = getParentProcess();
    return getProcessExecutors(parentProcess).stream()
            .map(this::getUserTitle)
            .collect(Collectors.toSet());
}

Код выше возвращает список представителей компании или СИ, которых может быть несколько, но они все должны заполняться в одно поле. Для подобного поведения используется дополнительный параметр аннотации @FieldName - fillingMode. По умолчанию это значение равно TABLE - что предполагает, что данные будут заполняться в листовые поля с индексами как в примере выше. Так же в аннотации есть параметр delimiter, который определяет разделитель для списка полученных данных. По умолчанию это ", ".

Передача дополнительных данных в билдер

На данный момент единственный способ передать данные внутрь билдера - через DynActionForm. Необходимо иметь в виду что такой способ является достаточно накладным, так как необходимо преобразовывать данные в текстовый формат. Однако если запрос на генерацию приходит из вне, например через API работы с ЭДО ('EDOProcessAction, EDOAgreementAction'), и эти данные уже в форме, то можно их сразу получить в виде класса через методы 'parseBody' или 'parseBodyFromParam'.

Важное замечание по работе с ЭДО

При текущей модели электронных документов, они оборачиваются в процесс "Документ", обработчик которого вызывает генерацию документа. Из этого следует, что при создании метода билдера поля необходимо учитывать, что при получении данных из процесса они находятся не в текущем, а в родительском процессе. (Получить его можно через метод FieldDataBuilder#getParentProcess())