UC-CTL-003. Фильтрация проектов (тип / класс / регион / город / статус строительства / цена)
Назначение
Пользователь фильтрует ленту проектов по типу объекта, классу, региону, городу, статусу строительства и
ценовому диапазону. Каталог накладывает фильтры на витрину CatalogProjectCard / CatalogPropertyObjectCard
без джойнов на справочники и возвращает пересечение всех активных условий. Применённые фильтры сохраняются в
URL (шаринг) и записываются в user-activity как действие FilterApplied (ADR-0054); каталог фильтры не
персистит.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-CTL-003 |
| Контекст | catalog (см. docs/06-services/catalog/) |
| Тип | основной |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | Гость (с реф-cookie) / Инвестор / Агент / Девелопер / МенеджерПИН |
| Permissions / scope | как UC-CTL-001: гейт ленты; permissions не требуются |
| Tenant | не применяется — платформенная витрина (ADR-0052 п.3) |
| Record-level | не применяется |
Предусловие
- Выполняются предусловия UC-CTL-001; лента открыта.
- Справочники значений фильтров загружены интерфейсом (
GET /api/v1/catalog/filters): переченьProjectKind,PropertyClass,ConstructionStatus, списки регионов{RegionId, RegionName}и городов{CityId, CityName}(distinct поPublished-карточкам) и фактические границы ценmin(MinPrice)..max(MaxPrice)поPublished-карточкам.
Источник гео-значений
Своей реплики справочника handbook (регионы/города) у каталога нет: regions/cities строятся
distinct-ом пар (RegionId, RegionName) / (CityId, CityName) по Published-карточкам — id и
display-имена приходят денормализованными в снапшотах событий chessboard.
Триггер
| Поле | Значение |
|---|---|
| Источник | UI button |
| Триггерящий элемент | Кнопка «Дополнительные фильтры» → панель фильтров ленты |
Основной Поток
- Пользователь открывает панель фильтров.
- Пользователь выбирает значения: тип объекта (
kind), класс (class), регион (regionId), город (cityId), статус строительства (constructionStatus), ценовой диапазон (priceMin/priceMax) и нажимает «Применить» (или фильтры применяются автоматически после выбора). - Интерфейс сериализует фильтры в URL-параметры (
?class=Business®ionId=63&priceMax=15000000) — ссылкой можно поделиться и восстановить состояние — и запрашивает ленту с фильтрами: тип проекта (kind, enumProjectKind), класс (class, enumPropertyClass), регион (regionId, ссылка на справочникhandbook), город (cityId, та же ссылка), статус строительства (constructionStatus, enumConstructionStatus), ценовой диапазон (price, границы > 0,Price.Min <= Price.Max), а также поиск/сортировку/курсор/лимит. - Каталог применяет фильтры:
- Проверка входа.
kind/class/constructionStatus— известные значения enum;regionId/cityId— положительные числа (несуществующий id — не ошибка, а пустая выдача); границыprice> 0 и well-formedPrice.Min <= Price.Max; неизвестное значение enum / неположительный id →ValidationError(400). - Выборка. Карточки
Status = Publishedс наложением активных фильтров по денормализованным колонкам витрины (Kind,PropertyClass,RegionId,CityId,ConstructionStatus); ценовой фильтр — пересечение диапазона цен проекта с фильтром (MinPrice <= price.Max ∧ MaxPrice >= price.Min). Джойны на справочники не нужны. - Состав карточек и гостевое маскирование — как UC-CTL-001.
- Результат. Отфильтрованный список.
- Проверка входа.
- Интерфейс заменяет ленту новым набором карточек.
- Интерфейс отправляет событие применения фильтров в
user-activity: действиеFilterAppliedс контекстом фильтров (kind?,class?,regionId?,cityId?,constructionStatus?,price?) и сессией. Дом состояния сессии —user-activity(ADR-0054); контекст фильтров пробрасывается в карточку лида агента; каталог фильтры не персистит. - Кнопка «Сбросить»: интерфейс очищает URL-параметры фильтров и повторяет запрос без них — выдача
возвращается к UC-CTL-001; отправляется
FilterAppliedс пустым набором фильтров.
Альтернативные Потоки
A1. Открытие ленты по расшаренной ссылке с фильтрами
- Пользователь открывает URL с параметрами фильтров (получен от другого пользователя).
- Интерфейс восстанавливает состояние панели из URL и сразу выполняет шаги 3–5 основного потока (гейт ленты действует как в UC-CTL-001). Сценарий продолжается как основной.
A2. Фильтрация объектов недвижимости в ленте /lots
- Тот же сценарий для ленты объектов недвижимости
/lots: фильтры уровня объекта —typeName(тип объекта: квартира/апартамент/паркинг/коммерция),rooms(комнатность), ценовой диапазон по эффективнойPrice, плюс проектные фильтрыclass/regionId/cityId/constructionStatus. - Завершается как основной поток.
A3. Комбинация с поиском и сортировкой
searchTerm(UC-CTL-002) иsort(UC-CTL-004) передаются в том же запросе; предикаты объединяются черезAND. Возврат к шагу 4 основного потока.
A4. Фильтры дают пустую выдачу
- Ответ
{ items: [] }; интерфейс показывает «Ничего не найдено, ослабьте фильтры» + кнопку «Сбросить» (переход к шагу 7).
A5. Смена фильтров при активном cursor-е
- Пользователь меняет набор фильтров после подгрузки нескольких страниц.
- Изменение множества условий — это новая выдача: интерфейс сбрасывает
cursorи запрашивает первую страницу с новым набором. Keyset-cursor кодирует только ключи порядка (не состав фильтров), поэтому переиспользовать его между разными наборами фильтров нельзя — иначе страница окажется вырезанной из другого множества карточек. Возврат к шагу 4.
Постусловие
- Лента отражает пересечение всех активных фильтров; состояние фильтров восстановимо из URL.
- В
user-activityсозданActionEvent (FilterApplied)с контекстом фильтров и сессией. - Состояние каталога не изменилось (read-only).
Возможные Ошибки
| Код | HTTP | Условие | Поведение клиента |
|---|---|---|---|
ValidationError | 400 | Неизвестное значение enum-фильтра, граница price <= 0, Price.Max < Price.Min, неположительный regionId/cityId | Подсветить фильтр, не применять |
CATALOG_FEED_GATE_REQUIRED | 401 | Гейт ленты не пройден | Auth-модалка (UC-CTL-001 A1) |
Kafka Интеграционные События
| Событие | Topic | Когда публикуется | Consumers |
|---|---|---|---|
ActionEventRecorded (производит user-activity) | pin.user-activity.actions.v1 | после приёма события фильтров (шаг 6) | crm (хотелки/контекст лида) |
Acceptance Criteria
| # | Критерий | Проверка |
|---|---|---|
| AC1 | Каждый фильтр работает по отдельности: тип, класс, регион, город, статус строительства, ценовой диапазон | шесть изолированных тестов |
| AC2 | Фильтры комбинируются через AND и совместимы с searchTerm/sort | комбинированный тест |
| AC3 | Ценовой фильтр — пересечение диапазонов: проект попадает, если [MinPrice, MaxPrice] ∩ [price.Min, price.Max] ≠ ∅ | граничные значения |
| AC4 | Запрос не делает джойнов на справочники — только колонки project_cards | проверка выборки |
| AC5 | Открытие URL с параметрами фильтров воспроизводит выдачу и состояние панели | e2e-тест |
| AC6 | Применение/сброс фильтров создаёт ActionEvent (FilterApplied) в user-activity | проверка записи |
| AC7 | GET /api/v1/catalog/filters отдаёт только значения, встречающиеся у Published-карточек (регионы {RegionId, RegionName}, города {CityId, CityName}, границы цен) | тест lookup-а |
| AC8 | Смена фильтров сбрасывает пагинацию: первая страница нового набора не наследует старый cursor, страницы не пересекаются | тест смены фильтра после подгрузки |
| AC9 | Несуществующий (но положительный) regionId/cityId даёт пустую выдачу (200), а не ошибку | тест несуществующего id |
Обратные ссылки
Автоссылки: где используется этот документ.
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- API метод:
../api/public/get-projects.md,../api/public/get-property-objects.md,../api/public/get-catalog-filters.md. - Domain entity: ../domain/catalog-project-card.md
(колонки фильтров:
Kind,PropertyClass,RegionId,CityId,ConstructionStatus,MinPrice/MaxPrice), ../domain/catalog-property-object-card.md (TypeName,RoomsCount,Price). - События действий:
../../user-activity/domain/action-event.md(домActionEvent, ADR-0054). - Связанные UC: UC-CTL-001 (лента), UC-CTL-002 (поиск), UC-CTL-004 (сортировка).