1. Сохранение шаблона в кабинете
sequenceDiagram
participant U as Пользователь
participant E as Редактор GrapesJS
participant A as FastAPI
participant DB as PostgreSQL
U->>E: Редактирует шаблон
E->>A: Сохраняет HTML/CSS и project JSON
A->>DB: Обновляет templates
DB-->>A: OK
A-->>E: Шаблон сохранён
Шаблон остаётся редактируемым. Сохранение не меняет созданные ранее generated_pages.
2. Внешнее создание страницы через API v2
sequenceDiagram
participant X as Внешняя система
participant A as API v2
participant T as Template engine
participant B as Billing
participant DB as PostgreSQL
X->>A: POST /api/v2/pages + X-Api-Key
A->>A: Разбирает multipart: template_id, data.*, rows.*
A->>DB: Проверяет ключ и шаблон своей организации
A->>T: Проверяет маски, рендерит текст и Table v2
T-->>A: rendered snapshot
A->>DB: Создаёт generated_page
A->>B: Списывает стоимость создания КП
B->>DB: Пишет транзакцию
A-->>X: page_id, url, pdf_url, page_slug
API v2 принимает только multipart/form-data. Простые маски передаются как data.<маска>, строки Table v2 — как rows.<маска>. Внешняя спецификация живёт в разделе API.
3. Открытие публичной страницы
sequenceDiagram
participant C as Клиент
participant P as Public page route
participant DB as PostgreSQL
participant W as Webhook получателя
C->>P: GET /{public_token}
P->>DB: Находит snapshot по public_token
P->>DB: Увеличивает open_count, создаёт событие
P->>DB: Списывает платное открытие после лимита, если требуется
P-->>C: Внешняя страница + iframe со snapshot
P->>W: POST page.opened, если webhook настроен
Открытие учитывается до отправки HTML. Если для платного открытия не хватает баланса, сервис возвращает техническую страницу с HTTP 402 вместо содержимого КП.
4. Скачивание PDF
sequenceDiagram
participant C as Клиент
participant P as PDF route
participant DB as PostgreSQL
participant W as WeasyPrint
C->>P: GET /{public_token}/download.pdf
P->>DB: Находит snapshot и пишет pdf_download event
P->>W: Генерирует PDF из snapshot HTML
W-->>P: PDF bytes
P-->>C: attachment download
PDF не хранится в БД и не кладётся в постоянное файловое хранилище. Имя файла строится из нормализованных названия организации и шаблона.
5. Результат по исходной сущности
Внешняя система может отправить POST /api/v2/entity-results. Сервис ищет все страницы текущей организации с совпадающим source_entity_id и обновляет их result_status. Это поддерживает связь «одна CRM-сущность — несколько КП».
6. Заявка с лендинга
Публичная форма «Подобрать подрядчика для внедрения» отправляет JSON в /api/implementation-requests. Сервер валидирует имя, компанию, телефон, email, ИНН, число менеджеров и ожидаемое число КП, затем сохраняет запись в implementation_requests.
Сейчас заявка только сохраняется. Автоматическая пересылка в Telegram, CRM или почту намеренно не включена.