TU
src/stack/components/ticket/ticket-tu.service.ts — Family-окрема анкета, навколо якої визначаються робочий доступ і чат.
Суть
TU — анкета в конкретній Family. Одна клієнтка в різних Family має різні TU; для чату це різні сутності.
Поточний доступ до TU
Доступ визначають поточні призначення в Family.
| Роль | Які TU бачить |
|---|---|
StackRoles.operator | TU, які напряму закріплені за цим оператором. |
StackRoles.supervisor | TU операторів своєї команди. |
StackRoles.topManager | TU всіх команд тімлідів, закріплених за цим топ-менеджером. Прямого призначення топ-менеджера на TU немає. |
StackRoles.client_manager | TU, які напряму закріплені за цим КМ. |
Неактивне призначення доступу не дає.
Логи призначень
Логи потрібні не для поточного доступу, а щоб визначити, які повідомлення користувач мав побачити раніше.
У всіх Family лог призначення має однаковий зміст: коли TU призначили або зняли з Family-призначення оператора, тімліда чи КМ. Запис містить TU, Family, це призначення та час події. Пари «призначили» / «зняли» утворюють проміжок доступу до TU.
Єдиний формат інтервалу
Адаптер Family повертає нормалізований інтервал, а решта чату не залежить від назв моделей і подій конкретної Family:
type TuAccessInterval = {
family: Families;
tuId: string;
role: StackRoles.operator | StackRoles.supervisor | StackRoles.client_manager | StackRoles.topManager;
assignmentId: string;
startedAt: number;
endedAt: number | null;
source: { setLogId: string; offLogId?: string };
};endedAt: nullозначає доступ, який триває зараз.- Логи сортуються за
timestamp, а для однаковихtimestamp- за ID логу. set_*відкриває інтервал, повторнийset_*для вже відкритої пари не створює дубль.off_*закриває останній відкритий інтервал тієї ж париTU + Family assignment; orphanoff_*та незакриті/дубльовані пари мають потрапляти в технічний лог для подальшої перевірки.- Поточний стан Family використовується лише як sanity check для незакритого інтервалу, але не замінює історичний лог.
Принципи по ролях
Оператор
Прямий доступ. Інтервал належить парі TU + operatorFamilyId:
- відкриття:
set_operator; - закриття:
off_operator; - джерело ідентифікатора TU: локальний ID анкети у Family;
- поточний стан:
IFamilyOperator.ladies.
Тімлід (supervisor)
Прямий доступ. Інтервал належить парі TU + supervisorFamilyId:
- відкриття:
set_supervisor; - закриття:
off_supervisor; - поточний стан: усі TU операторів, що зараз входять до команди тімліда.
Цей доступ не потрібно виводити через оператора, якщо Family пише прямі set_supervisor / off_supervisor: прямий лог є джерелом правди і не дає помилок на проміжних переміщеннях оператора.
Клієнт-менеджер (client_manager)
Прямий доступ. Інтервал належить парі TU + clientManagerFamilyId:
- відкриття:
set_cm; - закриття:
off_cm; - поточний стан:
IFamilyClientManager.ladies.
Топ-менеджер (topManager)
Непрямий доступ. Окремий інтервал для topManager не створюється: він отримує об’єднання інтервалів доступу своїх тімлідів.
- Визначити список
supervisorFamilyIdтімлідів, що належать цьому топ-менеджеру. - Взяти для них прямі інтервали
TU -> supervisorFamilyId. - Об’єднати перетинні або суміжні інтервали однієї
TU.
Джерелом історії є логи тімлідів set_supervisor / off_supervisor; окремий set_top_manager / off_top_manager не потрібен. Межі інтервалу зберігаються такими, якими вони є в логах тімлідів: топ-менеджер бачить повідомлення за періоди, коли TU була у будь-якого з його тімлідів.
Матриця джерел логів
| Family | Оператор | Тімлід | Клієнт-менеджер | Топ-менеджер | Стан |
|---|---|---|---|---|---|
| Udate | udate_log_lady: set/off_operator | udate_log_lady: set/off_supervisor | udate_log_lady: set/off_cm | Об’єднання інтервалів його тімлідів | Перша реалізація |
| Golden | Логи доступні через зовнішній сервіс/RMQ | Логи доступні через зовнішній сервіс/RMQ | Логи доступні через зовнішній сервіс/RMQ | Потрібно підтвердити повний історичний ланцюг | Після Udate |
| Prime | Формат та read API треба підтвердити | Формат та read API треба підтвердити | Формат та read API треба підтвердити | Потрібно підтвердити повний історичний ланцюг | Заблоковано дослідженням |
| Chathouse | log_lady, але події та відповідність TU треба нормалізувати | Немає прямого логу TU → тімлід | log_lady | Потрібен повний історичний ланцюг | Не виводити непрямо |
Порівняння наявних логів TU
Спільне
udate_log_lady, Chathouse log_lady та Golden golden_log_lady описують зміну зв’язку анкети з Family-призначенням. У всіх є:
- час події
timestamp; - ініціатор
initiatorIdі його рольinitiatorType; - ідентифікатор TU (локальний Mongo ID та/або зовнішній API ID);
- ідентифікатори призначень оператора, тімліда або КМ;
- тип події
event; - додаткові дані на момент переміщення, наприклад admin і бонуси.
Саме пара TU + Family assignment + event + timestamp потрібна для формування інтервалу доступу. Дані про ініціатора, бонуси й admin корисні для аудиту, але не впливають на межі інтервалу.
Відмінності
| Ознака | Udate udate_log_lady | Chathouse log_lady | Golden golden_log_lady |
|---|---|---|---|
| Ідентифікатор TU | ladyMongoId | ladyDbId | ladyMongoId |
| Зовнішній ID TU | ladyId_api: number | api_id: Mixed | ladyId_api: number |
| ID КМ | clManFamilyId | cmFamilyId | clManFamilyId |
| Події та роль ініціатора | рядкові StackLadyLogEventType / StackInitiatorLogType | числові LogLadyEventType / LogInitiatorType | рядкові StackLadyLogEventType / StackInitiatorLogType |
| Family | визначається моделлю Udate | явне поле family у спільній колекції | визначається моделлю Golden |
| Події доступу | прямі set/off_operator, set/off_supervisor, set/off_cm | за поточними записами гарантовані лише set/off_cm | прямі set/off_operator, set/off_supervisor, set/off_cm; також є не пов’язаний з доступом operator_login |
| Індекси доступу | складені за operator/supervisor/CM, event, timestamp | лише timestamp | потрібно звірити з реальною моделлю перед міграцією |
Коментар у LogLadyModel.ts про golden_log_lady є документацією іншої моделі, а не частиною runtime-схеми Chathouse log_lady. Їх не можна вважати однією колекцією лише через сусідство в файлі.
Пропозиція: єдина колекція подій доступу до TU
Не об’єднувати поточні audit-колекції фізично: вони мають різні поля, enum-и, додаткові бізнес-дані та можуть бути потрібні іншим модулям. Замість цього створити окрему append-only колекцію tu_assignment_logs з єдиним контрактом. Вона містить лише дані, потрібні чату для доступу та unread.
type TuAssignmentLog = {
id: string;
family: Families;
tuId: string;
tuApiId?: string | number;
role: StackRoles.operator | StackRoles.supervisor | StackRoles.client_manager;
assignmentId: string;
action: 'assigned' | 'unassigned';
timestamp: number;
initiator?: { id: string; role: string };
source?: { collection: string; logId: string };
metadata?: Record<string, unknown>;
};topManager не пишеться в цю колекцію: його доступ похідний від інтервалів supervisor його тімлідів.
Запис нових подій
Кожна Family після успішної зміни призначення записує свою звичну audit-подію і, в межах того самого use case, додає нормалізований запис у tu_assignment_logs:
- Udate: напряму мапить
set/off_operator,set/off_supervisor,set/off_cm. - Golden: мапить ті самі події, але публікує/реплікує запис у Stack, якщо Golden лишається окремим сервісом.
- Chathouse: одразу пише доступні
set/off_cm; для оператора й тімліда спочатку треба додати прямі події у місце фактичної зміни призначення. - Prime: додає writer після підтвердження його джерела подій.
Ідемпотентність забезпечується унікальним індексом source.collection + source.logId. Якщо подія не має першоджерельного ID, ключем є family + tuId + role + assignmentId + action + timestamp.
Читання та міграція
TuAccessIntervalsServiceспочатку читаєtu_assignment_logs, а не Family-специфічні моделі.- Існуючі Udate й Golden логи одноразово backfill-яться у новий формат із посиланням на source log.
- Старі колекції лишаються джерелом повного аудиту й не видаляються.
- Для Chathouse історію можна backfill-ити лише для КМ; відсутні події оператора/тімліда не реконструюються з поточного стану.
- Після backfill запускається звірка: відкритий інтервал повинен відповідати поточному Family-призначенню, а розбіжності логуються для ручної перевірки.
Перша реалізація: Udate
Udate обрано першим, бо всі логи прямого доступу зберігаються локально в udate_log_lady, мають timestamp, ідентифікатори TU та Family-призначень, а також індекси для operatorFamilyId, supervisorFamilyId і clManFamilyId.
- Створити
TuAccessIntervalsServiceта інтерфейсITuAccessIntervalsAdapterу модулі TU-chat. - Реалізувати
UdateTuAccessIntervalsAdapter, який читаєUdateLogLadyModelі формує інтервали дляoperator,supervisor,client_manager; дляtopManagerсервіс об’єднує інтервали його тімлідів. - Додати unit-тести для звичайної пари
set/off, активного інтервалу, повторногоset, orphanoffі кількох перепризначень однієї TU. - Використати сервіс лише для перевірки історичного доступу й розрахунку unread у Udate; поточний socket
joinяк і раніше перевіряє саме поточний доступ. - Додати тести для
topManager: один тімлід, кілька тімлідів, перетинні інтервали та активний інтервал. - Для Golden, Prime та Chathouse додати окремі адаптери тільки після підтвердження їхніх read API та повноти подій; не змішувати їхню специфіку з Udate-адаптером.
| Роль | Як визначити проміжок доступу до TU |
|---|---|
| Оператор | Знайти події призначення та зняття TU з Family-призначення цього оператора. |
| КМ | Знайти події призначення та зняття TU з Family-призначення цього КМ. |
| Тімлід | Знайти події призначення та зняття TU з Family-призначення тімліда. |
| Топ-менеджер | Взяти та об’єднати проміжки TU його тімлідів. |
Udate веде окремий журнал таких подій; Chathouse пише зміни КМ у log_lady; Golden і Prime мають власні лог-сервіси. Потрібен адаптер, який для кожної Family повертає ці події в одному форматі.
Нюанси
supervisorу продукті — тімлід.- Лог показує зміну прив’язки до TU, а не факт прочитання повідомлення.
Зв’язки
- Чати по TU — використовують поточні призначення для доступу та історію призначень для непрочитаних.
- entities — реєстр структур Stack.