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