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.
| Поле | Значение |
|---|
| Сервис/контекст | invest-analytics |
| API area | admin (ADR-0053) |
| Feature group | admin-property-object-types |
| Статус | draft |
| Документ | docs/06-services/invest-analytics/api/admin/put-property-object-type.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | Operator(Admin) — администратор платформы (доступ через admin API, m2m client-credentials) |
| Auth policy / Permissions | policy AdminSystemOnly (клиент pin-admin-system); каталожное соответствие — permission invest-analytics.property-object-types.manage |
| Scope (RBAC) | платформа |
| Tenant isolation | не применяется — платформенная настройка (ADR-0052 п.3) |
| Audit | обязателен: аудит правок admin API — актор, groupId, значения до/после; штампуются Version; поднимаются доменные события |
HTTP-контракт
| Поле | Значение |
|---|
| Method | PUT (delete-дельта: DELETE, success 204) |
| Route | /api/admin/v1/property-object-types/{groupId} |
| Success status | 200 |
| Idempotency header | не применяется — PUT идемпотентен по семантике полного замещения |
| Correlation | сквозной trace через traceparent; X-Admin-User-Id — обязателен |
Входные данные / параметры запроса
Диапазоны заполненных коэффициентов — INV-MC2/INV-LT2 (дома
economic-model-config.md,
property-object-type-config.md); null = «наследовать от платформы».
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
GroupId | route | long | Да | > 0; существование группы планировок в chessboard — шаг проверки (INV-LT1, только при создании) | — |
Name | body name | string | Да | непустое после trim, ≤ 128 символов (INV-LT4) | — |
MarketPremium | body marketPremium | decimal? | Нет | при not null: >= 0 && <= 5, конечное (INV-LT3) | — |
PriceGrowthMonthly | body priceGrowthMonthly | decimal? | Нет | при not null: > -1 && <= 1 | — |
DailyRentM2 | body dailyRentM2 | decimal? | Нет | при not null: >= 0 | — |
RentGrowthMonthly | body rentGrowthMonthly | decimal? | Нет | при not null: > -1 && <= 1 | — |
ExpenseShare | body expenseShare | decimal? | Нет | при not null: >= 0 && <= 1 | — |
AppreciationTaxMonthly | body appreciationTaxMonthly | decimal? | Нет | при not null: >= 0 && <= 1 (легаси-оверрайд) | — |
EbitdaTaxMonthly | body ebitdaTaxMonthly | decimal? | Нет | при not null: >= 0 && <= 1 (легаси-оверрайд) | — |
Occupancy | body occupancy | decimal? | Нет | при not null: >= 0 && <= 1 | — |
HorizonMonths | body horizonMonths | int? | Нет | при not null: >= 1 && <= 360 | — |
ExpectedVersion | body expectedVersion | long? | Нет | если задан и строка существует и Version не совпал → 409; null — без проверки | — |
AdminUserId | header X-Admin-User-Id | Guid | Да | != Guid.Empty | audit-stamping |
Модель ответа PropertyObjectTypeItemModel
Та же item-модель, что в list-property-object-types.md
(состояние после сохранения: новые значения, version инкрементирован; monthOverridesCount — текущий
счётчик, этой командой не меняется).
Алгоритм
- Контекст и доступ. Администратор платформы через admin API (policy
AdminSystemOnly); scope —
платформа; право invest-analytics.property-object-types.manage; заголовок X-Admin-User-Id → аудит.
- Проверка входа.
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, создание без проверки
запрещено). При обновлении существующей строки проверка не повторяется.
- Что читаем. Существующую настройку типа по
GroupId (nullable — upsert). Children-оверрайды не
загружаются (не мутируются).
- Бизнес-правила и переходы. Если
ExpectedVersion задан, строка существует и её Version не совпал
→ INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_CONFLICT (409). Маппинг поле-в-поле: Name и десять
nullable-коэффициентов ← тело (полное замещение: отсутствие поля = null = «наследовать»); Version
инкрементируется (+1, или 1 при создании); UpdatedByUserId ← администратор; UpdatedAt ← текущее
время. При создании появляется новая настройка типа. Агрегат поднимает доменное событие
PropertyObjectTypeConfigUpsertedEvent.
- Что меняется. Строка типа создаётся или обновляется по натуральному ключу
GroupId. Конкурентная
правка двух администраторов → 409 INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_CONFLICT. Delete-дельта:
физическое удаление строки, каскад удаляет помесячные оверрайды; поднимается
PropertyObjectTypeConfigDeletedEvent.
- Какие факты/события фиксируются. Доменное событие
PropertyObjectTypeConfigUpsertedEvent (при
DELETE — PropertyObjectTypeConfigDeletedEvent) → аудит-лог admin-правок. Интеграционное событие
EconomicModelChangedKafkaEvent (топик pin.invest-analytics.model-changed.v1,
ChangeScope = PropertyObjectType, GroupId, ChangeKind = Upserted/Deleted) — для пересчёта метрик
объектов этого типа (../../events/model-changed.md).
- Согласованность. Агрегат, штампы, запись аудита и исходящее событие
model-changed фиксируются в
одном согласованном изменении.
- Результат. Настройка типа после сохранения (новые значения, инкрементированная ревизия). 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 |
|---|
ValidationError | 400 | Нарушение диапазонов / пустой name / заголовки | Ошибка валидации запроса | no retry |
FORBIDDEN | 403 | Клиент не pin-admin-system / нет policy | Доступ запрещён | no retry |
INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_NOT_FOUND | 404 | Только DELETE: настройка типа не существует | Настройка типа не найдена | no retry |
INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_GROUP_NOT_FOUND | 422 | Создание: группа планировок не существует в chessboard (INV-LT1) | Группа планировок не найдена | no retry |
INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_CONFLICT | 409 | ExpectedVersion не совпал / конфликт конкурентной правки | Настройка изменена другим администратором — обновите данные | retry после GET |
INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_SOURCE_UNAVAILABLE | 503 | chessboard недоступен при проверке создания | Проверка группы временно недоступна | 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.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы