← Вернуться к сервису
Документация проекта

Сервис подготовки документов рассрочки

Локальное веб-приложение для:

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

Важно: юридические редакции Word-шаблонов хранятся непосредственно в DOCX. После каждого изменения проверяйте обе стадии на тестовых данных.


Как создать документ

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

Шаг 1. Выберите стадию работы

В блоке 00 Стадия работы выберите нужный вариант:

Стадия определяет используемый Word-шаблон. Если изменить стадию, проверьте введённые данные и параметры перед формированием нового документа.

Шаг 2. Добавьте сведения о должнике

  1. В разделе «Сведения о должнике» нажмите «Добавить».
  2. Заполните фамилию, имя и ИИН.
  3. При необходимости заполните отчество, адрес и номер телефона.
  4. Нажмите «Добавить» внутри открытой формы.

После сохранения появится карточка должника. Используйте «Изменить», если нужно исправить данные, или «Удалить», чтобы заполнить карточку заново.

Шаг 3. Заполните сведения о судебном деле

Этот раздел появляется только на судебной стадии. Все три поля необязательны:

Если оставить их пустыми, в русскую часть документа будут подставлены редакционные подсказки «Указать наименование суда», «Указать Фамилию и Инициалы судьи» и «Указать представителя истца». Для казахской части используются отдельные подсказки на казахском языке.

При попытке сформировать судебный документ в PDF, скачать архив с PDF или отправить такой архив сервис предупредит о незаполненном разделе 02. В Word заглушки можно исправить после скачивания, а в готовом PDF — нельзя. Текст предупреждения начинается так: «Внимание! Раздел 02 „Сведения о судебном деле“ заполнен не полностью».

Шаг 4. Добавьте договор банковского займа

  1. Нажмите «Добавить» в разделе «Сведения о договоре банковского займа».
  2. Укажите номер договора, дату договора и сумму долга.
  3. Нажмите «Добавить» внутри формы договора.

Можно добавить несколько договоров. Активный договор выделяется в списке, а раздел «Параметры рассрочки» показывает настройки именно выбранного договора.

Шаг 5. Настройте рассрочку

В верхней части раздела выберите один из трёх самостоятельных способов расчёта.

При необходимости заполните блок «Авансовый платёж»: укажите сумму и дату, которая должна быть раньше даты платежа по графику. Аванс уменьшает долг, а срок рассрочки относится только к последующим регулярным платежам. В графиках, CSV и Word аванс отображается отдельной строкой по своей дате. Сумма аванса должна быть меньше суммы долга. В общем расчёте аванс распределяется между договорами пропорционально их задолженности.

Вариант 1. Равные платежи

Этот вариант подходит, когда платежи должны быть одинаковыми каждый месяц.

Расчёт по сроку рассрочки:

  1. Нажмите «Равные платежи».
  2. Выберите «По сроку рассрочки».
  3. Проверьте сумму долга.
  4. Укажите дату платежа по графику.
  5. Укажите срок рассрочки в месяцах.

Сервис автоматически рассчитает ежемесячный платёж. Последний платёж может отличаться из-за остатка при делении суммы долга.

Расчёт по ежемесячному платежу:

  1. Нажмите «Равные платежи».
  2. Выберите «По ежемесячному платежу».
  3. Проверьте сумму долга.
  4. Укажите дату платежа по графику.
  5. Укажите желаемый ежемесячный платёж.

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

Вариант 2. Особые платежи

Этот вариант нужен, если в отдельные месяцы или периоды должны применяться суммы, отличающиеся от обычного платежа.

  1. Выберите «Особые платежи».
  2. Проверьте сумму долга.
  3. Укажите дату платежа по графику и общий срок рассрочки.
  4. Выберите способ распределения остатка:
    • Ежемесячно — после учёта особых условий остаток долга распределяется поровну между остальными свободными месяцами;
    • Только особые платежи — платежи создаются только по добавленным условиям, а в остальных месяцах указывается 0.
  5. Нажмите «Добавить условие».
  6. Выберите тип условия:
    • Конкретный месяц — особая сумма применяется в одном выбранном месяце;
    • Период — одинаковая особая сумма применяется с начального по конечный месяц включительно.
  7. Выберите месяц или период и укажите сумму погашения.
  8. При необходимости добавьте ещё несколько условий. Ненужное условие можно удалить кнопкой с изображением корзины.

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

Вариант 3. Общий расчёт

Этот вариант становится доступен после добавления двух или более договоров и создаёт единый график по всей задолженности.

  1. Добавьте все договоры и проверьте сумму долга в каждой карточке.
  2. Выберите «Общий расчёт».
  3. Проверьте поле «Общий долг» — оно рассчитывается автоматически как сумма долгов всех договоров.
  4. Укажите общий срок рассрочки.
  5. Укажите дату платежа по графику.
  6. Укажите общий ежемесячный платёж.
  7. Нажмите «Подтвердить».

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

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

Шаг 6. Подтвердите параметры

После заполнения параметров нажмите «Подтвердить». Договор и его настройки должны перейти в подтверждённое состояние. Для нескольких индивидуальных договоров повторите настройку и подтверждение для каждой карточки. При выборе «Общего расчёта» подтверждаются параметры всех включённых договоров.

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

Шаг 7. Проверьте график

Нажмите «Показать график» и проверьте:

Шаг 8. Скачайте результат

В нижней панели выберите формат документа:

Затем используйте нужное действие:

Кнопка «Очистить» удаляет все введённые данные после дополнительного подтверждения. Используйте её только после скачивания нужных файлов.

Отправка графика на согласование

Кнопка «Отправить письмо» формирует ZIP-архив и открывает системное меню отправки с готовым текстом письма: «Направляем график в отношении Ф.И.О. на согласование». Если браузер и почтовая программа поддерживают передачу файлов через системное меню, архив будет добавлен к письму автоматически.

Если такая возможность недоступна, сервис скачает ZIP и откроет новое письмо с заполненными темой и текстом. В этом случае приложите скачанный архив к письму вручную — браузеры не разрешают добавлять вложения через ссылку mailto:.

Какой разделитель CSV выбрать

Переключатель «Разделитель CSV» влияет только на файлы графиков. На Word, PDF и расчёт сумм он не влияет.

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

В каждом CSV после столбца «ИИН» расположен столбец «Договор». В индивидуальном графике в каждой строке указывается номер соответствующего договора банковского займа. В общем графике перечисляются номера всех включённых договоров через /.

Если после открытия CSV все значения оказались в одном столбце, выберите при импорте тот же разделитель, который был установлен в сервисе, либо сформируйте график повторно с другим вариантом. Для обычного открытия в русской версии Excel сначала выбирайте ;.

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

Перед использованием документа обязательно откройте скачанный файл и проверьте Ф.И.О., ИИН, реквизиты договоров, судебные сведения, суммы, даты и все приложения с графиками.

Важные замечания для пользователя


1. Основные возможности

Интерфейс построен как последовательный рабочий сценарий:

В правом верхнем углу расположен переключатель RU / ҚАЗ. Он переводит интерфейс между русским и казахским без перезагрузки и потери введённых данных. Выбранный язык сохраняется в браузере для следующего открытия сервиса.

Рядом расположен переключатель светлой и тёмной темы. При первом открытии используется системная тема устройства, после ручного выбора настройка сохраняется в браузере. Такой же переключатель доступен на странице «Документация проекта»; обе страницы используют общую настройку темы.

  1. выбрать стадию и соответствующий ей шаблон документа;
  2. добавить и подтвердить должника;
  3. добавить один или несколько договоров;
  4. выбрать индивидуальные параметры договора либо общий расчёт для нескольких договоров;
  5. нажать «Подтвердить» для индивидуальных или общих параметров;
  6. проверить индивидуальные или общий график;
  7. сформировать Word или PDF, экспортировать графики в CSV либо скачать общий архив.

Стадии и шаблоны

Перед разделом 01 расположен взаимоисключающий выбор стадии:

Конфигурация стадий находится в объекте documentStages файла src/app.js. В запрос дополнительно передаётся поле stage, а templateId выбирается из конфигурации активной стадии. Сервер также проверяет соответствие стадии и шаблона. Для обеих стадий доступны Word, PDF и архив с графиками.

Блок выбора стадии имеет номер 00. На досудебной стадии рабочие разделы нумеруются 0103. На судебной стадии между должником и договором появляется раздел 02 «Сведения о судебном деле», поэтому последующие разделы получают номера 03 и 04.

Кнопки добавления сведений о должнике, судебном деле и договоре одинаково называются «Добавить». Формы по умолчанию свёрнуты. После сохранения данные превращаются в компактные карточки. У карточек и настроек есть отдельные действия «Изменить» и «Удалить»:

Состояния показаны не только текстом, но и цветом:

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

Сведения о должнике

Правила нормализации:

Сведения о судебном деле

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

Все поля необязательны. Если значение не задано, в русские теги Word-контекста передаются редакционные подсказки Указать наименование суда, Указать Фамилию и Инициалы судьи и Указать представителя истца. Для казахской части предусмотрены отдельные теги с окончанием _kz и казахскими заглушками. После добавления сведения отображаются компактной карточкой, которую можно изменить или удалить.

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

Сведения о договоре банковского займа

Варианты рассрочки

Дата платежа по графику во всех режимах вводится как текст по маске ДД.ММ.ГГГГ. Некорректная календарная дата не используется в расчёте. Число дня проверяется по выбранному месяцу, включая високосные годы. В состоянии договора дата хранится в ISO-формате ГГГГ-ММ-ДД.

  1. Равными платежами по сроку рассрочки

    • указывается сумма долга;
    • указывается срок;
    • рассчитывается ежемесячный платёж.
  2. Равными платежами по ежемесячному платежу

    • указывается сумма долга;
    • указывается размер ежемесячного платежа;
    • срок рассчитывается автоматически.
  3. Сложный расчёт

    • указывается сумма долга;
    • дата платежа по графику;
    • срок рассрочки;
    • способ распределения остатка;
    • особые платежи по конкретному месяцу или периоду месяцев.
  4. Общий расчёт по нескольким договорам

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

Результаты

В нижней панели нет отдельного поля «Ежемесячный платёж». Она содержит только действия «Показать график», «Сформировать документ», «Отправить письмо» и «Скачать архив». Переключатель Word / PDF определяет формат документа при отдельном скачивании и внутри итогового ZIP. Настройка разделителя CSV работает независимо и не меняется при выборе формата документа.

В футере находится ссылка «Документация проекта». Маршрут /about при каждом запросе читает актуальный README.md, преобразует Markdown в оформленный HTML и поэтому не требует отдельной сборки. После изменения README достаточно обновить страницу документации.


2. Текущие ограничения

Перед развитием проекта важно учитывать текущие границы реализации.

Сумма долга договора и сумма рассрочки

Источником истины является contract.debtAmount — сумма долга, указанная при добавлении договора. Она автоматически используется:

Техническое поле installment.debt сохраняется в отправляемой модели для удобства расчётных функций, но перед расчётом всегда синхронизируется с contract.debtAmount. Изменение суммы в любом из экранов параметров обновляет сумму договора и остальные режимы, а подтверждение параметров договора сбрасывается.

Сервер повторяет это правило независимо от браузера: если в запросе contract.debtAmount и installment.debt расходятся, расчёт и документ используют contract.debtAmount. Для совместимости со старыми запросами, в которых debtAmount отсутствует, сервер использует installment.debt.


3. Требования

Для запуска приложения

Проверьте установленную версию:

node -v
npm -v

Если стандартный репозиторий Ubuntu устанавливает Node.js ниже версии 20, установите Node.js 20 для пользователя, от имени которого запускается сервис, через nvm:

sudo apt-get update
sudo apt-get install -y curl ca-certificates

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.6/install.sh | bash
source ~/.bashrc

command -v nvm
nvm install 20
nvm alias default 20
nvm use 20

node -v
npm -v
which node

После обновления Node.js переустановите зафиксированные зависимости проекта и проверьте приложение:

cd ~/apps/calc-rassrochka
npm ci
npm run check
HOST=0.0.0.0 PORT=8080 npm start

Версия из node -v должна начинаться с v20. Если приложение запускается через systemd, учтите, что служба не загружает nvm из ~/.bashrc автоматически: в ExecStart следует использовать абсолютный путь, который показывает команда which node.

Для формирования и скачивания документа любой стадии в PDF LibreOffice необходимо установить на сервере, где запущено приложение. Графическая оболочка сервера не требуется: конвертация выполняется командой soffice в режиме --headless.

# Ubuntu / Debian
sudo apt-get update
sudo apt-get install -y libreoffice-writer

# проверка установки
which soffice
soffice --version

# если soffice не находится через PATH
SOFFICE_PATH=/usr/bin/libreoffice HOST=0.0.0.0 PORT=8080 npm start

После установки нужно перезапустить Node.js-сервис. Word, CSV, ZIP с Word и просмотр графика работают без LibreOffice; зависимость нужна для отдельного PDF и ZIP-архива, если внутри выбран PDF. Шрифты, используемые в DOCX-шаблонах, также должны быть установлены на сервере, иначе LibreOffice может заменить их при конвертации.

Рекомендуется использовать актуальную LTS-версию Node.js.

Для восстановления технического DOCX-шаблона

Дополнительно требуются:

Установка:

python3 -m pip install python-docx

Python не нужен для обычного запуска и генерации документов. Он используется только скриптом восстановления исходного технического шаблона.


4. Быстрый запуск

Установить зависимости:

npm install

Запустить приложение:

npm start

По умолчанию приложение доступно только на текущем компьютере:

http://127.0.0.1:8080

Если порт 8080 занят, можно указать другой:

PORT=8081 npm start

Чтобы открыть доступ по IP сервера или компьютера в локальной сети, запустить:

HOST=0.0.0.0 PORT=8080 npm start

После запуска открыть с другого компьютера:

http://IP_СЕРВЕРА:8080

Например:

http://192.168.1.50:8080

0.0.0.0 означает прослушивание всех сетевых интерфейсов. В адресной строке браузера вместо него всегда указывается настоящий IP-адрес сервера. Порт 8080 должен быть разрешён локальным или облачным сетевым экраном.

В приложении пока нет авторизации. Не публикуйте порт напрямую в интернете: сервис обрабатывает ИИН и другие персональные данные. Для внешнего доступа требуется закрытый контур либо обратный прокси с HTTPS и аутентификацией.

Запустить все автоматические проверки:

npm run check

Создать тестовый Word-документ:

npm run sample

Результат для нескольких договоров будет записан в:

.tmp/document-qa/sample-obligation-multiple.docx

Для проверки документа с одним договором:

node scripts/generate-sample-document.js --single

Результат: .tmp/document-qa/sample-obligation-single.docx.


5. Команды проекта

Команда Назначение
npm start Запускает локальный HTTP-сервер и веб-интерфейс
HOST=0.0.0.0 PORT=8080 npm start Запускает сервер с доступом по IP компьютера
npm run check Запускает все тесты через встроенный node:test
npm run sample Формирует тестовый Word-документ
python3 scripts/create_default_template.py Создаёт отдельный технический DOCX, не затрагивая рабочий шаблон

create_default_template.py по умолчанию пишет только в server/templates/installment-agreement-generated.docx. Этот файл не регистрируется в manifest и исключён из Git. Существующий файл скрипт не перезаписывает. Намеренная перезапись возможна только с флагом --force. Рабочие юридические шаблоны v1, v2 этим скриптом не затрагиваются.


6. Архитектура

Проект разделён на пять логических слоёв:

  1. интерфейс и состояние страницы;
  2. доменная логика форматирования и расчётов;
  3. локальный HTTP-сервер;
  4. генерация документов из DOCX-шаблона;
  5. серверная генерация CSV и ZIP-архивов.
flowchart LR
    U["Пользователь"] --> UI["index.html + src/app.js"]
    UI --> F["src/domain/format.js"]
    UI --> S["src/domain/schedule.js"]
    UI -->|POST /api/documents| H["server/index.js"]
    UI -->|POST /api/exports/csv| H
    UI -->|POST /api/exports/archive| H
    H --> G["server/generate-document.js"]
    H --> E["server/generate-csv-export.js"]
    H --> A["server/generate-archive.js"]
    G --> S
    E --> S
    G --> C["server/document-context.js"]
    C --> T["DOCX-контекст"]
    T --> D["Docxtemplater + PizZip"]
    M["manifest.json"] --> G
    W["Word-шаблон .docx"] --> D
    D --> O["Готовый .docx"]
    O --> U
    E --> X["CSV или ZIP с CSV"]
    X --> U
    G --> A
    E --> A
    A --> Z["Итоговый ZIP"]
    Z --> U

Принципы


7. Структура репозитория

.
├── index.html
├── package.json
├── package-lock.json
├── README.md
├── src
│   ├── app.js
│   ├── about.css
│   ├── i18n.js
│   ├── styles.css
│   └── domain
│       ├── format.js
│       └── schedule.js
├── server
│   ├── index.js
│   ├── generate-document.js
│   ├── generate-document-export.js
│   ├── convert-document-to-pdf.js
│   ├── generate-csv-export.js
│   ├── generate-archive.js
│   ├── file-names.js
│   ├── document-context.js
│   ├── number-to-words.js
│   ├── render-readme.js
│   └── templates
│       ├── manifest.json
│       ├── installment-agreement-v1.docx
│       ├── installment-agreement-v2.docx
│       ├── installment-agreement-v3.docx
│       ├── installment-agreement-v4.docx
│       ├── judicial-installment-agreement-v1.docx
│       ├── judicial-installment-agreement-v2.docx
│       └── judicial-installment-agreement-v3.docx
├── scripts
│   ├── create_default_template.py
│   ├── create_judicial_template.py
│   ├── patch_template_conditionals.mjs
│   └── generate-sample-document.js
└── test
    ├── archive-export.test.js
    ├── csv-export.test.js
    ├── document-context.test.js
    ├── document-export.test.js
    ├── document-generation.test.js
    ├── format.test.js
    ├── number-to-words.test.js
    ├── proportional-contracts.test.js
    ├── readme-page.test.js
    └── schedule.test.js

Назначение файлов

index.html

Содержит всю семантическую разметку:

В проекте нет шаблонизатора интерфейса и компонентного фреймворка. Динамические карточки и строки особых платежей создаются средствами DOM в src/app.js.

src/styles.css

Содержит:

Основные визуальные уровни:

  1. фон страницы;
  2. белые секции form-section;
  3. светлые поля field;
  4. подтверждённые карточки зелёного оттенка без акцентной полосы слева;
  5. неподтверждённые договоры янтарного оттенка без акцентной полосы слева.

src/app.js

Оркестрирует работу интерфейса:

src/about.css

Оформляет страницу документации: типографику README, таблицы, блоки кода, цитаты, закреплённую навигацию и мобильное представление.

src/i18n.js

Содержит словарь русского и казахского интерфейса, переключение языка без перезагрузки и локализацию динамически создаваемых элементов. Страница документации /about намеренно остаётся русскоязычной.

src/domain/format.js

Чистые функции форматирования:

src/domain/schedule.js

Чистая расчётная логика:

Файл не зависит от DOM и используется как браузером, так и серверным генератором документов.

server/index.js

Минимальный HTTP-сервер на стандартном модуле node:http.

Обязанности:

server/generate-document.js

Конвейер Word-генерации:

  1. читает manifest.json;
  2. выбирает шаблон;
  3. пересчитывает график каждого договора;
  4. строит общий график;
  5. получает плоский контекст из document-context.js;
  6. открывает DOCX как ZIP через PizZip;
  7. передаёт контекст в Docxtemplater;
  8. возвращает готовый DOCX как Buffer.

server/generate-document-export.js

Выбирает формат итогового документа. Для docx возвращает результат generate-document.js без преобразований. Для pdf передаёт тот же буфер в convert-document-to-pdf.js и возвращает PDF с соответствующим MIME-типом.

server/convert-document-to-pdf.js

Создаёт изолированный временный каталог и отдельный профиль LibreOffice, записывает туда сгенерированный DOCX, запускает headless-конвертацию и читает готовый PDF. Временные файлы удаляются в finally. Исполняемый файл выбирается из SOFFICE_PATH, иначе используется команда soffice из PATH.

server/calculate-contracts.js

Единый серверный адаптер расчёта. Он:

server/generate-csv-export.js

Генерирует CSV-файлы без временных файлов:

server/generate-archive.js

Собирает итоговый пакет по кнопке «Скачать архив»:

server/document-context.js

Адаптер между внутренней моделью и Word-шаблоном.

Именно здесь:

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

server/templates/manifest.json

Реестр доступных Word-шаблонов.

server/number-to-words.js

Преобразует сумму в русскую и казахскую запись прописью без названия валюты. Используется для {total_debt_amount_words}, {debt_amount_words} и их казахских аналогов с окончанием _kz.

server/render-readme.js

Читает README.md, скрывает встроенный блок содержания, преобразует Markdown в HTML и добавляет идентификаторы заголовков, закреплённое оглавление, поиск с переходом к найденному тексту и переключатель темы для страницы /about.

scripts/create_default_template.py

Создаёт технический DOCX с помощью python-docx. Фиксирует:

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

scripts/generate-sample-document.js

Формирует документ без браузера. Используется для ручной проверки шаблона.

test

Набор автоматических тестов:


8. Зависимости

Runtime-зависимости

Пакет Зафиксированная версия Назначение
docxtemplater 3.69.3 Подстановка данных и обработка циклов внутри DOCX
marked 16.2.1 Преобразование актуального README в HTML для /about
pizzip 3.2.0 Чтение и сборка DOCX как ZIP-архива

Версии в package.json заданы через ^, а конкретные установленные версии фиксируются package-lock.json.

Что не используется


9. Модель данных в браузере

Должник

После нажатия «Добавить» формируется debtorRecord:

{
  lastName: "Иванов",
  firstName: "Иван",
  middleName: "Иванович",
  iin: "900512300123",
  address: "г. Алматы, ул. Абая, д. 10",
  phone: "87001234567"
}

Судебное дело

После нажатия «Добавить» в судебном разделе формируется judicialCaseRecord:

{
  courtName: "Районный суд № 2",
  judgeNameInitials: "Петров П.П."
}

Представитель истца хранится отдельно для совместимости с Word-контекстом:

plaintiffInfoRecord = {
  name: "",
  representative: "Сидоров С.С."
}

Все три видимых значения могут быть пустыми. В запрос они передаются объектами judicialCase и plaintiffInfo, а сервер заменяет пустые строки понятными русскими и казахскими редакционными подсказками. Поле name сейчас не заполняется через интерфейс, но сохраняется в модели для совместимости с шаблонными тегами {plaintiff_name} и {plaintiff_name_kz}.

Договор

Каждый договор находится в массиве contracts:

{
  id: 1,
  number: "01-2026",
  date: "2026-08-03",
  debtAmount: 250000,
  confirmed: false,
  settings: {
    calculationType: "equal",
    calculationMode: "amount",
    equal: {
      amount: 250000,
      months: 0,
      paymentAmount: 0,
      paymentDebt: 250000,
      startDate: "",
      advanceAmount: 0,
      advanceDate: ""
    },
    complex: {
      debt: 250000,
      months: 0,
      startDate: "",
      advanceAmount: 0,
      advanceDate: "",
      remainderMode: "all",
      conditions: []
    }
  }
}

Почему в договоре хранятся оба режима

В settings.equal сохраняются значения обоих подрежимов:

При переключении пользователь не теряет уже введённые значения.

Выбор договора

selectedContractId определяет, настройки какого договора открыты в разделе «Параметры рассрочки».

Перед переключением договора вызывается persistSelectedContractSettings(). После выбора нового договора вызывается applyContractSettings().

Подтверждение

Поле confirmed означает, что пользователь завершил настройку договора.

Перед подтверждением проверяются:

После редактирования подтверждённого договора флаг снова становится false. До добавления договора раздел параметров не показывает лишнее пояснение. После добавления в его заголовке отображается номер и дата выбранного договора.

Общие параметры нескольких договоров

Общий режим хранится отдельно от индивидуальных настроек договоров:

jointSettings = {
  months: 12,
  payment: 100000,
  startDate: "2026-08-03",
  advanceAmount: 100000,
  advanceDate: "2026-08-02",
  confirmed: true
}

При подтверждении каждый участвующий договор получает признак joint: true. В запрос к серверу дополнительно передаётся jointInstallment:

{
  enabled: true,
  months: 12,
  payment: 100000,
  startDate: "2026-08-03",
  advanceAmount: 100000,
  advanceDate: "2026-08-02"
}

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


10. Расчёт графиков

Равные платежи по сроку

Вход:

{
  amount: 250000,
  months: 12,
  startDate: "2026-08-03",
  mode: "amount"
}

Алгоритм:

  1. сумма переводится в целые сотые;
  2. обычный платёж вычисляется целочисленным делением;
  3. остаток округления добавляется в последний платёж;
  4. последний баланс всегда должен стать равным нулю.

Равные платежи по размеру платежа

Вход:

{
  amount: 300000,
  payment: 25000,
  startDate: "2026-08-03",
  mode: "payment"
}

Срок:

ceil(сумма долга / размер платежа)

Последний платёж уменьшается до фактического остатка.

Даты платежей

addMonths старается сохранить число первого платежа.

Пример:

Общий пропорциональный расчёт

Вход:

{
  contracts: [
    { id: 1, debt: 800000 },
    { id: 2, debt: 200000 }
  ],
  months: 3,
  payment: 100000,
  startDate: "2026-08-03"
}

Для долгов 800 000 и 200 000 обычный общий платёж 100 000 делится как 80 000 и 20 000. В последний месяц функция направляет на погашение весь оставшийся долг. Поэтому при сроке три месяца общий ряд платежей будет 100 000, 100 000, 800 000.

Алгоритм работает в целых тенге:

  1. округляет долг каждого договора и общий регулярный платёж до целого тенге;
  2. определяет долю каждого договора в общем долге;
  3. распределяет платёж пропорционально этим долям;
  4. распределяет остаток методом наибольших остатков так, чтобы сумма частей точно совпала с общим платежом;
  5. не позволяет платежу по договору превысить его остаток;
  6. при необходимости перераспределяет свободную часть между другими договорами;
  7. в последнем месяце обнуляет все остатки.

Сложный расчёт

Условие конкретного месяца:

{
  kind: "date",
  startDate: "2026-08-03",
  endDate: "",
  amount: 25000
}

Условие периода:

{
  kind: "period",
  startDate: "2026-08-03",
  endDate: "2026-11-03",
  amount: 25000
}

Для периода одна и та же сумма назначается каждому месяцу диапазона.

remainderMode: "all"

Остаток долга распределяется равными частями между месяцами, не занятыми особыми условиями.

remainderMode: "manual"

По обычным месяцам создаются строки с платежом 0. Нераспределённая сумма остаётся в балансе.

Конфликты особых условий

Интерфейс ограничивает доступные месяцы:

Общий график

combineContractSchedules:


11. CSV-экспорт

Браузер отправляет текущую модель на POST /api/exports/csv. Сервер заново рассчитывает графики и формирует CSV-файлы.

Столбцы:

  1. Номер;
  2. ИИН;
  3. Договор;
  4. Дата;
  5. Платеж;
  6. Остаток.

Особенности:

Если добавлен один договор, скачивается один файл:

{ИИН}-{PRE|COURT}-Schedule-{НОМЕР ДОГОВОРА}.csv

Префикс PRE используется на досудебной стадии, COURT — на судебной.

Если добавлено два или более договоров, скачивается ZIP-архив:

CSV-архив называется {ИИН}-Schedules.zip. В него входят:

При двух и более договорах в архив дополнительно включается {ИИН}-COMBINED-Schedule.csv.

Итоговый ZIP

Кнопка «Скачать архив» формирует единый пакет документов. Имя архива:

{ИИН}-{ДД-ММ-ГГГГ}.zip

Например:

900512300123-05-08-2026.zip

На досудебной стадии документ называется {ИИН}-PRE-Obligation, на судебной — {ИИН}-COURT-Mediation. Расширение .docx или .pdf зависит от выбранного формата.

Если договор один, архив содержит:

Если договоров два или более, архив содержит:


12. HTTP API

GET /about

Возвращает оформленную HTML-страницу «Документация проекта». Сервер при каждом запросе читает текущий README.md и преобразует его через marked, поэтому страница обновляется после обычного обновления браузера и не требует сборки.

POST /api/documents

Создаёт Word- или PDF-документ. Сначала сервер всегда заполняет DOCX-шаблон. Если выбран PDF, готовый DOCX конвертируется через LibreOffice, поэтому текст, условные разделы и приложения в обоих форматах идентичны.

Заголовок:

Content-Type: application/json

Пример запроса:

{
  "stage": "judicial",
  "templateId": "judicial-installment-agreement-v3",
  "documentFormat": "pdf",
  "csvDelimiter": ";",
  "judicialCase": {
    "courtName": "Районный суд № 2",
    "judgeNameInitials": "Петров П.П."
  },
  "plaintiffInfo": {
    "name": "",
    "representative": "Сидоров С.С."
  },
  "debtor": {
    "lastName": "Иванов",
    "firstName": "Иван",
    "middleName": "Иванович",
    "iin": "900512300123",
    "address": "г. Алматы, ул. Абая, д. 10",
    "phone": "87001234567"
  },
  "contracts": [
    {
      "number": "01-2026",
      "date": "2026-08-03",
      "debtAmount": 250000,
      "confirmed": true,
      "installment": {
        "type": "equal",
        "mode": "amount",
        "debt": 250000,
        "months": 12,
        "payment": 0,
        "startDate": "2026-08-03",
        "advanceAmount": 0,
        "advanceDate": ""
      }
    }
  ]
}

Допустимые значения documentFormat: docx и pdf. Если поле отсутствует, используется docx. Значение csvDelimiter не влияет на документ и применяется только к CSV-файлам.

Допустимые стадии: pretrial и judicial. Сервер связывает стадию с нужным шаблоном и отклоняет несовместимую пару stage/templateId. Объект judicialCase и plaintiffInfo необязательны; вместо пустых реквизитов сервер передаёт в русские и казахские теги шаблона соответствующие редакционные подсказки. Предупреждение перед формированием неполного судебного PDF является клиентской защитой интерфейса; прямой API-запрос не показывает диалог.

Успешный ответ:

Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document
Content-Disposition: attachment; filename*=UTF-8''900512300123-PRE-Obligation.docx

Для PDF:

Content-Type: application/pdf
Content-Disposition: attachment; filename*=UTF-8''900512300123-COURT-Mediation.pdf

Ошибка:

{
  "error": "Описание ошибки"
}

HTTP-статус ошибок генерации — 400.

Перед отправкой браузер требует:

Сервер не доверяет переданному графику: он заново рассчитывает каждый индивидуальный график общей функцией из src/domain/schedule.js, затем строит сводный график и только после этого формирует Word. Имя скачиваемого файла зависит от стадии:

{ИИН}-PRE-Obligation.docx
{ИИН}-COURT-Mediation.docx

При выборе PDF расширение в соответствующем имени меняется на .pdf.

POST /api/exports/csv

Принимает ту же структуру должника, договоров и параметров рассрочки, что и POST /api/documents.

Для одного договора успешный ответ:

Content-Type: text/csv; charset=utf-8
Content-Disposition: attachment; filename*=UTF-8''...

Для нескольких договоров успешный ответ:

Content-Type: application/zip
Content-Disposition: attachment; filename*=UTF-8''...

Имя ZIP с графиками — {ИИН}-Schedules.zip. Внутри находятся отдельные графики договоров и {ИИН}-COMBINED-Schedule.csv.

Графики всегда повторно рассчитываются на сервере. Ошибка валидации или расчёта возвращается с HTTP-статусом 400:

{
  "error": "Описание ошибки"
}

POST /api/exports/archive

Принимает ту же JSON-модель, что и остальные операции формирования файлов. Успешный ответ:

Content-Type: application/zip
Content-Disposition: attachment; filename*=UTF-8''...

Имя итогового архива — {ИИН}-{ДД-ММ-ГГГГ}.zip.

Сервер независимо формирует Word и CSV, затем помещает результаты в один архив. Ошибки валидации или генерации возвращаются с HTTP-статусом 400.


13. Конвейер формирования Word

sequenceDiagram
    participant B as Браузер
    participant S as HTTP-сервер
    participant G as generate-document.js
    participant C as document-context.js
    participant W as DOCX-шаблон

    B->>S: POST /api/documents
    S->>G: payload
    G->>G: расчёт каждого графика
    G->>G: объединение графиков
    G->>C: данные + рассчитанные графики
    C-->>G: контекст Word
    G->>W: открыть через PizZip
    G->>G: Docxtemplater.render(context)
    G-->>S: Buffer DOCX
    S-->>B: файл .docx

Параметры Docxtemplater:

{
  paragraphLoop: true,
  linebreaks: true,
  nullGetter: () => ""
}

14. Система Word-шаблонов

Расположение

Активные шаблоны:

server/templates/installment-agreement-v4.docx
server/templates/judicial-installment-agreement-v3.docx

Предыдущие версии сохранены рядом как история юридических редакций и точки восстановления.

Реестр:

server/templates/manifest.json

Текущий manifest:

{
  "defaultTemplateId": "installment-agreement-v4",
  "templates": [
    {
      "id": "installment-agreement-v1",
      "version": 1,
      "name": "Обязательство — сохранённая редакция пользователя",
      "file": "installment-agreement-v1.docx"
    },
    {
      "id": "installment-agreement-v2",
      "version": 2,
      "name": "Обязательство — рабочая версия с условными разделами",
      "file": "installment-agreement-v2.docx"
    },
    {
      "id": "installment-agreement-v3",
      "version": 3,
      "name": "Обязательство — предыдущая двуязычная версия",
      "file": "installment-agreement-v3.docx"
    },
    {
      "id": "installment-agreement-v4",
      "version": 4,
      "name": "Обязательство — вычитанная и проверенная версия",
      "file": "installment-agreement-v4.docx"
    },
    {
      "id": "judicial-installment-agreement-v1",
      "version": 1,
      "name": "Медиативное соглашение — предыдущая версия судебной стадии",
      "file": "judicial-installment-agreement-v1.docx"
    },
    {
      "id": "judicial-installment-agreement-v2",
      "version": 2,
      "name": "Медиативное соглашение — предыдущая версия судебной стадии",
      "file": "judicial-installment-agreement-v2.docx"
    },
    {
      "id": "judicial-installment-agreement-v3",
      "version": 3,
      "name": "Медиативное соглашение — рабочая версия судебной стадии",
      "file": "judicial-installment-agreement-v3.docx"
    }
  ]
}

Как выбирается шаблон

  1. Клиент передаёт templateId.
  2. generate-document.js читает manifest.
  3. Если templateId не передан, используется defaultTemplateId.
  4. Сервер ищет запись с соответствующим id.
  5. Путь к файлу разрешается только внутри server/templates.

Сейчас клиент выбирает шаблон из конфигурации стадии:

const documentStages = {
  pretrial: {
    templateId: "installment-agreement-v4",
  },
  judicial: {
    templateId: "judicial-installment-agreement-v3",
  },
};

Рабочий шаблон судебного документа зарегистрирован по пути:

server/templates/judicial-installment-agreement-v3.docx

Это отдельный документ «Медиативное соглашение». Он не заменяет досудебное обязательство и выбирается только для стадии judicial.

Откройте указанный DOCX непосредственно в Microsoft Word или LibreOffice Writer. В нём используются два вида обозначений:

Исходный скрипт, которым создана болванка, находится в scripts/create_judicial_template.py. Повторная команда

python3 scripts/create_judicial_template.py --force

перезапишет DOCX и уничтожит ручные изменения, сделанные в Word. Обычно редактировать нужно сам файл .docx, а скрипт использовать только для осознанного восстановления технической версии или изменения её структуры.

Простого изменения defaultTemplateId недостаточно: клиент всегда передаёт явный templateId, выбранный по стадии.


15. Синтаксис меток в DOCX

Метки Docxtemplater — это обычный текст Word. Их нужно напечатать с клавиатуры или вставить как обычный текст. Это не:

Например, в документе должно буквально находиться {full_name}.

Обычное поле

{full_name}

Цикл

{#contracts}
...
{/contracts}

Условный раздел

{#has_multiple_contracts}
Этот текст и расположенные здесь таблицы появятся только при двух и более
договорах.
{/has_multiple_contracts}

В рабочем installment-agreement-v4.docx таким условием закрыты:

При одном договоре эти элементы полностью отсутствуют в итоговом DOCX.

Вложенный цикл

{#contracts}
Договор № {contract_number}

{#schedule}
{number} | {date} | {payment} | {balance}
{/schedule}
{/contracts}

Внутри schedule имена number, date, payment, balance относятся к строке текущего графика. После закрытия {/schedule} контекст возвращается к текущему договору.

Область видимости важна:

глобальный документ
├── contracts[]
│   └── schedule[]
└── combined_schedule[]

16. Поля, передаваемые в Word

Ниже перечислен фактический контекст, создаваемый server/document-context.js.

Глобальные поля

Метка Тип Пример Описание
{last_name} строка Иванов Фамилия
{first_name} строка Иван Имя
{middle_name} строка Иванович Необязательное отчество; может быть пустым
{full_name} строка Иванов Иван Иванович Собранное Ф.И.О.
{full_name_initials} строка Иванов И.И. Фамилия и инициалы; при отсутствии отчества — Иванов И.
{full_name_upper} строка ИВАНОВ ИВАН ИВАНОВИЧ Ф.И.О. прописными буквами для юридического текста
{iin} строка 900512300123 ИИН из 12 цифр
{address} строка г. Алматы, ул. Абая, д. 10 Необязательный нормализованный адрес; может быть пустым
{phone} строка 87001234567 Необязательный телефон из 10 или 11 цифр без форматирования
{court_name} строка Районный суд № 2 Необязательное наименование суда; если поле пустое — Указать наименование суда
{court_name_kz} строка Районный суд № 2 Наименование суда для казахской части; если поле пустое — Соттың атауын көрсету
{judge_name_initials} строка Петров П.П. Необязательные фамилия и инициалы судьи; если поле пустое — Указать Фамилию и Инициалы судьи
{judge_name_initials_kz} строка Петров П.П. Судья для казахской части; если поле пустое — Судьяның тегі мен аты-жөнін көрсету
{plaintiff_name} строка ТОО «Истец» Необязательное наименование истца; если поле пустое — Указать наименование истца
{plaintiff_name_kz} строка ТОО «Истец» Наименование истца для казахской части; если поле пустое — Талапкердің атауын көрсету
{plaintiff_representative} строка Сидоров С.С. Необязательный представитель истца; если поле пустое — Указать представителя истца
{plaintiff_representative_kz} строка Сидоров С.С. Представитель истца для казахской части; если поле пустое — Талапкердің өкілін көрсету
{document_city} строка г. Караганда Город подписания, заданный в серверном контексте
{document_date} строка 05 августа 2026 Текущая дата формирования документа
{document_date_kz} строка 2026 жылғы 05 тамыз Текущая дата формирования документа для казахской части
{contracts_text} строка № 01-2026 от 03.08.2026; № 02-2026 от 03.09.2026 Только номера и даты; слово «договор» должно находиться в тексте шаблона
{contracts_text_kz} строка 03.08.2026 күнгі № 01-2026; 03.09.2026 күнгі № 02-2026 Номера и даты договоров в казахском порядке
{contracts_count} число 2 Количество договоров
{has_multiple_contracts} логическое true true только при двух и более договорах; используется как условная секция
{total_debt_amount} строка 550 000,00 Сумма синхронизированного contract.debtAmount всех договоров, без обозначения валюты
{total_debt_amount_words} строка пятьсот пятьдесят тысяч Общая сумма прописью, без обозначения валюты
{total_debt_amount_words_kz} строка бес жүз елу мың Общая сумма прописью на казахском, без обозначения валюты
{combined_appendix_number} число 3 Номер сводного приложения
{contracts} массив Договоры для цикла
{schedule} массив График для судебного документа: индивидуальный при одном договоре, общий при нескольких
{combined_schedule} массив Строки общего графика; при одном договоре всегда пустой массив

Поля внутри {#contracts}

Метка Тип Пример Описание
{appendix_number} число 1 Номер приложения договора
{contract_number} строка 01-2026 Номер договора
{contract_date} строка 03.08.2026 Дата договора
{calculation_type} строка Равными платежами Вид расчёта
{debt_amount} строка 250 000,00 Отформатированная сумма рассрочки, без обозначения валюты
{debt_amount_words} строка двести пятьдесят тысяч Сумма договора прописью, без обозначения валюты
{debt_amount_words_kz} строка екі жүз елу мың Сумма договора прописью на казахском, без обозначения валюты
{debt_value} число 250000 Та же сумма без форматирования
{term_months} строка 12 Срок
{first_payment_date} строка 03.08.2026 Дата первой строки графика
{advance_payment_amount} строка 100 000,00 Сумма авансового платежа без обозначения валюты; 0,00, если аванса нет
{advance_payment_date} строка 13.08.2026 Дата авансового платежа; пустая строка, если аванса нет
{has_advance_payment} логическое true Признак наличия авансового платежа для условного блока шаблона
{schedule} массив График текущего договора

Поля внутри {#schedule}

Метка Тип Пример Описание
{number} число 1 Порядковый номер строки
{date} строка 03.08.2026 Дата платежа
{payment} строка 20 833,33 Платёж без символа тенге
{balance} строка 229 166,67 Остаток без символа тенге
{is_advance} логическое true true только для отдельной строки авансового платежа

Поля внутри {#combined_schedule}

Метка Тип Пример Описание
{number} число 1 Порядковый номер строки
{date} строка 03.08.2026 Дата общего платежа
{payment} строка 45 833,33 Суммарный платёж
{balance} строка 529 166,67 Суммарный остаток

Метки, используемые текущим шаблоном

В активном досудебном шаблоне installment-agreement-v4.docx используются:

{#combined_schedule}
{#contracts}
{#has_multiple_contracts}
{#schedule}
{/combined_schedule}
{/contracts}
{/has_multiple_contracts}
{/schedule}
{appendix_number}
{address}
{balance}
{combined_appendix_number}
{contract_date}
{contract_number}
{contracts_count}
{contracts_text_kz}
{contracts_text}
{date}
{debt_amount}
{document_date_kz}
{document_date}
{full_name_initials}
{full_name}
{iin}
{number}
{payment}
{phone}
{total_debt_amount_words_kz}
{total_debt_amount_words}
{total_debt_amount}

В активном судебном шаблоне judicial-installment-agreement-v3.docx используются верхнеуровневый цикл {#schedule} и реквизиты суда, судьи, представителя истца, даты, перечня договоров и суммы на двух языках.

Остальные поля из таблиц выше уже передаются в контекст и могут быть добавлены в Word без изменения JavaScript, если соблюдена область видимости тегов.


17. Текущая компоновка Word-шаблонов

Досудебное обязательство

Шаблон installment-agreement-v4 содержит:

  1. заголовок «Обязательство о добровольном погашении суммы долга»;
  2. город и автоматически сформированную дату документа;
  3. юридический текст с Ф.И.О., ИИН, перечнем договоров и общей суммой долга;
  4. нумерованные обязательства должника;
  5. адрес, телефон и строку подписи должника;
  6. цикл приложений по договорам;
  7. таблицу индивидуального графика внутри каждого приложения;
  8. последнее приложение со сводным графиком по всем договорам.

Технические параметры, задаваемые Python-скриптом:

Судебное медиативное соглашение

Шаблон judicial-installment-agreement-v3 содержит:

  1. соглашение об урегулировании спора в порядке медиации;
  2. сведения об истце, ответчике, суде и судье;
  3. перечень договоров и общую сумму долга, в том числе сумму прописью;
  4. один график платежей: индивидуальный при одном договоре или общий при нескольких договорах;
  5. заявление об утверждении соглашения и прекращении производства по делу.

Суд, судья и представитель истца заполняются через {court_name}, {judge_name_initials} и {plaintiff_representative}. Если поля интерфейса оставлены пустыми, выводятся соответствующие редакционные подсказки. Для казахской части используются теги {court_name_kz}, {judge_name_initials_kz} и {plaintiff_representative_kz}. Дата документа разделена на {document_date} и {document_date_kz}.


18. Как правильно изменить существующий Word-шаблон

Что можно менять без изменения JavaScript

В Word можно свободно менять:

JavaScript не нужно менять, пока используются поля из раздела 16 и сохраняется правильная вложенность циклов.

Что требует изменения кода

Изменение кода потребуется, если нужно:

Порядок добавления нового поля описан в разделе 21.

Самый безопасный сценарий: изменить только текст

  1. Зафиксировать рабочее состояние:

    npm run check
    npm run sample
    
  2. Скопировать server/templates/installment-agreement-v2.docx в новый файл v3 и редактировать только новую версию.

  3. Открыть рабочий .docx в Microsoft Word.

  4. Отключить «Исправления»/Track Changes. Если исправления уже есть, принять или отклонить их до итогового сохранения.

  5. Включить отображение непечатаемых знаков . Так легче увидеть пустые абзацы, разрывы страниц и границы циклов.

  6. Изменить юридические формулировки, не затрагивая метки и строки циклов.

  7. Сохранить файл именно как Документ Word (.docx). Не использовать .doc, .docm, .dotx, PDF или формат Google Docs.

  8. Сформировать новый образец:

    npm run sample
    
  9. Открыть .tmp/document-qa/sample-obligation-multiple.docx в Word и проверить визуально.

  10. Сформировать и открыть однодоговорный вариант:

    node scripts/generate-sample-document.js --single
    

    В нём не должно быть пункта 3.2, сводного приложения и лишней пустой страницы.

  11. Запустить:

    npm run check
    
  12. Зафиксировать изменение шаблона отдельным Git-коммитом.

Останавливать сервер для редактирования необязательно. Каждый новый запрос читает DOCX с диска заново.

Правила работы с метками

Word хранит текст частями — runs. Визуально цельная метка иногда оказывается разделена на несколько таких частей из-за форматирования, режима исправлений или вставки из другого документа. Docxtemplater умеет работать со многими такими случаями, но самый надёжный способ исправления — удалить проблемную метку и напечатать её заново одним фрагментом.

Как перемещать поле

  1. Выделить всю метку вместе со скобками.
  2. Вырезать её.
  3. Вставить в новый абзац или ячейку как обычный текст.
  4. Применить стиль ко всей метке.
  5. Убедиться, что она осталась внутри нужного цикла.

Например, {contract_number} нельзя вынести за пределы {#contracts}, иначе у поля не будет текущего договора.

Как менять таблицу графика

Можно менять оформление заголовка, ширину столбцов и порядок столбцов. Нельзя разрывать повторяемую строку:

{#schedule}{number} | {date} | {payment} | {balance}{/schedule}

Открывающий тег должен находиться перед данными повторяемой строки, а закрывающий — после них. Заголовок таблицы не должен попадать внутрь цикла, иначе он повторится для каждого платежа.

Как не потерять ручные изменения

  1. Не заменяйте активный файл новой редакцией под тем же именем.
  2. Скопируйте последнюю рабочую версию в следующий номер: v2v3.
  3. Редактируйте только новую копию в Word.
  4. Закройте Word перед тестированием, чтобы рядом не оставался служебный файл вида ~$...docx.
  5. Добавьте новую версию в server/templates/manifest.json.
  6. Переключите templateId в src/app.js.
  7. Выполните npm run check, затем сформируйте образцы с одним и несколькими договорами.
  8. Только после визуальной проверки добавьте DOCX и код в Git и создайте коммит. Git хранит предыдущую бинарную версию целиком, поэтому её можно восстановить.

Текущее состояние:

Что нельзя делать с рабочим шаблоном

DOCX является бинарным файлом: Git может сохранить и восстановить его версию, но не покажет удобный построчный diff. Поэтому юридические редакции лучше хранить отдельными файлами v1, v2, v3, v4, а не постоянно перезаписывать единственный шаблон.


19. Как готовить новый шаблон допсоглашения

Вариант A. Создать новую версию на основе текущей

Рекомендуемый вариант.

  1. Скопировать:

    server/templates/installment-agreement-v2.docx
    
  2. Назвать новый файл, например:

    installment-agreement-v3.docx
    
  3. Не удаляя v2, отредактировать v3 в Word.

  4. Добавить v3 новой записью в массив templates.

    {
      "defaultTemplateId": "installment-agreement-v3",
      "templates": [
        {
          "id": "installment-agreement-v1",
          "version": 1,
          "name": "Сохранённая пользовательская редакция",
          "file": "installment-agreement-v1.docx"
        },
        {
          "id": "installment-agreement-v2",
          "version": 2,
          "name": "Рабочая версия с условными разделами",
          "file": "installment-agreement-v2.docx"
        },
        {
          "id": "installment-agreement-v3",
          "version": 3,
          "name": "Новая юридическая редакция",
          "file": "installment-agreement-v3.docx"
        }
      ]
    }
    
  5. Переключить явный templateId в getDocumentPayload() файла src/app.js:

    templateId: "installment-agreement-v3"
    
  6. Если для нового шаблона добавлены поля, обновить server/document-context.js и тесты.

  7. Выполнить npm run sample и npm run check.

  8. Сформировать документ через веб-интерфейс и проверить имя, текст и все приложения.

Изменение только defaultTemplateId не переключит текущий интерфейс, пока клиент явно отправляет конкретный templateId.

Вариант B. Создать чистый DOCX вручную

  1. Создать новый документ в Word.
  2. Настроить формат страницы, поля, шрифты и стили.
  3. Написать юридический текст.
  4. Вставить глобальные метки.
  5. Создать таблицу договоров.
  6. Создать цикл приложений {#contracts}.
  7. Внутри него создать таблицу {#schedule}.
  8. После закрытия цикла договоров создать сводный график и целиком обернуть его в {#has_multiple_contracts}...{/has_multiple_contracts}.
  9. Зарегистрировать файл в manifest.
  10. Переключить templateId.
  11. Выполнить полную проверку.

Перед созданием чистого шаблона рекомендуется выписать структуру:

  1. основной юридический текст;
  2. сведения о должнике;
  3. список договоров;
  4. подписи;
  5. индивидуальное приложение, повторяемое для каждого договора;
  6. общее приложение.

Это помогает не вложить общий график внутрь цикла договоров и не получить его копию после каждого договора.

Минимальный текстовый каркас

СОГЛАШЕНИЕ О РАССРОЧКЕ

Должник: {full_name}
ИИН: {iin}
Договоры: {contracts_text}
Общая сумма: {total_debt_amount}

{#contracts}
Приложение № {appendix_number}
Договор № {contract_number} от {contract_date}
Сумма: {debt_amount}
Срок: {term_months} месяцев
Первый платёж: {first_payment_date}

{#schedule}
{number} | {date} | {payment} | {balance}
{/schedule}
{/contracts}

Приложение № {combined_appendix_number}
Сводный график

{#combined_schedule}
{number} | {date} | {payment} | {balance}
{/combined_schedule}

В реальном Word-документе строки графика следует располагать в строках таблицы, а не в виде текста с символом |.

Контроль перед регистрацией нового шаблона


20. Циклы в таблицах Word

Таблица договоров

Чтобы строка таблицы повторялась для каждого договора:

Пример содержимого строки:

Ячейка 1 Ячейка 2 Ячейка 3 Ячейка 4
{#contracts}{contract_number} {contract_date} {debt_amount} {term_months}{/contracts}

Docxtemplater повторит всю строку.

Таблица индивидуального графика

Дата Платёж Остаток
{#schedule}{number} {date} {payment} {balance}{/schedule}

Эта таблица должна находиться внутри {#contracts}, иначе у неё не будет контекста текущего договора.

Таблица общего графика

Дата Платёж Остаток
{#combined_schedule}{number} {date} {payment} {balance}{/combined_schedule}

Общий график располагается вне цикла {#contracts}.

Целое приложение на каждый договор

Если нужно повторять не одну строку, а несколько абзацев и таблицу:

  1. поместить {#contracts} в отдельный абзац перед приложением;
  2. разместить текст и таблицу приложения;
  3. поместить {/contracts} в отдельный абзац после приложения;
  4. при необходимости добавить разрыв страницы внутрь повторяемого блока.

В текущем техническом шаблоне разрыв страницы находится перед циклом и ещё один — внутри повторяемого блока после индивидуальной таблицы. Поэтому каждое приложение начинается с новой страницы, а сводный график идёт после всех договоров.

Если появляется пустая страница:

  1. включить знаки ;
  2. проверить, нет ли двух соседних разрывов;
  3. проверить пустой абзац после таблицы;
  4. убедиться, что разрыв находится с правильной стороны {/contracts}.

Практический способ собрать повторяемую строку

  1. Создать таблицу с заголовком и одной строкой данных.
  2. Внести в строку все поля без циклов.
  3. В начало текста первой ячейки добавить {#schedule}.
  4. В конец текста последней ячейки добавить {/schedule}.
  5. Сформировать образец.
  6. Убедиться, что повторяется строка данных, а не вся таблица.

Для таблицы договоров те же действия выполняются с {#contracts} и {/contracts}.


21. Как добавить новое поле в шаблон

Допустим, нужно добавить {organization_name}.

Шаг 1. Определить источник

Решить, откуда приходит значение:

Шаг 2. Добавить в браузерную модель

Если значение вводит пользователь:

  1. добавить поле в index.html;
  2. получить DOM-элемент в src/app.js;
  3. добавить нормализацию и валидацию;
  4. сохранить значение в состояние;
  5. включить его в getDocumentPayload().

Шаг 3. Добавить в серверный контекст

В server/document-context.js:

return {
  // существующие поля
  organization_name: requireText(
    payload.organizationName,
    "Наименование организации",
  ),
};

Если поле относится к договору, добавить его в объект, возвращаемый внутри contracts.map(...).

Если поле относится к строке графика, добавить его внутри schedule.map(...).

Шаг 4. Вставить метку в DOCX

{organization_name}

Шаг 5. Добавить тест

Проверить:

Шаг 6. Выполнить QA

npm run check
npm run sample

22. Версионирование шаблонов

Не рекомендуется постоянно заменять один файл новой юридической редакцией.

Предпочтительная схема:

installment-agreement-v1.docx
installment-agreement-v2.docx
installment-agreement-v3.docx
installment-agreement-v4.docx

Manifest:

{
  "defaultTemplateId": "installment-agreement-v4",
  "templates": [
    {
      "id": "installment-agreement-v1",
      "version": 1,
      "name": "Редакция 1",
      "file": "installment-agreement-v1.docx"
    },
    {
      "id": "installment-agreement-v2",
      "version": 2,
      "name": "Редакция 2",
      "file": "installment-agreement-v2.docx"
    },
    {
      "id": "installment-agreement-v3",
      "version": 3,
      "name": "Редакция 3",
      "file": "installment-agreement-v3.docx"
    },
    {
      "id": "installment-agreement-v4",
      "version": 4,
      "name": "Редакция 4",
      "file": "installment-agreement-v4.docx"
    }
  ]
}

Рекомендации:


23. Проверка шаблона

Автоматическая

npm run check

Тест генерации:

Создание образца

npm run sample

Команда использует данные из scripts/generate-sample-document.js: один договор с расчётом по сроку и второй с расчётом по размеру платежа. Готовый файл создаётся в .tmp/document-qa/sample-obligation-multiple.docx.

Команда node scripts/generate-sample-document.js --single создаёт .tmp/document-qa/sample-obligation-single.docx.

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

Ручной чек-лист

Рекомендуемая матрица визуальной проверки

Сценарий Что проверять
Один договор, 1–2 платежа основной текст и один индивидуальный график; пункт 3.2 и сводный график отсутствуют
Два договора с разными датами нумерацию приложений и объединение дат
Длинное Ф.И.О. и номер договора переносы и ширину ячеек
Пустое отчество отсутствие лишних двойных пробелов
Большая сумма формат 1 234 567,89 и ширину столбца; валюту задаёт шаблон
График на несколько страниц повторение заголовка и отсутствие разрыва строки
Сложный график с нулевыми платежами наличие помесячных нулевых строк
Будущая дата платежа по графику правильную дату первой строки и все последующие месяцы

Минимально новая юридическая редакция должна быть проверена на одном и на нескольких договорах. Успешного открытия одного короткого образца недостаточно.

Просмотр меток текущего DOCX

DOCX является ZIP-архивом. Для диагностического просмотра:

unzip -p server/templates/installment-agreement-v4.docx \
  word/document.xml

Для обычного редактирования распаковывать DOCX не нужно.


24. Типовые ошибки шаблона

«Выбранный шаблон не найден»

Причины:

«Файл не найден» или ошибка чтения DOCX

Проверить:

Ошибка парсинга Docxtemplater

Частые причины:

Правильный порядок:

{#contracts}
  {#schedule}
  {/schedule}
{/contracts}

Пустое поле в документе

nullGetter заменяет отсутствующие значения пустой строкой. Проверить:

Если отчество отсутствует, пустое {middle_name} является штатным поведением. Для готового Ф.И.О. предпочтительно использовать {full_name}: сервер сам убирает лишний пробел.

В готовом документе осталась метка

Если виден текст вроде {contract_number}:

Сначала удалить и заново напечатать метку целиком, затем повторно выполнить npm run sample.

В таблице появляется только одна строка

Проверить расположение {#schedule} и {/schedule}. Они должны охватывать повторяемую строку.

Приложение создаётся только для одного договора

Проверить {#contracts} и {/contracts} вокруг всего блока приложения.

Word сообщает о повреждении файла

Возможные причины:

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


25. Тестирование

Проект использует встроенный тестовый раннер Node.js:

npm run check

Актуальное количество проверок всегда видно в итоговой строке node --test; README намеренно не фиксирует это число, поскольку набор тестов развивается.

test/format.test.js

Проверяет:

test/schedule.test.js

Проверяет:

test/document-context.test.js

Проверяет:

test/document-generation.test.js

Проверяет:

Дополнительные файлы проверяют PDF-конвертацию, CSV и ZIP-архивы, серверный пересчёт пропорциональных графиков, русскую и казахскую запись сумм прописью, а также HTML-страницу документации.


26. Конфиденциальность и безопасность

Что происходит с персональными данными

Что не сохраняется

Исключения:

Защита HTTP-сервера

Для публичного размещения этого недостаточно. Потребуются:


27. Рекомендуемый рабочий процесс

Изменение интерфейса

  1. Изменить index.html.
  2. Изменить src/styles.css.
  3. Обновить поведение в src/app.js.
  4. Проверить мобильную компоновку.
  5. Запустить тесты.

Изменение расчёта

  1. Изменить src/domain/schedule.js.
  2. Добавить тест в test/schedule.test.js.
  3. Проверить интерфейс.
  4. Проверить Word, потому что сервер использует тот же модуль.

Изменение нормализации

  1. Изменить src/domain/format.js.
  2. Добавить тест в test/format.test.js.
  3. Убедиться, что серверная нормализация не расходится с клиентской.

Изменение Word-контекста

  1. Изменить server/document-context.js.
  2. Добавить или обновить тест контекста.
  3. Изменить DOCX.
  4. Запустить npm run sample.
  5. Открыть результат в Word.

Изменение юридического текста

  1. Создать новую версию DOCX.
  2. Сохранить старую версию.
  3. Обновить manifest.
  4. Переключить templateId.
  5. Сформировать образец.
  6. Получить юридическое согласование.
  7. Зафиксировать отдельным Git-коммитом.

28. Рекомендации для дальнейшего развития

При переходе от локального прототипа к рабочей системе разумно выделить:

  1. Схему данных

    • должник;
    • договор;
    • конфигурация рассрочки;
    • версия шаблона;
    • сформированный документ.
  2. Серверную валидацию

    • единая схема payload;
    • формальное закрепление contract.debtAmount как единственного поля суммы долга в следующей версии схемы;
    • проверка дат и ИИН;
    • проверка полного погашения.
  3. Хранилище

    • черновики;
    • история;
    • аудит изменений;
    • версии документов.
  4. Управление шаблонами

    • выбор шаблона в интерфейсе;
    • статусы «черновик/утверждён»;
    • дата вступления редакции в силу;
    • запрет удаления использованной версии.
  5. Генерацию

    • очередь задач;
    • PDF-копию;
    • электронную подпись;
    • контрольные суммы документов.
  6. Табличный экспорт

    • настраиваемый выбор CSV/XLSX;
    • объединение графиков в одну книгу с листами;
    • стили и форматы ячеек для XLSX.
  7. Конфиденциальность

    • роли;
    • шифрование;
    • сроки хранения;
    • маскирование ИИН в журналах;
    • журнал доступа.

29. Краткая памятка по шаблону

  1. Шаблон — обычный .docx.
  2. Текст можно менять в Microsoft Word.
  3. Метки — обычный текст, а не поля слияния Word.
  4. Метки пишутся как {field_name} без пробелов.
  5. Договоры повторяются через {#contracts}.
  6. Их графики повторяются через {#schedule} внутри договора.
  7. Общий график повторяется через {#combined_schedule} вне договоров и показывается только внутри {#has_multiple_contracts}.
  8. Новое поле сначала добавляется в document-context.js.
  9. Новая версия регистрируется в manifest.json.
  10. Клиент должен отправлять нужный templateId.
  11. Перед редактированием нужно сохранить предыдущую версию.
  12. create_default_template.py создаёт отдельный технический файл; флаг --force нельзя направлять на рабочий юридический шаблон.
  13. После каждого изменения:
npm run check
npm run sample
node scripts/generate-sample-document.js --single
  1. Итоговый файл обязательно открыть и визуально проверить в Microsoft Word.
  2. Проверять нужно как минимум один и несколько договоров.