Шаблоны документов в Word
Смета, счёт, акт, договор и доп. соглашение собираются из шаблона Word (DOCX). Выпущенный документ хранится в двух видах: DOCX — поправить руками, PDF — отправить клиенту. PDF из Word делает конвертер Gotenberg (LibreOffice в отдельном контейнере). Этап 5 ТЗ «смета → документы → расходы → план и факт».
Какой шаблон берётся
Для документа с продавцом (наше юрлицо проекта или сметы) шаблон ищется так:
- шаблон этого вида для этого юрлица;
- иначе общий шаблон вида («Для всех юрлиц»);
- иначе стандартный — встроенный, его собирает код (
internal/modules/documents/app/docx/defaults.go).
Шаблон один на пару «вид + юрлицо»: повторная загрузка — новая версия того же шаблона. Удаление шаблона возвращает документы к следующему по списку; уже выпущенные документы не меняются.
Документ по HTML-шаблону (явный template_id у счёта) собирается по-старому, без DOCX.
Экран «Администрирование → Шаблоны документов»
/admin/document-templates. По каждому виду — строка общего шаблона (или «Стандартный») и строки шаблонов отдельных юрлиц. Действия строки:
- Скачать / Скачать стандартный — файл Word, чтобы поправить;
- Загрузить свой / Загрузить новую версию — вид, юрлицо, название, файл
.docxдо 5 МБ; - Образец на данных проекта — PDF по шаблону на данных выбранного проекта: номер «0», ничего не выпускается и не сохраняется;
- Поля шаблона — справочник полей с примерами, поле копируется кнопкой;
- Удалить — только у загруженного.
Экран и API открыты тем, кто ведёт документы: глобальный администратор, documents.manage, legal.manage (договоры) и финансы (затраты «все»). Во фронте — capability document_templates_manage из /org/me.
Как устроен шаблон
Шаблон — обычный документ Word. Поле пишется в двойных фигурных скобках: {{document.number}}, {{client.name}}. Word иногда режет текст поля на куски (проверка орфографии, смена шрифта посреди поля) — система склеивает такие куски сама.
Список — блок {{#lines}} … {{/lines}}:
- если начало и конец блока стоят в одной строке таблицы (обычно в первой и последней ячейке), строка повторяется для каждой позиции;
- если это отдельные абзацы, повторяется всё между ними, а сами абзацы с метками убираются;
- внутри одного абзаца повторяется кусок текста.
Внутри списка поля — без префикса: {{n}}, {{name}}, {{qty}}, {{unit}}, {{price}}, {{total}}. Элемент простого списка (изменения доп. соглашения) — {{.}}.
Необязательное — тот же блок по полю-значению: {{#client.kpp}}, КПП {{client.kpp}}{{/client.kpp}} исчезнет целиком, если КПП не заполнен. Обратный блок {{^notes}}…{{/notes}} выводится, когда поля нет.
Незнакомое поле при загрузке не ошибка: шаблон сохраняется, а экран предупреждает — «поля … система не знает, они останутся пустыми» (обычно опечатка). Незакрытый блок и не-DOCX — ошибка загрузки.
Поля
| Поле | Что это |
|---|---|
{{document.number}}, {{document.date}} | номер и дата документа (номер — по нашему юрлицу при выпуске) |
{{document.version}} | версия сметы («версия 2») |
{{project.name}}, {{project.dates}} | проект и его даты |
{{seller.*}} | наше юрлицо: name, inn, kpp, ogrn, address, bank, bik, account, corr_account, director, director_title, phone, email |
{{client.*}} | заказчик — те же поля |
{{#lines}}…{{/lines}} | строки: n, name, qty, unit, price, total |
{{totals.total}}, {{totals.total_words}} | сумма документа и прописью (у счёта на аванс — сумма аванса) |
{{totals.vat_text}} | «В том числе НДС 22 %: … руб.» или «Без НДС» |
{{notes}}, {{due_date}} | назначение платежа, срок оплаты счёта |
{{parent.number}}, {{parent.date}} | договор, к которому доп. соглашение |
{{#changes}}{{.}}{{/changes}}, {{new_total}} | изменения доп. соглашения и новая цена |
Полный список по виду с примерами — GET /api/v1/word-templates/fields?type=… и справочник на экране. Суммы приходят уже отформатированными («1 780 000,00»). Места подписи — пустые линии, без картинок подписи и печати (решение заказчика).
Выпуск документа
- Данные документа собираются как раньше (
render.Context: проект, заказчик, наше юрлицо, строки, итоги). WordDataраскладывает их в поля шаблона, шаблон заполняется (docx.Fill).- DOCX уходит в Gotenberg (
POST /forms/libreoffice/convert), обратно — PDF. - Оба файла загружаются в папку проекта (смета — «Коммерческие предложения», счета — «Счета» и т.д.); у документа
file_id— PDF,docx_file_id— DOCX.
Если конвертер недоступен или упал, документ всё равно выпускается: PDF рисуется кодом (fpdf), как до этапа 5, DOCX сохраняется. В журнале — documents: convert … to pdf: … (fallback to built-in pdf). Образец на экране шаблонов без конвертера недоступен — сервер отвечает понятной ошибкой.
Скачать: GET /api/v1/documents/:id/download — PDF, ?format=docx — Word. Во вкладке «Документы» проекта у документа с DOCX есть кнопка «Скачать Word».
Предпросмотр офисных файлов
Тот же Gotenberg готовит предпросмотр загруженных DOC/DOCX/XLS/XLSX/PPT/PPTX/ODT/ODS/ODP/RTF: файл конвертируется в PDF и кладётся в previews/<id>/preview.pdf. Без конвертера — прежний путь через soffice в контейнере, если он есть. Миграция 0168 переводила файлы с неудавшимся предпросмотром обратно в pending — они готовятся при следующем открытии.
Настройка
APP_GOTENBERG_URL— адрес конвертера; вcomposeпо умолчаниюhttp://gotenberg:3000. Пусто — конвертера нет (PDF кодом, образцы недоступны).- Сервис
gotenberg(gotenberg/gotenberg:8) вcompose.yamlиdocker-compose.yml: таймаут 90 с, перезапуск LibreOffice после 50 конвертаций, память до 1 ГБ.
Где в коде
internal/modules/documents/app/docx— заполнение DOCX, стандартные шаблоны, список полей;internal/modules/documents/app/wordtemplates— загрузка, версии, выбор шаблона;internal/modules/documents/app/generated/word.go— поля из данных документа, DOCX + PDF при выпуске, образец;internal/infra/gotenberg— клиент конвертера;internal/transport/http/word_templates.go—/api/v1/word-templates;- миграция
0168_docx_templates.sql—document_templates.format/content/seller_requisite_id,documents.docx_file_id.