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

Паттерны администрирования ДП

Формат: паттерн, когда применять, код. Без tutorial-нарратива.

ДП «Выбор пользователя» -- JSON-формат

Полная JSON-схема

Значение ДП «Выбор пользователя» передается строкой JSON. Три ветви: пользователи, группы, орг. единицы. Каждая содержит массивы Added и Deleted с ID.

'{"Users":{"Deleted":[XX,...,XX],"Added":[XX,...,XX]},"Groups":{"Deleted":[XX,...,XX],"Added":[XX,...,XX]},"OrgUnits":{"Deleted":[XX,...,XX],"Added":[XX,...,XX]}}'

Добавить пользователей

Когда: заполнение ДП при постановке/переходе -- список пользователей по условию.

'{"Users":{"Deleted":[],"Added":[2110,2122]},"Groups":{"Deleted":[],"Added":[]},"OrgUnits":{"Deleted":[],"Added":[]}}'

Добавить орг. единицы

Когда: назначение целой орг. единицы (напр. «Менеджер проекта» округа).

Напрямую по ID:

'{"Users":{"Deleted":[],"Added":[]},"Groups":{"Deleted":[],"Added":[]},"OrgUnits":{"Deleted":[],"Added":["1234","5678"]}}'

Через текстовый ДП, содержащий ID:

'{"Users":{"Deleted":[],"Added":[]},"Groups":{"Deleted":[],"Added":[]},"OrgUnits":{"Deleted":[],"Added":["' + ДП_ID_орг.единицы + '"]}}'

Удалить пользователей из ДП

Когда: при переходе нужно оставить в ДП только определенных пользователей (напр. только ответственного).

Используется смарт-выражение: функция «Сконкатенировать в строку» для формирования списка ID удаляемых пользователей, помещаемого в "Deleted".

SQL-паттерн: STRING_AGG для динамического заполнения

Когда: список пользователей собирается SQL-запросом по бизнес-логике (напр. менеджеры проекта по округу).

DECLARE @managers VARCHAR(50) = ''
SET @managers =
    (SELECT STRING_AGG(UO.UserID, ',')
    FROM TasksInSubcat53Denormalized OPVZ WITH (NOLOCK)
        JOIN TasksInSubcat52Denormalized PPVZ WITH (NOLOCK)
            ON OPVZ.extparam174nativevalue = PPVZ.taskid
        JOIN TasksInSubcat47Denormalized OKR WITH (NOLOCK)
            ON OKR.taskid = PPVZ.extparam143nativevalue
        JOIN ExtParamSelectUsersValuesOrgUnits RO WITH (NOLOCK)
            ON OKR.taskid = RO.taskid AND RO.extparamid = 150
        JOIN OrgStructureUnit OU WITH (NOLOCK)
            ON OU.id = RO.OrgStructureUnitID
        JOIN OrgStructureUnit OU2 WITH (NOLOCK)
            ON OU.ParentId = OU2.Id
        JOIN OrgStructureUnit OU3 WITH (NOLOCK)
            ON OU3.ParentId = OU2.Id AND OU3.IsDirector <> 1
        JOIN UserOrgStructureUnit UO WITH (NOLOCK)
            ON UO.OrgStructureUnitId = OU3.id
    WHERE OPVZ.taskid = @contextid)

SELECT
'{"Users":{"Deleted":[],"Added":['+@managers+']},"Groups":{"Deleted":[],"Added":[]},
"OrgUnits":{"Deleted":[],"Added":[]}}'

Ключевой прием: STRING_AGG(UserID, ',') формирует строку '2110,2122,2131', которая подставляется в JSON.

ДП «Таблица» -- JSON-наполнение

Добавление строки

Когда: автоматическое добавление строк в таблицу (напр. наполнение проектной команды).

Указываются только заполняемые колонки. Пустые можно пропустить. Массив заключается в [ ].

'+[{ID_колонки:{"First":"значение"}, ID_колонки2:{"First":"значение2"}}]'

Пример -- добавить номер задачи в колонку 3:

'+[{3:{"First":"' + CAST(TaskID AS varchar(10)) + '"}}]'

Добавление строки с несколькими значениями в ячейке (STRING_AGG)

Когда: в ячейку записывается список (напр. несколько пользователей в колонку «Ответственный»).

SELECT DISTINCT
    '+[{5:{"First":"' + CAST(t1.TaskID AS varchar(10)) + '"},'
    + '6:{"First":"'
    + (SELECT STRING_AGG(CAST(t2.UserID AS varchar(10)), ',')
       FROM CTE t2
       WHERE t1.TaskID = t2.TaskID)
    + '"}}]'
FROM CTE t1;

Обновление существующей строки

Когда: изменить значения в конкретной строке таблицы. Указываются только изменяемые колонки, остальные сохраняются.

'={"First":"ID_строки","Second":{ID_колонки:{"First":"новое_значение"}}}'

Пример -- обновить колонку 6 (ответственный) в существующей строке:

SELECT '={"First":"' + CAST(t1.RowID AS varchar(4)) + '",'
    + '"Second":{6:{"First":"'
    + STUFF((SELECT ',' + ISNULL(CAST(t2.UserID AS varchar(10)), '')
             FROM CTE2 t2
             WHERE t1.TaskID = t2.TaskID
             FOR XML PATH('')), 1, 1, '')
    + '"}}}'
FROM CTE2 t1

Важно: ID_строки берется в кавычки (это строковое значение в JSON), ID_колонки -- нет (это ключ JSON-объекта, числовой).

Удаление строки

Когда: убрать строку из таблицы. Важен только ID строки.

'-ID_строки'

ID строки НЕ берется в кавычки (здесь это не JSON-значение, а строковый литерал с префиксом -).

Полная перезапись таблицы (операторы # и |)

Когда: нужно обновить все строки таблицы одной операцией.

Оператор Поведение
\| (pipe) Удаляет ВСЕ строки, затем создает из переданных данных
# Удаляет отсутствующие, добавляет новые, обновляет совпадающие (diff)

Формат одинаковый:

'#{"ID_строки":{ID_колонки:{"First":XX},...},...}'
'|{"ID_строки":{ID_колонки:{"First":XX},...},...}'

ID строки берется в кавычки, ID колонки -- нет.

Мультилукапы -- операции

Добавить одну запись

Когда: при смене ДП или статуса -- добавить задачу в мультилукап.

ВСтроку(ID_задачи)

Удалить одну запись

Когда: при переходе в неактивный статус -- убрать задачу из мультилукапа.

'-' + ВСтроку(ID_задачи)

Префикс - перед ID означает удаление из мультилукапа.

Заполнить мультилукап по условию

Когда: после постановки задачи -- автоматически заполнить мультилукап из категории-источника по фильтру (напр. все контактные лица контрагента).

Способ: создать смарт-правило «После постановки» / «После смены ДП». Действие: «Изменить значение ДП». Значение: выбрать все задачи из категории-источника, где ДП = значению ДП текущей задачи.

Связи ДП

Жесткая связь (parent-child)

Когда: подчиненный ДП доступен для выбора ТОЛЬКО если заполнен родительский (напр. Округ -> Регион -> Город).

Настройка: «Доп. параметры» >> «Связи доп. параметров» >> Создать.

Параметр Значение для Lookup -> Lookup
Источник данных ExtParamValue
Колонка для отбора SelectedTaskID
Значение подчиненного параметра TaskID
Текст подчиненного параметра TaskID
Жесткая связь True

Поведение: при пустом родительском ДП подчиненный пуст. При заполнении -- только связанные значения.

Слабая (нежесткая) связь

Когда: подчиненный ДП фильтруется по родительскому, но при пустом родительском показывает ВСЕ значения (напр. Контрагент -> Контактное лицо).

Настройка аналогична жесткой, но Жесткая связь = False.

Доступы к ДП

Уровни доступа

Значение Право
0 Нет доступа
1 Чтение
2 Редактирование

Скалярная SQL-функция доступа

Когда: доступ к ДП зависит от роли пользователя в задаче (ответственный, руководитель), статуса задачи.

Принцип: начать с @access = 0, затем повышать от меньшего к большему.

Входные параметры: @TaskID, @ExtParamID, @UserID, @StateID, @IsResponsiblePerformer и др.

Одна функция может обслуживать несколько ДП категории -- по @ExtParamID.

Пакетная SQL-функция (batch)

Когда: ДП отображается в гриде -- скалярная функция вызывается N раз (на каждую задачу), нужна оптимизация.

Именование: {имя_скалярной}_batch.

Шаблон:

CREATE OR ALTER FUNCTION dbo.smartAccessBatch
(
  @taskIds dbo.id_tbl_type READONLY,
  @extParamId int,
  @userId int
)
RETURNS TABLE
AS
RETURN
   SELECT tid.Id TaskId, 2 Access
   FROM @taskIds tid
;

Вход: массив TaskID (dbo.id_tbl_type). Выход: таблица (TaskId, Access).

Логика аналогична скалярной, но IF заменяется на CASE:

SELECT tid.Id AS TaskId,
    CASE
        WHEN @ExtParamId IN (166,177)
             AND ts.ResponsiblePerformerId = @userId
             AND StateId NOT IN (2, 46, 47)
        THEN 2
        WHEN @extParamId IN (167,166,177)
             AND (ts.ResponsiblePerformerId = @userId OR ...)
        THEN 1
        ELSE 0
    END AS Access
FROM @taskIds tid
LEFT JOIN TaskStates ts ON ts.taskid = tid.Id

Альтернатива пакетной функции: «Просмотр не ограничивается»

Когда: скалярная функция возвращает только 1 или 2 (не возвращает 0). Тогда в гриде можно не вызывать функцию -- все ДП видны, а при открытии задачи доступы определяются по скалярной функции.

Базовые настройки ДП в категории

Типы доступа к ДП

Способ Когда применять
По задаче Нет специфических ограничений по группам/ролям
Только по группам Доступ зависит только от группы пользователя
По матрице доступа Доступ зависит от комбинации группа + роль + статус
По SQL-функции Доступ зависит от данных в задаче (роль в задаче, значения ДП)

Доступ по статусу

Когда: ДП нередактируемо после определенного статуса.

Правило: на статусы ДО и включая текущий -- чтение + редактирование. На ВСЕ последующие -- только чтение.

Свойства ДП: общие vs в категории

Уровень Что настраивается Область действия
Свойства ДП (отдельная страница) Формат, источник данных, разрешения Все категории, где используется
Настройки в категории (правая панель) Видимость, обязательность, блок, доступ Только эта категория

Изменение свойств ДП применяется ко ВСЕМ категориям, где он используется.

Тип ДП нельзя изменить

Если тип выбран неверно -- удалить и создать заново. Удаление невозможно, если ДП используется в задачах.

Синхронизация ДП между задачами

Настройка Направление Когда срабатывает
Синхронизировать в родительскую Дочерняя -> Родительская При заполнении ДП в дочерней
Синхронизировать в дочернюю Родительская -> Дочерняя При изменении ДП в родительской (не при создании дочерней)

Условие: одинаковый ID ДП в обеих категориях.