ADR-0052: Tenancy-модель PIN — TenantId = CompanyId, инвестор вне tenant
Статус
Accepted
Дата
2026-07-13
Контекст
SDK Talent.Common.* даёт tenant-изоляцию через ITenantedEntity/INullableTenantedEntity и
tenant-интерсепторы. В PIN верхнеуровневые организации — компании (застройщик/агентство/брокер/партнёр/ПИН)
со своими workspace; но инвестор — самостоятельный пользователь без родительской компании (ТЗ.md:45),
а часть данных (каталог, посты, избранное) — платформенные.
Решение
TenantId = CompanyId: границей tenant в PIN является компания. Данные компании (проекты, фиксации, сделки со стороны компании, роли компании) —ITenantedEntity.- Инвестор и его данные — вне tenant: сущности инвестора (профиль, избранное, лиды со стороны
инвестора) используют
INullableTenantedEntity(TenantId = null) либо не tenanted; изоляция — поUserId/record-level. - Платформенные данные (каталог, справочники, посты, permissions-SEED) — не tenanted; видимость решается permissions/ранжированием.
- Scope-иерархия авторизации (платформа → организация → группа проектов → проект) живёт в
identityи не подменяет tenant-изоляцию: tenant — граница данных, scope — граница прав.
Последствия
- Один и тот же SDK-механизм (
ITenantContext, интерсепторы) работает для компаний; для инвесторских сущностей обязательные фильтры — поUserId(record-level), тесты изоляции обязательны. - Сущности с двумя «сторонами» (сделка: компания и инвестор) хранят
TenantIdкомпании-владельца процесса (застройщик для сделки; агентство для лида) + явныеInvestorUserId/AgentUserIdдля record-level. - Кросс-tenant доступ (Менеджер ПИН, Служба заботы) — только через явные permissions уровня «платформа».
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (23): DevStatsFilterModel (общий вход метрик dev-stats), Entity (Replica): MessengerUserEntity, Entity (Replica): UserDisplayReplicaEntity, Entity / Aggregate: AppointmentEntity, Entity / Aggregate: ArbitrageCaseEntity (+ ArbitrageNoteEntity), Entity / Aggregate: AvailabilitySlotEntity, Entity / Aggregate: CalendarEventEntity, Entity / Aggregate: Channel (enum) + ChannelConfigEntity, Entity / Aggregate: ChatEntity (+ParticipantEntity как owned-child), Entity / Aggregate: ChatEventEntity, Entity / Aggregate: ClientEntity, Entity / Aggregate: KnowledgeBaseArticleEntity (+11)
- API (168): Admin: модерация постов и комментариев (m2m), DELETE /api/agent/crm/deals/{dealId}/members/{memberId} — Удалить участника сделки, DELETE /api/messenger/contacts/{contactId} — Удалить контакт / отозвать исходящую заявку, DELETE /api/v1/comments/{id} — Удалить/скрыть комментарий, DELETE /api/v1/posts/{id} — Удалить пост (soft + detach snapshots), GET /api/agent/crm/clients — Клиентская база агента (список + поиск), GET /api/agent/crm/clients/{clientId} — Карточка клиента, GET /api/agent/crm/deals — Сделки агента (список/поиск), GET /api/agent/crm/deals/{dealId} — Карточка сделки (агент), GET /api/agent/crm/fixations — Список фиксаций агентства (+ поиск), GET /api/agent/crm/fixations/{fixationId} — Карточка фиксации (агент), GET /api/agent/crm/leads — Список лидов агента (+156)
- Use Cases (58): UC-BKG-001. Инвестор инициирует бронирование с витрины, UC-BKG-002. Бронирование Резерв → Бронь, UC-BKG-003. Истечение резерва/брони и продление, UC-BKG-004. Оплата платного бронирования (чек + подтверждение), UC-CAL-001. Агент ведёт рабочий календарь, UC-CAL-002. Агент публикует слоты доступности, UC-CAL-003. Инвестор бронирует слот с витрины, UC-CAL-004. Агент подтверждает или отклоняет запись, UC-CAL-005. Отмена и перенос записи, UC-CAL-006. Агент фиксирует исход встречи, UC-CAL-007. Дедлайны брони CRM в календаре агента, UC-CAL-008. Напоминания о событиях календаря (+46)
- События (20): Event Contract: pin.calendar.appointment-cancelled (AppointmentCancelledEvent), Event Contract: pin.calendar.appointment-completed (AppointmentCompletedEvent), Event Contract: pin.calendar.appointment-confirmed (AppointmentConfirmedEvent), Event Contract: pin.calendar.appointment-created (AppointmentCreatedEvent), Event Contract: pin.calendar.event-created (CalendarEventCreatedEvent), Event Contract: pin.calendar.event-owner-changed (CalendarEventOwnerChangedEvent), Event Contract: pin.calendar.event-rescheduled (CalendarEventRescheduledEvent), Event Contract: pin.calendar.event-status-changed (CalendarEventStatusChangedEvent), Event Contract: pin.calendar.reminder-due (ReminderDueEvent), Event Contract: pin.crm.booking-created (BookingCreatedEvent), Event Contract: pin.crm.deal-created (DealCreatedEvent), Event Contract: pin.crm.fixation-rejected (FixationRejectedEvent) (+8)
- Консюмеры (29): Consumer: Calendar CRM Booking Sync (calendar-crm-booking-events), Consumer: Calendar CRM Deal Sync (calendar-crm-deal-events), Consumer: Calendar Events → Notifications (notifications-calendar-events), Consumer: Calendar Identity User Sync (calendar-identity-users), Consumer: CRM Agent Offboarding (CrmAgentOffboardingConsumer), Consumer: CRM Calendar Appointments Sync (crm-calendar-appointments), Consumer: CRM Events → Notifications (notifications-crm-events), Consumer: CRM Identity User Sync (CrmUserUpsertedConsumer), Consumer: Deal Chat Members Sync (MessengerDealMembersChangedConsumer), Consumer: Deal Chat Provisioning (MessengerDealCreatedConsumer), Consumer: Deal Chat Status (pin-crm-deal-chat-status), Consumer: Favorites Events (UserActivityFavoriteAddedConsumer / ...RemovedConsumer) (+17)
- Фоновые задачи (20): Background Job: AccessFanOutJob — веерная рассылка UserAccessChangedKafkaEvent при смене состава прав роли, Background Job: AppointmentTimeoutJob, Background Job: BookingExpirationJob, Background Job: CallTimeoutReaperJob, Background Job: ChatProvisionRetryJob, Background Job: ConferenceRoomReaperJob, Background Job: DelistedRetentionJob, Background Job: DocumentExpiryReminderJob, Background Job: ExpirePromotionsJob, Background Job: LeadExpirationJob, Background Job: PopularityRecomputeJob, Background Job: project-aggregates-recalc (пересчёт денормализованных агрегатов) (+8)
- Саги (2): Saga «Booking» (Резерв → Бронь → передача в продажу), Saga «Deal Completion» (fan-out завершения сделки)
- Поиск (5): ClientSearchDocument, DealSearchDocument, FixationRequestSearchDocument, KbArticleSearchDocument, TicketSearchDocument
- Отчёты (1): Отчет/Export: dev-stats-export (выгрузка аналитики застройщика)
- Дашборды (3): Панель Метрик Застройщика: dev-stats-overview (аналитика внимания), Панель Наблюдаемости: news-overview (эксплуатация), Панель Наблюдаемости: support-overview (эксплуатация)
- adr (7): ADR-0060: Продвижение — отдельный bounded context promotion (полиморфный таргет); catalog и news — консюмеры буста, ADR-0061: Независимый агент, отвязка от агентства и приглашения по номеру телефона, ADR-0064 — контекст calendar (календарь и запись на показ/консультацию), ADR-0066 — удержание ПИН и реферальные ставки, ADR-0069: Co-broking — расщепление комиссии между несколькими агентами (дизайн, v-next), ADR-0070: Мульти-агентство и контекст активного агентства, ADR-0071: Владение процессами CRM компанией и единая модель участников
- scenarios (4): UC-OVR-001. Запрос консультации у агента недвижимости, UC-OVR-002. Запрос консультации у инвест-консультанта ПИН (+2a), UC-OVR-010. От завершённой сделки до выплаты комиссии и реферального вознаграждения, UC-OVR-011. Самозапись инвестора: от слота до исхода встречи и лида в воронке
- index.md (8): Calendar Service, CRM Service, DevStats Service (Pin.DevStats), Messenger Service, News Service (Pin.News), Notifications Service, Support Service, user-activity Service
Связанные документы
docs/06-services/identity/(scope-модель, permissions),docs/00-overview/service-boundaries.md, SDK:docs/11-backend-sdk/(tenant-интерсепторы).