Перейти к основному содержимому

UC-CTL-003. Фильтрация проектов (тип / класс / регион / город / статус строительства / цена)

Назначение

Пользователь фильтрует ленту проектов по типу объекта, классу, региону, городу, статусу строительства и ценовому диапазону. Каталог накладывает фильтры на витрину CatalogProjectCard / CatalogPropertyObjectCard без джойнов на справочники и возвращает пересечение всех активных условий. Применённые фильтры сохраняются в URL (шаринг) и записываются в user-activity как действие FilterApplied (ADR-0054); каталог фильтры не персистит.

Metadata

ПолеЗначение
IDUC-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
Триггерящий элементКнопка «Дополнительные фильтры» → панель фильтров ленты

Основной Поток

  1. Пользователь открывает панель фильтров.
  2. Пользователь выбирает значения: тип объекта (kind), класс (class), регион (regionId), город (cityId), статус строительства (constructionStatus), ценовой диапазон (priceMin/priceMax) и нажимает «Применить» (или фильтры применяются автоматически после выбора).
  3. Интерфейс сериализует фильтры в URL-параметры (?class=Business&regionId=63&priceMax=15000000) — ссылкой можно поделиться и восстановить состояние — и запрашивает ленту с фильтрами: тип проекта (kind, enum ProjectKind), класс (class, enum PropertyClass), регион (regionId, ссылка на справочник handbook), город (cityId, та же ссылка), статус строительства (constructionStatus, enum ConstructionStatus), ценовой диапазон (price, границы > 0, Price.Min <= Price.Max), а также поиск/сортировку/курсор/лимит.
  4. Каталог применяет фильтры:
    • Проверка входа. kind/class/constructionStatus — известные значения enum; regionId/cityId — положительные числа (несуществующий id — не ошибка, а пустая выдача); границы price > 0 и well-formed Price.Min <= Price.Max; неизвестное значение enum / неположительный id → ValidationError (400).
    • Выборка. Карточки Status = Published с наложением активных фильтров по денормализованным колонкам витрины (Kind, PropertyClass, RegionId, CityId, ConstructionStatus); ценовой фильтр — пересечение диапазона цен проекта с фильтром (MinPrice <= price.Max ∧ MaxPrice >= price.Min). Джойны на справочники не нужны.
    • Состав карточек и гостевое маскирование — как UC-CTL-001.
    • Результат. Отфильтрованный список.
  5. Интерфейс заменяет ленту новым набором карточек.
  6. Интерфейс отправляет событие применения фильтров в user-activity: действие FilterApplied с контекстом фильтров (kind?, class?, regionId?, cityId?, constructionStatus?, price?) и сессией. Дом состояния сессии — user-activity (ADR-0054); контекст фильтров пробрасывается в карточку лида агента; каталог фильтры не персистит.
  7. Кнопка «Сбросить»: интерфейс очищает URL-параметры фильтров и повторяет запрос без них — выдача возвращается к UC-CTL-001; отправляется FilterApplied с пустым набором фильтров.

Альтернативные Потоки

A1. Открытие ленты по расшаренной ссылке с фильтрами

  1. Пользователь открывает URL с параметрами фильтров (получен от другого пользователя).
  2. Интерфейс восстанавливает состояние панели из URL и сразу выполняет шаги 3–5 основного потока (гейт ленты действует как в UC-CTL-001). Сценарий продолжается как основной.

A2. Фильтрация объектов недвижимости в ленте /lots

  1. Тот же сценарий для ленты объектов недвижимости /lots: фильтры уровня объекта — typeName (тип объекта: квартира/апартамент/паркинг/коммерция), rooms (комнатность), ценовой диапазон по эффективной Price, плюс проектные фильтры class/regionId/cityId/constructionStatus.
  2. Завершается как основной поток.

A3. Комбинация с поиском и сортировкой

  1. searchTerm (UC-CTL-002) и sort (UC-CTL-004) передаются в том же запросе; предикаты объединяются через AND. Возврат к шагу 4 основного потока.

A4. Фильтры дают пустую выдачу

  1. Ответ { items: [] }; интерфейс показывает «Ничего не найдено, ослабьте фильтры» + кнопку «Сбросить» (переход к шагу 7).

A5. Смена фильтров при активном cursor-е

  1. Пользователь меняет набор фильтров после подгрузки нескольких страниц.
  2. Изменение множества условий — это новая выдача: интерфейс сбрасывает cursor и запрашивает первую страницу с новым набором. Keyset-cursor кодирует только ключи порядка (не состав фильтров), поэтому переиспользовать его между разными наборами фильтров нельзя — иначе страница окажется вырезанной из другого множества карточек. Возврат к шагу 4.

Постусловие

  • Лента отражает пересечение всех активных фильтров; состояние фильтров восстановимо из URL.
  • В user-activity создан ActionEvent (FilterApplied) с контекстом фильтров и сессией.
  • Состояние каталога не изменилось (read-only).

Возможные Ошибки

КодHTTPУсловиеПоведение клиента
ValidationError400Неизвестное значение enum-фильтра, граница price <= 0, Price.Max < Price.Min, неположительный regionId/cityIdПодсветить фильтр, не применять
CATALOG_FEED_GATE_REQUIRED401Гейт ленты не пройден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проверка записи
AC7GET /api/v1/catalog/filters отдаёт только значения, встречающиеся у Published-карточек (регионы {RegionId, RegionName}, города {CityId, CityName}, границы цен)тест lookup-а
AC8Смена фильтров сбрасывает пагинацию: первая страница нового набора не наследует старый cursor, страницы не пересекаютсятест смены фильтра после подгрузки
AC9Несуществующий (но положительный) regionId/cityId даёт пустую выдачу (200), а не ошибкутест несуществующего id

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные Документы

  • 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 (сортировка).