tu

TU

src/stack/components/ticket/ticket-tu.service.ts — Family-окрема анкета, навколо якої визначаються робочий доступ і чат.

Суть

TU — анкета в конкретній Family. Одна клієнтка в різних Family має різні TU; для чату це різні сутності.

Поточний доступ до TU

Доступ визначають поточні призначення в Family.

РольЯкі TU бачить
StackRoles.operatorTU, які напряму закріплені за цим оператором.
StackRoles.supervisorTU операторів своєї команди.
StackRoles.topManagerTU всіх команд тімлідів, закріплених за цим топ-менеджером. Прямого призначення топ-менеджера на TU немає.
StackRoles.client_managerTU, які напряму закріплені за цим КМ.

Неактивне призначення доступу не дає.

Логи призначень

Логи потрібні не для поточного доступу, а щоб визначити, які повідомлення користувач мав побачити раніше.

У всіх 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; orphan off_* та незакриті/дубльовані пари мають потрапляти в технічний лог для подальшої перевірки.
  • Поточний стан 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 не створюється: він отримує об’єднання інтервалів доступу своїх тімлідів.

  1. Визначити список supervisorFamilyId тімлідів, що належать цьому топ-менеджеру.
  2. Взяти для них прямі інтервали TU -> supervisorFamilyId.
  3. Об’єднати перетинні або суміжні інтервали однієї TU.

Джерелом історії є логи тімлідів set_supervisor / off_supervisor; окремий set_top_manager / off_top_manager не потрібен. Межі інтервалу зберігаються такими, якими вони є в логах тімлідів: топ-менеджер бачить повідомлення за періоди, коли TU була у будь-якого з його тімлідів.

Матриця джерел логів

FamilyОператорТімлідКлієнт-менеджерТоп-менеджерСтан
Udateudate_log_lady: set/off_operatorudate_log_lady: set/off_supervisorudate_log_lady: set/off_cmОб’єднання інтервалів його тімлідівПерша реалізація
GoldenЛоги доступні через зовнішній сервіс/RMQЛоги доступні через зовнішній сервіс/RMQЛоги доступні через зовнішній сервіс/RMQПотрібно підтвердити повний історичний ланцюгПісля Udate
PrimeФормат та read API треба підтвердитиФормат та read API треба підтвердитиФормат та read API треба підтвердитиПотрібно підтвердити повний історичний ланцюгЗаблоковано дослідженням
Chathouselog_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_ladyChathouse log_ladyGolden golden_log_lady
Ідентифікатор TUladyMongoIdladyDbIdladyMongoId
Зовнішній ID TUladyId_api: numberapi_id: MixedladyId_api: number
ID КМclManFamilyIdcmFamilyIdclManFamilyId
Події та роль ініціаторарядкові 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.

Читання та міграція

  1. TuAccessIntervalsService спочатку читає tu_assignment_logs, а не Family-специфічні моделі.
  2. Існуючі Udate й Golden логи одноразово backfill-яться у новий формат із посиланням на source log.
  3. Старі колекції лишаються джерелом повного аудиту й не видаляються.
  4. Для Chathouse історію можна backfill-ити лише для КМ; відсутні події оператора/тімліда не реконструюються з поточного стану.
  5. Після backfill запускається звірка: відкритий інтервал повинен відповідати поточному Family-призначенню, а розбіжності логуються для ручної перевірки.

Перша реалізація: Udate

Udate обрано першим, бо всі логи прямого доступу зберігаються локально в udate_log_lady, мають timestamp, ідентифікатори TU та Family-призначень, а також індекси для operatorFamilyId, supervisorFamilyId і clManFamilyId.

  1. Створити TuAccessIntervalsService та інтерфейс ITuAccessIntervalsAdapter у модулі TU-chat.
  2. Реалізувати UdateTuAccessIntervalsAdapter, який читає UdateLogLadyModel і формує інтервали для operator, supervisor, client_manager; для topManager сервіс об’єднує інтервали його тімлідів.
  3. Додати unit-тести для звичайної пари set/off, активного інтервалу, повторного set, orphan off і кількох перепризначень однієї TU.
  4. Використати сервіс лише для перевірки історичного доступу й розрахунку unread у Udate; поточний socket join як і раніше перевіряє саме поточний доступ.
  5. Додати тести для topManager: один тімлід, кілька тімлідів, перетинні інтервали та активний інтервал.
  6. Для 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.