Почему 1С игнорирует ошибки заполнения при программном проведении документов и как это исправить?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При автоматизации бизнес-процессов, интеграции различных систем или переносе данных между базами 1С через COM-соединение, Web-сервисы или XML/JSON-файлы разработчики часто сталкиваются с неожиданным поведением платформы. Документ, который при ручном вводе выдает массу предупреждений и наотрез отказывается записываться или проводиться из-за незаполненных реквизитов, при программном создании «тихо» и успешно проводится, оставляя базу данных с некорректными или пустыми аналитическими разрезами.

В этой статье мы подробно разберем, почему так происходит, изучим внутреннюю архитектуру платформы «1С:Предприятие 8», вскроем опасную ошибку из популярных советов на форумах и научимся правильно и гарантированно валидировать данные при программной работе с объектами.

Архитектурная причина: почему платформа «прощает» программные ошибки?

Для начала выясним причину такого поведения платформы. В свойствах реквизитов любого объекта конфигурации (справочника, документа, процесса) есть свойство «Проверка заполнения», для которого можно установить значение «Выдавать ошибку».

Важно понимать: этот механизм ориентирован исключительно на интерактивную работу пользователя в интерфейсе (через формы управляемого или обычного приложения). Когда пользователь нажимает кнопку «Записать» или «Провести» на форме документа, платформа автоматически инициирует проверку заполнения всех обязательных полей формы и связанных с ними реквизитов объекта.

Если же мы работаем с объектом программно — например, создаем его через СоздатьДокумент(), заполняем реквизиты и вызываем метод Записать(РежимЗаписиДокумента.Проведение), — платформа намеренно игнорирует свойство «Проверка заполнения». Это проектное решение разработчиков платформы 1С, обусловленное следующими факторами:

  1. Обеспечение работы обменов и фонового импорта. При синхронизации данных или миграции со старых систем документы часто должны быть записаны «как есть», даже если они заполнены не полностью. Блокировка записи из-за отсутствия второстепенного реквизита остановила бы весь процесс интеграции.
  2. Производительность. Интерактивные проверки могут требовать выполнения тяжелых запросов к базе данных или сложных алгоритмов. При пакетной обработке тысяч документов такие проверки критически замедлили бы выполнение операции.

Таким образом, по умолчанию программная запись объекта считается операцией доверенной, где вся ответственность за корректность заполнения полей целиком и полностью ложится на плечи разработчика, пишущего этот код.

Анализ скрытой ошибки в советах с форумов

Очень часто на форумах в ответ на подобный вопрос разработчики получают лаконичный совет: «Используй метод ПроверитьЗаполнение() перед записью». И приводят код, похожий на этот:


// Опасный код с логической ошибкой!
Об.Заполнить(Неопределено);
// Заполнение реквизитов...
Об.ПроверитьЗаполнение(); 
Об.Записать(РежимЗаписиДокумента.Проведение);

На первый взгляд кажется, что проблема решена — метод вызван, система отработала без ошибок. Однако здесь кроется серьезная логическая ошибка, способная привести к нарушению целостности данных. Давайте разберем по шагам, как на самом деле работает метод ПроверитьЗаполнение():

  1. Метод ПроверитьЗаполнение() проверяет реквизиты объекта на заполненность согласно правилам, заданным в конфигураторе, и алгоритмам в модуле объекта.
  2. Он возвращает булево значение: Истина, если все заполнено корректно, и Ложь, если обнаружены пустые обязательные реквизиты.
  3. Метод не вызывает исключение самостоятельно и никак не прерывает выполнение транзакции или дальнейшего кода. Он лишь регистрирует сообщения об ошибках в системной коллекции сообщений.

Если выполнить вышеуказанный код, то метод ПроверитьЗаполнение() вернет значение Ложь, выведет сообщения в окно сообщений (или в лог COM-соединения), но выполнение кода продолжится! Строка Об.Записать(РежимЗаписиДокумента.Проведение) благополучно запишет и проведет документ со всеми его пустыми реквизитами.

Правильное использование метода ПроверитьЗаполнение()

Чтобы проверка заполнения действительно заблокировала запись некорректного документа, мы обязаны обработать возвращаемое значение метода и принудительно прервать выполнение алгоритма с помощью вызова исключения. Рассмотрим правильный шаблон написания такого кода:


// Правильный вариант программной проверки перед записью/проведением
Если Не Об.ПроверитьЗаполнение() Тогда
    // Если проверка не пройдена, генерируем исключение для отката транзакции
    ВызватьИсключение "Документ не прошел проверку заполнения и не может быть записан!";
КонецЕсли;

Попытка
    Об.Записать(РежимЗаписиДокумента.Проведение);
Исключение
    // Обработка ошибки проведения, например, запись в режиме черновика
    Об.Записать(РежимЗаписиДокумента.Запись);
КонецПопытки;

При таком подходе, если метод ПроверитьЗаполнение() обнаружит ошибку, выполнение будет немедленно остановлено оператором ВызватьИсключение, транзакция откатится, и пустой документ не попадет в базу данных.

Как работает ПроверитьЗаполнение() «под капотом»

Проанализируем, что происходит на уровне платформы при вызове метода ПроверитьЗаполнение(). Алгоритм платформы отрабатывает в два этапа:

  1. Проверка метаданных. Платформа сканирует все реквизиты объекта и табличных частей, у которых в конфигураторе установлено свойство «Проверка заполнения = Выдавать ошибку», и проверяет их на наличие пустых значений (для ссылочных типов — пустая ссылка, для чисел — 0, для строк — пустая строка и т.д.).
  2. Вызов обработчика модуля. Автоматически вызывается обработчик события ОбработкаПроверкиЗаполнения, расположенный в модуле самого записываемого объекта.

Разработчик конфигурации может использовать этот обработчик для динамического управления проверками. Посмотрим на классический пример реализации этого обработчика в модуле объекта:


Процедура ОбработкаПроверкиЗаполнения(Отказ, ПроверяемыеРеквизиты)
    
    // Пример 1: Исключаем реквизит из проверки при определенных условиях
    Если Не ПолучитьФункциональнуюОпцию("ИспользоватьСклады") Тогда
        // Удаляем склад из обязательных полей, если опция отключена
        Индекс = ПроверяемыеРеквизиты.Найти("Склад");
        Если Индекс <> Неопределено Тогда
            ПроверяемыеРеквизиты.Удалить(Индекс);
        КонецЕсли;
    КонецЕсли;
    
    // Пример 2: Добавляем реквизит в проверку динамически
    Если СтатусДокумента = Перечисления.СтатусыДокументов.Согласован Тогда
        ПроверяемыеРеквизиты.Добавить("ОтветственныйЗаСогласование");
    КонецЕсли;

КонецПроцедуры

Через параметр ПроверяемыеРеквизиты (который представляет собой массив строк с именами реквизитов) мы можем гибко управлять поведением платформенной проверки как при интерактивной работе, так и при вызове метода ПроверитьЗаполнение() программно.

Где должны находиться «железные» проверки данных?

Иногда свойство «Проверка заполнения» в метаданных оказывается недостаточным. Например, если незаполненность какого-либо аналитического разреза (договора, статьи затрат, номенклатурной группы) может привести к критическим ошибкам в учете и «расползанию» итогов по регистрам накопления или бухгалтерии.

Если документ **ни при каких обстоятельствах** (включая программный импорт, обмены и внешние обработки) не должен проводиться с некорректными данными, проверки необходимо выносить на уровень бизнес-логики самого объекта. Для этого предназначены предопределенные события в модуле объекта:

Рассмотрим пример надежной программной проверки в процедуре проведения модуля документа:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // Жесткая проверка бизнес-логики, которую невозможно обойти программно
    Если Не ЗначениеЗаполнено(ДоговорКонтрагента) Тогда
        Отказ = Истина;
        Сообщение = Новый СообщениеПользователю;
        Сообщение.Текст = "Проведение невозможно! Не заполнен договор контрагента.";
        Сообщение.Поле = "ДоговорКонтрагента";
        Сообщение.Сообщить();
    КонецЕсли;
    
    Если Отказ Тогда
        Возврат; // Прерываем проведение, платформа автоматически откатит транзакцию
    КонецЕсли;
    
    // Далее идет формирование движений документа...
    
КонецПроцедуры

Если в этих процедурах присвоить параметру Отказ = Истина, платформа полностью остановит запись или проведение, отменит начатую транзакцию базы данных и выдаст ошибку как интерактивному пользователю, так и внешней программе, вызвавшей метод Записать(). Обойти такую проверку обычными методами записи невозможно.

Влияние свойства ОбменДанными.Загрузка

Нельзя не упомянуть еще один важный нюанс программной работы с объектами — свойство ОбменДанными.Загрузка. Часто при импорте данных разработчики устанавливают этот флаг в значение Истина:

Об.ОбменДанными.Загрузка = Истина;

Этот флаг сообщает платформе и логике конфигурации, что происходит перенос готовых данных из другой системы. При этом:

  1. Большинство стандартных проверок в обработчиках ПередЗаписью и ПриЗаписи в типовых конфигурациях принудительно отключаются (стоит условие Если ОбменДанными.Загрузка Тогда Возврат; КонецЕсли;).
  2. Проведение документа с флагом Загрузка = Истина стандартными средствами платформы заблокировано, либо оно выполняется без формирования реальных движений в регистрах (так как предполагается, что движения будут перенесены отдельно в виде наборов записей).

Поэтому, если ваша задача — не просто перенести «сырые» данные, а полноценно провести документы в целевой базе с формированием всех движений и проверкой бизнес-логики, не устанавливайте флаг ОбменДанными.Загрузка = Истина, либо делайте это осознанно и контролируйте целостность данных программными вызовами ПроверитьЗаполнение().

Резюме и чеклист для разработчика

Для того чтобы ваши программно создаваемые документы никогда не приводили к хаосу в учете и пустой аналитике, придерживайтесь простых правил:

  1. Помните, что обычный вызов метода Записать(РежимЗаписиДокумента.Проведение) полностью игнорирует настройки «Проверка заполнения = Выдавать ошибку» в конфигураторе.
  2. Если вам нужно выполнить стандартные интерактивные проверки программно, всегда используйте метод ПроверитьЗаполнение(), но обязательно анализируйте возвращаемое им булево значение и вызывайте исключение в случае неудачи.
  3. Для критически важных проверок, которые не должны отключаться ни при каких обстоятельствах, пишите программный код непосредственно в процедурах ПередЗаписью и ОбработкаПроведения модуля самого объекта, используя параметр Отказ = Истина.
  4. Будьте аккуратны со свойством ОбменДанными.Загрузка = Истина: оно отключает прикладные проверки в большинстве типовых конфигураций.
← На главную