
Я собирался выступить на ежегодной конференции в ПАО «Гипротюменнефтегаз», однако из-за сильной текущей загрузки просто не успел подготовиться. Чем не оправдал ожиданий моих знакомых и коллег (ожидалась небольшая публичная дискуссия). Описываемое далее решение не содержит ничего революционного с организационной точки зрения, но, смею надеяться, некоторого внимания оно все же заслуживает.
Несколько начальных пунктов в статье посвящены «Зачем». Далее — «Каким образом» (VBScript,
1. РЕВИЗИИ. ЧТО ЭТО ТАКОЕ.
Ревизии – это версии документов. Само слово пришло из иностранных стандартов, где ГОСТы 21-й серии распространения не имеют. Там, «у них», на документах в основных надписях есть поле «Rev.», которое, собственно, и расшифровывается как «Revision».
С другой стороны, жизнь ведет нас к тому, что передача документов будет выполняться в электронном виде
Компании с иностранным участием начали активно внедрять ревизии примерно 3-4 года назад.
Т.е. ревизиям — быть. Больше того, один из наших Заказчиков после получения первых документов
В процессе разработки проектной документации проектировщик не просто чертит чертежи. Проектировщик очень активно общается с экспертами Заказчика
Правильный способ работы заключается в том, чтобы перед отправкой сдать документ

Т.е. внедрен учет ревизий или нет – обмен «без архива» идет в любом случае. Ставить номер изменения
Для того, чтобы путаницы не происходило – внедряется учет ревизий. На каждом переданном
2. КАК ОБЫЧНО РАБОТАЮТ С РЕВИЗИЯМИ.
Как ни печально, в большинстве организаций по сию пору обычной ситуацией является «ручная автоматизация».
Т.е. для того, чтобы передать документ Заказчику, инженер-проектировщик присваивает номер ревизии, распечатывает документ, идет к начальнику группы, подписывает, потом к главному специалисту,

Заказчики, нужно сказать, попадаются разные. Одним нужно непременно через почту. Даже если там пара гигабайт. Другим – непременно через ссылку на ресурс Проектировщика. Третьим нужно выложить все
3. А ЧТО НУЖНО ОТ РЕВИЗИЙ, КРОМЕ ИХ УЧЕТА?
После передачи документа начинается самое интересное. Документов много, поэтому чуть ли не самое первое,
- «В разработке» — еще не передавался Заказчику.
- «Передан» — нужен номер письма.
- «Доработка» — есть замечания.
- «Согласован» — документ согласован.
И еще нужно учитывать, что согласование – процесс многоэтапный, есть внутренняя экспертиза,
Такой реестр в «запущенном» случае составить реально, но уйдет недели две, и ошибки будут в 20% случаев.
Кроме того, есть вопрос контроля соответствия документации друг другу. В проектных документах часто упоминаются другие проектные документы. Составляется даже специальная «Ведомость ссылочных
4. КАК АВТОМАТИЗИРОВАЛИ УЧЕТ РЕВИЗИЙ МЫ.
При внедрении учета ревизий мы исходили из 2-х предпосылок. Во-первых, нужно сократить объем работы проектировщику (все эти распечатывания и сканирования). Во-вторых, нужно дать инструмент для отслеживания процесса согласования. Кому-то. Лучше всего – проектировщику.
Начнем с 1-го пункта. На момент внедрения учета ревизий у нас уже был налаженный процесс сдачи
Дальше сначала была реализована просто выгрузка файлов документов (исходные и PDF). А после этого была реализована функция, которая разрешила сразу же половину неразберихи в части отслеживания процессов –
И еще никто не тратит много времени на архивирование материалов и правильную их передачу Заказчику.

По пункту 2 была реализована следующая функция – в TDMS проектировщик выбирает ревизию документа, нажимает кнопку «Письмо согласования» и выбирает из перечня входящее письмо, в котором есть информация о согласовании документа. Также выбирается, согласован документ,
В таком виде реализован процесс согласования конструкторской документации. Там интерес есть,
Что имеем в результате? В результате проектировщик:
- получает способ хранения в системе всех вариантов, которые он разрабатывал для Заказчика
и для смежных отделов; - может указать прямо в системе, каким письмом какой вариант он отправил;
- занимается отправкой не день-два, как раньше, а только час (нужна проверка вышестоящими специалистами);
- сам видит, в каком состоянии находится его работа (документов много, всего не запомнишь).
- не отвлекается еженедельно по нескольку раз на подготовку отчетов по просьбе Заказчика,
ГИП сам подготавливает реестр за 5 минут! - и т.д.
Пример реестра:

Проектировщику необязательно формировать реестр – для него есть специальная выборка

5. ТЕХНИЧЕСКИЕ АСПЕКТЫ. ИНТЕГРАЦИЯ TDMS С EASLA.COM.
Интеграция реализована в 3-х точках:
- Функция «Добавить к письму»
- Функция «Письмо согласования»
- Функция «Подготовка реестра»
Особенность задачи заключается в том, что при удалении из письма в easla.com какого-нибудь приложения
В каждом файле приложения имеются поля для хранения информации. Более того, в каждом файле приложения easla.com позволяет хранить не просто поле описания длиной, положим, 256 байт

Мы, фактически, являлись заказчиками этой особенности easla.com, и она нам очень пригодилась.
5.1. ФУНКЦИЯ «ДОБАВИТЬ К ПИСЬМУ» И ФУНКЦИЯ «ПИСЬМО СОГЛАСОВАНИЯ»
Разработка бизнес-логики в TDMS производится на VBS. За что отдельное спасибо разработчикам TDMS.
Кроме того, в системе easla.com есть COM-интерфейс для работы с системой. Он встроен в агент,

Вверху – строка для фильтрации элементов, внизу – перечень объектов (в данном случае –
Далее несколько кусков кода, которые показывают, как реализована интеграция. Некоторые места кода вызывают удивление не очень профессиональным уровнем, можно и покомпактнее, и покрасивее написать,
Получение исходящего письма из easla.com
Function GetOutingLetter() Set GetOutingLetter = Nothing Dim ea On Error Resume Next Err = 0 Set ea = CreateObject("Easla.Agent") If Err <> 0 Then MsgBox "EaslaAgent не установлен. Обратитесь к администратору", VbOKOnly & VbCritical, "Ошибка" Set ea = Nothing Exit Function End If ‘получение определения процесса «управление перепиской» Set process = ea.getProcessCOM("crs_management") If process Is Nothing Then MsgBox "EaslaAgent не смог авторизоваться. Обратитесь к администратору", VbOKOnly & VbCritical, "Ошибка" Set ea = Nothing Exit Function End If ‘получение определения объекта «исходящее» Set object_def = ea.getObjectdefCOM(process, "crs_management_outgoing", 0) Dim arr1(2) arr1(0) = "crs_management_outgoing_regnum" arr1(1) = "crs_management_outgoing_document" arr1(2) = "crs_management_outgoing_attachments" ‘формирование условий поиска, в данном случае – по статусу Set d = ea.CreateKeyValuesPairSoapItemCOM("status", Array("crs_management_outgoing_created")) ‘получение результатов выборки Set result = ea.getObjectrefsCOM(object_def, arr1, Array(d)) ‘задание списка выводимых в диалоге параметров Set kvps1 = CreateObject("EaslaAgent.com.easla.KeyValuePairSoap") kvps1.key = "Идентификатор" kvps1.value = "id" Set kvps2 = CreateObject("EaslaAgent.com.easla.KeyValuePairSoap") kvps2.key = "Описание" kvps2.value = "description" Set kvps3 = CreateObject("EaslaAgent.com.easla.KeyValuePairSoap") kvps3.key = "Дата создания" kvps3.value = "createtime" Set kvps4 = CreateObject("EaslaAgent.com.easla.KeyValuePairSoap") kvps4.key = "Статус" kvps4.value = "status.name" ‘вывод диалога Err = 0 Set ooo = ea.ShowSelectObjectDialogCOM(result, Array(kvps1, kvps2, kvps3, kvps4)) If Err = 0 Then Set GetOutingLetter = ooo End If Set ea = Nothing End Function
Загрузка файла в easla.com и установка параметров
‘o – объект письма ‘commonname – название файла приложения ‘revision – идентификатор ревизии для основного описания ‘filename – название файла для основного описания ‘revdata – описание ревизии для основного описания ‘guids – список объектов в TDMS типа TDMSSheet для списка «Состав файла» Function AddFileToOutingLetter(o, commonname, revision, filename, guids, revdata) Set AddFileTotOutingLetter = Nothing Dim ea On Error Resume Next Err = 0 Set ea = CreateObject("Easla.Agent") If Err <> 0 Then MsgBox "EaslaAgent не установлен. Обратитесь к администратору", VbOKOnly & VbCritical, "Ошибка" Set ea = Nothing Exit Function End If Set process = ea.getProcessCOM("crs_management") If process Is Nothing Then MsgBox "EaslaAgent не смог авторизоваться. Обратитесь к администратору", VbOKOnly & VbCritical, "Ошибка" Set ea = Nothing Exit Function End If ‘сначала – проверка, есть ли такие файлы в письме (прикрепить-то easla.com позволяет, но нам этого не надо) ‘извлекаем атрибуты объекта, файлы приложений – это тоже атрибуты set attrs = ea.GetAttrsFromObjectCOM(o) colKeys = attrs.Keys For Each strKey In colKeys If attrs.Item(strKey).code = "crs_management_outgoing_attachments" Then For Each f In attrs.Item(strKey).values If f.nowname = commonname And f.isdeleted = "0" Then MsgBox "Ошибка прикрепления: удалите из письма и прикрепите единовременно все файлы этого комплекта " & commonname, VbOKOnly & VbCritical, "Ошибка" Exit Function End If Next End If Next ‘загрузка в easla.com Set orss = CreateObject("EaslaAgent.com.easla.ObjectrefSimpleSoap") orss.id = o.id Err = 0 ‘собственно загрузка set uploadfile = ea.uploadFileCOM(orss, code, filename) If Err <> 0 or uploadfile is Nothing Then MsgBox "Во время прикрипления документа к письму возникла ошибка. Обратитесь к администратору", VbOKOnly & VbCritical, "Ошибка" Set ea = Nothing Exit Function End If Set kvps1 = CreateObject("EaslaAgent.com.easla.KeyValuePairSoap") kvps1.key = "srcname" kvps1.value = commonname Set kvps2 = CreateObject("EaslaAgent.com.easla.KeyValuePairSoap") kvps2.key = "revcode" kvps2.value = revision Set kvps4 = CreateObject("EaslaAgent.com.easla.KeyValuePairSoap") kvps4.key = "revdata" kvps4.value = revdata ‘обновление основной информации о файле set ooo = ea.updateFileCOM(uploadfile, Array(kvps1, kvps2,kvps4)) If Err = 0 Then Dim arrFCSS() ReDim Preserve arrFCSS(0) ‘обновление списка «Состав файла» For Each ob in guids.Objects n = UBound(arrFCSS) ReDim Preserve arrFCSS(n + 1) arrFCSS(n) = ob.description ‘описание объекта n = UBound(arrFCSS) ReDim Preserve arrFCSS(n + 1) arrFCSS(n) = ob.guid ‘связь с объектом TDMS n = UBound(arrFCSS) ReDim Preserve arrFCSS(n + 1) arrFCSS(n) = ob.Attributes("REV_LETTER").Classifier.Code & ob.Attributes("REV_NUM") ‘ревизия n = UBound(arrFCSS) ReDim Preserve arrFCSS(n + 1) arrFCSS(n) = ob.Attributes("REV_DESCRIPTION") ‘описание ревизии Next ‘обновление описания приложения Set AddFileTotOutingLetter = ea.updateFileConsistCOM2(ooo, arrFCSS) End If Set ea = Nothing End Function
Функции «Добавить к письму» и «Письмо согласования» в части получения номеров писем похожи,
5.2. ФУНКЦИЯ «ПОДГОТОВКА РЕЕСТРА»
Данные в системах о связи писем и ревизий есть, теперь стоит озаботиться извлечением этих данных
Весь код запросов приводить не буду, остановлюсь на ключевых моментах.
Получение перечня писем по перечню идентификаторов документов
CREATE PROCEDURE [dbo].[PR_TDMSReport_GetDocList_SENT] ( @guid uniqueidentifier –элемент дерева, с которого начинать поиск документов ) AS BEGIN declare @id bigint select @id = f_objid from tngptdms.dbo.tobject (nolock) where f_guid = @guid; declare @temp_revs table ([guid_rev] uniqueidentifier --GUID ревизии ,descr varchar(max) --описание ревизии ,letter varchar(10) --1-е поле ид-ра ревизии ,id varchar(10) --2-е поле ид-ра ревизии ,t datetime --время фиксирования ревизии ,objid_rev bigint --системный ид-р ревизии ); --заполнение таблицы документами, находящимися ниже по дереву insert into @temp_revs EXEC [TNGP].[dbo].[PR_TDMSRevision_GetListByObject] @guid = @guid --формирование перечня GUID-ов для XML-запроса в ESLA declare @subquery varchar (MAX) set @subquery = cast ((select '{'+cast([guid_rev] as varchar(50))+'}' from @temp_revs for xml path('item'), elements, type) as varchar(MAX)) --здесь заполняется секция условий для XML-запроса declare @query XML set @query = '<conditions> <item> <key>crs_management_outgoing_attachments</key> <values>' + @subquery + '</values> </item> </conditions>' --временная таблица для хранения результатов, получаемых из easla.com declare @temquerytbl table ( regnum varchar(32), regdate varchar(32), revguids xml, [SentStatus] varchar(40) ) --параметры, необходимые для запроса в easla.com DECLARE @organization_id AS INT; DECLARE @process_id AS INT; DECLARE @objectdef_id AS INT; DECLARE @user_id AS INT; declare @ret int declare @logined int set @logined = 0 if(@subquery is not NULL) begin -- пользователь,пароль – можно завести пользователя с правами просмотра всей информации в БД ESLA, чтобы не указывать системного администратора -- EASLAlogin – функция из расширения для easla.com select @logined = [master].[dbo].[EASLAlogin](пользователь,пароль) IF (@logined = 1) BEGIN --выборка параметров для запроса в easla.com SELECT @organization_id = organization.id, @process_id = process.id, @objectdef_id = objectdef.id FROM [master].[dbo].[EASLAgetOrganization] ('TNGP') AS organization LEFT JOIN [master].[dbo].[EASLAgetAllProcesses] () AS process ON process.oid = organization.id LEFT JOIN [master].[dbo].[EASLAgetAllObjectdefs] () AS objectdef ON objectdef.pid = process.id WHERE process.code = 'crs_management' AND objectdef.code = 'crs_management_outgoing'; -- ид-р пользователя в easla.com SELECT @user_id = id FROM [master].[dbo].[EASLAgetOrganizationUser] (@organization_id, пользователь) --запрос в easla.com необходимой информации insert into @temquerytbl SELECT attributerefs.value('(ArrayOfAttributerefSimpleSoap[1]/AttributerefSimpleSoap[1]/values/item/node())[1]','varchar(30)') as letternum ,attributerefs.value('(ArrayOfAttributerefSimpleSoap[1]/AttributerefSimpleSoap[2]/values/item/node())[1]','varchar(30)') as regdate ,cast(attributerefs.query('(ArrayOfAttributerefSimpleSoap[1]/AttributerefSimpleSoap[3]/values/item/consist/item/revdata)')as varchar(max)) as revguid ,cast([status].query('(StatusSimpleSoap[1]/name[1]/node())')as varchar(max)) as sentstatus from master.[dbo].[EASLAgetobjectrefs]( @process_id, @objectdef_id --перечень запрашиваемых полей ,CAST(' <attributerefs> <item>crs_management_outgoing_regnum</item> <item>crs_management_outgoing_sentdate</item> <item>crs_management_outgoing_attachments</item> </attributerefs> ' AS XML) ,@query, @user_id ); end declare @temquerytbl1 table ( revguid varchar(50), regnum varchar(30), regdate varchar(30), sentstatus varchar(40) ) insert into @temquerytbl1 select cast(t2.loc.query('(./node())') as varchar(50)), regnum,replace(regdate,'/','.'),sentstatus from @temquerytbl as T CROSS APPLY revguids.nodes('/revdata') as T2(Loc) END IF (@logined = 1) BEGIN select @logined = 1 - [master].[dbo].[EASLAlogout]() end …
Также есть похожая процедура PR_TDMSReport_GetDocList_RECEIVED, в которой запрашивается информация по письмам согласования. Атрибуты, в которых указано письмо согласования,
Есть набор процедур, через операцию JOIN объединяющих все эти данные с данными из TDMS
ЗАКЛЮЧЕНИЕ
По моему опыту, внедрение очень небольшого количества видов учета проходит так же гладко, как внедрение учета ревизий. После внедрения архив стал действительно «живым», потому что проектировщики получили возможности, существенно сокращающие им время на, скажем так, не самые основные задачи.
Серьезную роль сыграло и наличие хорошего рабочего контакта с разработчиками easla.com.

