Полезные мелочи при работе с карточками ч.6 (Поиск, прогресс и как делать не надо)

10 9

1. Поиск документов и работа с ним на небольшом примере.

Задачи бывают разными, но работа с документами - едва ли не в ТОП-3 всех задач в СЭД. Поэтому ничего удивительного в том, что иногда нам приходится с ними сталкиваться. Ну так вот: Ко мне поступила задача - собрать документы для дальнейшего формирования отчета или массового редактирования, для чего надо было сначала все найти. И вот тут как раз и понадобился такой инструмент, как "Поиск" ("Search"). 

Для первичного выбора, я воспользовался системной функцией "InputDialog" (Хотя вариантов для этого тоже может быть масса):

InputData = InputDialog('Автор|Подразделение|Статус ВНД|С|По';'||||';
'Аналитика:ПОЛ|Аналитика:ПОД|Признак:Действующий,Заменен,Отменен|Дата|Дата';
'Фильтр по Внутренним нормативным документам')

Для начала вполне сносно и позволяет пользователям неплохо прикладывать фильтр к перечню документов. По результатам работы InputDialog, мы получаем строку InputData, состоящую из строк разделенных символом "|", а значит, нам надо их разделить:

AuthorCode = SubString(InputData; '|'; 1)
PodrCode = SubString(InputData; '|'; 2)
Status = SubString(InputData; '|'; 3)
DataS = SubString(InputData; '|'; 4)
DataPo = SubString(InputData; '|'; 5)

Вот. Теперь у нас есть 5 реквизитов в виде отдельных строчек, которые нам будут полезны в нашем поиске, а точнее "фильтрации", и вот с ними можно уже работать. Для упрощения конструкции поиска, я собрал небольшую функцию "ФильтрПоискаПоЗначению":

SearchCriteria = Search.SearchCriteria
if Znach <<>> ''
  Criterion = Search.SearchCriteria.Add(Recv)
  if Data <<>> ''
     Criterion.AddRange(Znach; Data)
  else
      Criterion.Add(Znach)
  endif
endif

Не ахти что, да и не предусмотрен ввод строки с участием условий, но на текущий момент вполне достаточно. 

Если же функцию не использовать, конструкция будет выглядеть так:

Search = Searches.CreateNew(ckEdocument)
Criterion = Search.SearchCriteria.Add('ISBEDocKind')
Criterion.Add(VEDCode)
Criterion = Search.SearchCriteria.Add('ISBEDocAuthor')
Criterion.Add(AuthorCode)
Criterion = Search.SearchCriteria.Add('Подразделение')
Criterion.Add(PodrCode)
Criterion = Search.SearchCriteria.Add('СтатусКПО')
Criterion.Add(Status)
Criterion = Search.SearchCriteria.Add('ISBEDocCreateDate')
Criterion.AddRange(DataS; DataPO))
Search.Show(ssmBrowse; FALSE)

И так, у нас есть строки для фильтрации и функция для работы. А значит, можно уже начинать:

Search = Searches.CreateNew(ckEdocument)       
ФильтрПоискаПоЗначению(Search;'ISBEDocKind';VEDCode) // Здесь указывается Вид документа
ФильтрПоискаПоЗначению(Search;'ISBEDocAuthor';AuthorCode)
ФильтрПоискаПоЗначению(Search;'Подразделение';PodrCode)
ФильтрПоискаПоЗначению(Search;'СтатусКПО';Status)
ФильтрПоискаПоЗначению(Search;'ISBEDocCreateDate';DataS; DataPO)
Search.Show(ssmBrowse; FALSE)

Альтернативный способ описания поиска, можно описать и иначе (Подробности):

Search = Searches.CreateNew(ckEdocument)
  SearchCriteria = Search.SearchCriteria
  SearchCriteria.AddWhere = Format('(%0:s.ISBEDocKind = %1:s and
%0:s.ISBEDocAuthor = %2:s and
%0:s.Podrazdelenie = %3:s and
%0:s.StatusKPO = %4:s and
%0:s.ISBEDocCreateDate >= %5:s and %0:s.ISBEDocCreateDate <= %6:s)';
 ArrayOf(DOCUMENTS_ALIAS;VEDCode;AuthorCode;PodrCode;Status;FormatSQLDate(DataS);FormatSQLDate(DataPO)))
  Search.Show(ssmBrowse; FALSE)

Хотя если уж честно, работать они будут фактически одинаково.

А вот если вы решите перебирать все документы и отсеивать их по приницпу "сито":

Search = Searches.CreateNew(ckEdocument)       
SearchJob = Search.Execute
foreach Str in SearchJob
 if Str.ISBEDocKind == VEDCode and
   Str.ISBEDocAuthor == AuthorCode and
   Str.Подразделение == PodrCode and
   Str.СтатусКПО == Status and
   DateDiff("D"; Str.ISBEDocCreateDate; DataS) <= 0 and
   DateDiff("D"; Str.ISBEDocCreateDate; DataPO) >= 0
    DocId = AddSubString(Str.ID;DocId;';')
 endif
endforeach

То тут диагноз один: "Месье знает толк в извращениях!"

Поиск и выборку документов можно делать различными способами и тут решение за вами, что вам удобнее в вашем случае и как много условий вам надо соблюсти. В справке Директума есть все необходимые указатели и методы реализации ваших затей, так что если все сделать верно, то поиск будет не только точным, но и быстрым. И если все сделаем верно, в результате, мы увидим весь отфильтрованный список документов соответствующих условиям и соберем его в переменную "SearchJob", с которой можно уже начинать работать далее. 

SearchJob = Search.Execute // Поиск.Выполнить

Если же вы хотите не только отфильтровать но и посмотреть на результат вашего выбора, тогда добавляем Метод "Show":

Search.Show(ssmBrowse; FALSE) // смотрим и восхищяемся (процесс при этом не тормозит!)
//ИЛИ
Search.Show(ssmSelect; TRUE) // Открываем список и выбираем нужный нам документ! 
SearchJob = Search.SelectedContents // Получаем его в переменную "SearchJob" 
//ИЛИ
Search.Show(ssmMultiSelect; TRUE) // Выбираем МНОГО!!! документов!
SearchJob = Search.SelectedContents // Получаем их в переменную "SearchJob" 

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

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

2. Прогресс (CreateProgress)

При работе с циклами, а тем более с большими массивами данных, иногда хочется понимать, как же долго нам еще ждать и стоит ли вообще это делать. Для этого в СЭД "Директум", существует шикарная системная функция "CreateProgress" и все ей сопутствующее. Как же это работает?

Для начала, нами объявляется некий цикл, который состоит из некоего числа витков. Исходя из примера выше, я могу указать количество витков, как "SearchJob.Count". А значит мы уже можем начать и описать весь процесс, да так, что следить за ним будет не скучно:

Progress = CreateProgress("Минутку, пожалуйста..."; SearchJob.Count)   //Создаем прогресс-бар
Progress.Show   //Выводим его на свет Божий
//Далее, внутри цикла вносим значение текста, например наименование документа
Progress.Text = Document.Requisites('Наименование').DisplayText
//В конце цикла ставим указатель на новый элемент
Progress.Next
//После цикла указываем на конец
Progress.Hide

Скажем больше: это дело можно еще и останавливать, и даже притормаживать. Всего-то и надо, что добавить параметры в функцию:

Progress = CreateProgress("Минутку, пожалуйста..."; SearchJob.Count; TRUE;'Отменяем?')
/* Параметры:
•	Caption – заголовок окна индикатора;
•	Max – максимальная позиция полосы прогресса, расположенной в окне индикатора. Если значение не задано, то форма индикатора не будет отображать полосу прогресса;
•	IsBreakable – возможность прерывания процесса, ход выполнения которого отображает индикатор: True, если процесс можно будет прервать, нажав на кнопку Прервать, False, если процесс невозможно прервать. Если процесс невозможно прервать, то на форме индикатора не будет кнопки Прервать;
•	BreakProcessText – имя кнопки для прерывания процесса;
•	TextLineCount – допустимое число строк в метке с текстом, который будет отображаться над полосой прогресса. */

И вуа-ля:

А если нажмем на кнопку "Прервать", то процесс "замрет" до окончательного решения:

Жмем "Ок" и все закончилось, жмем "Отмена" и процесс движется дальше как ни в чем не бывало :)

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

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

3. Как делать не надо

В начале своего "Творчества", я уже сталкивался с разными вариантами реализации одних и тех же процессов, но всегда вспоминал слова своей первой преподавательницы по программированию Хиценко Валентины Павловны: "Слишком много действий" (с). И правда, очень часто, стараясь сделать все верно и поставить максимальное кол-во блокировок, мы создаем нереальную тучу лишних действий, которые можно избежать без проблем.

Вот один из примеров:

Ссылаясь на статью выше, я уже расписал как мы получаем документы и как видим прогресс, а вот как мы потом поступаем с этим списком? Давайте допустим, что нам надо собрать из выбранных документов реквизиты по списку и вывести их на экран (Ну, чтобы не расписывать как это можно вбивать в Excel или файл)

Вариант 1 (так делать не надо!):

DocId = ''
Index = 1
All = 0
FullText = Сейчас() & CR
  foreach Str in SearchJob
    DocId = AddSubString(Str.ID;DocId;';')
    All = All + 1
  endforeach
  Progress = CreateProgress("Минутку, пожалуйста..."; All)
  Progress.Show                                                                                  
foreach ReqName in CSubString(DocId; ';')
    if Assigned(ReqName)
      Docum = EDocuments.GetObjectByID(ReqName) 
      Progress.Text = Docum.Requisites('ISBEDocName').DisplayText     
      Per1 = Index
      Per2 = Docum.Requisites('ISBEDocName').Value
      Per3 = Docum.Requisites('Дата4').Value
      if Assigned(Docum.Requisites('Дополнение3').Value)
         Per4 = Docum.Requisites('Дополнение3').Value
      else
         Per4 = ''
      endif
      if Assigned(Docum.Requisites('Ответственный').Value)
         User = ServiceFactory.GetUserByCode(Docum.Requisites('Ответственный').Value)
         Per5 = User.FullName
      else
         Per5 = 'Ответственный не указан'
      endif 
      if Docum.Requisites('ISBEDocName').IsNull
         Per2 = ''
      endif
      if Docum.Requisites('Дата4').IsNull
         Per3 = ''
      endif
      Progress.Next                          
      Index = Index + 1
      FullText = FullText & Per1 & '|' & Per2 & '|' & Per3 & '|' & Per4 & '|' & Per5 & CR
    endif
endforeach
FullText = FullText & Сейчас()
Progress.Hide
EditText(FullText)

В данном примере, ужасно практически все.

1. Дважды проход по списку Документов, а ведь многие этим грешат!

2. "ALL" - это вообще что? Зачем?

3. Per№ - к чему?

4. Assigned(ReqName) - бессмысленно;

5. проверки на Null - сомнительное удовольствие...

6. FullText из кучи Per`oв - м-да...

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

Данный образец работал на 150 документов - 15 секунд.

Теперь вариант 2:

Index = 1
FullText = Сейчас()
  Progress = CreateProgress("Минутку, пожалуйста..."; SearchJob.Count)
  Progress.Show                                                                                  
foreach ReqName in SearchJob
      Docum = EDocuments.GetObjectByID(ReqName.ID) 
      Progress.Text = Docum.Requisites('ISBEDocName').DisplayText 
      FullText = AddSubString(Index;FullText;CR)
      FullText = AddSubString(Docum.Requisites('ISBEDocName').Value;FullText;'|') 
      FullText = AddSubString(Docum.Requisites('Дата4').AsString;FullText;'|') 
      FullText = AddSubString(Docum.Requisites('Дополнение3').AsString;FullText;'|')
      try 
         FullText = AddSubString(ServiceFactory.GetUserByCode(Docum.Requisites('Ответственный').Value).FullName;FullText;'|') 
      except
         FullText = AddSubString('Ответственный не указан';FullText;'|')
      endexcept    
      Progress.Next                          
      Index = Index + 1
endforeach
FullText = FullText & Сейчас()
Progress.Hide
EditText(FullText)

Вот все тоже самое, но смотрится и работает иначе: на 150 документов - 9 секунд (сокращение времени на 40%)

Что изменилось:

1. Docum.Requisites('Дата4').AsString - заменила конструкцию на вычисление значения на Null;

2. Избавились от лишних действий;

3. Убрали лишние переменные;

4. Убрали ненужные проверки.

Возможно, я выбрал плохой пример, но он показателен хотя бы тем, что наглядно показывает как часто иногда мы делаем лишние телодвижения в попытке предусмотреть все или избавиться от мимолетной ошибки на манер "Невозможно привести значение NULL к Строке". Так же мы часто перестраховываемся и дробим то, что дробить не нужно, ведь каждая переменная - это ресурс, который хоть и велик, но все же конечен. Будьте внимательнее и чаще смотрите на форум, вполне вероятно, ваш вопрос уже кто-то рассматривал и выписал пути его решение и обхода. 

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

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

Анатолий Придыбайло

Спасибо за статью! В целях улучшения вашего кода замечу следующие моменты:

1. Используйте функции на русском только если нет английского аналога;

2. При указании параметров функций ставьте пробел после разделителя переменных (; - точки с запятой);

3. Строковые значения оборачивайте в " " - двойные кавычки, а не ' ' - одинарные.

Cтандарты по ведению прикладной разработки в DIRECTUM

Елена Питомцева: обновлено 22.08.2019 в 11:56
Даниил Бабарин

Анатолий, ваша ссылка ведёт на закрытый ресурс.

Недостаточно прав для доступа

У вас нет прав на доступ к странице: https://club.directum.ru/post/164842 (403 Forbidden)

Елена Питомцева: обновлено 22.08.2019 в 11:58
Анатолий Придыбайло

Видимо эта информация доступна только для партнеров) Можете попробовать попросить эти материалы в службе поддержки или у закрепленного за вами партнера.

Тарас Асачёв

Анатолий Придыбайло, попросить могу, но дадут ли доступ? да и в любом случае - вы написали, а за мной самообучение не заржавеет 

Даниил Бабарин

Согласен, заголовок статьи выглядит как общеполезный материал для любого разработчика. Может кто-то из "партнёров" может попросить обнародовать материал для обычных разработчиков?)

Фёдор Сироткин

Анатолий, с первыми двумя пунктами понятно. А почему строки нужно писать в двойных кавычках, а не в одинарных?

Анатолий Придыбайло

>>Анатолий, с первыми двумя пунктами понятно. А почему строки нужно писать в двойных кавычках, а не в одинарных?

Этот стандарт идет из C# и по последним правилам в SQL запросах для строк должны использоваться ' ' - одинарные кавычки, а т.к. в Directum частенько приходится писать запросы к базе, этот стандарт, использовать " " - двойные кавычки оправдан.

Анатолий Придыбайло: обновлено 22.08.2019 в 14:22
Фёдор Сироткин

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

Артем Сунцов

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

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