Минимальная модель франчайзинга¶
Статус документа¶
Документ фиксирует согласованную целевую модель. В модуле franchise реализованы Franchisee и
LegalEntity. Для модуля restaurant создан каркас и публичная граница API; его доменная модель
ещё не реализована. Реализация выполняется поэтапно; после каждого этапа результат принимается
отдельно.
Основная модель¶
Связи:
Franchisee 1 ── N LegalEntity
LegalEntity 1 ── N IikoConnection
IikoConnection 1 ── N IikoOrganization
IikoOrganization 1 ── 0..1 Restaurant
Термины:
- ГК — головная компания, управляющая франчайзинговой сетью.
- Франчайзи — партнёр ГК. Он может работать через одно или несколько юридических лиц.
- Юридическое лицо — сторона договора с iiko и будущий продавец при оформлении заказа.
- iiko-подключение — техническое подключение к аккаунту iiko, принадлежащее конкретному юридическому лицу.
- Организация iiko — импортированный внешний объект внутри iiko-подключения.
- Ресторан — самостоятельная сущность платформы, доступная витрине после активации.
Роли сотрудников, Админка ГК и Личный кабинет франчайзи описаны в модели административного доступа. Единая для всей сети учётная запись покупателя и изоляция покупательских данных между ФЧ описаны в модели доступа покупателей.
Границы модулей¶
Модель разделяется на три модуля.
franchise¶
Модуль отвечает на вопрос: кто является партнёром ГК и через какие юридические лица он работает?
Владеет:
- франчайзи;
- юридическими лицами;
- их статусами и реквизитами;
- в будущем — договорами, сотрудниками франчайзи и политиками доступа.
Не знает об организациях iiko, импорте меню и жизненном цикле ресторанов.
iiko¶
Модуль отвечает за подключение к iiko Transport и зеркало внешних данных.
Владеет:
- iiko-подключениями;
- учётными данными подключений (
apiLogin, хранится зашифрованным, см. «Секреты юридического лица»); - токенами и HTTP-взаимодействием;
- импортированными организациями;
- в будущем — терминалами, меню и стоп-листами.
Каждое подключение содержит legal_entity_id, указывающий владельца. Модуль iiko не читает
таблицы модуля franchise напрямую.
restaurant¶
Модуль отвечает за представление ресторана на платформе и его жизненный цикл.
Владеет:
- рестораном;
- публичным названием и
slug; - адресом и часовым поясом;
- статусом публикации;
- неизменяемой привязкой к организации iiko;
- операциями активации, временного отключения и окончательного закрытия.
Зависимости модулей¶
storefront ──▶ restaurant
storefront ──▶ catalog
storefront ──▶ customer::api
restaurant ──▶ franchise::api
restaurant ──▶ iiko::api
admin ──▶ franchise::api, restaurant::api, iiko::api
Правила:
franchiseне зависит отrestaurantиiiko;iikoне зависит отrestaurant;- модули не читают таблицы друг друга;
- проверки и команды выполняются через публичные интерфейсы;
- обратная связь между
iikoиrestaurantвыполняется событиями.
Изменение статуса ФЧ публикуется как FranchiseeStatusChanged. Это событие является сигналом для
зависящих модулей немедленно пересчитать операционную доступность принадлежащих ему данных.
Административный экран может показывать единое дерево
франчайзи → юридическое лицо → подключение → организация → ресторан, хотя данные принадлежат
разным модулям. Представление интерфейса не определяет границы модулей.
Минимальные сущности¶
Franchisee¶
LegalEntity¶
Одно юридическое лицо принадлежит ровно одному франчайзи. Один франчайзи может иметь несколько юридических лиц. Модель также покрывает частный случай «один франчайзи — одно юридическое лицо».
IikoConnection¶
Подключение принадлежит юридическому лицу, поскольку договор с iiko заключается от конкретного
юридического лица. На этапе ручного тестирования apiLogin временно хранится в базе открытым
текстом, но не публикуется через API и не пишется в логи. До production отдельным обязательным
этапом добавляется прикладное шифрование с мастер-ключом из Kubernetes/Docker Compose Secret.
Общие требования ко всем секретам юрлица — в разделе «Секреты юридического лица».
IikoOrganization¶
Внешний идентификатор организации уникален в паре с connection_id.
Restaurant¶
id
iiko_connection_id
iiko_organization_id
name
slug
address
timezone
status
closed_at
close_reason
created_at
updated_at
Пара (iiko_connection_id, iiko_organization_id) уникальна. Юридическое лицо и франчайзи
ресторана однозначно определяются по цепочке:
legal_entity_id и franchisee_id не дублируются в ресторане, чтобы не создавать противоречивые
источники истины.
Создание подключения и импорт организаций¶
Согласованный сценарий:
- Сотрудник ГК создаёт франчайзи.
- Внутри франчайзи создаёт юридическое лицо.
- Для юридического лица создаёт одно или несколько iiko-подключений.
- Модуль
iikoимпортирует организации каждого подключения. - Для новой организации публикуется событие.
- Модуль
restaurantсоздаёт ресторан в статусеDRAFT, если привязки ещё нет. - Сотрудник ГК проверяет данные, заполняет обязательные настройки и активирует ресторан.
Franchisee
↓
LegalEntity
↓
IikoConnection
↓ import
IikoOrganization
↓ event
Restaurant(DRAFT)
↓ approval by ГК
Restaurant(ACTIVE)
Модуль iiko публикует событие с connectionId и organizationId, но ничего не знает о созданном
ресторане. Обработчик события находится в модуле restaurant.
Жизненный цикл ресторана¶
Минимальные статусы:
| Статус | Значение |
|---|---|
DRAFT |
создан после импорта, ещё не доступен витрине |
ACTIVE |
работает и доступен витрине |
DISABLED |
временно отключён и может быть восстановлен |
CLOSED |
закрыт окончательно, терминальный статус |
Переходы:
Ресторан в статусе CLOSED нельзя активировать повторно.
Исчезновение организации из ответа iiko не закрывает ресторан автоматически. Это может быть ошибка или временная недоступность внешней системы. В такой ситуации ресторан становится технически недоступным, но окончательное закрытие выполняется отдельной командой сотрудника ГК.
Операционная блокировка владельцем¶
Собственный статус ресторана не перезаписывается при отключении ФЧ. Эффективная доступность вычисляется из собственного статуса и операционных блокировок:
restaurant.status = ACTIVE
operational blocks = [FRANCHISEE_DISABLED]
effective availability = UNAVAILABLE
Модуль restaurant в будущем обрабатывает FranchiseeStatusChanged и устанавливает либо снимает
FRANCHISEE_DISABLED. Поэтому после повторной активации ФЧ восстанавливаются только рестораны,
которые были активны сами по себе; DRAFT, вручную DISABLED и CLOSED не меняют свой статус.
Плановый импорт iiko выполняется только если одновременно активны подключение, его юридическое
лицо и ФЧ. Неактивный владелец приводит к пропуску запуска, а не к ошибке импорта. Конкретный
контракт пакетной проверки операционной активности будет согласован на этапе LegalEntity.
Неизменяемость привязки¶
После создания ресторана его привязка неизменяема:
Запрещены:
- перенос ресторана на другое iiko-подключение;
- замена организации iiko;
- перенос ресторана к другому юридическому лицу;
- восстановление окончательно закрытого ресторана.
Юридическое лицо ресторана определяется подключением iiko. Изменение подключения фактически означало бы смену юридического лица и создание другого операционного объекта.
Смена юридического лица¶
При смене юридического лица ресторан не переносится:
- Старый ресторан закрывается и получает статус
CLOSED. - Для нового юридического лица создаётся или выбирается другое iiko-подключение.
- Импортируется другая организация iiko.
- Создаётся новый ресторан с новым
restaurant.idи отдельным жизненным циклом.
Старый и новый рестораны никак не связываются в модели. Поля replaces_restaurant_id или другие
ссылки преемственности не создаются. С юридической и операционной точки зрения это разные объекты.
Старый ресторан остаётся в системе ради собственной истории заказов, платежей, чеков и отчётности.
Адрес¶
Старый и новый рестораны могут иметь одинаковый адрес, но адрес является только атрибутом:
Совпадение адресов не создаёт связь и не означает преемственность. Адрес нельзя использовать как идентификатор, поскольку он может быть исправлен, нормализован или совпадать у нескольких объектов.
Секреты юридического лица¶
Секретами юрлица являются apiLogin iiko, shopId и секретный ключ ЮKassa, доступ к данным
оператора фискальных данных. Требования ко всем:
- хранятся в БД, зашифрованные прикладным шифрованием с мастер-ключом из Kubernetes/Docker Compose Secret;
- после сохранения не отображаются, не возвращаются через API и не попадают в логи и аудит;
- доступны только внутренним компонентам интеграции: transport и импорту iiko, созданию платежа, получению чеков;
- вводятся только сотрудником ФЧ в Личном кабинете; сотрудник ГК секреты ФЧ не видит;
- в переменных окружения остаются только мастер-ключ и учётные данные глобальных сервисов ГК.
До реализации шифрования боевые учётные данные не используются и доступ к таблицам с секретами никому не выдаётся.
Денежный контур¶
Платформа не участвует в расчётах (см. Франшиза). Для реализации это означает:
- продавец определяется по ресторану корзины до создания платежа; платёж создаётся по реквизитам ЮKassa юрлица этого ресторана, после чего ресторан заказа не меняется;
- платформа хранит платёж и его статусы, но не деньги; возврат инициируется в Личном кабинете ФЧ, исполняется в ЮKassa ФЧ, а платформа хранит основание, сумму и статус;
- чек платформа не пробивает. Для показа покупателю чек ищется в данных ОФД касс ресторана заказа по сумме, признаку оплаты, времени в окне вокруг заказа и полному составу, включая модификаторы. Привязывается только единственный полностью совпавший чек; при нескольких одинаковых не привязывается ничего. Если iiko отдаёт фискальные реквизиты закрытого заказа, сопоставление по составу и доступ к ОФД станут не нужны;
- реестры онлайн-канала строятся по данным платформы; выручку из iiko платформа не выгружает и не хранит.
Поэтапная реализация¶
Каждый этап реализуется и принимается отдельно. Переход к следующему этапу выполняется только после явного подтверждения результата предыдущего.
- Выполнено: создать каркас Spring Modulith-модуля
franchiseи его публичный API без бизнес-сущностей. - Выполнено: реализовать
Franchisee: миграцию, доменную модель, операции и тесты. - Выполнено: реализовать
LegalEntity: принадлежность франчайзи, операции, проверки и тесты. - Выполнено: создать каркас Spring Modulith-модуля
restaurantи его публичный API. - Выполнено: добавить владельца
legal_entity_idдляiiko.connection, доменный сценарий создания и проверку юридического лица черезfranchise::api. - Выполнено: добавить HTTP ownership-сценарии создания, списка и отключения подключений в
franchiseofficeбез раскрытияapiLogin. - После ручного тестирования зашифровать
apiLoginс мастер-ключом из Secret. - Реализовать
Restaurant, его статусы, неизменяемую привязку и закрытие. - Добавить событие появления организации iiko и автоматическое создание ресторана
DRAFT. - Добавить публичное read-only API активных ресторанов для витрины.
На каждом этапе обновляются соответствующая документация модуля и этот архитектурный документ.