Одной из малоосвещенных, на мой взгляд, областей разработки является автоформирование документов в DIRECTUM. Внесу свою лепту в исправление этой ситуации.
Итак, чем вообще является автоформирование документов и зачем оно может понадобиться. Под автоформированием я подразумеваю процесс, когда пользователь заполняет лишь карточку документа и не участвует в процессе формирования тела документа в DIRECTUM. Система сама, на основе некоторых правил, формирует документ.
Для чего это может быть нужно:
Почему не реализовать карточку справочника, привязав к ней формирующийся документ? Можно систему прав тоже привязать к документу и использовать ее при открытии справочника (т.е. сделать фильтр в событии "Набор данных" - "Открытие": если имеешь права на привязанный документ, то видишь запись справочника). Все дело опять в юзер экспириенсе. Ввел данные на карточке документа, сохранил и получил тело этого же самого документа - это для пользователя естественно, все данные между собой связаны визуально. А ввод данных в справочнике и формирование документа где-то в другом месте - это уже явно выглядит хуже.
Недостатки технологии:
Общими особенностями для всего процесса автоформирования документов будут:
1) При первом сохранении документа всегда в тело документа записывается выбранный шаблон. При этом запись происходит после отработки всех событий сохранения, поэтому повлиять на сохраняемый шаблон никак нельзя. Обращение к разработчикам от меня зарегистрировано, но в самой свежей на данный момент версии это поведение сохранилось, поэтому будем работать с тем, что есть. Это порождает нам некоторые сложности. Например, раз мы не можем использовать событие сохранения, то код формирования тела документа можно поместить в событие "Запись" - "Открытие". Тогда, если пользователь внес данные на карточку документа, сохранил его и тут же открыл, он увидит нормальную отчетную форму. Но если произошло подписание документа электронной подписью без срабатывания события открытия (у нас такое бывало), то тело документа у нас так и останется в пустом состоянии шаблона.
Это уже не говоря о том, что само по себе размещение кода, изменяющего документ, в событии открытия, а не сохранения, противоречит логике и может ухудшить поддержку разработки тем, кто будет разбираться во всем этом после вас.
2) Автоформирование документа подразумевает, что пользователь во многих случаях не должен управлять созданием версий, за него это должна делать система. На практике выходит так, что помешать цивилизованными средствами пользователю создать новую версию документа мы можем, но это не очень стабильный механизм. То работает, то начинают вылазить некрасивые ошибки, типа Access Violation. На форуме есть небольшое обсуждение. Поэтому в этом вопросе полагаемся в том числе на сознательность пользователей. Многолетняя практика показала, что обычно этого хватает, и очень-очень редко кто-то по ошибке пытается создать версию там, где это не нужно.
3) В реквизит ISBVersionData второй таблицы списка реквизитов документа (это где хранится тело документа) данные можно записывать напрямую. Теоретически это может быть запрещено в будущих версиях и приведет к несовместимости, но на данный момент этим очень удобно пользоваться, если в версию нужно сохранить текстовые данные. Это наглядно в коде и быстро выполняется у пользователя, поэтому кое-где я такой способ применяю. Как говорится, трюк исполняется профессионалами в специально подготовленных условиях, не пытайтесь повторить это дома, лучше через IEDocument.Versions, тем более, что такой пример тоже будет.
4) Нужно некоторым образом определять, когда нужно формировать тело документа, а когда нет. Самый очевидный вариант: при каждом изменении любого реквизита устанавливать некий флаг и потом его анализировать. Недостаток этого метода заключается в том, что при добавлении нового реквизита в документ нужно не забыть добавить установку флага и на изменение этого нового реквизита. Да и вообще, множество однотипного кода заставляет задуматься о том, как бы фиксировать изменения скопом (особенно, если реквизитов штук сорок, включая табличные). Второй простой вариант - это установка флага в событии "Форма" - "Показ" (флаг можно устанавливать в IObject.Params) и последующий анализ флага в событии сохранения. Т.е. если была открыта карточка и после этого было сохранение, то предполагаем, что были изменены какие-то данные на карточке. Помимо красивого минимализма у этого метода есть глобальный недостаток - будут пропущены мимо все программные изменения объекта, например, из-под Workflow или в скрипте. Какой из методов применять и как их улучшить, решается в каждой конкретной ситуации.
Итак, рассмотрим два случая автоформирования документов:
1) Когда нужно подготовить файл для другой программы. Я приведу конкретный пример, который используется у нас. Нужно сформировать XML-файл, в который помещаются нужные данные. В данном случае при изменении данных на карточке документа пересоздавать тело документа не надо. Можно рассматривать тело документа, как табличную часть, хранящуюся вне DIRECTUM и обладающую версионностью.
Добавляем в список реквизитов новый строковый реквизит avkFlag. В событии "Запись" - "Открытие" размещаем код:
if not Object.Inserted if not Assigned(Object.avkFlag) //если документ только что создан sysCode = Application.Connection.SystemInfo.Code docID = Object.ID str = '' & CR & '' & CR str = str & ' " Object.DetailDataset(2).ISBVersionData = str Object.avkFlag = "1" Object.Save else if Object.avkFlag == "2" //если создана версия документа dds = Object.DetailDataset(2) dds.Last str = dds.ISBVersionData path = IncludeTrailingPathDelimiter(GetTempFolder()) & "temp.xml" WriteFile(path;;str) XMLDoc = CreateObject("MSXml.DomDocument") XMLDoc.Load(path) RootElem = XMLDoc.DocumentElement //Version Node node = RootElem.selectSingleNode("//DocumentVersion") baseVers = node.text node.text = dds.НомСтр //Source Version Node node = RootElem.selectSingleNode("//SourceVersion") node.text = baseVers XMLDoc.Save(path) str = ReadFile(path) dds.ISBVersionData = str Object.avkFlag = "1" Object.Save else if Object.avkFlag == "3" //если создана копия документа docID = Object.ID dds = Object.DetailDataset(2) str = dds.ISBVersionData path = IncludeTrailingPathDelimiter(GetTempFolder()) & "temp.xml" WriteFile(path;;str) XMLDoc = CreateObject("MSXml.DomDocument") XMLDoc.Load(path) RootElem = XMLDoc.DocumentElement //Version Node node = RootElem.selectSingleNode("//DocumentVersion") baseVers = node.text node.text = dds.НомСтр //Source Version Node node = RootElem.selectSingleNode("//SourceVersion") node.text = '' //DocumentId Node node = RootElem.selectSingleNode("//DocumentId/id") sourceDocID = node.text node.text = Object.ID //Source document id node = RootElem.selectSingleNode("//SourceDocumentID") node.text = sourceDocID //Source document version number node = RootElem.selectSingleNode("//SourceDocumentVersionNum") node.text = baseVers XMLDoc.Save(path) str = ReadFile(path) dds.ISBVersionData = str Object.avkFlag = "1" Object.Save endif endif endif endif' & CR & ' " & CR str = str & '' & docID & ' ' & CR & "1 ' & CR str = str & '' & CR str = str & ' ' & CR str = str & ' ' & CR str = str & ' '&sysCode&' ' & CR & "
Формат XML-файла был предоставлен нашим разработчиком, я оставил только наиболее интересные моменты. В файл записывается ИД документа, номер версии, ИД и номер версии скопированного документа (если есть), код текущей системы (чтобы различать документы тестовой и рабочей баз). Получать данные об исходном документе можно из EDocumentVersionSource, но вышеприведенный код был написан еще до появления этой предопределенной переменной. Оставил здесь для примера работы с MSXml.DomDocument.
Подобным образом можно формировать HTML-файл с редиректом на какой-нибудь отчет на MS Reporting Services.
В событие "Запись" - "Добавление после" устанавливаем флаг, если документ был скопирован:
if Object.Copied Object.avkFlag = "3" endif
А в событии "Таблица 2" - "Добавление после" ставим признак добавления версии:
if not Object.Inserted Object.avkFlag = "2" endif
Для наведения лоска запретим создавать документ, кроме как путем копирования и создания из шаблона. В событие "Запись" - "Добавление до" пишем код:
if EDocumentVersionSource.SourceType <> edvstTemplate AND EDocumentVersionSource.SourceType <> edvstEDocumentVersionCopy Raise(CreateException('';'Документ может быть только создан из шаблона или скопирован из другого документа этого вида';ecInformation)) endif
Тут бы еще надо проверять вид исходного документа, но это уже частности.
2) Второй вид автоформируемых документов - это отчетные формы для данных, представленных на карточке документа. Такие формы удобно формировать в Word или Excel из заранее заготовленных шаблонов. После заполнения документы конвертируются в pdf и импортируются в DIRECTUM в документ.
Вариантов программного формирования документов много, есть обсуждения на форуме (например, здесь), приведу в пример только один, с использованием закладок в Word. В шаблоне документа расставляем закладки (вкладка "Вставка" на риббон-панели, там кнопка "Закладка"), а при формировании документа вставляем текст в зависимости от имени закладки. В событие "Запись" - "Открытие" добавляем текст:
if not Object.Inserted if not Assigned(Object.avkFlag) tempFileName = IncludeTrailingPathDelimiter(GetTempFolder()) & 'directum tempfile.doc' pdfFileName = IncludeTrailingPathDelimiter(GetTempFolder()) & 'directum tempfile.pdf' versionNum = Object.Versions.Count Version = Object.Versions.Values(versionNum - 1) templateID = 243398 template = EDocuments.GetObjectByID(templateID) versionsTemplate = template.Versions versTemplate = versionsTemplate.Values(0) versTemplate.Export(tempFileName) versTemplate = null versionsTemplate = null template = null Word = CreateObject("Word.Application") Word.DisplayAlerts = FALSE doc = Word.Documents.Open(tempFileName) count = doc.Bookmarks.Count i = 1 while i <= count if doc.Bookmarks.Item(i).Name == "t1" doc.Bookmarks.Item(i).Range.Text = Object.ISBEDocName else if doc.Bookmarks.Item(i).Name == "t2" doc.Bookmarks.Item(i).Range.Text = Object.Requisites("SIN_Rab").DisplayText endif endif i = i + 1 endwhile doc.Save() doc.ExportAsFixedFormat(pdfFileName;17) doc.Close() Word.Quit() if version.Signed version.ChangeState(vsObsolete) Object.ImportFromFile(-1;'Версия ' & (versionNum + 1);pdfFileName) else Version.ImportFromFile(pdfFileName) endif Object.avkFlag = "1" Object.Save endif endif
С форматированием шаблона помогут отличные статьи Елизаветы Великановой: Word и Excel
Еще раз напомню, что скидывать значение avkFlag нужно при изменении значений реквизитов, в соответствии с тем способом, который был выбран. Например, в событие "Форма" - "Показ" пишем код:
Object.Params.Add("CardShowed";'1')
А в событии "Запись" - "Сохранение до" размещаем следующий код:
flagShowedCard = Object.Params.FindItem("CardShowed") if Assigned(flagShowedCard) Object.avkFlag = '' endif
Приведенный код работал в версии 4.8, точно работает (давно) в версии 4.9.1, проверено в версии 5.0.
Тестовая разработка приложена. В КД SIN_Test2 формируется XML-файл, в КД SIN_Test3 формируется pdf.
Для тестирования создаем вид документа Test2, снимаем галочку "Доступны все шаблоны", указываем шаблон "Общий шаблон документов формата TXT" (так сразу будет открываться в Блокноте, чтобы удобнее смотреть было), указываем карточку Test 2, указываем вид жизненного цикла, состояние версии по умолчанию и право создания.
Для второй карточки создаем или берем существующий документ Word (должен быть установлен Office 2010), создаем в этом документе две закладки t1 и t2 (место можно выбрать по желанию), помещаем его в DIRECTUM (вид не важен, можно указать "Шаблон электронного документа"), ИД нового документа указываем в событии "Запись" - "Открытие" в переменной templateID, создаем вид документа Test3, указываем тип карточки Test 3 и заполняем все остальные обязательные параметры.
Очень интересно, только вот хочется увидеть результат, пощупать или что-то еще.
Опубликовал пакеты для версии 4.9.1 и для 5.0. В принципе, в эти карточки просто помещен код из статьи - его вполне достаточно для работоспособности.
Андрей, а нет желания несколько преобразовав содержимое статьи, поучаствовать с ней в Awards?
Поддержку Руслана, и со своей стороны также рекомендую вам оформить данный материал, как заявку на DIRECTUM Awards 2014
Пока нет времени переделывать статью под Awards.
Ну ничего, дело не горящее. Так что ждемс.
Авторизуйтесь, чтобы написать комментарий