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

PUT /api/admin/v1/property-object-types/{groupId} — Upsert настройки типа объекта недвижимости (admin)

Назначение

Позволяет администратору платформы создать или полностью заместить настройку типа объекта недвижимости — агрегат PropertyObjectTypeConfig по натуральному ключу GroupId (группа планировок LayoutGroup из chessboard); таб «типы объектов недвижимости». Заполненные коэффициенты замещают платформенные (economic-model-config.md), null — «наследовать». Помесячные оверрайды этим методом не меняются — отдельная операция put-month-overrides.md.

Обратная операция — DELETE /api/admin/v1/property-object-types/{groupId} — симметрична и описана дельтой здесь (отдельный документ не заводится): физическое удаление строки + каскад помесячных оверрайдов (мягкого удаления нет — ../../domain/property-object-type-config.md), доменное событие PropertyObjectTypeConfigDeletedEvent, расчёт по типу откатывается к платформенному уровню; 404 INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_NOT_FOUND, если настройки нет; успех — 204.

Metadata

ПолеЗначение
Сервис/контекстinvest-analytics
API areaadmin (ADR-0053)
Feature groupadmin-property-object-types
Статусdraft
Документdocs/06-services/invest-analytics/api/admin/put-property-object-type.md

Актор и доступ

ПроверкаЗначение
Actor typeOperator(Admin) — администратор платформы (доступ через admin API, m2m client-credentials)
Auth policy / Permissionspolicy AdminSystemOnly (клиент pin-admin-system); каталожное соответствие — permission invest-analytics.property-object-types.manage
Scope (RBAC)платформа
Tenant isolationне применяется — платформенная настройка (ADR-0052 п.3)
Auditобязателен: аудит правок admin API — актор, groupId, значения до/после; штампуются Version; поднимаются доменные события

HTTP-контракт

ПолеЗначение
MethodPUT (delete-дельта: DELETE, success 204)
Route/api/admin/v1/property-object-types/{groupId}
Success status200
Idempotency headerне применяется — PUT идемпотентен по семантике полного замещения
Correlationсквозной trace через traceparent; X-Admin-User-Id — обязателен

Входные данные / параметры запроса

Диапазоны заполненных коэффициентов — INV-MC2/INV-LT2 (дома economic-model-config.md, property-object-type-config.md); null = «наследовать от платформы».

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
GroupIdroutelongДа> 0; существование группы планировок в chessboard — шаг проверки (INV-LT1, только при создании)
Namebody namestringДанепустое после trim, ≤ 128 символов (INV-LT4)
MarketPremiumbody marketPremiumdecimal?Нетпри not null: >= 0 && <= 5, конечное (INV-LT3)
PriceGrowthMonthlybody priceGrowthMonthlydecimal?Нетпри not null: > -1 && <= 1
DailyRentM2body dailyRentM2decimal?Нетпри not null: >= 0
RentGrowthMonthlybody rentGrowthMonthlydecimal?Нетпри not null: > -1 && <= 1
ExpenseSharebody expenseSharedecimal?Нетпри not null: >= 0 && <= 1
AppreciationTaxMonthlybody appreciationTaxMonthlydecimal?Нетпри not null: >= 0 && <= 1 (легаси-оверрайд)
EbitdaTaxMonthlybody ebitdaTaxMonthlydecimal?Нетпри not null: >= 0 && <= 1 (легаси-оверрайд)
Occupancybody occupancydecimal?Нетпри not null: >= 0 && <= 1
HorizonMonthsbody horizonMonthsint?Нетпри not null: >= 1 && <= 360
ExpectedVersionbody expectedVersionlong?Нетесли задан и строка существует и Version не совпал → 409; null — без проверки
AdminUserIdheader X-Admin-User-IdGuidДа!= Guid.Emptyaudit-stamping

Модель ответа PropertyObjectTypeItemModel

Та же item-модель, что в list-property-object-types.md (состояние после сохранения: новые значения, version инкрементирован; monthOverridesCount — текущий счётчик, этой командой не меняется).

Алгоритм

  1. Контекст и доступ. Администратор платформы через admin API (policy AdminSystemOnly); scope — платформа; право invest-analytics.property-object-types.manage; заголовок X-Admin-User-Id → аудит.
  2. Проверка входа. Name непустое ≤ 128 символов (INV-LT4), диапазоны not null-коэффициентов (INV-LT3/INV-MC2), GroupId > 0 — иначе ValidationError (400). При создании (строки нет) — группа планировок должна существовать в chessboard (INV-LT1, запрос GET layout-groups/{groupId}, timeout 2 с): если нет → INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_GROUP_NOT_FOUND (422); если chessboard недоступен → INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_SOURCE_UNAVAILABLE (503, создание без проверки запрещено). При обновлении существующей строки проверка не повторяется.
  3. Что читаем. Существующую настройку типа по GroupId (nullable — upsert). Children-оверрайды не загружаются (не мутируются).
  4. Бизнес-правила и переходы. Если ExpectedVersion задан, строка существует и её Version не совпал → INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_CONFLICT (409). Маппинг поле-в-поле: Name и десять nullable-коэффициентов ← тело (полное замещение: отсутствие поля = null = «наследовать»); Version инкрементируется (+1, или 1 при создании); UpdatedByUserId ← администратор; UpdatedAt ← текущее время. При создании появляется новая настройка типа. Агрегат поднимает доменное событие PropertyObjectTypeConfigUpsertedEvent.
  5. Что меняется. Строка типа создаётся или обновляется по натуральному ключу GroupId. Конкурентная правка двух администраторов → 409 INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_CONFLICT. Delete-дельта: физическое удаление строки, каскад удаляет помесячные оверрайды; поднимается PropertyObjectTypeConfigDeletedEvent.
  6. Какие факты/события фиксируются. Доменное событие PropertyObjectTypeConfigUpsertedEvent (при DELETE — PropertyObjectTypeConfigDeletedEvent) → аудит-лог admin-правок. Интеграционное событие EconomicModelChangedKafkaEvent (топик pin.invest-analytics.model-changed.v1, ChangeScope = PropertyObjectType, GroupId, ChangeKind = Upserted/Deleted) — для пересчёта метрик объектов этого типа (../../events/model-changed.md).
  7. Согласованность. Агрегат, штампы, запись аудита и исходящее событие model-changed фиксируются в одном согласованном изменении.
  8. Результат. Настройка типа после сохранения (новые значения, инкрементированная ревизия). DELETE → 204 без тела.

Диаграмма

Побочные эффекты

ТипДетали
Интеграционные событияEconomicModelChangedKafkaEvent (pin.invest-analytics.model-changed.v1, scope PropertyObjectType, Upserted/Deleted) — триггер пересчёта метрик
Аудитдоменные события PropertyObjectTypeConfigUpsertedEvent / PropertyObjectTypeConfigDeletedEvent → аудит-лог admin-правок
Внешние вызовыchessboard — только при создании (проверка INV-LT1; timeout 2 с, 1 retry)
Кэш / проекции / поискnone — значения действуют со следующего расчёта серии

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
ValidationError400Нарушение диапазонов / пустой name / заголовкиОшибка валидации запросаno retry
FORBIDDEN403Клиент не pin-admin-system / нет policyДоступ запрещёнno retry
INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_NOT_FOUND404Только DELETE: настройка типа не существуетНастройка типа не найденаno retry
INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_GROUP_NOT_FOUND422Создание: группа планировок не существует в chessboard (INV-LT1)Группа планировок не найденаno retry
INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_CONFLICT409ExpectedVersion не совпал / конфликт конкурентной правкиНастройка изменена другим администратором — обновите данныеretry после GET
INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_SOURCE_UNAVAILABLE503chessboard недоступен при проверке созданияПроверка группы временно недоступнаretry с backoff

Совместимость и наблюдаемость

  • Additive-поля тела/ответа — без breaking; сужение диапазонов, удаление полей — breaking. PUT — полное замещение: клиент обязан присылать все сохраняемые значения (отсутствие поля = сброс в null/наследование) — правило контракта, менять нельзя без версии.
  • Logs: admin-property-object-type-upserted / admin-property-object-type-deleted (adminUserId, groupId, version). Metrics: invest_analytics_admin_operations_total{operation="put-property-object-type"|"delete-property-object-type"}, конфликты. Traces — сквозной trace через traceparent.

Acceptance Criteria

  • Given группа планировок GroupId существует в chessboard, настройки типа ещё нет, тело валидно, When администратор шлёт PUT, Then 200 с созданной настройкой (version = 1); chessboard опрошен ровно один раз (INV-LT1); событие model-changed (PropertyObjectType, Upserted, GroupId) публикуется атомарно.
  • Given создание, но группа планировок в chessboard не найдена (INV-LT1), When PUT, Then 422 INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_GROUP_NOT_FOUND; настройка не создаётся.
  • Given создание, но chessboard недоступен, When PUT, Then 503 INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_SOURCE_UNAVAILABLE (создание без проверки запрещено; retry с backoff).
  • Given настройка типа уже существует, When PUT-обновление, Then проверка группы в chessboard не повторяется, version инкрементируется, коэффициенты полностью замещаются (отсутствие поля = null = «наследовать»).
  • Given name пусто/длиннее 128 символов (INV-LT4) или коэффициент вне INV-LT3/INV-MC2, When PUT, Then 400 ValidationError.
  • Given expectedVersion не совпал или конкурентная правка, When PUT, Then 409 INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_CONFLICT.
  • Given настройка типа существует, When администратор шлёт DELETE, Then 204, строка удалена физически, помесячные оверрайды удалены каскадом, событие (PropertyObjectType, Deleted, GroupId) публикуется; расчёт по объектам этого типа откатывается к платформенному уровню.
  • Given настройки типа нет, When DELETE, Then 404 INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_NOT_FOUND.

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

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

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