apps/backend-service · Техническая документация
реализовано, покрыто pricing.e2e-spec.ts + booking.e2e-spec.ts

Расчёт цены housing/booking

Не вносить изменения в логику ценообразования (в частности SQL) без подтверждения — правило зафиксировано в CLAUDE.md проекта.

Где живёт формула

Миграции и synchronize

Эти SQL-функции создаются только миграцией — synchronize строит лишь таблицы/колонки из entity, сырой SQL миграций не выполняет. На любой БД с нуля (включая e2e-стенд) без ручного прогона миграции любой расчёт цены падает 500-й ошибкой calc_children_detailed(...) does not exist. На prod/staging миграции гоняются вручную отдельным шагом. Для e2e — ensurePricingFunctions() в apps/backend-service/test/db.ts реплеит up() этой миграции в beforeAll.

Правила расчёта

unavailable_reasons

Housing.unavailable_reasons: [UnavailableReason!] — вычисляемое (не колоночное) поле, одно inline SQL-выражение в addSelections, работает одинаково и в housingWithPrice/ ownerHousingWithPrice (один объект), и в housings/ownerHousings (список, без N+1). Подзапрос считается только если поле реально запрошено. addFilters для housings получил guard: если unavailable_reasons в выборке — жёсткие WHERE-фильтры (animalsFilter/minChildAgeFilter/minNightsFilter/ rangeDateBookingFilter+availabilityFilter/guestCountFilter) не исключают объект из выдачи — объект получает причины вместо того, чтобы пропасть.

КодСмысл
PETS_NOT_ALLOWEDобъект не принимает животных
CHILD_AGE_NOT_ALLOWEDвозраст ребёнка не подходит
MIN_NIGHTS_NOT_METбронь короче минимума ночей
DATES_BOOKEDдаты заняты
NOT_ENOUGH_BEDSне хватает спальных мест

Enum-каталог: @shared/catalogs/housing/unavailable-reason.ts. Бэкенд отдаёт только код нарушения, текст/i18n — на фронте (message.unavailable_reason_<CODE> в apps/shared/src/i18n/ru.js). Поля booked/enough_beds/ nights_count удалены целиком — заменены на unavailable_reasons и клиентский расчёт (dayjs(end).diff(start,'day')) соответственно.

ORDER BY поверх unavailable_reasons

Postgres не разрешает ссылаться на алиас SELECT-списка внутри составного ORDER BY-выражения; TypeORM's addOrderBy валидирует любую raw-строку вида identifier.identifier как алиас. Рабочее решение — свой собственный addSelect(sql, 'sort_alias') (sort_dates_booked/sort_enough_beds) и ссылка на него bare-именем, без составного выражения.

prepareForBooking / findOneWithPrice

i18n-правило

Любое сообщение об ошибке, которое может дойти до пользователя ($t(error) с err.message целиком), обязано иметь перевод в apps/shared/src/i18n/ru.js — иначе показывается сырой английский ключ.

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