#
nullInvoice
**nullInvoice** - это микросервис Spring Boot для **автоматической генерации и управления счетами** с **полностью настраиваемыми HTML-шаблонами**, предназначенный для интеграции с веб-магазинами и SaaS-платформами.
## Обзор
Компании используют nullInvoice для генерации счетов после завершения продаж. Поставщики настраиваются один раз через веб-интерфейс, затем ваше приложение вызывает REST API для автоматической генерации корректных счетов по запросу.
**Как это работает:**
1. **Настройка**: Настройте поставщиков в UI с данными компании, локалью, валютой, ставками налогов, пользовательским брендингом и шаблонами счетов
2. **Аутентификация**: Сгенерируйте API-ключи из Панели администратора для безопасного доступа к REST API
3. **Интеграция**: Ваш веб-магазин/SaaS выполняет аутентифицированные API-вызовы к `/api/v1/invoices/generate` с использованием ID поставщика
4. **Генерация**: Счета создаются из полностью настраиваемых HTML-шаблонов и возвращаются как JSON или PDF с метаданными в заголовках
5. **Доставка**: Ваше приложение получает счет и может отправить его клиентам или сохранить для учета
### Типичный поток интеграции
```
┌─────────────────┐
│ Клиент │
│ завершает │
│ покупку │
└────────┬────────┘
│
▼
┌─────────────────────────────────────────┐
│ Ваша платформа Webstore/SaaS │
│ ───────────────────────────────────── │
│ 1. Обработка платежа │
│ 2. Выдача цифрового чека │
│ (обязательно) │
│ 3. Клиент запрашивает счет? ────────┐ │
└──────────────────────────────────────┼──┘
│
│ API-вызов
▼
┌──────────────────────────────┐
│ Сервис nullInvoice │
│ ────────────────────────── │
│ POST /api/v1/invoices/ │
│ generate │
│ │
│ - Валидирует ID поставщика │
│ - Применяет шаблон │
│ - Сохраняет HTML-снимок │
│ - Генерирует PDF │
│ - Возвращает счет │
└──────────────┬───────────────┘
│
│ Ответ (JSON или PDF)
▼
┌──────────────────────────────────────────┐
│ Ваша платформа Webstore/SaaS │
│ ────────────────────────────────────── │
│ - Получает JSON-метаданные ИЛИ PDF-файл │
│ - Сохраняет номер счета для учета │
│ - Отправляет PDF клиенту по email │
└──────────────────────────────────────────┘
```
### Основные возможности
**Полностью настраиваемые шаблоны**
- Счета основаны на HTML-шаблонах, определяемых пользователем, с inline CSS
- Шаблоны поддерживают более 30 плейсхолдеров для данных поставщика, клиента и финансовых данных
- Шаблоны по умолчанию для поставщиков позволяют различный брендинг для разных бизнес-единиц
**Неизменяемость документов**
- Каждый счет хранит **снимок разобранного HTML-шаблона** в момент генерации
- Изменения шаблона не влияют на уже созданные счета
- Счета можно последовательно регенерировать из сохраненного снимка
- Права в базе данных предотвращают удаление записей счетов (финансовое соответствие)
**Готовность к мульти-арендности**
- Настройте нескольких поставщиков с независимыми настройками (локаль, валюта, нумерация, брендинг)
- Вызывающие API указывают ID поставщика для генерации счетов для разных юридических лиц
**Гибкая доставка**
- Возврат метаданных счета как JSON для учета (`response_type: number`)
- Возврат PDF напрямую с метаданными в заголовках для немедленной доставки (`response_type: pdf`)
- Получение PDF позже через `/api/v1/invoices/{invoiceNumber}/pdf`
**Асинхронная очередь генерации**
- Альтернативный путь для интеграций, которые не хотят синхронно ждать завершения генерации
- Запрос сохраняется в очередь, фоновый обработчик создаёт счёт, клиенты опрашивают статус
- Те же гарантии нумерации счетов, что и у синхронного пути (общая блокировка строки поставщика)
- Обработчик можно отключить через `QUEUE_ENABLED=false` (по умолчанию `true`)
**Документация OpenAPI**
- Интерактивная документация API доступна на `/swagger`
- Спецификация OpenAPI JSON на `/openapi`
- Полные примеры запросов/ответов и функция try-it-out
## Важные уведомления
**⚠️ Уведомление о безопасности**
nullInvoice включает **встроенную аутентификацию** (вход в UI по сессии + аутентификация по API-ключу для REST-эндпоинтов). Хотя это обеспечивает безопасность по умолчанию, приложение предназначено для развертывания во внутренней/частной сети.
**Рекомендуемое развертывание:**
- За firewall или VPN
- В приватной сети, доступной только доверенным приложениям
- С включенным HTTPS/TLS для всех соединений
- За reverse proxy с настроенным rate limiting
**Для продакшн-развертываний необходимы дополнительные меры безопасности:**
- Включить HTTPS/TLS
- Настроить rate limiting на уровне reverse proxy
- Использовать надежные пароли администратора
- Регулярно ротировать API-ключи
- Хранить API-ключи в безопасном хранилище секретов (HashiCorp Vault, AWS Secrets Manager и др.)
См. раздел [Безопасность и лучшие практики](#безопасность-и-лучшие-практики) для полного чек-листа продакшн.
**⚠️ Это приложение НЕ является бухгалтерским инструментом.**
nullInvoice - это **конвейер генерации счетов**, предназначенный для создания, хранения и доставки документов счетов. Оно не:
- Отслеживает платежи или статус платежа за пределами базовых флагов "unpaid/issued"
- Управляет дебиторской или кредиторской задолженностью
- Формирует финансовые отчеты или балансы
- Интегрируется с бухгалтерскими системами (главные книги, журналы и т. п.)
- Выполняет бухгалтерию, сверку или налоговую отчетность
Для комплексного финансового управления интегрируйте nullInvoice с отдельной бухгалтерской системой. Используйте этот сервис для генерации счетов, затем импортируйте их в вашу бухгалтерскую систему для учета и соответствия.
## Стек
- Java 21, Spring Boot 3.5.3
- MariaDB + JPA
- Thymeleaf (UI)
- OpenHTMLToPDF (PDFBox)
- OpenAPI на `/openapi`, Swagger UI на `/swagger`
## Предварительные требования
**Для развертывания Docker (рекомендуется):**
- Docker
- Docker Compose
**Для локальной разработки:**
- Java 21 (JDK - Eclipse Temurin или OpenJDK)
- Maven 3.9+
- MariaDB 10.5+ (или MySQL 8.0+)
- Standalone-бинарник Tailwind CSS (для сборки CSS - из [GitHub releases](https://github.com/tailwindlabs/tailwindcss/releases))
## Настройка базы данных
**Создайте базу данных:**
```sql
CREATE DATABASE nullinvoice CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
**Создайте выделенного пользователя приложения с ограниченными правами:**
```sql
CREATE USER 'nullinvoice'@'localhost' IDENTIFIED BY 'your_secure_password';
-- Права для обычных операций и миграций схемы
GRANT SELECT, INSERT, UPDATE ON nullinvoice.* TO 'nullinvoice'@'localhost';
GRANT CREATE, ALTER, INDEX, REFERENCES ON nullinvoice.* TO 'nullinvoice'@'localhost';
FLUSH PRIVILEGES;
```
**Для удаленного доступа настройте хост:**
```sql
CREATE USER 'nullinvoice'@'%' IDENTIFIED BY 'your_secure_password';
GRANT SELECT, INSERT, UPDATE ON nullinvoice.* TO 'nullinvoice'@'%';
GRANT CREATE, ALTER, INDEX, REFERENCES ON nullinvoice.* TO 'nullinvoice'@'%';
FLUSH PRIVILEGES;
```
**Важно: почему такие ограниченные права?**
Счета - **неизменяемые финансовые документы**, которые никогда не должны удаляться после создания. Пользователь приложения намеренно ограничен и не может:
- `DELETE` - не может удалять записи
- `DROP` - не может удалять таблицы или базу данных
- `TRUNCATE` - не может очищать таблицы
- `GRANT` - не может выдавать права другим
Это обеспечивает целостность данных и соответствие требованиям хранения финансовых записей. Право `UPDATE` предоставляется для изменения статуса (например, пометить счет как оплаченный) и soft delete для записей сторон.
### Управление схемой
- Схема базы данных управляется миграциями **Flyway**
- Начальная схема: `nullInvoice/src/main/resources/db/migration/V1__initial_schema.sql`
- Схема автоматически создается/обновляется при запуске приложения
- Режим Hibernate DDL установлен в `none` (Flyway управляет всеми изменениями схемы)
## Конфигурация
Пример конфигурационного файла находится в `.env.example`. Скопируйте его в `.env` и настройте значения под вашу среду.
Переменные окружения (значения по умолчанию в скобках):
- `TZ` - **ОБЯЗАТЕЛЬНО** системный часовой пояс (Europe/Sofia)
- `APP_PORT` (8080)
- `DB_HOST` (localhost)
- `DB_PORT` (3306)
- `DB_USER` (nullinvoice)
- `DB_PASSWORD` (пусто)
- `DB_NAME` (nullinvoice)
- `DB_PARAMS` - **ОБЯЗАТЕЛЬНО** параметры JDBC, добавляемые к URL подключения
### **ВАЖНО: Настройка часового пояса**
**НУЖНО установить часовой пояс в ДВУХ местах:**
1. **`TZ`** переменная окружения - задает системный/приложенческий часовой пояс
2. **`serverTimezone`** параметр в `DB_PARAMS` - задает часовой пояс подключения к базе данных
**Оба значения ДОЛЖНЫ совпадать с часовым поясом сервера базы данных**, чтобы корректно интерпретировать и сохранять дату/время.
Пример конфигурации:
```bash
TZ=Europe/Sofia
DB_PARAMS=?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Europe/Sofia
```
Распространенные часовые пояса: `UTC`, `Europe/London`, `America/New_York`, `Asia/Tokyo`. См. [полный список](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
**Несовпадающие часовые пояса приведут к неверным датам и временным меткам счетов.**
### Первоначальная настройка администратора
При первом запуске приложение перенаправит на `/setup` для создания начальной учетной записи администратора. Это необходимо до доступа к остальному функционалу.
**Чтобы сбросить и создать новую учетную запись администратора (экстренное восстановление):**
1. Остановите приложение
2. Очистите таблицы `users` и `api_keys` в базе данных
3. Перезапустите приложение
4. Пройдите `/setup` снова
**Рекомендации по безопасности:**
- Используйте надежный пароль для учетной записи администратора
- Установите подсказку пароля (необязательно, но рекомендуется)
- Генерируйте отдельные API-ключи для разных окружений (dev, staging, prod)
- Отзывайте неиспользуемые API-ключи
## Безопасность и лучшие практики
**Архитектура аутентификации:**
- **Доступ к UI:** Аутентификация по сессии с form login
- **Доступ к API:** Stateless Bearer token аутентификация (API-ключи)
- **Пароли:** BCrypt с 10 раундами
- **API-ключи:** UUID-формат, хешируются BCrypt, показываются один раз при генерации
**Время сессии:**
По умолчанию сессии UI истекают после **30 минут неактивности** (по умолчанию для Spring Boot/Tomcat). Пользователи будут автоматически выведены из системы и перенаправлены на страницу входа.
Чтобы настроить время сессии, добавьте следующее в `nullInvoice/src/main/resources/application.yml`:
```yaml
server:
servlet:
session:
timeout: 60m # Варианты: 15m, 30m, 1h, 2h, и т.д.
```
Распространенные значения timeout:
- `15m` - 15 минут (более строгая безопасность)
- `30m` - 30 минут (по умолчанию)
- `1h` - 1 час (удобство для активных пользователей)
- `8h` - 8 часов (расширенное для длительных сессий)
**Чек-лист для продакшн:**
- Включить HTTPS/TLS для всех соединений
- Использовать надежный пароль администратора
- Создавать отдельные API-ключи для каждого приложения/окружения
- Развертывать за firewall или VPN
- Настроить rate limiting на уровне reverse proxy
- Настроить корректное логирование и мониторинг
- Регулярно проверять использование API-ключей (timestamp последнего использования)
- Немедленно отзывать неиспользуемые или скомпрометированные API-ключи
- Хранить API-ключи в переменных окружения, никогда в коде
- Использовать безопасное хранение секретов (HashiCorp Vault, AWS Secrets Manager и др.)
**Защита CSRF:**
- Включена для всех UI-форм
- Отключена для API-эндпоинтов (stateless Bearer token auth)
**Безопасность при первом запуске:**
- Приложение недоступно, пока не будет создана учетная запись администратора через `/setup`
- Страница настройки доступна только если администратор еще не создан
- После настройки требуется вход для всех функций
## Docker Compose
**Официальные Docker-образы:**
Готовые образы доступны на [Docker Hub](https://hub.docker.com). Найдите официальный образ nullInvoice, чтобы пропустить шаг сборки.
Сборка без кеша:
```bash
docker compose build --no-cache
```
Запуск стека:
```bash
docker compose up -d
```
Остановка стека:
```bash
docker compose down
```
## Локальная разработка (без Docker)
### Настройка
1. Убедитесь, что MariaDB запущена, и создайте базу данных. См. [Настройка базы данных](#настройка-базы-данных)
2. Сборка Tailwind CSS (нужно перед первым запуском):
```bash
./build-tailwind.sh
```
3. Сборка проекта с Maven:
```bash
cd nullInvoice
mvn clean package
```
4. Запуск приложения:
```bash
java -jar target/nullinvoice-0.0.1-SNAPSHOT.jar
```
### Использование NetBeans IDE
Проект собран в NetBeans и может быть открыт как Maven-проект с плагином Spring Boot.
**Настройка переменных окружения в NetBeans:**
Вариант 1 - через IDE:
1. Правой кнопкой по проекту >> Properties
2. Перейдите в Actions >> Run
3. Укажите переменные окружения: `APP_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`
Вариант 2 - редактирование напрямую:
- Измените `nbactions.xml` в корне проекта
## Начало работы
### Настройка при первом запуске
При первом доступе вы будете перенаправлены на `/setup` для создания начальной учетной записи администратора:
1. Перейдите на `http://localhost:8080` (или ваш настроенный URL)
2. Вы будете автоматически перенаправлены на `/setup`
3. Создайте учетную запись администратора с именем пользователя, паролем и необязательной подсказкой
4. После настройки вы будете перенаправлены на `/login`
**Вход:**
- Откройте страницу входа `/login`
- Используйте учетные данные администратора, которые вы создали
- Подсказка пароля доступна через иконку info (если настроена)
**Панель администратора:**
После входа получите доступ к панели администратора из меню пользователя:
- Изменить пароль администратора
- Сгенерировать API-ключи для доступа к REST API
- Отозвать API-ключи
- Просмотреть использование API-ключей (timestamp последнего использования)
### Настройка первого поставщика
**Предварительные условия:**
- Завершена первоначальная настройка (учетная запись администратора создана)
- Вход с учетными данными администратора
Прежде чем вы сможете генерировать счета через API, необходимо настроить хотя бы одного поставщика через веб-интерфейс. Поставщики определяют данные компании, локаль, валюту, ставки налогов, пользовательский брендинг (через шаблоны) и нумерацию счетов для генерации.
1. Войдите в веб-интерфейс по адресу `http://localhost:8080` (или на вашем порту)
2. Перейдите в Поставщики
3. Создайте нового поставщика с данными компании
4. Настройте локаль, валюту и налоговые параметры
5. Установите параметры нумерации счетов (префикс, padding)
6. Запишите ID поставщика для API-интеграции
7. Сгенерируйте API-ключ в Администратор > API-ключи для доступа к REST API
**Для примеров настройки поставщика см. `example-images` - `en-us` для примера США или не ЕС; и `eu-de` для примера ЕС.**
После настройки вы можете создать XHTML-шаблон и использовать ID поставщика (показан в меню редактирования поставщика), чтобы выполнять вызовы к `/api/v1/invoices/generate`.
## Возможности
- Управление поставщиками и клиентами с soft delete и проверками уникальности.
- Создание и управление шаблонами счетов с пользовательским брендингом, с глобальным шаблоном по умолчанию и по поставщику.
- Генерация счетов с сохраненными HTML-снимками в записи счета.
- Рендеринг счетов в PDF по запросу.
- Поиск и сортировка счетов по номеру, дате, клиенту, поставщику и статусу.
- Управление статусом счета как `unpaid` или `issued` (оплачен/финальный).
## Жизненный цикл и статус счетов
- Значения статуса: `unpaid` и `issued`. `issued` считается оплаченным и финальным.
- Создание счета через API всегда приводит к статусу `issued`, и API не принимает переопределения статуса.
- Создание счета через UI может пометить счет как `unpaid` только при установленном сроке оплаты.
- Неоплаченные счета можно пометить как `issued` на странице деталей счета.
- Выставленные счета нельзя вернуть в `unpaid`.
## Поведение UI
- `/invoices/new` создает счета с использованием поставщика, выбранного из cookie (если есть).
- Переключатель `unpaid` отключен, пока не задан срок оплаты.
- `/invoices` поддерживает фильтрацию по поставщику (dropdown) и поиск по номеру, дате или клиенту. Поиск по дате принимает ISO (`YYYY-MM-DD`) или `dd.MM.yyyy`.
- Список счетов поддерживает сортировку по статусу; сортировка по статусу переключает порядок `unpaid` и `issued`.
- `/invoices/{invoiceNumber}` показывает статус, итоги, сохраненный HTML-предпросмотр и предоставляет одностороннее действие «Отметить как оплаченный» для неоплаченных счетов.
## Рабочий процесс UI
1) Поставщики: сначала настройте данные поставщика. Профиль поставщика определяет локаль, валюту, нумерацию счетов и ставку налога по умолчанию.
2) Шаблоны: создайте шаблон для пользовательского брендинга и установите его по умолчанию. Используйте глобальный шаблон по умолчанию или задайте шаблон по поставщику, чтобы переопределить глобальный выбор.
3) Клиенты (опционально): клиентов можно добавлять вручную, но генерация счетов также создает/обновляет клиентов из введенных данных.
4) Выбор активного поставщика: выберите поставщика по умолчанию в UI, что задает cookie, используемую при создании счетов.
5) ID поставщика для API: откройте поставщика в режиме редактирования и используйте ID, показанный в левом верхнем углу страницы.
6) Счета: список, поиск и фильтрация счетов; откройте счет, чтобы посмотреть детали и отметить неоплаченные счета как выставленные/оплаченные.
7) Генерация счета: введите данные клиента или найдите существующего клиента, добавьте позиции и задайте налог для каждой позиции. Если позиция без налога, применяется ставка поставщика по умолчанию.
8) Скидки и примечания: введите фиксированную скидку или используйте калькулятор % скидки; добавьте примечания и сгенерируйте счет, чтобы увидеть страницу обзора.
Генерация счетов использует пессимистичную блокировку записи поставщика, чтобы избежать гонок при вычислении следующего номера счета. Это блокирует параллельные запросы для одного и того же поставщика, пока номер не назначен.
## Аутентификация
**Требуется аутентификация:** Все API-эндпоинты требуют:
- Bearer token в заголовке `Authorization` (рекомендуется для интеграций)
- Активную сессию (если вы вошли через веб-интерфейс)
### Аутентификация по API-ключу (рекомендуется для внешних интеграций)
Сгенерируйте API-ключ в панели администратора и укажите его в заголовке `Authorization`:
```bash
curl -H "Authorization: Bearer YOUR_API_KEY_HERE" \
http://localhost:8080/api/v1/invoices
```
### Сессионная аутентификация (для запросов, инициированных UI)
Если вы вошли через веб-интерфейс, ваша сессия автоматически используется для API-запросов.
**Генерация API-ключа:**
1. Войдите в веб-интерфейс
2. Перейдите в Администратор (меню пользователя)
3. Прокрутите до раздела "API-ключи"
4. Введите необязательное описание и нажмите "Сгенерировать ключ"
5. **Скопируйте ключ сразу** - он не будет показан повторно
6. Ключ отображается в формате `Authorization: Bearer {key}`
**Примечания по безопасности:**
- API-ключи хранятся в базе данных в виде хеша (BCrypt)
- Ключи можно отозвать в любое время из панели администратора
- Для каждого ключа отслеживается timestamp последнего использования
- Генерируйте отдельные ключи для разных приложений/окружений
## REST API (База: `/api/v1`)
### Генерация счетов
`POST /api/v1/invoices/generate`
**Требуется аутентификация** - укажите Bearer token в заголовке `Authorization`.
- Требуются `supplier_id` и `client`.
- `response_type` поддерживает `number` (по умолчанию) или `pdf`.
- `number`: возвращает JSON только с метаданными счета
- `pdf`: возвращает PDF напрямую с метаданными в заголовках ответа
- Статус всегда `issued` для счетов, созданных через API.
Пример запроса (существующий клиент по id):
```bash
curl -X POST http://localhost:8080/api/v1/invoices/generate \
-H "Authorization: Bearer YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"response_type": "number",
"supplier_id": 1,
"client": { "id": 42 },
"items": [
{ "description": "Consulting", "quantity": 1, "unit_price": 1000, "tax_rate": 0.2 }
],
"issue_date": "2026-01-16",
"due_date": "2026-01-30",
"currency_code": "EUR",
"notes": "Thank you"
}'
```
Пример запроса (новые данные клиента):
```bash
curl -X POST http://localhost:8080/api/v1/invoices/generate \
-H "Authorization: Bearer YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"response_type": "number",
"supplier_id": 1,
"client": {
"name": "Client Co",
"addressLine1": "2 Side St",
"city": "Burgas",
"country": "BG",
"taxId": "123",
"vatId": "BG123"
},
"items": [
{ "description": "Consulting", "quantity": 1, "unit_price": 1000, "tax_rate": 0.2 }
]
}'
```
Пример ответа (response_type: number):
```json
{
"status": "issued",
"message": "invoice generated",
"invoiceNumber": "INV-000001",
"issueDate": "2026-01-16"
}
```
Пример запроса (response_type: pdf для прямой загрузки PDF):
```bash
curl -X POST http://localhost:8080/api/v1/invoices/generate \
-H "Authorization: Bearer YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"response_type": "pdf",
"supplier_id": 1,
"client": {
"name": "Client Co",
"addressLine1": "2 Side St",
"city": "Plovdiv",
"country": "BG",
"taxId": "123",
"vatId": "BG123"
},
"items": [
{ "description": "Consulting", "quantity": 1, "unit_price": 1000, "tax_rate": 0.2 }
]
}' \
-o invoice.pdf -i
```
Пример ответа (response_type: pdf):
```
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="INV-000001.pdf"
X-Invoice-Number: INV-000001
X-Invoice-Status: issued
X-Invoice-Issue-Date: 2026-01-16
[PDF binary data]
```
PDF-ответ включает метаданные счета в пользовательских заголовках ответа (`X-Invoice-Number`, `X-Invoice-Status`, `X-Invoice-Issue-Date`), позволяя вашему приложению сохранять детали счета, одновременно получая PDF напрямую.
### Список и фильтрация счетов
`GET /api/v1/invoices`
**Требуется аутентификация** - укажите Bearer token в заголовке `Authorization`.
- Опциональный фильтр: `status=unpaid` или `status=issued`
Пример запроса:
```bash
curl -H "Authorization: Bearer YOUR_API_KEY_HERE" \
http://localhost:8080/api/v1/invoices?status=unpaid
```
Пример ответа (фильтр):
```json
[
{ "invoiceNumber": "INV-000002", "status": "unpaid" }
]
```
### Получение счета
**Требуется аутентификация** - укажите Bearer token в заголовке `Authorization`.
Получение метаданных счета:
```bash
curl -H "Authorization: Bearer YOUR_API_KEY_HERE" \
http://localhost:8080/api/v1/invoices/INV-000001
```
Скачать PDF счета:
```bash
curl -H "Authorization: Bearer YOUR_API_KEY_HERE" \
http://localhost:8080/api/v1/invoices/INV-000001/pdf \
-o invoice.pdf
```
### Асинхронная очередь генерации счетов
Альтернатива `POST /api/v1/invoices/generate` для вызывающих, которые не хотят блокироваться в ожидании синхронной генерации. Запрос сохраняется в очередь, фоновый обработчик создаёт счёт, а клиенты опрашивают статус.
Оба пути проходят через одну и ту же блокировку строки поставщика, поэтому номера счетов остаются последовательными между синхронным и асинхронным путём.
#### Отправка запроса на генерацию
`POST /api/v1/invoice-requests`
**Требуется аутентификация** - укажите Bearer token в заголовке `Authorization`.
Тело запроса идентично `POST /api/v1/invoices/generate`. Поле `response_type` игнорируется - асинхронные запросы только сохраняют строку в очереди; после завершения извлеките счёт через стандартные endpoint'ы с использованием `invoiceNumber`, возвращённого в ответе о статусе.
```bash
curl -X POST http://localhost:8080/api/v1/invoice-requests \
-H "Authorization: Bearer YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"supplier_id": 1,
"client": { "id": 42 },
"items": [
{ "description": "Consulting", "quantity": 1, "unit_price": 1000, "tax_rate": 0.2 }
]
}'
```
Ответ (201 Created):
```json
{
"requestId": 42,
"status": "PENDING",
"message": "invoice generation request queued",
"createdAt": "2026-05-17T10:00:00"
}
```
#### Проверка статуса запроса
`GET /api/v1/invoice-requests/{requestId}`
```bash
curl -H "Authorization: Bearer YOUR_API_KEY_HERE" \
http://localhost:8080/api/v1/invoice-requests/42
```
Ответ при `COMPLETED`:
```json
{
"requestId": 42,
"status": "COMPLETED",
"invoiceId": 107,
"invoiceNumber": "INV-000042",
"attempts": 1,
"createdAt": "2026-05-17T10:00:00",
"completedAt": "2026-05-17T10:00:02"
}
```
#### Жизненный цикл статусов
```
новый запрос -> PENDING
обработчик успешно завершил -> COMPLETED
обработчик выбросил ошибку -> PENDING (будет повторная попытка)
attempts >= max_attempts -> FAILED (терминальное состояние)
```
`PENDING` - единственное состояние, допускающее повторную обработку. Сбои во время обработки откатываются полностью и не расходуют бюджет повторов. После `COMPLETED` извлеките счёт через `GET /api/v1/invoices/{invoiceNumber}` (JSON) или `/api/v1/invoices/{invoiceNumber}/pdf` (PDF).
Обработчик можно отключить через переменную окружения `QUEUE_ENABLED=false`.
### Стороны
**Требуется аутентификация** - укажите Bearer token в заголовке `Authorization`.
- `GET /api/v1/parties/client?taxId=...&vatId=...` (нужно одно из taxId/vatId)
- `GET /api/v1/parties/clients/search?q=...` (минимум 2 символа)
- `GET /api/v1/parties/suppliers`
Пример:
```bash
curl -H "Authorization: Bearer YOUR_API_KEY_HERE" \
http://localhost:8080/api/v1/parties/suppliers
```
### Health
- `GET /api/v1/health` (аутентификация не требуется)
## Шаблоны и PDF
- Шаблоны находятся в `invoice_templates` и должны включать HTML-контент.
- Генерация счетов требует действующего шаблона по умолчанию (по поставщику или глобального).
- Поставщики могут переопределять глобальный шаблон по умолчанию на шаблон поставщика.
- Сгенерированные счета сохраняют HTML-снимок для последовательного повторного рендеринга.
- PDF рендерятся из сохраненного HTML-снимка, когда он доступен.
### Требования к формату шаблона
Шаблоны должны быть в формате **XHTML** для корректного PDF-рендеринга:
- Включите XML-декларацию: ``
- Используйте XHTML namespace: ``
- Весь CSS должен быть **inline** внутри тега `