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