Как правильно реализовать проверку на дубли контрагентов по ИНН, КПП и наименованию?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

Анализ проблемы: почему стандартный подход с «ИЛИ» может не работать

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


ГДЕ
    СпрКонтрагенты.Наименование = &Наименование ИЛИ
    СпрКонтрагенты.ИНН = &ИНН И СпрКонтрагенты.КПП = &КПП

В чем здесь кроется главная опасность? В языке запросов 1С (как и в классической логике предикатов) операция И имеет более высокий приоритет, чем ИЛИ. Без использования скобок система интерпретирует это условие следующим образом:

(Наименование = &Наименование) ИЛИ (ИНН = &ИНН И КПП = &КПП)

Если пользователь вводит нового контрагента с уже существующим в базе наименованием, запрос вернет результат, и запись будет заблокирована. Однако, если программист добавляет в код 1С условие Если ЗначениеЗаполнено(ИНН) Тогда, то при пустом ИНН вся проверка (включая проверку наименования) просто игнорируется. Это нарушает логику контроля данных.

Вариант решения №1: Правильная расстановка скобок и логических условий

Разберем, как изменить запрос, чтобы он корректно обрабатывал ситуацию. Мы должны убедиться, что проверка по наименованию выполняется всегда, а проверка по ИНН/КПП — только если эти данные предоставлены. Проанализируем исправленную структуру функции:


Функция ЕстьВБазеКонтрагент(ИНН, КПП, Наименование)
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ ПЕРВЫЕ 1
    |   Контрагенты.Ссылка КАК Ссылка
    |ИЗ
    |   Справочник.Контрагенты КАК Контрагенты
    |ГДЕ
    |   Контрагенты.Наименование = &Наименование
    |   ИЛИ (&ИННЗаполнен И Контрагенты.ИНН = &ИНН И Контрагенты.КПП = &КПП)";
    
    Запрос.УстановитьПараметр("Наименование", Наименование);
    Запрос.УстановитьПараметр("ИНН", ИНН);
    Запрос.УстановитьПараметр("КПП", КПП);
    Запрос.УстановитьПараметр("ИННЗаполнен", ЗначениеЗаполнено(ИНН));
    
    РезультатЗапроса = Запрос.Выполнить();
    Возврат НЕ РезультатЗапроса.Пустой();
КонецФункции

В данном примере мы ввели параметр &ИННЗаполнен. Если ИНН пустой, выражение в скобках после ИЛИ всегда будет ложным, и запрос будет искать дубли только по Наименованию. Обратите внимание на использование ВЫБРАТЬ ПЕРВЫЕ 1 — это оптимизирует работу базы данных, так как системе достаточно найти хотя бы одно совпадение.

Вариант решения №2: Использование объединения запросов (UNION)

Как справедливо заметили участники обсуждения, использование конструкции ОБЪЕДИНИТЬ часто является более «прозрачным» и надежным способом. Это позволяет избежать сложных логических ветвлений внутри одного условия ГДЕ. Выясним, как это выглядит на практике:


Функция ПроверитьДублиКонтрагента(ИНН, КПП, Наименование)
    ТекстЗапроса = 
    "ВЫБРАТЬ Ссылка ИЗ Справочник.Контрагенты ГДЕ Наименование = &Наименование
    |ОБЪЕДИНИТЬ
    |ВЫБРАТЬ Ссылка ИЗ Справочник.Контрагенты ГДЕ ИНН = &ИНН И КПП = &КПП И &ИННЗаполнен";
    
    // ... установка параметров аналогична предыдущему примеру
КонецФункции

Этот подход хорош тем, что платформа 1С может более эффективно использовать индексы для каждой части объединенного запроса по отдельности. Если у вас проиндексированы поля Наименование и ИНН, такой запрос отработает мгновенно даже на огромных справочниках.

Вариант решения №3: Конструкция ВЫБОР в запросе

Проанализируем еще один интересный подход, упомянутый на форуме — использование конструкции ВЫБОР КОГДА.... Это позволяет реализовать гибкую логику прямо в теле запроса, не перегружая код 1С лишними проверками:


Запрос.Текст = 
"ВЫБРАТЬ
|   1
|ИЗ
|   Справочник.Контрагенты КАК Спр
|ГДЕ
|   ВЫБОР
|       КОГДА Спр.Наименование = &Наименование ТОГДА ИСТИНА
|       КОГДА &ИННЗаполнен И Спр.ИНН = &ИНН И Спр.КПП = &КПП ТОГДА ИСТИНА
|       ИНАЧЕ ЛОЖЬ
|   КОНЕЦ";

Такой синтаксис наглядно показывает приоритеты проверки: сначала ищем по названию, затем — по ключевым реквизитам организации.

Важные технические нюансы и рекомендации

При реализации подобных доработок необходимо учитывать несколько критических факторов, которые могут повлиять на корректность данных в будущем:

  1. Индексирование полей. Чтобы поиск дублей не тормозил работу системы при записи каждого нового контрагента, убедитесь, что реквизиты ИНН и Наименование имеют свойство «Индексировать» в метаданных справочника.
  2. Нормализация строк. Перед передачей наименования в запрос, мы рекомендуем очищать его от лишних пробелов в начале и в конце с помощью функции СокрЛП(). Также полезно привести строку к единому регистру или использовать в запросе оператор ПОДОБНО, чтобы избежать дублей вида «ООО Ромашка» и «ооо ромашка».
  3. Особенности физических лиц. Помните, что у индивидуальных предпринимателей и физических лиц ИНН состоит из 12 цифр, а поле КПП отсутствует (пустое). Если ваша проверка жестко требует совпадения КПП, вы можете пропустить дубли по физлицам. В запросе стоит добавить условие, проверяющее длину ИНН или тип контрагента.
  4. Точка вызова проверки. Обработка ОбработкаПроверкиЗаполнения — отличное место для интерактивного взаимодействия с пользователем. Однако, если вы хотите гарантированно избежать дублей при программной загрузке данных (например, из Excel), логику проверки следует продублировать в событии ПередЗаписью на стороне модуля объекта.

Пример реализации в модуле объекта

Посмотрим на итоговый вариант того, как может выглядеть процедура в модуле объекта справочника Контрагенты:


Процедура ОбработкаПроверкиЗаполнения(Отказ, ПроверяемыеРеквизиты)
    
    Если ЭтоНовый() Тогда
        
        Если ЗначениеЗаполнено(Наименование) Тогда
            Если ЕстьВБазеКонтрагент(ИНН, КПП, Наименование) Тогда
                Отказ = Истина;
                Сообщение = Новый СообщениеПользователю;
                Сообщение.Текст = "Внимание! Контрагент с таким наименованием или ИНН/КПП уже существует в системе.";
                Сообщение.Поле = "Наименование";
                Сообщение.Сообщить();
            КонецЕсли;
        КонецЕсли;
        
    КонецЕсли;
    
КонецПроцедуры

Разберем, почему мы проверяем ЭтоНовый(). При редактировании уже существующего контрагента (например, при смене телефона или адреса), система не должна находить «самого себя» как дубль и блокировать запись. Если же проверка требуется и при изменении ИНН, в запрос нужно добавить условие ГДЕ Ссылка <> &ТекущаяСсылка.

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

← На главную