GET /api/investor/crm/deals/{dealId} — Карточка сделки (инвестор)
Read-модель карточки сделки со стороны инвестора-покупателя (/profile/deals/[dealId], вкладки
«Условия/Документы/О объекте недвижимости/О проекте»): инвестор открывает свою сделку и видит объект и
полную стоимость, статус, платное бронирование, банк/эскроу, тип договора и сводку своих документов
(US-INV-02..08, US-INV-22). Без фиксаций (инвестор не видит фиксаций и агентской «кухни», README
«Ответственность»). Только чтение: дом полей агрегата — deal.md; здесь описана
проекция InvestorDealCardModel и алгоритм её сборки. «Ход сделки» (таймлайн) и полный список документов —
отдельные постраничные методы (см. «Побочные эффекты»).
Назначение
Дать инвестору карточку одной своей сделки по dealId: заголовок и коарс-статус (US-INV-03), дату
последнего обновления (US-INV-04), объект и полную стоимость объекта недвижимости (US-INV-05), сумму и статус
платного бронирования (US-INV-06), банк и статус эскроу (US-INV-07/19, номер счёта — маска ****NNNN), тип
договора ДДУ/ДКП (US-INV-08), отметку «Ознакомлен» (US-INV-18), готовность чата сделки (US-INV-22) и сводку
своих документов с их статусами (US-INV-12/13/14). Вкладка «Условия» = финансовая сводка ниже; «Документы» =
сводка Documents; «О объекте недвижимости»/«О проекте» — данные chessboard/catalog, отдельные запросы.
| Поле | Значение |
|---|
| Сервис/контекст | crm |
| API area | investor |
| Feature group | deals |
| Статус | draft |
| Документ | docs/06-services/crm/api/investor/get-deal.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (Инвестор — только свои сделки) |
| Auth policy / Permissions | crm.deals.view (JWT инвестора); фактический доступ ограничен record-level self |
| Scope (RBAC) | самообслуживание: инвестор видит только записи с PrimaryInvestorUserId = actor (не organization-scope) |
| Record-level | DealEntity.PrimaryInvestorUserId = ActorUserId — участник-покупатель; чужая/несуществующая сделка → CRM_DEAL_NOT_FOUND |
| Tenant isolation | Запрос не tenanted (Deal.TenantId = DeveloperCompanyId ≠ актор): чтение по явному record-level предикату PrimaryInvestorUserId = ActorUserId (ADR-0052) |
| Audit | read; EscrowAccountNumber — Sensitive: наружу маска ****NNNN, в логи запрещён |
Инвестор видит только свои сделки (PrimaryInvestorUserId = ActorUserId) — не organization-scope и не
tenant-scope застройщика. Фиксации, DealMemberEntity и клиентская карточка агентства инвестору не
раскрываются. Полный номер эскроу-счёта наружу не отдаётся — только маска ****NNNN.
HTTP-контракт
| Поле | Значение |
|---|
| Method | GET |
| Route | /api/investor/crm/deals/{dealId} |
| Success status | 200 |
| Idempotency header | не применяется (GET — безопасный, семантически идемпотентный) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
DealId | route {dealId} | Guid | Да | != Guid.Empty | — |
ActorUserId | identity claims | Guid | Да | JWT инвестора | record-level (PrimaryInvestorUserId) |
Модель ответа InvestorDealCardModel
Проекция агрегата; дом полей — deal.md (VO Money; enum DealStatus/DealContractType/
PaymentMethod/EscrowStatus/DealChatStatus; группа этапа — pipeline-step.md).
Ниже — только дельта инвесторской проекции; все enum сериализуются строкой.
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
DealId | Guid | Да | Id | — | Идентификатор сделки (dealId маршрута). |
Title | string | Да | Title | — | Заголовок карточки («Объект №…, ЖК …»). |
Status | DealStatus | Да | Status | — | Application/Booking/Sale/Completed/Cancelled (US-INV-03). |
StepGroup | PipelineStepGroup | Да | StepGroup | — | Коарс-стадия воронки для покупателя (кастомный StepId застройщика наружу не отдаём). |
PropertyObjectId | Guid | Да | PropertyObjectId | — | Объект сделки (chessboard); display-данные — отдельный запрос вкладки «О объекте недвижимости». |
ProjectId | Guid | Да | ProjectId | — | Проект объекта (вкладка «О проекте»). |
BuildingId | Guid? | Нет | BuildingId | — | Здание/литер. |
Finance | InvestorDealFinanceModel | Да | computed (см. ниже) | — | Вкладка «Условия»: цена, оплата, договор, банк/эскроу. |
CurrentBooking | InvestorBookingModel? | Нет | бронь по BookingId | — | Текущая бронь (US-INV-06); null — активной брони нет. |
Documents | InvestorDocumentsSummaryModel | Да | computed по crm.deal_documents | — | Сводка вкладки «Документы» (статусы + индикатор «загрузите», US-INV-16). |
ChatStatus | DealChatStatus | Да | ChatStatus | — | Готовность чата сделки (проекция messenger, US-INV-22, ADR-0055). |
TermsAcknowledgedAt | DateTimeOffset? | Нет | TermsAcknowledgedAt | — | Отметка инвестора «Ознакомлен» (US-INV-18). |
CancelReason | string? | Нет | CancelReason | — | Причина отказа (при Status = Cancelled). |
CreatedAt | DateTimeOffset | Да | CreatedAt | — | Создание сделки. |
UpdatedAt | DateTimeOffset | Да | UpdatedAt | — | Дата последнего обновления (US-INV-04). |
InvestorDealFinanceModel
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
Price | Money (owned: Amount,Currency) | Да | Deal.Price (snapshot chessboard) | — | Полная стоимость объекта недвижимости (US-INV-05). |
PaymentMethod | PaymentMethod? (enum→string) | Нет | Deal.PaymentMethod | — | Способ оплаты (purchase-program.md). |
InstallmentPresetId | Guid? | Нет | Deal.InstallmentPresetId | — | Пресет рассрочки (при PaymentMethod = Installment). |
ContractType | DealContractType? (enum→string) | Нет | Deal.ContractType | — | Ddu/Dkp (US-INV-08). |
BankId | Guid? | Нет | Deal.BankId | — | Банк сделки/эскроу; null → UI «Не указан» (US-INV-07). |
EscrowStatus | EscrowStatus (enum→string) | Да | Deal.EscrowStatus | — | None/Provided («Эскроу забронирован», US-INV-19). |
EscrowAccountNumber | string? | Нет | Deal.EscrowAccountNumber | маска ****NNNN (последние 4) | Эскроу-счёт; полный номер инвестору не отдаётся — только SellManager/PinManager (deal.md). |
InvestorBookingModel
Дом — booking.md; проекция текущей брони (US-INV-06/10).
| Поле | Тип | Обязательность | Источник | Описание |
|---|
BookingId | Guid | Да | BookingEntity.Id | Идентификатор брони. |
Status | BookingStatus (enum→string) | Да | Status | Reserved/AwaitingPayment/Booked/… |
ProgramName | string | Да | ProgramTerms.Name | Название программы бронирования (snapshot). |
IsPaid | bool | Да | ProgramTerms.IsPaid | Платная ли программа. |
PaymentStatus | BookingPaymentStatus (enum→string) | Да | PaymentStatus | NotRequired/Pending/Paid (US-INV-06). |
PaymentAmount | Money? (owned) | Нет | PaymentAmount | Сумма платного бронирования (US-INV-06). |
PaidAt | DateTimeOffset? | Нет | PaidAt | Дата подтверждения оплаты («Оплачено» + дата). |
ReserveExpiresAt | DateTimeOffset | Да | ReserveExpiresAt | Дедлайн резерва. |
BookingExpiresAt | DateTimeOffset? | Нет | BookingExpiresAt | Дедлайн подтверждённой брони. |
InvestorDocumentsSummaryModel
Дом — deal-document.md; скалярная сводка вкладки по документам, видимым
инвестору-участнику (record-level как в deal-document.md). Полный список —
отдельный постраничный метод.
| Поле | Тип | Обязательность | Источник | Описание |
|---|
RequiresUpload | bool | Да | RequestedCount > 0 | Индикатор «загрузите документы» (US-INV-16). |
RequestedCount | int | Да | Count(Status = Requested) | Запрошено, файла нет. |
UploadedCount | int | Да | Count(Status ∈ {Uploaded, UnderReview}) | Загружено / на проверке (US-INV-13). |
AcceptedCount | int | Да | Count(Status = Accepted) | Принято (US-INV-14). |
RejectedCount | int | Да | Count(Status = Rejected) | Отклонено — нужна повторная загрузка (US-INV-14). |
Алгоритм
- Контекст и доступ. Операцию выполняет инвестор с permission
crm.deals.view; ActorUserId — из
токена. Запрос не tenanted (у инвестора нет TenantId = DeveloperCompanyId сделки).
- Проверка входа и предусловий.
DealId != Guid.Empty, иначе VALIDATION_FAILED (400). Существование
и принадлежность — при загрузке; чтение разрешено во всех статусах, включая терминальные
Completed/Cancelled.
- Что читаем.
- сделку — строго по record-level предикату
Id == DealId && PrimaryInvestorUserId == ActorUserId && DeletedAt == null (scope-bypass developer-tenant, ADR-0052). Нет совпадения → CRM_DEAL_NOT_FOUND
(чужую/несуществующую/удалённую сделку не раскрываем). Фиксации, DealMemberEntity и клиентская
карточка агентства не читаются;
- текущую бронь: если
deal.BookingId != null — бронь по BookingId (проекция в InvestorBookingModel);
иначе null;
- сводку документов: скалярные счётчики по статусам документов сделки, видимых инвестору (без выборки
строк) →
InvestorDocumentsSummaryModel; RequiresUpload = RequestedCount > 0. Полный список документов
и таймлайн здесь не загружаются (отдельные вкладки).
- Бизнес-правила и маппинг. Переходов состояний нет. Маппинг поле-в-поле
deal →
InvestorDealCardModel + Finance/CurrentBooking/Documents (таблицы выше); Money → owned
Amount/Currency. Маскирование эскроу: Finance.EscrowAccountNumber = ****NNNN (последние 4 цифры;
полный номер инвестору не отдаётся — deal.md). Enum-поля сериализуются строкой.
- Результат.
InvestorDealCardModel → HTTP 200. Чтение согласовано на момент запроса (агрегат + бронь
- счётчики документов;
ChatStatus — eventual-проекция из messenger). Ошибки → 404/400.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|
| Integration events | none (read-only GET) |
| External calls | none в самом методе (display объекта недвижимости/проекта/банка — отдельные запросы к chessboard/catalog) |
| Cache / projections / search | none (прямое чтение агрегата, брони, скалярных счётчиков документов) |
| Notifications | none |
| Отдельные запросы вкладок | «Ход сделки» — таймлайн (get-deal-timeline.md); полный список документов — постраничный метод по DealId. В этот ответ не входят. |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
VALIDATION_FAILED | 400 | DealId = Guid.Empty | «Некорректный идентификатор сделки» | no retry |
UNAUTHORIZED | 401 | нет/просрочен JWT | «Требуется авторизация» | no retry |
FORBIDDEN | 403 | нет permission crm.deals.view | «Доступ запрещён» | no retry |
CRM_DEAL_NOT_FOUND | 404 | сделки нет, PrimaryInvestorUserId не совпал или удалена | «Сделка не найдена» | no retry |
Чужая сделка намеренно даёт 404, а не 403: существование сделки другого инвестора не раскрывается.
Acceptance Criteria
- Given своя сделка, When инвестор запрашивает карточку, Then возвращается
InvestorDealCardModel с Finance, CurrentBooking?, Documents; EscrowAccountNumber — маска ****NNNN; кастомный StepId наружу не отдаётся (только StepGroup); ответ 200.
- Given
deal.BookingId == null, When запрос, Then CurrentBooking = null, остальная карточка заполнена.
- Given
Status ∈ {Completed, Cancelled}, When запрос, Then карточка читается, при Cancelled заполнен CancelReason.
- Given
PrimaryInvestorUserId != actor, сделка удалена или не существует, When запрос, Then CRM_DEAL_NOT_FOUND (404) — а не 403 (существование чужой сделки не раскрывается).
- Given нет permission
crm.deals.view, When запрос, Then FORBIDDEN (403); пустой DealId → VALIDATION_FAILED (400).
- Given у сделки есть документы в статусе
Requested, When запрос, Then Documents.RequiresUpload = true и счётчики (RequestedCount/UploadedCount/AcceptedCount/RejectedCount) соответствуют статусам; полный список документов и таймлайн в ответ не входят (отдельные методы).
- Given проекция чата из
messenger (ADR-0055) отстаёт, When запрос, Then ChatStatus — eventual-проекция; допустима микрорассинхронизация с прочими полями, консистентными на момент запроса.
Совместимость и наблюдаемость
- Новые optional поля
InvestorDealCardModel/подмоделей — additive (без breaking change); удаление/
переименование/смена типа — breaking (migration + rollout). Enum-поля (DealStatus/PaymentMethod/
DealContractType/EscrowStatus/BookingStatus/BookingPaymentStatus/…) сериализуются строкой; клиент
обязан толерантно обрабатывать неизвестное значение.
- Logs:
investor deal-card viewed (DealId, PrimaryInvestorUserId; EscrowAccountNumber не пишется); metrics:
crm_investor_operations_total{operation="get-deal",result}, latency p95 (< 500 мс, SLO чтений README);
traces — OTel, correlation через traceparent; dashboard pin-crm; runbook —
docs/06-services/crm/runbooks/ (будущий путь по конвенции).
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы