Паттерны администрирования ДП¶
Каталог паттернов администрирования дополнительных параметров (ДП) в категориях задач. Охватывает 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 ДП в обеих категориях.