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