При разработке механизмов контроля уникальности данных в справочнике Контрагенты программисты часто сталкиваются с ситуацией, когда проверка работает некорректно: либо пропускает дубли, либо блокирует создание новых записей без явных причин. В этой статье мы подробно разберем, как реализовать надежную проверку существования контрагента в базе данных, учитывая такие нюансы, как наличие или отсутствие ИНН, и разберем типичные ошибки в логике запросов 1С.
Рассмотрим ситуацию, с которой столкнулся автор темы. Изначально задача стояла так: проверять наличие контрагента по Наименованию или по связке ИНН + КПП. При этом, если ИНН не заполнен, проверка по нему проводиться не должна. Проанализируем ошибочный код запроса, который часто встречается у начинающих разработчиков:
ГДЕ
СпрКонтрагенты.Наименование = &Наименование ИЛИ
СпрКонтрагенты.ИНН = &ИНН И СпрКонтрагенты.КПП = &КПП
В чем здесь кроется главная опасность? В языке запросов 1С (как и в классической логике предикатов) операция И имеет более высокий приоритет, чем ИЛИ. Без использования скобок система интерпретирует это условие следующим образом:
(Наименование = &Наименование) ИЛИ (ИНН = &ИНН И КПП = &КПП)
Если пользователь вводит нового контрагента с уже существующим в базе наименованием, запрос вернет результат, и запись будет заблокирована. Однако, если программист добавляет в код 1С условие Если ЗначениеЗаполнено(ИНН) Тогда, то при пустом ИНН вся проверка (включая проверку наименования) просто игнорируется. Это нарушает логику контроля данных.
Разберем, как изменить запрос, чтобы он корректно обрабатывал ситуацию. Мы должны убедиться, что проверка по наименованию выполняется всегда, а проверка по ИНН/КПП — только если эти данные предоставлены. Проанализируем исправленную структуру функции:
Функция ЕстьВБазеКонтрагент(ИНН, КПП, Наименование)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Контрагенты.Ссылка КАК Ссылка
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.Наименование = &Наименование
| ИЛИ (&ИННЗаполнен И Контрагенты.ИНН = &ИНН И Контрагенты.КПП = &КПП)";
Запрос.УстановитьПараметр("Наименование", Наименование);
Запрос.УстановитьПараметр("ИНН", ИНН);
Запрос.УстановитьПараметр("КПП", КПП);
Запрос.УстановитьПараметр("ИННЗаполнен", ЗначениеЗаполнено(ИНН));
РезультатЗапроса = Запрос.Выполнить();
Возврат НЕ РезультатЗапроса.Пустой();
КонецФункции
В данном примере мы ввели параметр &ИННЗаполнен. Если ИНН пустой, выражение в скобках после ИЛИ всегда будет ложным, и запрос будет искать дубли только по Наименованию. Обратите внимание на использование ВЫБРАТЬ ПЕРВЫЕ 1 — это оптимизирует работу базы данных, так как системе достаточно найти хотя бы одно совпадение.
Как справедливо заметили участники обсуждения, использование конструкции ОБЪЕДИНИТЬ часто является более «прозрачным» и надежным способом. Это позволяет избежать сложных логических ветвлений внутри одного условия ГДЕ. Выясним, как это выглядит на практике:
Функция ПроверитьДублиКонтрагента(ИНН, КПП, Наименование)
ТекстЗапроса =
"ВЫБРАТЬ Ссылка ИЗ Справочник.Контрагенты ГДЕ Наименование = &Наименование
|ОБЪЕДИНИТЬ
|ВЫБРАТЬ Ссылка ИЗ Справочник.Контрагенты ГДЕ ИНН = &ИНН И КПП = &КПП И &ИННЗаполнен";
// ... установка параметров аналогична предыдущему примеру
КонецФункции
Этот подход хорош тем, что платформа 1С может более эффективно использовать индексы для каждой части объединенного запроса по отдельности. Если у вас проиндексированы поля Наименование и ИНН, такой запрос отработает мгновенно даже на огромных справочниках.
Проанализируем еще один интересный подход, упомянутый на форуме — использование конструкции ВЫБОР КОГДА.... Это позволяет реализовать гибкую логику прямо в теле запроса, не перегружая код 1С лишними проверками:
Запрос.Текст =
"ВЫБРАТЬ
| 1
|ИЗ
| Справочник.Контрагенты КАК Спр
|ГДЕ
| ВЫБОР
| КОГДА Спр.Наименование = &Наименование ТОГДА ИСТИНА
| КОГДА &ИННЗаполнен И Спр.ИНН = &ИНН И Спр.КПП = &КПП ТОГДА ИСТИНА
| ИНАЧЕ ЛОЖЬ
| КОНЕЦ";
Такой синтаксис наглядно показывает приоритеты проверки: сначала ищем по названию, затем — по ключевым реквизитам организации.
При реализации подобных доработок необходимо учитывать несколько критических факторов, которые могут повлиять на корректность данных в будущем:
ИНН и Наименование имеют свойство «Индексировать» в метаданных справочника.СокрЛП(). Также полезно привести строку к единому регистру или использовать в запросе оператор ПОДОБНО, чтобы избежать дублей вида «ООО Ромашка» и «ооо ромашка».ИНН состоит из 12 цифр, а поле КПП отсутствует (пустое). Если ваша проверка жестко требует совпадения КПП, вы можете пропустить дубли по физлицам. В запросе стоит добавить условие, проверяющее длину ИНН или тип контрагента.ОбработкаПроверкиЗаполнения — отличное место для интерактивного взаимодействия с пользователем. Однако, если вы хотите гарантированно избежать дублей при программной загрузке данных (например, из Excel), логику проверки следует продублировать в событии ПередЗаписью на стороне модуля объекта.Посмотрим на итоговый вариант того, как может выглядеть процедура в модуле объекта справочника Контрагенты:
Процедура ОбработкаПроверкиЗаполнения(Отказ, ПроверяемыеРеквизиты)
Если ЭтоНовый() Тогда
Если ЗначениеЗаполнено(Наименование) Тогда
Если ЕстьВБазеКонтрагент(ИНН, КПП, Наименование) Тогда
Отказ = Истина;
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Внимание! Контрагент с таким наименованием или ИНН/КПП уже существует в системе.";
Сообщение.Поле = "Наименование";
Сообщение.Сообщить();
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Разберем, почему мы проверяем ЭтоНовый(). При редактировании уже существующего контрагента (например, при смене телефона или адреса), система не должна находить «самого себя» как дубль и блокировать запись. Если же проверка требуется и при изменении ИНН, в запрос нужно добавить условие ГДЕ Ссылка <> &ТекущаяСсылка.
Используя предложенные методы и учитывая логику приоритетов операций в запросах, вы сможете создать надежный механизм контроля чистоты данных в вашей информационной базе.