Как разрешить пометку на удаление при проверке дублей в справочниках 1С?

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

При разработке конфигураций на платформе «1С:Предприятие 8» часто возникает задача контроля уникальности данных. Типичный сценарий: необходимо запретить пользователям создавать элементы справочника с одинаковыми наименованиями. Мы пишем код проверки, размещаем его в модуле объекта и на первый взгляд всё работает отлично. Однако вскоре пользователи сталкиваются с проблемой: при попытке пометить элемент на удаление система выдает ошибку «Обнаружен дубль!» и полностью блокирует операцию.

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

Выясняем причину проблемы: почему блокируется пометка на удаление?

Для начала проанализируем, что происходит на платформенном уровне, когда пользователь нажимает кнопку «Пометить на удаление». Многие начинающие разработчики ошибочно полагают, что это отдельное действие, не связанное с обычной записью. На самом деле, пометка на удаление — это стандартная операция перезаписи объекта, при которой меняется значение системного реквизита ПометкаУдаления на Истина.

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

Шаг 1. Перенос проверки в обработчик «ПередЗаписью»

В обсуждениях на форумах часто можно встретить код проверки, расположенный в процедуре ПриЗаписи. Это архитектурная ошибка. Рассмотрим подробнее, почему проверку уникальности следует делать исключительно в событии ПередЗаписью модуля объекта:

  1. Экономия ресурсов базы данных: В обработчике ПередЗаписью проверка выполняется до того, как начнется транзакция записи данных в таблицы СУБД. Если проверка не пройдена, мы сразу прерываем процесс. Это предотвращает лишние транзакционные блокировки таблиц базы данных и экономит время сервера.
  2. Доступ к исходному состоянию: В процедуре ПередЗаписью через обращение к свойству Ссылка (ЭтотОбъект.Ссылка) мы всё еще можем получить данные объекта в том виде, в каком они сохранены в базе на текущий момент (до записи), и сравнить их с новыми значениями в оперативной памяти (в свойствах ЭтотОбъект). Это позволяет точно определить, изменились ли ключевые реквизиты.

Шаг 2. Игнорирование проверки при установке пометки на удаление

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

Давайте посмотрим, как реализовать это условие в самом начале процедуры проверки в модуле объекта:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    // Если идет пометка на удаление, проверку уникальности выполнять не нужно
    Если ЭтотОбъект.ПометкаУдаления Тогда
        Возврат;
    КонецЕсли;
    
КонецПроцедуры

Если свойство ПометкаУдаления равно Истина, выполнение процедуры немедленно прекращается, запись разрешается, и отказ не устанавливается. Пользователь беспрепятственно пометит элемент на удаление.

Шаг 3. Исключение помеченных на удаление элементов из поиска дублей

Разберем еще один критически важный нюанс, который часто упускают из виду. Что произойдет, если пользователь попытается создать новый элемент с наименованием, которое уже есть в базе, но у старого элемента установлена пометка на удаление?

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

Проанализируем пример правильного текста запроса для проверки дублей наименований:


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

Запрос.УстановитьПараметр("Наименование", ЭтотОбъект.Наименование);
Запрос.УстановитьПараметр("Ссылка", ЭтотОбъект.Ссылка);

Обратите внимание на три ключевых условия в секции ГДЕ:

  1. Мы ищем совпадение по наименованию (Номенклатура.Наименование = &Наименование).
  2. Мы обязательно исключаем текущую ссылку (Номенклатура.Ссылка <> &Ссылка). Это необходимо, чтобы при обычном переименовании или перезаписи существующего элемента система не находила «дублем» саму себя.
  3. Мы игнорируем элементы, помеченные на удаление (И НЕ Номенклатура.ПометкаУдаления). Это позволяет повторно использовать наименования удаленных объектов.

Шаг 4. Обязательный учет обмена данными (Загрузка)

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

Для этого в самом начале обработчика ПередЗаписью обязательно нужно проверять свойство ОбменДанными.Загрузка:


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

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

Шаг 5. Интерактивная запись против программной (Использование ДополнительныхСвойств)

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

Если проверка написана в модуле объекта, она сработает при любом вызове метода Записать(). Чтобы обойти это ограничение и сделать проверку гибкой, мы можем задействовать коллекцию ДополнительныеСвойства объекта.

Давайте посмотрим, как модифицировать код в модуле объекта:


Если ЭтотОбъект.ДополнительныеСвойства.Свойство("РазрешитьДубли") 
   И ЭтотОбъект.ДополнительныеСвойства.РазрешитьДубли Тогда
    Возврат;
КонецЕсли;

Теперь, если стороннему коду или внешней обработке необходимо принудительно записать элемент-дубль в обход стандартной проверки, разработчику достаточно написать простой код перед вызовом записи:


НовыйЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
НовыйЭлемент.Наименование = "Дублирующийся товар";
// Устанавливаем флаг разрешения записи дублей
НовыйЭлемент.ДополнительныеСвойства.Вставить("РазрешитьДубли", Истина);
НовыйЭлемент.Записать();

Шаг 6. Корректный вывод сообщений об ошибке пользователю

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

Для корректного уведомления пользователя в управляемом интерфейсе следует использовать объект СообщениеПользователю. Это позволит не просто вывести текст, но и визуально привязать ошибку к конкретному полю формы:


Если ДублиНайдены Тогда
    Сообщение = Новый СообщениеПользователю;
    Сообщение.Текст = "Элемент с таким наименованием уже существует в базе данных!";
    Сообщение.Поле = "Наименование";
    Сообщение.УстановитьДанные(ЭтотОбъект);
    Сообщение.Сообщить();

    Отказ = Истина;
КонецЕсли;

Благодаря использованию свойства Поле и метода УстановитьДанные(), платформа не просто покажет всплывающее окно, но и подсветит красной рамкой поле «Наименование» прямо на форме элемента справочника. Это значительно упрощает понимание ошибки пользователем.

Итоговый шаблон кода для модуля объекта справочника

Давайте объединим все рассмотренные шаги в единый готовый шаблон, который вы можете перенести в модуль своего справочника (например, «Номенклатура» или «Контрагенты»):


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    // 1. Игнорируем проверку при загрузке данных
    Если ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;
    
    // 2. Игнорируем проверку при пометке на удаление
    Если ЭтотОбъект.ПометкаУдаления Тогда
        Возврат;
    КонецЕсли;
    
    // 3. Проверяем наличие флага принудительного обхода проверки
    Если ЭтотОбъект.ДополнительныеСвойства.Свойство("РазрешитьДубли") 
       И ЭтотОбъект.ДополнительныеСвойства.РазрешитьДубли Тогда
        Возврат;
    КонецЕсли;
    
    // 4. Поиск дубликатов в базе данных
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ ПЕРВЫЕ 1
        |	Номенклатура.Ссылка КАК Ссылка
        |ИЗ
        |	Справочник.Номенклатура КАК Номенклатура
        |ГДЕ
        |	Номенклатура.Наименование = &Наименование
        |	И Номенклатура.Ссылка <> &Ссылка
        |	И НЕ Номенклатура.ПометкаУдаления";
    
    Запрос.УстановитьПараметр("Наименование", СокрЛП(ЭтотОбъект.Наименование));
    Запрос.УстановитьПараметр("Ссылка", ЭтотОбъект.Ссылка);
    
    РезультатЗапроса = Запрос.Выполнить();
    
    Если НЕ РезультатЗапроса.Пустой() Тогда
        
        // 5. Вывод сообщения и отмена транзакции записи
        Сообщение = Новый СообщениеПользователю;
        Сообщение.Текст = "Элемент с наименованием """ + ЭтотОбъект.Наименование + """ уже зарегистрирован в базе данных!";
        Сообщение.Поле = "Наименование";
        Сообщение.УстановитьДанные(ЭтотОбъект);
        Сообщение.Сообщить();
        
        Отказ = Истина;
        
    КонецЕсли;
    
КонецПроцедуры

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

← На главную