Пример поиска документов с обложки

17 2

Введение

Цель обложки — предоставить быстрый доступ к основному функционалу модуля. Одна из таких функциональностей - поиск документов по некоторым заданным критериям. Проще всего расположить ссылку для открытия окна, в котором пользователь указывает критерии поиска. После чего отобразить список найденных документов.

Простые пути — не наш выбор. Поэтому попробуем обойтись без дополнительного окна, а сразу расположим поля для ввода критериев поиска на обложке. Это не слишком сложная задача, с учетом того, что аналог перед глазами — поиск РКК с обложки. Вот только одно дело — открыть список с фильтрацией. По документам реализация немного отличается. 

Мы эту реализацию сделали. Спешу поделиться опытом с сообществом.

Задача

Предположим, есть у нас модуль Финансовый архив. И хотим мы реализовать поиск первичных учетных документов. В стандартной версии есть прикладной поиск (запись справочника Поиски) — Поиск первичных учетных документов:

Перенесем его на обложку.

Учитываем следующее:

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

Реализация

Расположение полей на обложке нагло стащим с обложки того самого модуля Канцелярия. В итоге в htm будет такой большой узел:

             <table class="table">
                <tr>
                  <td class="ParameterName">
                    Наименование:
                  </td>
                  <td class="ParameterValue">
                    <input type="text" id="DocName" />
                  </td>
                </tr>
                <tr>
                  <td class="ParameterName">
                    Тип карточки:
                  </td>
                  <td class="ParameterValue">
                    <select id="DocType" style="width: 100%">
                      <option value=""></option>
                      <option value="DFAActsWaybills">Передаточные документы</option>
                      <option value="DFASourceFinanceDocument">Платежные документы</option>
                      <option value="DFAInvoices">Счета-фактуры</option>
                    </select>
                  </td>
                </tr>
                <tr>
                  <td class="ParameterName">
                    Вид документа:
                  </td>
                  <td class="ParameterValue">
                    <select id="DocKind" style="width: 100%">
                      <option value=""></option>
                      <option value="DFAIncomingActDocKind">Входящий акт</option>
                      <option value="DFAIncomingWaybillDocKind">Входящая накладная</option>
                      <option value="DFAIncomingInvoiceDocKind">Входящий счет-фактура</option>
                      <option value="DFAIncomingUTDDocKind">Входящий УПД</option>
                      <option value="DFAIncomingUTDInvDocKind">Входящий УПД со счетом-фактурой</option>
                      <option value="DFAOutgoingActDocKind">Исходящий акт</option>
                      <option value="DFAOutgoingWaybillDocKind">Исходящая накладная</option>
                      <option value="DFAOutgoingInvoiceDocKind">Исходящий счет-фактура</option>
                      <option value="DFAOutgoingUTDDocKind">Исходящий УПД</option>
                      <option value="DFAOutgoingUTDInvDocKind">Исходящий УПД со счетом-фактурой</option>
                    </select>
                  </td>
                </tr>
                <tr>
                  <td class="ParameterName">
                    Номер:
                  </td>
                  <td class="ParameterValue">
                    <input type="text" id="DocNumber" />
                  </td>
                </tr>
                <tr>
                  <td class="ParameterName">
                    Дата:
                  </td>
                  <td class="ParameterValue">
                    <input type="text" class="DatePicker" id="DocDateBgn" />&nbsp;-&nbsp;<input type="text" class="DatePicker" id="DocDateEnd" />
                  </td>
                </tr>
                <tr>
                  <td class="ParameterName">
                    Контрагент:
                  </td>
                  <td class="ParameterValue">
                    <input type="text" id="Counterparty" />
                  </td>
                </tr>
                <tr>
                  <td class="ParameterName">
                    Наша организация:
                  </td>
                  <td class="ParameterValue">
                    <input type="text" id="OurFirm" />
                  </td>
                </tr>
                <tr>
                  <td class="ParameterName">
                    Договор:
                  </td>
                  <td class="ParameterValue">
                    <input type="text" id="Contract" />
                  </td>
                </tr>
                <tr>
                  <td class="ParameterName">
                    ИД:
                  </td>
                  <td class="ParameterValue">
                    <input type="text" id="DocID" />
                  </td>
                </tr>
                <tr>
                  <td colspan="2" style="text-align: right">
                    <input type="button" id="Find" onclick="DFADocSearch()" value=" Поиск " />
                  </td>
                </tr>
              </table>

Который будет выглядеть так:

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

/**
* Поиск первичных документов
* @method DFADocSearch
* @param {string} UserID - ид текущего пользователя
*/
function DFADocSearch(UserID) {
    var DocName = document.getElementById("DocName").value;
    var DocType = document.getElementById("DocType").value;
    var DocKind = document.getElementById("DocKind").value;
    var DocNumber = document.getElementById("DocNumber").value;
    var DocDateBgn = document.getElementById("DocDateBgn").value;
    var DocDateEnd = document.getElementById("DocDateEnd").value;
    var Counterparty = document.getElementById("Counterparty").value;
    var OurFirm = document.getElementById("OurFirm").value;
    var Contract = document.getElementById("Contract").value;
    var DocID = document.getElementById("DocID").value;

    ComponentExecuteInNewProcess("Script", "DFADocSearch",
      "DocName=" + DocName +
      "|DocType=" + DocType +
      "|DocKind=" + DocKind +
      "|DocNumber=" + DocNumber +
      "|DocDateBgn=" + DocDateBgn +
      "|DocDateEnd=" + DocDateEnd +
      "|Counterparty=" + Counterparty +
      "|OurFirm=" + OurFirm +
      "|Contract=" + Contract +
      "|DocID=" + DocID, "COMMON");
}

Завершающей стадией будет реализовать собственно поиск.

Что нужно учитывать? Чтобы по кнопке "Изменить критерии" в списке документов были только поля, важные для первички, поиск мы подгружаем из справочника "Поиски".

  Search = Searches.Load("Поиск первичных учетных документов")
  Search.Description = LoadString('DIRSCRIPT_4E192A2C_4A22_4D6F_A9EB_5EC74549E1B8'; 'DFA')    

Номер документа — обычное строковое поле. И номер "15" при невнимательном поиске будет искать документы, в номере которых конструкция эта содержится. Например, отыщется документ с номером 150 и 115. Отсечем это так:

  /* Критерий по номеру документа */
  DocNumber = GetComponentLaunchParam('DocNumber'; '')
  if DocNumber <<>> ''
    Criterion = Search.SearchCriteria.Add("String2")
    Criterion.SetCompleteValue("РАВНО " & DocNumber) 
  endif  

Для того чтобы заполнить критерий по справочникам (в нашем случае это Контрагент, Наша организация, Договор), надо сначала найти их в справочнике. Поисков таких несколько, поэтому шаблон запроса вынесем в переменную LikeQueryTmpl. Запрос простенький, типа такого:

  LikeQueryTmpl = "-- Сценарий DFADocSearch
    select
      rec.Kod
    from
      dbo.MBVidAn ref
      inner join dbo.MBAnalit rec on
        rec.Vid = ref.Vid
        -- Тут будет критерий для поиска по наименованию 
        and %s 
    where
      -- Код справочника будет меняться в зависимости от поля 
      ref.Kod = '%s'"

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

  // Поиск по контрагенту
  RefLikeValue = GetComponentLaunchParam('Counterparty'; '')
  if RefLikeValue <<>> ''
    DEL = " " // Слова обычно разделяются пробелами
    RefLikeValueList.Delimiter = DEL
    ReqName = "Организация"
    RefCode = "ОРГ"
    // Учитываем возможные SQL-иньъекции
    RefLikeValue = Replace(RefLikeValue; "'"; "''") 
    // Уберем лишние пробелы на случай если у пользователя дрогнула рука
    RefLikeValue = Replace(RefLikeValue; "  "; " ") 
    if SubStringCount(RefLikeValue; DEL) = 1
      RefLikeValue = "NameAn like '%" & RefLikeValue & "%'" 
    else
      RefLikeValue = Replace(RefLikeValue; DEL & DEL; DEL)
      RefLikeValueList.DelimitedText = RefLikeValue
      RefLikeValueList.Delimiter = "%') and (NameAn like '%"
      RefLikeValue = "(NameAn like '%" & RefLikeValueList.DelimitedText & "%')"     
    endif
    Criterion = Search.SearchCriteria.Add(ReqName)
    Query = Format(LikeQueryTmpl; ArrayOf(RefLikeValue; RefCode))
    Values = SQL(Query;; CONST_ELEMENT_DELIMITER; CONST_VALUE_DELIMITER)
    foreach Code in CSubString(Values; CONST_ELEMENT_DELIMITER)
      Criterion.Add(Code)
    endforeach 
    Criterion.ValuesBuildType = btOr
  endif   

Этот вариант вполне себе рабочий, вот только при попытке изменить критерии видим ужасное:

Почему в типах карточках красивое и читабельное название, а в другом поле коды с пробелами? Нехорошо. Но решаемо. Из всех вариантов заполнения критериев используем AddWithPhysical. Для этого метода одного кода записи не достаточно, поэтому запросом достанем ИД и Наименование записи:

  LikeQueryTmpl = "-- Сценарий DFADocSearch
    select
      rtrim(ltrim(rec.NameAn)), rec.xRecID
    from
      dbo.MBVidAn ref
      inner join dbo.MBAnalit rec on
        rec.Vid = ref.Vid and %s 
    where
      ref.Kod = '%s'"

И используем полученные данные:

    Query = Format(LikeQueryTmpl; ArrayOf(RefLikeValue; RefCode))
    Values = SQL(Query;; CONST_ELEMENT_DELIMITER; CONST_VALUE_DELIMITER)
    foreach Value in CSubString(Values; CONST_ELEMENT_DELIMITER)
      Criterion.AddWithPhysical(SubString(Value; CONST_VALUE_DELIMITER; 1); SubString(Value; CONST_VALUE_DELIMITER; 2))
    endforeach 

Получаем вполне себе читабельный вариант:

Ну и в завершение посмотрим, как мы искали виды документов по имени константы: на обложке в выпадающем списке 10 значений, соответствующих не самим видам, а константам:

  // Поиск по виду документа
  DocKind = GetComponentLaunchParam('DocKind'; '')
  if DocKind <<>> ''
    RefCode = "ВЭД"
    ReqName = 'ISBEDocKind'
    // Получить виды документов из константы
    RefLikeValueList.Delimiter = ','
    RefLikeValueList.DelimitedText = Replace(GetConstant(DocKind); " "; '')
    Criterion = Search.SearchCriteria.Add(ReqName)
    if RefLikeValueList.Count = 1
      Criterion.SetCompleteValue(RefLikeValueList.DelimitedText)
    else
      RefLikeValueList.Delimiter = "','"
      RefLikeValue = "rtrim(ltrim(rec.Kod)) in ('" & RefLikeValueList.DelimitedText & "')"
      Query = Format(LikeQueryTmpl; ArrayOf(RefLikeValue; RefCode))
      Values = SQL(Query;; CONST_ELEMENT_DELIMITER; CONST_VALUE_DELIMITER)
      foreach Value in CSubString(Values; CONST_ELEMENT_DELIMITER)
        Criterion.AddWithPhysical(SubString(Value; CONST_VALUE_DELIMITER; 1); SubString(Value; CONST_VALUE_DELIMITER; 2))
      endforeach 
      Criterion.ValuesBuildType = btOr
    endif
  endif

Заключение

Таким образом мы получаем пример, как легко и непринужденно встроить на обложку поиск документов на примере поиска первичных учетных документов.

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

17
Авторизуйтесь, чтобы оценить материал.
4
Александр Куклин

Анна, интересная статья.
Есть несколько вопросов и предложений по коду:

  1.  
    // Учитываем возможные SQL-иньъекции
    RefLikeValue = Replace(RefLikeValue; "'"; "''") 

Тут, скорее, не защита от инъекций (звучит громковато), а обычное экранирование кавычек для поиска по значениям вида <ООО 'Рога и копыта'>

  1. С какой целью замена двойных пробелов на одинарный делается дважды в одном и том же тексте?
    Вот тут:
// Уберем лишние пробелы на случай если у пользователя дрогнула рука
    RefLikeValue = Replace(RefLikeValue; "  "; " ") 

И сразу же чуть ниже

RefLikeValue = Replace(RefLikeValue; DEL & DEL; DEL)
  1. Каким образом работает запрос LikeQueryTmpl со следующим шаблоном фильтрации? 
      RefLikeValueList.Delimiter = "%') and (NameAn like '%"
      RefLikeValue = "(NameAn like '%" & RefLikeValueList.DelimitedText & "%')"   

?
В итоге получится условие вида

(NameAn like '%value1%') and (NameAn like '%value2%') and (NameAn like '%value3%') and ...

P.S. Вопрос номер №3 неактуален :)

 

Александр Куклин: обновлено 15.03.2018 в 11:02
Анна Долганова

Александр, спасибо за дельные предложения. С которыми я бесспорно соглашусь 

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