Как сохранить результаты вычислений при отмене транзакции проведения документа в 1С

Программист 1С v8.3 (Обычные формы) Управленческий учет Промышленность, строительство и АПК
← На главную

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

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

Причины отката данных и особенности транзакций в 1С

Проанализируем базовый механизм работы платформы: событие ОбработкаПроведения всегда исполняется внутри неявной транзакции записи базы данных. Согласно принципам транзакционности (ACID), откат транзакции возвращает базу данных в состояние, строго предшествовавшее началу операции проведения. Это означает, что:

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

Способ 1. Передача данных через ДополнительныеСвойства документа

Рассмотрим один из самых простых и эффективных способов передачи накопленной информации из процедуры проведения обратно в вызывающий программный код. Коллекция ДополнительныеСвойства принадлежит экземпляру объекта, находящемуся в оперативной памяти сервера, и изменения этой структуры не откатываются механизмом транзакций СУБД.

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


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

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

Теперь разберем, как прочитать эти данные в вызывающем коде (например, в обработке группового проведения или регламентном задании):


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

Обратите внимание: при интерактивном проведении документа из формы этот объект на сервере уничтожается после завершения вызова, поэтому для интерактивной работы подходят другие способы.

Способ 2. Использование общих модулей с повторным использованием значений

Рассмотрим вариант кэширования сообщений и ошибок в оперативной памяти сеанса с помощью механизма модулей с повторным использованием возвращаемых значений. Кэш таких модулей не зависит от состояния транзакций СУБД.

Создадим общий модуль с признаком Повторное использование возвращаемых значений (на время сеанса или вызова) и объявим в нем экспортируемую функцию:


Функция ПолучитьБуферОшибок() Экспорт
    Возврат Новый Массив;
КонецФункции

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


БуферОшибок = МодульПовтИсп.ПолучитьБуферОшибок();
БуферОшибок.Добавить(Новый Структура("Номенклатура, Дефицит", НоменклатураСсылка, ТребуемоеКоличество - Остаток));
Отказ = Истина;

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

Способ 3. Использование механизма СообщениеПользователю

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


// Внутри ОбработкаПроведения:
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "[ДАННЫЕ_ДЕФИЦИТА] " + ТекстДанных;
Сообщение.Сообщить();
Отказ = Истина;

В вызывающем коде вне транзакции извлекаем все сообщения вызовом специального метода платформы:


МассивСообщений = ПолучитьСообщенияПользователю(Истина);
Для Каждого Сообщение Из МассивСообщений Цикл
    Если СтрНачинаетсяС(Сообщение.Текст, "[ДАННЫЕ_ДЕФИЦИТА]") Тогда
        // Обрабатываем и сохраняем данные в БД вне транзакции
    КонецЕсли;
КонецЦикла;

Способ 4. Временное хранилище платформы

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


// Помещаем данные во временное хранилище по известному адресу
АдресХранилища = ПоместитьВоВременноеХранилище(ТаблицаДефицита, УникальныйИдентификатор);

После отката транзакции данные по полученному адресу можно извлечь методом ПолучитьИзВременногоХранилища(АдресХранилища) и записать в базу данных отдельным независимым вызовом.

Способ 5. Асинхронный запуск фонового задания

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


ПараметрыЗадания = Новый Массив;
ПараметрыЗадания.Добавить(СтруктураПараметров);

ФоновыеЗадания.Выполнить("РегламентныеПроцедуры.ЗаписатьЛогДефицита", ПараметрыЗадания);

Фоновое задание запускается в отдельном сеансе со своей собственной независимой транзакцией и успешно фиксирует необходимые записи в регистрах сведений.

Архитектурные рекомендации

Проанализируем архитектурную сторону проблемы: постоянная отмена транзакций при проведении создает избыточную нагрузку на СУБД и приводит к удержанию блокировок. Чтобы оптимизировать систему, рекомендуется:

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