С чего все начиналось
А началось все с разработки ERP-платформы в одной торговой компании примерно 2 года назад. Был выбран Linux, стек С++/Qt, PostgreSql и фронт под web. На C++/Qt был реализован сервер приложений и там же, через прослойку JS интерпретатора писалась бизнес логика. Почему так — это отдельная история, здесь рассмотрим, как разрабатывалась система печати.
Первые пробы пера
HTML
Изначально, пока все документы печатались из 1С (используем связку 1С + своя «ERP» для управленческого учета в компании) все было хорошо, а в планах был переход на свою систему. Тут то и понадобилось прикручивать печать к системе.
Первая идея была верстать форму html, программно заполнять данные, пользователю html-ку в браузер и пусть оттуда печатает.
Сразу выяснились некоторые нюансы:
- Под все документы придется с 0 верстать шаблон
- Конечный пользователь не в состоянии подредактировать html
- Менеджеры вели некоторую аналитику в excel
- Проблемы с переносом строк при печати многостраничных файлов
В итоге был сделан только один шаблон для печати заявки в логистику (одностраничная форма) которым до сих пор успешно пользуются
XLSX
Вторая идея была работать с XLSX документами. Гугл быстро подсказал про библиотеку QtXlsxWriter. Были еще варианты, но в итоге остановились на QtXlsxWriter.
Что умела библиотека:
- Открывать/Создавать xlsx и считывать значение ячеек
- Изменять значение ячеек, сохранять файл
- Работа с форматом ячейки (в том числе обрамление)
- Высота/ширина строк/столбцов
- Объединение ячеек
- Группировка строк/столбцов
- Вставка изображений
Это позволило сразу
Сразу же появились подводные камни, QtXlsxWriter «некачественно» обрабатывала загрузку документа из шаблона, терялись форматы ячеек (wrap/aling и т.д.). После часов копания «xlsx формата (если кто не знает, xlsx представляет собой zip архив с набором xml документов) выяснили что, в разных версиях boolean атрибуты в xml файлах сохраняются по-разному, либо
<t a="0" b="1" />
либо
<t a="true" b="false" />
а QtXlsxWriter местами парсил только 1/0, местами только true/false, а местами и то и то. Но ничего, руки есть, пофиксили.
Еще был неприятный момент, после формирования xlsx файла через QtXlsxWriter, если его открыть в MS Office, тот начинал ругаться.
В книге „test1.xlsx“ было обнаружено содержимое, которое не удалось прочитать. Попробовать восстановить содержимое книги? Если вы доверяете источнику ...
При этом, после открытия, визуально, все данные были на месте. При этом многие рядовые пользователи (наши клиенты) могли пугаться такого сообщения при открытии наших прайс листов.
После многих часов втыкания в xml файлы MS Office и QtXlsxWriter и поиска того
libreoffice --headless --invisible --quickstart --convert-to xlsx test.xlsx --outdir valid_xlsx
И жить стало хорошо, под нужные отчеты писался небольшой код по формированию xlsx документа, выгружался пользователям, они с ним работали, если надо печатали и MS Office их больше не пугал. Даже смогли реализовать выгрузку OLAP отчетов в xlsx с группировками
Автоматизация
Компания росла, клиентов больше, документов еще больше ( заявки, реализации, накладные и т.д. ), печать стала отнимать очень много рабочего времени. При этом часть документов печаталась из 1С часть из нашей системы. Решили это дело как-то автоматизировать. До этого (лет 5-7 назад) был опыт печати через Windows OLE контейнеры (создавался контейнер с Excel, открывался файл, задавались настройки печати и отправлялся на печать), но с этим не очень хотелось связываться, да и платформа крутится на Linux и тащить сюда виндовый модуль не хотелось (хотя как крайний вариант рассматривали принт-сервер на винде).
Все в PDF
В Linux есть CUPS и с этим вроде как все хорошо, командой lpr можно легко отправить на печать pdf файл. Вот только pdf генерировать мы не умеем. Решение было найдено быстро.
libreoffice --convert-to pdf 1.xlsx --headless
Но не все так просто оказалось. Файлы конвертировались со 100% масштабом и ни как не подгонялись к размеру страниц (А4/А3, книжная/альбомная, отступы), точнее все подгонялось по стандартным параметрам (А4, книжная). Выяснилось, что если задать эти настрой через LibreOffice(руками открыть LibreOffice Calc), сохранить в xlsx и конвертировать через libreoffice --convert-to pdf, все работало почти отлично.
- Отступы и настройки страницы обрабатывались корректно.
- Если надо было подгонять по масштабу, то этот параметр игнорировался и конвертировало с масштабом 100%.
- Если стояли настройки подгонять по размеру/ числу страниц, все работало
По поводу пункта 2 отписался в поддержку LibreOffice, жду от них ответа.
Благо пункт 3 работает правильно, решили отталкиваться от него. Теперь надо научить QtXlsxWriter работать с настройками страницы. Расковыряв xml файлы в xlsx документах нашли места, отвечающие за это дело
xl/worksheets/sheet1.xml
<worksheet>
<sheetPr filterMode="false">
<pageSetUpPr fitToPage="false"/>
</sheetPr>
...
...
<pageMargins
left="0.7875"
right="0.7875"
top="1.05277777777778"
bottom="1.05277777777778"
header="0.7875"
footer="0.7875"/>
<pageSetup
paperSize="9"
scale="50"
firstPageNumber="0"
fitToWidth="1"
fitToHeight="1"
pageOrder="downThenOver"
orientation="portrait"
usePrinterDefaults="false"
blackAndWhite="false"
draft="false"
cellComments="none"
useFirstPageNumber="false"
horizontalDpi="300"
verticalDpi="300"
copies="1"/>
...
</worksheet>
Что здесь есть интересного:
pageMargins — думаю с этим все понятно
fitToPage — подгонять под размеры/кол-во страниц или использовать масштаб
fitToWidth — кол-во страниц по ширине
fitToHeight — кол-во страниц по высоте
scale — масштаб в %
paperSize — размер листа (9=А4)
orientation — книжная/альбомная
Добавили работу с этими параметрами в QtXlsxWriter. Осталось только сформировать xlsx документ с отступами в нужных местах, чтобы не печатались куски незавершенного контента на разных листах. С этим не совсем все просто оказалось.
Печать
Рассмотрим ситуацию когда печатаем маршрутный лист на листе А4 книжной ориентации, без отступов.
При этом необходимо чтоб по ширине документ вмещался в 1 страницу. Ставим настройки:
fitToPage=false
fitToWidth=1
fitToHeight=100
pageMargins — все по 0
При таких условиях fitToHeight должно быть заведомо больше числа предполагаемых страниц при печати.
Маршрутный лист представляет собой заголовок с указанием маршрута и список клиентов с доп. информацией, по которым будет производиться доставка.
Если оставить все как есть, велика вероятность что часть блока с информацией о клиенте, попадающие на конец листа будут разбиваться, часть будет в конце первого и часть в начале второго, а это неприемлемо для нас.
В итоги родился следующий подход (возможно костыль).
Нам изначально известен размер листа А4:
Ширина 21 см
Высота 29.7 см
И мы знаем, что наш контент будет подогнан под ширину листа, т.о. можно посчитать относительную степень сжатия контента:
scale = ширина листа / ширину контента
Тут нас ждал сюрприз, чтоб посчитать ширину контента, необходимо сложить ширину всех столбцов, это сделать не сложно
double QXlsx::Document::columnWidth(int column);
Вот только было совершенно непонятно, в каких единицах измерения получается результат. Возможно, правильное решение можно найти тут, но не смогли, в итоги эмпирическим путем было найдено магическое число 5.10238
1 см = 5.10238 е.и.ш.к. (единица измерения ширины колонки)
scale = А4_ширина * 5.10238 / sum(columnWidth)
далее посчитаем размер контента, который мы способны вмести по ширине листа
height=А4_высота * 28.3464567 / scale
Появилось еще одно магическое число, как Вы уже догадались, это для перевода высоты строки в из см. в е.и.в.с (единица измерения высоты строки, на просторах интернета нашел такую информацию „r.Height = ht * 28.3464567 // Convert CM to postscript points“ )
Высоту строки можно найти через:
double QXlsx::Document::rowHeight(int column);
Используя параметр height, мы забиваем контент в xlsx файл пока высота контента <=height. Если при добавлении нового блока Б, мы выходим за границы height, то перед Б вставляем пустую строчку необходимой высоты, чтобы блок Б печатался с новой строки. Высоту пустой строчки можно посчитать зная высоту контента( sum (rowHeight) ) вставленного до блока Б.
Не рассматриваю тут расчет разбивки по страницам с использованием отступов (pageMargins), скажу лишь что в xml данных хранятся значения этих переменных в дюймах (1 дюйм = 2.54 см ).
Таким образом, получается xlsx файл с готовыми настройками и разбивкой по строка для печати. Далее с помощью libreoffice --convert-to pdf конвертируем в pdf и наш документ готов к печати.
Осталось напечатать:
lpr -pFS-4300DN test.pdf
Сейчас делаем автоматизацию печати на МФУ с финишером (степлирование). Уже немного поиграли с тестовым аппаратом и под Linux, оказалось все просто для степлирования.
Печать со степлированием c одной скрепкой в левом верхнем углу:
lpr -P printer_name -o StapleLocation="UpperLeft" order.pdf
Конец
На этом все. Буду рад узнать другие подходы к реализации этой задачи.
Спасибо за внимание )
Комментарии (15)
muxa_ru
03.04.2017 21:00Вот это вот всё было сделано для того что бы печатать XLSX-файл?
> Буду рад узнать другие подходы к реализации этой задачи.
Поставить винду, расшарить папку и поставить что ни будь типа FolderMill для мониторинга этойт папки и разруливания кого-куда печатать.alexander_8901
03.04.2017 21:13- Формирование документа из шаблона в xlsx формате
- Формирование xlsx с 0 (формируем не только документы, еще отчеты, прайслисты и т.д.)
- Постраничная разбивка с правильным переносом строк при печати(FolderMill так умеет?)
- Болльшое желание обойтись без винды
muxa_ru
03.04.2017 21:24-1Ой
> Формирование документа из шаблона в xlsx формате
Я в восторге от Вашего подвига.
Это интересная задача позволяющая получить удовольствие.
Но лучше сделать интерфейс с формочками.
Там будет проверка вводимых данных на валидность.
Выбор контрагентов из списка.
И много чего другого полезного.
Если вы потом с этими данными работаете, то лучше сразу их загонять в базу.alexander_8901
03.04.2017 21:56+1Эти данные уже в базе(часть из них, как Вы заметили, заносятся из интерфейса с кнопочками и формочками). А на основе этих данных автоматически формируются документы (маршрутные листы, реализации и т.д.) для печати и отчеты (менеджерам для аналитики).
Здесь описана одна из частей системы, отвечающая за выгрузку и печать документов/отчетов
alexander_8901
03.04.2017 21:43Эти данные уже в базе(часть из них, как Вы заметили, заносятся из интерфейса с кнопочками и формочками). А на основе этих данных автоматически формируются документы (маршрутные листы, реализации и т.д.) для печати и отчеты (менеджерам для аналитики).
zedalert
04.04.2017 09:39А не легче было внутри ERP от 1С это допилить? Под линукс он тоже ведь есть, и веб.
alexander_8901
04.04.2017 11:44Дело в том, что много бизнес процессов проходит по нашей ERP, пришлось бы перегонять эти данные в 1С (что в принципе возможно) и оттуда печатать. Так же, по нашей идеологии, мы переносим всю работу компании (продажи, закупка, склад, логистика), кроме бухгалтерии на свою ERP систему.
aspire
04.04.2017 11:46Спасибо за статью, получилась интересная реализация. Но все же чет у вас какая то предвзятость к 1с. Для массовой печати документов мы создали одни справочник(в 1с) в который пользователь вносил задания, в большинстве случаем ему даже на кнопку ненужно нажимать(например при создании расходной создавалось задание на печать). А для печати завели служебного пользователя(к которому подключили принтера предприятия), который работал автономно и печатал все документы. Получилось все унифицировано, а при такой задаче одна лицензия на windows и одна на excel это не деньги. И для справки 1с может работать с шаблонами word и excel через com, думаю аналогичные реализации можно найти под openoffice.
alexander_8901
04.04.2017 11:49Как уже писал выше, по нашей идеологии мы уходим от 1С в сторону своей разработки, оставляя 1С только для бухгалтерского учета, так как 1С с этим справляется очень хорошо и переписывать это все на свой велосипед, на данный момент, не рационально. К 1С предвзятости нет. Ничего личного 1С, просто бизнес =)
Magnetic_Air
05.04.2017 11:27Тоже пилю потихоньку ERP систему, правда для Win. Для себя пока решил остановиться на шаблонах документов в EXCEL (XLTX) и HTML со встроенным JS-кодом для редактирования содержимого HTML таблиц (вариантов в интернетах на любой вкус). А для выведения списков и отчетов с динамическим добавлением строк — специальный класс-генератор, слепляющий шапку XLTX/HTML со строками и строгой разметкой по-горизонтали.
Emelian
05.04.2017 13:51А блочную вёрстку FOP не пробовали использовать? См. мою тему на sql.ru:
Как ускорить создание макетов сложных печатных форм?alexander_8901
06.04.2017 20:06Нет, не пробовал, если появится время, возможно, посмотрю, что это за зверь.
nApoBo3
05.04.2017 18:36А почему просто не научиться создавать pdf из ваших данных на сервере? Библиотек для этого хватает. Плюс удобный шаблон. Т.е. в библиотеку кладем не заполненный pdf шаблон и набиваем его данными. А сам шаблон можно использовать для печати заполняемой руками формы.
alexander_8901
06.04.2017 20:02Менеджерам для аналитики и других нужд нужны отчеты в экселе. Изначально только это требование (выгрузка в эксель) было, позже добавилась печать. Про pdf думали, но в итоге остановились на xlsx.
alexander_8901
Хотелось бы узнать, за что минусанули статью?