B2B-кабинет часто начинают проектировать с привычной фразы: «клиент заходит в личный кабинет и оформляет заказ». Проблема в том, что в B2B слово «клиент» почти всегда означает сразу несколько разных объектов.

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

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

Главный принцип: аккаунт, заказчик, юрлицо и плательщик — это разные сущности. Их можно осознанно схлопнуть в простом MVP, но нельзя проектировать так, будто различий не существует.

Пять слоёв B2B-клиента

Главная причина путаницы — слово «клиент». В B2B оно может описывать минимум пять уровней.

Слой Что это За что отвечает
Аккаунт Учётная запись человека: телефон, email, пароль, ФИО Авторизация и персональная идентификация
Членство Связь «человек работает от имени заказчика» Роли, права, доступ к юрлицам и адресам
Заказчик Бизнес-субъект: компания, дилер, лаборатория, клиника, филиальная группа Коммерческие условия, сегмент, менеджер, история отношений
Юрлицо ООО, ИП или другое лицо с реквизитами Договоры, счета, закрывающие документы
Плательщик Юрлицо, на которое выставлен счёт по конкретному заказу Финансовая ответственность в заказе
Пять слоёв B2B-клиента: человек, роль, заказчик, юрлицо и плательщик как связанные уровни

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

Но как только появляются сотрудники, филиалы, разные договоры или оплата через управляющую компанию, прямой user.customer_id превращается в ограничение. Правильнее мыслить через связь account ↔ customer, то есть через членство.

Базовая схема сущностей

Универсальная модель B2B-кабинета строится вокруг заказа. Заказ фиксирует, кто оформил действие, от имени какого заказчика оно выполнено, какое юрлицо участвует, по какому договору и кто платит.

Минимальный набор сущностей:

Сущность Назначение
Account Человек и его учётная запись
Membership Роль человека у конкретного заказчика
Customer Заказчик как коммерческая единица
LegalEntity Юрлицо с реквизитами
Contract Договор и условия оплаты
DeliveryAddress Адрес доставки и контакт на точке
Order Заказ, его состав, статусы и снапшоты
Invoice Счёт на оплату
Payment Поступившие оплаты
Document УПД, накладные, акты, счета-фактуры
PriceType Тип цены и правила расчёта

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

Где должны жить роли

Роль сотрудника нельзя надёжно хранить только в аккаунте. Один и тот же человек может быть администратором в одной организации и наблюдателем в другой. Поэтому роль живёт на связи между аккаунтом и заказчиком.

Типовая матрица ролей:

Роль Что может делать
Владелец или админ Управляет сотрудниками, ролями, доступами, настройками
Закупщик Собирает корзину, оформляет заказы, видит цены и остатки
Бухгалтер Видит счета, оплаты, закрывающие документы, акты сверки
Получатель или наблюдатель Видит заказы, доставки, статусы, но не меняет коммерческие данные

Даже если в MVP есть только один владелец, таблицу членств стоит заложить сразу. Добавить роли позже намного проще, если авторизация уже понимает связь account ↔ customer.

Если бизнес говорит «у нас пока один пользователь на компанию», это не всегда означает, что можно делать прямой FK из пользователя в компанию. Часто «пока» заканчивается в первой же итерации после запуска, когда клиент просит добавить бухгалтера или закупщика.

Заказчик и юрлицо — не одно и то же

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

Юрлицо — это носитель реквизитов. У него есть ИНН, КПП, ОГРН, юридический адрес, банковские реквизиты, договоры и документы.

Иногда заказчик и юрлицо совпадают. Например, небольшой партнёр работает от одного ООО, платит сам за себя и не использует филиальную структуру. Но у сетевых клиентов, дилеров, групп компаний и B2B-порталов для сложной дистрибуции это быстро перестаёт быть правдой.

Полезное правило:

Вопрос Если ответ «нет» Если ответ «да»
У клиента бывает несколько юрлиц? Можно схлопнуть заказчика и юрлицо Нужна связь Customer 1:N LegalEntity
Платит иногда не то юрлицо, которое заказывает? Плательщик равен юрлицу заказа Нужен payer_entity_id в заказе
С кабинетом работает несколько людей? Можно начать с одного владельца Нужны Membership и роли
Один человек представляет несколько клиентов? Достаточно одного активного заказчика Нужен переключатель заказчика и M:N-связь
У юрлица бывает несколько договоров? Условия можно хранить на заказчике Нужен Contract и выбор договора

Что фиксировать в заказе

Заказ — это исторический документ. Он не должен меняться задним числом из-за правки адреса, реквизитов или прайса.

Поэтому в заказе нужны не только ссылки, но и снапшоты:

Отдельно стоит хранить quantity_requested и quantity_actual: сколько клиент заказал и сколько реально собрано или отгружено. Для B2B с дефицитом склада, частичной сборкой и заменами это критичнее, чем кажется на старте.

Финансы лучше не считать в кабинете

Если у компании уже есть 1С, ERP или CRM, кабинет обычно не должен становиться самостоятельной финансовой системой. Он создаёт заказы и показывает данные, но источником истины по оплатам, счетам, актам и задолженности остаётся учётная система.

Интеграционный контур B2B-кабинета: портал, CRM, 1С, склад, документы и статусы

Практичная модель:

Так кабинет не спорит с мастер-системой и не создаёт расхождения в деньгах.

Сквозные паттерны, которые стоит заложить

Эти решения обычно окупаются даже в умеренно сложных B2B-проектах.

  1. external_id на синхронизируемых сущностях. Если объект приходит из 1С или CRM, у него должен быть внешний идентификатор и уникальный индекс.
  2. Очередь обмена. Заказ не должен теряться, если 1С временно недоступна. Нужны ретраи и понятный статус синхронизации.
  3. Заявки вместо прямого редактирования. Реквизиты, отмены, новые сотрудники и изменения профиля лучше проводить через request-сущности со статусами.
  4. Деактивация вместо удаления. Всё, на что ссылаются заказы и документы, выключается через is_active, а не удаляется.
  5. Раздельные статусы. Статус заказа, оплаты и доставки — разные поля. Заказ может быть собран, но не оплачен; оплачен, но не отгружен.
  6. unknown для внешних статусов. Если 1С пришлёт новый статус, синхронизация не должна падать из-за enum.
  7. Снапшоты для истории. Адреса, цены и реквизиты в заказе должны сохраняться копией.

Как выбрать уровень сложности

Не каждому проекту нужна максимальная модель с M:N-связями во всех местах. Но каждому проекту нужна честная проверка сценариев.

Уровень 1. Простой кабинет

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

Что можно упростить:

Что всё равно стоит заложить:

Уровень 2. Кабинет для компании с несколькими юрлицами

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

Здесь уже нужны:

Уровень 3. Полноценный B2B-портал

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

В этом случае модель должна включать:

Типичные ошибки

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

Хранить роль в аккаунте.
Роль должна зависеть от заказчика. Иначе невозможно нормально поддержать сценарий «один человек — несколько компаний».

Привязать договор к пользователю.
Договор относится к юрлицу, а не к человеку. Пользователь только выбирает доступный договор при заказе.

Не сохранять снапшоты.
Если заказ хранит только ссылку на адрес или реквизиты, правка справочника меняет историю.

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

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

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

Чек-лист проверки модели

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

  1. Новый клиент регистрируется, подтверждает контакт, проходит проверку и активацию менеджером.
  2. Владелец приглашает бухгалтера, который видит счета и документы, но не оформляет заказы.
  3. Один человек работает с двумя компаниями и переключает текущего заказчика.
  4. Заказ оформляется от филиала, а счёт выставляется на головное юрлицо.
  5. В одной корзине товары разделяются на несколько заказов по разным юрлицам.
  6. Юрлицо деактивировали во внешней системе, и кабинет корректно закрывает доступ к заказам от него.
  7. Заказ частично собран: клиент просил 10 единиц, склад подтвердил 7.
  8. Реквизиты изменились в 1С, но старые счета и заказы остались исторически корректными.

Вывод

B2B-кабинет редко ломается из-за того, что в нём мало экранов. Чаще он ломается из-за модели, которая не различает человека, заказчика, юрлицо, договор и плательщика.

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

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