При разработке в системе 1С:Предприятие 8.3 мы часто сталкиваемся с необходимостью записывать данные в регистры сведений. Однако возникают ситуации, когда эта запись должна произойти независимо от того, будет ли успешно проведена основная операция, например, проведение документа. Давайте разберем подробнее, почему возникают сложности с записью данных в регистры сведений при отмене проведения документа и как мы можем гарантировать сохранение необходимой информации.
Представим типовую ситуацию: у нас есть документ, который проходит некую проверку при проведении. Если проверка не пройдена, проведение документа должно быть отменено, но информацию о неудачной проверке необходимо зафиксировать в специальном регистре сведений. На первый взгляд, задача кажется простой: создаем менеджер записи регистра, заполняем его данными и вызываем метод Записать(). Но на практике оказывается, что при отмене проведения документа (когда параметр Отказ = Истина), все записи, сделанные внутри процедуры проведения, исчезают, как будто их и не было.
Рассмотрим пример кода, который был предложен в исходной теме:
Процедура ЗаполнитьРезультатПроверки(Источник, РезультатПроверки, Ошибка, ТипПроверки)
Запись = РегистрыСведений.РезультатыПроверок.СоздатьМенеджерЗаписи;
Запись.Дата = ТекущаяДата;
Запись.ОбъектДанных = Источник;
Запись.ОписаниеОшибки = ?(РезультатПроверки, Ошибка, "");
Запись.Результат = ?(РезультатПроверки, "Ошибка", "Успешно");
Запись.ТипПроверки = ТипПроверки;
Запись.Записать;
КонецПроцедуры
// Обращение к этой процедуре идет так:
Пока Выборка.Следующий Цикл
***************
ЗаполнитьРезультатПроверки(Источник.Ссылка, РезультатПроверки, Выборка.ОписаниеОшибки, Выборка.Тиверки);
КонецЦикла;
В этом примере мы видим попытку записать данные о результатах проверки. Отладчик показывает, что все поля записи заполняются корректно, метод Записать вызывается. Однако после завершения проведения документа (с Отказ = Истина) регистр сведений оказывается пустым. Почему так происходит?
Основная причина кроется в механизме транзакций, используемом платформой 1С:Предприятие. Когда мы начинаем проведение документа, платформа открывает транзакцию. Все действия по изменению данных в базе (запись движений документа, запись в регистры сведений и накопления, справочники и т.д.) выполняются внутри этой транзакции. Если в процессе проведения мы устанавливаем Отказ = Истина, это сигнализирует платформе о необходимости отменить (откатить) текущую транзакцию. При откате транзакции все изменения, сделанные в её рамках, аннулируются. Это сделано для обеспечения атомарности операций: либо все изменения успешно применены, либо ни одно из них.
Таким образом, наша запись в регистр сведений также попадает под откат транзакции, если проведение документа завершается неудачей. Нам необходимо найти способ записать данные вне основной транзакции проведения документа, чтобы они сохранились независимо от её исхода.
Одним из наиболее эффективных и часто используемых способов решить проблему гарантированной записи данных при откате транзакции является использование фоновых заданий. Фоновые задания выполняются асинхронно, в отдельной транзакции (или вовсе без неё, если код не инициирует новую), что позволяет им не зависеть от исхода основной транзакции проведения документа.
Рассмотрим шаги для реализации решения с фоновым заданием:
Эта процедура будет выполнять непосредственную запись в регистр и будет вызываться фоновым заданием. Важно, чтобы модуль был серверным и экспортным.
// Общий модуль, например, "РезультатыПроверокСервер"
// Свойство модуля: Клиент (обычное приложение), Сервер, Внешнее соединение, Вызов сервера, Менеджер сеансов, Фоновое задание (должны быть установлены нужные флаги)
Процедура ЗаписатьРезультатПроверкиВФоновомЗадании(ПараметрыЗаписи) Экспорт
Попытка
Для Каждого СтрокаПараметров Из ПараметрыЗаписи Цикл
Запись = РегистрыСведений.РезультатыПроверок.СоздатьМенеджерЗаписи;
Запись.Дата = СтрокаПараметров.Дата;
Запись.ОбъектДанных = СтрокаПараметров.ОбъектДанных;
Запись.ОписаниеОшибки = СтрокаПараметров.ОписаниеОшибки;
Запись.Результат = СтрокаПараметров.Результат;
Запись.ТипПроверки = СтрокаПараметров.ТипПроверки;
Запись.Записать();
КонецЦикла;
Исключение
// Здесь можно залогировать ошибку выполнения фонового задания
ЗаписьЖурналаРегистрации("РезультатПроверки", УровеньЖурналаРегистрации.Ошибка, , , "Ошибка при записи результатов проверки в фоновом задании: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Обратите внимание, что мы передаем не единичные параметры, а коллекцию (например, ТаблицаЗначений или МассивСтруктур), чтобы избежать запуска множества фоновых заданий, если проверок много.
Вместо прямой записи, мы будем собирать данные для записи в коллекцию и затем запускать фоновое задание.
// В процедуре проведения документа или в подписке на событие
// Например, в ОбработкаПроведения()
ТаблицаПараметровДляЗаписи = Новый ТаблицаЗначений;
ТаблицаПараметровДляЗаписи.Колонки.Добавить("Дата");
ТаблицаПараметровДляЗаписи.Колонки.Добавить("ОбъектДанных");
ТаблицаПараметровДляЗаписи.Колонки.Добавить("ОписаниеОшибки");
ТаблицаПараметровДляЗаписи.Колонки.Добавить("Результат");
ТаблицаПараметровДляЗаписи.Колонки.Добавить("ТипПроверки");
Пока Выборка.Следующий Цикл
// ... выполнение проверок ...
Если РезультатПроверки Тогда // Если есть ошибка, которую нужно записать
НоваяСтрока = ТаблицаПараметровДляЗаписи.Добавить();
НоваяСтрока.Дата = ТекущаяДата();
НоваяСтрока.ОбъектДанных = Источник.Ссылка; // Или другой идентификатор
НоваяСтрока.ОписаниеОшибки = Выборка.ОписаниеОшибки;
НоваяСтрока.Результат = "Ошибка";
НоваяСтрока.ТипПроверки = Выборка.ТипПроверки;
Отказ = Истина; // Если обнаружена критическая ошибка, отменяем проведение
Иначе
НоваяСтрока = ТаблицаПараметровДляЗаписи.Добавить();
НоваяСтрока.Дата = ТекущаяДата();
НоваяСтрока.ОбъектДанных = Источник.Ссылка;
НоваяСтрока.ОписаниеОшибки = "";
НоваяСтрока.Результат = "Успешно";
НоваяСтрока.ТипПроверки = Выборка.ТипПроверки;
КонецЕсли;
КонецЦикла;
Если ТаблицаПараметровДляЗаписи.Количество() > 0 Тогда
// Запускаем фоновое задание, если есть что записывать
ФоновыеЗадания.ВыполнитьВФоне("РезультатыПроверокСервер.ЗаписатьРезультатПроверкиВФоновомЗадании", ТаблицаПараметровДляЗаписи, "Запись результатов проверок документа");
КонецЕсли;
Источник.Ссылка, она будет "битой" или пустой, и фоновое задание не сможет корректно идентифицировать объект.
Решение: Вместо ссылки на сам объект, передавайте в фоновое задание его ключевые реквизиты (например, ВидДокумента, Номер, Дата) или полные данные в виде структуры или таблицы значений. Это позволит идентифицировать попытку проведения, даже если сам документ не был сохранен.
Например, можно передавать не Источник.Ссылка, а Новый Структура("ВидДокумента, Номер, Дата", Источник.Метаданные().Имя, Источник.Номер, Источник.Дата). В фоновом задании потом можно будет работать с этими данными. Или, если нужно просто зафиксировать факт проверки, достаточно передать идентификатор изначального запроса или просто описание ошибки без ссылки на несуществующий объект.
ТекущаяДата() для всех записей в рамках одной секунды (или другой минимальной единицы времени), и при этом другие измерения также совпадают, то будут записаны только уникальные комбинации измерений. Это означает, что если у вас несколько ошибок для одного и того же объекта с одинаковым "ТипомПроверки" в одно и то же время, запишется только одна из них. Чтобы этого избежать, убедитесь, что каждое измерение уникально, или используйте дополнительное измерение (например, ИдентификаторСтроки), если необходимо сохранять все детали.
Помимо фоновых заданий, существуют и другие методы для сохранения информации о неудачных попытках проведения, каждый со своими особенностями.
Это наиболее простой и штатный механизм для фиксации различных событий, включая ошибки.
В обработчике проведения документа, перед установкой параметра Отказ = Истина, следует вызвать процедуру ЗаписьЖурналаРегистрации().
Если Отказ Тогда
ЗаписьЖурналаРегистрации("МоиПроверки.ОшибкаПроведения", УровеньЖурналаРегистрации.Ошибка, , Источник.Ссылка, "Документ " + Источник.Представление() + " не проведен из-за ошибки: " + ОписаниеОшибкиПроверки);
КонецЕсли;
В ней можно указать событие (например, МоиПроверки.ОшибкаПроведения), уровень (Ошибка, Предупреждение, Информация) и передать в комментарий подробное описание проблемы. В качестве объекта метаданных можно указать ссылку на документ, для которого произошла ошибка.
В некоторых специфических случаях можно имитировать отказ от проведения, не вызывая откат транзакции.
Вместо установки Отказ = Истина, мы можем программно очистить все движения документа (ЭтотОбъект.Движения.Очистить()), снять флаг проведения (ЭтотОбъект.Проведен = Ложь), записать нужную информацию в свой регистр сведений и выдать сообщение пользователю. Таким образом, транзакция успешно завершается, документ остается непроведенным, но информация об ошибке сохраняется.
Если Отказ Тогда
// Вместо Отказ = Истина;
ЭтотОбъект.Движения.Очистить(); // Очищаем все движения
ЭтотОбъект.Проведен = Ложь; // Снимаем флаг проведения
// Здесь выполняем запись в регистр сведений напрямую,
// т.к. транзакция завершится успешно (хоть и без проведения документа)
Запись = РегистрыСведений.РезультатыПроверок.СоздатьМенеджерЗаписи;
Запись.Дата = ТекущаяДата;
Запись.ОбъектДанных = ЭтотОбъект.Ссылка;
Запись.ОписаниеОшибки = ОписаниеОшибкиПроверки;
Запись.Результат = "Ошибка";
Запись.ТипПроверки = "Проведение";
Запись.Записать;
Сообщить("Документ не проведен из-за ошибки: " + ОписаниеОшибкиПроверки, СтатусСообщения.Важное);
// Важно: в этом случае Отказ = Ложь, чтобы транзакция зафиксировалась
// Но мы уже вручную 'откатили' проведение документа
Отказ = Ложь;
КонецЕсли;
Хотя этот метод используется реже, он также является рабочим вариантом для записи данных вне транзакции 1С.
Node.js, Python или любом другом языке), который будет принимать и сохранять информацию об ошибках.Выбирая метод для сохранения данных о неудачных попытках проведения документа, всегда помните о главном принципе: любая операция, которая должна быть выполнена гарантированно, независимо от исхода основной транзакции, должна быть вынесена за ее пределы. Фоновые задания являются наиболее универсальным и рекомендованным способом для решения этой задачи в большинстве случаев в 1С:Предприятии 8.3.
Мы рассмотрели несколько подходов к решению проблемы незаписи данных при откате транзакции, начиная от прямого использования фоновых заданий и заканчивая альтернативными методами. Выбор конкретного решения будет зависеть от требований к надежности, производительности, сложности реализации и общих архитектурных принципов вашей конфигурации.