Почему не заполняется табличная часть Расходной накладной при создании на основании Заказа покупателя в 1С

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

При разработке в среде 1С:Предприятие часто возникает задача автоматического создания цепочки документов. Например, при проведении документа ЗаказПокупателя необходимо автоматически сформировать и заполнить документ РеализацияТоваровУслуг (расходную накладную). Однако разработчики часто сталкиваются с неприятным поведением системы: шапка созданной накладной заполняется корректно, а её табличная часть остаётся абсолютно пустой.

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

Выясняем причину: транзакции базы данных и виртуальные таблицы

Давайте проанализируем, что происходит в системе в тот момент, когда в процедуре ОбработкаПроведения документа ЗаказПокупателя выполняется стандартный код ввода на основании:


РН = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
РН.Заполнить(Ссылка);

Метод Заполнить() вызывает обработчик ОбработкаЗаполнения в модуле объекта создаваемой РеализацииТоваровУслуг. В типовых конфигурациях (таких как «Управление торговым предприятием», «Управление производственным предприятием» или «Управление торговлей») этот обработчик устроен достаточно сложно. Он не просто копирует строки из документа-основания, а выполняет запрос к виртуальным таблицам остатков регистров накопления (например, к регистру ЗаказыПокупателей.Остатки).

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

  1. Процедура ОбработкаПроведения выполняется внутри одной неделимой транзакции базы данных.
  2. В момент выполнения кода в самом конце процедуры проведения движения по регистрам накопления для текущего заказа уже сформированы в памяти, но физически они еще не записаны и не зафиксированы в базе данных (коммит транзакции произойдет только после успешного завершения всей процедуры ОбработкаПроведения).
  3. Запрос в процедуре ОбработкаЗаполнения обращается к базе данных. Но поскольку транзакция проведения заказа еще не завершена, виртуальная таблица остатков регистра накопления не видит новых записей по этому заказу. Она возвращает пустой результат.
  4. В итоге алгоритм заполнения считает, что по данному заказу нечего отгружать, и оставляет табличную часть расходной накладной пустой.

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

Решение автора темы: перенос кода в событие формы "ПослеЗаписи"

Автор темы нашел рабочий для его ситуации выход: перенес логику создания расходной накладной в модуль формы документа ЗаказПокупателя в процедуру ПослеЗаписи.

Разберем, почему это сработало. Когда пользователь нажимает кнопку «Провести и закрыть» или «Записать», система сначала выполняет транзакцию проведения на сервере, закрывает ее, фиксирует все движения в регистрах базы данных, и только после этого управление возвращается на клиент в процедуру формы ПослеЗаписи. На этом этапе данные в базе уже актуальны, и вызов метода Заполнить() отрабатывает без ошибок.

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

Поэтому рассмотрим более надежные и архитектурно правильные альтернативные варианты решения.

Способ 1. Прямое копирование данных (без запросов к регистрам)

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

Этот способ абсолютно безопасен, работает очень быстро и не зависит от состояния транзакции и регистров накопления. Рассмотрим пример реализации такого кода:


// Создаем новый документ реализации
РН = Документы.RealizaciaTovarovUslug.СоздатьДокумент();

// Заполняем реквизиты шапки напрямую из заказа
ЗаполнитьЗначенияСвойств(РН, Ссылка);
РН.Дата = ТекущаяДата(); // или другая необходимая дата

// Нам также нужно передать ссылку на заказ как документ-основание
РН.Сделка = Ссылка; 

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

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

Важное предупреждение: Создание и проведение другого документа внутри транзакции проведения текущего документа нарушает стандарты разработки 1С. Это может приводить к взаимным блокировкам (deadlocks) в СУБД при одновременной работе нескольких пользователей. Используйте этот метод с осторожностью и только в малонагруженных базах.

Способ 2. Использование параметров формы для управляемого приложения

Если ваша конфигурация использует Управляемые формы (платформа 1С v8.3), правильным решением для интерактивного ввода на основании будет использование механизма параметров при открытии формы.

Рассмотрим, как это реализовать. Вместо создания объекта на сервере мы открываем форму нового документа с клиента, передавая структуру параметров:


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

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

Способ 3. Очередь задач и фоновые задания (для нагруженных систем)

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

Разберем по шагам наиболее масштабируемое и профессиональное решение задачи:

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

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

← На главную