Запрет старта задачи по свободному маршруту

22 30

Задача
Запретить некоторым пользователям возможность старта задачи по свободному маршруту, но оставить возможность старта задачи по типовому.

Решение
Сложность задачи заключается в том, что её нельзя решить стандартными средствами: в Директуме нет механизма, который бы запретил нажатие кнопки "Маршрут" (именно с её помощью создаются задачи по свободным маршрутам) или каким-то другим способом запретил создание задач по свободным маршрутам.

Тем не менее, решение есть и сводится оно к созданию триггер SQL,  который будет срабатывать каждый раз, когда пользователь пытается стартовать задачу по свободному маршруту и прекращать старт задачи, если этому пользователю задачи можно создавать только по ТМ.

Внимание! Все описанные ниже действия необходимо сначала выполнять на тестовой базе и, в случае удачного завершения, выполнять на рабочей. Также хочется отметить, что все изменения, которые вносятся в базу данных, необходимо вручную переносить при последующих обновлениях версии системы.

Шаг 1. В DIRECTUM создаем группу пользователей, которым запрещено создавать задачи по свободным маршрутам.
Имя: StandardRouteOnlyUsersGroup
Полное имя: Стартуют задачи только по типовым маршрутам
Запоминаем её ИД (пригодится дальше).

Шаг 2. Заполняем группу нужными пользователями

Шаг 3. Создаем константу, в которой указываем ИД только что созданной группы пользователей.
Имя: StandardRouteOnlyUsersGroup
Значение: ИД только что созданной группы пользователей
Примечание: ИД группы пользователей, которые могут стартовать задачи только по типовым маршрутам

Шаг 4. SQL-запросом создаем сообщение, которое будет выводиться пользователям при попытке старта задачи по свободному маршруту (2000 в запросе - код ошибки, который используется в триггере). Сообщение об ошибке можно написать любое.

insert XErrors(ErrCode, ErrGroup, Description, SrvCode, ErrType, Sost)
	values(20000, 'XMBMSG', 'Нельзя создавать задачи по свободным маршрутам. Для создания задачи воспользуйтесь кнопкой Типовой маршрут', null, 16, 'Н')

update XErrors
	set Sost='З'
	where ErrCode=20000 and Sost='Н'

 

Шаг 5. SQL-запросом создаем триггер StandardRouteOnly для таблицы SBTask (сердце решения), который будет срабатывать в момент старта задачи и будет проверять, может ли текущий пользователь стартовать задачи по указанному маршруту.

SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO

CREATE TRIGGER [dbo].[StandardRouteOnly] ON  [dbo].[SBTask] AFTER UPDATE
AS
DECLARE
	@Route VARCHAR(1), — маршрут задчи (NULL - свободный)
	@State VARCHAR(1), — состояние задачи (W - "В работе")
	@Author INT, — автор задачи (для получения пользователя)
	@UserID INT, — ИД пользователя автора
	@UsersGroup NVARCHAR(255) — группа пользователей, которым запрещено стартовать задачи по свободным маршрутам
    
-- получить маршрут задачи
SET @Route = (SELECT StandardRoute FROM Inserted)
-- получить состояние задачи
SET @State = (SELECT State FROM Inserted)
-- получить автора задачи
SET @Author = (SELECT Author FROM Inserted)
-- получить ИД пользователя автора
SET @UserID = (SELECT MBUser.UserID
		FROM MBUser
			INNER JOIN MBAnalit ON MBuser.Userkod = MBAnalit.Dop
		WHERE Analit = @Author)
-- получить группу пользователей из константы StandardRouteOnlyUsersGroup
SET @UsersGroup = (SELECT VALUE FROM MBConst WHERE Const = 'StandardRouteOnlyUsersGroup')

IF @Route IS NULL AND @State = 'W'
-- идет попытка старта задачи по свободному маршруту
BEGIN
	IF @UsersGroup IN (SELECT GroupID FROM MBUserLinkFull WHERE UserID = @UserID)
	-- пользователь входит в группу StandardRouteOnlyUsersGroup
	BEGIN
		-- откатить действие и выдать сообщение об ошибке 
		ROLLBACK TRANSACTION
		EXEC XRaisError @ErrCode = 20000
		RETURN
	END
END
GO

Теперь каждый раз, когда пользователь, входящий в группу StandardRouteOnlyUsersGroup попытается стартовать задачу по свободному маршруту, он получит вот такое сообщение:

 

Огромное спасибо Артуру за подсказку решения, Мохаммеду за красивый триггер и Антону за задачу.

Отредактировал Иван Серёдкин, 20.06.2013 в 11:00
Отредактировал Алексей Якимов, 20.06.2013 в 11:33
Иван Середкин

Комментарий для привлечения внимания (материал почему-то не попал в RSS-ленту).

Денис Баранов

А не слетит ли этот триггер при каждой перегенерации серверной части? В том числе при переносе базы с одного сервера на другой, при обновлении системы... Триггеры, насколько я помню, удаляются и создаются заново при этом.

Иван Середкин

Слетит. И сообщение об ошибке слетит (которое в таблицу XErrors добавляется).

Поэтому и написал, что все манипуляции с базой - создание сообщения об ошибке и создание триггера - необходимо выполнять каждый раз, при обновлении системы (в том числе и про активации системы с помощью SASystemActivator).

Для удобства можно объединить операцию по созданию сообщения и созданию триггера в один запрос и выполнять его после активации/конвертации/переноса базы.

Андрей Сидорчук

А, ведь такую задачу ставило передо мной руководство и сказано было технарями-  нельзя запретить. Пойду обрадую. Спасибо!

Алексей Лапихин

Отличный материал! Нужно предусмотреть такую группу и триггер в базовой поставке DIRECTUM.

Дмитрий Старков

И сразу вопрос: воможен ли аналогичный механизм при такой постановке задачи: "Нельзя отправлять задачу по свободному маршруту конкретному исполнителю". Например: чтоб генеральному директору (или губернатору области) ФИЗИЧЕСКИ никто никогда "свободные" задачи отсылать не мог - только через ТМ по строго определенным процессам (после всех необходимых согласований). А то сейчас для этого только механизм правил используем (чистим его папку "Входящие" от "свободных" заданий и перекидываем их в папку, которая у помошника - для дальнейшего разбирательства.

Антон Бирилло

Материал без сомнений полезный, убеждён не малое количество компаний нуждается в подобном.

Я думаю есть смысл всё тонкости триггера обсуждать на одноимённой теме форума:

http://club.directum.ru/forum/topic1152-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D1%82%D0%BE%D0%BB%D1%8C%D0%BA%D0%BE-%D1%82%D0%B8%D0%BF%D0%BE%D0%B2%D1%8B%D1%85-%D0%BC%D0%B0%D1%80%D1%88%D1%80%D1%83%D1%82%D0%BE%D0%B2-.aspx  

Иван Середкин

Дмитрий, возможен. Для этого:

1. Создаете константу с нужным названием.

2. В неё записываете ИД пользователя, которому нельзя стартовать задачи по свободным маршрутам.

3. Создаете нужное сообщение в таблице XErrors.

4. Меняете триггер таким образом, чтобы вместо автора задачи получался список пользователей, которые указаны в маршруте, а затем проверяете, входит ли указанный в константе пользователей в список этих пользователей. Делается это примерно так:

...
declare @UserID	varchar(255)
...
set @UserID = '44882' -- тут должен быть запрос к константе для получения правильного ИД
...
if exists(select 1 
			from Inserted
			where ParticipantsIDs = @UserID 
					or ParticipantsIDs like @UserID + ',%' 
					or ParticipantsIDs like '%,' + @UserID + ',%'
					or ParticipantsIDs like '%,' + @UserID)
-- в маршруте указан запретный пользователь
begin
	-- откатить действие и выдать сообщение об ошибке
	rollback transaction
	exec XRaisError @ErrCode = 10000
	return
end
Алексей Пестов

А лучше не константу, а справочник, чтоб указывать в справочнике ИД пользователей :)

Наталья Евшина

Иван, добрый день. Очень хороший материал, помог в решении задачи. Возникла единственная проблема, может поможете с решением. Задача на запрет ручных задач, поставилась не с самого момента внедрения системы, то есть накопилось много уже стартованных ручных задач, в том числе и сейчас в работе их очень много.

Если создаю триггер, то у всех пользователей существующие задачи прекращаются с ошибкой

"IS-Builder System User (ISBuilderSystem) за ПОЛЬЗОВАТЕЛЯ [26.08.2013 16:26:55] :
Выполнение задачи прервано по причине: "Вам нельзя создавать задачи по свободным маршрутам. Для создания задачи выберите Типовой маршрут Розница - Постановка задач|SQL State: 42000, SQL Error Code: 50349"." 

Может подскажете как можно ее избежать? Спасибо большое.

Иван Чурбаков

Как вариант, в триггере дополнительно проверять дату старта задачи (чтобы позволить рестартовать старые задачи, еще и историю можно дополнительно проверить).

Адик Крымгужин

Кстати так же тем кому необходимо запретить запуск задачи по свободному маршруту, но разрешить запуск подзадач - необходимо в триггер добавить следующие параметры:

...
@MainTaskID INT, -- ИД Главной задачи
...
 
...
-- получить главную задачу
SET @MainTaskID = (SELECT MainTaskID FROM Inserted)
...
 
...
IF @Route IS NULL AND @State = 'W' AND @MainTaskID IS NULL
-- идет попытка старта задачи по свободному маршруту
...
 
 
 
Максим Борисов

В современных реалиях надо код ошибки указывать более 50 000. Предлагаю автору чуть откорректировать статью.

Кстати возможно следует учесть и последнее замечание про подзадачи и также внести их в текст статьи.

Константин Егоров

SQL State: 42000, SQL Error Code: 52654

 

как быть? кто что делал?

Максим Борисов

Константин, а сама ошибка как звучит(поле с текстом ошибки из таблицы XErrors)?

Константин Егоров

Большое спасибо. все получилось. Рестартнул сервак и все норм встало

Денис Федоров

Есть вопрос по выводу сообщений об ошибке.

Добавили строку в таблицу XErrors, но при попытке вывести - идёт просто ошибка SQL, без вывода нашего сообщения. Или вывод идёт только Англоязычного сообщения.

Это на SQL 2005, на SQL 2008 R2 - всё хорошо.

Есть ли вариант какой-то работы функции XRaisError без использования дополнительной таблицы XErrors с текстом? Как еще можно вывести ошибку на экран пользователя.

 

Денис Федоров

Опа похоже ошибка то похожая же как и в прошлом сообщении... 

Рестартовать скул похоже... надо.

Денис Федоров

Перезагрузка не помогла.

Такое ощущение, что какая-то нестыковка сообщений с сообщениями в master.dbo.sysmessages.

Подскажите как всё же вывести нормальное сообщение об ошибке с текстом?

Сергей Камышев

Попробуйте вместо

exec XRaisError @ErrCode = 10000

вот так

RAISERROR ('Что то пошло не так, возможно задача не по ТМ', 16, 1)

подробнее можно глянуть в справку по функции raiserror по SQL Server
 

Денис Федоров

Да, я RAISERROR и использую в итоге для вывода.

В общем, если кому интересно, придумал такой вариант.

Создавать спец.группы людей, между которыми разрешены свободные маршруты.

Например Бухгалтерия_маршрут, Юристы_маршрут, Кассиры_маршрут

Теперь все кто включен в одну и ту же группу - могут друг другу писать, а если общей группы нет, то писать не могут. 
Одновременно человек может быть сразу в нескольких группах.

Для начальников создал группу StandardRouteAlwaysUsersGroup - они могут писать кому угодно.

В итоге получился навороченный триггер. Его конечно еще можно дорабатывать, например, не учитывается получатели "Наблюдатели", а так же если идёт отправка сразу нескольким сотрудникам у которых нет общей группы между собой (а есть только общие с отправителем) - то отправку тоже запрещает.

Пришлось правда еще использовать функцию преобразования текста, в таблицу и переводить ID пользователей к одному типу. Но в целом работает. 

 

Денис Федоров
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO

CREATE TRIGGER [dbo].[StandardRouteOnly] ON  [dbo].[SBTask] AFTER UPDATE
AS
DECLARE
	@Route VARCHAR(1), -- маршрут задчи (NULL - свободный)
	@State VARCHAR(1), --состояние задачи (W - "В работе")
	@Author INT, -- автор задачи (для получения пользователя)
	@UserID INT, -- ИД пользователя автора
	@UsersGroup NVARCHAR(255), -- группа пользователей, которым запрещено стартовать задачи по свободным маршрутам
	@UsersGroupOK NVARCHAR(255), -- группа пользователей, которым РАЗРЕШЕНО стартовать задачи по свободным маршрутам	
	@ParticipantsIDs  VARCHAR(255), -- тест со списком ID всех пользователей
	@UserID2 VARCHAR(255) -- ID инициатора

    
-- получить маршрут задачи
SET @Route = (SELECT StandardRoute FROM Inserted)
-- получить состояние задачи
SET @State = (SELECT State FROM Inserted)
-- получить автора задачи
SET @Author = (SELECT Author FROM Inserted)
-- получить ИД пользователя автора
SET @UserID = (SELECT MBUser.UserID
		FROM MBUser
			INNER JOIN MBAnalit ON MBuser.Userkod = MBAnalit.Dop
		WHERE Analit = @Author)
-- получить группу пользователей из констант StandardRouteOnlyUsersGroup и StandardRouteAlwaysUsersGroup
SET @UsersGroup = (SELECT VALUE FROM MBConst WHERE Const = 'StandardRouteOnlyUsersGroup')
SET @UsersGroupOK = (SELECT VALUE FROM MBConst WHERE Const = 'StandardRouteAlwaysUsersGroup')

BEGIN

set @UserID2 = CONVERT(VARCHAR(255), @Author)
SET @ParticipantsIDs = (SELECT ParticipantsIDs FROM Inserted) +','+ @UserID2
IF @Route IS NULL AND @State = 'W' -- идет попытка старта задачи по свободному маршруту
 BEGIN	
	IF (@UsersGroupOK NOT IN (SELECT GroupID FROM MBUserLinkFull WHERE UserID = @UserID)) -- пользователь не входит в группу StandardRouteAlwaysUsersGroup	
	
	-- ЗАКОММЕНТАРИТЬ ВТОРОЕ УСЛОВИЕ, ЧТОБЫ ВСЕ ПОЛЬЗОВАТЕЛИ КТОМЕ ТЕХ КТО В StandardRouteAlwaysUsersGroup не могли отправлять задачи по Свободному Маршруту
	and (@UsersGroup IN (SELECT GroupID FROM MBUserLinkFull WHERE UserID = @UserID))	

	BEGIN	
	-- проверяем наличие у всех получателей и инициатора, специалиных групп - их количество должно быть равно количеству пользователей
	if (
	SELECT MBUserLinkFull.GroupID
	FROM MBUserLinkFull, MBUser
	WHERE MBUserLinkFull.UserID in 
	(SELECT UserID FROM MBUser INNER JOIN MBAnalit ON MBuser.Userkod = MBAnalit.Dop WHERE Analit in (select * from Split(@ParticipantsIDs, ','))) 
	and MBUser.UserID=MBUserLinkFull.GroupID
	and MBUserLinkFull.ParentGroupID=MBUserLinkFull.GroupID --проверка идёт только по верхней группе пользователя, то есть все вышестоящие группы не включатся
	and MBUser.UserName like '%_маршрут' --Это ключевое слово которое должно быть в названии слежебного маршрута
	GROUP BY MBUserLinkFull.GroupID,MBUser.UserName 
	HAVING COUNT(MBUser.UserName) = (select COUNT(item) from Split(@ParticipantsIDs, ',')) -- Количество одинаковых спец.групп у всех пользователей дожно равнятся количеству пользователей, то есть у каждого должна быть такая группа
	) is null
		BEGIN    -- откатить действие и выдать сообщение об ошибке 			
			ROLLBACK TRANSACTION			
			EXEC XRaisError @ErrCode = 10002
			RETURN
		END
	END
 END
END

 

Денис Федоров

Понимаю, что основной запрос получился несколько запутанным для понимания... Возможно, зная структуру точнее можно его упростить... Но если просить у специалистов реализацию этой функции, то стоимость вызывает желание сделать всё самому...

Денис Федоров

Еще доработочка: 

Проверку производить через "if not exists (", а не через "if () is null"

И дописать DISTINCT в строку: 

HAVING COUNT(MBUser.UserName) = (select COUNT(DISTINCT item) from Split(@ParticipantsIDs, ','))
===========
В общем дорабатывать есть куда, думаю после тестирования вылезет еще что-нибудь.

Анатолий Придыбайло

лучше оформите в виде отдельной статьи! думаю многие пропустят ваш полезный триггер в этих комментариях

Денис Федоров

Те, кому интересно - найдут исходный вариант, и исходя из комментариев - додумают свой... как собственно я и сделал, просто кому-то поможет этот код, чтобы не придумывать алгоритм и реализацию.
К тому же, у меня не идеально законченный вариант.

Смысл его в том, что анализируются отправитель и получатели, и если они все имеют общую группу с определенным типом называния, то маршрут разрешается. Технические особенности же в том, чтобы выцепить IDшники участников, запросом получить необходимые типы групп и посмотреть есть ли группа, которая встречается столько же раз, сколько участников.

Максим Кошарный

Вариант типовыми средставми на скорую руку: Создаем закольцованный типовой маршрут, который раз в минуту или чаще, в зависимости от нагрузки на Workflow, находит все незавершенные задачи запущенные по свободному маршруту и принудительно завершает их, отправляя уведомление инициатору. Если необходимо оставить кому-то возможность отправлять по свободному маршруту - добавляем еще одно условие перед тем как завершить задачу.

Anton Kobzar

А можно ли это сделать не для группы пользователей, а для определенных типов карточек эл. документов?

Anton Kobzar

т.е. если есть во сложении задачи этот тип карточки то выдавать сообщение, что данные документы можно отправлять только по типовому маршруту.

Никита Захаров

А если в поле инициатор выбрать пользователя которому не запрещен старт по свободному маршруту?

Авторизуйтесь, чтобы написать комментарий