Рассылка напоминаний о незавершенных задачах

8 15

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

Для начала нам надо создать сценарий - назовите его как вам будет угодно, не суть важно это. Далее создаем вычисления:

Email = ""     //Объявляем переменные
Ссылки = ""    //Объявляем переменные
Users = CreateReference('ПОЛ') //Создаем объект типа справочник "Пользователи"
Users.Open()    //Открываем его
Users.First     //Ставим указатель на первый элемент
While (Not Users.EOF)    //Пока пользователи в списки есть, работаем:
  if Users.Состояние == "Действующая"    //Выбираем только действующих пользователей
    Perfomer = Users.Код    //Получаем Код пользователя
    Search = Searches.CreateNew(ckJob) //Начинаем поиск по критериям Исполнителя и статусу задания
    Criteria = Search.SearchCriteria
    CriterionPerformer = Criteria.Add('Performer')
    CriterionPerformer.Add(Perfomer)
    CriterionJobState = Criteria.Add('JobState')
    CriterionJobState.Add('В работе')
    SearchResult = Searches.Execute(Search)  //Получили результаты
    if SearchResult.count > 0    //Если результаты вообще есть, работаем:
      User = ServiceFactory.GetUserByCode(Perfomer)   //Получаем Пользователя
      Email = НайтиРеквизитПоФИО(User.FullName;"Email")  //Об этой функции мы поговорим отдельно!!!
      foreach ReqName in SearchResult  //Получаем ID заданий для генерации ссылок
        Ссылки = Ссылки & ГиперссылкаСоздать(ReqName.ID; "Задание") & CR
      endforeach
      Theme = Format("Для: " & User.FullName & "." & CR & " У вас есть невыполненные задания. Всего: %s."; SearchResult.count) //Генерируем тему
      Body= Format("Для: " & User.FullName & "." & CR & " У вас есть невыполненные задания. Всего: %s." & CR & "%s"; ArrayOf(SearchResult.count;Ссылки)) //Генерируем Тело письма
      //Предусматриваем вариант работы с заместителем -- начало
      Зам = CreateReference('ЗМЩ')
      Зам.open()
      Зам.first()
      if Зам.Locate('ISBReplaceableUser';Perfomer) 
        if Зам.requisites('ISBSubstituteType').value == "Полное"
          and Зам.requisites('ISBSubstituteStart').value <<>> ""
          and Зам.requisites('ISBSubstituteFinish').value <<>> ""
          and Today() >= Зам.requisites('ISBSubstituteStart').value  
          and Today() <= Зам.requisites('ISBSubstituteFinish').value 
          Заместитель = ServiceFactory.GetUserByCode(Зам.requisites('ISBSubstitutingUser').value)
          Email = НайтиРеквизитПоФИО(Заместитель.FullName;"Email") 
          Theme = Format("У сотрудника " & User.FullName & ", которого вы замещаете, есть невыполненные задания. Всего: %s."; SearchResult.count) //Генерируем тему
          Body= Format("У сотрудника " & User.FullName & ", которого вы замещаете, есть невыполненные задания. Всего: %s." & CR & "%s"; ArrayOf(SearchResult.count;Ссылки)) //Генерируем Тело письма
        endif
      endif
      Зам.close()
      //Предусматриваем вариант работы с заместителем -- конец 
      Shapka = "Добрый день!" & CR & CR   //Объявляем шапку
      Podwal = "Данное сообщение сформировано автоматически, просьба не отвечать на него."  //Объявляем Подвал
      if Assigned(Email)  // Если email не пустой - шлем письмо  
        ПЧТОтправитьПисьмо(Email;;;Theme; Shapka & Body & CR & Podwal;;;)
      endif 
    endif    
    SearchResult = NIL   //Убираемся
    CriterionPerformer = NIL  //Убираемся
    CriterionJobState = NIL   //Убираемся
    Criteria = NIL  //Убираемся
    Search = NIL    //Убираемся  
    Ссылки = ""     //Убираемся
  endif
  Users.Next() //Следующий пользователь
endwhile

Пример входящего сообщения:

Кстати пример удачный - пользователь не из самых оперативных.

По сути это все. Но перед завершением темы, раскрываем функцию:

Давно написал эту функцию, надоело постоянно искать все подряд и как попало. Можете написать свою или использовать несколько базовых функций. Мой код вот:

FIO = ФИО
Reference = References.РАБ.GetComponent
Podrazd = ''
AddWhere = Reference.AddWhere(Format("%0:s.%1:s = '%2:s'"; ArrayOf(Reference.TableName;
Reference.Requisites('Дополнение').FieldName; FIO)))
Reference.Open

if Вариант == "Подразделение"
  if Reference.RecordCount > 0
    Code = Reference.Requisites('Подразделение').AsString
    Podrazd = ReferenceRequisiteValue('ПОД';Code;'Наименование')
  endif
  Reference.Close
  Reference.DelWhere(AddWhere)
  Reference = nil
  Result = Podrazd
endif

if Вариант == "Пользователь"
  UserCod = ''
  if Reference.RecordCount > 0
    Code = Reference.Requisites('Пользователь').AsString
    UserCod = ReferenceRequisiteValue('ПОЛ';Code;'Код')
  endif
  Reference.Close
  Reference.DelWhere(AddWhere)
  Reference = nil
  Result = UserCod
endif

if Вариант == "Подразделение_GUID"
  PodrazdGUID = ''
  if Reference.RecordCount > 0
    Code = Reference.Requisites('Подразделение').AsString
    PodrazdGUID = ReferenceRequisiteValue('ПОД';Code;'GUID')
  endif
  Reference.Close
  Reference.DelWhere(AddWhere)
  Reference = nil
  Result = PodrazdGUID
endif

if Вариант == "Email"
  Email = ''
  if Reference.RecordCount > 0
    Email = Reference.Requisites('Email').AsString
  endif
  Reference.Close
  Reference.DelWhere(AddWhere)
  Reference = nil
  Result = Email
endif

По сути все - Сценарий работает неспеша, но если он включен всего 2 раза в день в 9 и в 15 - его "неторопливость" совершенно незаметна, а эффект от такой рассылки видно практически сразу - Задания начинают выполняться немного оперативнее.

--------------------------

Вне темы:

Текст вы можете изменить, причем это иногда даже бывает полезно. У меня в одном маршруте так же ведется отправка писем, чтобы оперативнее было выполнение задания, там текст немного иной:

Theme = 'Новая заявка: ' & Params.ValueByName("ТемаЗадачи").Value
MailText = 'Добрый день!' & CR & CR & 'Сожалею, Сэр, но похоже в СЭД Директум на Вас опять упала какая-то заявка:'& CR &
Theme & CR & CreateHyperlink(Object.ID;'Задание';'Ссылка на задание') & CR & 'Спасибо, Сэр. Удачного дня, Сэр.'
ПЧТОтправитьПисьмо('Адресат@Доменопочта.com';;;Theme;MailText;;;)

Как говорят ответственные сотрудники, такой текст им нравится куда больше, чем сухие факты, и они идут "на задание" с приподнятым настроением.

Всем удачного дня!

Сергей Шишканов

Может работать неверно

1. при несовпадении users.Fullname с РАБ.Дополнение

2. при наличии полных тезок

3. при наличии нескольких записей (и закрытых тоже) в справочнике работники для одного сотрудника, если, допустим, было увольнение и снова прием (адреса электропочты могут вполне различаться)

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

Сергей Шишканов: обновлено 06.03.2019 в 14:40
Тарас Асачёв

Сергей Шишканов, Так это уже вопрос необходимости и самодеятельности. Я не претендую на идеальный вариант - он работает при текущих условиях, плюс у нас синхронизация сотрудников проходит автоматическая и вариант Уволен/Принят как правило просто восстанавливает закрытую запись, а вот при смене фамилии - да, может возникнуть проблема, так как там могут возникнуть разночтения. Но это надо задаться целью - нет ничего невозможного. 

Алексей Семакин

1. Справочники ПОЛ и ЗМЩ — кэшируемые. Почему решили не использовать кэш?

2. Зачем всех пользователей загружаете с сервера, если закрытых не обрабатываете? С течением времени закрытых пользователей будет все больше, агент будет работать все дольше, работая при этом большей частью вхолостую.

3. Справочник ЗМЩ у вас тоже открывается без серверной фильтрации, хотя нужны в нем вполне конкретные записи. Хуже того, открытие всего ЗМЩ у вас происходит для каждого обрабатываемого пользователя...

Сколько у вас записей в справочнике пользователей? Сколько работает сценарий?

Тарас Асачёв

Алексей Семакин, есть над чем поработать)))

Евгений Куликов

Вместо не говорящих ссылок можно прописывать названия ссылок из наименования в DIR.

Сергей Меньших

У нас другая метода. На корп. сайте в личном кабинете есть опция "отправка уведомлений о заданиях", а на SQL сервере хранимочка, которая обрабатывает данный списочек и рассылает письма средствами SQL. Производительность высокая, нагрузка крайне низкая.

Сергей Меньших: обновлено 07.03.2019 в 12:15
Тарас Асачёв

Первая Замена "Долой вычисления в цикле":

 Users = CreateReference('ПОЛ')
 AddWhereUsers = Users.AddWhere(Format("%s.%s = 'Д'"; ArrayOf(Users.TableName; Users.Requisites('Состояние').SQLFieldName)))
 Users.Open()

Теперь условие "if Users.Состояние == "Действующая"" - ненужное!

Тарас Асачёв

С ссылками смог доработать только до этого варианта:

foreach ReqName in SearchResult
 Ссылки = Ссылки & ReqName.Name & " (" & ГиперссылкаСоздать(ReqName.ID; "Задание") & ") " & CR 
endforeach

 

Юлия Литвинюк

Для получения ссылки на объект можно использовать свойство Hyperlink

Тарас Асачёв

Юлия Литвинюк, В условиях настройки нашей политики безопасности, все сообщения ЭП по-умолчанию создаются обычным текстом с запретом на html. Так что я пробовал - не выходит. Но возможно другим этот метод подойдет. Спасибо!

Юлия Литвинюк

Не вижу связи между созданием гиперссылки и форматом текста эл. почты.

Свойство Hyperlink в отличие от функции CreateHyperlink() или ГиперссылкаСоздать() возвращает гиперссылку на объект в 3х форматах: текстовом, RTF и HTML. Т.е. 

Job.Hyperlink() = ГиперссылкаСоздать(ReqName.ID; "Задание")

 

Тарас Асачёв

Юлия Литвинюк, ну собственно вот в чем связь, если пойти вашим путем:

foreach ReqName in SearchResult
   Job = Jobs.GetObjectByID(ReqName.ID).Hyperlink(0) // а так же 1 и 2                
   Ссылки = Ссылки & ReqName.Name & " (" & Job & ") " & CR 
endforeach

0 - >> Доступ к серверу СКУД для просмотра видеонаблюдения КОНКОРД (http://directum/job.asp?sys=DIRECTUM&id=2572798)

1 - >> Доступ к серверу СКУД для просмотра видеонаблюдения КОНКОРД ({\field{\*\fldinst{HYPERLINK "http://directum/job.asp?sys=DIRECTUM&id=2572798"}}{\fldrslt{\cf1\ul>> \u1044?\u1086?\u1089?\u1090?\u1091?\u1087? \u1082? \u1089?\u1077?\u1088?\u1074?\u1077?\u1088?\u1091? \u1057?\u1050?\u1059?\u1044? \u1076?\u1083?\u1103? \u1087?\u1088?\u1086?\u1089?\u1084?\u1086?\u1090?\u1088?\u1072? \u1074?\u1080?\u1076?\u1077?\u1086?\u1085?\u1072?\u1073?\u1083?\u1102?\u1076?\u1077?\u1085?\u1080?\u1103? \u1050?\u1054?\u1053?\u1050?\u1054?\u1056?\u1044?}}})

2 - >> Доступ к серверу СКУД для просмотра видеонаблюдения КОНКОРД (http://directum/job.asp?sys=DIRECTUM&id=2572798">>> Доступ к серверу СКУД для просмотра видеонаблюдения КОНКОРД)

Чувствуется, что все должно быть иначе, но политика все же сильнее...

Тарас Асачев: обновлено 18.03.2019 в 13:26
Тарас Асачёв

Конструктивные правки по подсказке Алексея Семакина:

 Users = CreateReference('ПОЛ')
 Зам = CreateReference('ЗМЩ')   // Замещение вынесено за пределы цикла
 Зам.Open()
 AddWhereUsers = Users.AddWhere(Format("%s.%s = 'Д'"; ArrayOf(Users.TableName; Users.Requisites('Состояние').SQLFieldName)))
 AddWhereZam = Зам.AddWhere(Format("%s.%s = 'П' and %s.%s <> ''"; ArrayOf(Зам.TableName; Зам.Requisites('ISBSubstituteType').SQLFieldName;
 Зам.TableName; Зам.requisites('ISBSubstituteFinish').value))) // добавлены фильтры на Заместителя  
// далее все почти так же кроме входа в замещение:
if Зам.Locate('ISBReplaceableUser';Perfomer) 
                    if Today() >= Зам.requisites('ISBSubstituteStart').value  
                       and Today() <= Зам.requisites('ISBSubstituteFinish').value
// и далее как раньше

//Соответственно после цикла
 Зам.close()
 Зам.DelWhere(AddWhereZam)  

 

Алексей Семакин

Что стало с неторопливостью сценария после правок? Удалось ли сократить время его работы?

Тарас Асачёв

Боюсь давать сравнительную характеристику, но процентов 30% сбросилось точно. Хотя нас и до этого процесс не особо тревожил - 2 раза в сутки, это не тот случай когда время критично. 

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