- Платформа PARTS SOFT /
- Возможности /
- Шаблоны документов. Миграция шаблона с jxls на liquid_xlsx
Шаблоны документов. Миграция шаблона с jxls на liquid_xlsx
Отличия форм на старом и новом движках
Мы подготовили сравнительную таблицу различий между печатными формами на старом (.xls) и новом (Liquid .xlsx) движках.
| Параметр | Старый движок (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. Отредактируйте шаблон с учетом требований, перечисленных в разделе:
- Замените разметку шаблона;
- Разведите структурные теги по отдельным строкам.
В шаблоне формата .jxls <jx:forEach> мог стоять в одной строке с данными, в шаблонах Liquid_xlsx так делать нельзя.
Сохраните обновленный шаблон на свой компьютер.
ШАГ 3. В панели администрирования перейдите в раздел Служебные → Шаблоны документов и добавьте обновленный шаблон.
В поле Движок шаблона выберите вариант Liquid (.xlsx).
Проверка перед загрузкой: в файле не должно остаться ни одного ${...} и ни одного <jx:.

Загрузка обновленного шаблона
ШАГ 4. Выполните проверку. Распечатайте документ с помощью обновлённого шаблона и сравните его со старым шаблоном:
- Проверка по типу документа
- Типовой документ — должен содержать 3–10 позиций;
-
Граничный документ — документ может быть без позиций или с одной позицией. Проверьте цикл
for, веткуelseи итоговые формулы; - Объёмный документ — должен содержать столько позиций, чтобы документ занял более двух страниц. Проверьте разрывы страниц, повтор шапки и область печати;
- Накладные — проверьте варианты «с НДС» и «без НДС», а также «клиент-юрлицо» и «клиент-физлицо».
- Проверьте критичные отличия
Критичными считаются отличия в:
- значениях ячеек
- количестве строк и страниц
- объединениях ячеек
- Убедитесь, что:
- значения во всех ячейках совпадают со старой формой;
- число строк после разворачивания цикла совпадает;
- число страниц совпадает, шапка повторяется в нужном месте;
- числовые ячейки остались числовыми (проверяется по выравниванию и формату:
1 234,50, а не1234.5в текстовом виде); - итоговые формулы посчитаны по всему диапазону позиций;
- объединения, рамки и заливки на месте;
- печать в PDF даёт тот же результат, что и
.xlsx.
Если ячейка, в которой ожидались данные, пуста, скорее всего, допущена опечатка в пути переменной. В рабочем режиме неизвестное поле печатается как пустая строка, а не как ошибка.
В этом случае проверьте переменные и убедитесь, что путь верен.
- Допустимые отличия
При сравнении новой формы со старой возможны расхождения, которые не являются ошибками переноса:
- Точность сумм: новый движок выводит значения точнее;
- Пустые строки: новый движок не добавляет пустые строки после цикла;
-
Смещение подписей: на широких формах (УПД, Торг-12) возможно небольшое смещение из-за перехода
.xls→.xlsx; -
Длинные адреса: при фиксированной высоте строки в
.xlsxотображается весь адрес, а не только последний фрагмент.
Необходимые требования
В помощь при самостоятельной вёрстке файлов ниже описаны требования к печатным формам на новом движке Liquid для формата .xlsx, ключевые различия между старым и новым движками, а также приведены рекомендации по работе с шаблонами.
Ограничения нового движка
-
Формат шаблона — только
.xlsx; -
Структурные конструкции —
forиifразмещайте только на отдельных строках; - Циклы — только по строкам, циклов по колонкам нет;
-
Модификаторы циклов —
reversed,limit:,offset:,break,continueи перебор хеша не поддерживаются; - Объединённые ячейки — объединение не должно пересекать границу блока;
- Формулы — смещаются только по строкам, сдвиг по колонкам не поддерживается;
-
При вставке строк — условное форматирование, именованные диапазоны и
Print_Areaне смещаются; - Сводные таблицы и таблицы Excel (ListObject) — не перестраиваются;
- Форматированный текст в ячейке — может потеряться в ячейках с разметкой;
-
Внутриячеечный
% case %с% else %— не поддерживается; -
% sheet %— в печатных формах отключён.% image_tag %работает только с двоичными данными; -
Коллекция для
% for %— должна быть списком. Все коллекции из этого руководства уже подготовлены правильно.
Правила синтаксиса Liquid в ячейках
- Переменные
Любая текстовая ячейка может содержать несколько Liquid-выражений и обычный текст. Поддерживается точечная нотация любой глубины и обращение к элементам массива.
- Тип значения ячейки
Если ячейка содержит только одну переменную без фильтра и дополнительного текста, значение сохраняет исходный тип: число остаётся числом, булево — булевым.
Если добавить фильтр, второй элемент или текст, результат становится текстом. Поэтому в ячейках, на которые ссылаются формулы или где задан числовой формат Excel, используйте чистое выражение без фильтров.
- Ограничения
Ограничение для % case %:
Внутриячеечный % case % с веткой % else % не работает: структурный парсер принимает % else % за тег уровня строки. Используйте % case %/% when % без % else % либо замените конструкцию на % if %/% elsif %/% else %.
Пустой массив — истина. Условие % unless items % сработает только при items = nil, но не при пустом списке. Для проверки пустого списка используйте % if items == empty %.
assignиcapture
Все ячейки одного листа используют общий контекст и обрабатываются сверху вниз, слева направо. Переменные, созданные через assign, доступны в последующих ячейках и сохраняются после endfor. capture сохраняет блок текста в переменную.
Соответствие конструкций
| № | 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 |
Переименование корней
| № | 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 %.
Поля, которые нельзя переводить механически
| № | 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.
Следующие фильтры доступны в любой ячейке любой печатной формы.
| № | Фильтр | Пример | Результат | Заменяет в старом шаблоне |
|---|---|---|---|---|
| 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" %
| № | Параметр | Описание |
|---|---|---|
| 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: неизвестная переменная вызывает ошибку с указанием листа, строки и ячейки. В рабочем режиме режим отключён.
| № | Ошибка | Причина |
|---|---|---|
| 1 | TemplateSyntaxError |
Структурный тег не на отдельной строке, непарные for / if. |
| 2 | RenderError |
Ошибка вычисления выражения или неверные параметры % image_tag % / % sheet %. |
| 3 | MissingVariableError |
Неизвестная переменная в строгом режиме. |
| 4 | UnsupportedTemplateError |
Объединение через границу блока или неподдерживаемый формат изображения. |
| 5 | InvalidXlsxError |
Повреждённый файл шаблона. |
Сообщение об ошибке содержит лист, номер строки, адрес ячейки и текст тега.