Направление работ: Back Код раздела: 1.3.2 Аккаунты и пользователи > Клиенты > Характеристики клиента Статус: черновик Ответственный: Панкова Л.А. Версия: 0.1 Обновлено: 17.07.26


1. Назначение

Раздел описывает характеристики клиента как партнерские данные, которые отражают историю и условия взаимоотношений клиента с партнерами.

Система хранит эти данные в связке с Карточкой клиента и группирует их по партнерским зонам.

Карточка клиента, клиентская зона доступа и подробная логика скидок, штрафов и условий оплаты раскрываются в соседних разделах.

2. Ключевые понятия

Характеристики клиента — сгруппированная по партнерским зонам информация о взаимоотношениях клиента с партнерами.

Карточка клиента — сущность, с которой связаны характеристики клиента. Подробнее описана в Back: 1.3.

Партнерская зона — зона партнера, в рамках которой партнер ведет данные по клиенту.

Персональный промокод — ручная скидка, связанная с Карточкой клиента.

Блокировка клиента — ограничение, при котором клиент не может совершать заказы в конкретной партнерской зоне.

3. Scope

Входит

S1 Характеристики клиента: партнерские данные Карточки клиента, которые заполняются партнерами и группируются по партнерским зонам.

S2 Состав характеристик: персональные промокоды, непогашенные штрафы, блокировки, условия оплаты и заметки партнера по клиенту.

S3 Правила применения характеристик: привязка характеристик к партнерской зоне, заказам, срокам, статусам и ограничениям применения условий оплаты.

Не входит

  • Back: 1.3 Клиенты, Карточка клиента, персональные и технические данные
  • Back: 1.3.1 Клиентская зона доступа
  • Back: 1.3.3 Группы деятельности / группы клиентов
  • Back: 1.3.4 Расширение клиентских прав доступа через подписки
  • Back: 2.7.1 Акции и промокоды
  • Back: 3.2.1 Сборы за заказ
  • Back: 2.4.2 Условия оплаты
  • Mobile: 5.8, 5.9, 5.9.1, 5.9.2 Скидки и ограничения клиента
  • WebLk: 3.4.3, 3.4.6, 4.6, 4.6.1, 4.6.2, 6.4, 6.4.1 Условия оплаты, сборы, скидки и карточка клиента
  • Widget: 5.2, 6.1 Скидки, комментарий, правила и информация о клиенте
  • AdmLk: 2.2, 3.2, 3.2.1 Клиентские справочники, управление клиентами и карточка клиента
  • Catalog: 2.4, 4.5, 4.6 Баллы, промокоды, способ оплаты, скидки и ограничения

4. Требования

Правила и ограничения бизнеса на уровне данных, источник истины в системе

IDТребованиеКомментарийТип требованияВесСтатус
S1: BR1.3.2-1Характеристики клиента должны хранить партнерские данные, связанные с Карточкой клиентаХарактеристики отражают результаты взаимоотношений клиента с партнерами. Подробнее Карточка клиента описана в https://wiki.yandex.ru/homepage/d750da42741c/texnicheskaja-dokumentacija/brd/dokumentacija-po-funkcionalurazdelam/1-akkaunty-i-polzovateli/1.3-klienty/ .Бизнес1Реализовано
S1: BR1.3.2-2Характеристики клиента должны заполняться в рамках конкретной партнерской зоныБизнес1Реализовано
S2: BR1.3.2-3Характеристики клиента должны хранить список персональных промокодов клиентаСостав данных:

• партнерская зона
• название промокода
• описание промокода
• размер или номинал скидки в рублях или процентах
• количество доступных для применения заказов
• количество заказов, к которым применен промокод
• срок действия промокода
• статус промокода

Подробная логика акций и промокодов раскрывается в https://wiki.yandex.ru/homepage/d750da42741c/texnicheskaja-dokumentacija/brd/dokumentacija-po-funkcionalurazdelam/55f6f0b0f201/2.7-skidki/2.7.1-akcii-i-promokodyavtomaticheskie-i-ruchnye-s/ .
Системное1Частично \ #OQ1.3.2-3 \ #OQ1.3.2-4
S2: BR1.3.2-4Характеристики клиента должны хранить список непогашенных штрафов клиентаСостав данных:

• партнерская зона
• сумма штрафа
• ID заказа, по которому был начислен штраф
• дата начисления

Подробная логика сборов раскрывается в https://wiki.yandex.ru/homepage/d750da42741c/texnicheskaja-dokumentacija/brd/dokumentacija-po-funkcionalurazdelam/c4a24f042617/3.2-processing-zakazov/3.2.1-sbory-za-zakaz/ .
Системное1Частично \ #OQ1.3.2-12
S2: BR1.3.2-5Характеристики клиента должны хранить список блокировок клиента по партнерским зонамСостав данных:

• партнерская зона
• причина блокировки
• дата блокировки
Бизнес1Частично \ #OQ1.3.2-1
S2: BR1.3.2-6Характеристики клиента должны хранить условия оплаты клиента по партнерским зонамСостав данных:

• наименование
• описание
• способы оплаты (канал оплаты зоны)

Подробная логика условий оплаты раскрывается в https://wiki.yandex.ru/homepage/d750da42741c/texnicheskaja-dokumentacija/brd/dokumentacija-po-funkcionalurazdelam/55f6f0b0f201/2.4-pravila-raboty-s-zakazami/2.4.1-uslovija-sozdanija/ .
Системное1Частично \ #OQ1.3.2-5 \ #OQ1.3.2-10
S2: BR1.3.2-7Характеристики клиента должны хранить заметки партнера по клиентуСостав данных:

• партнерская зона
• текст
• ID заказа, по которому была создана запись
• дата создания записи
• флаг переноса в каждое бронирование
Системное1Частично \ #OQ1.3.2-6 \ #OQ1.3.2-11
S3: BR1.3.2-8Блокировка клиента должна ограничивать возможность клиента совершать заказы только в той партнерской зоне, где она установленаБизнес1Частично \ #OQ1.3.2-7
S3: BR1.3.2-9Для клиента в партнерской зоне должно применяться одно из допустимых условий оплатыПо умолчанию Описание: Действуют стандартные условия внесения предоплаты. Условия оплаты: не заполняется

Доверять Описание: Условия внесения предоплаты игнорируются. Условия оплаты: не заполняется

Только по предоплате: Описание: Создание заказа возможно только по предоплате. Условия оплаты: ID условий оплаты с установленным значением поля ”% от итога”.

Персональные условия: Описание: Действуют особые условия предоплаты. Способы оплаты: ID условий оплаты указанных партнером вручную из добавленных в партнерской зоне.
Бизнес1Частично \ #OQ1.3.2-2 \ #OQ1.3.2-14 \ #OQ1.3.2-9
S3: BR1.3.2-10Изменение условий оплаты клиента доступно только в партнерских зонах с подключенным терминаломЕсли в партнерской зоне не указана платежная информация, для клиента применяется условие оплаты по умолчанию без возможности редактирования партнером.Системное2Не реализовано \ #OQ1.3.2-8

5. Открытые вопросы

IDИсточникВопросКомуСтатусРекомендацииИтоговое решение
#OQ1.3.2-1Код BR1.3.2-5Требование говорит: блокировка хранит партнёрскую зону, причину и дату блокировки. В коде блокировка — это один булев флаг IsBanned на записи клиента в зоне. Причины нет вообще; дата отдельным полем не хранится — момент бана попадает только в событие ClientBannedEvent (аудит), но не в саму запись. Зона есть. \ ClientPartAggregate.cs:16; ClientPartMapping.cs:23-29; ChangeClientBanStatusCommand.cs:11РазработкаЗакрывает PMГайдбук 1.3.2 прямо перечисляет состав блокировки: партнёрская зона, причина блокировки, дата блокировки — значит это недоделка, а не спорный вопрос. Закрываю сам: добавить «причину» и «дату» в запись блокировки (сейчас дата живёт только в событии, в данных её нет) → тикет в Bitrix. Обе нужны для разбора спорных ситуаций.Добавить в запись блокировки клиента по партнерской зоне поля «Причина блокировки» и «Дата блокировки». При установке блокировки сохранять причину и дату в данных клиента, при снятии блокировки сохранять историю через аудит/события без удаления контекста предыдущей блокировки. В характеристиках клиента отображать блокировку вместе с партнерской зоной, причиной и датой блокировки.
#OQ1.3.2-2Код BR1.3.2-9Требование говорит: «Персональные условия» — действуют особые условия предоплаты, партнёр вручную указывает ID условий оплаты, добавленных в зоне. В коде режим PersonalConditions есть в перечислении, но ни одной ветки логики по нему нет ни в заказах, ни в расчёте оплаты — клиент с этим режимом ведёт себя как «По умолчанию». Список выбранных партнёром ID условий оплаты нигде не хранится. \ ClientPayKind.cs:32; ClientPartAggregate.cs:15 (нет поля под список ID)Разработка (Влад)OpenГайдбук 1.3.2 явно описывает «Персональные условия» и то, что они ссылаются на ID условий оплаты — значит режим нужен, вариант «упростить BRD до трёх режимов» гайдбуку противоречит. Продуктового выбора нет, но есть архитектурный: вопрос к Владу — где хранить выбранные ID условий оплаты у клиента (на записи клиента в зоне сейчас только PayKind, справочник живёт в другом сервисе) и как реализовать ветку. Общий корень с OQ1.3.2-14, OQ1.3.2-9, OQ1.3.2-10.Добавить хранение выбранных ID условий оплаты для клиента в рамках партнерской зоны и связать их с режимом оплаты «Персональные условия». При установке режима PersonalConditions сохранять список выбранных условий оплаты, валидировать, что они относятся к доступным условиям оплаты этой партнерской зоны, и использовать их при создании заказа и расчете оплаты вместо поведения «По умолчанию». Если список условий оплаты не передан или невалиден, режим PersonalConditions не должен сохраняться.
#OQ1.3.2-3Код BR1.3.2-3Требование говорит: персональный промокод — ручная скидка, связанная с Карточкой клиента. В коде сущности «персональный промокод на одну карточку» нет: ручная скидка адресуется на список клиентов и/или групп (многие-ко-многим), партнёрская зона — коллекция scope-ов, а не атрибут промокода клиента. «Промокоды клиента» — это выборка скидок, в чьём списке клиентов он есть. \ ManualDiscountAggregate.cs:64-65, :43-50Бизнес (Герман)OpenГайдбук 1.3.2 говорит «список выданных персональных промокодов — ручных скидок, связанных с карточкой клиента» и даёт «Партнёрская зона» в составе записи, но эксклюзивность промокода для одной карточки в гайдбуке не оговорена — подтвердить нечем, не додумываю. Решение бизнеса: целевая модель «персональный промокод клиента» — 1:1 к карточке или ручная скидка на список клиентов? зона как атрибут записи? Разрыв концептуальный, закрыть до правок кода; влияет на состав и на клиентские выборки. Связать с OQ1.3.2-4.
#OQ1.3.2-4Код BR1.3.2-3Требование говорит: в составе промокода есть статус. В коде статуса как хранимого поля нет: ни перечисления, ни колонки — только булев IsArchived, а «активен / просрочен / исчерпан» вычисляется на лету в каждом запросе. На клиентских выборках промокодов статус не возвращается вовсе. \ ArchiveInfo.cs:9-11; GetMyPromocodesQuery.cs:194-214; ManualPromocodeReadOnlyRepository.cs:39-45Бизнес/РазработкаOpenГайдбук 1.3.2 задаёт статус ровно двумя значениями — активен / неактивен, а код вычисляет три (активен / просрочен / исчерпан). То, что статус должен быть в списке, гайдбук подтверждает → выборки обязаны его отдавать (сейчас не отдают — это дефект). Спорно другое: (А) оставить статус вычисляемым (архив + срок + лимит) и маппить на два значения гайдбука; (Б) ввести явный хранимый статус. Формат — с продуктом; зависит от модели промокода (OQ1.3.2-3).Согласовать с бизнесом целевую модель статуса персонального промокода: статус хранится отдельным полем или вычисляется по признаку архивации, сроку действия и лимиту применений. Для пользовательского отображения статус промокода должен возвращаться в клиентских выборках. Если выбирается вычисляемая модель, состояния кода необходимо привести к бизнес-значениям «Активен» и «Неактивен»; если выбирается хранимый статус, добавить отдельное поле статуса и правила его изменения.
#OQ1.3.2-5Код BR1.3.2-6Справочник условий оплаты (PaymentFiltersAggregate) хранит наименование и описание, но способы оплаты в нём не хранятся — они привязаны к платёжному каналу зоны (AllowPaymentMethodOnPayChannel), уровнем выше. Запись условия оплаты сама по себе перечня способов оплаты не несёт. — нужно решение: считать способы оплаты частью условия оплаты или признать, что они принадлежат каналу оплаты зоны \ PaymentFiltersAggregate.cs:26; PaymentMethod.cs:3-11; AllowPaymentMethodOnPayChannel.cs:12-20БизнесClosedБРД приведен к коду
#OQ1.3.2-6Код BR1.3.2-7Требование говорит: заметка несёт флаг переноса в каждое бронирование. В коде такого флага нет. Вместо него у заметки есть поле Admission — это видимость заметки (кому показывать: сотрудникам / только мне / только сервису / другим партнёрам), а не перенос в бронирования. \ ClientMessageAggregate.cs:17; ClientMessageAdmissionEnum.cs:3-9РазработкаЗакрывает PMГайдбук 1.3.2 прямо перечисляет «Флаг переноса в каждое бронирование» в составе записи — правило есть, продуктового выбора нет, вариант «заменить на видимость» гайдбуку противоречит (у Admission другой смысл, под это поле не подходит). Закрываю сам: добавить отдельный булев флаг переноса → тикет в Bitrix. Оговорка: семантику самого переноса (как заметка попадает в бронирование) гайдбук 1.3.2 не описывает — уточнить в разделе заказов при реализации.Добавить для заметки партнера по клиенту отдельный булев флаг «Переносить в каждое бронирование». Поле Admission оставить только для управления видимостью заметки, так как оно не отвечает за перенос. При создании и обновлении заметки сохранять значение нового флага, отображать его в характеристиках клиента и использовать при формировании бронирований, где заметка должна переноситься автоматически.
#OQ1.3.2-7Код BR1.3.2-8Требование говорит: блокировка ограничивает заказы только в той зоне, где установлена. В коде при создании заказа проверка корректна: запись клиента берётся по (клиент, зона), бан режет заказ только в этой зоне. Но обработчик события бана отменяет/архивирует уже существующие брони и заявки клиента, фильтруя их только по clientId, без зоны — хотя событие несёт PartnerZoneId. В итоге бан в одной зоне снимает ожидающие брони клиента во всех зонах. \ CreateOrderCommandHandler.cs:91-107 (корректно); ClientBannedConsumer.cs:37-69 (утечка)РазработкаЗакрывает PMГайдбук 1.3.2 подтверждает зональность: «Список партнёрских зон, в которых клиент не может совершать заказы» — значит это баг, а не спорный вопрос. Закрываю сам: в обработчике события бана фильтровать брони/заявки ещё и по PartnerZoneId из события (поле уже есть, просто не используется) → тикет в Bitrix. Правка локальная, проверка при создании заказа уже верна.Исправить обработчик события блокировки клиента: при отмене или архивировании существующих броней и заявок фильтровать их не только по ClientId, но и по PartnerZoneId из события блокировки. Блокировка должна влиять только на заказы, брони и заявки в той партнерской зоне, где она установлена. Проверку при создании нового заказа оставить без изменений, так как она уже использует связку клиент + зона.
#OQ1.3.2-8Код BR1.3.2-10Требование говорит: менять условия оплаты клиента можно только в зонах с подключённым терминалом; без платёжной информации — «По умолчанию» без редактирования. В коде проверки терминала при смене режима нет нигде: команда обновления записи клиента меняет PayKind без обращения к настройкам оплаты. Флага «терминал подключён» наружу сервис партнёров не отдаёт, состояние выводимо лишь косвенно. Запрет редактирования и откат к «По умолчанию» не реализованы. \ UpdateOrCreateClientPartCommand.cs:19,52; PaymentSettingsAggregate.cs:40; терминал-проверка — не найдено за обходРазработка (Влад)OpenГайдбук 1.3.2 прямо подтверждает правило: «Функция активируется только в партнёрских зонах, в которых заполнена платёжная информация… В остальных случаях устанавливается элемент справочника „По умолчанию“, и его редактирование партнёром невозможно» — значит это недоделка, а не спорный вопрос. Но реализация межсервисная: вопрос к Владу — как сервис клиентов узнаёт «терминал подключён» (флаг наружу от сервиса партнёров или запрос) и где живёт откат к «По умолчанию». Разблокируется вместе с OQ1.3.2-2.Добавить проверку наличия платежной информации/подключенного терминала партнерской зоны перед изменением условий оплаты клиента. Сервис клиентов должен получать этот признак из сервиса партнеров через согласованный контракт: отдельный флаг или запрос по PartnerZoneId. Если платежная информация отсутствует, сохранять для клиента режим оплаты «По умолчанию» и запрещать изменение PayKind партнером. Если платежная информация есть, разрешать изменение PayKind с учетом правил выбранного режима оплаты.
#OQ1.3.2-9Код BR1.3.2-9Проблема: команда обновления записи клиента принимает PayKind, но не валидирует согласованность: для «Только по предоплате» / «Персональные условия» не требует и не сохраняет ID условий оплаты; любой режим можно выставить без проверок. \ UpdateOrCreateClientPartCommand.cs:19,43-52РазработкаOpenГайдбук задаёт, что два режима из четырёх опираются на ID условий оплаты → валидация состава нужна. Реализуется после появления самого хранения ID (OQ1.3.2-2, OQ1.3.2-10): требовать ID условий оплаты там, где режим их предполагает. Отдельного продуктового вопроса нет, но и чинить пока нечего — ждёт архитектурного решения. Связать с OQ1.3.2-2, OQ1.3.2-10.После добавления хранения ID условий оплаты на записи клиента в партнерской зоне реализовать валидацию режима PayKind при создании и обновлении записи клиента. Для режимов «Только по предоплате» и «Персональные условия» требовать наличие ID условий оплаты и проверять, что выбранные условия доступны в этой партнерской зоне. Для режимов «По умолчанию» и «Доверять» ID условий оплаты не сохранять и не требовать. Некорректную комбинацию PayKind и условий оплаты не принимать.
#OQ1.3.2-10Код BR1.3.2-6Проблема: на записи клиента в зоне условие оплаты представлено только перечислением PayKind; ссылки на запись справочника условий оплаты у клиента нет. Сам справочник живёт в другом сервисе. Связь «клиент → конкретное условие оплаты» в данных отсутствует. \ ClientPartAggregate.cs:15; PaymentFiltersAggregate.cs:25-26Разработка (Влад)OpenГайдбук требует, чтобы «Только по предоплате» и «Персональные условия» ссылались на ID условий оплаты — значит связь нужна. Вопрос к Владу: где она живёт (поле на записи клиента в зоне / отдельная связь) с учётом того, что справочник в другом сервисе. Общий корень с OQ1.3.2-2, OQ1.3.2-14, OQ1.3.2-9 — решать одним архитектурным заходом.Для клиента в партнерской зоне нужно хранить не только общий режим оплаты, но и выбранные партнером условия оплаты, когда режим этого требует. Для режимов «Только по предоплате» и «Персональные условия» система должна сохранять конкретные условия оплаты из справочника этой партнерской зоны и применять их при создании заказа и расчете оплаты. Для режимов «По умолчанию» и «Доверять» отдельные условия оплаты не выбираются. Если выбранные условия оплаты не относятся к партнерской зоне или недоступны, такое значение не сохраняется.
#OQ1.3.2-11Код BR1.3.2-7Требование говорит: заметка несёт ID заказа, по которому создана. В коде поля OrderId у заметки нет: ни в агрегате, ни в команде создания, ни в модели хранения. Привязать заметку к заказу нельзя. \ ClientMessageAggregate.cs; CreateClientMessageCommand.cs:10-15; ClientMessageMapping.cs:28-39РазработкаЗакрывает PMГайдбук 1.3.2 прямо перечисляет «ID заказа, вследствие которого была создана запись» в составе заметки — правило есть, продуктового выбора нет. Закрываю сам: добавить OrderId в агрегат / команду / модель заметки → тикет в Bitrix. Правка небольшая и изолированная.Добавить к заметке партнера по клиенту поле «ID заказа, по которому создана заметка». При создании заметки из контекста заказа сохранять этот ID в данных заметки. В характеристиках клиента отображать заметку вместе с привязанным заказом, чтобы было понятно, по какому заказу и по какой причине запись появилась.
#OQ1.3.2-12Код BR1.3.2-4Проблема: основной расчёт суммы непогашенных штрафов корректен (не оплачен полностью, не в архиве, точное сравнение зоны). Но соседний метод выборки непогашенных штрафов по зоне фильтрует зону через PartnerZoneId <= ... (сравнение «меньше-либо-равно» по Guid вместо равенства) — тянет штрафы из «меньших» по значению зон. \ ForfeitsRepository.cs:54-74 (корректный); ForfeitsRepository.cs:46 (подозрительный <=)РазработкаЗакрывает PMГайдбук 1.3.2 требует список штрафов по партнёрской зоне — сравнение <= этому прямо противоречит, это дефект зональной фильтрации. Закрываю сам: заменить PartnerZoneId <= ... на == в выборке непогашенных штрафов по зоне → тикет в Bitrix. При реализации уточнить у владельца кода, где метод используется (оценить радиус).Исправить выборку непогашенных штрафов клиента по партнерской зоне: в список должны попадать только штрафы из той партнерской зоны, по которой выполняется запрос. Штрафы из других зон не должны отображаться или учитываться в характеристиках клиента для выбранной зоны. Расчет суммы непогашенных штрафов оставить по текущей логике, так как он уже использует точное сравнение партнерской зоны.
#OQ1.3.2-13Код вне BRD (обратный проход)Что нашли: запись клиента в зоне ключуется суррогатным Id; запрета дублей в базе на пару (клиент, зона) нет, а обработчик создаёт запись по схеме «прочитать-потом-создать». При гонке возможны две записи на одну пару (клиент, зона) — с расходящимися бан / штраф / условием оплаты. \ ApplicationDbContext.cs:155 (только HasKey(Id)); UpdateOrCreateClientPartCommand.cs:40-51Разработка (Влад)OpenГайдбук 1.3.2 говорит о характеристиках как о «сгруппированной по партнёрским зонам информации» — то есть запись клиента в зоне по смыслу единственная, но явного требования уникальности в BRD нет. Вопрос к Владу: (А, рекоменд.) добавить запрет дублей в базе (уникальный индекс) на пару (ClientId, PartnerZoneId) и опереть обновление на него — БД-ограничение дёшево и закрывает гонку; (Б) оставить на уровне приложения, если гонок на этом пути не ожидается. Заодно завести требование уникальности в BRD.Для одного клиента в одной партнерской зоне должна существовать только одна запись характеристик. При повторном изменении данных система должна обновлять существующую запись, а не создавать новую. Дубли записей по одной паре «клиент + партнерская зона» не допускаются, чтобы блокировка, штрафы, условия оплаты и заметки клиента не расходились между несколькими записями.
#OQ1.3.2-14Код BR1.3.2-9Требование говорит: «Только по предоплате» — условия оплаты: ID условий оплаты с установленным значением поля «% от итога». В коде режим PrepaymentOnly работает (исключает оплату на месте), но привязки к конкретному ID условия оплаты с «% от итога» на стороне клиента нет — на записи клиента в зоне есть только сам признак PayKind. Сам «% от итога» живёт как PrepaymentPercent на настройках оплаты зоны, а не выбирается через ID условий оплаты для клиента. \ ClientPartAggregate.cs:15; PaymentEvaluationHelper.cs:43-44; PaymentSettingsAggregate.cs:36Разработка (Влад)OpenГайдбук 1.3.2 отвечает на вопрос Влада «ID или процент из зоны»: у элемента «Только по предоплате» указано «ID условий оплаты с установленным значением поля „% от итога“» — то есть ссылка на условие нужна, процента из настроек зоны недостаточно. Продуктового выбора нет, архитектурный есть: вопрос к Владу — где хранить ссылку (общий корень с OQ1.3.2-2, OQ1.3.2-10). Отличие от OQ1.3.2-2: там режим не работает вовсе, здесь режим работает, но без привязки к условию.Для режима «Только по предоплате» у клиента в партнерской зоне должно сохраняться конкретное условие оплаты из справочника этой зоны. Выбранное условие должно содержать заполненное значение «% от итога» и применяться при создании заказа и расчете оплаты. Общий процент из настроек партнерской зоны не заменяет выбранное условие оплаты клиента, а используется только там, где не требуется персональная привязка к условию.

Вопросы по сверке с кодом

BRD 1.3.2 v0.1 · booking-monorepo · commit 9e19870 / dev/cms-conflict-warning · 2026-06-30 · якорь src/services/booking.clients (+ booking.discounts, booking.orders, booking.partner-management) Сводка: вопросов 14 (бизнесовых 7 / программных 7) · подтверждено без вопросов 3 (BR1.3.2-1, BR1.3.2-2, BR1.3.2-4) · отрезано находок обратного прохода 2

Бизнесовые

Связано сЧто в BRDЧто в кодеГде в коде
Б1BR1.3.2-5Блокировка должна хранить состав: партнерская зона, причина блокировки, дата блокировкиБлокировка хранится как один булев флаг IsBanned на записи клиента в зоне. Причины блокировки нет вообще; дата блокировки отдельным полем не хранится — момент бана попадает только в событие ClientBannedEvent (аудит), но не в саму запись блокировки. Зона есть. — нужно решение: нужны ли поля «причина» и «дата» как часть данных блокировкиClientPartAggregate.cs:16; ClientPartMapping.cs:23-29; ChangeClientBanStatusCommand.cs:11
Б2BR1.3.2-9«Персональные условия» — действуют особые условия предоплаты; партнер вручную указывает ID условий оплаты, добавленных в зонеВариант оплаты PersonalConditions есть в перечислении, но ни одной ветки логики по нему нет ни в заказах, ни в расчёте оплаты — клиент с PersonalConditions ведёт себя как Default. Список выбранных партнером ID условий оплаты нигде не хранится. — нужно решение: нужен ли режим «Персональные условия» и где хранить выбранные IDClientPayKind.cs:32; ClientPartAggregate.cs:15 (нет поля под список ID); веток по PersonalConditions нет в booking.orders и booking.partner-management
Б3BR1.3.2-9«Только по предоплате» — условия оплаты: ID условий оплаты с установленным значением поля «% от итога»Режим PrepaymentOnly работает (исключает оплату на месте), но привязки к конкретному ID условий оплаты с «% от итога» на стороне клиента нет — на записи клиента в зоне есть только сам признак PayKind. Сам «% от итога» живёт как PrepaymentPercent на настройках оплаты зоны, а не выбирается через ID условий оплаты для клиента. — нужно решение: должен ли «Только по предоплате» ссылаться на конкретное условие оплаты, или достаточно процента из настроек зоныClientPartAggregate.cs:15; PaymentEvaluationHelper.cs:43-44; PaymentSettingsAggregate.cs:36
Б4BR1.3.2-3Персональный промокод — «ручная скидка, связанная с Карточкой клиента» (один промокод на карточку)В коде нет сущности «персональный промокод на одну карточку». Ручная скидка (ManualDiscountAggregate) адресуется на список клиентов и/или групп (связь многие-ко-многим), партнерская зона — это коллекция scope-ов, а не атрибут промокода клиента. «Промокоды клиента» — это выборка скидок, в чьём списке клиентов он есть. — нужно решение: модель «персональный промокод» = ручная скидка на список клиентов или нужна отдельная привязка 1:1 к карточкеManualDiscountAggregate.cs:64-65 (_clients), :43-50 (scope/зоны)
Б5BR1.3.2-3Среди состава промокода — статус промокодаСтатуса как отдельного хранимого поля нет: в скидках нет ни перечисления статусов, ни колонки. Есть только булев IsArchived; «активен / просрочен / исчерпан» вычисляется на лету в каждом запросе. На клиентских выборках промокодов статус не возвращается вовсе. — нужно решение: считать ли статус вычисляемым (архив + срок + лимит) или ввести явный статусArchiveInfo.cs:9-11; GetMyPromocodesQuery.cs:194-214; ManualPromocodeReadOnlyRepository.cs:39-45
Б6BR1.3.2-6Условия оплаты клиента — состав: наименование, описание, способы оплатыСправочник условий оплаты (PaymentFiltersAggregate) хранит наименование и описание, но способы оплаты в нём не хранятся — они привязаны к платёжному каналу зоны (AllowPaymentMethodOnPayChannel), уровнем выше. Запись условия оплаты сама по себе перечня способов оплаты не несёт. — нужно решение: считать способы оплаты частью условия оплаты или признать, что они принадлежат каналу оплаты зоныPaymentFiltersAggregate.cs:26; PaymentMethod.cs:3-11; AllowPaymentMethodOnPayChannel.cs:12-20
Б7BR1.3.2-7Заметка должна нести флаг переноса в каждое бронированиеТакого флага нет. Вместо него у заметки есть поле Admission — это видимость заметки (кому показывать: сотрудникам/только мне/только сервису/другим партнёрам), а не перенос в бронирования. — нужно решение: нужен ли отдельный булев «переносить в каждое бронирование» (это иной смысл, чем видимость)ClientMessageAggregate.cs:17; ClientMessageAdmissionEnum.cs:3-9

Рекомендация / варианты решения

  • Состав блокировки (Б1). Вариант А (рекоменд.): согласовать с продуктом и, если причина/дата нужны бизнесу, добавить их в запись блокировки (сейчас даже дата живёт только в событии, не в данных). Вариант Б: привести BRD к коду — блокировка как чистый флаг, причину/дату убрать из состава. Рекомендуется А: «причина» и «дата блокировки» обычно нужны для разбора спорных ситуаций.
  • Условия оплаты клиента — PersonalConditions и PrepaymentOnly (Б2, Б3 — общая первопричина: на записи клиента в зоне хранится только PayKind, без привязки к конкретным условиям оплаты). Вариант А (рекоменд.): решить, что режимы со ссылкой на условия оплаты нужны, и тогда добавить хранение выбранных ID условий оплаты у клиента + реализовать ветку PersonalConditions. Вариант Б: упростить BRD до трёх рабочих режимов (Default/Trust/PrepaymentOnly через процент зоны), отказавшись от «Персональных условий». Рекомендуется А, если сценарий «индивидуальные условия по клиенту» реально востребован; иначе Б честнее отражает текущий код.
  • Модель и статус промокода (Б4, Б5). Вариант А: привести BRD к коду — описать промокод как ручную скидку на список клиентов с вычисляемым статусом (без зоны-атрибута и без хранимого статуса). Вариант Б (рекоменд.): согласовать с продуктом целевую модель «персональный промокод клиента» (1:1 к карточке? зона как атрибут? явный статус?) — это влияет и на состав, и на клиентские выборки, которые сейчас статус/лимиты не отдают. Рекомендуется Б: разрыв концептуальный, его нужно закрыть продуктовым решением до правок кода.
  • Способы оплаты в условии оплаты (Б6). Вариант А (рекоменд.): привести BRD к коду — указать, что способы оплаты принадлежат каналу оплаты зоны, а условие оплаты несёт только наименование/описание/правила. Вариант Б: переносить/дублировать способы оплаты на запись условия оплаты. Рекомендуется А: текущее разнесение (методы на канале) — осознанная модель, дублирование породит рассинхрон.
  • Смысл поля заметки (Б7). Вариант А (рекоменд.): согласовать с продуктом, нужен ли отдельный флаг «переносить в каждое бронирование» — и если да, добавить его (поле Admission под это не подходит, у него другой смысл). Вариант Б: привести BRD к коду — заменить «флаг переноса» на «видимость заметки». Рекомендуется А, если перенос в бронирования — реальный сценарий; это не то же самое, что видимость.

Программные

Связано сЧто в BRDЧто в кодеГде в коде
П1BR1.3.2-8Блокировка ограничивает заказы только в той зоне, где установленаПри создании заказа проверка корректно по зоне: запись клиента берётся по (клиент, зона), и бан режет заказ только в этой зоне. Но обработчик события бана отменяет/архивирует уже существующие брони и заявки клиента, фильтруя их только по clientId, без зоны — хотя событие несёт PartnerZoneId. В итоге бан в одной зоне снимает ожидающие брони клиента во всех зонах.Проверка при создании: CreateOrderCommandHandler.cs:91-107; утечка: ClientBannedConsumer.cs:37-69 (читается только context.Message.ClientId)
П2BR1.3.2-10Менять условия оплаты клиента можно только в зонах с подключённым терминалом; без платёжной информации — Default без редактированияПроверки терминала при смене PayKind нет нигде: команда обновления записи клиента меняет PayKind без обращения к настройкам оплаты/терминалу. Флага «терминал подключён» наружу partner-management не отдаёт; состояние выводимо лишь косвенно из PaymentSettingsAggregate.TerminalId. Запрет редактирования и откат к Default не реализованы.UpdateOrCreateClientPartCommand.cs:19,52; PaymentSettingsAggregate.cs:40; терминал-проверка — не найдено за обход (booking.clients, booking.partner-management)
П3BR1.3.2-9На запись клиента в зоне применяется одно из условий оплаты; набор значений — 4 режимаКоманда обновления записи клиента принимает PayKind, но не валидирует согласованность: для PrepaymentOnly/PersonalConditions не требует и не сохраняет ID условий оплаты; любой режим можно выставить без проверок. — нужно решение: какие проверки обязательны при установке PayKindUpdateOrCreateClientPartCommand.cs:19,43-52
П4BR1.3.2-6Условия оплаты клиента по зонам (состав справочника)На записи клиента в зоне (ClientPartAggregate) условие оплаты представлено только перечислением PayKind; ссылки на запись справочника условий оплаты (PaymentFiltersAggregate) у клиента нет. Сам справочник живёт в другом сервисе. Связь «клиент → конкретное условие оплаты» в данных отсутствует. — нужно решение: нужна ли у клиента прямая ссылка на ID условия оплатыClientPartAggregate.cs:15; PaymentFiltersAggregate.cs:25-26
П5BR1.3.2-7Заметка должна нести ID заказа, по которому созданаПоля OrderId у заметки нет: ни в агрегате, ни в команде создания, ни в модели хранения. Привязать заметку к заказу нельзя.ClientMessageAggregate.cs (нет OrderId); CreateClientMessageCommand.cs:10-15; ClientMessageMapping.cs:28-39
П6BR1.3.2-4Список непогашенных штрафов по зонеОсновной расчёт суммы непогашенных штрафов корректен (PaidAmount < Amount, не в архиве, точное сравнение зоны). Но соседний метод выборки непогашенных штрафов по зоне фильтрует зону через PartnerZoneId <= ... (сравнение «меньше-либо-равно» по Guid вместо равенства) — это похоже на ошибку и тянет штрафы из «меньших» по значению зон.Корректный: ForfeitsRepository.cs:54-74; подозрительный <=: ForfeitsRepository.cs:46
П7вне BRD (обратный проход)(нет явного требования об уникальности записи клиента в зоне)Запись клиента в зоне (ClientPart) ключуется суррогатным Id; запрета дублей в базе на пару (клиент, зона) нет, а обработчик создаёт запись по схеме «прочитать-потом-создать». При гонке возможны две записи на одну пару (клиент, зона) — с расходящимися бан/штраф/PayKind. — нужно решение: добавить запрет дублей в базе на (ClientId, PartnerZoneId)ApplicationDbContext.cs:155 (только HasKey(Id)); UpdateOrCreateClientPartCommand.cs:40-51

Рекомендация / варианты решения

  • Утечка бана за пределы зоны (П1). Вариант А (рекоменд.): привести код к BRD — в обработчике события бана фильтровать брони/заявки ещё и по PartnerZoneId из события (поле уже есть, просто не используется). Вариант Б: привести BRD к коду — признать, что бан в любой зоне снимает все ожидающие брони клиента (вряд ли желаемо). Рекомендуется А: локальная правка фильтра, устраняющая прямое противоречие требованию; проверка при создании заказа уже корректна.
  • Терминал-гейт смены условий оплаты (П2) + проверки при установке PayKind (П3, П4). Вариант А (рекоменд.): после продуктового решения по BR1.3.2-9/-10 (см. бизнес-блок) реализовать на стороне booking.clients проверку «терминал подключён» (через флаг/запрос к partner-management) и валидацию состава PayKind (требовать ID условий оплаты там, где он нужен), плюс откат к Default без редактирования при отсутствии платёжной информации. Вариант Б: оставить как есть и зафиксировать в BRD, что ограничение не применяется. Рекомендуется А, но он разблокируется только после бизнес-решения — сейчас это «не найдено за обход», а не просто недоделка.
  • ID заказа у заметки (П5). Вариант А (рекоменд.): добавить OrderId в агрегат/команду/модель заметки. Вариант Б: убрать пункт из состава в BRD. Рекомендуется А, если заметки по заказу — рабочий сценарий; правка небольшая и изолированная.
  • Ошибка <= в выборке непогашенных штрафов (П6). Вариант А (рекоменд.): заменить PartnerZoneId <= ... на == в GetCLientUnpaidForfeitsByPartnerZoneAsync. Вариант Б: оставить, если метод где-то намеренно полагается на это поведение (маловероятно). Рекомендуется А: почти наверняка дефект зональной фильтрации; уточнить у владельца кода, где этот метод используется.
  • Дубли записи клиента в зоне (П7). Вариант А (рекоменд.): добавить запрет дублей в базе (unique-индекс) на пару (ClientId, PartnerZoneId) и опереть обновление на него. Вариант Б: оставить на уровне приложения, если гонок на этом пути не ожидается. Рекомендуется А: запись клиента в зоне по смыслу единственная, БД-ограничение дёшево и закрывает гонку.

0 items under this folder.