Назначение
support («Служба заботы») — greenfield PIN-внутренний back-office-контекст для контроля качества и
поддержки. Владеет: обращениями пользователей (тикеты) с очередью/маршрутизацией и перепиской, базой
знаний/FAQ, шаблонными ответами и арбитражными делами (конфликты/жалобы). Источник требований —
ТехЗадание/Служба заботы.html (разделы 1–9 матрицы) + Список фич §11; роль в ролевой модели —
technical-assignment.md, glossary «Служба заботы»
(glossary.md). Уровни доступа: наблюдатель / оператор / супервизор.
Покрывает матрицу 4.3–4.7 (тикеты, шаблоны, база знаний, уведомления о событиях), 6.1–6.5
(арбитраж), 8.1/8.4 (дашборд обращений). Разделы 2.x (сквозной мониторинг), 3.x (ручные
вмешательства), 5.x (документы/юр.этапы), 7.3/7.4 (вебинары/прогресс), 9.x (справочники/внешний
Service Desk) — НЕ owned здесь: реализуются как platform-scope доступ к чужим сервисам через permissions
либо отложены (см. Ответственность И Границы /
Ограничения).
| Поле | Значение |
|---|
| Статус | draft |
| Owning team | PIN Platform (Care squad) |
| Domain | Поддержка/контроль качества |
| Data classification | Mixed (PII заявителей: ФИО/телефон/email реплики; тексты обращений) |
| Runtime criticality | medium (не в денежной воронке, но SLA поддержки) |
Ownership
| Artifact | Owner/Rule |
|---|
| PostgreSQL schema | support |
| API areas | public (заявитель) / support (наблюдатель/оператор/супервизор); admin — вне контекста (эскалация в админ-панель, ADR-0053) |
| Owned entities | TicketEntity (+child TicketMessageEntity), TicketQueueEntity, KnowledgeBaseArticleEntity, TicketTemplateEntity, ArbitrageCaseEntity (+child ArbitrageNoteEntity) |
| Produced events | pin.support.ticket-created.v1, pin.support.ticket-message-posted.v1, pin.support.ticket-status-changed.v1, pin.support.arbitrage-resolved.v1 |
| Consumed events | pin.identity.users.v1 (display-реплика заявителя) |
| Search indexes | Elasticsearch-проекции (Talent.Common.Domain.Search, ADR-0059): support-tickets — внутренний поиск тикетов по номеру/теме/заявителю (search/tickets.md); support-kb-articles — публичный поиск базы знаний по заголовку/тексту/тегам (search/kb.md). Арбитраж — низкий объём: PostgreSQL ILIKE по номеру/заголовку, без отдельного индекса (ADR-0056) |
| Analytics facts | нет (дашборд обращений 8.1/8.4 — SQL-query по своим таблицам; ClickHouse не используется, объём мал при 1–10k активных, ADR-0056; ClickHouse только dev-stats, ADR-0057) |
Ответственность И Границы
- Делает: приём входящих обращений (форма обратной связи 4.4) в единую очередь; маршрутизация по
очередям (
TicketQueue) и категориям; управление тикетами — взятие в работу, назначение/переназначение
оператора, приоритет, смена статуса New/InWork/Resolved/Closed, реопен (4.5); переписка по тикету
(публичные ответы заявителю + внутренние заметки операторов, 4.1 в рамках тикета); шаблонные ответы (4.3);
база знаний/FAQ с полнотекстовым поиском (Elasticsearch) и аудиторией Public/Authenticated/Internal (4.7, 7.1, 7.2); арбитраж конфликтов/
жалоб — регистрация (6.1), расследование с привязкой к аудиту/чатам/истории (6.2), вынесение решения (6.3),
информирование сторон событием (6.4), эскалация администратору (6.5); эскалация тикетов супервизору/
администратору; дашборд обращений и анализ тематик (8.1, 8.4); SLA-мониторинг просрочек первого ответа/
решения (4.6) фоновым worker-ом.
- Не делает (границы):
- Сквозной просмотр пользователей/проектов/сделок/финансов (2.x) — support читает чужие сервисы
(
identity/chessboard/crm/rewards) их API в platform-scope, как Менеджер ПИН; копий не хранит.
- Ручные вмешательства (3.x: правка профиля/роли/блокировка —
identity; смена статуса сделки/
переназначение клиента/сделки — crm; ручной резерв/освобождение объекта недвижимости — chessboard; корректировка
комиссий — rewards; модерация постов — news; массовые/системные уведомления — notifications)
выполняются в owning-сервисах через выданные support platform-scope permissions; support фиксирует
факт/ссылку в ArbitrageNoteEntity/аудите, но чужие таблицы не пишет и permissions не дублирует.
- Чаты сделок/групповые чаты (4.1/4.2) — владелец
messenger
(ADR-0055); support подключается к чату через
messenger в platform-scope; TicketMessageEntity — отдельная переписка тикета, не чат сделки.
- Документы/юр.этапы (5.x) —
crm/profile/storage (support вызывает их decide/request-document
ручки в scope). Хранение бинарей вложений — storage/CDN (только FileId в метаданных).
- Централизованный аудит-лог для расследований — стемпинг
IAuditableEntity/таймлайны в каждом сервисе +
лог-хранилище (ELK); support только читает/ссылается.
- Source of truth: тикеты, очереди, переписка тикетов, база знаний, шаблоны, арбитражные дела — support.
- Реплики/проекции:
RequesterName/RequesterPhone/RequesterEmail — display-реплика из identity
(консюмер user-upserted, version-guard); связи RelatedDealId/RelatedPropertyObjectId/RelatedCompanyId/
ArbitrageCaseId — логические id без загрузки чужих агрегатов.
Зависимости
| Зависимость | Тип | Зачем Нужна | Failure mode |
|---|
PostgreSQL (support) | db | все агрегаты (source of truth), outbox | недоступность = недоступность сервиса |
| Elasticsearch | search | search-проекции support-tickets/support-kb-articles (ADR-0059, Talent.Common.Domain.Search) | недоступность = поиск тикетов/базы знаний недоступен; PostgreSQL source of truth и команды работают |
| Kafka | queue | outbox-исходящие + консюмер identity | деградация: события копятся в outbox, реплика заявителя отстаёт |
identity | service/events | actor/permissions (JWT, Scoped-RBAC platform-scope), lookup пользователей/компаний для карточки тикета; консюмер user-upserted для реплики | недоступность = 401/503 на командах, реплика eventual |
crm / chessboard / rewards / profile / news | service | сквозной просмотр (2.x) и ручные вмешательства (3.x/5.x) через platform-scope API этих сервисов | вмешательство/просмотр недоступны, own-функции тикетов работают |
messenger | service | подключение оператора к чатам сделок (4.1) и внутренние чаты (4.2) в platform-scope | чат недоступен, тикеты работают |
notifications | consumer наших событий | оповещение операторов/заявителя/сторон конфликта | при отставании уведомления запаздывают (eventual) |
| storage/CDN | external | пресайн вложений тикетов/KB | загрузка/скачивание вложений недоступны |
Проекты
src/services/support/
Pin.Support.Api/ # области public / support (admin — вне контекста, ADR-0053)
Pin.Support.Application/ # сценарии операций (команды/запросы) над обращениями, очередями, базой знаний, арбитражем
Pin.Support.Domain/ # сущности, инварианты, доменные события
Pin.Support.Infrastructure/ # БД support, исходящие интеграционные события (outbox), Elasticsearch-проекции (поиск)
Pin.Support.Background/ # worker SLA-мониторинга + консюмер реплики пользователей + проекторы/rebuild поиска
Pin.Support.Migrator/ # миграции + SEED (дефолтные очереди, шаблоны/KB-категории; каталог permissions -> identity SEED)
Pin.Support.Contracts/ # контракты интеграционных событий pin.support.*
Основные Сущности
ER-обзор
API
Карта областей (детальные документы — api/<area>/<method>.md):
- Public (
/api/public/support/...): подать тикет (create-ticket), мои тикеты (list-my-tickets,
get-my-ticket), ответ по своему тикету (add-ticket-message), база знаний (search-kb-articles — поиск
Elasticsearch, get-kb-article). Заявитель = любой авторизованный пользователь; чтение public-KB допускает Anonymous.
- Support (
/api/support/...):
- тикеты:
list-tickets (фильтры статус/очередь/assignee/приоритет/категория/заявитель/даты),
get-ticket, create-ticket-on-behalf, take-ticket, assign-ticket, change-ticket-status,
update-ticket (приоритет/категория/очередь), escalate-ticket, reply-ticket
(Public/Internal + apply template);
- очереди:
list/create/update/archive-queue;
- база знаний:
list/create/update/publish/archive-kb-article;
- шаблоны:
list/create/update/archive-template;
- арбитраж:
list/get/register/add-note/resolve/escalate-arbitrage-case;
- дашборд:
get-support-dashboard.
- Admin: вне контекста — приём эскалаций админ-панелью
(ADR-0053).
Events И Consumers
| Type | Name | Topic | Назначение | Link |
|---|
| Produced | TicketCreatedEvent | pin.support.ticket-created.v1 | новый тикет; консюмер notifications (оповестить дежурных операторов/очередь) | events/ticket-created.md |
| Produced | TicketMessagePostedEvent | pin.support.ticket-message-posted.v1 | публичный ответ оператора (reply-ticket, Visibility = Public); notifications уведомляют заявителя о новом ответе (4.1/4.5) | events/ticket-message-posted.md |
| Produced | TicketStatusChangedEvent | pin.support.ticket-status-changed.v1 | смена статуса (взятие/решение/закрытие/реопен); notifications информируют заявителя (4.5/4.6) | events/ticket-status-changed.md |
| Produced | ArbitrageResolvedEvent | pin.support.arbitrage-resolved.v1 | решение по конфликту вынесено/эскалировано; notifications информируют стороны (6.4) | events/arbitrage-resolved.md |
| Consumed | pin.identity.users.v1 | identity | display-реплика заявителя (ФИО/телефон/email) на тикетах/арбитраже (fan-out по RequesterUserId), version-guard | consumers/identity-user-upserted.md |
Все produced-события несут ContractVersion и публикуются через транзакционный outbox; консюмер идемпотентен
по идентификатору сообщения (MessageId) и защищён version-guard.
Кросс-сервисные контракты и окна согласованности
Что support ожидает от соседей (входные гарантии) и что предоставляет (исходящие гарантии). Ключ
партиционирования исходящих событий — AggregateId (TicketId/ArbitrageCaseId): гарантирует порядок
событий по одному обращению/делу; между разными агрегатами порядок не гарантируется.
| Направление | Контракт | Ключ / версия | Гарантии и окно согласованности |
|---|
Ожидает от identity | pin.identity.users.v1 — display-реплика заявителя (RequesterUserId, FullName, Phone, Email, монотонная IdentityRequesterVersion) | key UserId; version-guard | Реплика eventual: применяется, только если версия события ≥ сохранённой; отставание консюмера → карточки показывают устаревшее/пустое ФИО до догоняющего события. Отсутствие реплики на момент создания тикета — штатно (поля null), не блокирует |
Ожидает от identity (sync) | внутренняя проверка оператора при assign-ticket (существование, активность, support.tickets.manage) | синхронный вызов | Недоступность → IDENTITY_UNAVAILABLE (503, retry); own-функции тикетов не зависят от этого вызова |
| Предоставляет | pin.support.ticket-created.v1 — новое обращение (TicketId, Number, QueueId, Category, Priority, RequesterUserId, Channel) | key TicketId; ContractVersion = 1 | At-least-once через outbox; консюмер notifications идемпотентен по MessageId. Заявитель видит тикет сразу (read-your-writes), дежурный оператор — после доставки события (секунды) |
| Предоставляет | pin.support.ticket-message-posted.v1 — публичный ответ оператора | key TicketId; ContractVersion = 1 | Уведомление заявителю о новом ответе — после доставки (окно eventual); в промежутке заявитель видит ответ при следующем открытии карточки |
| Предоставляет | pin.support.ticket-status-changed.v1 — смена статуса (взятие/решение/закрытие/реопен) | key TicketId; ContractVersion = 1 | Порядок статусных событий по одному тикету сохранён (общий ключ). Уведомление заявителя/оператора — eventual; статус в карточке меняется сразу после коммита |
| Предоставляет | pin.support.arbitrage-resolved.v1 — решение/эскалация по конфликту | key ArbitrageCaseId; ContractVersion = 1 | Стороны конфликта уведомляются только по этому событию (не по заметкам расследования); окно eventual |
Внутренние справочники (очереди, шаблоны, база знаний) интеграционных Kafka-событий не публикуют — их
изменения видны через search-проекции (support-tickets/support-kb-articles, свежесть < 1 мин, ADR-0059)
и прямые чтения source of truth; внешние контексты о них не знают.
Runtime И Эксплуатация
| Область | Требование |
|---|
| Health checks | /health/live, /health/ready (PostgreSQL + Kafka), /health/startup (миграции применены) |
| SLO/SLA | команды p95 < 500 мс; списки/поиск p95 < 1 с; SLA-worker — точность цикла ≤ 1 мин; свежесть search-проекций < 1 мин |
| Background jobs | ticket-sla-monitor (цикл 1 мин; выборка просроченных обращений под блокировку; помечает SlaBreached, эмитит уведомление о просрочке первого ответа/решения) — идемпотентен (ADR-0056) |
| Dashboard | Grafana pin-support (ops) + продуктовый дашборд обращений (get-support-dashboard, SQL) |
| Alerts | лаг консюмера identity > 5 мин; лаг search-проекций (support-tickets/support-kb-articles) > 5 мин; outbox backlog > 1000; рост SlaBreached/просрочек; ошибки worker-а |
| Runbooks | docs/06-services/support/runbooks/ (будущий путь по конвенции) |
| Feature flags | внешний Service Desk (Jira/Zendesk, 9.2) — выключенный модуль (отложено) |
Безопасность
- Actor types: Заявитель (любой авторизованный пользователь /
Anonymous для public-KB), Служба заботы —
Наблюдатель / Оператор / Супервизор (все platform-scope), Админ-панель
(ADR-0053, приём эскалаций), система
(worker/consumer).
- Permission model: Scoped-RBAC (identity/domain/permission.md),
platform-scope + record-level (
AssigneeUserId/QueueId). Каталог support-permissions (SEED в
identity): support.tickets.view (наблюдатель), support.tickets.manage (оператор:
take/reply/status/update/create-on-behalf), support.tickets.assign (супервизор),
support.tickets.escalate (супервизор), support.queues.manage (супервизор),
support.kb.view/support.kb.manage, support.templates.manage,
support.arbitrage.view/support.arbitrage.manage/support.arbitrage.decide (супервизор),
support.dashboard.view. Уровни доступа = профили permissions: Наблюдатель = *.view (+ cross-service
*.view для 2.x); Оператор = + tickets/kb/templates/arbitrage .manage (+ выданные crm/identity/
chessboard интервенционные perms для 3.x); Супервизор = + assign/escalate/queues/arbitrage.decide
(+ финансовые/блокировка 3.6/3.8, эскалация 6.5).
- Cross-service интервенции (3.x/5.x): permissions живут и проверяются в owning-сервисах
(
crm.*/identity.*/chessboard.*/rewards.*/news.*/notifications.*); support-роли назначаются в
platform-scope; support их НЕ определяет и НЕ дублирует (описано как зависимость).
- Tenant isolation (ADR-0052): support —
PIN-внутренний single-tenant platform-tool; owned-сущности несут
TenantId = PIN CompanyId (для SDK
ITenantedEntity), доступ — по platform-scope permissions, не по агентство/застройщик-tenant. Контекст
заявителя — явные колонки RequesterCompanyId/RequesterRole (для фильтра/маршрутизации), не tenant.
- Sensitive/PII: ФИО/телефон/email заявителя (PII, реплика; маска в логах
+7***1234, И***); тексты
обращений/заметок — Internal; вложения — метаданные здесь, бинарь в storage. Внутренние заметки
(Visibility=Internal) не видны заявителю (public-API исключает).
- Audit:
IAuditableEntity + Version на всех агрегатах; переписка тикета и заметки арбитража —
append-only (модерация — soft-delete); арбитражное решение фиксируется иммутабельно (ArbitrageDecision
owned + DecidedByUserId).
Известные Ограничения И Assumptions
- ClickHouse/facts не используются: объём тикетов мал (1–10k активных,
ADR-0056); дашборд/анализ — SQL по
своим таблицам; экспорт отчётов в Excel/PDF (8.5) — отложен (report-export, будущее).
- Внешний Service Desk (Jira/Zendesk 9.2), вебинары/трансляции (7.3) и трекинг прогресса обучения (7.4) —
вне первой версии; база знаний покрывает 7.1/7.2.
- Ревизионная история KB (полный snapshot по образцу
news) — отложена; версия контента через Version +
аудит.
- Отчёты «проблемные сделки» (8.2) и «активность пользователей» (8.3) — читаются из
crm/user-activity их
API в platform-scope, не дублируются в support.
Принятые решения контекста
| # | Вопрос | Решение |
|---|
| 1 | Тенантность support | single-tenant platform-tool, TenantId = PIN CompanyId, доступ platform-scope (ADR-0052) — не агентство/застройщик-tenant |
| 2 | Ручные вмешательства и сквозной просмотр (2.x/3.x/5.x) | не owned; выполняются в owning-сервисах через выданные permissions; support фиксирует ссылку в арбитраже/аудите |
| 3 | Переписка тикета vs чат сделки | TicketMessageEntity — свой тред тикета; чаты сделок/групповые — messenger (ADR-0055), подключение в platform-scope |
| 4 | Арбитраж «через аудит» | ArbitrageNoteEntity.AuditRefs (jsonb) ссылается на аудит-записи чужих сервисов/лог-хранилище; support не владеет аудит-логом |
| 5 | Поиск и таймеры | полнотекстовый поиск тикетов/базы знаний — Elasticsearch-проекции (ADR-0059, Talent.Common.Domain.Search; PostgreSQL — source of truth); арбитраж — ILIKE (низкий объём); SLA/просрочки — polling-worker (ADR-0056); без Redis/ClickHouse |
| 6 | Эскалация администратору (6.5) | статус Escalated + arbitrage-resolved (Outcome=EscalatedToAdmin) / для тикетов — флаг + переназначение; приём — админ-панель (ADR-0053) |
Связанные Документы
- Домены: domain/ (6 документов выше); события: events/, консюмеры:
consumers/, API:
api/, поиск: search/ (support-tickets/support-kb-articles),
use-cases: use-cases/, background: background/, дашборды:
dashboards/ — следующий слой ТЗ контекста (будущие пути по конвенции).
- Обзор: technical-assignment.md,
service-boundaries.md,
glossary.md; эталон — crm/README.md.
- ADR: 0052 tenancy,
0053 admin-identity,
0055 мини-мессенджер,
0056 simplicity-first,
0057 dev-stats ClickHouse,
0008 Elasticsearch,
0059 Elasticsearch-проекции.