При разработке и доработке конфигураций 1С разработчики часто сталкиваются с ситуацией: при интерактивном создании и заполнении документа через форму всё отрабатывает штатно, данные табличных частей сохраняются и формируются движения. Однако при попытке программно создать документ, заполнить его реквизиты и провести табличные части оказываются пустыми, хотя в отладчике на этапах расчета видно, что коллекции строк заполнены.
Разберем детально, почему возникает такая ошибка, проанализируем типовые сценарии и рассмотрим правильные подходы к программному созданию и заполнению документов.
Рассмотрим типовой ошибочный код, с которого начинается возникновение проблемы:
ДокументРасчета = Документы.Расчет.СоздатьДокумент();
ДокументРасчета.Дата = ДатаДокумента;
ДокументРасчета.УстановитьНовыйНомер();
ДокументРасчета.Организация = Организация;
ДокументРасчета.ЛицевойСчет = ЛицевойСчет;
ДокументРасчета.Автор = Пользователь;
ДокументРасчета.Записать();
Документы.Расчет.ЗаполнитьТабличнуюЧастьИнформациейНеобходимойДляРасчетов(ДокументРасчета);
Документы.Расчет.РасчитатьНачисления(ДокументРасчета);
Документы.Расчет.ОтразитьДанныеТабличнойЧастиРасчетыВРегистрахРасчетов(ДокументРасчета);
Документы.Расчет.РасчитатьЛьготы(ДокументРасчета);
Документы.Расчет.ОчиститьДвиженияПоРегиструРасчетаНеобходимыхДляРасчетаЛьгот(ДокументРасчета);
ДокументРасчета.Записать(РежимЗаписиДокумента.Проведение);
Выясним основные причины, приводящие к тому, что после выполнения такого кода табличные части остаются пустыми:
ПолучитьОбъект() и выполняется промежуточная запись, то ссылка в базе данных обновляется. Экземпляр ДокументРасчета, находящийся в исходной процедуре, становится устаревшим. При финальном вызове ДокументРасчета.Записать() старое состояние объекта затирает изменения.ПередЗаписью или подписках. В модуле объекта или подписках на события документа может быть заложена проверка условий. Если документ записывается программно и обязательные для проведения реквизиты не были заполнены так, как это делает форма, обработчик может принудительно сбросить строки.Знач). Если в вызываемой процедуре параметр объекта объявлен со спецификатором Знач и внутри процедуры табличная часть пересоздается или присваивается заново, вызывающий код не увидит изменений в переданном экземпляре.Для фоновых заданий, внешних обработок, интеграций и серверных методов создание и проведение документов должно выполняться исключительно на стороне сервера без обращения к клиентскому контексту и формам. Рассмотрим правильную структуру такого кода.
Для исключения конфликтов версий не следует выполнять промежуточную запись документа только ради получения ссылки. Если ссылка необходима до записи, используем метод УстановитьСсылкуНового():
&НаСервере
Процедура РассчитатьИПровестиДокумент(ДатаДокумента, Организация, ЛицевойСчет, Пользователь)
ДокументОбъект = Документы.Расчет.СоздатьДокумент();
// Генерируем ссылку для нового документа без записи в базу данных
НоваяСсылка = Документы.Расчет.ПолучитьСсылку();
ДокументОбъект.УстановитьСсылкуНового(НоваяСсылка);
ДокументОбъект.Дата = ДатаДокумента;
ДокументОбъект.УстановитьНовыйНомер();
ДокументОбъект.Организация = Организация;
ДокументОбъект.ЛицевойСчет = ЛицевойСчет;
ДокументОбъект.Автор = Пользователь;
// Заполняем табличные части напрямую в объекте
Документы.Расчет.ЗаполнитьТабличнуюЧастьИнформациейНеобходимойДляРасчетов(ДокументОбъект);
Документы.Расчет.РасчитатьНачисления(ДокументОбъект);
Документы.Расчет.ОтразитьДанныеТабличнойЧастиРасчетыВРегистрахРасчетов(ДокументОбъект);
Документы.Расчет.РасчитатьЛьготы(ДокументОбъект);
Документы.Расчет.ОчиститьДвиженияПоРегиструРасчетаНеобходимыхДляРасчетаЛьгот(ДокументОбъект);
// Проводим документ один раз со всеми заполненными данными
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
КонецПроцедуры
Если в конфигурации логика очистки или проверки зависит от признака интерактивности, можно явно указать платформе режим загрузки данных перед записью:
ДокументОбъект.ОбменДанными.Загрузка = Истина;
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
Важно: режим ОбменДанными.Загрузка = Истина отключает выполнение обработчиков ПередЗаписью и ПриЗаписи, а также контроль проведения, поэтому его следует применять осознанно, если требуется загрузить подготовленные данные без дополнительных проверок.
Проанализируем архитектурно правильный подход к структуре кода. Логику наполнения табличных частей оптимально выносить в экспортные процедуры самого модуля объекта. Это исключает передачу объекта через модуль менеджера и гарантирует работу с текущим контекстом через ЭтотОбъект.
Посмотрим на пример реализации метода внутри модуля объекта документа:
// В модуле объекта документа "Расчет"
Процедура ЗаполнитьИРассчитать() Экспорт
ЭтотОбъект.Расчеты.Очистить();
ЭтотОбъект.Льготы.Очистить();
// Вызов внутренних процедур заполнения собственных коллекций
ЗаполнитьДанныеДляРасчета();
ВыполнитьРасчетНачислений();
ВыполнитьРасчетЛьгот();
КонецПроцедуры
Тогда вызывающий программный код становится компактным и надежным:
ДокументОбъект = Документы.Расчет.СоздатьДокумент();
ДокументОбъект.Дата = ДатаДокумента;
ДокументОбъект.УстановитьНовыйНомер();
ДокументОбъект.Организация = Организация;
ДокументОбъект.ЛицевойСчет = ЛицевойСчет;
ДокументОбъект.Автор = Пользователь;
// Вызываем расчет напрямую у объекта
ДокументОбъект.ЗаполнитьИРассчитать();
// Записываем с проведением
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Если конфигурация сильно завязана на контекст управляемой формы (например, формулы и флаги рассчитываются исключительно в процедурах формы при событии ПриСозданииНаСервере или через элементы интерфейса) и нет возможности быстро переписать логику на чистый серверный модуль, применяется заполнение через открытие или контекст формы.
Разберем по шагам, как эмулировать работу пользователя через форму программно:
&НаКлиенте
Процедура РассчитатьАбонентаЧерезФорму(ДатаДокумента)
// Шаг 1. Создаем предварительный документ на сервере и получаем ссылку
ДокументСсылка = СоздатьНовыйДокументНаСервере(ДатаДокумента);
// Шаг 2. Инициализируем форму созданного документа
ПараметрыФормы = Новый Структура("Ключ", ДокументСсылка);
ФормаДокумента = ПолучитьФорму("Документ.Расчет.ФормаОбъекта", ПараметрыФормы);
// Шаг 3. Получаем структуру данных формы и заполняем ее на сервере
ДанныеОбъекта = ФормаДокумента.Объект;
ЗаполнитьДанныеФормыНаСервере(ДанныеОбъекта);
// Шаг 4. Копируем обновленные данные обратно в контекст формы
КопироватьДанныеФормы(ДанныеОбъекта, ФормаДокумента.Объект);
// Шаг 5. Записываем и проводим документ через форму
ПараметрыЗаписи = Новый Структура("РежимЗаписи", РежимЗаписиДокумента.Проведение);
ФормаДокумента.Записать(ПараметрыЗаписи);
КонецПроцедуры
&НаСервере
Функция СоздатьНовыйДокументНаСервере(ДатаДокумента)
НовыйДокумент = Документы.Расчет.СоздатьДокумент();
НовыйДокумент.Дата = ДатаДокумента;
НовыйДокумент.УстановитьНовыйНомер();
НовыйДокумент.Организация = Организация;
НовыйДокумент.ЛицевойСчет = ЛицевойСчет;
НовыйДокумент.Автор = Пользователь;
НовыйДокумент.Записать();
Возврат НовыйДокумент.Ссылка;
КонецФункции
&НаСервере
Процедура ЗаполнитьДанныеФормыНаСервере(ДанныеОбъекта)
ДанныеОбъекта.Расчеты.Очистить();
ДанныеОбъекта.Льготы.Очистить();
Документы.Расчет.ЗаполнитьТабличнуюЧастьИнформациейНеобходимойДляРасчетов(ДанныеОбъекта);
Документы.Расчет.РасчитатьНачисления(ДанныеОбъекта);
Документы.Расчет.ОтразитьДанныеТабличнойЧастиРасчетыВРегистрахРасчетов(ДанныеОбъекта);
Документы.Расчет.РасчитатьЛьготы(ДанныеОбъекта);
Документы.Расчет.ОчиститьДвиженияПоРегиструРасчетаНеобходимыхДляРасчетаЛьгот(ДанныеОбъекта);
КонецПроцедуры
Ограничения данного метода: этот подход не может работать в регламентных и фоновых заданиях, Web- и HTTP-сервисах, так как метод ПолучитьФорму() доступен только в клиентском контексте. Кроме того, передача данных формы между клиентом и сервером замедляет групповую обработку документов.
Если после выполнения программного кода табличная часть всё равно остается пустой, выполним следующие шаги диагностики:
ПередЗаписью, ПриЗаписи и ОбработкаПроведения на наличие условий, очищающих табличные части при незаполненных реквизитах.ДокументОбъект, срабатывающих перед записью документа данного вида.Ссылка.ПолучитьОбъект() с перезаписью состояния объекта.