Как бороться с xml в теле документа ЭДО

15 4

Почти все, кто имеет ЭДО Диадок в Директуме, наверное, сталкивались с тем, что иногда поступают  такие документы, в которых ответственный за контрагента ничего не может понять в открывшемся xml-тексте. Приходится обращаться к ответственному за кабинет оператора, чтобы попробовать извлечь pdf-представление. 
Данная статья помогает автоматизировать бОльшую часть процесса загрузки печатных представлений и ускорить документооборот. 

Как я уже упомянул, речь идет об операторе Контур. Механизм основан на использовании API Diadoc и соответствующей внешней библиотеке. Полагаю, аналогично можно все сделать и у других операторов, если они имеют API.

Итак начнем.

  1. Приобретаем и запрашиваем АПИ-ключ у вашего менеджера Диадока. Ключ должен быть в формате API-********-****-****-****-************.
  2. Перекрываем  BusinessUnitBox и добавляем свойство в перекрытие  и контрол на форму ApiKey. Это абонентские ящики ваших юридических лиц. В ApiKey после публикации записываем предоставленный апи-ключ. 
  3. Теперь вы должны определиться, как вы будете авторизоваться в Диадоке: по логину/паролю или по сертификату. Я для простоты покажу первый вариант. Следовательно, правильным будет сделать еще одно свойство и контрол в BusinessUnitBox для поля пароля, т.к. уже заведенный вами пароль в штатном базовом поле при установлении соединения ящика хранится в зашифрованном виде.
  4. Заводим константу:
    // URL веб-сервиса Диадок
    public const string DefaultApiUrl = "https://diadoc-api.kontur.ru";
  5. Создаем серверную функцию:
     
    /// <summary>
    /// Загружает из Диадока входящий документ в печатном представлении pdf и сохраняет его в PublicBody документа
    /// </summary>
    [Public, Remote]
    public bool DownloadDocDiadocAsPdf(Sungero.Docflow.IOfficialDocument document)
    {
        // Активен ли НОР
        var nor = document.BusinessUnit;
        if (! nor.Status.Equals(Sungero.Company.BusinessUnit.Status.Active))
            return false;
    
        // Находим соотвтетсвующий активный абонентский ящик Директума
        var box = OurSolution.OurOrg.BusinessUnitBoxes.GetAll(r => r.Status.Equals(Sungero.ExchangeCore.BusinessUnitBox.Status.Active)&& r.BusinessUnit.Equals(nor)).FirstOrDefault();    
    	if (box == null)
    	    return false;
    
        // Идентификатор клиента, он же апи-ключ разработчика
        var apiKey = box.API;  
        if (apiKey == null)
    	    return false;
    
        // Определяем ссылку на нужный документ у оператора обмена
        var docInfo = Sungero.Exchange.PublicFunctions.ExchangeDocumentInfo.Remote.GetLastDocumentInfo(document);
    
        // Определяем расширение файла и обрабатываем желаемые 
        var version = document.LastVersion;
        string extension = document.LastVersion.AssociatedApplication.Extension;
        string[] listExt = {"_", "xml", "*"};
    
        if (docInfo == null 
    	  || string.IsNullOrEmpty(extension) 
    	  || ! listExt.Contains(extension))
    	    return false;
    
        try
        {
           var crypt = new WinApiCrypt();
           var diadocApi = new DiadocApi(apiKey, Constants.Module.DefaultApiUrl, crypt);
     
           // Запрашиваем сеансовый токен в Диадоке
           var authTokenByLogin = diadocApi.Authenticate(box.Login, Constants.Module.DefaultPassword);
    
           // Определяем ИД ящика Диадока для НОР
           var foundOrgs = diadocApi.GetOrganizationsByInnKpp(nor.TIN, nor.TRRC).Organizations;
           var foundOrg = foundOrgs.FirstOrDefault(r => ! r.IsRoaming);
           var boxId = foundOrg.Boxes.FirstOrDefault().BoxIdGuid;
    
           int sleep = 1;
           byte[] bytes = null;
    
           // В цикле ожидаем готовности запрошенного документа
           while (sleep > 0)
           {
              var result = diadocApi.GeneratePrintForm(authTokenByLogin, boxId, docInfo.ServiceMessageId, docInfo.ServiceDocumentId);
    
              sleep = result.RetryAfter;
              if (sleep >0)
                 System.Threading.Thread.Sleep(sleep);
    
              if (result.HasContent)
                 bytes = result.Content.Bytes;
            };            
    
            // Если надо, то сохраняем результат в файл
            System.IO.File.WriteAllBytes("C:\\temp\\DiadocFile_" + Calendar.Now.ToString("yyMMdd_HHmmss") + ".pdf", bytes);
    
            // Сохраняем результат в видимое представление документа
            using (var memoryStream = new System.IO.MemoryStream(bytes))
            {
               version.PublicBody.Write(memoryStream);
               version.AssociatedApplication = Sungero.Content.AssociatedApplications.GetByExtension("pdf");
               document.Save();
            }
    
            return true;
        }
        catch (Exception ex)
        {
           string msg = ex.Message;
        }
    
        return false;
    }
  6. Перекрываем ExchangeDocumentProcessingTask - это задача, создаваемая автоматически при поступлении документа, и которая отправляет документ на обработку ответственному.
  7. Создаете новую версию Схемы и вносите в нее блок сценария в самом начале:
  8. В блоке вставляем простой код:
    public virtual void Script4InnerAuditExecute()
    {
      var atts = _obj.Attachments;
    
      foreach (var att in atts)
        if (Sungero.Docflow.OfficialDocuments.Is(att))
        {           
            var document = Sungero.Docflow.OfficialDocuments.As(att);
    
            if (document.DocumentKind.Code == 'ВХЭДО')
                bool b = OurSolution.InnAudit.PublicFunctions.Module.Remote.DownloadDocDiadocAsPdf(document);          
        }
    }

     

Знаю, что многие запрашивали реализацию аналогичной фичи давно и в Клубе, и у техподдержки. Директум обещал, но пока не реализовал. Мы же ждать больше не можем, т.к. поток документов ЭДО удваивается каждый год.

Берите, пользуйтесь ! 
Также замечу, что это описание несет гораздо больше, чем просто про xml, т.к. оно может подтолкнуть вас в "ворота к безграничной власти" над ящиками оператора ЭДО. Это полное управление вами из DRX множеством операций и по своей логике - отправкой/приемом документов, считывание дополнительных значений, управление сертификатами, и т.д. и т.п.!

Исключение: Если xml приходит от контрагента, который не в Диадоке, а от другого оператора, то, скорее всего,  даже Диадок покажет его вам вот таким образом и тут уже ничто не поможет. Поможет только расширение федералами списка формализованных типов документов по ФЗ-63.

Антон Максунов

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

Иван Романов

Спасибо за статью. 

Очень жаль, что коннектор не реализует весь функционал, доступный в апи, иногда это очень требуется.

Сергей Королев

Антон, вот-вот .
1. А зачем, если есть более быстрый и легкий путь получения того же результата?
2. И еще, представьте, что у вас несколько операторов и у каждого надо запрашивать схемы всех типов доков(!) и под каждого опять программировать!.. Но у всех есть простая апи-функция визуального представления - это единственный оператор, которым воспользоваться проще для добавления и последующего текущего сопровождения.

Сергей Королев: обновлено 01.02.2024 в 09:22
Денис Зайцев

"Следовательно, правильным будет сделать еще одно свойство и контрол в BusinessUnitBox для поля пароля, т.к. уже заведенный вами пароль в штатном базовом поле при установлении соединения ящика хранится в зашифрованном виде."

Вот так можно сделать:

Sungero.Core.Encryption.Decrypt(box.Password)

 

Еще есть нюанс, если печатную форму создавать не во время получения документа из сервиса обмена, а позже, например старые документы, созданные до данной доработки. Теоретически НОР в документе может быть изменена, поэтому лучше её получить следующим образом:

var nor = docInfo.RootBox?.BusinessUnit;

Однако нужно быть аккуратным, в старых версиях был баг и RootBox был равен Box'у подразделения

Денис Зайцев: обновлено 14.02.2024 в 17:52

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