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

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

Каталог паттернов администрирования дополнительных параметров (ДП) в категориях задач. Охватывает JSON-форматы значений для ДП «Выбор пользователя» и «Таблица», операции с мультилукапами, настройку связей между ДП, управление доступами через SQL-функции и базовые конфигурационные настройки ДП в категории. Каждый паттерн содержит условие применения и готовый код или описание настройки. Формат записи: паттерн, когда применять, код — без tutorial-нарратива. Паттерны сгруппированы по типу ДП и области применения.

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

Значение ДП «Выбор пользователя» передается строкой 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]}}'

Для добавления пользователей при постановке или переходе задачи указывается список ID в массиве Added:

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

Для назначения орг. единиц используются строковые ID. Напрямую по 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-запросом по бизнес-логике (например менеджеры проекта по округу), применяется STRING_AGG:

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-наполнение и CRUD-операции

Добавление строки в таблицу (например наполнение проектной команды): указываются только заполняемые колонки, пустые можно пропустить, массив заключается в [ ].

'+[{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:

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

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

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

Связи ДП — жесткая и слабая фильтрация

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

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

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

Слабая (нежесткая) связь фильтрует подчиненный ДП по родительскому, но при пустом родительском показывает ВСЕ значения (например Контрагент → Контактное лицо). Настройка аналогична жесткой, но Жесткая связь = False.

Доступы к ДП — уровни и SQL-функции

Уровни доступа к ДП определяются числовыми значениями:

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

Скалярная SQL-функция доступа применяется, когда доступ к ДП зависит от роли пользователя в задаче (ответственный, руководитель) или статуса задачи. Принцип: начать с @access = 0, затем повышать от меньшего к большему. Входные параметры: @TaskID, @ExtParamID, @UserID, @StateID, @IsResponsiblePerformer и др. Одна функция может обслуживать несколько ДП категории по @ExtParamID.

Пакетная SQL-функция доступа и альтернативы

Пакетная 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-функции Доступ зависит от данных в задаче (роль в задаче, значения ДП)

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

Свойства ДП, неизменяемость типа и синхронизация между задачами

Настройки ДП существуют на двух уровнях:

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

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

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

Синхронизация ДП между задачами настраивается по направлению:

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

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