Как использовать функцию обработки входящей почты в SharePoint 2010 — пример из практики

от автора

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

Работа рутинная, человеческий фактор провоцирует ошибки. Как автоматизировать процесс и избавить оператора от необходимости ручной обработки писем и их вложений? Мы нашли простое решение с использованием MS SharePoint. По своему обыкновению, мы постарались по максимуму задействовать имеющийся функционал систем, существующую библиотеку и немного своей программистской магии:)

Настройка Exchange

Если вы прочитали документацию и все настроили правильно, то дальше для вас все делается в несколько нажатий, в настройках библиотеки документов:

На сервере с SharePoint настраивается почтовый сервер на прием писем. Входящие письма в виде eml-файлов появляются в файловой системе в папке inbox. SharePoint каждые две минуты проверяет папку inbox, и если там есть файлы, он их обрабатывает и удаляет.

Стоит заметить, что все подряд файлы (с неправильным адресом получателя, с неправильными дополнительными тегами либо созданные с помощью программы Outlook Express) обрабатываться не будут — файл должен быть создан именно сервером SMTP из состава IIS.

В результате, все письма, приходящие на этот адрес, попадают в библиотеку документов:

В каждом письме присутствует одно или несколько вложений в форматах jpg, png, tiff, pdf, в которых может быть один или несколько документов, отмеченных штрих-кодом:

Для распознавания штрих-кодов мы использовали open source библиотеку zxing, с помощью которой можно сканировать и обрабатывать различные типы штрих-кодов. Здесь нам пришлось немного доработать возможности библиотеки, т.к. у заказчика используется своя специфическая кодировка, которая в zxing не поддерживается (хоть и похожа на codabar).

Далее мы реализовали Event Handler, с помощью zxing каждые 15 минут определяющий документы во вложениях писем, которые автоматически попадают на портал через Exchange. Далее он забирает их через библиотеку, вытаскивает вложения, адрес отправителя и код отправителя (личный код агента компании).

Код

using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using ADODB; using CDO; using ETR.REBT.BarcodeReader; using Stream = System.IO.Stream;  namespace ETR.BusinessLogic.EBTBlanks {     internal class EBTMailParser     {         private static readonly string[] SupportedExtensions =         {             "application/pdf"             , "image/jpeg"             , "image/bmp"             , "image/png"             , "image/tiff"         };          private static readonly Regex AgencyCodePattern = new Regex(@"{\w*}");         public static EBTMailParseResult ParseEmail(Stream emlStream)         {             var result = new EBTMailParseResult             {                 Success = true             };             Message msg = new MessageClass();             ADODB.Stream stream = new StreamClass();             try             {                 CopyStream(emlStream, stream);                  msg.DataSource.OpenObject(stream, "_Stream");                   result.FromAddress = msg.From;                 result.SendDate = msg.SentOn;                 result.ReceivedDate = msg.ReceivedTime;                 string agentCode;                 if (TryParseAgencyCode(msg.Subject, out agentCode))                 {                     result.AgencyCode = agentCode;                 }                 else                 {                     result.Status = "Не удалось распознать код Агента. Код должен содержаться в теме письма, содержать только цифры и располагаться внутри фигурных скобок. Например, {12345}";                     return result;                 }                  if (msg.Attachments.Count == 0)                 {                     result.Status = "В письме не обнаружено ни одного вложения";                     return result;                 }                  result.Attachments = ParseAttachments(msg.Attachments).ToList();                 return result;             }             catch (Exception ex)             {                 //If we get unknown error - we don't mark letter as parsed an try next time                 result.Success = false;                 result.Status = "Внутренняя ошибка.";                 result.ExceptionMessage = ex.Message;                 return result;             }             finally             {                 stream.Close();             }          }          private static bool TryParseAgencyCode(string subject, out string result)         {             var allMatchResults = AgencyCodePattern.Matches(subject);             if (allMatchResults.Count != 1 || false == allMatchResults[0].Success)             {                 result = null;                 return false;             }             result = allMatchResults[0].Value.Substring(1, allMatchResults[0].Value.Length - 2);             return true;         }          private static void CopyStream(Stream netStream, ADODB.Stream adoStream)         {             //adoStream.Open(Type.Missing, ADODB.ConnectModeEnum.adModeUnknown, ADODB.StreamOpenOptionsEnum.adOpenStreamUnspecified, String.Empty, String.Empty);             adoStream.Type = StreamTypeEnum.adTypeBinary;             adoStream.Open();              netStream.Position = 0;             var buffer = new byte[1024];             while (netStream.Read(buffer, 0, buffer.Length) != 0)             {                 adoStream.Write(buffer);             }             adoStream.Flush();         }           private static void CopyStream(ADODB.Stream adoStream, Stream netStream)         {             while (!adoStream.EOS)             {                 var bytes = (byte[])adoStream.Read(1024);                 netStream.Write(bytes, 0, bytes.Length);             }             netStream.Flush();         }           private static IEnumerable<EBTAttachmentParseResult> ParseAttachments(IBodyParts attachments)         {             var barcodeReader = new BarcodeReader(true);              for (var i = 1; i <= attachments.Count; i++)             {                 var attachment = attachments[i];                 var fileResult = new EBTAttachmentParseResult                 {                     FileName = attachment.FileName                 };                  if (false == SupportedExtensions.Any(ct => ct == attachment.ContentMediaType))                 {                     fileResult.Status = String.Format("Файлы {0} не поддерживаются", attachment.ContentMediaType);                     yield return fileResult;                 }                  var stream = attachment.GetDecodedContentStream();                 try                 {                     var memoryStream = new MemoryStream();                     CopyStream(stream, memoryStream);                     memoryStream.Position = 0;                     var parseResult = barcodeReader.Decode(memoryStream, attachment.FileName);                      fileResult.Status = parseResult.AllPages > parseResult.RecognizedPages                         ? String.Format("Распознано {0} из {1} страниц", parseResult.RecognizedPages,                             parseResult.AllPages)                         : fileResult.Status;                     fileResult.Stream = memoryStream;                      if (parseResult.ResultList != null && parseResult.ResultList.Count > 0)                     {                         fileResult.BarcodeNumbers = parseResult.ResultList.Select(b => ParseBarcode(b.Text)).ToList();                     }                 }                 catch (Exception ex)                 {                     fileResult.Status = "Внутренняя ошибка.";                     fileResult.ExceptionMessage = ex.Message;                 }                 finally                 {                     stream.Close();                 }                 yield return fileResult;             }         }          private static EBTBarcodeParseResult ParseBarcode(string barCode)         {             if (barCode.Length != 15)                 return new EBTBarcodeParseResult                 {                     BarcodeNumber = barCode,                     Status = "Не удалось распознать код документа"                 };               return new EBTBarcodeParseResult             {                 BarcodeNumber = barCode,                 CouponNumber = barCode.Substring(1, 13).Insert(3, " ")             };         }     } } 

Если хэндлер находит какую-то ошибку (неправильное расширение файлов, не распознается код агента и т.д.), в EventBus регистрируется событие с ее описанием.

Если ошибок нет, вложения разбираются через библиотеку, из них «вытаскиваются» штрих-коды, с которых считывается информация. На каждый определенный штрих-код создается бланк в SharePoint, который уже и обрабатывают операционисты, когда получают оригиналы документов.

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

Данный бизнес-процесс — маленькая часть большой работы офиса крупной транспортной компании. Также его легко можно транслировать и на другие сферы, где необходимо много и рутинно обрабатывать входящую документацию, и где используются SharePoint и Exchange.

Наш постулат в таких условиях — не переставать совершенствование существующей системы, автоматизировать все рутины, используя доступные средства, смекалку и свои навыки в программировании.

ссылка на оригинал статьи http://habrahabr.ru/company/eastbanctech/blog/211669/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *