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

Entity / Aggregate: ReferralRegistrationEntity

Дом полей сущности «Регистрация по реферальной ссылке» (журнал) контекста referral. Все проекции (модели ReferralRegistrationItem, AdminReferralRegistrationEntry, счётчик registrationsCount ссылки) ссылаются сюда и описывают только дельту.

Назначение

ReferralRegistrationEntityappend-only журнальная запись факта «пользователь зарегистрировался (или был привязан по правилам авторизации), придя по реферальной ссылке». Журнал — основа выдач getLinkRegistrations (по одной ссылке) и getAllRegistrations (по всем ссылкам владельца), admin-журнала и метрик каналов (конверсия «переход → регистрация»).

Отличие от реферальной связи: журнал — иммутабельная историческая лента (1:1 с созданием/заменой связи: Initial — первая привязка, Rebind — admin-коррекция), а связь — текущее действующее отношение «кто привёл кого» для выплат (не более одной активной, INV-REF-001). Срабатывания правил, которые создают только лид без связи (правила R3/R4 в attach-rules.md), журнал не пишут — их след живёт в карточке лида CRM.

Классификация

ПолеЗначение
Контекстreferral
KindEntity (append-only журнал; входит в границу согласованности процесса привязки, отдельным агрегатом не является)
Source of truthreferral
Таблицаreferral.referral_registrations
Tenant isolationTenantId = CompanyId реферера на момент записи (ADR-0052); приглашённый — инвестор, record-level по ReferredUserId

Поля

ПолеТипОбязательностьОграничения (PK/FK/index/constraint)ИсточникОписание
IdGuidДаPKappИдентификатор записи журнала.
ReferralLinkIdGuidДаFK fk_referral_registrations_link → referral.referral_links(id), index ix_referral_registrations_referral_link_idconsumer (резолв slug → Id)Ссылка, по которой пришёл пользователь (referralLinkId). Резолв slug ReferrerUrl события identity — в момент записи.
LinkUrlstringДаvarchar(64)копия ReferralLinkEntity.Url на момент записиДенормализованный slug (linkUrl в ReferralRegistrationItem/AdminReferralRegistrationEntry): журнал показывает исторический адрес даже после переименования/удаления ссылки.
ReferrerUserIdGuidДаindex ix_referral_registrations_referrer_user_id; логическая ссылка на identity.users(id)копия ReferralLinkEntity.OwnerUserId на момент записиРеферер (владелец ссылки в момент регистрации). Денормализация — метрики владельца не зависят от последующих изменений ссылки.
ReferredUserIdGuidДаindex ix_referral_registrations_referred_user_id; partial unique ux_referral_registrations_initial (referred_user_id) where registration_type = 'Initial'; логическая ссылка на identity.users(id)UserUpsertedKafkaEvent.UserId / UserSignedInKafkaEvent.UserIdПриглашённый пользователь (referredInvestorId). Не более одной записи Initial на пользователя (идемпотентность first-seen).
TenantIdGuid?Нетindex ix_referral_registrations_tenant_id where not nullкопия ReferralLinkEntity.TenantIdCompanyId реферера; null — инвестор (ADR-0052).
RegistrationTypeReferralRegistrationTypeДахранится как varchar(16)app (правила привязки)Initial / Rebind (см. ниже).
RegisteredAtDateTimeOffsetДаindex ix_referral_registrations_registered_at (desc; сортировка журнала)время события-триггераМомент регистрации/привязки (registeredAt).
AppliedRuleReferralAttachRuleДахранится как varchar(16)app (правила привязки)Какое правило матрицы сработало: R1 (регистрация), R2 (авторизация в окне lazy-регистрации), R6 (admin-коррекция) — attach-rules.md.
LeadRequestedboolДаdefault falseappОтправлен ли этой привязкой запрос на создание лида в CRM (ReferralLeadAttachRequestedKafkaEvent); false — владелец ссылки не агент/застройщик (INV-REF-002) или admin-коррекция. Фиксирует намерение (запрос-событие опубликовано), а не подтверждение, что CRM создала карточку: создание/дедуп карточки — асинхронно и на стороне crm (окно < 5 мин).
CreatedAtDateTimeOffsetДаappТехнический момент записи (может отставать от RegisteredAt на лаг доставки события; в выдачах используется RegisteredAt).

Enum ReferralRegistrationType

ЗначениеОписание
InitialПервая привязка пользователя: регистрация с реф-ссылкой (правило R1) или авторизация в окне lazy-регистрации (правило R2).
RebindПерепривязка к другой ссылке/рефереру. Автоматический rebind отключён — запись Rebind создаёт только admin-операция коррекции связи (решение в README.md; инвариант INV-REF-001). Пишется дополнительно к исходной Initial (на пользователя — ровно одна Initial + ноль-или-более Rebind, INV-REF-R1); ReferralLinkId/LinkUrl/ReferrerUserId записи Rebind указывают на новую ссылку/реферера коррекции, AppliedRule = R6.

Enum ReferralAttachRule (R1R6) — дом в attach-rules.md, здесь не переописывается.

Value objects

VO не используются: все поля скалярные. ФИО/телефон/email приглашённого в admin-выдаче (AdminReferralRegistrationEntry.name/phoneNumber/email) — не колонки журнала: join к реплике пользователей ReferralUserReplica (attach-rules.md) в момент запроса.

Модели JSONB

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

Связи

СвязьТипКлючDelete behavior
ReferralLinkEntityreferenceReferralLinkId (FK)restrict (ссылка soft-delete, журнал живёт)
ReferralBondEntityreference (обратная)referral_bonds.registration_id → referral_registrations.idrestrict
ReferralUserReplica (приглашённый/реферер)replicaReferredUserId / ReferrerUserId

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

ИнвариантУсловиеГде проверяетсяОшибка
INV-REF-R1 SingleInitialНа ReferredUserId — не более одной записи Initial (first-seen идемпотентность consumer-а: повторная доставка события не плодит записи)partial unique ux_referral_registrations_initial + проверка записи Initial перед вставкойидемпотентный no-op (не ошибка)
INV-REF-R2 ImmutableЗапись после создания не изменяется и не удаляетсяжурнал только дополняется (нет update/delete)
INV-REF-R3 NoSelfRegistrationReferredUserId != ReferrerUserId (нельзя зарегистрироваться по собственной ссылке)guard процесса привязки (attach-rules.md, шаг 4)запись не создаётся (лог warning)
INV-REF-R4 ConsistentDenormLinkUrl, ReferrerUserId, TenantId копируются из ReferralLinkEntity в том же согласованном изменении, что и записьпроцесс привязки consumer-а

Переходов состояний нет (иммутабельный журнал).

Согласованность записи и видимость

  • Порядок и атомарность. Запись Initial создаётся в одном согласованном изменении со связью (referral-bond.md) и исходящими событиями процесса привязки; порядок — связь → журнал → события (INV-REF-R4). Правила без связи (R3/R4) журнал не пишут — их след только в карточке лида CRM.
  • Идемпотентность при переигровке. Повторная доставка триггер-события не плодит записей: Initial отсекается partial-unique ux_referral_registrations_initial (INV-REF-R1) — повтор завершается идемпотентным no-op, а не ошибкой.
  • Rebind. Пишется только admin-коррекцией связи, атомарно с переходом старой связи в Replaced и созданием замены; поля ReferralLinkId/LinkUrl/ReferrerUserId записи Rebind указывают на новую ссылку/реферера, AppliedRule = R6. Исходная Initial не изменяется (INV-REF-R2).
  • Видимость admin-проекций. ФИО/телефон/email приглашённого в AdminReferralRegistrationEntry подтягиваются join-ом к реплике ReferralUserReplica в момент запроса. Реплика отсутствует/отстаёт → graceful degradation (пустое ФИО, null телефон), не отказ; email в реплику не реплицируется — всегда null (при необходимости админ-панель читает identity напрямую).

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

СобытиеТипКогдаSnapshot/поля
ReferralRegistrationRecordedEventдоменноеСоздание записи журналаId, ReferralLinkId, ReferredUserId, ReferrerUserId, RegistrationType, AppliedRule

Собственных интеграционных событий журнал не публикует: наружу уходят события процесса привязки — ReferralBondCreatedKafkaEvent (referral-bond.md) и ReferralLeadAttachRequestedKafkaEvent (attach-rules.md); журнальная запись пишется в одном согласованном изменении со связью и исходящими событиями.

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

АспектЗначение
ИндексыPK (id); ux_referral_registrations_initial unique btree (referred_user_id) where registration_type = 'Initial'; ix_referral_registrations_referral_link_id btree (журнал ссылки + COUNT для registrationsCount); ix_referral_registrations_referrer_user_id btree (журнал владельца getAllRegistrations); ix_referral_registrations_referred_user_id btree; ix_referral_registrations_registered_at btree desc; ix_referral_registrations_tenant_id btree where not null
Партиционирование / retentionnone: ~1 млн регистраций всего (ADR-0056) — обычная таблица; журнал бессрочный (основание выплат)
Concurrencyнет (append-only; гонка двойной вставки решается partial unique INV-REF-R1)
Permissions / PIIСама запись PII не содержит (только id). Выдача владельцу (getLinkRegistrations/getAllRegistrations) — обезличенная (referredInvestorId без ФИО/телефона), permission referral.registrations.view-self; admin-выдача с ФИО/телефоном/email (join реплики) — referral.registrations.view-all; телефоны в логах маскируются +7***1234
Auditappend-only журнал сам является аудит-лентой привязок; actor — system (consumer), trace — на исходящих/consumer-записях

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

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

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