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