Шаблоны документов. Миграция шаблона с jxls на liquid_xlsx

Содержание
Подробнее о шаблонах документов — в статье Шаблоны документов (Накладные, УПД, счета и т.д.).

Отличия форм на старом и новом движках

Мы подготовили сравнительную таблицу различий между печатными формами на старом (.xls) и новом (Liquid .xlsx) движках.

Таблица 1. Сравнительная таблица форм на старом и новом движках
Параметр Старый движок (JXLS) Новый движок (Liquid XLSX)
Формат шаблона .xls (Excel 97-2003) .xlsx
Формат результата .xls .xlsx
Разметка ${invoice.customer.compileName}, <jx:forEach> customer.compile_name, % for %.
Данные промежуточный JSON с фиксированным набором полей Живые объекты системы, как в шаблонах писем.
Форматирование чисел и дат отдельные поля-двойники (sumPrint, dateWords) Фильтры (invoice.sum | money2).
Рендер внешний Java-процесс Внутри приложения.

Формы поставки на новом движке имеют системный код с суффиксом -lx (bill-lx, order-lx, packing-list-lx и т. д.) и отображаются рядом со старыми. 

Настройка миграции

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

ШАГ 1. Откройте устаревший файл шаблона (в формате .xls) в редакторе (например, MS Excel) и сохраните его в формате .xlsx.

ШАГ 2. Отредактируйте шаблон с учетом требований, перечисленных в разделе:

  1. Замените разметку шаблона;
  2. Разведите структурные теги по отдельным строкам.

В шаблоне формата .jxls <jx:forEach> мог стоять в одной строке с данными, в шаблонах Liquid_xlsx так делать нельзя.

Сохраните обновленный шаблон на свой компьютер.

ШАГ 3. В панели администрирования перейдите в раздел Служебные → Шаблоны документов и добавьте обновленный шаблон.

В поле Движок шаблона выберите вариант Liquid (.xlsx). 

Проверка перед загрузкой: в файле не должно остаться ни одного ${...} и ни одного <jx:.

Миграция шаблона с jxls на liquid_xlsx иллюстрация №1

Загрузка обновленного шаблона

ШАГ 4. Выполните проверку. Распечатайте документ с помощью обновлённого шаблона и сравните его со старым шаблоном:

  1. Проверка по типу документа
  • Типовой документ — должен содержать 3–10 позиций;
  • Граничный документ — документ может быть без позиций или с одной позицией. Проверьте цикл for, ветку else и итоговые формулы;
  • Объёмный документ — должен содержать столько позиций, чтобы документ занял более двух страниц. Проверьте разрывы страниц, повтор шапки и область печати;
  • Накладные — проверьте варианты «с НДС» и «без НДС», а также «клиент-юрлицо» и «клиент-физлицо».
  1. Проверьте критичные отличия

Критичными считаются отличия в:

  • значениях ячеек
  • количестве строк и страниц
  • объединениях ячеек
  1. Убедитесь, что:
  • значения во всех ячейках совпадают со старой формой;
  • число строк после разворачивания цикла совпадает;
  • число страниц совпадает, шапка повторяется в нужном месте;
  • числовые ячейки остались числовыми (проверяется по выравниванию и формату: 1 234,50, а не 1234.5 в текстовом виде);
  • итоговые формулы посчитаны по всему диапазону позиций;
  • объединения, рамки и заливки на месте;
  • печать в PDF даёт тот же результат, что и .xlsx.

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

В этом случае проверьте переменные и убедитесь, что путь верен.

  1. Допустимые отличия

При сравнении новой формы со старой возможны расхождения, которые не являются ошибками переноса:

  • Точность сумм: новый движок выводит значения точнее;
  • Пустые строки: новый движок не добавляет пустые строки после цикла;
  • Смещение подписей: на широких формах (УПД, Торг-12) возможно небольшое смещение из-за перехода .xls → .xlsx;
  • Длинные адреса: при фиксированной высоте строки в .xlsx отображается весь адрес, а не только последний фрагмент.

Необходимые требования

В помощь при самостоятельной вёрстке файлов ниже описаны требования к печатным формам на новом движке Liquid для формата .xlsx, ключевые различия между старым и новым движками, а также приведены рекомендации по работе с шаблонами.

Ограничения нового движка

  1. Формат шаблона — только .xlsx;
  2. Структурные конструкции — for и if размещайте только на отдельных строках;
  3. Циклы — только по строкам, циклов по колонкам нет;
  4. Модификаторы циклов — reversed, limit:, offset:, break, continue и перебор хеша не поддерживаются;
  5. Объединённые ячейки — объединение не должно пересекать границу блока;
  6. Формулы — смещаются только по строкам, сдвиг по колонкам не поддерживается;
  7. При вставке строк — условное форматирование, именованные диапазоны и Print_Area не смещаются;
  8. Сводные таблицы и таблицы Excel (ListObject) — не перестраиваются;
  9. Форматированный текст в ячейке — может потеряться в ячейках с разметкой;
  10. Внутриячеечный % case % с % else % — не поддерживается;
  11. % sheet % — в печатных формах отключён. % image_tag % работает только с двоичными данными;
  12. Коллекция для % for % — должна быть списком. Все коллекции из этого руководства уже подготовлены правильно.

Правила синтаксиса Liquid в ячейках

  1. Переменные

Любая текстовая ячейка может содержать несколько Liquid-выражений и обычный текст. Поддерживается точечная нотация любой глубины и обращение к элементам массива.

  1. Тип значения ячейки

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

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

  1. Ограничения

Ограничение для % case %:

Внутриячеечный % case % с веткой % else % не работает: структурный парсер принимает % else % за тег уровня строки. Используйте % case %/% when % без % else % либо замените конструкцию на % if %/% elsif %/% else %.

Пустой массив — истина. Условие % unless items % сработает только при items = nil, но не при пустом списке. Для проверки пустого списка используйте % if items == empty %.

  1. assign и capture

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

Соответствие конструкций

Таблица 2. Необходимые требования. Соответствие конструкций
№ JXLS Liquid Примечание
1 ${invoice.no} invoice.no в Liquid используются двойные фигурные скобки
2 ${invoice.customer.compileName} customer.compile_name camelCase → snake_case; стороны документа подняты на верхний уровень
3 <jx:forEach items="${invoice.invoiceItems}" var="invoice_item" varStatus="status"> for item in items тег занимает отдельную строку листа; в Liquid используются управляющие конструкции
4 </jx:forEach> endfor тоже на отдельной строке
5 ${status.index+1} forloop.index в JXLS нумерация с нуля, в Liquid — с единицы
6 ${status.index} forloop.index0  
7 <jx:if condition="..."> … </jx:if> if ... … endif на отдельных строках
8 ${cond ? 'Оплачено' : ''} if cond — Оплачено — endif тернарного оператора нет
9 ${invoice.sum - invoice.sumNds} invoice.sum_without_nds арифметики в выражениях нет — есть готовое поле
10 ${invoice_item.sumPrint} item.sum | money2 форматирование фильтром
11 ${invoice.dateWords} invoice.created_at | date_words то же
12 ${invoice.createdAtPrint} invoice.created_at | ru_date то же
13 ${invoice.sumRublej} invoice.sum | propisju_rub то же
14 ${order.clientFileCells[2][7]+0} order.client_file_cell.r2_c7 +0 не нужен; запись через точку сохраняет числовой тип, запись со скобками — нет
15 ${place_item.innerOrderId} item.order_order_id  

Переименование корней

Таблица 3. Необходимые требования. Переименование корней
№ JXLS Liquid
1 ${invoice.customer.*} customer.*
2 ${invoice.region.*} region.*
3 ${invoice.consignee.*} consignee.*
4 ${invoice.shipper.*} shipper.*
5 ${invoice.invoiceItems} items
6 ${bill.billItems} items
7 ${order.orderItems} items
8 ${basketBundle.basketItems} items (синоним basket_bundle.baskets)
9 ${packing_list.packingListPlaces} places
10 ${balance_act.balanceActItems} items
11 ${return_invoice.returnInvoiceItems} items

Внутри цикла переменная называется так, как вы её объявили: % for item in items % → item.oem. Имя переменной из старого шаблона (invoice_item, bill_item) можно сохранить: % for invoice_item in items %.

Поля, которые нельзя переводить механически

Таблица 4. Необходимые требования. Поля, которые нельзя переводить механически
№ JXLS Liquid Проблема с механическим переводом
1 ${invoice.sum} invoice.sum это сумма с НДС, а не «сумма» объекта
2 ${invoice.sumNds} invoice.sum_nds сумма НДС
3 ${invoice.ndsPercent} invoice.nds_percent действующая ставка с учётом режима НДС
4 ${invoice_item.sum} item.sum сумма позиции с НДС
5 ${invoice_item.price} item.price цена позиции с НДС
6 ${bill.sum} bill.sum сумма счёта с НДС
7 ${bill_item.cost} item.cost цена позиции счёта с НДС
8 ${order.sum} order.sum сумма заказа по балансу, не итог по позициям
9 ${order_item.status} item.status название статуса строкой, не объект статуса
10 ${essential.shortEssential} customer.essential.short имя поля другое
11 ${essential.cityBank} customer.essential.city имя поля другое
12 ${customer.contractComment} customer.contract_comment берётся из договора-основания
13 ${return_request.returnQnt} return_request.return_qnt  
14 ${route.routeName} route.route_name  
15 ${packing_list_place.itemsCount} place.items_count  
Все перечисленные имена уже настроены на стороне системы — писать их необходимо в точности как в правой колонке, чтобы не допустить ошибок.

Фильтры

Фильтры применяются к значениям через символ |. Стандартные фильтры Liquid позволяют изменять текст и числа, работать со списками и форматировать даты.

Основные фильтры: upcase, downcase, capitalize, strip, round, plus, minus, times, divided_by, size, join, first, last, append, prepend, replace, truncate, date, default.

Арифметические фильтры над дробными числами сохраняют дробный вид: 100.0 | plus: 10 → 110.0. Чтобы получить целое число, используйте round.

Следующие фильтры доступны в любой ячейке любой печатной формы.

Таблица 5. Необходимые требования. Фильтры печатных форм
№ Фильтр Пример Результат Заменяет в старом шаблоне
1 money2 invoice.sum | money2 1234.50 ${invoice.sumPrint}
2 money_delimited invoice.sum | money_delimited 1 234,50 —
3 money invoice.sum | money 1 234,50 (формат приложения) —
4 propisju_rub invoice.sum | propisju_rub одна тысяча... рублей ${invoice.sumRublej}
5 propisju_tenge invoice.sum | propisju_tenge сумма прописью в тенге ${invoice.sumTenge}
6 propisju_griven invoice.sum | propisju_griven сумма прописью в гривнах ${invoice.sumGriven}
7 sum_tenge invoice.sum | sum_tenge тенге и тиыны прописью —
8 propisju_shtuk invoice.position_count | propisju_shtuk три ${...positionCountWord}
9 date_words invoice.created_at | date_words 28 мая 2026 г. ${invoice.dateWords}
10 month_word invoice.created_at | month_word мая ${invoice.monthWord}
11 ru_date invoice.created_at | ru_date 28.05.2026 ${invoice.createdAtPrint}
12 ru_date: '<формат>' invoice.created_at | ru_date: '%d.%m.%Y %H:%M' 28.05.2026 14:30 —
13 day_num invoice.created_at | day_num 28 ${invoice.day}
14 month_num invoice.created_at | month_num 05 ${invoice.monthNum}
15 year_num invoice.created_at | year_num 2026 ${invoice.year}
16 dash_if_blank item.gtd | dash_if_blank значение или - —
17 dash_if_blank: '--' item.gtd | dash_if_blank: '--' значение или -- —

Раньше в JSON могли храниться отдельные поля для значения и его форматированного представления, например, sum и sum_print. Теперь достаточно одного поля и фильтра: форматирование задаётся в шаблоне, а не в данных.

Структурные теги: циклы и условия

Теги % for %,  % endfor %,  % if %, % elsif %, % else % и % endif % уровня строк должны занимать отдельную строку листа: тег находится в первой непустой ячейке, остальные ячейки строки пусты.

Нарушение вызывает ошибку рендера.

Циклы: cтроки между % for % и % endfor % повторяются для каждого элемента. Стили, высоты, формулы и объединения строк тела цикла сохраняются.

Внутри цикла доступны служебные переменные:

  • forloop.index — номер итерации с 1;
  • forloop.index0 — номер итерации с 0;
  • forloop.first — true на первой итерации;
  • forloop.last — true на последней итерации;
  • forloop.length — количество итераций.

Пустой список: ветка else выполняется, если коллекция пуста или отсутствует.

Поддерживаются вложенные циклы.

Условия уровня строк: блок между % if % и % endif % целиком включается или исключается. Условия можно использовать внутри циклов и циклы внутри условий.

Формулы и объединённые ячейки

Формулы в теле цикла смещаются по строкам. Например, =C6*D6 после трёх итераций становится =C6*D6, =C7*D7, =C8*D8.

Итоговые формулы под циклом, ссылающиеся на строку-шаблон, расширяются на весь диапазон. Например, =SUM(E6:E6) после трёх итераций становится =SUM(E6:E8).

Объединённые ячейки в теле цикла клонируются для каждой итерации, а расположенные ниже блока смещаются.
Важно: объединённая ячейка не должна пересекать границу блока for или if — это вызывает ошибку рендера.

Область печати (Print_Area) при вставке строк не расширяется. Для многостраничных форм либо не задавайте область печати, либо проверьте результат на документе с большим количеством позиций.

Кэш вычисленных значений формул в файл не записывается. Excel и LibreOffice пересчитывают формулы при открытии.

Вставка изображений

Тег % image_tag % вставляет изображение (PNG, JPEG, GIF) из двоичных данных.

% image_tag region.sign_data, colspan: 2, rowspan: 3 %
% image_tag logo, width: 120, height: 60 %
% image_tag logo, to: "F10" %
Таблица 6. Необходимые требования. Параметры image_tag
№ Параметр Описание
1 colspan: сколько колонок вправо занимает картинка
2 rowspan: сколько строк вниз
3 to: правый нижний угол, например "F10"
4 width: / height: фиксированный размер в пикселях
  • Если задан только width: или height:, вторая сторона рассчитывается пропорционально.
  • Без параметров картинка занимает одну ячейку; если ячейка-якорь входит в объединённый диапазон, картинка заполняет его.
  • Источник должен содержать двоичные данные изображения. Загрузка по пути или URL в печатных формах не подключена.
Печать и подпись региона через % image_tag % — возможность нового движка, которая пока не используется в системных формах.
% sheet % (генерация листов по данным) в печатных формах не включён и вызывает ошибку рендера.

Поведение при ошибке

В рабочем режиме отсутствующее поле не вызывает ошибку: в ячейке будет отображаться пустое значение.

Для пустых связей используются заглушки. Они применяются к essential, official_address, delivery_address, contact и user, если пользователя нет.

Проверяйте конкретное поле, а не объект целиком.

В Liquid любой объект, кроме nil и false, истинен, поэтому объект-заглушка всегда истинный. Проверяйте конкретное поле: % if customer.essential.inn %.

Для отладки используйте строгий режим DOC_TEMPLATE_STRICT_LIQUID=1: неизвестная переменная вызывает ошибку с указанием листа, строки и ячейки. В рабочем режиме режим отключён.

Таблица 7. Необходимые требования. Возможные ошибки рендера
№ Ошибка Причина
1 TemplateSyntaxError Структурный тег не на отдельной строке, непарные for / if.
2 RenderError Ошибка вычисления выражения или неверные параметры % image_tag % / % sheet %.
3 MissingVariableError Неизвестная переменная в строгом режиме.
4 UnsupportedTemplateError Объединение через границу блока или неподдерживаемый формат изображения.
5 InvalidXlsxError Повреждённый файл шаблона.

Сообщение об ошибке содержит лист, номер строки, адрес ячейки и текст тега.

Начните тест-драйв платформы PARTS SOFT уже сегодня!

скриншот платформы