В процессе разработки на платформе 1С:Предприятие 8 часто возникает задача по обеспечению чистоты данных. Нам необходимо гарантировать, что ни один элемент справочника не будет записан в базу, если он не соответствует заданным бизнес-правилам. При этом проверка должна работать "в одном месте", независимо от того, создается ли элемент пользователем вручную (интерактивно) или загружается/создается программным кодом. Рассмотрим подробнее, как реализовать такую защиту максимально эффективно и надежно.
Первый и самый важный вопрос: где именно разместить код проверки? Существует два основных события, которые срабатывают при записи объекта. Проанализируем их особенности.
Вариант 1: Событие ПередЗаписью (объекта или подписки)
Это наиболее распространенный вариант. Проверка происходит до того, как данные физически попадут в таблицы базы данных. Основные преимущества:
Однако здесь кроется "подвох". В сложных конфигурациях на один и тот же справочник может быть назначено несколько Подписок на событие. Если ваша проверка стоит первой, а после нее другая подписка изменит реквизиты объекта так, что они перестанут соответствовать критериям, "плохой" объект все равно попадет в базу.
Вариант 2: Событие ПриЗаписи
Использование этого события считается более надежным (так называемый "bulletproof" подход). На этом этапе объект уже записан в базу, но транзакция еще не зафиксирована. Если мы установим Отказ = Истина, система выполнит откат (Rollback) транзакции.
Почему это надежнее? Потому что после события ПриЗаписи реквизиты объекта уже точно не изменятся. Если вы хотите гарантировать 100% соблюдение правил, проверка в ПриЗаписи — ваш выбор. Минус здесь один: лишняя нагрузка на сервер из-за записи с последующим откатом.
Чтобы не править модуль каждого конкретного справочника (особенно если это типовая конфигурация), мы будем использовать механизм подписок. Разберем по шагам, как это сделать.
1. Создадим Общий модуль (например, ПроверкиДанныхСервер), в котором разместим процедуру проверки. Важно, чтобы модуль имел флаг "Сервер" и "Вызов сервера".
2. Напишем код проверки. Рассмотрим пример, где мы запрещаем создавать элементы справочника Контрагенты без заполненного ИНН:
Процедура ПроверитьЗаполнениеКонтрагентаПередЗаписью(Источник, Отказ, Замещение) Экспорт
// Проверяем флаг загрузки данных, чтобы не остановить обмен
Если Источник.ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Исключаем проверку для предопределенных элементов
Если Источник.Предопределенный Тогда
Возврат;
КонецЕсли;
// Основная логика критериев
Если ПустаяСтрока(Источник.ИНН) Тогда
Отказ = Истина;
// Сообщаем пользователю о причине отказа
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Невозможно записать контрагента: ИНН должен быть заполнен!";
Сообщение.Поле = "ИНН"; // Подсветит поле на форме
Сообщение.УстановитьДанные(Источник);
Сообщение.Сообщить();
КонецЕсли;
КонецПроцедуры
3. Создадим объект конфигурации Подписка на событие. В качестве источника выберем СправочникОбъект.Контрагенты, событие — ПередЗаписью, а в качестве обработчика укажем нашу процедуру.
Хотя проверка в модуле объекта или подписке сработает всегда, пользователь увидит ошибку только в момент нажатия кнопки "Записать". С точки зрения интерфейса лучше подсвечивать ошибки заранее. Для этого проанализируем событие ОбработкаПроверкиЗаполнения.
В модуле объекта справочника мы можем переопределить стандартную проверку:
Процедура ОбработкаПроверкиЗаполнения(Отказ, ПроверяемыеРеквизиты)
// Добавляем наш реквизит в список обязательных для проверки платформой
Если ПроверяемыеРеквизиты.Найти("ИНН") = Неопределено Тогда
ПроверяемыеРеквизиты.Добавить("ИНН");
КонецЕсли;
КонецПроцедуры
Это позволит платформе автоматически проверять поле ИНН при нажатии кнопки "Записать и закрыть" на форме, еще до начала транзакции записи.
Для крупных проектов мы рекомендуем разделять логику проверки и вызовы. Разберем правильную структуру:
Истина/Ложь).Такой подход позволяет повторно использовать одну и ту же проверку и в подписках, и в формах, и в обработках загрузки данных.
Проанализируем ситуацию с обменом данными. Если вы внедряете жесткий запрет, всегда помните про свойство ОбменДанными.Загрузка. Если в одной базе критерии изменились, а в другой остались старыми, при синхронизации обмен может "встать" колом, так как новые элементы не будут проходить проверку в базе-приемнике.
Посмотрим на использование ДополнительныхСвойств. Бывают случаи, когда программно НУЖНО создать элемент в обход правил (например, служебный узел). В этом случае в коде создания пишем:
НовыйОбъект = Справочники.Контрагенты.СоздатьЭлемент();
НовыйОбъект.ДополнительныеСвойства.Вставить("ПропуститьПроверкуКритериев", Истина);
// ... заполнение ...
НовыйОбъект.Записать();
А в самой проверке добавим условие:
Если Источник.ДополнительныеСвойства.Свойство("ПропуститьПроверкуКритериев")
И Источник.ДополнительныеСвойства.ПропуститьПроверкуКритериев Тогда
Возврат;
КонецЕсли;
Выясним причину выбора конкретного метода: если вам важна производительность и типовой функционал — используйте ПередЗаписью в подписке. Если вам важна абсолютная гарантия неизменности данных после проверки — используйте ПриЗаписи. И не забывайте про СообщениеПользователю с заполнением свойства Поле, чтобы ваши пользователи не гадали, почему система отказывает им в сохранении данных.