Создаем записи справочника чужими руками, на примере справочника Организации

7 10

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

Главное понимание - надо вам это и для чего. В большой компании, где новые Контрагенты появляются по пять штук в день минимум - совсем не желательно раздавать права на создание записей во все желающие руки - начнется бардак с созданием записей с одним только названием, причем частенько даже не проверяя: есть ли уже такая запись. В противовес такому "человеческому фактору", мы создали механизм круговой поруки (которая мажет как копоть) где решение о создании или изменении записи, лежит не на специалисте, а на Верификаторе или Андерайтере, которым потом самим же и заниматься идентификацией контрагента. А для простоты работы с новым Сценарием, мы внесли функционал в рабочий маршрут. 

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

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

Мы не будем глубоко углубляться в моменты контроля и наполнения карточки - ничего в этом сакрального нет. Хотя пару отступлений стоит сделать:

1. Для того чтобы поиск Организации работал не только по названию, но и по ИНН, в "Выборе" были сделаны следующие записи:

// До выбора
  KeyRequisite = Object.Requisites("Организация")
  KeyRequisiteArray = ArrayOf(ArrayOf("Наименование"; "Справочник:ОРГ"; KeyRequisite.Title; KeyRequisite.AsString; TRUE; TRUE))
  LookUpRequisiteNames.Add('LongString5')
  LookUpRequisiteNames.Add('ИНН')  
  BeforeSelectingFromRefRequisite(SelectMode; InputValue; LookUpReference; KeyRequisiteArray; FALSE)

// После выбора
    LookUpRequisiteNames.DelimitedText = AfterSelectingFromRefRequisite(LookUpReference)

2. Так же, на реквизите Организации мы создали вычисления, которые в случае выбора - выбирали все необходимые данные из Справочника. А в случае удаления - зачищали поля:

if Assigned(Object.Requisites("Организация").Value)
   RequisitesTabMain = 'String;String2;String3;String4;DFAWorker;Email;LongString3;Телефон;НаселенныйПункт;LongString;LongString2'
    foreach ReqName in CSubString(RequisitesTabMain; ';')
      Object.Requisites(ReqName).Value = null
    endforeach
   КодОрг = Object.Requisites("Организация").Value
   Организация = References.ОРГ.GetObjectByCode(КодОрг)
   Object.Requisites("String").Value = Trim(Организация.Requisites(SYSREQ_NAME).Value)  // Наименование
   Object.Requisites("String2").Value = Trim(Организация.Requisites("LongString5").Value) // Юридическое наименование
   if Assigned(Организация.Requisites("ИНН").Value)  // ИНН
      Object.Requisites("String3").Value = Trim(Организация.Requisites("ИНН").Value)
   endif
   if Assigned(Организация.Requisites("Содержание2").Value) // Адрес
      Object.Requisites("LongString").Value = Trim(Организация.Requisites("Содержание2").Value)
   endif
   if Assigned(Организация.Requisites("Ед_Изм").Value)  // КПП
      Object.Requisites("String4").Value = Trim(Организация.Requisites("Ед_Изм").Value)
   endif
   if Assigned(Организация.Requisites("Дополнение4").Value)  // Телефоны
      Object.Requisites("Телефон").Value = Trim(Организация.Requisites("Дополнение4").Value)
   endif
   if Assigned(Организация.Requisites("LongString4").Value)  // Отрасль
      Object.Requisites("LongString3").Value = Организация.Requisites("LongString4").Value
   endif
   if Assigned(Организация.Requisites("Email").Value)  // Email
      Object.Requisites("Email").Value = Trim(Организация.Requisites("Email").Value)
   endif
   if Assigned(Организация.Requisites("Employee").Value)  // Ответственный
      Object.Requisites("DFAWorker").Value = Организация.Requisites("Employee").Value
   endif
   if Assigned(Организация.Requisites("Город").Value)  // Населенный пункт
      Object.Requisites("НаселенныйПункт").Value = Организация.Requisites("Город").Value
   endif
   Object.SYSREQ_EDOC_NAME = Format("Архив документов по клиенту: %s на %s (Отв: %s)"; ArrayOf(Организация.SYSREQ_NAME;Сегодня();Object.Requisites("DFAWorker").DisplayText)) 
else
   RequisitesTabMain = 'String;String2;String3;String4;DFAWorker;Email;LongString3;Телефон;НаселенныйПункт;LongString;LongString2'
    foreach ReqName in CSubString(RequisitesTabMain; ';')
      Object.Requisites(ReqName).Value = null
    endforeach
endif

Прошу обратить внимание, что реквизит "Отрасль" в справочнике мы изменили - учитывайте это, когда будете копировать!

3. Ну, и конечно куча вычислений "Перед Сохранением", часть из которых - суть проверки сведений Данных в карточке и в записи справочника:

if Assigned(Object.Requisites("Организация").Value)
    КодОрг = Object.Requisites("Организация").Value
    Организация = References.ОРГ.GetObjectByCode(КодОрг)
   if (Object.Requisites("String").Value <<>> Trim(Организация.Requisites(SYSREQ_NAME).DisplayText)) or
      (Object.Requisites("String2").Value <<>> Trim(Организация.Requisites("LongString5").DisplayText)) or
      (Object.Requisites("String3").Value <<>> Организация.Requisites("ИНН").Value) or
      (Object.Requisites("String4").Value <<>> Организация.Requisites("Ед_Изм").Value) or
      (Object.Requisites("Телефон").Value <<>> Организация.Requisites("Дополнение4").Value) or
      (Object.Requisites("LongString3").Value <<>> Организация.Requisites("LongString4").Value) or
      (Object.Requisites("Email").Value <<>> Организация.Requisites("Email").Value) or
      (Object.Requisites("DFAWorker").Value <<>> Организация.Requisites("Employee").Value) or
      (Object.Requisites("НаселенныйПункт").Value <<>> Организация.Requisites("Город").Value)
   Object.Requisites("YesNo9").Value = YES_VALUE
   endif
endif

Вот в этой части у нас всплывает реквизит "Object.Requisites("YesNo9").Value", который отмечает - стоит ли вносить изменения в запись справочника или все осталось по старому. Такой же реквизит мы активируем, когда у нас в принципе нет Организации, но есть все необходимые для ее создания данные.

Ну, и по мелочи - ищем схожие записи по ИНН, а так же проверяем ИНН в параметрах:

if not Assigned(Object.Requisites("Организация").Value)      
  Reference =  References.ОРГ.GetComponent
    View = Reference.CreateView('Главное')
    AddWhere = Reference.AddWhere(Format("%0:s.%1:s like char(37) + '%2:g' + char(37)"; ArrayOf(Reference.TableName; Reference.Requisites('ИНН').FieldName; INN)))
  Reference.Open 
      if Reference.RecordCount > 0     
          Code = Reference.Requisites('Код').AsString
          Organiz = ReferenceRequisiteValue('ОРГ';Code;'Наименование')
          Text1 = "В справочнике Организаций, уже существует запись с ИНН = "
          Text2 = "Найденная организация: "
          Text3 = "Выбрать в документ найденную организацию?"
          Raise(СоздатьИсключение("Найден Дубликат!"; Text1 & INN & CR & Text2 & Organiz ; ecWarning ))
      endif          
  Reference.Close
  Reference.DelWhere(AddWhere)
  Reference = nil
  Object.Requisites("YesNo9").Value = YES_VALUE
else
  OrgINN = ReferenceRequisiteValue('ОРГ';Object.Requisites("Организация").Value;'ИНН')
  if INN <<>> OrgINN
  text4 = "Введенный вами ИНН не совпадает с ИНН выбранной организации!" & CR & "ИНН выбранной организации: " & OrgINN
  Raise(СоздатьИсключение("Несовпадение ИНН!"; text4 ; ecWarning ))
  endif
endif

И до кучи проверяем ИНН:

  INN = Object.Requisites("String3").Value
      if (ДлинаСтр(INN) > 8) and (ДлинаСтр(INN) < 13)
        //
      else
        TextExcept = "1. ИНН физического лица является последовательностью из 12 цифр." & CR &
        "2 .ИНН индивидуального предпринимателя присваивается при регистрации физического лица." & CR &
        "3. ИНН юридического лица является последовательностью из 10 цифр." & CR &
        "4. ИНН иностранного юридического лица всегда начинается с цифр «9909» и далее еще 5 цифр."
        Object.Requisites("String3").Value = ''
        Raise(СоздатьИсключение("Ошибка формата ИНН!"; TextExcept ; ecException))
      endif  

Достаточно - остальное уже мелочи и суть-украшательство.

 

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

Как понятно из сути - первый условный блок №33 смотрит значение переменной в Документе "YesNo9" и если надо вносить правки или создавать запись - включается ответвление. 

Блок №34 "Ответственный за Согласование Карточки ОРГАНИЗАЦИИ" Событие Страт:

Params = Object.WorkflowParams
EDocInfo = Params.ValueByName("Attachment").Value   // Наш Документ "Сканы юридических дел"
Doc = EDocuments.GetObjectByID(EDocInfo.id)
  if Assigned(Doc.Requisites('Организация').Value)
      Theme = Format("Согласование ИЗМЕНЕНИЯ карточки Контрагента: %s"; Doc.Requisites('String').Value)
      TextFull = "В уже существующей карточке Контрагента были внесены изменения:" & CR & 
        "Наименование клиента: " & Doc.Requisites("String").Value & CR &
        "Юридическое наименование: " & Doc.Requisites("String2").Value & CR &
        "ИНН: " & Doc.Requisites("String3").Value & CR &
        "КПП: " & Doc.Requisites("String4").Value & CR &
        "Город: " & Doc.Requisites("НаселенныйПункт").DisplayText & CR &
        "Вид Деятельности: " & Doc.Requisites("LongString3").Value & CR &
        "Email: " & Doc.Requisites("Email").Value & CR &
        "Партнер: Да" & CR &
        "Телефон: " & Doc.Requisites("Телефон").Value & CR & CR &
        "Принимаете ли вы данные изменения или требуется их изменение?" & CR &
        "Если вы согласуете данную задачу, в справочнике обновятся данные у указанного Контрагента."
  else
      Theme = Format("Согласование СОЗДАНИЯ карточки Контрагента: %s"; Doc.Requisites('String').Value)
      TextFull = "Требуется согласовать создание новой записи в справочник Организации:" & CR & 
        "Наименование клиента: " & Doc.Requisites("String").Value & CR &
        "Юридическое наименование: " & Doc.Requisites("String2").Value & CR &
        "ИНН: " & Doc.Requisites("String3").Value & CR &
        "КПП: " & Doc.Requisites("String4").Value & CR &
        "Город: " & Doc.Requisites("НаселенныйПункт").Value & CR &
        "Вид Деятельности: " & Doc.Requisites("LongString3").Value & CR &
        "Email: " & Doc.Requisites("Email").Value & CR &
        "Партнер: Да" & CR &
        "Телефон: " & Doc.Requisites("Телефон").Value & CR & CR &
        "Принимаете ли вы данные изменения или требуется их изменение?" & CR &
        "Если вы согласуете данную задачу, в справочнике Организации появиться новая запись с вышеуказанными данными."
  endif
                                                                  
Object.ActiveText = TextFull
Sender.Properties.ValueByName(JOB_BLOCK_SUBJECT_PROPERTY).Value = Theme

Блок №36 - отправка инициатору на Доработку - не обсуждаем, там ничего интересного нет.

Самая соль в блоке Сценария №35 "Создание/Изменение карточки Организации", в котором у нас вложены те самые, нужные нам, Вычисления:

Params = Object.WorkflowParams
EDocInfo = Params.ValueByName("Attachment").Value
Doc = EDocuments.GetObjectByID(EDocInfo.id)

if Assigned(Doc.Requisites('Организация').Value) 
  OrgCode = ИзмененияСпрОрганизации(Doc.Requisites('Организация').Value;
  Doc.Requisites("String").Value; Doc.Requisites("String2").Value; Doc.Requisites("String3").Value;
  Doc.Requisites("String4").Value; Doc.Requisites("НаселенныйПункт").Value; Doc.Requisites("LongString3").Value;
  Doc.Requisites("Email").Value; 'Да'; Doc.Requisites("Телефон").Value; Doc.Requisites("DFAWorker").Value; Doc.Requisites("LongString").Value)
  Text = "Обновлена карточка Конрагента: " & OrgCode
else
 OrgCode = ИзмененияСпрОрганизации(;Doc.Requisites("String").Value; Doc.Requisites("String2").Value; Doc.Requisites("String3").Value;
  Doc.Requisites("String4").Value; Doc.Requisites("НаселенныйПункт").Value; Doc.Requisites("LongString3").Value;
  Doc.Requisites("Email").Value; 'Да'; Doc.Requisites("Телефон").Value; Doc.Requisites("DFAWorker").Value; Doc.Requisites("LongString").Value)
  Text = "Создана новая карточка Конрагента: " & OrgCode
endif
  Object.ActiveText = Text
  Params.ValueByName("КодКлиента").Value = OrgCode
  if Assigned(OrgCode)
      Doc.Requisites('YesNo9').Value = NO_VALUE
      Doc.Requisites('Организация').Value = OrgCode
      Doc.Save
  endif

Не густо - не так ли?

Суть всего этого танца, заключена в создании Функции "ИзмененияСпрОрганизации" в которую передаются параметры.

Вот ее основа и наполнение:

А вот и само наполнение:

if Assigned(Контрагент)
  try
   SprOrg = References.ОРГ.GetObjectByCode(Trim(Контрагент))
  except
   SprOrg = References.ОРГ.CreateNew
  endexcept 
else
   SprOrg = References.ОРГ.CreateNew
endif
   SprOrg.Requisites(SYSREQ_NAME).AsString = Наименование
   SprOrg.Requisites("LongString5").AsString = ЮрНаименование
   SprOrg.Requisites("ИНН").AsString = ИНН
   SprOrg.Requisites("Ед_Изм").AsString = КПП
   SprOrg.Requisites("Город").Value = Город
   SprOrg.Requisites("LongString4").Value = ВидДеятельности
   SprOrg.Requisites("Партнерство").Value = Партнер
   SprOrg.Requisites("Email").AsString = Email
   SprOrg.Requisites("Дополнение4").AsString = Телефон
   SprOrg.Requisites("Employee").Value = Ответственный
   SprOrg.Requisites("Содержание2").AsString = Адрес
   SprOrg.Save 
   Rez = SprOrg.SYSREQ_CODE
   SprOrg.Close
   
   Result = Rez

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

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

По сути это все. Если вы узнали что-то новое или полезное - я писал это не зря!

Всем удачи!

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

Тарас, если я правильно понимаю, у вас на каждую заявку на создание/актуализацию Контрагента в системе создается документ. Назначение полей карточки и логика их обработки из материала понятны. А что с телом документа? Что туда заносится и как потом используется?

Евгения Сарварова

1. Дополню вопрос Алексея. Интересно что происходит с телом документа после завершения согласования и внесения корректировок в справочник Организации. Остается лежать мертвым грузом/используется для статистики/удаляется?

Также интересно:

2. Какова нагрузка на ответственного за согласование карточки Организации?

3. Как быстро новая заявка проходит согласование?

Тарас Асачёв

Алексей Семакин, Документ создается под архив с утверждающими документами на Партнера, где все, что является основой для работы - Юридические документы, которые как правило не меняются, разве что адреса или телефоны могут быть изменены.

В Типовом маршруте, где данный документ и Сценарий задействован у нас предусмотрены 3 линии движения:

  1.  Открытие счетов для нового Партнера;
  2.  Изменение данных нашего Партнера;
  3.  Закрытие счетов партнера.

Все эти линии нуждаются в документах основных - "Сканы юридических лиц" а так же документах добавляемых по сетке:

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

Евгения Сарварова, 1. После завершения согласования, все документы из сетки выше (они все принадлежат одному типу документов) принудительно привязываются к первичному документу: "Сканы юридических лиц", который в свою очередь связан со справочником Организации. Круговая порука как она есть. Связывания не происходит только в случае отказа в периоде согласования, о всех остальных случаях - связывание происходит в самом конце маршрута. Документы не входящие в сетку - не подшиваются. 

А для архива и отчетности, у нас организована папка поиска, которая все эти документы собирает. С ней работает ФинМониторинг.

2. Ответственный за согласование изменения/создания карточки Организации и до этого занимался только тем, что перебирал документы из архивов во вложениях и сверял их с карточками и указанными в маршруте параметрах. Сейчас проверка происходит в 1 месте - сверка документов и единственной карточки, да и то, только если надо создавать карточку или утвердить правки. Вроде как стало только проще. 

3. В виду высокой заинтересованности Контролера и ведущего Партнера менеджера, данный узел в Маршруте проходится за 30-60 минут максимум (ранее проверка отнимала время больше на другом этапе - проверка документов), а весь маршрут не согласуется более суток в любой его линии согласования.

Евгения Сарварова

Тарас, спасибо за развернутый ответ!

Тарас Асачёв

Собственно вот вся карта:

Дмитрий Тарасов
   SprOrg = References.ОРГ.GetObjectByCode(Trim(Контрагент))
   SprOrg.Open

Мне кажется SprOrg.Open тут лишнее, т.к. метод GetObjectByCode и так инициирует процесс открытия записи справочника.

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

Зачем использовать такие конструкции:

SprOrg.Requisites("Город").Value = References.ГРД.GetObjectByCode(Город).SYSREQ_CODE
SprOrg.Requisites("Employee").Value = References.РАБ.GetObjectByCode(Ответственный).SYSREQ_CODE

У вас и так в переменных Город и Ответственный содержится код записи! достаточно:

SprOrg.Requisites("Город").Value = Город
SprOrg.Requisites("Employee").Value = Ответственный

Артем Сергеев

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

Trim(Организация.Requisites(SYSREQ_NAME).Value)

тк в случае получения null будет exception, используйте лучше:

Trim(Организация.Requisites(SYSREQ_NAME).DisplayText)

если в реквизите будет null, то DisplayText возвращает пустую строку.

Артем Сергеев: обновлено 20.03.2019 в 09:26
Тарас Асачёв

 

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

Анатолий Придыбайло, да - я снова перестраховался. 

Артем Сергеев, Ну, я учитывал невозможность Null до этого, но вы правы - лучше привыкать к лучшему. 

Все предложения в коде учтены. Спасибо!

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

Дополню, что вместо вычислений для поиска Организации по ИНН можно выполнить аналогичную настройку, бонусом настройка станет работать для всех полей Организация во всех карточках.

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