Почему при программном создании и проведении документа не сохраняются табличные части

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

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

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

Анализ проблемы: почему данные исчезают при записи

Рассмотрим типовой ошибочный код, с которого начинается возникновение проблемы:


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

Документы.Расчет.ЗаполнитьТабличнуюЧастьИнформациейНеобходимойДляРасчетов(ДокументРасчета);
Документы.Расчет.РасчитатьНачисления(ДокументРасчета);
Документы.Расчет.ОтразитьДанныеТабличнойЧастиРасчетыВРегистрахРасчетов(ДокументРасчета);
Документы.Расчет.РасчитатьЛьготы(ДокументРасчета);
Документы.Расчет.ОчиститьДвиженияПоРегиструРасчетаНеобходимыхДляРасчетаЛьгот(ДокументРасчета);

ДокументРасчета.Записать(РежимЗаписиДокумента.Проведение);

Выясним основные причины, приводящие к тому, что после выполнения такого кода табличные части остаются пустыми:

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

Вариант 1. Корректное серверное заполнение без использования формы (рекомендуемый)

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

Для исключения конфликтов версий не следует выполнять промежуточную запись документа только ради получения ссылки. Если ссылка необходима до записи, используем метод УстановитьСсылкуНового():


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

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


ДокументОбъект.ОбменДанными.Загрузка = Истина;
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);

Важно: режим ОбменДанными.Загрузка = Истина отключает выполнение обработчиков ПередЗаписью и ПриЗаписи, а также контроль проведения, поэтому его следует применять осознанно, если требуется загрузить подготовленные данные без дополнительных проверок.

Вариант 2. Перенос логики заполнения в модуль объекта

Проанализируем архитектурно правильный подход к структуре кода. Логику наполнения табличных частей оптимально выносить в экспортные процедуры самого модуля объекта. Это исключает передачу объекта через модуль менеджера и гарантирует работу с текущим контекстом через ЭтотОбъект.

Посмотрим на пример реализации метода внутри модуля объекта документа:


// В модуле объекта документа "Расчет"
Процедура ЗаполнитьИРассчитать() Экспорт
    
    ЭтотОбъект.Расчеты.Очистить();
    ЭтотОбъект.Льготы.Очистить();
    
    // Вызов внутренних процедур заполнения собственных коллекций
    ЗаполнитьДанныеДляРасчета();
    ВыполнитьРасчетНачислений();
    ВыполнитьРасчетЛьгот();
    
КонецПроцедуры

Тогда вызывающий программный код становится компактным и надежным:


ДокументОбъект = Документы.Расчет.СоздатьДокумент();
ДокументОбъект.Дата = ДатаДокумента;
ДокументОбъект.УстановитьНовыйНомер();
ДокументОбъект.Организация = Организация;
ДокументОбъект.ЛицевойСчет = ЛицевойСчет;
ДокументОбъект.Автор = Пользователь;

// Вызываем расчет напрямую у объекта
ДокументОбъект.ЗаполнитьИРассчитать();

// Записываем с проведением
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);

Вариант 3. Использование формы объекта (клиентское решение)

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

Разберем по шагам, как эмулировать работу пользователя через форму программно:


&НаКлиенте
Процедура РассчитатьАбонентаЧерезФорму(ДатаДокумента)
    
    // Шаг 1. Создаем предварительный документ на сервере и получаем ссылку
    ДокументСсылка = СоздатьНовыйДокументНаСервере(ДатаДокумента);
    
    // Шаг 2. Инициализируем форму созданного документа
    ПараметрыФормы = Новый Структура("Ключ", ДокументСсылка);
    ФормаДокумента = ПолучитьФорму("Документ.Расчет.ФормаОбъекта", ПараметрыФормы);
    
    // Шаг 3. Получаем структуру данных формы и заполняем ее на сервере
    ДанныеОбъекта = ФормаДокумента.Объект;
    ЗаполнитьДанныеФормыНаСервере(ДанныеОбъекта);
    
    // Шаг 4. Копируем обновленные данные обратно в контекст формы
    КопироватьДанныеФормы(ДанныеОбъекта, ФормаДокумента.Объект);
    
    // Шаг 5. Записываем и проводим документ через форму
    ПараметрыЗаписи = Новый Структура("РежимЗаписи", РежимЗаписиДокумента.Проведение);
    ФормаДокумента.Записать(ПараметрыЗаписи);
    
КонецПроцедуры

&НаСервере
Функция СоздатьНовыйДокументНаСервере(ДатаДокумента)
    
    НовыйДокумент = Документы.Расчет.СоздатьДокумент();
    НовыйДокумент.Дата = ДатаДокумента;
    НовыйДокумент.УстановитьНовыйНомер();
    НовыйДокумент.Организация = Организация;
    НовыйДокумент.ЛицевойСчет = ЛицевойСчет;
    НовыйДокумент.Автор = Пользователь;
    НовыйДокумент.Записать();
    
    Возврат НовыйДокумент.Ссылка;
    
КонецФункции

&НаСервере
Процедура ЗаполнитьДанныеФормыНаСервере(ДанныеОбъекта)
    
    ДанныеОбъекта.Расчеты.Очистить();
    ДанныеОбъекта.Льготы.Очистить();
    
    Документы.Расчет.ЗаполнитьТабличнуюЧастьИнформациейНеобходимойДляРасчетов(ДанныеОбъекта);
    Документы.Расчет.РасчитатьНачисления(ДанныеОбъекта);
    Документы.Расчет.ОтразитьДанныеТабличнойЧастиРасчетыВРегистрахРасчетов(ДанныеОбъекта);
    Документы.Расчет.РасчитатьЛьготы(ДанныеОбъекта);
    Документы.Расчет.ОчиститьДвиженияПоРегиструРасчетаНеобходимыхДляРасчетаЛьгот(ДанныеОбъекта);
    
КонецПроцедуры

Ограничения данного метода: этот подход не может работать в регламентных и фоновых заданиях, Web- и HTTP-сервисах, так как метод ПолучитьФорму() доступен только в клиентском контексте. Кроме того, передача данных формы между клиентом и сервером замедляет групповую обработку документов.

Чек-лист для поиска ошибки при пустых табличных частях

Если после выполнения программного кода табличная часть всё равно остается пустой, выполним следующие шаги диагностики:

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