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

CRM Service

Центральный «денежный» bounded context PIN и самый крупный контекст платформы: сквозная воронка продажи недвижимости от клиентской базы агента до завершённой сделки, kanban-воронка застройщика, бронирования, программы покупки, документы и таймлайны сделок.

Назначение

crm владеет всей цепочкой продажи недвижимости PIN: клиентская база агента → лид → заявка на фиксацию → бронирование → сделка — плюс настраиваемая kanban-воронка застройщика, программы бронирования и программы покупки, документы сделки и таймлайны. Это центральный контекст платформы: агент ведёт клиентов и лидов, застройщик решает по заявкам на фиксацию и управляет своей воронкой, инвестор видит свои сделки, документы и статусы, менеджер ПИН — арбитр по всем сделкам. Самый большой bounded context — центр «денежной» воронки продаж, где сходятся фиксации, бронирования, оплаты и завершение сделок.

Контекст в платформе

CRM стоит в центре сквозной воронки: слева — контексты, которые питают его событиями и данными (лента лида, записи на показы, данные объекта, роли и реквизиты), справа — контексты, которые реагируют на его события (чаты, комиссии, портфель, статусы объектов). Направление стрелок — направление потока данных/событий.

Metadata

ПолеЗначение
Статусdraft
Owning teamPIN Platform (CRM squad)
DomainСделки/продажи недвижимости
Data classificationMixed (PII клиентов, Sensitive: эскроу/документы)
Runtime criticalitymission-critical (центр воронки денег)

Ownership

ArtifactOwner/Rule
PostgreSQL schemacrm
API areasagent / developer / investor / pin-manager / admin / public (лид-заявки, purchase-config)
Owned entitiesClient, Lead(+LeadEvent), FixationRequest, Deal(+DealMember), PipelineStep(+SEED группы), Booking, BookingProgram, PurchaseProgram(+InstallmentPreset), DealDocument, DealTimelineEvent
Produced eventspin.crm.lead-created/lead-expired/fixation-approved/fixation-rejected/deal-created/deal-stage-changed/deal-members-changed/deal-completed/deal-cancelled/booking-created/booking-paid/booking-confirmed/booking-expired/booking-cancelled/document-requested/document-status-changed.v1
Consumed eventspin.referral.lead-attachments.v1, pin.user-activity.property-object-view-ended.v1, pin.user-activity.action-tracked.v1, pin.selections.selection-shared.v1, pin.messenger.deal-chat-status-changed.v1, pin.identity.users.v1, pin.calendar.appointment-created/appointment-confirmed/appointment-cancelled/appointment-completed.v1
Search indexesElasticsearch-проекции (ADR-0059): crm-clients, crm-fixations, crm-deals (search/); PostgreSQL-FTS (tsvector) не используется
Analytics factsнет (статистика ЛК — SQL-query по своим таблицам; view-статистика застройщика — контекст dev-stats, ADR-0057)

Ответственность и границы

Делает: клиентская база агента (/crm/clients); лиды с типизированными хотелками, лентой событий клиента, сроком жизни +1 месяц с продлением; заявки на фиксацию с дубль-детектом (Elasticsearch: fuzzy ФИО + точный телефон) и решением застройщика; сделки на единой kanban-доске (системные группы SEED + настраиваемые этапы застройщика); бронирование Резерв → Бронь с таймерами программ и ручным подтверждением оплаты; программы покупки (100%/рассрочка/ипотека) с серверным расчётом графика платежей; документы сделки со статусами проверки; таймлайны; статистика ЛК застройщика/агентства (query-методы по своим таблицам).

Не делает — границы контекста
  • Хранение/статусы объектов — владелец chessboard; CRM просит смену статуса событиями.
  • Чаты — владелец messenger (ADR-0055); CRM публикует события, хранит проекцию ChatStatus.
  • Расчёт/выплата комиссийrewards; CRM даёт события фиксаций/сделок.
  • Реквизиты и персональные документыprofile; CRM хранит только RequisiteSetId.
  • Эквайринг/платежи — решение 9 контекста: структуры есть, интеграция отложена.
  • Хранение бинарных файлов — storage/CDN, только FileId.
  • Календарь агента, слоты самозаписи и записи на показы/консультации — владелец calendar (ADR-0064); CRM потребляет pin.calendar.appointment-* в воронку (consumers/calendar-appointments.md).
  • Source of truth: все owned-сущности выше.
  • Реплики/проекции: display-поля клиента из identity (консюмер user-upserted); ProjectId/ BuildingId/цена объекта недвижимости — snapshot из chessboard на момент операции; ChatStatus — проекция messenger.

Зависимости

ЗависимостьТипЗачем НужнаFailure mode
PostgreSQL (crm)dbвсе агрегаты, outboxнедоступность = недоступность сервиса
Elasticsearchsearchпроекции поиска клиентов/фиксаций/сделок + дубль-детект фиксаций (ADR-0059)деградация: полнотекстовый поиск/дубль-детект недоступны; source-of-truth списки читаются из PostgreSQL
Kafkaqueueвходящие/исходящие события (outbox-first)деградация: события копятся в outbox, консюмеры отстают
identityserviceactor/tenant/permissions (JWT), lookup пользователей (GET /investors/lookup)недоступность = 401/503 на командах
chessboardservice/eventsданные объекта недвижимости (владелец, цена, проект) при создании/смене объекта; статусы объекта недвижимости — консюмер наших booking-событийсоздание сделки/брони при недоступном chessboard — отказ операции (ретрай клиентом)
profileserviceRequisiteSetId при брони/договоревыбор реквизитов недоступен, остальное работает
messengerconsumer наших событийчаты сделок, карточки целевых действий (ADR-0055)чат отстаёт; ChatStatus = Provisioning/Failed
referral, user-activity, selectionsproducer событий для наслента событий лида, авто-создание лидовлента лида отстаёт (eventual)
calendarproducer событий для насзаписи на показы/консультации (слоты): get-or-create клиента/лида + лента лида по pin.calendar.appointment-*.v1 (ADR-0064)лид/лента по записям отстают (eventual); календарь и записи работают автономно
storage/CDNexternalпресайн-ссылки файлов документовзагрузка/скачивание документов недоступны

Основные сущности

EntityНазначениеSource Of TruthДокумент
ClientEntityКлиентская база агента (/crm/clients)crmdomain/client.md
LeadEntity (+LeadEventEntity)Активная связь инвестор↔агент, хотелки, лента событий, срок жизниcrmdomain/lead.md
FixationRequestEntityЗаявка на фиксацию клиента у застройщика, дубль-детект (Elasticsearch), решениеcrmdomain/fixation-request.md
DealEntity (+DealMemberEntity)Сделка: участники, этап доски, объект недвижимости, способ оплатыcrmdomain/deal.md
PipelineStepEntity + SEED PipelineStepGroupОдна доска: системные группы + настраиваемые этапы застройщикаcrmdomain/pipeline-step.md
BookingEntityБронирование Резерв→Бронь с таймерами и оплатой (US-INV-21)crmdomain/booking.md
BookingProgramEntityПрограмма бронирования застройщика (платность/сумма/длительности)crmdomain/booking-program.md
PurchaseProgramEntity (+InstallmentPresetEntity)Программы покупки (100%/рассрочка/ипотека, пресеты рассрочки)crmdomain/purchase-program.md
DealDocumentEntityДокументы сделки: типы, статусы проверкиcrmdomain/deal-document.md
DealTimelineEventEntityTPH-таймлайн сделки (выполнено/в процессе/просрочено)crmdomain/deal-timeline-event.md

ER-обзор

API

Карта областей (детальные документы — api/<method>.md, создаются следующим слоем ТЗ):

  • Agent (/api/agent/crm/...): клиентская база (list/get/create/update/archive + поиск Elasticsearch), лиды (list/get/create/extend/archive/update-wishes + лента событий; состав участников — add/remove-participant, set-primary-investor), фиксации (list с фильтрами агент/застройщик + поиск Elasticsearch, get, create c override хотелок, cancel), сделки (list/search, get, create из лида по объекту — сразу Application при выполненном гейте вовлечения застройщика либо Draft, вовлечение застройщика engage-developer Draft → Application, change-property-object, move-step, отправка подборки, передача ведения reassign-executor / назначение куратора reassign-responsible), бронирование (создать от имени клиента — решение 7), статистика агентства/агента (активные, за периоды, успех/провал, суммы, комиссии из rewards; владелец агентства — переключатель «свои/все»).
  • Developer (/api/developer/crm/...): сделки (list/search, карточка), подтверждение документов брони, загрузка договора брони, подтверждение оплаты, запрос/подтверждение документов ДДУ/ДКП, Росреестр, завершение сделки, фиксации (list + поиск Elasticsearch, approve unique/non-unique, reject), этапы воронки (CRUD/цвет/порядок), программы бронирования (CRUD), программы покупки (CRUD пресетов), продление брони, статистика ЛК (фильтры проект/здание/периоды).
  • Investor (/api/investor/crm/...): мои лиды/сделки (list, карточка, без фиксаций), таймлайн, документы (upload/download, US-INV-12/13), эскроу (US-INV-19), «Ознакомлен» (US-INV-18), оплата брони — прикрепление чека (US-INV-21), выбор реквизитов при брони.
  • Pin-manager: сквозные списки/карточки всех сделок/лидов/фиксаций, ручные статусы/этапы, ручные записи таймлайна (US-MGR-01..06; platform-scope permission).
  • Public (гейт витрины): заявка на консультацию/презентацию (409 DUPLICATE_PHONE при дубле телефона), заявка на бронирование (форма ФИО+телефон), getPurchaseConfig + серверный график платежей.
  • Admin: операционные ручки — доступны администратору платформы (admin API, ADR-0053).

Events и consumers

TypeNameTopicНазначениеLink
ProducedLeadCreatedEventpin.crm.lead-created.v1уведомление агенту, подтверждение реф-привязкиevents/lead-created.md
ProducedLeadExpiredEventpin.crm.lead-expired.v1«продлите лид» агентуevents/lead-expired.md
ProducedFixationApprovedEventpin.crm.fixation-approved.v1право на комиссию (rewards), уведомленияevents/fixation-approved.md
ProducedFixationRejectedEventpin.crm.fixation-rejected.v1уведомление агентуevents/fixation-rejected.md
ProducedDealCreatedEventpin.crm.deal-created.v1чат сделки (messenger), уведомленияevents/deal-created.md
ProducedDealStageChangedEventpin.crm.deal-stage-changed.v1карточки чата, уведомленияevents/deal-stage-changed.md
ProducedDealMembersChangedEventpin.crm.deal-members-changed.v1синк участников чатаevents/deal-members-changed.md
ProducedDealCompletedEventpin.crm.deal-completed.v1портфель (investor-owner), комиссионное + реферальное начисления (rewards), объект недвижимости «Продан» (chessboard)events/deal-completed.md
ProducedDealCancelledEventpin.crm.deal-cancelled.v1освобождение объекта недвижимости, уведомленияevents/deal-cancelled.md
ProducedBookingCreatedEventpin.crm.booking-created.v1объект недвижимости → «Резерв» (chessboard), карточка чатаevents/booking-created.md
ProducedBookingPaidEventpin.crm.booking-paid.v1карточка «договор оплачен», rewardsevents/booking-paid.md
ProducedBookingConfirmedEventpin.crm.booking-confirmed.v1объект недвижимости → «Бронь» + таймерevents/booking-confirmed.md
ProducedBookingExpiredEventpin.crm.booking-expired.v1объект недвижимости → «Свободен», карточка «подтвердите/продлите»events/booking-expired.md
ProducedBookingCancelledEventpin.crm.booking-cancelled.v1объект недвижимости → «Свободен»events/booking-cancelled.md
ProducedDocumentRequestedEventpin.crm.document-requested.v1карточка «загрузите документы» (US-INV-16)events/document-requested.md
ProducedDocumentStatusChangedEventpin.crm.document-status-changed.v1уведомления о статусе документа (US-INV-14)events/document-status-changed.md
Consumedpin.referral.lead-attachments.v1referralавто-создание клиента/лида по правилам привязкиconsumers/referral-registered.md
Consumedpin.user-activity.property-object-view-ended.v1user-activityсобытие лида PropertyObjectViewedconsumers/user-activity-actions.md
Consumedpin.user-activity.action-tracked.v1user-activityсобытия лида FilterApplied/FavoriteAddedconsumers/user-activity-actions.md
Consumedpin.selections.selection-shared.v1selectionsсобытия лида SelectionAttached/SelectionViewedconsumers/selection-shared.md
Consumedpin.messenger.deal-chat-status-changed.v1messengerпроекция Deal.ChatStatusconsumers/deal-chat-status.md
Consumedpin.identity.users.v1identitydisplay-реплика ФИО/телефона клиентаconsumers/identity-user-upserted.md
Consumedpin.calendar.appointment-created.v1calendarget-or-create клиента/лида + лента: запись на показ/консультацию созданаconsumers/calendar-appointments.md
Consumedpin.calendar.appointment-confirmed.v1calendarлента лида «встреча подтверждена»consumers/calendar-appointments.md
Consumedpin.calendar.appointment-cancelled.v1calendarлента лида: запись отклонена/отменена/просрочена (по Reason)consumers/calendar-appointments.md
Consumedpin.calendar.appointment-completed.v1calendarлента лида: исход встречи (Completed/NoShow)consumers/calendar-appointments.md
Consumedpin.identity.agent-membership-changed.v1 (Status = Left)identityoffboarding-каскад: soft-remove участий агента в лидах/сделках агентства + пометка reassign, где он Executorconsumers/agent-offboarding.md
Доставка и версионирование

Все produced-события доставляются через транзакционный outbox; все консюмеры идемпотентны по MessageId (поле SourceMessageId у ленты лида). Версионирование топиков — суффиксом .vN; изменения контрактов — additive (breaking-изменения = новая версия топика).

Гарантии доставки и согласованности (cross-service contract)

  • Ключ партиционирования и порядок. Каждое pin.crm.* событие партиционируется по своему агрегату: booking-события — по BookingId, deal-события — по DealId, lead/fixation-события — по LeadId/FixationRequestId. Это гарантирует порядок событий одного агрегата (booking-createdbooking-confirmedbooking-expired/booking-cancelled не переставляются местами); межагрегатного порядка нет — консюмер не должен предполагать хронологию событий разных агрегатов.
  • Окно eventual-consistency. Статус объекта в chessboard (Свободен/Резерв/Бронь/Продан) меняет консюмер booking/deal-событий асинхронно; типичное окно — доставка события + обработка консюмером (в пределах одного таймер-цикла worker-а). Пока событие не долетело, витрина шахматки показывает прежний статус объекта, а карточка сделки/брони в CRM — уже новый: source of truth процесса — CRM, статуса объекта — chessboard. Промежуточное состояние «бронь оформлена, объект в шахматке ещё Свободен» — ожидаемое, не ошибка.
  • Поведение при устаревании реплик. Реплика chessboard (владелец/цена/проект) читается snapshot-ом на момент операции; при недоступности источника команды создания сделки/смены объекта завершаются отказом (CHESSBOARD_UNAVAILABLE, ретрай клиентом), а не работают по устаревшим данным. Display-реплика identity (ФИО/телефон клиента) обновляется консюмером user-upserted асинхронно; расхождение отображаемого имени — временное и на бизнес-решения (фиксация/сделка) не влияет (snapshot ФИО берётся из карточки клиента на момент операции). Проекции поиска (Elasticsearch, ADR-0059) — тоже eventual: при их отставании/недоступности списки и дубль-детект деградируют, source-of-truth выборки читаются из PostgreSQL.
  • Идемпотентность downstream. Все fan-out консюмеры саг (deal-completed, booking-события) идемпотентны по бизнес-ключу (DealId/BookingId): повторная доставка при retry/DLQ не двоит эффект (портфель investor-owner, комиссия rewards, статус объекта chessboard).

Sagas

CRM оркеструет два cross-service процесса без отдельного оркестратора-сервиса (ADR-0056): агрегат/сделка — это состояние саги, фоновые worker-ы — драйверы таймаутов, транзакционный outbox — доставка переходов; downstream-контексты реагируют хореографией + компенсацией по событиям pin.crm.*. Дом переходов/полей — в агрегатах (booking.md / deal.md); saga-документы описывают только кросс-сервисную координацию (участники, state machine, компенсация, таймауты, reconciliation).

SagaОркестраторПроцессКомпенсацияДокумент
Bookingcrm (агрегат BookingEntity = инстанс; worker booking-expiration = таймаут-драйвер)Резерв → Бронь → передача в продажуrelease объекта недвижимости (chessboard Release) при истечении/отмене; страховка — chessboard/property-object-hold-expiration; денежного отката нет (эквайринг отложен)sagas/booking-saga.md
Deal Completioncrm (Deal.Complete, событие deal-completed)Fan-out завершения: chessboard MarkSold + investor-owner портфель + rewards комиссионное и реферальное начисления (продолжение — commission-settlement-saga)отката нет — завершение терминально (INV-DEAL-9); downstream-сбои → retry/DLQ per-consumer (идемпотентно по DealId)sagas/deal-completion-saga.md

Обе саги — фазы сквозных воронок UC-OVR-004 и UC-OVR-006; шаблон — saga-template.

Runtime и эксплуатация

ОбластьТребование
Health checks/health/live, /health/ready (PostgreSQL + Kafka), /health/startup (миграции применены)
SLO/SLAкоманды p95 < 500 мс; списки p95 < 1 с; таймеры брони — точность worker-цикла ≤ 1 мин
Background jobsbooking-expiration (цикл 1 мин), lead-expiration (цикл 1 ч), timeline-overdue (цикл 5 мин) — все идемпотентны (ADR-0056)
DashboardGrafana pin-crm (создаётся при реализации)
Alertsлаг консюмеров > 5 мин; outbox backlog > 1000; ошибки worker-ов; ChatStatus = Failed > 10/ч
Runbooksdocs/06-services/crm/runbooks/ (будущий путь по конвенции)
Feature flagsнет критических; эквайринг — выключенный модуль (решение 9)

Безопасность

  • Actor types: Инвестор, Агент, Руководитель агентства, Менеджер застройщика, Менеджер ПИН, Служба заботы (read-only + арбитраж), администратор платформы (ADR-0053), система (worker/consumer).
  • Permission model: Scoped-RBAC + record-level (identity/domain/permission.md); permissions crm.clients.*, crm.leads.*, crm.fixations.* (+.decide), crm.deals.* (+.decide), crm.bookings.* (+.decide), crm.booking-programs.*, crm.purchase-programs.*, crm.deal-documents.*, crm.pipeline.*.
  • Tenant isolation (ADR-0052): лид/клиент — TenantId = AgencyCompanyId; фиксация/сделка/бронь/этапы/ программы — TenantId = DeveloperCompanyId + иммутабельная атрибуция OriginAgencyCompanyId/ OriginAgentUserId, текущий исполнитель ExecutorAgentUserId и инвесторы-участники (PrimaryInvestorUserId/InvestorSecondary; у фиксации — скаляр InvestorUserId) для record-level/ membership-выборок второй стороны (ADR-0071); кросс-tenant — только platform-scope (Менеджер ПИН, забота).
  • Sensitive/PII: ФИО/телефоны клиентов (PII), эскроу-счёт (Sensitive, маска ****NNNN), документы (метаданные здесь, бинарь в storage). Инвестор не видит фиксаций; застройщик не видит лидов/клиентской базы.
  • Audit: аудит-поля (создатель/изменивший, время создания/изменения) + версия на всех агрегатах; бизнес-история — append-only таймлайны.

Известные ограничения и assumptions

  • Эквайринг не реализуется (решение 9): оплата брони подтверждается менеджером по чеку.
  • Simplicity-first (ADR-0056): PostgreSQL — основная СУБД (таймеры — polling worker-ы без Redis/TTL); целевая нагрузка 1–10k активных. Полнотекстовый поиск (клиенты/фиксации/сделки) и дубль-детект фиксаций вынесены в Elasticsearch-проекции — ADR-0059 отменяет исходный пункт «PostgreSQL-FTS без внешнего поиска» для CRM.
  • Комиссии считает rewards; в статистике агентства CRM показывает данные rewards по API/событиям.
  • Расхождение статусов: US-INV-03 называет 4 статуса (заявка/бронь/продажа/отказ) — в модели добавлен терминальный Completed (см. решение 4 ниже).

Принятые решения контекста

#ВопросРешение
1Две точки входа брони (решение 7)Единый агрегат BookingEntity c InitiatedBy; таймеры — параметры BookingProgramEntity (вкл. ReserveDurationMinutes, default 60)
2Хотелки при фиксации«Дом» — Lead.Wishes; фиксация несёт nullable snapshot WishesOverride (лид не мутируется)
3Tenancy двухсторонних сущностейПо ADR-0052: владелец процесса — tenant; вторая сторона — явные колонки + record-level
4Статусы сделкиApplication/Booking/Sale/Completed/Cancelled; Completed отображается «продажа (завершена)» — расхождение с 4 статусами US зафиксировано
5Статусы объекта в шахматкеМеняет chessboard как консюмер booking/deal-событий (границы контекстов); CRM чужие таблицы не пишет
6Чат сделкиADR-0055: события CRM → messenger; ChatStatus — проекция
7Дубль-детект фиксацийElasticsearch-проекция (fuzzy ФИО, ADR-0059) + точное совпадение телефона (keyword); решение «уникальный/не уникальный» — за менеджером
8График платежейСерверный расчёт графика по формуле рассрочки; дом формулы — domain/purchase-program.md

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