Паттерны администрирования ДП¶
Формат: паттерн, когда применять, код. Без 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 ДП в обеих категориях.