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