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