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

Entity / Aggregate: PipelineStepEntity + SEED enum PipelineStepGroup

Дом полей многоуровневой системы этапов воронки контекста crm. Модель двухуровневая: системные группы этапов (SEED + enum, меняются только через код/миграции) и настраиваемые этапы застройщика (цвет/порядок/название). Досок/воронок несколько не бывает — доска одна.

Назначение

  • PipelineStepGroup — системная группа этапов kanban-доски: enum в коде + SEED-справочник. Никто, кроме владельцев платформы, не может менять группы (только код и миграции).
  • PipelineStepEntity — настраиваемый этап воронки застройщика: менеджеры застройщика создают, удаляют (архивируют), переименовывают, меняют порядок и цвет; каждый этап обязательно привязан к системной группе. Сделки (deal.md) лежат внутри этапов (Deal.StepId).

SEED enum PipelineStepGroup (системные группы)

Хранится в коде (enum, как text) + read-only SEED-таблица crm.pipeline_step_groups для FK-целостности и display-метаданных. Изменение набора — только миграцией.

ЗначениеOrderDisplay (SEED)Семантика (маппинг на DealStatus)
Application10ЗаявкаСделка создана; работа до брони (DealStatus.Application).
Booking20БронированиеРезерв/бронь объекта, документы и оплата брони (DealStatus.Booking).
Contract30ДоговорСбор документов ДДУ/ДКП, подготовка/подписание договора (DealStatus.Sale).
Payment40ОплатаОплата по договору, эскроу (DealStatus.Sale).
Registration50РегистрацияРегистрация в Росреестре (DealStatus.Sale).
Completed60ЗавершеноСделка завершена (DealStatus.Completed). Терминальная группа.
Cancelled70ОтказСделка прекращена (DealStatus.Cancelled). Терминальная группа.
Способ оплаты и настраиваемые этапы

Способ оплаты влияет на этапы: для PaymentMethod = Mortgage внутри групп Contract/Payment застройщик обычно добавляет этапы «Одобрение ипотеки», «Кредитный договор»; для Installment — этапы графика платежей. Набор групп при этом не меняется — гибкость даёт настраиваемый уровень.

SEED-таблица crm.pipeline_step_groups (read-only справочник)

ПолеТипОбязательностьОграниченияИсточникОписание
CodePipelineStepGroupДаPK, enum как text, varchar(32)SEED-миграцияКод группы (enum).
NamestringДаvarchar(128)SEEDОтображаемое имя (русское).
OrderintДаunique ux_step_groups_orderSEEDПорядок колонок групп на доске.
IsTerminalboolДаdefault falseSEEDCompleted/Cancelled = true (guard переходов).

Классификация (PipelineStepEntity)

ПолеЗначение
Контекстcrm
KindAggregateRoot (простой настраиваемый справочник — CRUD без бизнес-логики)
Source of truthcrm
Таблицаcrm.pipeline_steps
Tenant isolationTenantId = DeveloperCompanyId (этапы настраивает застройщик)

Поля (PipelineStepEntity)

ПолеТипОбязательностьОграничения (PK/FK/index/constraint)ИсточникОписание
IdGuidДаPKappИдентификатор шага.
TenantIdGuidДаindex ix_pipeline_steps_tenant_id; часть unique-индексовidentity (actor застройщика) / SEEDЗастройщик-владелец этапа.
GroupPipelineStepGroupДаFK fk_pipeline_steps_group → crm.pipeline_step_groups(code), enum как text, varchar(32), indexappОбязательная привязка к системной группе. Иммутабельно после создания (история сделок и статистика считаются по группе).
NamestringДаvarchar(128), не пустое; unique ux_pipeline_steps_tenant_name on (TenantId, lower(Name)) where deleted_at is nullappНазвание этапа.
ColorstringДаchar(7), формат #RRGGBB (regex ^#[0-9A-Fa-f]{6}$), default #9CA3AFappЦвет колонки/ярлыка.
OrderintДа>= 0; unique ux_pipeline_steps_tenant_order on (TenantId,Order) where deleted_at is null, deferrable (пакетный reorder)app / SEEDПозиция на доске. Глобальная сортировка доски: Group.Order, затем Order.
IsSeedDefaultboolДаdefault falseSEEDЭтап создан дефолтным SEED-набором застройщика (по одному этапу на группу при онбординге компании); удалять можно, флаг — для UI-подсказок.
CreatedAtDateTimeOffsetДаappСоздание.
CreatedByUserIdGuid?НетappМенеджер застройщика (null — SEED).
UpdatedAtDateTimeOffsetДаappДата последнего изменения.
UpdatedByUserIdGuid?НетappПоследний изменивший.
DeletedAtDateTimeOffset?НетappАрхив этапа.
DeletedByUserIdGuid?НетappКто заархивировал.

Value objects

Value objects не используются (простой справочник).

Модели JSONB (versioned)

JSONB-модели не используются.

Связи

СвязьТипКлючПравило загрузкиDelete behavior
pipeline_step_groups (SEED)referenceGroupin-memory enum + справочникrestrict
DealEntityreference (children)deals.step_idвыборка по (TenantId,StepId) — колонки канбанаrestrict (архив вместо удаления при наличии сделок)

Инварианты и переходы состояний

ИнвариантУсловиеКогда проверяетсяОшибка
INV-STEP-1Каждый этап привязан к существующей системной группе; Group иммутабеленпри создании (поле не редактируется)CRM_STEP_GROUP_IMMUTABLE
INV-STEP-2В рамках TenantId живые этапы уникальны по имени (case-insensitive) и по Orderпри создании/переименовании/reorder (unique-индексы)CRM_STEP_NAME_DUPLICATE / CRM_STEP_ORDER_DUPLICATE
INV-STEP-3На каждую группу у застройщика — минимум один живой этап (иначе сделкам некуда попадать при авто-переходах)при архивировании этапаCRM_STEP_LAST_IN_GROUP
INV-STEP-4Архивирование этапа с активными сделками требует указания этапа-приёмника той же группы; сделки переносятся в одном согласованном изменениипри архивировании этапа с указанием этапа-приёмникаCRM_STEP_HAS_DEALS_TARGET_REQUIRED
INV-STEP-5Системные группы не редактируются через API (только SEED-миграции)нет операций над группамиCRM_STEP_GROUP_READONLY
Без диаграммы состояний

Диаграмма состояний неприменима — у этапа нет статусной машины (живой ↔ архив через soft-delete); переходы сделок по этапам — deal.md.

Доменные и интеграционные события

СобытиеТипКогдаSnapshot/поля
PipelineStepChangedдоменноеcreate/update/reorder/archiveId, TenantId, Group, Order
Интеграционные события не публикуются

Этапы — внутренний справочник CRM; внешние контексты видят только StepGroup в pin.crm.deal-stage-changed.v1 (deal.md).

Индексы, хранение, безопасность

АспектЗначение
ИндексыPK; ix_pipeline_steps_tenant_id; (tenant_id,group); ux_pipeline_steps_tenant_name unique partial; ux_pipeline_steps_tenant_order unique partial deferrable
Партиционирование / retentionnone (десятки строк на застройщика)
Concurrencyоптимистическая блокировка — reorder-конфликты
Permissions / PIIcrm.pipeline.view (все участники CRM своего застройщика/сделок), crm.pipeline.manage (менеджеры застройщика; Менеджер ПИН — platform-scope). PII отсутствует.
Auditотметки создания/изменения/удаления (кто и когда), soft-delete

Целостность, конкуренция и восстановление

  • Иммутабельная группа. Group этапа не редактируется после создания (INV-STEP-1) → CRM_STEP_GROUP_IMMUTABLE; история сделок и статистика считаются по группе, поэтому переклейка группы исказила бы отчётность.
  • Уникальность имени и порядка. Живые этапы застройщика уникальны по имени (case-insensitive) и по Order (INV-STEP-2) → CRM_STEP_NAME_DUPLICATE/CRM_STEP_ORDER_DUPLICATE. Пакетный reorder использует deferrable-уникальность Order, поэтому промежуточные состояния внутри одного согласованного изменения допустимы; конкурентный reorder разрешается оптимистической блокировкой (проигравший перечитывает доску).
  • Минимум один этап на группу. Архивировать последний живой этап группы нельзя (INV-STEP-3) → CRM_STEP_LAST_IN_GROUP; иначе авто-переходам сделок некуда попадать.
  • Архив с активными сделками. Архивирование этапа со сделками требует указания этапа-приёмника той же группы; сделки переносятся в одном согласованном изменении (INV-STEP-4) → иначе CRM_STEP_HAS_DEALS_TARGET_REQUIRED. StepGroup сделок при переносе не меняется (та же группа).
  • Системные группы read-only. Набор PipelineStepGroup меняется только SEED-миграциями (INV-STEP-5); API-операций над группами нет → CRM_STEP_GROUP_READONLY.
  • Отдача наружу. Этапы — внутренний справочник; внешние контексты видят только StepGroup в pin.crm.deal-stage-changed.v1 (deal.md), поэтому переименование/перекраска этапа безопасны для интеграций.

Acceptance-критерии

  • Happy (создание этапа). Дано: у застройщика есть группа Booking. Когда менеджер создаёт этап «Ожидает оплаты» в этой группе. Тогда этап привязан к группе, уникален по имени/порядку.
  • Дубль имени/порядка. Дано: живой этап с именем/порядком X. Когда создаётся второй с тем же. Тогда отказ CRM_STEP_NAME_DUPLICATE/CRM_STEP_ORDER_DUPLICATE.
  • Смена группы. Дано: существующий этап. Когда пытаются сменить Group. Тогда отказ CRM_STEP_GROUP_IMMUTABLE.
  • Архив последнего в группе. Дано: в группе один живой этап. Когда его архивируют. Тогда отказ CRM_STEP_LAST_IN_GROUP.
  • Архив с сделками без приёмника. Дано: на этапе есть сделки. Когда его архивируют без этапа-приёмника. Тогда отказ CRM_STEP_HAS_DEALS_TARGET_REQUIRED; при указании приёмника той же группы сделки переносятся.

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

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

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

  • deal.mdStepId/StepGroup, правила MoveToStep.
  • README контекста — SEED-набор этапов застройщика.