При разработке обработок, связанных с формированием и сохранением большого количества документов, например, квитанций или отчетов, часто возникает необходимость временно хранить эти данные перед их финальной записью в файлы. Классический сценарий – когда для каждого объекта (например, лицевого счета) нужно сформировать отдельный табличный документ и сохранить его в отдельный файл. Если при этом работать с одним и тем же объектом табличного документа в цикле, можно столкнуться с проблемой перезаписи, когда в итоге сохраняются только данные последнего обработанного элемента. Давайте рассмотрим, почему это происходит и как правильно организовать работу с временным хранилищем в таких случаях.
Изначально разработчик столкнулся с тем, что при формировании квитанций для множества лицевых счетов и попытке сохранения их в файлы, сохранялись только данные по последнему лицевому счету. Первая мысль, которая часто приходит на ум – использовать временное хранилище 1С (ПоместитьВоВременноеХранилище), чтобы сохранить каждый табличный документ по отдельному адресу, а затем уже извлекать и записывать их в файлы.
Давайте проанализируем фрагмент кода, который описывает эту проблему:
Для Каждого ЭлементСписка Из ПараметрыОтчета.ТаблицаОбъектов Цикл
Если АдресПервойКвитанции = Адрес ИЛИ АдресПервойКвитанции = НЕОПРЕДЕЛЕНО Тогда
// АдресПервойКвитанции в самом начале всегда неопределено
Если АдресПервойКвитанции = НЕОПРЕДЕЛЕНО Тогда
УникальныйИдентификатор = Новый УникальныйИдентификатор;
СоответствиеАдресов = Новый Соответствие;
КонецЕсли;
СформироватьТекущуюКвитанциюФорма12(СтруктураОбщихТаблиц, ЭлементСписка.ЛицевойСчет, тмДокумент, тмОбщийДок, ДокументРезультат, ДокументРезультатВывод, ТекНомерВыводимой, ПараметрыОтчета, ОбщиеДанные, ЭтоПервая);
АдресПервойКвитанции = Адрес;
АдресВременногоХранилища = ПоместитьВоВременноеХранилище(ДокументРезультатВывод, УникальныйИдентификатор);
СоответствиеАдресов.Вставить(ИмяФайла, АдресВременногоХранилища);
Иначе // Если у нас изменился адрес ЛС то создаем новый адрес хранилища.
УникальныйИдентификатор = Новый УникальныйИдентификатор;
ДокументРезультатВывод.Очистить; // <-- Проблемная строка
СформироватьТекущуюКвитанциюФорма12(СтруктураОбщихТаблиц, ЭлементСписка.ЛицевойСчет, тмДокумент, тмОбщийДок, ДокументРезультат,ДокументРезультатВывод, ТекНомерВыводимой, ПараметрыОтчета, ОбщиеДанные, ЭтоПервая);
АдресПервойКвитанции = Адрес;
АдресВременногоХранилища = ПоместитьВоВременноеХранилище(ДокументРезультатВывод, УникальныйИдентификатор);
СоответствиеАдресов.Вставить(ИмяФайла, АдресВременногоХранилища);
КонецЕсли;
КонецЦикла;
Для Каждого Элемент Из СоответствиеАдресов Цикл
ТабДокЛС = ПолучитьИзВременногоХранилища(Элемент.Значение);
ТабДокЛС.Записать(КаталогФайла + Элемент.Ключ + ".pdf", ТипФайлаТабличногоДокумента.PDF);
КонецЦикла;
На первый взгляд, все кажется логичным: мы создаем новый УникальныйИдентификатор для каждого адреса хранилища, что, по идее, должно гарантировать уникальное местоположение для каждого табличного документа. Однако, как выяснилось, в конечном итоге сохраняются только последние данные. В чем же причина?
Причина кроется в том, как 1С работает с объектами по ссылке. Объект ТабличныйДокумент (переменная ДокументРезультатВывод) является изменяемым (мутабельным) объектом. Когда мы вызываем метод ПоместитьВоВременноеХранилище(ДокументРезультатВывод, УникальныйИдентификатор), во временное хранилище помещается не копия самого табличного документа, а фактически ссылка на этот объект. Это означает, что если мы изменим содержимое объекта ДокументРезультатВывод после того, как он был помещен в хранилище, эти изменения отразятся и на том "экземпляре", который находится во временном хранилище.
В приведенном коде, в каждой итерации цикла, мы работаем с одним и тем же объектом ДокументРезультатВывод. Даже вызов ДокументРезультатВывод.Очистить() лишь очищает содержимое текущего объекта, но не создает новый объект. После очистки, процедура СформироватьТекущуюКвитанциюФорма12 снова заполняет тот же самый объект новыми данными. Когда этот объект снова помещается во временное хранилище (даже по новому уникальному адресу), по сути, мы сохраняем ссылку на объект, который уже был модифицирован для следующего лицевого счета. В результате, к моменту завершения цикла, во всех адресах временного хранилища, которые мы сохранили в СоответствиеАдресов, будет находиться ссылка на последний заполненный объект ДокументРезультатВывод с данными последнего лицевого счета.
Для того чтобы каждый табличный документ был сохранен во временном хранилище как независимый объект, необходимо на каждой итерации цикла создавать новый экземпляр объекта ТабличныйДокумент. Это гарантирует, что каждый раз, когда мы вызываем ПоместитьВоВременноеХранилище, мы передаем совершенно новый объект, который не будет изменен последующими итерациями цикла.
Решение, как было подсказано, заключается в замене строки ДокументРезультатВывод.Очистить; на ДокументРезультатВывод = Новый ТабличныйДокумент;. Это позволит нам получить желаемый результат.
Давайте посмотрим на исправленный фрагмент кода:
Для Каждого ЭлементСписка Из ПараметрыОтчета.ТаблицаОбъектов Цикл
// В самом начале цикла или при смене адреса ЛС,
// всегда создаем новый экземпляр табличного документа
ДокументРезультатВывод = Новый ТабличныйДокумент; // <-- Исправленная строка
Если АдресПервойКвитанции = Адрес ИЛИ АдресПервойКвитанции = НЕОПРЕДЕЛЕНО Тогда
Если АдресПервойКвитанции = НЕОПРЕДЕЛЕНО Тогда
УникальныйИдентификатор = Новый УникальныйИдентификатор;
СоответствиеАдресов = Новый Соответствие;
КонецЕсли;
СформироватьТекущуюКвитанциюФорма12(СтруктураОбщихТаблиц, ЭлементСписка.ЛицевойСчет, тмДокумент, тмОбщийДок, ДокументРезультат,ДокументРезультатВывод, ТекНомерВыводимой, ПараметрыОтчета, ОбщиеДанные, ЭтоПервая);
АдресПервойКвитанции = Адрес;
АдресВременногоХранилища = ПоместитьВоВременноеХранилище(ДокументРезультатВывод, УникальныйИдентификатор);
СоответствиеАдресов.Вставить(ИмяФайла, АдресВременногоХранилища);
Иначе // Если у нас изменился адрес ЛС то создаем новый адрес хранилища.
УникальныйИдентификатор = Новый УникальныйИдентификатор;
// ДокументРезультатВывод = Новый ТабличныйДокумент; // Эта строка уже есть в начале цикла, здесь не нужна
СформироватьТекущуюКвитанциюФорма12(СтруктураОбщихТаблиц, ЭлементСписка.ЛицевойСчет, тмДокумент, тмОбщийДок, ДокументРезультат,ДокументРезультатВывод, ТекНомерВыводимой, ПараметрыОтчета, ОбщиеДанные, ЭтоПервая);
АдресПервойКвитанции = Адрес;
АдресВременногоХранилища = ПоместитьВоВременноеХранилище(ДокументРезультатВывод, УникальныйИдентификатор);
СоответствиеАдресов.Вставить(ИмяФайла, АдресВременногоХранилища);
КонецЕсли;
КонецЦикла;
Для Каждого Элемент Из СоответствиеАдресов Цикл
ТабДокЛС = ПолучитьИзВременногоХранилища(Элемент.Значение);
ТабДокЛС.Записать(КаталогФайла + Элемент.Ключ + ".pdf", ТипФайлаТабличногоДокумента.PDF);
КонецЦикла;
В этом исправленном коде мы перенесли инициализацию ДокументРезультатВывод = Новый ТабличныйДокумент; в самое начало каждой итерации цикла. Таким образом, перед формированием каждой новой квитанции, мы создаем совершенно новый, пустой объект ТабличныйДокумент. Затем, этот новый объект заполняется данными для текущего лицевого счета и помещается во временное хранилище. Поскольку каждый объект уникален, изменения в одном не влияют на другие, уже помещенные в хранилище. Это гарантирует, что каждый адрес во временном хранилище будет указывать на свою, уникальную версию табличного документа.
Обратите внимание, что создание Новый УникальныйИдентификатор на каждой итерации является правильным подходом, так как он обеспечивает получение нового адреса во временном хранилище для каждого нового объекта ТабличныйДокумент. Объект СоответствиеАдресов затем корректно связывает имя файла с уникальным адресом в хранилище, позволяя нам извлечь и записать каждый документ по его специфическому имени и содержимому.
Разбирая эту проблему, мы затрагиваем фундаментальные принципы работы с временным хранилищем в 1С. Давайте рассмотрим подробнее основные моменты, которые следует учитывать при использовании этого механизма:
ПоместитьВоВременноеХранилище(), которая возвращает строковый адрес, а затем можете получить их с помощью ПолучитьИзВременногоХранилища().ПоместитьВоВременноеХранилище() работает с тем, что ему передают. Для изменяемых объектов (таких как ТабличныйДокумент, коллекции, структуры, массивы и т.д.), если вы передадите ссылку на существующий объект, а затем измените этот объект, содержимое во временном хранилище также изменится. Чтобы сохранить "снимок" объекта в текущем состоянии, необходимо либо передавать неизменяемые типы, либо создавать копию изменяемого объекта перед помещением (например, Новый ТабличныйДокумент для табличного документа, или глубокую копию для коллекций, если это требуется).ПоместитьВоВременноеХранилище() без указания параметра Адрес или с НЕОПРЕДЕЛЕНО, система автоматически генерирует уникальный адрес для помещаемых данных. Если вы хотите явно управлять адресом (например, для перезаписи данных по существующему адресу), вы можете передать строку с желаемым адресом. В нашем случае, использование Новый УникальныйИдентификатор позволяет получить уникальную строку для каждого нового адреса.УникальныйИдентификатор формы в качестве адреса), данные будут храниться, пока существует эта форма. После закрытия формы данные автоматически удаляются.УдалитьИзВременногоХранилища(Адрес).ПоместитьВоВременноеХранилище(). В сериализованном виде объем не должен превышать 4 ГБ. Это важно учитывать при работе с очень большими табличными документами или коллекциями.ПолучитьИзВременногоХранилища(Адрес). Передавая ей адрес, который вы получили при помещении данных, вы можете получить обратно сохраненное значение.В нашем случае, когда мы помещаем множество табличных документов, крайне важно, чтобы каждый документ представлял собой отдельный объект. Правильное использование ДокументРезультатВывод = Новый ТабличныйДокумент; на каждой итерации цикла гарантирует, что во временном хранилище будут находиться действительно независимые копии квитанций, готовые к последующей обработке и сохранению в файлы. Этот подход является основой для надежной и предсказуемой работы с временными данными в 1С.