При внедрении DIRECTUM на предприятии и в ходе эксплуатации системы к ней регулярно возникают пожелания. В условиях ограниченных ресурсов – сроков, трудоемкости - далеко не все пожелания можно реализовать, однако, можно с помощью небольших «фишек» и волков накормить (пользователей), и овец сберечь (время).
В этой статье поделюсь маленькими «фишками» с последнего проекта, которые облегчили жизнь нашим пользователям. Сразу оговорюсь, что внедряли стандартную версию с минимальными доработками.
Компания заказчика относительно молодая и быстрорастущая. Регулярно случаются изменения в организационной структуре: появляются новые отделы, дробятся управления, подразделения переезжают из одного департамента в другой.
При этом согласование документов, в соответствии с регламентами, должно происходить со всеми вышестоящими руководителями по иерархии, т.е. исполнитель согласует сначала с непосредственным руководителем, далее с руководителем вышестоящего подразделения и т.д. до заместителя директора.
Если в типовом маршруте по согласованию оставить автоматическое вычисление лишь непосредственного руководителя, а указание остальных начальников оставить на совести исполнителя (например, в параметре дополнительные согласующие), тогда в условиях регулярно меняющейся орг. структуры неизбежен риск, что какой-нибудь нужный руководитель не получит на согласование документ.
Для решения этой задачи мы разработали функцию, которая по исполнителю, запускающему документ на согласование, вычисляет подразделение (стандартный справочник Подразделения) и определяет у него руководителя. Далее проверяем, есть ли у полученного подразделения головное, если есть - повторяем алгоритм, и так далее, пока не добираемся до самого верха иерархии. Если у подразделения нет руководителя (а такое у нас было), просто пропускаем его. На выходе имеем список пользователей – руководителей по иерархии.
В типовой маршрут по согласованию документа добавляем блок «Согласование с вышестоящими руководителями», в котором в качестве исполнителя выступают пользователи из полученного списка. Делаем согласование последовательным.
В итоге, начало всех маршрутов по согласованию у нас совпадало и имело вид:
Пример функции:
if not Assigned(UserID)
UserID = EDocuments.CurrentUser.ID
endif
ManagersList:IUserList = ServiceFactory.GetUserList
EmployeesList = GetEmployeesByUserID(UserID)
if EmployeesList.Count > 0
EmployeeCode = EmployeesList.Values(0)
DepCode = GetRequisiteValueAsString("РАБ"; EmployeeCode; "ПодразделениеОргСтрук")
if DepCode <<>> ""
ManagerCode = GetRequisiteValueAsString("ПОДОРГСТРУК"; DepCode; "Работник")
if ManagerCode <<>> ""
ManagerUserCode = GetRequisiteValueAsString("РАБ"; ManagerCode; "Пользователь")
if ManagerUserCode <<>> ""
ManagerUserInfo = References.ПОЛ.ObjectInfoByCode(ManagerUserCode)
ManagerUser = ServiceFactory.GetUserByID(ManagerUserInfo.ID)
if not ManagersList.Find(ManagerUser)
ManagersList.Add(ManagerUser)
endif
endif
endif
// Взять руководителя головного подразделения
HighDepCode = GetRequisiteValueAsString("ПОДОРГСТРУК"; DepCode; "ПодразделениеОргСтрук")
while HighDepCode <<>> ""
DepCode = HighDepCode
ManagerCode = GetRequisiteValueAsString("ПОДОРГСТРУК"; DepCode; "Работник")
if ManagerCode <<>> ""
ManagerUserCode = GetRequisiteValueAsString("РАБ"; ManagerCode; "Пользователь")
if ManagerUserCode <<>> ""
ManagerUserInfo = References.ПОЛ.ObjectInfoByCode(ManagerUserCode)
ManagerUser = ServiceFactory.GetUserByID(ManagerUserInfo.ID)
if not ManagersList.Find(ManagerUser)
ManagersList.Add(ManagerUser)
endif
endif
endif
HighDepCode = GetRequisiteValueAsString("ПОДОРГСТРУК"; DepCode; "ПодразделениеОргСтрук")
endwhile
endif
endif
Result = ManagersList
Каждое подразделение имеет свои потоки входящей и исходящей корреспонденции. Письма, служебные записки, распорядительные документы могут быть как инициированы в подразделении – исходящий поток, так и поступить в подразделение извне – входящий поток.
Для удобства хранения и поиска корреспонденции в общей папке проводника системы были созданы папки подразделений. В каждой из них сотрудники хранили свои документы - некий архив или реестр документов подразделения или всего управления. Если нет ограничения по доступу (не конфиденциальный документ), то логичным выглядит пожелание автоматически сохранять ссылки на документы в папки подразделения, чтобы каждый сотрудник мог найти входящий или исходящий документ - т.е. сделать автоматический архив.
Если с исходящей корреспонденцией все более-менее понятно – можно создать папки поиска по видам документов, ограничить по авторам и дате создания (например, текущим годом) - то с входящими документами немного сложнее.
Как правило, входящий документ поступает в подразделение в рамках исполнения поручений по документу, либо для ознакомления. При этом создавать эти документы могут любые сотрудники:
Конечно, можно было сделать папку поиска по заданиям в рамках типовых маршрутов (стандартные ТМ «Исполнение поручений», «Ознакомление документа») и ограничить исполнителями задания – сотрудниками текущего подразделения. Но у этого решения есть минусы:
Мы упростили задачу и взяли в работу только документы, поступающие в рамках исполнения поручений.
При этом, если для нескольких групп использовалась одна папка, то создавалось несколько записей, где ИД папки был одинаковый, а группа пользователей различалась. Да, конечно, вариант не из красивых. Можно было сделать множественный выбор групп, либо табличную часть, чтобы не плодить записи. Но я напомню, что по времени и трудоемкости мы были ограничены. Наша задача была сделать максимально просто и быстро.
3. Далее проверяем поручения (стандартный справочник Поручения): смотрим, есть ли по поручению основной документ, если есть, проверяем исполнителей. По каждому исполнителю определяем группу пользователей, в которую он входит. После этого проверяем, задано ли соответствие найденной группы и папки в нашем новом справочнике:
4. Для поиска папки мы изменили типовой маршрут «Исполнение поручений». После старта вставили блок с вычислением, где определяли папку (как описано в предыдущем пункте) и вставляли туда ссылку. При этом, если в папке уже имеется ссылка на данный документ, то вставка не осуществлялась.
Пример вычислений на блоке ТМ:
Params = Object.WorkflowParams
AssignmentInfo = Params.ValueByName('Поручение').Value
if not VarIsNull(AssignmentInfo)
//Найти основной документа по РКК
Doc = nil
DocID = GetRequisiteValueAsString("RRCAssignments"; AssignmentInfo.Code; "ISBIntNumber")
if Assigned(DocID)
Doc = EDocuments.GetObjectByID(DocID)
UserList = Params.ValueByName('ОтветственныеИсполнители').Value
//Вложить документ в нужную папку
foreach User in UserList
Query = "select s.IDSpr
from MBAnValR t
join MBAnalitSpr a
on t.Analit = a.Analit
join MBAnalit s
on t.Analit = s.ParentGroup
and s.Vid = " & ИДТипСпр("IncomingFoldersSettings") & "
and s.Sost = 'Д'
and s.XRecStat = '+'
where t.Vid = " & ИДТипСпр("ГПЛ") & "
and a.Sost = 'Д'
and a.XRecStat = '+'
and t.PolzovatelT = " & User.ID
foreach FolderID = CSQL(Query)
Folder = Folders.GetObjectByID(FolderID)
Folders.PasteToFolder(Folder.Info; Doc.Info)
endforeach
endforeach
endif
endif
Doc = nil
UserList = nil
Если компания использует штрихкоды для быстрой идентификации документов в системе, то как правило:
Наш заказчик использовал штрихкод также для подтверждения того, что исходящий или внутренний документ, поступивший на подпись, был создан в системе.
Т.е. при создании документа в системе исполнитель должен был вставить в него штрихкод.
Как мы знаем, документ можно создать из шаблона и из файла. В шаблоне сразу можно настроить вставку штрихкода в колонтитулы, в этом случае пользователю останется ввести только сам текст документа.
Но часто бывает, что пользователи создают документы из файла: на абсолютно все случаи шаблоны не создашь, да это и не нужно. А у пользователя, скажем, уже есть на рабочем месте заготовка письма или предыдущее письмо на ту же тему (тому же корреспонденту), в котором достаточно изменить пару предложений.
Импортируя документ из файла, для вставки штрихкода необходимо открыть документ и через меню интеграции вставить штрихкод. Это не всегда удобно, например, когда документ импортируется полностью готовым к согласованию или же в документе настроены разные колонтитулы для разных страниц (а штрихкод должен вставляться на все страницы). Т.е. пользователю приходится делать дополнительные действия перед отправкой готового к согласованию документа.
Для упрощения этой задачи мы сделали кнопку на карточке документа Вставить штрихкод, которая генерирует штрихкод и вставляет его в колонтитулы документа в word на каждую страницу. Действия пользователя следующие:
Пример вычислений на кнопке:
DocumentID = Object.ID
Versions = Object.Versions
VersionNumber = ""
VersionNote = ""
EDocExt = ""
foreach Version in Versions
VersLock = Version.GlobalLock
if VersLock.Locked
Exit("Текст документа на настоящий момент сохраняется и заблокирован, подождите некоторое время перед повторной попыткой вставки штрихкода")
endif
VCS = Version.CurrentState
if VCS = 2
VersionNumber = Version.Number
VersionNote = Version.Note
EDocExt = Version.Editor.Extension
exitfor
endif
endforeach
if Assigned(VersionNumber)
// Символ slash
SLASH_SYMBOL = "\"
// Символ подчёркивания
UNDERLINE_SYMBOL = "_"
// Массив запрещённых при создании папок символов
BANNED_FOR_FILE_NAME_SYMBOLS = ArrayOf("\"; "/"; ":"; "|"; "*"; "?"; '"'; "<"; ">"; "'"; Char(145); Char(146); Char(147);
Char(148); Char(9))
// Шаблон формирования имени временного файла
EXPORTED_TEMP_DOCUMENT_TEMPLATE = "%s\%s_%s.%s"
// Сгенерировать и сохранить картинку со штрихкодом
BARCODE_ORIENTATION_NUMBER = 3
BARCODE_STRING_INDEX = 0
EDocumentName = Object.Name
// Заменить все запрещённые символы в названии файла на знак подчёркивания
foreach BannedSymbol in CArrayElement(BANNED_FOR_FILE_NAME_SYMBOLS)
EDocumentName = Replace(EDocumentName; BannedSymbol; UNDERLINE_SYMBOL)
endforeach
// Получить путь к временной папке текущего пользователя
CurrentUserTempFolder = GetTempFolder()
// Взять последний символ пути к временной папке пользователя
LastTempFolderChar = Copy(CurrentUserTempFolder; Length(CurrentUserTempFolder); 1)
// Если последний символ - "\"
if LastTempFolderChar == SLASH_SYMBOL
CurrentUserTempFolder = Copy(CurrentUserTempFolder; 0; Length(CurrentUserTempFolder) - 1)
endif
// Сформировать путь к файлу, в который будет экспортирован документ
InputFilePath = Format(EXPORTED_TEMP_DOCUMENT_TEMPLATE; ArrayOf(CurrentUserTempFolder; DocumentID; EDocumentName; EDocExt))
// Экспортировать электронный документ без блокировки
Object.Export(VersionNumber; InputFilePath; False)
// Получить строку штрихкода
BarCodeStringAndType = GetDocumentBarcodeString(DocumentID)
BarCodeString = BarCodeStringAndType[BARCODE_STRING_INDEX]
// Определить путь для сохранения временного файла со штрихкодом
// Шаблон для формирования имени файла со штрихкодом
BARCODE_FILE_PATH_TEMPLATE = "%s\barcode_%s.wmf"
BarCodeDestinationPath = Format(BARCODE_FILE_PATH_TEMPLATE; ArrayOf(CurrentUserTempFolder; DocumentID))
// Создать файл штрихкода на диске во временной папке пользователя
GenerateBarCode(BarCodeString;; '';BarCodeDestinationPath)
// вставить штрихкод в колонтитул документа
// с помощью ОМ Word
wdSeekCurrentPageFooter = 10
wdAlignParagraphRight = 2
wdHeaderFooterFirstPage = 2
Word = CreateObject("Word.Application")
DocW = Word.Documents.Open(InputFilePath)
DocW.ActiveWindow.ActivePane.View.SeekView = wdSeekCurrentPageFooter
Insh = DocW.Sections(1).Footers(1).Range.InlineShapes.AddPicture(BarCodeDestinationPath;False;True)
Insh.Height = 32
Insh.Width = 107
DocW.Sections(1).Footers(1).Range.ParagraphFormat.Alignment = wdAlignParagraphRight
// Если установлен признак "Особый колонтитул для первой страницы",
// то вставить ШК туда отдельно
If DocW.PageSetup.DifferentFirstPageHeaderFooter
InSh1 = DocW.Sections(1).Footers(wdHeaderFooterFirstPage).Range.InlineShapes.AddPicture(BarCodeDestinationPath;False;True)
InSh1.Height = 32
InSh1.Width = 107
DocW.Sections(1).Footers(wdHeaderFooterFirstPage).Range.ParagraphFormat.Alignment = wdAlignParagraphRight
EndIf
DocW.SaveAs(InputFilePath) // куда сейвим
DocW.Close(false) // закрываем док
Word.Quit()
// импортировать документ уже со штрихкодом обратно в текущую версию
Object.ImportFromFile(VersionNumber; VersionNote; InputFilePath)
Object.Save
ShowMessage("Штрихкод вставлен в документ")
else
ShowMessage("У документа нет ни одной действующей версии.")
endif
На этом у меня все. Пишите в комментариях свои примеры маленьких, но полезных фишек.
Оч нравится решение касательно согласования с руководителями по иерархии. Минимум блоков.
Не бывает у них случаев, когда согласование должно идти по схеме Непосредственный руководитель - Доп. согласующие - Прочие руководители по иерархии?
Гульназ, спасибо!
У данного клиента таких случаев нет. Но можно для вашего примера добавить в схему блок согласования с непосредственным руководителем (например, взять из стандартного ТМ "Согласование официальных документов"), а функцию переделать, чтобы она сначала вычисляла подразделение работника, у этого подразделения искала головное и в головном вычисляла руководителя. Или же оставить как есть, но в ТМ добавить вычисление по удалению непосредственного руководителя из списка вышестоящих руководителей
Есть несколько вопросов по вычислению руководителей:
1. Вычисления происходят до генерального директора или генеральный -1 подразделение? Бывают случаи, что даже согласование с замом лишнее, т.к. он может подписывать документ.
2. Если мы еще раз входим в блок, например при доработке, снова вычисляем иерархию? Если за время доработки сменился руководитель ему придет задание или "старому" руководителю?
По штрихкодам интересное решение, но тоже пару вопросов)
1. Правильно я понимаю, что текст смещается после вставки колонтитула и его придется редактировать? А если в документе уже есть колонтитул? А если нажать дважды?)
2. Если несколько версий у документа, в какую вставится штрихкод?
Андрей:
1. Здесь гибкая настройка - если вам надо, чтобы генеральный или его зам участвовал в согласовании, тогда создавайте подразделение, например Администрация и делайте его головным для остальных подразделений. Если не надо - тогда не заполняйте подразделение у таких работников. Функция "останавливается" когда у подразделения нет головного. У этого клиента согласование по иерархии должно было доходить до уровня начальников управлений. Замы и сам директор - подписывающие документ, а значит отдельный блок в согласовании ТМ. Расскажу на примере, я сотрудник бюро комплектации, мое бюро входит в сметный отдел, а он в свою очередь в проектное управление. Выше него главный инженер и далее директор. У бюро комплектации я указываю головным подразделением сметный отдел, у сметного отдела - проектное управление. А у проектного управления поле головное подразделение оставляю пустым - тогда список вышестоящих руководителей будет состоять только из 3х человек.
2. Если функцию включить в вычисления на блоке, тогда да. А мы делали это на старте ТМ, поэтому в нашем случае ответ нет, список статичный
3. >> Правильно я понимаю, что текст смещается после вставки колонтитула и его придется редактировать? - нет, текст не смещается, потому что место под колонтитулы в word изначально "заложены" ниже них текст не уйдет. Даже если специально удалить колонтитулы, то удаляется содержание в колонтитуле (текст, нумерация, таблицы), а само место остается
>> А если в документе уже есть колонтитул? - вставит ШК на него. Здесь никак не обрабатывали, т.е. придется сдвигать ШК, чтобы видно было и текст в колонтитуле и ШК
>> А если нажать дважды?) - два раза вставит) 10 раз нажмете - 10 раз вставит)))
4. Здесь изначально кейс другой был: работник создает новый документ в системе из файла. а не импортирует в новую версию уже имеющегося документа. Если документ уже есть в системе, то новые его версии, как правило создаются на основе старых. Поэтому других вариантов даже не рассматривали. Но да, Ваш кейс тоже имеет место быть, поэтому проверила в тестовой базе - вставляет только в одну версию. В первую :)
А вообще, здорово, что столько вопросов!) Значит надо вам брать код и пробовать самостоятельно экспериментировать ;)
Автоматический архив для входящих документов.
В версии 5.6 появились управляемые папки документов, которые решают эту задачу: можно сделать папку поиска по заданиям в рамках типовых маршрутов (стандартные ТМ «Исполнение поручений», «Ознакомление документа») и ограничить исполнителями задания – сотрудниками текущего подразделения. При этом пользователи видят документы, а не задания
Верно! Для меня расширение функционала папок - это очень долгожданная фича. Новые колонки в папках - это же и сортировка и группировка и фильтрация по всем нужным полям. Наконец-то не надо изобретать отчеты или сценарии поиска, чтобы работать с документами также как и со справочниками.
Да и вообще версия 5.6 хороша - одно только автоматическое создание поручений из протокола чего стоит! :)
любопытно, что автоматическое создание поручений по протоколу совещаний уже было в 5.1, но несколько в ином виде: в модуле "совещания и заседания" на основе повестки формировался протокол в виде табличной части в карточке совещания, уточнялись пункты и исполнители, а дальше формировался уже документ в ворде и отправлялся на согласование, затем расходились поручения.
в 5.2 этот функционал упростили, табличной части с пунктами протокола не стало
Авторизуйтесь, чтобы написать комментарий