Перейти к содержанию

Замещение (Substitution)

Источники: DB_MSSQL (UserAssistant, fn_UserTaskPermissionsTemplate), core (AssistantService)

1. Что это

Замещение — механизм временной передачи прав от одного пользователя (принципала) другому (заместителю). Заместитель получает доступ к задачам принципала на указанный период, с ограничениями по конфиденциальности и шифрованию.

Не путать с: - Перевоплощением — администратор работает от имени пользователя (другая сессия). См. impersonation.md - Процессными помощниками — информационная запись, НЕ дающая прав (см. §8)

2. Бизнес-логика

Что получает заместитель

  • Доступ к задачам принципала в разрешённых категориях (по OR-логике, как 6-й уровень доступа)
  • Копии писем и комментариев принципала (кроме служебных комментариев о действиях)
  • Блок «Избранное (Фамилия Имя)» в боковой панели — дерево Избранного принципала
  • Тикеры принципала — счётчики задач принципала отображаются в скобках рядом с собственными

Чего НЕ получает заместитель

  • Конфиденциальные задачи — принципала (IsConfidential=1). Полная блокировка
  • Зашифрованные задачи — принципала (IsEncrypted=1). Полная блокировка
  • Чаты принципала — по умолчанию исключены (CategoryInclusionMode=2, blacklist содержит ChatSubcatID)
  • Скрытую оценку исполнителей — ViewSecretPerformerPoint

Матрица защиты

Защитный механизм Замещение Перевоплощение
Конфиденциальность (IsConfidential) Заблокировано Доступ есть (by design)
Шифрование (IsEncrypted) Заблокировано Заблокировано
Конфиденциальность + шифрование Заблокировано Заблокировано

3. Управление (пользовательский интерфейс)

Кто назначает

Пользователь назначает заместителя себе сам — раздел «Заместители» в профиле. Администратор тоже может это сделать.

Параметры назначения

Параметр Описание
Кому Выбор заместителя
Период (DTFrom — DTTo) Начало и окончание. NULL = бессрочно
Режим категорий «Только категории» (whitelist) или «Кроме категорий» (blacklist)
Категории Список категорий для режима

По умолчанию — режим «Кроме категорий» (blacklist). Заместителям в этом режиме недоступны чаты и приватные задачи в категориях с шифрованием/конфиденциальностью.

Завершение замещения

  • Принципал: кнопка «Завершить замещение» во вкладке «Заместители»
  • Заместитель: кнопка «Завершить замещение» во вкладке «Заместитель у»
  • Автоматически: по истечении DTTo
  • Если период ещё не начался и замещение удалено вручную — запись удаляется из таблицы (с версии 265 Цефей)

Взаимное замещение

Допускается: можно назначить заместителем пользователя, который сам уже является замещающим.

4. Таблицы БД

4.1 UserAssistant (главная)

Колонка Тип Описание
ID int, PK, Identity ID записи
UserId int, FK → Users Принципал (кого замещают)
AssistantUserID int, FK → Users Заместитель (кто замещает)
DTFrom datetime, NULL Начало периода. NULL = бессрочно
DTTo datetime, NULL Конец периода. NULL = бессрочно
CategoryInclusionMode int 1 = whitelist, 2 = blacklist
RangeFrom computed ISNULL(DTFrom, '19000101')
RangeTo computed ISNULL(DTTo, '20790606')
IsActual bit Пересчитывается по датам

CHECK: CategoryInclusionMode IN (1, 2).

7 индексов: - ix__...__AssistantUserID__where_IsActual — filtered WHERE IsActual=1 - ix__...__RangeTo — для пересчёта актуальности - ix__...__UserID — filtered WHERE DTFrom IS NULL AND DTTo IS NULL (бессрочные) - ix_fk_UserAssistant_UserId(UserId, DTTo) INCLUDE(DTFrom, AssistantUserID, CategoryInclusionMode) - ix_UserAssistant_AssistantUserID_RangeFrom / RangeTo — диапазонный поиск - ux__...__ID__where_IsActual — unique filtered

4.2 UserAssistantCategories (ограничения по категориям)

Колонка Тип Описание
Id int, PK, Identity ID записи
AssistanceId int, FK → UserAssistant.ID Ссылка на замещение
SubcatId int, FK → Subcategories Категория

Два unique-индекса: (SubcatId, AssistanceId) и (AssistanceId, SubcatId) — оба направления поиска.

4.3 Семантика CategoryInclusionMode + UserAssistantCategories

Mode Записи в UAC Результат
1 (whitelist) Список категорий Заместитель видит задачи принципала только в перечисленных категориях
2 (blacklist) Список исключений Заместитель видит задачи принципала во всех категориях кроме перечисленных

5. SQL-функции

5.1 fn_GetUserAssistants

Возвращает всех принципалов, для которых @UserID является заместителем.

Параметры: @UserID, @include_user_itself, @convert_tz_f

Логика: SELECT FROM UserAssistant WHERE IsActual=1 AND AssistantUserID=@UserID

Возвращает: (UserId, CategoryInclusionMode, AssistanceId)

5.2 fn_GetUserAssistantsBySubcat

Фильтрует принципалов с учётом конкретной категории.

Параметры: @UserID, @SubcatID, @include_user_itself, @convert_tz_f

Логика: 1. Получить принципалов через fn_GetUserAssistants 2. LEFT JOIN UserAssistantCategories по AssistanceId + SubcatId 3. Фильтр: - Mode=2 AND UAC IS NULL → категория НЕ в blacklist → доступ есть - Mode=1 AND UAC IS NOT NULL → категория В whitelist → доступ есть

5.3 vwAllCurrentUserAssistants (view)

SELECT ua.UserID AS PrincipalUserID, ua.AssistantUserID,
       ua.CategoryInclusionMode, ua.ID AS AssistanceId
FROM UserAssistant ua WHERE ua.IsActual = 1

Используется в DescendantSubcatAccessRefresh.

6. Интеграция с моделью прав

6.1 В fn_UserTaskPermissionsGeneral (через шаблон fn_UserTaskPermissionsTemplate)

Каждый источник доступа в шаблоне имеет два подзапроса: 1. Прямой доступ @UserID 2. Доступ через замещение (внутри маркеров --$$BEGIN ASSISTANTS$$ / --$$END ASSISTANTS$$)

Паттерн замещения в каждой ветви:

SELECT TaskID
FROM fn_GetUserAssistants(@UserID, 0, 0) uass
     LEFT JOIN UserAssistantCategories uac ON uac.AssistanceId = uass.AssistanceId
               AND uac.SubcatId = @SubcatID
     JOIN <источник> ON <источник>.UserID = uass.UserId
     LEFT JOIN vwTasksPrivate tpr ON tpr.TaskID = <источник>.TaskID
WHERE @WithoutAssistantsOnlyF = 0
  AND tpr.TaskID IS NULL  -- исключение конфиденциальных + зашифрованных
  AND (
      uass.CategoryInclusionMode = 2 AND uac.AssistanceId IS NULL   -- blacklist ok
      OR
      uass.CategoryInclusionMode = 1 AND uac.AssistanceId IS NOT NULL -- whitelist ok
  )

Ключевые элементы: - fn_GetUserAssistants — список принципалов - LEFT JOIN vwTasksPrivate + IS NULL — исключение конфиденциальных/зашифрованных задач - CategoryInclusionMode whitelist/blacklist логика - @WithoutAssistantsOnlyF=1 отключает все ветви замещения

Источники, имеющие ветвь замещения: акцептанты, смарт-доступ, подписки индивидуальные, подписки групповые, гибкие права, кастомные права (6 из 9 источников).

Не имеют ветви замещения: категорийный доступ (уже в DescendantSubcatAccess), руководители заказчиков, руководители исполнителей.

6.2 В DescendantSubcatAccessRefresh

SP включает замещение как отдельный источник в кэш DescendantSubcatAccess:

-- Замы, наследующие доступ к правам принципалов
SELECT uas.AssistantUserID, uascat.SubcatID,
       0 AS OwnAccessSourceF, 1 AS AssistanceSourceF, 0 AS DirectorSourceF
FROM vwAllCurrentUserAssistants uas
     CROSS APPLY (
         -- Mode 1: whitelist
         SELECT uac.SubcatId FROM UserAssistantCategories uac
                JOIN @UserGroupsPermissions ugp ON ugp.SubcatID = uac.SubcatId
                     AND ugp.UserID = uas.PrincipalUserID
         WHERE uas.CategoryInclusionMode = 1 AND uac.AssistanceId = uas.AssistanceId
         UNION ALL
         -- Mode 2: blacklist
         SELECT ugp.SubcatId FROM @UserGroupsPermissions ugp
                LEFT JOIN UserAssistantCategories uac ON uac.AssistanceId = uas.AssistanceId
                     AND uac.SubcatId = ugp.SubcatID
         WHERE uas.CategoryInclusionMode = 2 AND ugp.UserID = uas.PrincipalUserID
               AND uac.SubcatId IS NULL
     ) uascat
WHERE @AssistanceF = 1

Результат: если принципал имеет ActionID=8 (ViewAll) на категорию и замещение для неё разрешено — заместитель получает AssistanceSourceF=1 в кэше. Это даёт категорийный доступ без задача-по-задаче проверки.

7. ExcludeSystemSubstitutes (подписи)

Настройка на уровне шага маршрута подписи. Если включена (ExcludeSystemSubstitutes = true), заместитель не может подписывать вместо принципала — требуется именно тот пользователь, который указан акцептантом.

Две точки управления: - Глобальная: SettingsCustom ключ ExcludeSystemSubstitutes - На шаге маршрута: StatesRoutesSignatures.ExcludeSystemSubstitutes

Обе должны быть true для исключения заместителей из подписи.

Код: TaskSignaturesEntityService.cs:540, TaskSignatureActionsService.cs:1179

8. Процессные помощники

Не путать с замещением. Процессный помощник: - НЕ получает прав — назначение чисто информационное - Отображается в профиле пользователя - Указывает процесс, в котором помогает - Удалить может только назначивший пользователь

9. Связанная документация

  • business.md §5 — доступ заместителя в модели 6 уровней
  • database.md §11 — SQL-механика замещения в DescendantSubcatAccessRefresh
  • access-check.md — fn_UserTaskPermissionsGeneral с ветвями замещения
  • impersonation.md — сравнение с перевоплощением
  • docs/domains/signatures/ — подписи и ExcludeSystemSubstitutes

10. Источники кода

Объект Файл
UserAssistant DDL DB_MSSQL/dbo.UserAssistant.Table.sql
UserAssistantCategories DDL DB_MSSQL/dbo.UserAssistantCategories.Table.sql
fn_GetUserAssistants DB_MSSQL/dbo.fn_GetUserAssistants.UserDefinedFunction.sql
fn_GetUserAssistantsBySubcat DB_MSSQL/dbo.fn_GetUserAssistantsBySubcat.UserDefinedFunction.sql
fn_UserTaskPermissionsTemplate DB_MSSQL/dbo.fn_UserTaskPermissionsTemplate.UserDefinedFunction.sql
DescendantSubcatAccessRefresh DB_MSSQL/dbo.DescendantSubcatAccessRefresh.StoredProcedure.sql
ExcludeSystemSubstitutes core/TCDataAccess/Kernel/Services/Entity/Tasks/TaskSignaturesEntityService.cs:540