Как сохранить данные в регистре сведений при отмене проведения документа в 1С?

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

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

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

Рассмотрим пример кода, который был предложен в исходной теме:


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

// Обращение к этой процедуре идет так:
Пока Выборка.Следующий Цикл
    ***************
    ЗаполнитьРезультатПроверки(Источник.Ссылка, РезультатПроверки, Выборка.ОписаниеОшибки, Выборка.Тиверки);
КонецЦикла;

В этом примере мы видим попытку записать данные о результатах проверки. Отладчик показывает, что все поля записи заполняются корректно, метод Записать вызывается. Однако после завершения проведения документа (с Отказ = Истина) регистр сведений оказывается пустым. Почему так происходит?

Причина проблемы: Откат транзакции

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

Таким образом, наша запись в регистр сведений также попадает под откат транзакции, если проведение документа завершается неудачей. Нам необходимо найти способ записать данные вне основной транзакции проведения документа, чтобы они сохранились независимо от её исхода.

Решение: Использование фоновых заданий

Одним из наиболее эффективных и часто используемых способов решить проблему гарантированной записи данных при откате транзакции является использование фоновых заданий. Фоновые задания выполняются асинхронно, в отдельной транзакции (или вовсе без неё, если код не инициирует новую), что позволяет им не зависеть от исхода основной транзакции проведения документа.

Как это работает:

  1. В процедуре проведения документа (или в подписке на событие, связанной с проведением), мы формируем все необходимые данные для записи в регистр сведений.
  2. Вместо прямой записи в регистр, мы передаем эти данные в качестве параметров в специально созданное фоновое задание.
  3. Фоновое задание запускается, принимает переданные параметры и выполняет запись в регистр сведений. Эта запись происходит уже вне контекста основной транзакции проведения документа.

Рассмотрим шаги для реализации решения с фоновым заданием:

  1. Создадим экспортную процедуру или функцию в общем модуле:

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

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

    Обратите внимание, что мы передаем не единичные параметры, а коллекцию (например, ТаблицаЗначений или МассивСтруктур), чтобы избежать запуска множества фоновых заданий, если проверок много.

  2. Изменим вызов процедуры в обработчике события документа:

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

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

Важные нюансы при работе с фоновыми заданиями:

  1. Проблема "битой" ссылки: Если документ, для которого мы пытаемся записать результаты, является новым и транзакция его проведения откатывается, то этот документ не будет записан в базу данных, и его ссылка не будет существовать. Если мы передадим в фоновое задание Источник.Ссылка, она будет "битой" или пустой, и фоновое задание не сможет корректно идентифицировать объект.

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

    Например, можно передавать не Источник.Ссылка, а Новый Структура("ВидДокумента, Номер, Дата", Источник.Метаданные().Имя, Источник.Номер, Источник.Дата). В фоновом задании потом можно будет работать с этими данными. Или, если нужно просто зафиксировать факт проверки, достаточно передать идентификатор изначального запроса или просто описание ошибки без ссылки на несуществующий объект.

  2. Передача параметров: Данные в фоновое задание передаются через временное хранилище. Убедитесь, что передаваемая структура или таблица значений не содержит сложных типов данных, которые не могут быть сериализованы (например, открытые формы, объекты менеджеров и т.д.). Простые типы (строки, числа, даты, булево), ссылки на существующие объекты, структуры, таблицы значений, массивы – передаются без проблем.
  3. Нагрузка на систему: Частое инициирование фоновых заданий (например, при массовом перепроведении большого количества документов, каждый из которых вызывает фоновое задание) может создавать дополнительную нагрузку на сервер 1С. Важно контролировать количество одновременно выполняемых заданий и оптимизировать передачу данных (например, собирать все записи в одну таблицу значений и передавать её один раз, как показано в примере выше).
  4. Статус выполнения: Фоновое задание запускается асинхронно, а значит, результат его выполнения не будет известен немедленно. Если вам нужно отслеживать статус записи или результаты фонового задания, можно использовать механизм отслеживания фоновых заданий или дополнить процедуру фонового задания логированием в тот же регистр сведений о его собственной работе.
  5. Имитация периодичности в непериодическом регистре: В исходном обсуждении возник вопрос о поле "Дата" в непериодическом регистре. Если регистр сведений непериодический, а вы используете поле "Дата" как измерение и записываете ТекущаяДата() для всех записей в рамках одной секунды (или другой минимальной единицы времени), и при этом другие измерения также совпадают, то будут записаны только уникальные комбинации измерений. Это означает, что если у вас несколько ошибок для одного и того же объекта с одинаковым "ТипомПроверки" в одно и то же время, запишется только одна из них. Чтобы этого избежать, убедитесь, что каждое измерение уникально, или используйте дополнительное измерение (например, ИдентификаторСтроки), если необходимо сохранять все детали.

Альтернативные подходы

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

Использование Журнала Регистрации

Это наиболее простой и штатный механизм для фиксации различных событий, включая ошибки.

  1. Преимущества:
    • Запись в Журнал Регистрации происходит вне основной транзакции и не будет отменена.
    • Это штатный инструмент, предназначенный именно для логирования событий системы.
  2. Реализация:

    В обработчике проведения документа, перед установкой параметра Отказ = Истина, следует вызвать процедуру ЗаписьЖурналаРегистрации().

    
    Если Отказ Тогда
        ЗаписьЖурналаРегистрации("МоиПроверки.ОшибкаПроведения", УровеньЖурналаРегистрации.Ошибка, , Источник.Ссылка, "Документ " + Источник.Представление() + " не проведен из-за ошибки: " + ОписаниеОшибкиПроверки);
    КонецЕсли;
    

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

  3. Недостатки:
    • Журнал регистрации не всегда удобен для последующего анализа и построения отчетов.
    • Данные из него, как правило, нужно дополнительно обрабатывать, например, регламентным заданием, которое будет переносить их в более удобный для анализа регистр сведений.
    • Может быть сложно извлекать структурированную информацию.

Режим "проведения без проведения"

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

  1. Как это работает:

    Вместо установки Отказ = Истина, мы можем программно очистить все движения документа (ЭтотОбъект.Движения.Очистить()), снять флаг проведения (ЭтотОбъект.Проведен = Ложь), записать нужную информацию в свой регистр сведений и выдать сообщение пользователю. Таким образом, транзакция успешно завершается, документ остается непроведенным, но информация об ошибке сохраняется.

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

Использование внешних ресурсов

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

  1. Варианты:
    • Запись в текстовый файл или лог-файл на сервере. Это быстрый способ, но требует последующей обработки этих файлов для анализа.
    • Обращение к внешним веб- или HTTP-сервисам. Можно настроить простой сервис (например, на Node.js, Python или любом другом языке), который будет принимать и сохранять информацию об ошибках.
  2. Сложности:
    • Эти подходы значительно усложняют архитектуру решения.
    • Требуют дополнительных механизмов для обеспечения надежности (например, контроль доступа к файлам, обработка недоступности сервиса).
    • Может потребоваться настройка сети и прав доступа.

Заключение

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

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

← На главную