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

GET /api/agent/crm/deals/{dealId} — Карточка сделки (агент)

Read-модель карточки сделки агентской стороны (/crm/deals/[dealId], вкладки «Условия/Об объекте/О проекте/ Документы»): агент (или руководитель его агентства) открывает сделку любого своего клиента и видит финансовую сводку, участников, текущую бронь и сводку документов (US-AGT-02/03). Только чтение: дом полей агрегата — deal.md. Таймлайн («Ход сделки») и полный список документов — отдельные paged-методы (см. «Побочные эффекты»).

Назначение

Дать агенту карточку одной сделки по dealId: финансовые условия (цена, банк/эскроу, способ оплаты, предупреждение о неоплаченной брони), активный состав участников, текущее бронирование и индикатор «Требуется загрузка» по документам (US-AGT-06).

Metadata

ПолеЗначение
Сервис/контекстcrm
API areaagent
Feature groupdeals
Статусdraft
Документdocs/06-services/crm/api/agent/get-deal.md

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

ПроверкаЗначение
Actor typeUser (Агент — свои сделки; Руководитель агентства — весь OriginAgencyCompanyId; Менеджер ПИН — platform-scope)
Auth policy / Permissionscrm.deals.view (JWT)
Scope (RBAC)организация (агентство) — OriginAgencyCompanyId
Record-levelактор — активный участник сделки (обычно Executor) или руководитель агентства-владельца (DealEntity.OriginAgencyCompanyId = actor.CompanyId)
Tenant isolationЗапрос не tenanted (Deal.TenantId = DeveloperCompanyId ≠ актор): чтение агентской стороны по явному предикату OriginAgencyCompanyId + членство участника-исполнителя (ADR-0052)
Auditread; EscrowAccountNumber — маска ****NNNN (Sensitive); телефоны/PII в логи не пишутся
Доступ

Сделка живёт в tenant застройщика (Deal.TenantId = DeveloperCompanyId), а не агентства актора — запрос не tenanted. Агентская сторона читает сделку по явному предикату OriginAgencyCompanyId (ADR-0052) плюс record-level: рядовой агент должен быть активным участником-исполнителем сделки, руководитель агентства (IsAgencyManager) видит любую сделку своего OriginAgencyCompanyId без этого условия.

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/agent/crm/deals/{dealId}
Success status200
Idempotency headerне применяется (GET — безопасный)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
DealIdrouteGuidДа!= Guid.Empty
ActorUserIdidentity claimsGuidДаJWT агентаrecord-level (владелец)
ActorCompanyIdidentity claimsGuidДаOriginAgencyCompanyId сделки — предикат record-level
IsAgencyManageridentity claims (scope permission)boolДавычисляется из permission; снимает фильтр участия (актор — Executor)

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

Проекция агрегата; дом полей — deal.md (VO Money; enum DealStatus/PaymentMethod/ DealContractType/EscrowStatus/DealChatStatus). Ниже — только дельта.

ПолеТипОбязательностьИсточникMaskingОписание
DealIdGuidДаIdИдентификатор сделки.
TitlestringДаTitleЗаголовок карточки.
StatusDealStatus (enum строкой)ДаStatusApplication/Booking/Sale/Completed/Cancelled.
StepIdGuidДаStepIdТекущий этап канбана застройщика.
StepGroupPipelineStepGroup (enum строкой)ДаStepGroupСистемная группа этапа.
ClientIdGuidДаClientIdКарточка клиента агентства (client.md).
PrimaryInvestorUserIdGuidДаPrimaryInvestorUserIdОсновной инвестор сделки (возможны дополнительные InvestorSecondary в Members).
PropertyObjectIdGuidДаPropertyObjectIdОбъект (display-данные — API chessboard).
ProjectIdGuidДаProjectIdПроект объекта.
BuildingIdGuid?НетBuildingIdЗдание/литер.
FinanceDealFinanceModelДаcomputedФинансовая сводка (цена, банк/эскроу, оплата).
MembersIReadOnlyList<DealMemberModel>Даактивные DealMemberEntity (RemovedAt is null)Состав участников (bounded, ≤4).
CurrentBookingDealBookingModel?Нетбронь по BookingIdТекущая бронь; null — активной брони нет.
DocumentsDealDocumentsSummaryModelДаcomputed по crm.deal_documentsСводка вкладки «Документы» (статусы + индикатор US-AGT-06).
ChatStatusDealChatStatus (enum строкой)ДаChatStatusГотовность чата сделки (проекция messenger).
TermsAcknowledgedAtDateTimeOffset?НетTermsAcknowledgedAtОтметка инвестора «Ознакомлен» (US-INV-18).
CancelReasonstring?НетCancelReasonПричина отказа (при Status = Cancelled).
CreatedAtDateTimeOffsetДаCreatedAtСоздание сделки.
UpdatedAtDateTimeOffsetДаUpdatedAtДата последнего обновления (US-INV-04).

DealFinanceModel

ПолеТипОбязательностьИсточникMaskingОписание
PriceMoney (owned: Amount,Currency)ДаDeal.Price (snapshot chessboard)Полная стоимость объекта (US-INV-05).
PaymentMethodPaymentMethod? (enum строкой)НетDeal.PaymentMethodСпособ оплаты (purchase-program.md).
InstallmentPresetIdGuid?НетDeal.InstallmentPresetIdПресет рассрочки (если Installment).
ContractTypeDealContractType? (enum строкой)НетDeal.ContractTypeДДУ/ДКП (US-INV-08).
BankIdGuid?НетDeal.BankIdБанк сделки; null → UI «Не указан» (US-INV-07).
EscrowStatusEscrowStatus (enum строкой)ДаDeal.EscrowStatusNone/Provided (US-INV-19).
EscrowAccountNumberstring?НетDeal.EscrowAccountNumberмаска ****NNNN (последние 4)Эскроу-счёт; полный — только SellManager/PinManager (deal.md).
PaymentWarningboolДаcomputedtrue, если CurrentBooking != null и CurrentBooking.PaymentStatus = Pending — предупреждение «бронь не оплачена».

DealMemberModel

Дом — DealMemberEntity (deal.md); дельты нет.

ПолеТипОбязательностьИсточникОписание
UserIdGuidДаDealMemberEntity.UserIdПользователь-участник.
RoleParticipantRole (enum строкой)ДаDealMemberEntity.RoleInvestorPrimary/Executor/SellManager/PinManager (доп. CoAgent/Assistant/Lawyer/InvestorSecondary/Responsible).

DealBookingModel

Дом — booking.md; ниже проекция текущей брони (US-INV-06/10).

ПолеТипОбязательностьИсточникОписание
BookingIdGuidДаBookingEntity.IdИдентификатор брони.
StatusBookingStatus (enum строкой)ДаStatusReserved/AwaitingPayment/Booked/…
ProgramNamestringДаProgramTerms.NameНазвание программы бронирования (snapshot).
IsPaidboolДаProgramTerms.IsPaidПлатная ли программа.
PaymentStatusBookingPaymentStatus (enum строкой)ДаPaymentStatusNotRequired/Pending/Paid (источник PaymentWarning).
PaymentAmountMoney? (owned)НетPaymentAmountСумма платной брони.
ReserveExpiresAtDateTimeOffsetДаReserveExpiresAtДедлайн резерва.
BookingExpiresAtDateTimeOffset?НетBookingExpiresAtДедлайн подтверждённой брони.

DealDocumentsSummaryModel

Дом — deal-document.md; скалярная сводка вкладки (полный список — отдельный paged-метод).

ПолеТипОбязательностьИсточникОписание
RequiresUploadboolДаесть документы Status = RequestedИндикатор «Требуется загрузка» (US-AGT-06).
RequestedCountintДачисло Status = RequestedЗапрошено, файла нет.
UploadedCountintДачисло Status ∈ {Uploaded, UnderReview}Загружено / на проверке.
AcceptedCountintДачисло Status = AcceptedПринято.
RejectedCountintДачисло Status = RejectedОтклонено (нужна повторная загрузка).

Алгоритм

  1. Контекст и доступ. Операцию выполняет агент (permission crm.deals.view); признак IsAgencyManager снимает фильтр по владельцу.
  2. Проверка входа. DealId != Guid.Empty — иначе ошибка валидации. Существование и доступ проверяются при чтении (шаги 3–4).
  3. Что читаем. Сделку (агентская сторона выбирается по явному предикату, у актора нет tenant застройщика) с активными участниками (RemovedAt is null); если нет/soft-deleted → CRM_DEAL_NOT_FOUND. Текущую бронь (если BookingId != null) — проекция в DealBookingModel. Скалярную сводку документов по статусам — полный список документов и таймлайн здесь не грузятся (отдельные вкладки).
  4. Маппинг и правила доступа. Record-level: IsAgencyManager && OriginAgencyCompanyId = actor.CompanyId или актор — активный участник сделки (Executor) — иначе FORBIDDEN (403, чужая сделка не раскрывается). Вычислить Finance.PaymentWarning = бронь != null && PaymentStatus == Pending; EscrowAccountNumber → маска ****NNNN. Остальные поля маппятся 1:1.
  5. Результат. Карточка сделки → HTTP 200. Freshness — актуально на момент запроса (ChatStatus/ФИО — eventual-проекции). Ошибки → ProblemDetails (404/403/400).

Диаграмма

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

ТипДетали
Интеграционные событияnone (read-only GET)
Внешние вызовыnone (display-данные объекта/банка — отдельные запросы клиента к chessboard/catalog)
Проекции / поискnone (прямое чтение агрегата, брони, скалярных счётчиков документов)
Уведомленияnone
Отдельные запросы вкладок«Ход сделки» — таймлайн (deal-timeline-event.md); полный список документов по DealId. В этот ответ не входят.

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400DealId = Guid.Empty«Некорректный идентификатор сделки»no retry
FORBIDDEN403не владелец сделки и не руководитель агентства-владельца«Нет доступа к сделке»no retry
CRM_DEAL_NOT_FOUND404нет сделки с таким Id (или soft-deleted)«Сделка не найдена»no retry

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

  • Новые optional поля DealCardModel/подмоделей — additive (без breaking change); удаление/переименование/смена типа — breaking (migration + rollout). Enum-поля (DealStatus/PaymentMethod/BookingStatus/ BookingPaymentStatus/EscrowStatus/…) сериализуются строкой; клиент обязан толерантно обрабатывать неизвестное значение.
  • Logs: deal-card requested (DealId, OriginAgencyCompanyId, ExecutorAgentUserId; эскроу/телефоны — не пишутся); metrics: crm_deal_card_requests_total{result}, latency p95 (< 1 с, SLO чтений README); traces — сквозной trace через traceparent; dashboard pin-crm; runbook — docs/06-services/crm/runbooks/.

Acceptance Criteria

  • Happy path. Given агент-владелец и его сделка; When GET; Then 200 с DealCardModel: финансовая сводка (вычислен PaymentWarning), активные участники, текущая бронь, сводка документов (RequiresUpload), эскроу — маска ****NNNN.
  • Руководитель. Given руководитель агентства и сделка его OriginAgencyCompanyId другого агента; When GET; Then 200 (record-level по владельцу снят).
  • Неоплаченная бронь. Given активная бронь в статусе оплаты Pending; When GET; Then Finance.PaymentWarning = true.
  • Чужая сделка. Given актор не владелец и не руководитель агентства-владельца; When GET; Then 403 FORBIDDEN (сделка не раскрывается).
  • Не найдена. Given несуществующая или soft-deleted сделка; When GET; Then 404 CRM_DEAL_NOT_FOUND.
  • Валидация. Given DealId = Guid.Empty; When GET; Then 400 VALIDATION_FAILED.
  • Маска эскроу. Given заполненный EscrowAccountNumber; When GET агентом; Then возвращается маска ****NNNN (полный номер — только SellManager/PinManager).
  • Без брони. Given сделка без активной брони; When GET; Then CurrentBooking = null, PaymentWarning = false.

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

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

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