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