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