При проведении документов в платформе 1С:Предприятие часто возникает ситуация, когда в обработчике проведения вычисляются важные данные (например, список дефицитных материалов, расчет себестоимости или детализированные ошибки контроля остатков), но из-за нехватки ресурсов проведение отменяется установкой флага Отказ = Истина. При этом срабатывает откат транзакции СУБД, и любые записи, сделанные внутри транзакции в регистры или справочники, полностью аннулируются.
Разберем подробно, почему это происходит и какими способами можно сохранить или передать наружу вычисленные данные в случае отмены транзакции проведения.
Проанализируем базовый механизм работы платформы: событие ОбработкаПроведения всегда исполняется внутри неявной транзакции записи базы данных. Согласно принципам транзакционности (ACID), откат транзакции возвращает базу данных в состояние, строго предшествовавшее началу операции проведения. Это означает, что:
Рассмотрим один из самых простых и эффективных способов передачи накопленной информации из процедуры проведения обратно в вызывающий программный код. Коллекция ДополнительныеСвойства принадлежит экземпляру объекта, находящемуся в оперативной памяти сервера, и изменения этой структуры не откатываются механизмом транзакций СУБД.
Посмотрим на практическую реализацию. В обработчике проведения заполняем структуру дополнительных свойств:
Процедура ОбработкаПроведения(Отказ, Режим)
// Выполняем расчет потребностей и проверку остатков
МассивНедостающихМатериалов = Новый Массив;
// ... Алгоритм выявления нехватки материалов ...
// Допустим, обнаружен дефицит:
МассивНедостающихМатериалов.Добавить("Металлопрокат 10мм: не хватает 5 шт.");
Если МассивНедостающихМатериалов.Количество() > 0 Тогда
// Сохраняем вычисленные данные в объект
ЭтотОбъект.ДополнительныеСвойства.Вставить("ОшибкиПроведения", МассивНедостающихМатериалов);
Отказ = Истина;
Возврат;
КонецЕсли;
КонецПроцедуры
Теперь разберем, как прочитать эти данные в вызывающем коде (например, в обработке группового проведения или регламентном задании):
ДокументОбъект = ВыбраннаяСсылка.ПолучитьОбъект();
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
// В случае возникновения исключения или отказа анализируем ДополнительныеСвойства
Если ДокументОбъект.ДополнительныеСвойства.Свойство("ОшибкиПроведения") Тогда
МассивОшибок = ДокументОбъект.ДополнительныеСвойства.ОшибкиПроведения;
Для Каждого СтрСообщения Из МассивОшибок Цикл
// Сохраняем данные вне транзакции или выводим пользователю
КонецЦикла;
КонецЕсли;
КонецПопытки;
Обратите внимание: при интерактивном проведении документа из формы этот объект на сервере уничтожается после завершения вызова, поэтому для интерактивной работы подходят другие способы.
Рассмотрим вариант кэширования сообщений и ошибок в оперативной памяти сеанса с помощью механизма модулей с повторным использованием возвращаемых значений. Кэш таких модулей не зависит от состояния транзакций СУБД.
Создадим общий модуль с признаком Повторное использование возвращаемых значений (на время сеанса или вызова) и объявим в нем экспортируемую функцию:
Функция ПолучитьБуферОшибок() Экспорт
Возврат Новый Массив;
КонецФункции
В процессе проведения при возникновении ошибок или необходимости зафиксировать данные обращаемся к полученному массиву и модифицируем его содержимое напрямую:
БуферОшибок = МодульПовтИсп.ПолучитьБуферОшибок();
БуферОшибок.Добавить(Новый Структура("Номенклатура, Дефицит", НоменклатураСсылка, ТребуемоеКоличество - Остаток));
Отказ = Истина;
После завершения транзакции сохраненные в массиве данные остаются доступными в текущем сеансе. Их можно считать внешней обработкой, записать в отдельный журнал или очистить вызовом метода ОбновитьПовторноИспользуемыеЗначения().
Разберем штатный механизм вывода сообщений. Объекты СообщениеПользователю, сформированные внутри транзакции, не уничтожаются при откате. Их можно программно перехватывать и сериализовать.
// Внутри ОбработкаПроведения:
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "[ДАННЫЕ_ДЕФИЦИТА] " + ТекстДанных;
Сообщение.Сообщить();
Отказ = Истина;
В вызывающем коде вне транзакции извлекаем все сообщения вызовом специального метода платформы:
МассивСообщений = ПолучитьСообщенияПользователю(Истина);
Для Каждого Сообщение Из МассивСообщений Цикл
Если СтрНачинаетсяС(Сообщение.Текст, "[ДАННЫЕ_ДЕФИЦИТА]") Тогда
// Обрабатываем и сохраняем данные в БД вне транзакции
КонецЕсли;
КонецЦикла;
Выясним, как использовать временное хранилище. Данные, помещенные во временное хранилище с помощью метода ПоместитьВоВременноеХранилище() с передачей уникального идентификатора формы или сеанса, размещаются в памяти сервера приложений и не откатываются СУБД при установке Отказ = Истина.
// Помещаем данные во временное хранилище по известному адресу
АдресХранилища = ПоместитьВоВременноеХранилище(ТаблицаДефицита, УникальныйИдентификатор);
После отката транзакции данные по полученному адресу можно извлечь методом ПолучитьИзВременногоХранилища(АдресХранилища) и записать в базу данных отдельным независимым вызовом.
Если требуется гарантированно и автономно записать факт сбоя или вычисленные значения в базу данных независимо от основной транзакции, можно инициировать фоновое задание через менеджер фоновых заданий:
ПараметрыЗадания = Новый Массив;
ПараметрыЗадания.Добавить(СтруктураПараметров);
ФоновыеЗадания.Выполнить("РегламентныеПроцедуры.ЗаписатьЛогДефицита", ПараметрыЗадания);
Фоновое задание запускается в отдельном сеансе со своей собственной независимой транзакцией и успешно фиксирует необходимые записи в регистрах сведений.
Проанализируем архитектурную сторону проблемы: постоянная отмена транзакций при проведении создает избыточную нагрузку на СУБД и приводит к удержанию блокировок. Чтобы оптимизировать систему, рекомендуется:
Записать(РежимЗаписиДокумента.Проведение).