В практике разработки на платформе 1С часто возникает задача, когда проведение одного документа должно инициировать цепочку изменений в других объектах системы. Например, при проведении Заказа покупателя необходимо проверить наличие резервов по другим заказам и, если товар зарезервирован под этого же контрагента, автоматически создать и провести документ Корректировка заказа, чтобы освободить остатки. Основная сложность здесь заключается в обеспечении атомарности операции: либо проводятся оба документа, либо, в случае ошибки, изменения откатываются везде.
Рассмотрим подробно, как реализовать этот механизм, избежав типичных ошибок с транзакциями, блокировками и видимостью данных.
Разберем ситуацию, с которой столкнулся автор темы. Попытка управлять транзакцией из формы документа (в событии ПередЗаписью) часто приводит к логическому тупику. Если зафиксировать транзакцию корректировки в форме, а основное проведение документа (в модуле объекта) сорвется, то корректировка останется в базе «оторванной» от контекста. Если же не фиксировать транзакцию в форме, то в модуле объекта при контроле остатков система может «не увидеть» освободившиеся ресурсы.
Ключевая особенность: в 1С при вызове метода Записать(РежимЗаписиДокумента.Проведение) платформа автоматически открывает системную транзакцию. Любые попытки вызвать НачатьТранзакцию() внутри этого процесса создают так называемую «вложенную» транзакцию, которая на самом деле просто увеличивает счетчик транзакций на уровне сервера 1С.
По условию задачи корректировка должна создаваться только в том случае, если пользователь проводит документ вручную из формы. Чтобы передать этот признак из контекста формы в модуль объекта, проанализируем использование коллекции ДополнительныеСвойства. Это наиболее корректный путь в сравнении с использованием глобальных переменных модуля.
В модуле формы документа (Обычное приложение) в процедуре ПередЗаписью добавим следующий код:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
Если РежимЗаписи = РежимЗаписиДокумента.Проведение Тогда
// Передаем флаг, что проведение инициировано пользователем
ЭтотОбъект.ДополнительныеСвойства.Вставить("ИнтерактивноеПроведение", Истина);
КонецЕсли;
КонецПроцедуры
Теперь в модуле объекта в процедуре ОбработкаПроведения мы сможем проверить этот флаг и выполнить логику по созданию корректировки.
Посмотрим, как организовать работу в модуле объекта. Поскольку ОбработкаПроведения уже выполняется внутри транзакции, нам не обязательно открывать новую, но важно правильно обработать создание другого документа.
Рассмотрим пошагово алгоритм действий в модуле объекта:
ИнтерактивноеПроведение в ДополнительныеСвойства.КорректировкаЗаказаПокупателя.Записать(РежимЗаписиДокумента.Проведение)). Только в этом случае ее движения по регистрам попадут в текущую транзакцию и будут видны при последующем контроле остатков в текущем сеансе.Отказ = Истина.
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// Проверяем, нужно ли делать корректировку
НужноДелатьКорректировку = Ложь;
ЭтотОбъект.ДополнительныеСвойства.Свойство("ИнтерактивноеПроведение", НужноДелатьКорректировку);
Если НужноДелатьКорректировку Тогда
Попытка
// Логика поиска резервов и создания документа Корректировка
ДокКорректировка = Документы.КорректировкаЗаказаПокупателя.СоздатьДокумент();
// Заполнение реквизитов...
// Проводим корректировку в рамках текущей транзакции
ДокКорректировка.Записать(РежимЗаписиДокумента.Проведение);
Исключение
// Если произошла ошибка СУБД (например, блокировка), транзакция будет помечена к отмене
Отказ = Истина;
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Не удалось создать корректировку резервов: " + ОписаниеОшибки();
Сообщение.Сообщить();
Возврат;
КонецПопытки;
КонецЕсли;
// Далее идет стандартный контроль остатков, который "увидит" изменения от корректировки
// ...
КонецПроцедуры
Проанализируем ситуацию, упомянутую в обсуждении. Если внутри блока Попытка...Исключение происходит ошибка на уровне базы данных (например, нарушение уникальности индекса или конфликт блокировок при записи корректировки), транзакция в СУБД помечается как неисправная (Rollback-only). Даже если вы «погасили» ошибку в блоке Исключение, 1С не позволит вам совершить ни одну операцию записи в этой транзакции. Любая последующая попытка записи приведет к ошибке «В данной транзакции уже происходили ошибки».
Рекомендация: если программная запись связанного документа внутри ОбработкаПроведения завершилась неудачей, единственно верным решением будет установка Отказ = Истина и немедленный выход из процедуры. Не пытайтесь продолжать проведение основного документа.
Выясним, почему автор сообщения 11 видел или не видел остатки. В 1С внутри транзакции текущий сеанс обладает преимуществом: он видит все свои незафиксированные изменения. Это означает, что если вы записали движения корректировки, а потом выполняете запрос к остаткам (через Запрос к виртуальным таблицам), платформа обеспечит получение данных с учетом этих движений.
Однако стоит помнить о следующих правилах:
ЗафиксироватьТранзакцию (которую платформа вызовет сама после ОбработкаПроведения).Создание и проведение «вторичных» документов в процессе проведения «основного» — это архитектурно сложная операция. Рассмотрим риски:
Когда вы проводите Заказ покупателя, система накладывает блокировки на таблицы остатков. Если в этот же момент создается Корректировка, она также накладывает блокировки. Если другой пользователь в этот же момент проводит аналогичный документ, может возникнуть ситуация взаимной блокировки (Deadlock). Один процесс ждет освобождения ресурса, захваченного вторым, а второй ждет ресурс первого.
Как минимизировать риски:
Для корректного решения задачи мы рекомендуем использовать следующую схему:
ЭтотОбъект.ДополнительныеСвойства для отделения интерактивных действий от программных.ОбработкаПроведения модуля объекта.Отказ.Такой подход гарантирует целостность данных в базе: либо заказ проводится вместе с корректировкой резервов, либо база остается в исходном состоянии.