Как записать основной документ, если при записи связанного документа возникла ошибка или отказ

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

В процессе разработки на платформе «1С:Предприятие 8» программисты часто сталкиваются с ситуацией, когда при записи одного документа необходимо программно создать или изменить второй. Типичный сценарий: в модуле объекта основного документа, в процедуре ПриЗаписи, вызывается метод Записать() для другого объекта. Однако, если во втором документе срабатывает проверка и устанавливается параметр Отказ = Истина, то и первый документ не сохраняется. Рассмотрим, почему это происходит и как реализовать задачу правильно.

Почему стандартная конструкция Попытка — Исключение не спасает?

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

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

Выясним причину такого поведения: платформа гарантирует целостность данных. Если часть атомарной операции не удалась, система обязана откатить все изменения, чтобы не возникло ситуации, когда первый документ записан, а связанные с ним данные — нет. Но что делать, если бизнес-логика требует записи первого документа «несмотря ни на что»? Рассмотрим доступные варианты решения.

Решение 1. Использование фоновых заданий (асинхронная запись)

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


Процедура ПриЗаписи(Отказ)
    
    // Подготовка параметров для создания второго документа
    ПараметрыЗадания = Новый Структура;
    ПараметрыЗадания.Вставить("ДанныеОснования", ЭтотОбъект.Ссылка);
    
    // Запускаем фоновое задание. Оно создаст свою собственную транзакцию.
    ФоновыеЗадания.Выполнить("МойОбщийМодуль.СоздатьСвязанныйДокумент", ПараметрыЗадания);
    
КонецПроцедуры

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

Решение 2. Перенос логики в модуль формы (ПослеЗаписи)

Если запись инициируется пользователем через интерактивное нажатие кнопки «Провести и закрыть», мы можем воспользоваться событиями формы. Проанализируем ситуацию: событие ПослеЗаписи в модуле формы срабатывает тогда, когда транзакция записи в базу данных уже полностью завершена и зафиксирована.

Рассмотрим алгоритм действий в модуле формы:

  1. Пользователь нажимает «Записать».
  2. Платформа выполняет запись в модуль объекта, фиксирует транзакцию.
  3. Срабатывает обработчик ПослеЗаписи на клиенте.
  4. Внутри этого обработчика мы вызываем серверную процедуру для создания второго документа.

&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
    // Основной документ уже в базе. 
    // Теперь пытаемся создать второй. 
    // Если здесь будет ошибка, пользователю выведется предупреждение, 
    // но первый документ никуда не исчезнет.
    СоздатьВторойДокументНаСервере(Объект.Ссылка);
КонецПроцедуры

Важно: этот способ не сработает, если документ записывается программно (например, какой-то внешней обработкой), так как модуль формы в этом случае не задействуется.

Решение 3. Предварительная проверка возможности записи

Чтобы минимизировать вероятность отказа во втором документе, мы можем выполнить проверку данных перед вызовом метода Записать(). Посмотрим, как это реализовать с помощью метода ПроверитьЗаполнение().


// В модуле объекта основного документа
ДокВторой = Документы.ДругойТип.СоздатьДокумент();
// Заполнение реквизитов...

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

Хотя этот метод не дает 100% гарантии (ошибка может возникнуть на уровне СУБД или в коде ПередЗаписью), он отсекает большинство сценариев, связанных с недозаполненными реквизитами.

Решение 4. Очередь задач через Регистр Сведений

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

Проанализируем преимущества:

Резюме

Выясним основной итог нашего разбора: если вам нужно гарантированно записать один объект независимо от успеха другого, никогда не вызывайте запись второго объекта внутри транзакции первого, если вы не готовы к откату всей операции. Используйте ФоновыеЗадания для серверной логики или событие ПослеЗаписи в форме для клиентской логики. Это позволит избежать блокировок и обеспечит стабильную работу системы даже при возникновении ошибок в связанных алгоритмах.

← На главную