Клиенты
Источник требований
Референс
YumaPOS — customers, customers-list, add-customer
Клиенты — отдельная сущность, не связанная с сотрудниками. Единый пул на уровне франшизы: владелец бренда и все партнёры-франчайзи видят всех клиентов одной франшизы. На POS кассир ищет клиента по телефону и прикрепляет к заказу. В админке ведётся полноценная CRM-карточка с историей заказов.
Эта спека — фундамент для следующих модулей Phase 3 (Loyalty & Marketing): баллы, скидки, промо-кампании, подарочные карты.
Сущность «Клиент»
Нет поля «Номер карты лояльности» в MVP
В YumaPOS у клиента есть отдельный номер карты лояльности (физическая карта). В BR 3.1 это не вводится — появится в BR 3.2 «Баллы лояльности» когда будет Loyalty Service.
Адреса клиента
У одного клиента может быть несколько адресов доставки (дом / работа / родителям). Отдельная сущность — customer_addresses.
| Поле | Обязательность | Описание |
|---|---|---|
| Город | Обязательно | |
| Улица | Обязательно | |
| Квартира | Необязательно | |
| Подъезд | Необязательно | |
| Этаж | Необязательно | |
| Код домофона | Необязательно | |
| Заметки | Необязательно | «Оставить у консьержа», «позвонить за 5 минут» |
| Default | Обязательно | Ровно один адрес на клиента может быть default |
Один default одновременно
При отметке нового адреса как default — предыдущий default автоматически снимается. Переключение в одной транзакции.
Источники регистрации
Откуда клиент попал в систему. Нужно для аналитики источников и для фильтров в списке клиентов.
| Источник | Когда применяется | Доступен в MVP |
|---|---|---|
Админка (admin) | Владелец/партнёр/менеджер завёл клиента вручную | ✅ |
POS (pos) | Кассир завёл через quick-create при заказе | ✅ |
Веб-сайт (web) | Клиент сам зарегистрировался на сайте | ❌ — 3.6 |
Мобильное приложение (mobile) | Клиент сам зарегистрировался в приложении | ❌ — BR 3.5/3.6 |
Импорт (import) | Массовый импорт из Excel | ❌ — отдельная BR |
Операции с клиентом
Создание
Полная форма в админке (Владелец франшизы, Владелец партнёра, или сотрудник с customers.edit):
- Все поля сущности + блок адресов (можно добавить адреса сразу или позже)
- Чекбокс «Клиент дал согласие на обработку ПД» — если отмечен, пишется
consent_signed_at
Quick-форма на POS (Кассир с customers.create_quick):
- Только Телефон и Имя обязательные
- Опционально — Email и Дата рождения
- Остальные поля пустые (можно будет дополнить в админке позже)
- После создания — клиент автоматически прикрепляется к текущему заказу
Редактирование
- Владелец франшизы/партнёра с
customers.editможет менять любые поля кромеid,franchise_id,registered_at,registered_by - Изменение телефона проверяется на уникальность per
franchise_id(см. Бизнес-правила)
Soft delete + анонимизация
Физическое удаление клиента запрещено — есть связи с заказами (FK из orders.customer_id), плюс требования 54-ФЗ по хранению истории транзакций.
При запросе на удаление (кнопка «Удалить» в карточке, доступна только Владельцу франшизы с customers.delete):
deleted_at = now()- PII обезличиваются: имя → «Удалён», фамилия → null, телефон → обезличенный (с сохранением уникальности), email → null, заметки → null, ДР → null
- Адреса очищаются (удаляются записи)
- Связь с заказами через
customer_idсохраняется — история заказов остаётся видна в аналитике, но клиент анонимен
Это удовлетворяет требование ФЗ-152 «право на забвение» — персональные данные удалены, но обезличенная транзакционная история сохранена для учёта.
Объединение дубликатов (merge)
Если клиент случайно завёлся дважды (например, кассир не нашёл по другому формату телефона), есть операция объединения:
- Выбирается целевой клиент (остаётся) и источник (soft-deleted)
- На целевого переносятся: адреса, привязки в заказах
- Источник становится
deleted_at = now()с обезличенными PII - Доступна только Владельцу франшизы через
customers.edit
Отложено в MVP
UI объединения дубликатов (модалка сравнения двух записей, предпросмотр результата) — отдельная BR. В MVP операция доступна только через API для ручных сценариев.
Привязка клиента к заказу (POS flow)
Заказ может быть анонимным (заказ на вынос без привязки) или именным (привязан к клиенту).
Бизнес-flow на POS
- Кассир на экране заказа нажимает «Клиент»
- Появляется модалка поиска: поле для ввода телефона
- По мере ввода система нормализует номер в E.164 (
89991234567→+79991234567) и ищет в базе поfranchise_id - Если найден:
- Показываем карточку-мини: имя + chips групп клиента + LTV (сумма закрытых заказов) + бейдж «ДР скоро» если день рождения в ±14 дней
- Кнопка «Прикрепить» → клиент связывается с текущим заказом
- Если не найден:
- Предложение «Создать нового клиента»
- Quick-форма: телефон (уже введён) + имя (обязательно) + опционально email, ДР
- После создания клиент автоматически прикрепляется к заказу
Что кассир видит на экране заказа после прикрепления
- Имя клиента (крупно, сверху заказа)
- Chips групп клиента
- Кнопка «Открепить клиента» — работает до закрытия заказа
Что кассир ПОКА НЕ видит на POS
- Скидок по клиенту — в MVP BR 3.1 их нет. Появятся в BR 3.3+ (простые скидки), BR 3.4 (промо)
- Баллов лояльности — BR 3.2+
- Подарочных карт, кредитного счёта — BR 3.7/3.8
Список клиентов (в админке)
Колонки
- ФИО (фамилия + имя)
- Телефон
- Дата рождения
- Дата регистрации
- Источник регистрации (chip)
- Группы (chips)
- LTV (сумма всех закрытых заказов клиента)
Фильтры
- По группе
- По источнику регистрации (POS / Админка / …)
- По диапазону даты регистрации
Поиск
- По подстроке ФИО / телефона / email (единое поле поиска — ищет по всем трём)
Сортировка
- По умолчанию — по дате регистрации (новые сверху)
Пагинация
- 20 записей на страницу
Особенности отображения
- Клиенты с
deleted_at IS NOT NULL— не отображаются в основном списке. Отдельного фильтра «Удалённые» в MVP нет
Карточка клиента
Шапка карточки
- Имя + телефон
- Chips групп
- Бейджи (если применимо): «ДР ±14 дней», «Новый клиент» (первый месяц), «Нет заказов»
Вкладки
| Вкладка | Содержимое | Кто видит |
|---|---|---|
| Профиль | Все поля клиента + кнопки «Редактировать» / «Удалить» | Все с customers.read |
| Адреса | Список адресов + CRUD + отметка «Default» | Все с customers.read, редактирование — customers.edit |
| Заказы | История заказов клиента — дата / ТТ / сумма / статус. Фильтр по датам. Ссылка на карточку заказа | Все с customers.read и orders.read |
| Группы | Chips групп. Возможность добавить/убрать из статической группы (для динамических — read-only, рассчитываются по правилам) | Все с customers.read, редактирование — customer_groups.edit |
Сайдбар карточки (информационный блок)
- LTV — сумма закрытых заказов
- Средний чек — LTV / количество заказов
- N заказов — всего закрытых
- Дней с последнего визита — рассчитывается от даты последнего заказа
Отложено в MVP
Вкладки «Баллы», «Транзакции баллов», «Промокоды», «Подарочные карты», «Кредит» появятся в следующих BR Phase 3. В BR 3.1 — только четыре вкладки выше.
Бизнес-правила
- Формат телефона — E.164. На входе (создание, редактирование, поиск) телефон нормализуется:
89991234567,+7 (999) 123-45-67,79991234567→+79991234567. В хранилище — всегда один формат. - Уникальность телефона per франшиза. Два активных клиента с одним телефоном в рамках одного
franchise_idнедопустимы. Если кассир вводит телефон существующего клиента — POS предлагает «Использовать существующего». - Email без уникальности. Один адрес может использоваться несколькими членами семьи, у агентов доставки — один корпоративный адрес.
- Soft delete + анонимизация — единственный способ удаления. Физический DELETE из БД запрещён.
- Один клиент — в нескольких группах. Принадлежность к статическим и динамическим группам не взаимоисключается.
- Клиент — строго в одной франшизе. Между франшизами клиенты не пересекаются. Два разных бренда с одинаковым клиентом хранят две разные записи.
- Frontend-пост-уведомление после quick-create на POS: кассиру показывается «Клиент создан. Убедитесь что согласие на обработку ПД получено». Отметку ставит позже админ в админке — это compliance-паттерн.
Влияние franchises.type на раздел
В отличие от раздела «Юр. лица» (скрытого для type=individual — см. Юридические лица), раздел «Клиенты» виден всегда — и в corporate, и в individual режиме франшизы.
Scope клиентов — по franchise_id, идентичен в обоих режимах. Разница только в том что в individual нет партнёров-франчайзи — соответственно, не с кем делить «общий пул». Технически API, permissions, БД-уровневый scope не имеют ни одной особой ветки для type=individual.
Для реализации
При реализации Customer Service ни в коде, ни в миграциях не должно быть
if franchise.type == individual— scope всегда поfranchise_id. Отличие только в UI в разделе «Юр. лица» (уже реализовано в другом модуле).
Ролевая матрица
| Действие | Владелец франшизы | Владелец партнёра | Обычный сотрудник | Permission |
|---|---|---|---|---|
| Список клиентов в админке | ✅ | ✅ (все той же франшизы) | ❌ | customers.read |
| Карточка клиента | ✅ | ✅ | ❌ | customers.read |
| Создание в админке (полная форма) | ✅ | ✅ | ❌ | customers.edit |
| Редактирование клиента | ✅ | ✅ | ❌ | customers.edit |
| Soft delete (анонимизация) | ✅ | ❌ | ❌ | customers.delete |
| Объединение дубликатов | ✅ | ❌ | ❌ | customers.edit + только owner франшизы |
| Поиск клиента на POS | — | — | — | ✅ Кассир — customers.create_quick (включает read своей франшизы) |
| Quick-create на POS | — | — | — | ✅ Кассир — customers.create_quick |
| Прикрепить клиента к заказу | — | — | — | ✅ Кассир — customers.create_quick |
Почему
customers.create_quick, а неcustomers.editКассир не должен иметь доступ к списку всех клиентов в админке (это PII). Но он должен уметь создавать клиента при заказе и искать существующего по телефону. Поэтому выделяется отдельный permission, который даёт только эти операции на POS — без доступа к админке.
Подробно ролевая модель — Ролевая модель. Новые permissions добавляются в каталог разделов в Роли.
Пустые состояния
Клиентов пока нет
- Первое посещение раздела — центральный блок «Клиентов пока нет. Добавьте первого клиента или дождитесь пока кассир заведёт через POS».
- Кнопка «Создать клиента» (если есть
customers.edit)
Поиск не дал результатов
- «Клиентов по этому запросу не найдено. Попробуйте другой телефон / имя / email»
- В POS-модалке поиска — предложение «Создать нового»
Отложено в MVP
| Фича | Куда уйдёт |
|---|---|
| UI объединения дубликатов | Отдельная BR 3.X (после MVP) |
| Автообнаружение дубликатов («возможно это один клиент») | Отдельная BR |
| Клубы/статусы как отдельный tag на карточке | Не планируется — реализуется через группы |
| Импорт клиентов из Excel | Отдельная BR 3.X (с учётом compliance) |
| Вкладка «Баллы» и «Транзакции баллов» | BR 3.2 |
| Вкладка «Подарочные карты» | BR 3.7 |
| Кредитный счёт | BR 3.8 |
| Массовые операции (массовая рассылка, массовое перенесение в группу) | Отдельная BR |
| LTV/когортная аналитика, customer revenue report | Отдельная BR (после накопления данных) |
Открытые вопросы
Процесс сбора согласия на обработку ПД (ФЗ-152)
Поле
consent_signed_atвведено в сущности. Как именно собирается согласие (галочка в POS с аудио-записью / бумажная форма у кассира / электронная подпись через Госуслуги) — не определено. Это compliance-задача (юристы + продукт). В BR 3.1 фиксируем только наличие поля, UX/процесс — отдельной BR.
UI объединения дубликатов
В MVP операция
mergeдоступна только через API. Интерфейс в админке (две колонки «Источник» и «Цель» + предпросмотр + подтверждение) — отдельная BR.
Связи с другими модулями
- Заказы — заказ может быть привязан к клиенту через
customer_id. Отображается в карточке заказа. При закрытии заказа — событие, которое триггерит пересчёт принадлежности к группам (см. Группы клиентов) - Группы клиентов — сегментация для маркетинга. Динамические группы пересчитываются по
customer.updatedиorder.closed - Роли — добавляются permissions
customers.read/customers.edit/customers.delete/customers.create_quickиcustomer_groups.read/customer_groups.editв каталог разделов бэк-офиса и в POS-операции - Торговые точки — адреса клиентов могут быть связаны с
delivery_zones(для правил групп «по зоне доставки») - Customer BFF (мобильное приложение и сайт клиента) — пока не подключён. Появится в BR 3.5/3.6: регистрация клиента с внешней стороны, просмотр профиля, истории заказов