Как реализовать запрет проведения документа при совпадении даты, номера и ИНН контрагента

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

В процессе разработки и сопровождения систем на базе 1С:Предприятие часто возникает задача контроля уникальности вводимых данных. Одной из классических бизнес-задач является запрет создания или проведения документов-дублей, которые имеют одинаковый номер, дату (в пределах дня или точную) и относятся к одному и тому же контрагенту (идентификация по ИНН). Это особенно актуально для входящих документов от поставщиков, таких как «Поступление товаров и услуг» или «Счет-фактура полученный».

В этой статье мы подробно разберем, как правильно реализовать такой механизм, чтобы он работал быстро, надежно и не вызывал проблем при одновременной работе нескольких пользователей. Мы рассмотрим различные подходы: от простых проверок в модуле объекта до использования подписок на события и оптимизации запросов.

Выбор места для реализации проверки

Первый вопрос, который возникает у разработчика: где именно писать код проверки? Рассмотрим основные варианты, предложенные в обсуждении, и проанализируем их эффективность.

  1. Модуль объекта (событие ПередЗаписью): Это самый простой вариант. Однако, если проверка нужна для нескольких видов документов (например, для поступлений, возвратов и актов), нам придется копировать код в каждый модуль. Это затрудняет поддержку системы.
  2. Подписка на событие: Это наиболее архитектурно верный подход. Мы создаем одну общую подписку на событие ПередЗаписью для всех необходимых типов документов. Процедура-обработчик будет находиться в одном общем модуле, что упрощает отладку и обновление логики.
  3. Модуль набора записей регистра: Как упоминалось в обсуждении, проверку можно делать на уровне регистров, куда документ вносит записи. Это гарантирует жесткий контроль, но может быть неудобно с точки зрения информирования пользователя, так как ошибка возникнет уже на этапе записи движений.

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

Реализация проверки через подписку на событие

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

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


Процедура ПроверитьДублиДокументаПередЗаписью(Источник, Отказ, РежимЗаписи, РежимПроведения) Экспорт

    // Проверяем только при записи или проведении нового/измененного документа
    Если Источник.ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;

    // Подготавливаем запрос для поиска совпадений
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ ПЕРВЫЕ 1
        |	Т.Ссылка КАК Ссылка
        |ИЗ
        |	Документ.ПоступлениеТоваровУслуг КАК Т
        |ГДЕ
        |	Т.Номер = &Номер
        |	И Т.Дата МЕЖДУ &НачалоДня И &КонецДня
        |	И Т.Контрагент.ИНН = &ИНН
        |	И Т.Ссылка <> &ТекущаяСсылка";

    Запрос.УстановитьПараметр("Номер", Источник.Номер);
    Запрос.УстановитьПараметр("НачалоДня", НачалоДня(Источник.Дата));
    Запрос.УстановитьПараметр("КонецДня", КонецДня(Источник.Дата));
    Запрос.УстановитьПараметр("ИНН", Источник.Контрагент.ИНН);
    Запрос.УстановитьПараметр("ТекущаяСсылка", Источник.Ссылка);

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

КонецПроцедуры

Оптимизация производительности: работа с индексами

Рассмотрим ситуацию, когда база данных содержит сотни тысяч документов. Поиск по Т.Контрагент.ИНН может работать медленно, так как поле ИНН находится в справочнике Контрагенты, и СУБД придется выполнять соединение таблиц (JOIN). Для ускорения работы мы рекомендуем следующее:

Решение проблемы "состояния гонки" через управляемые блокировки

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

Чтобы этого избежать, необходимо использовать механизм Управляемых блокировок. Мы должны установить исключительную блокировку на комбинацию данных, которую проверяем. Выясним, как это сделать программно:


Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Документ.ПоступлениеТоваровУслуг");
ЭлементБлокировки.УстановитьОбласть("Номер", Источник.Номер);
// Дата блокируется по периоду
ЭлементБлокировки.ИспользоватьИзЛевогоСоединения("Контрагент", "Контрагент");
Блокировка.Заблокировать();

Важный нюанс: Блокировка должна устанавливаться внутри транзакции (в событии ПередЗаписью транзакция уже открыта). Второй пользователь, пытающийся записать такой же документ, будет ждать, пока завершится транзакция первого, после чего его запрос на проверку дублей увидит уже записанный документ и выдаст ошибку.

Улучшение интерфейса (UX)

Согласимся, что пользователю неприятно заполнить длинный документ из 50 строк и только в самом конце при нажатии кнопки "Провести" узнать, что такой документ уже существует. Посмотрим, как можно улучшить взаимодействие:

  1. Проверка при изменении: Добавим вызов аналогичной проверки (но без блокировки транзакции) в событие ПриИзменении для полей Номер и Контрагент непосредственно в форме документа.
  2. Асинхронность: Используйте асинхронные вызовы сервера, чтобы проверка не "подвешивала" интерфейс пользователя при вводе данных.
  3. Предупреждение вместо запрета: В некоторых случаях (в зависимости от настроек прав доступа) можно не блокировать запись, а выводить окно с вопросом: "Документ с таким номером и ИНН уже существует. Вы уверены, что хотите продолжить?".

Использование регистра сведений для контроля уникальности

Еще одно продвинутое решение — создание специального регистра сведений, например, УникальныеКлючиДокументов. В качестве измерений этого регистра мы выберем ИНН, Номер и Дата (тип Дата, без времени). При проведении документа мы пытаемся записать строку в этот регистр.

Поскольку платформа 1С автоматически контролирует уникальность записей в регистре сведений по набору измерений, попытка записи дубля вызовет исключение на уровне СУБД. Это считается "архитектурно чистым" решением, так как оно перекладывает ответственность за уникальность на механизмы базы данных и исключает ошибки в логике запросов ГДЕ Ссылка <> &ТекущаяСсылка.

Разберем преимущества этого метода:

Учет префиксов и РИБ

Проанализируем ситуацию с распределенными базами данных (РИБ). В 1С к номеру документа часто добавляется префикс организации или узла (например, "МСК-00001"). Если в базе "МСК" и "СПБ" создаются документы с одинаковым входящим номером, они могут иметь разные внутренние номера 1С.

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

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

← На главную