Как записать проводимый документ без проведения, если не пройдены проверки?

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

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

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

Почему стандартный Отказ в ОбработкеПроведения не подходит?

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

  1. ПередЗаписью (в модуле объекта)
  2. ПриЗаписи (в модуле объекта)
  3. ОбработкаПроведения (в модуле объекта)

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

Вывод: чтобы документ остался записанным, но не провелся, нам нужно изменить сам режим записи до того, как начнется транзакция проведения, либо на самом раннем ее этапе.

Решение 1. Изменение параметра РежимЗаписи в ПередЗаписью (Модуль объекта)

Наиболее простое и быстрое решение — перехватить проведение в обработчике ПередЗаписью модуля объекта документа. Вопреки распространенному заблуждению, параметр РежимЗаписи в этой процедуре доступен не только для чтения, но и для записи. Изменив его значение, мы можем перенаправить логику платформы.

Проанализируем пример кода, который решает эту задачу:


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

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

Внимание: Ловушка рассинхронизации данных!

Описанное выше решение отлично работает для новых или еще не проведенных документов. Но что произойдет, если пользователь открыл уже проведенный ранее документ, изменил в нем сумму, нажал «Провести», а проверка на остатки не прошла?

Если мы просто заменим РежимЗаписи на РежимЗаписиДокумента.Запись:

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

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


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

Решение 2. Интерактивная обработка на уровне управляемой формы (Рекомендуемый подход)

С точки зрения хорошего тона разработки (UX/UI), проведение бизнес-проверок непосредственно в модуле объекта без предупреждения пользователя — не самый лучший вариант. Пользователь ожидает либо успешного проведения, либо понятного диалогового окна с выбором действий.

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

В модуле формы перехватим событие ПередЗаписьюНаСервере или выполним интерактивную проверку в событии ПередЗаписью на клиенте:


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

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

Этот способ позволяет избежать лишней нагрузки на СУБД (транзакция проведения даже не стартует на сервере, если проверка не пройдена) и оставляет управление поведением системы в руках пользователя.

Рекомендация по использованию Дополнительных Свойств

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

Например, при записи из формы мы можем поместить туда признак интерактивности:

ЭтотОбъект.ДополнительныеСвойства.Вставить("ИнтерактивнаяЗапись", Истина);

А в модуле объекта анализировать этот параметр, чтобы понимать, нужно ли выводить сообщения или автоматически понижать режим проведения до обычной записи.

← На главную