Как сохранить в базе сообщение о причине отказа в проведении документа

Программист 1С v8.3 (Обычные формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

В процессе разработки в среде 1С программисты часто сталкиваются с важной архитектурной особенностью платформы — транзакционностью. Типичный пример: при проведении документа (например, РеализацияТоваровУслуг) выполняется проверка контрагента на превышение лимита долга или просрочку платежей. Если проверка не пройдена, программа должна заблокировать проведение документа и одновременно сохранить подробный отчет об ошибке в базу данных (например, в свойства документа или специальный регистр сведений), чтобы менеджер или супервайзер мог проанализировать причину отказа.

Однако здесь разработчик упирается в логику работы СУБД: если в процедуре ОбработкаПроведения установить параметр Отказ = Истина, система инициирует отмену всей транзакции. Все изменения, которые мы пытались записать в базу данных в рамках этой транзакции (включая запись ошибок в регистры сведений или изменение реквизитов документа), безвозвратно откатываются. В итоге проведение заблокировано, но и причина отказа нигде не зафиксирована.

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

Вариант 1. Проверка в форме документа (Быстрое, но небезопасное решение)

Самое очевидное решение, к которому часто приходят начинающие разработчики, — перенести алгоритм проверки из модуля объекта в модуль формы документа, перехватив событие перед записью.

Выясним, как это работает. Когда пользователь нажимает кнопку "ОК" или "Провести", управление сначала передается в модуль формы. Мы можем выполнить проверку лимитов прямо там. Если проверка не пройдена, мы записываем информацию об ошибке в независимый регистр сведений (или свойства документа) в рамках отдельной транзакции записи, а затем отменяем проведение еще до того, как начнется основная транзакция проведения объекта.

Рассмотрим пример реализации в модуле формы документа:


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

Почему этот подход считается плохой практикой?

Несмотря на то, что этот метод решает задачу "в лоб" при работе пользователя в интерфейсе, он обладает критическими недостатками:

  1. Обход логики при программной работе: Если документ будет проводиться программно — например, через групповое перепроведение документов, при обмене данными (синхронизации), загрузке заказов из мобильного приложения или интернет-магазина, через веб-сервисы или фоновые задания — события формы вообще не будут вызваны. В результате недобросовестный покупатель получит отгрузку без каких-либо проверок.
  2. Нарушение стандартов разработки 1С: Бизнес-логика системы и жесткие контроли проведения должны находиться исключительно на уровне модуля объекта или подписок на события. Модуль формы отвечает только за интерактивное взаимодействие с пользователем.

Поэтому данный метод подходит только для простых локальных баз с исключительно ручным вводом документов, но крайне не рекомендуется для промышленных решений.

Вариант 2. Использование фоновых заданий (Профессиональный метод)

Чтобы обойти транзакционный откат, нам необходимо сделать запись в базу данных в рамках другого, независимого сеанса связи с СУБД. В современной платформе 1С для этого идеально подходят фоновые задания.

Проанализируем этот метод по шагам. Когда в процедуре ОбработкаПроведения в модуле объекта мы понимаем, что проверку пройти не удалось, мы не просто пишем отказ, а программно запускаем фоновое задание, передавая ему параметры нашей ошибки. Фоновое задание запускается на сервере в отдельном потоке (сеансе). Оно имеет свою собственную транзакцию, которая успешно фиксируется. После этого основная транзакция проведения документа завершается ошибкой (Отказ = Истина) и откатывается, но запись, сделанная фоновым заданием, остается в базе данных!

Рассмотрим программную реализацию. В модуле объекта документа пишем следующий код:


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

В общем серверном модуле ОбщегоНазначенияСервер (у которого установлены флаги "Сервер" и "Вызов сервера") размещаем процедуру, которая будет выполняться фоновым заданием:


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

Этот способ является наиболее надежным и современным. Он гарантирует сохранение данных независимо от того, как именно проводится документ (вручную пользователем или программно через фоновые процессы).

Вариант 3. Связка «Журнал регистрации + Регламентное задание»

Этот метод часто незаслуженно обходят стороной, считая его сложным в реализации. Однако у него есть колоссальное преимущество — он работает максимально быстро и вообще не нагружает транзакцию проведения дополнительными блокировками баз данных.

Суть механизма заключается в следующем:

  1. При неудачной проверке в модуле объекта мы делаем запись в системный Журнал регистрации 1С. Журнал регистрации пишется на уровне платформы в отдельные файлы (или таблицы) и никак не зависит от транзакции СУБД. Даже если транзакция полностью откатится, запись в Журнале регистрации гарантированно сохранится.
  2. В конфигурации создается регламентное задание, которое запускается по расписанию (например, каждые 5-10 минут) и считывает новые записи из Журнала регистрации, относящиеся к отказам проведения.
  3. Найденные данные регламентное задание переносит в обычный Регистр сведений, по которому менеджеры и супервайзеры могут легко и быстро строить отчеты с помощью Системы компоновки данных (СКД).

Посмотрим на пример кода записи в Журнал регистрации в модуле объекта документа:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    РезультатПроверки = ПроверитьЛимитыДолга();
    
    Если РезультатПроверки.ЕстьОшибки Тогда
        
        // Формируем структуру данных для последующего разбора регламентным заданием
        СтруктураДанных = Новый Структура;
        СтруктураДанных.Вставить("ДокументСсылка", ЭтотОбъект.Ссылка);
        СтруктураДанных.Вставить("ПричинаОтказа", РезультатПроверки.ТекстОшибки);
        
        // Преобразуем структуру в XML или JSON для надежного сохранения в лог
        ЗаписьXML = Новый ЗаписьXML;
        ЗаписьXML.УстановитьСтроку();
        ЗаписатьXML(ЗаписьXML, СтруктураДанных);
        СтрокаДанных = ЗаписьXML.Закрыть();
        
        // Пишем лог, который никогда не откатится транзакцией
        ЗаписатьВЖурналРегистрации("Документы.РеализацияТоваровУслуг.ОтказПроведения",
            УровеньЖурналаРегистрации.Предупреждение,
            ЭтотОбъект.Метаданные(),
            ЭтотОбъект.Ссылка,
            СтрокаДанных);
            
        Отказ = Истина;
        Возврат;
        
    КонецЕсли;
    
КонецПроцедуры

Затем регламентное задание считывает данные с помощью метода ВыгрузитьЖурналРегистрации() по фильтру события "Документы.РеализацияТоваровУслуг.ОтказПроведения", десериализует полученные строки и аккуратно записывает их в предназначенный для отчетов регистр сведений.

Вариант 4. Изменение бизнес-логики через "Мягкое проведение" и статусы

В крупных учетных системах (таких как 1С:ERP или 1С:Комплексная автоматизация) разработчики часто отказываются от концепции "жесткого" запрета проведения на уровне СУБД. Вместо этого применяется концепция "мягкого проведения" или статусной модели.

Посмотрим, как устроен этот процесс:

  1. Документ проводится всегда! Это значит, что Отказ = Ложь, транзакция успешно завершается на уровне базы данных, и все изменения сохраняются.
  2. Но у документа добавляется обязательный реквизит Статус (например, со значениями: "Черновик", "На согласовании", "Заблокирован", "К отгрузке").
  3. При проведении документа выполняется проверка лимитов долга. Если проверка провалена, документ автоматически переводится в статус "Заблокирован", а в специальную табличную часть или регистр сведений записывается детальная причина блокировки.
  4. В коде проведения прописывается строгое условие: если статус документа равен "Заблокирован" или "На согласовании", то движения по регистрам остатков товаров и взаиморасчетов вообще не формируются (наборы записей движений остаются пустыми).

Разберем пример реализации такой бизнес-логики в модуле объекта:


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

Преимущества данного метода:

Резюме и рекомендации

Мы рассмотрели различные варианты решения проблемы транзакционного отката в 1С при блокировке проведения документов. Подведем итог, какой метод выбрать для вашей задачи:

← На главную