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

С чего все начиналось


А началось все с разработки ERP-платформы в одной торговой компании примерно 2 года назад. Был выбран Linux, стек С++/Qt, PostgreSql и фронт под web. На C++/Qt был реализован сервер приложений и там же, через прослойку JS интерпретатора писалась бизнес логика. Почему так — это отдельная история, здесь рассмотрим, как разрабатывалась система печати.

Первые пробы пера


HTML


Изначально, пока все документы печатались из 1С (используем связку 1С + своя «ERP» для управленческого учета в компании) все было хорошо, а в планах был переход на свою систему. Тут то и понадобилось прикручивать печать к системе.

Первая идея была верстать форму html, программно заполнять данные, пользователю html-ку в браузер и пусть оттуда печатает.

Сразу выяснились некоторые нюансы:

  1. Под все документы придется с 0 верстать шаблон
  2. Конечный пользователь не в состоянии подредактировать html
  3. Менеджеры вели некоторую аналитику в excel
  4. Проблемы с переносом строк при печати многостраничных файлов

В итоге был сделан только один шаблон для печати заявки в логистику (одностраничная форма) которым до сих пор успешно пользуются

XLSX


Вторая идея была работать с XLSX документами. Гугл быстро подсказал про библиотеку QtXlsxWriter. Были еще варианты, но в итоге остановились на QtXlsxWriter.

Что умела библиотека:

  • Открывать/Создавать xlsx и считывать значение ячеек
  • Изменять значение ячеек, сохранять файл
  • Работа с форматом ячейки (в том числе обрамление)
  • Высота/ширина строк/столбцов
  • Объединение ячеек
  • Группировка строк/столбцов
  • Вставка изображений

Это позволило сразу скоммуниздить взять шаблоны документов в xlsx формате из других систем (Привет 1С), заполнить нужными данными и статикой отдать пользователю 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 и поиска того не знаю чего, что MS не нравилось, был придуман костыль. Если взять файл, сгенерированный QtXlsxWriter, и обработать его с помощью LibreOffice получается валидный xlsx файл, со всеми данными первоначального, но на него не ругается MS Office:

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, все работало почти отлично.

  1. Отступы и настройки страницы обрабатывались корректно.
  2. Если надо было подгонять по масштабу, то этот параметр игнорировался и конвертировало с масштабом 100%.
  3. Если стояли настройки подгонять по размеру/ числу страниц, все работало

По поводу пункта 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)


  1. alexander_8901
    03.04.2017 20:59
    +3

    Хотелось бы узнать, за что минусанули статью?


  1. muxa_ru
    03.04.2017 21:00

    Вот это вот всё было сделано для того что бы печатать XLSX-файл?

    > Буду рад узнать другие подходы к реализации этой задачи.

    Поставить винду, расшарить папку и поставить что ни будь типа FolderMill для мониторинга этойт папки и разруливания кого-куда печатать.


    1. alexander_8901
      03.04.2017 21:13

      1. Формирование документа из шаблона в xlsx формате
      2. Формирование xlsx с 0 (формируем не только документы, еще отчеты, прайслисты и т.д.)
      3. Постраничная разбивка с правильным переносом строк при печати(FolderMill так умеет?)
      4. Болльшое желание обойтись без винды


      1. muxa_ru
        03.04.2017 21:24
        -1

        Ой

        > Формирование документа из шаблона в xlsx формате

        Я в восторге от Вашего подвига.
        Это интересная задача позволяющая получить удовольствие.

        Но лучше сделать интерфейс с формочками.
        Там будет проверка вводимых данных на валидность.
        Выбор контрагентов из списка.
        И много чего другого полезного.

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


        1. alexander_8901
          03.04.2017 21:56
          +1

          Эти данные уже в базе(часть из них, как Вы заметили, заносятся из интерфейса с кнопочками и формочками). А на основе этих данных автоматически формируются документы (маршрутные листы, реализации и т.д.) для печати и отчеты (менеджерам для аналитики).

          Здесь описана одна из частей системы, отвечающая за выгрузку и печать документов/отчетов


  1. alexander_8901
    03.04.2017 21:43

    Эти данные уже в базе(часть из них, как Вы заметили, заносятся из интерфейса с кнопочками и формочками). А на основе этих данных автоматически формируются документы (маршрутные листы, реализации и т.д.) для печати и отчеты (менеджерам для аналитики).


  1. zedalert
    04.04.2017 09:39

    А не легче было внутри ERP от 1С это допилить? Под линукс он тоже ведь есть, и веб.


    1. alexander_8901
      04.04.2017 11:44

      Дело в том, что много бизнес процессов проходит по нашей ERP, пришлось бы перегонять эти данные в 1С (что в принципе возможно) и оттуда печатать. Так же, по нашей идеологии, мы переносим всю работу компании (продажи, закупка, склад, логистика), кроме бухгалтерии на свою ERP систему.


  1. aspire
    04.04.2017 11:46

    Спасибо за статью, получилась интересная реализация. Но все же чет у вас какая то предвзятость к 1с. Для массовой печати документов мы создали одни справочник(в 1с) в который пользователь вносил задания, в большинстве случаем ему даже на кнопку ненужно нажимать(например при создании расходной создавалось задание на печать). А для печати завели служебного пользователя(к которому подключили принтера предприятия), который работал автономно и печатал все документы. Получилось все унифицировано, а при такой задаче одна лицензия на windows и одна на excel это не деньги. И для справки 1с может работать с шаблонами word и excel через com, думаю аналогичные реализации можно найти под openoffice.


    1. alexander_8901
      04.04.2017 11:49

      Как уже писал выше, по нашей идеологии мы уходим от 1С в сторону своей разработки, оставляя 1С только для бухгалтерского учета, так как 1С с этим справляется очень хорошо и переписывать это все на свой велосипед, на данный момент, не рационально. К 1С предвзятости нет. Ничего личного 1С, просто бизнес =)


  1. Magnetic_Air
    05.04.2017 11:27

    Тоже пилю потихоньку ERP систему, правда для Win. Для себя пока решил остановиться на шаблонах документов в EXCEL (XLTX) и HTML со встроенным JS-кодом для редактирования содержимого HTML таблиц (вариантов в интернетах на любой вкус). А для выведения списков и отчетов с динамическим добавлением строк — специальный класс-генератор, слепляющий шапку XLTX/HTML со строками и строгой разметкой по-горизонтали.


  1. Emelian
    05.04.2017 13:51

    А блочную вёрстку FOP не пробовали использовать? См. мою тему на sql.ru:
    Как ускорить создание макетов сложных печатных форм?


    1. alexander_8901
      06.04.2017 20:06

      Нет, не пробовал, если появится время, возможно, посмотрю, что это за зверь.


  1. nApoBo3
    05.04.2017 18:36

    А почему просто не научиться создавать pdf из ваших данных на сервере? Библиотек для этого хватает. Плюс удобный шаблон. Т.е. в библиотеку кладем не заполненный pdf шаблон и набиваем его данными. А сам шаблон можно использовать для печати заполняемой руками формы.


    1. alexander_8901
      06.04.2017 20:02

      Менеджерам для аналитики и других нужд нужны отчеты в экселе. Изначально только это требование (выгрузка в эксель) было, позже добавилась печать. Про pdf думали, но в итоге остановились на xlsx.