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