В процессе разработки на платформе «1С:Предприятие 8» программисты часто сталкиваются с ситуацией, когда при записи одного документа необходимо программно создать или изменить второй. Типичный сценарий: в модуле объекта основного документа, в процедуре ПриЗаписи, вызывается метод Записать() для другого объекта. Однако, если во втором документе срабатывает проверка и устанавливается параметр Отказ = Истина, то и первый документ не сохраняется. Рассмотрим, почему это происходит и как реализовать задачу правильно.
Проанализируем работу транзакционного механизма 1С. Когда мы инициируем запись документа, платформа автоматически открывает транзакцию базы данных. Все действия, происходящие внутри процедур ПередЗаписью и ПриЗаписи, выполняются в рамках этой единой транзакции. Если внутри этого процесса мы пытаемся записать второй документ, его запись происходит в той же самой транзакции.
Разберем причину неэффективности блока Попытка. В 1С существует понятие «обреченной» (или «битой») транзакции. Как только в любом месте внутри транзакции происходит ошибка базы данных или программно устанавливается флаг Отказ = Истина в обработчиках записи, платформа помечает всю текущую транзакцию как недействительную. Даже если вы «поймали» ошибку в блоке Исключение, транзакция уже не может быть зафиксирована (Commit). При попытке завершить запись основного документа система выдаст критическую ошибку: «В данной транзакции уже происходили ошибки!».
Выясним причину такого поведения: платформа гарантирует целостность данных. Если часть атомарной операции не удалась, система обязана откатить все изменения, чтобы не возникло ситуации, когда первый документ записан, а связанные с ним данные — нет. Но что делать, если бизнес-логика требует записи первого документа «несмотря ни на что»? Рассмотрим доступные варианты решения.
Для того чтобы запись второго документа не блокировала основной процесс, необходимо вынести её в отдельную транзакцию. Самый надежный способ сделать это на стороне сервера — запустить фоновое задание. Посмотрим на пример реализации:
Процедура ПриЗаписи(Отказ)
// Подготовка параметров для создания второго документа
ПараметрыЗадания = Новый Структура;
ПараметрыЗадания.Вставить("ДанныеОснования", ЭтотОбъект.Ссылка);
// Запускаем фоновое задание. Оно создаст свою собственную транзакцию.
ФоновыеЗадания.Выполнить("МойОбщийМодуль.СоздатьСвязанныйДокумент", ПараметрыЗадания);
КонецПроцедуры
В этом случае, даже если внутри процедуры СоздатьСвязанныйДокумент произойдет отказ или ошибка, это случится в другом сеансе и в другой транзакции. Основная транзакция записи первого документа успешно завершится. Этот метод идеально подходит для систем с высокой нагрузкой.
Если запись инициируется пользователем через интерактивное нажатие кнопки «Провести и закрыть», мы можем воспользоваться событиями формы. Проанализируем ситуацию: событие ПослеЗаписи в модуле формы срабатывает тогда, когда транзакция записи в базу данных уже полностью завершена и зафиксирована.
Рассмотрим алгоритм действий в модуле формы:
ПослеЗаписи на клиенте.
&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
// Основной документ уже в базе.
// Теперь пытаемся создать второй.
// Если здесь будет ошибка, пользователю выведется предупреждение,
// но первый документ никуда не исчезнет.
СоздатьВторойДокументНаСервере(Объект.Ссылка);
КонецПроцедуры
Важно: этот способ не сработает, если документ записывается программно (например, какой-то внешней обработкой), так как модуль формы в этом случае не задействуется.
Чтобы минимизировать вероятность отказа во втором документе, мы можем выполнить проверку данных перед вызовом метода Записать(). Посмотрим, как это реализовать с помощью метода ПроверитьЗаполнение().
// В модуле объекта основного документа
ДокВторой = Документы.ДругойТип.СоздатьДокумент();
// Заполнение реквизитов...
Если ДокВторой.ПроверитьЗаполнение() Тогда
Попытка
ДокВторой.Записать();
Исключение
ЗаписьЖурналаРегистрации("Ошибка записи второго документа", УровеньЖурналаРегистрации.Ошибка);
КонецПопытки;
Иначе
// Если данные не валидны, мы просто не вызываем Записать()
// Тем самым не «портим» основную транзакцию.
Сообщить("Второй документ не может быть заполнен, но основной будет сохранен.");
КонецЕсли;
Хотя этот метод не дает 100% гарантии (ошибка может возникнуть на уровне СУБД или в коде ПередЗаписью), он отсекает большинство сценариев, связанных с недозаполненными реквизитами.
Самый архитектурно правильный способ для сложных систем — использование механизма отложенной обработки. Вместо того чтобы записывать второй документ напрямую, мы записываем информацию о необходимости его создания в специальный РегистрСведений (например, «ОчередьЗадач»).
Проанализируем преимущества:
Отказ.РегламентноеЗадание раз в минуту проверяет регистр и пытается создать документы. Если возникает ошибка, оно просто попробует позже.Выясним основной итог нашего разбора: если вам нужно гарантированно записать один объект независимо от успеха другого, никогда не вызывайте запись второго объекта внутри транзакции первого, если вы не готовы к откату всей операции. Используйте ФоновыеЗадания для серверной логики или событие ПослеЗаписи в форме для клиентской логики. Это позволит избежать блокировок и обеспечит стабильную работу системы даже при возникновении ошибок в связанных алгоритмах.