Направление работ: Back Код раздела: 2.6 Цены Версия: 0.4 Обновлено: 08.07.26
Назначение
Цены задают базовую стоимость аренды площадки или услуги за единицу времени. Back обеспечивает хранение цен, все операции (создание, изменение, архивирование, удаление) и валидацию, проверку конфликтов между ценами из-за пересечения условий применения.
Ключевые понятия
- Цена — сущность, определяющая стоимость бронирования площадки/услуги в партнёрской зоне при заданных условиях применения. Каждая уникальная комбинация условий применения = отдельная цена.
- Стоимость — числовое значение для расчёта бронирования временного слота.
- Временной слот — промежуток в рамках режима работы площадки/объекта.
- Условия применения: Партнёрская зона, Объект, Площадка (мин. одна), Период действия (нет = бессрочно), День недели (нет = все дни), Время действия/слот, Опция заказа/вид работ/услуга, Диапазон посетителей, Минимальная длительность заказа, Признак «День в день».
- Доплата за превышение количества посетителей — фиксированная надбавка за каждого посетителя сверх диапазона, не отдельная цена.
Scope
Входит
- S1 — Сущность цены
- S2 — Параметры и данные
- S3 — Создание и изменение
- S4 — Архивация и восстановление
- S5 — Удаление
- S6 — Массовые операции
- S7 — Валидация и ограничения
Требования
| ID | Требование | Комментарий | Тип требования | Вес | Статус |
|---|---|---|---|---|---|
| S1: BR2.6-1 | Цена относится только к одной партнёрской зоне. Не может быть создана вне партнёрской зоны и перенесена в другую. | Бизнес | 1 | Реализовано | |
| S2: BR2.6-2 | Цена должна содержать параметры применимости: партнёрская зона, объект, площадка, период действия, день недели, время действия (слот), опция заказа, диапазон посетителей, минимальная длительность заказа, признак «День в день». | Бизнес | 1 | — | |
| S2: BR2.6-3 | Цена должна содержать набор значений стоимости для применимых временных слотов. | Бизнес | 1 | Частично \ #OQ2.6-10 | |
| S2: BR2.6-4 | Если необязательное условие не задано — цена применяется ко всем допустимым значениям (день недели, опция, период, диапазон посетителей). | Бизнес | 2 | Частично \ #OQ2.6-5 | |
| S2: BR2.6-5 | Доплата за превышение диапазона посетителей может быть задана только для цены с заданным диапазоном. Доплата не создаёт отдельную цену, меняет значения в матрице на фиксированное количество. | Бизнес | 2 | Не реализовано \ #OQ2.6-4 | |
| S3: BR2.6-6 | Система должна поддерживать создание и изменение всех параметров и значений цены (условия применения и значения стоимости по слотам). | Системное | 1 | Реализовано | |
| S4: BR2.6-7 | Система должна поддерживать архивацию цены. Архивная цена перестаёт применяться к новым бронированиям. | Системное | 1 | Реализовано | |
| S4: BR2.6-8 | Восстановление архивной цены только при отсутствии конфликтов с активными ценами. Применяются проверки применимости как при создании/изменении. | Бизнес | 2 | Реализовано | |
| S5: BR2.6-9 | Удаление цены необратимо. | Бизнес | 2 | Реализовано | |
| S6: BR2.6-10 | Массовые операции только в рамках одной партнёрской зоны. | Бизнес | 2 | — | |
| S6: BR2.6-11 | При массовой операции каждая цена обрабатывается по правилам одиночной операции. | Системное | 2 | Реализовано | |
| S6: BR2.6-12 | Результат массовой операции должен позволять определить, какие цены обработаны успешно, какие с ошибкой. | Системное | 2 | Не реализовано \ #OQ2.6-12 | |
| S7: BR2.6-13 | Нельзя сохранить цену с пустыми обязательными параметрами (только площадка и стоимость). | Системное | 1 | Частично \ #OQ2.6-11 | |
| S7: BR2.6-14 | Не допускается наличие нескольких цен, применимых к одному случаю бронирования в одной партнёрской зоне. Конфликт = совпадение/пересечение условий применения и временных слотов. Совпадение значений стоимости не конфликт. Только если совпадают все условия применения. | Бизнес | 1 | Частично \ #OQ2.6-7 \ #OQ2.6-8 \ #OQ2.6-9 \ #OQ2.6-2 | |
| S7: BR2.6-15 | Нельзя оставить незаполненный временной слот в пределах режима работы площадки. | Бизнес | 2 | Не реализовано \ #OQ2.6-3 | |
| S7: BR2.6-16 | Цена с признаком «День в день» не может существовать без цены «Заранее» с теми же условиями применения. «Заранее» может без «День в день». Условия совпадают кроме признака, стоимости могут отличаться. | Бизнес | 2 | Не реализовано \ #OQ2.6-1 |
5. Открытые вопросы
| ID | Источник | Вопрос | Кому | Статус | Рекомендации | Итоговое решение |
|---|---|---|---|---|---|---|
| #OQ2.6-1 | Код BR2.6-16 | Требование говорит: цену «День в день» нельзя создавать в одиночку — рядом обязательно должна быть парная цена «Заранее» с теми же условиями. В коде такой проверки нет вообще: «День в день» — просто галочка, и цену с ней можно сохранить одну, без пары (ни при создании, ни при изменении, ни при восстановлении система пару не требует). \ PriceAggregate.cs:78; CreatePriceCommandHandler.cs:70 | Бизнес (Герман) | Open | В гайдбуке 2.6 правила про обязательную пару вообще нет — подтвердить, что оно реально нужно, нечем. Решение бизнеса: (А) если правило нужно — добавить проверку и решить, где она живёт (Back или фронт), но одиночную «День в день» не сохранять; (Б) если «День в день» самодостаточна — убрать требование из BR2.6-16. | Вариант Б Нужен финальный ответ от бизнеса. Либо бэке проверяет наличие пары, либо убрать инвариант из BRD isToday - простой флаг, пары “заранее” не требуется |
| #OQ2.6-2 | Код BR2.6-14 | Что делает система: при проверке конфликтов считает цену «День в день» и цену «Заранее» с одинаковыми прочими условиями двумя разными, непересекающимися ценами. То есть галочка «День в день» работает как разделитель цен, а не как обычное условие. Это не ошибка — так и должна вести себя модель пары, но нужно, чтобы бизнес это подтвердил письменно. \ PriceRepository.cs:270 | Бизнес (Герман) | Open | Рекомендация — оставить как есть, логика непротиворечива. Нужно только письменное «да, так задумано» от бизнеса. | isToday = true/false разделяет цены при конфликт проверке. Стоит подтвердить вопрос с бизнесом |
| #OQ2.6-3 | Код BR2.6-15 | Требование говорит: у площадки не должно оставаться «дыр» в расписании — на каждый рабочий слот должна быть цена. Проблема: сервис цен вообще не знает график работы площадки (эти данные в него не приходят), поэтому проверить покрытие он физически не может. Заготовка под проверку в коде есть, но не используется — цену можно сохранить с дырой в расписании. \ OverLappingOffSchedulesException.cs:11 | Бизнес/Разработка | Open | Правило в сервисе цен в одиночку жить не может — нет данных о графике. Рекомендация: договориться, что покрытие проверяет фронт или сервис ресурсов при задании цены, а BR2.6-15 убрать из зоны Back-цен. Альтернатива (дороже): тянуть график в сервис цен. Решают продукт + архитектура. | Лучше закрепить ответственность за клиентом, а не связывать сервис цен с графиком работы площадки |
| #OQ2.6-4 | Код BR2.6-5 | Требование говорит: доплату за лишних посетителей можно задать только если у цены указан диапазон посетителей. В коде такой проверки нет — доплату принимают всегда, даже без диапазона (проверяют лишь, что она не отрицательная). Готовая проверка в коде заготовлена, но её не вызывают. \ PriceInfo.cs:26 | Разработка | Закрывает PM | Код просто не соответствует требованию, продуктового вопроса нет. Закрываю сам: включить проверку «доплата только при заданном диапазоне» (готовое исключение под это уже есть) → тикет в Bitrix. | доработка |
| #OQ2.6-5 | Код BR2.6-4 | Требование говорит: если условие не задано — цена действует на все его значения (не задан день недели → все дни, не задана опция → все опции). В коде это работает по-разному: для периода и опций пустое = «все» (верно), а для дня недели и слота пустое = «ни одного» (неверно). Причина — дни недавно перевели в список, и пустой список читается как «ничего». \ PriceRepository.cs:279-284 | Разработка | Закрывает PM | Гайдбук 2.6 прямо говорит: «день недели не задан → все дни». Значит это баг, а не спорный вопрос. Закрываю сам: привести проверку по дням/слотам к «все» → тикет в Bitrix. | доработка |
| #OQ2.6-6 | Код вне BRD | Что делает код сверх требований: запрещает две цены с одинаковым названием в одной партнёрской зоне (проверка уникальности имени). В требованиях 2.6 такого правила нет — код строже, чем BRD. \ PriceRepository.cs:157 | Бизнес (Герман) | Open | В гайдбуке 2.6 про уникальность имени тоже ничего нет — подтвердить нечем. Решение бизнеса: если уникальность имени — намеренное правило, внести его в требования; если нет — снять проверку или задокументировать как техническое ограничение. | Метод уникальности имени есть, но вообще нигде не используется. Формулировка в BRD не совсем точная. Активного ограничения нет, код не строже BRD. Это мертвый кусок, его надо либо удалить, либо подключить как бизнес-правило |
| #OQ2.6-7 | Код BR2.6-14 | Требование: в одной зоне не должно быть двух цен на один и тот же случай (конфликт). Проверка на это есть, но только в коде приложения — в самой базе запрета на дубли нет. Из-за этого при двух одновременных запросах обе конфликтующие цены могут пройти проверку каждая в своей операции и обе сохраниться (состояние гонки). \ ApplicationDbContext.cs:39-56 | Разработка (Влад) | Open | Правило важное (вес 1), а держится только на проверке в приложении — этого мало. Вопрос к Владу: добавить страховку на уровне базы (уникальное ограничение или блокировку на время записи)? Проверку в приложении оставить для понятных сообщений об ошибке. | Проверка конфликтов только прикладная; БД не страхует от одновременной записи двух конфликтующих цен. Уникальный индекс здесь сложно выразить из-за диапазонов; практичнее сериализовать запись в пределах партнёрской зоны. |
| #OQ2.6-8 | Код BR2.6-14 | Проблема: проверка пересечения слотов написана с ошибкой — кусок кода с выбором верхней границы времени бессмысленный (обе ветки дают одно и то же). Из-за этого слот, заканчивающийся в 00:00 (круглосуточный), не распознаётся как «до конца суток», и пересечение с ним почти никогда не срабатывает. \ PriceRepository.cs:282 | Разработка (Влад) | Open | Похоже на баг. Вопрос к Владу (подтвердить логику): трактовать конец в 00:00 как конец суток и убрать бессмысленный кусок? Или 00:00 намеренно значит «полночь начала» — тогда задокументировать и покрыть тестом. | 00:00 не трактуется как конец суток в проверке пересечения. Нужно определить семантику 00:00, вероятнее — конец суток, и добавить тесты. |
| #OQ2.6-9 | Код BR2.6-14 | Требование: конфликт цен определяется по набору «условий применения». В коде в проверке конфликта не участвуют два поля — минимальная длительность заказа и приоритет. Из-за этого две цены, различающиеся только длительностью (или только приоритетом), система ошибочно считает конфликтующими. \ PriceRepository.cs:269-285 | Бизнес/Разработка | Open | Гайдбук 2.6 подсказывает: приоритет — это способ обойти валидацию, а не условие (значит верно, что он не участвует), а минимальная длительность — полноценное условие применения (значит должна участвовать). Подтвердить и добавить длительность в проверку. | доработка |
| #OQ2.6-10 | Код BR2.6-3 | Требование: у цены должны быть значения стоимости по слотам, стоимость — значимый параметр. Проблема: стоимость по факту вообще не проверяется — единственная проверка (на «не 0 и не отрицательное») висит на типе, который в коде нигде не создаётся, а рабочий путь создания цены проверок стоимости не имеет. Итог: цену можно сохранить с нулевой или отрицательной стоимостью. \ TimeWithWeekdaysPrice.cs:17-23 | Разработка | Закрывает PM | Гайдбук 2.6 прямо говорит: «стоимость не может быть 0, нулевые суммы недопустимы». Значит правило есть, спорить не о чем. Закрываю сам: перенести проверку на рабочий тип цены → тикет в Bitrix. | доработка |
| #OQ2.6-11 | Код BR2.6-13 | Требование: обязательны только площадка и стоимость, остальное — нет. В коде наоборот: имя, приоритет и минимальная длительность фактически обязательны (без них не сохранить) — это строже требования. А сама стоимость при этом не проверяется вообще (см. OQ2.6-10). \ PriceInfo.cs:24-27 | Бизнес (Герман) | Open | Решение бизнеса: какие поля на самом деле обязательны при создании цены. От ответа — либо ослабляем код до «только площадка и стоимость», либо обновляем требование под фактический набор. Связать с OQ2.6-10. | Доработка Имя обязательно; Priority и MinOrderDuration не могут быть отрицательными, но могут быть равны 0. Последние два поля не обязательны по смыслу, у них допустимо значение по умолчанию 0. Остаётся несоответствие по обязательности имени и отсутствие валидации стоимости. |
| #OQ2.6-12 | Код BR2.6-12 | Требование: после массовой операции должно быть видно, какие цены обработались успешно, а какие — с ошибкой. В коде массовое изменение/удаление идёт одной общей транзакцией: на первой же проблемной цене всё откатывается целиком, и списка «эти ок, эти нет» не возвращается. \ UpdatePriceCommandHandler.cs:134,148 | Бизнес/Разработка | Open | Требование прямо просит частичный результат, а его нет. Решение: (А) переделать на пообъектную обработку с результатом по каждой цене (сломает нынешнюю «всё или ничего»); (Б, как временное) оставить общую транзакцию, но в ошибке возвращать id всех проблемных цен. Финальный выбор — за продуктом. | пока актуальнее наверное вариант б |
| #OQ2.6-13 | Код вне BRD | Что нашли: в коде есть мёртвые куски — один файл-репозиторий цен вообще не подключён к сборке и не используется, а два метода архивации/восстановления существуют, но их никто не вызывает (архивация идёт другим путём). На поведение системы сейчас не влияет, но путает при следующих сверках. \ Program.cs:186 | Разработка | Закрывает PM | Чисто техническая уборка, продуктового вопроса нет. Закрываю сам: удалить мёртвый/неиспользуемый код (или переключить архивацию на эти методы, если это целевой путь). | заказываем клининг, но попозже |
Вопросы по сверке с кодом
BRD 2.6 v0.4 · booking-monorepo · commit 326ea62/main · 2026-07-06 · якорь src/services/booking.prices Сводка: вопросов 13 (бизнесовых 6 / программных 7) · подтверждено без вопросов 6 (BR2.6-1, BR2.6-6, BR2.6-7, BR2.6-8, BR2.6-9, BR2.6-11) · отрезано находок обратного прохода 4 Под особым вниманием: BR2.6-14, BR2.6-15, BR2.6-16/8, BR2.6-5, BR2.6-13, массовые операции BR2.6-9..12. Переобход относительно c1fa925 (2026-06-23): снят вопрос по BR2.6-8 (восстановление теперь проверяет конфликты с активными ценами — требование выполнено); снят вопрос по BR2.6-7 (исключение архивных из расчёта подтверждено пруфами); углублена находка по BR2.6-3 (проверка стоимости на неиспользуемом типе); ссылки file:line перепроверены и актуализированы под текущий код.
Бизнесовые
| № | Связано с | Что в BRD | Что в коде | Где в коде |
|---|---|---|---|---|
| Б1 | BR2.6-16 | Цена «День в день» не может существовать без парной цены «Заранее» с теми же условиями; «Заранее» допустима без «День в день» (асимметрия). | Признак «День в день» (IsToday) — обычное поле без проверки парности. Ни при создании, ни при изменении, ни при восстановлении цены с IsToday=true нет проверки, требующей наличия парной цены «Заранее». Цену «День в день» можно создать в одиночку. Поиск по всему сервису логики парности/«заранее» дал ноль совпадений. — нужно решение: должна ли система блокировать одиночную «День в день» и где (Back/Front). | PriceAggregate.cs:78 (Create без проверки парности); CreatePriceCommandHandler.cs:70 |
| Б2 | BR2.6-14 | Признак «День в день» — часть условий применения; конфликтуют цены с совпадающими условиями. | В проверке конфликта цены с IsToday=true и IsToday=false считаются непересекающимися (p.IsToday == isToday). «Заранее» и «День в день» с прочими одинаковыми условиями никогда не конфликтуют между собой — это согласуется с моделью пары, но означает, что IsToday трактуется как разделитель цен, а не как обычное условие. Та же логика в расчёте (p.IsToday != isToday). — нужно подтверждение бизнеса, что это намеренно. | PriceRepository.cs:270; CalculateBookingCommandHandler.cs:289,361 |
| Б3 | BR2.6-15 | Нельзя оставить незаполненный временной слот в пределах режима работы площадки. | Проверки покрытия режима работы в сервисе цен нет. Заготовленное доменное исключение OverLappingOffSchedulesException определено, но нигде не выбрасывается; режим работы площадки в сервис цен не подгружается (поиск schedule/WorkTime/Regime по домену дал только using). Цену можно сохранить с «дырой» в расписании. — нужно решение: где должна жить проверка покрытия (Back цен / Front / сервис ресурсов). | OverLappingOffSchedulesException.cs:11 (определён, не используется); по обходу домена цен ссылок на режим работы не найдено |
| Б4 | BR2.6-5 | Доплата за превышение диапазона может быть задана только для цены с заданным диапазоном посетителей. | На бэкенде доплата (RangeOveragePricePerUnit) принимается всегда; проверки «диапазон задан» нет. Значение лишь проверяется на неотрицательность. Заготовленное RangeNotProvidedException определено, но никогда не выбрасывается. Можно сохранить доплату без диапазона. | PriceInfo.cs:26 (только проверка ≥0); RangeNotProvidedException.cs (определён, не используется) |
| Б5 | BR2.6-4 | Незаданное необязательное условие → цена применяется ко всем допустимым значениям (день недели, опция, период, диапазон). | Трактуется неоднородно. Пустой период → «все даты» (MinValue/MaxValue), пустой список опций → «любая опция». Но день недели теперь хранится массивом в слоте (миграция дней в массив), и пустой список дней/слотов в проверке конфликта означает «ни одного» (пустой Any() = ложь), а не «все дни». То есть по периоду и опциям пустое = «все», а по дню недели и слоту пустое = «ничего» — направления разные. — нужно решение: что значит пустой день недели/слот — «все» или «ни одного». | PriceRepository.cs:202 (даты = все), :272 (опции = все), :279-284 (дни/слоты = ни одного); ConvertDaysToArray.cs (дни в массив) |
| Б6 | вне BRD | В BRD нет требования об уникальности названия цены. | В коде есть отдельная проверка уникальности имени цены в паре с партнёрской зоной (IsNameUnique, регистронезависимо). В требованиях 2.6 такого условия применения или ограничения нет — код делает сверх BRD. — нужно решение: является ли уникальность названия бизнес-правилом; если да — внести в BRD, если нет — уточнить назначение проверки. | PriceRepository.cs:157 (IsNameUnique) |
Рекомендация / варианты решения
- Пара «День в день» / «Заранее» (Б1) и трактовка
IsTodayкак разделителя (Б2) — общая первопричина: признак «День в день» смоделирован как обычный флаг, а инвариант пары BRD нигде не обеспечивается. Вариант А (рекоменд.): согласовать с продуктом целевое поведение и добавить проверку наличия парной «Заранее» при создании/изменении/восстановлении «День в день» (где именно — Back или Front — решить отдельно, но одиночную «День в день» без пары не сохранять). Вариант Б: привести BRD к коду, если бизнес решает, что «День в день» самодостаточна — тогда убрать инвариант пары из BR2.6-16. Логику «IsTodayкак разделитель конфликта» (Б2) при этом оставить как есть — она непротиворечива при любом из вариантов; нужно лишь письменное подтверждение бизнеса. - Покрытие режима работы, «дыра» в расписании (Б3) — правило BRD в сервисе цен не живёт и жить в одиночку не может (нет данных о режиме работы). Вариант А (рекоменд.): согласовать, что проверка покрытия — ответственность фронта/сервиса ресурсов на этапе задания цены (у Back цен нет графика площадки), и явно вынести BR2.6-15 из зоны Back цен. Вариант Б: подтягивать режим работы в сервис цен и обеспечивать покрытие на Back — дороже, добавляет связанность с сервисом ресурсов.
- Доплата за диапазон без диапазона (Б4) — заготовка под правило есть, но оно не включено. Вариант А (рекоменд.): включить проверку «доплата задаётся только при заданном диапазоне/опции» (бросать уже существующее
RangeNotProvidedException). Вариант Б: если продукт допускает доплату без диапазона — скорректировать BR2.6-5 и удалить неиспользуемое исключение. - Неоднородная семантика пустых условий (Б5) — сейчас «пусто = все» для периода/опций и «пусто = ничего» для дня недели/слота. Вариант А (рекоменд.): согласовать с продуктом единую трактовку «пусто = все допустимые значения» (как в BR2.6-4) и привести проверку конфликта по дням/слотам к этой семантике. Вариант Б: если для дня недели/слота осознанно нужно «ничего при пустом» — отразить это исключение в BR2.6-4, чтобы правило не читалось как универсальное.
- Уникальность названия цены (Б6) — код строже BRD. Согласовать с продуктом: если уникальность имени в партнёрской зоне — намеренное бизнес-правило, внести его в требования 2.6; иначе оценить, нужна ли проверка (и снять её либо задокументировать как техническое ограничение — тогда вопрос уходит в программный блок).
Программные
| № | Связано с | Что в BRD | Что в коде | Где в коде |
|---|---|---|---|---|
| П1 | BR2.6-14 | Не допускается нескольких цен на один случай бронирования в одной зоне (конфликт по пересечению условий и слотов). | Проверка реализована только в приложении (ArePropertiesUnique): пересечение дат, диапазонов опций, временных слотов и дней; результат — список пересекающихся цен, при непустом — исключение. Уникального ограничения в базе нет (все индексы неуникальны, по price_id; в OnModelCreating нет IsUnique). При параллельных запросах две конфликтующие цены могут пройти проверку каждая в своей транзакции и сохраниться (гонка). | PriceRepository.cs:164; 20250804155738_Scopes.cs:136 (индексы не уникальны); ApplicationDbContext.cs:39-56 (нет unique-индекса) |
| П2 | BR2.6-14 | Проверка пересечения слотов должна корректно учитывать границы времени. | Условие верхней границы слота — фактически бессмысленный тернарник: (existing.TimeTo == TimeOnly.MinValue ? TimeOnly.MinValue : existing.TimeTo) > newSlot.Time.From — обе ветки дают одно и то же значение. Для слота, заканчивающегося в 00:00 (круглосуточный), верхняя граница не разворачивается в 24:00, поэтому 00:00 > начало почти всегда ложно и пересечение не фиксируется. — нужно подтверждение логики слота, заканчивающегося в 00:00. | PriceRepository.cs:282 |
| П3 | BR2.6-14 | Конфликт определяется по набору «условий применения». | В проверке конфликта ArePropertiesUnique не участвуют MinOrderDuration и Priority (не передаются в метод и не фигурируют в фильтре). Две цены, различающиеся только минимальной длительностью заказа (или только приоритетом), будут считаться конфликтующими. — нужно решение: входят ли мин. длительность и приоритет в набор условий применения для определения конфликта. | PriceRepository.cs:164-172,269-285 (нет MinOrderDuration/Priority); PriceInfo.cs:11 |
| П4 | BR2.6-3 | Цена должна содержать набор значений стоимости для слотов (стоимость как значимый параметр). | Валидация стоимости фактически не срабатывает на рабочем пути. Единственная проверка (value < 0 с текстом «Price can’t be 0 or negative») живёт в PriceWeekdayInfo, но этот тип нигде не создаётся (ноль вхождений new PriceWeekdayInfo во всём коде). Реальный путь создания/изменения цены использует TimeWithWeekdaysPrice, у которого проверок стоимости нет вовсе. Итог: при создании цены стоимость не проверяется ни на ноль, ни на отрицательность, а сам текст проверки противоречит её условию (0 проходит). — нужно решение: допустима ли нулевая/отрицательная стоимость; включить проверку на рабочем типе и согласовать текст. | PriceWeekdayInfo.cs:18 (не используется); TimeWithWeekdaysPrice.cs:17-23 (без проверок); CreatePriceCommand.cs:26 |
| П5 | BR2.6-13 | Нельзя сохранить цену с пустыми обязательными параметрами (обязательны только площадка и стоимость). | Строже BRD по одним полям и слабее по другим. Обязательность площадки/зоны обеспечивается (Create возвращает null без scope, в обработчике бросок при пустом scope), имя проверяется на непустоту, приоритет/мин.длительность/доплата — на неотрицательность. При этом Name, Priority, MinOrderDuration фактически обязательны — это строже BRD (BRD требует обязательными только площадку и стоимость). Одновременно сама «стоимость» по слоту не валидируется (см. П4). — нужно решение: считать ли название/приоритет/мин.длительность обязательными. | PriceAggregate.cs:92 (scope обязателен); PriceInfo.cs:24 (Name), :25-27 (Priority/MinOrderDuration/доплата) |
| П6 | BR2.6-12 | Результат массовой операции должен позволять определить, какие цены обработаны успешно, какие — с ошибкой. | Массовые Update/Delete выполняются в одной транзакции и бросают исключение на первой проблемной цене с откатом всего пакета — частичного результата «эти ок, эти нет» нет. Оба обработчика возвращают Unit; список успешных/ошибочных id не возвращается. — нужно решение: нужен ли частичный результат (тогда отказаться от общей транзакции в пользу пер-элементного результата). | UpdatePriceCommandHandler.cs:134 (бросок на первом конфликте), :148 (откат всего пакета); DeletePriceCommandHandler.cs:72 (возврат Unit) |
| П7 | вне BRD | В BRD не описан «мёртвый»/дублирующий код и служебные пути архивации. | Для навигации и доверия к последующим сверкам: src/Repositories/PriceRepository.cs (namespace PriceCalculator.Repositories) не входит ни в один проект и не используется — живой репозиторий цен другой (Prices.Persistence/Repositories/PriceRepository.cs, подключён в DI). Плюс методы ArchiveAsync/RestoreAsync живого репозитория (прямой ExecuteUpdate архивного флага) существуют, но обработчиками не вызываются — архивация идёт через PriceAggregate.Update по флагу. — нужно решение: удалить мёртвый код и/или неиспользуемые пути, чтобы не порождать ложные трактовки. | Repositories/PriceRepository.cs (вне сборки); Prices.Persistence/Repositories/PriceRepository.cs:103-125 (Archive/RestoreAsync не вызываются); Program.cs:186 (DI живого репозитория) |
Рекомендация / варианты решения
- Гонка при проверке уникальности (П1) — инвариант BR2.6-14 держится только на проверке в приложении внутри транзакции, без ограничения в базе. Вариант А (рекоменд.): добавить запрет дублей в базе (уникальное ограничение/частичный уникальный индекс по нормализованному набору условий применения) как страховку от гонки, оставив проверку в приложении для дружелюбных сообщений. Вариант Б: если полноценное ограничение в базе невозможно из-за сложности набора условий (диапазоны, массивы дней) — сериализовать конкурентные записи по партнёрской зоне (блокировка/advisory-lock на время проверки+вставки) и это явно задокументировать. Голая проверка в приложении без страховки — недостаточна для правила веса 1.
- Слот до полуночи (П2) и состав условий конфликта — мин. длительность/приоритет (П3) — оба про корректность самой проверки
ArePropertiesUnique. По П2: вариант А (рекоменд.) — исправить обработкуTimeTo == 00:00(трактовать как конец суток) и убрать бессмысленный тернарник; вариант Б — если 00:00 намеренно означает «полночь начала», задокументировать и покрыть тестом. По П3: сначала бизнес-решение (входят ли мин. длительность и приоритет в условия применения — см. также природу BR2.6-14), затем привести список полей вArePropertiesUniqueв соответствие с этим решением. - Стоимость не валидируется (П4) — рекоменд.: перенести проверку стоимости на реально используемый тип
TimeWithWeekdaysPrice(или в конструктор агрегата/обработчик), согласовав с продуктом допустимость нуля, и привести текст сообщения в соответствие с условием. Пока проверка стоит на неиспользуемомPriceWeekdayInfo, она не работает вовсе. - Обязательность полей строже BRD (П5) — упирается в бизнес-решение по BR2.6-13 (какие поля действительно обязательны). Вариант А (рекоменд.): согласовать перечень обязательных полей и привести код к нему (если название/приоритет/мин.длительность обязательны намеренно — обновить BR2.6-13, это вариант «привести BRD к коду»). Вариант Б: ослабить валидацию до «только площадка и стоимость», если BRD верен буквально. Связать с П4 (стоимость сейчас не проверяется вовсе).
- Нет частичного результата массовой операции (П6) — упирается в бизнес-решение по BR2.6-12 (нужна ли частичность). Вариант А: если частичный результат обязателен — отказаться от «всё или ничего» в пользу пер-элементной обработки с возвратом статуса по каждой цене (ломает текущую транзакционную атомарность). Вариант Б (рекоменд. как временный): сохранить атомарность, но возвращать в ошибке идентификаторы всех проблемных цен, чтобы вызывающая сторона могла показать, что именно не прошло. Финальный выбор — за продуктом.
- Мёртвый/дублирующий код (П7) — рекоменд.: удалить не входящий в сборку
PriceCalculator.Repositories/PriceRepository.csи неиспользуемыеArchiveAsync/RestoreAsync(или переключить архивацию на них, если это целевой путь). Снижает риск ложных выводов при будущих сверках; на поведение системы сейчас не влияет.