Разработка в среде «1С:Предприятие 8» требует строгого соблюдения стандартов проектирования и понимания жизненного цикла объектов. Часто начинающие (и даже опытные) специалисты сталкиваются с ситуацией, когда при программном создании документа на основании другого объекта его табличная часть остается пустой, хотя в отладчике весь код заполнения отрабатывает корректно. Давайте подробно разберем, почему возникает эта проблема, почему популярные «обходные пути» опасны для стабильности системы, и как решить задачу правильно и красиво.
Проанализируем ситуацию, описанную автором темы. Разработчик создал процедуру заполнения табличной части Температура и попытался вызвать её в событии ОбработкаПроведения. При этом в процедуре заполнения выполняется сложный запрос, в который передается параметр Ссылка текущего документа. На выходе из процедуры строки добавляются в табличную часть объекта, но при открытии формы документа пользователь видит пустую таблицу.
Здесь кроются сразу две серьезные архитектурные ошибки:
ОбработкаПроведения предназначен исключительно для генерации движений по регистрам накопления, сведений или бухгалтерии. К моменту начала проведения документ уже физически записан в базу данных. Любые изменения реквизитов или табличных частей объекта ЭтотОбъект внутри проведения просто останутся в оперативной памяти и не будут сохранены в СУБД, если не вызвать принудительную перезапись (что делать категорически нельзя).ПередЗаписью) у объекта в памяти уже может быть сформирована уникальная ссылка (GUID). Однако в таблицах базы данных этой записи еще физически не существует. Запрос СУБД вида ГДЕ Ссылка = &Ссылка обращается к жесткому диску и логично возвращает 0 строк. Данные просто неоткуда прочитать, пока транзакция записи не будет завершена.Рассмотрим, чем опасен предложенный на форуме «вредный совет» с вызовом метода Записать() внутри обработчиков записи или проведения. Если принудительно вызывать перезапись объекта, система инициирует повторный запуск всей цепочки событий (ПередЗаписью, ПриЗаписи и т.д.). Это приводит к бесконечной рекурсии, жестким блокировкам таблиц базы данных и резкому падению производительности системы при одновременной работе пользователей. Даже использование свойства ДополнительныеСвойства для обхода повторного вызова является лишь «костылем», усложняющим дальнейшую поддержку кода.
---
Если наш документ создается на основании другого документа, платформа предоставляет для этого штатный и наиболее правильный инструмент — обработчик события ОбработкаЗаполнения в модуле объекта. Этот метод вызывается в самый первый момент зарождения нового объекта в памяти компьютера, еще до того, как пользователю будет показана форма документа.
Давайте разберем по шагам, как переписать логику получения данных. Главный секрет успеха заключается в том, что нам нужно делать запрос не к создаваемому документу, а к документу-основанию, ссылка на который передается в параметре ДанныеЗаполнения.
Посмотрим на пример правильной реализации в модуле объекта:
Процедура ОбработкаЗаполнения(ДанныеЗаполнения, ТекстЗаполнения, СтандартнаяОбработка)
// Проверяем, что документ создается на основании нужного нам типа документа
Если ТипЗнч(ДанныеЗаполнения) = Тип("ДокументСсылка.ЗаказПокупателя") Тогда
// 1. Заполняем основные реквизиты шапки напрямую из источника
ЗаполнитьЗначенияСвойств(ЭтотОбъект, ДанныеЗаполнения);
// 2. Формируем запрос для получения данных для новой табличной части
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПогодаВРегионах.ТемператураВоздуха КАК ТемператураВоздуха,
| ПогодаВРегионах.Влажность КАК Влажность
|ИЗ
| РегистрСведений.ПогодаВРегионах КАК ПогодаВРегионах
|ГДЕ
| ПогодаВРегионах.АдресДоставки = &АдресДоставки";
// Передаем параметры из документа-основания
Запрос.УстановитьПараметр("АдресДоставки", ДанныеЗаполнения.АдресДоставки);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Заполняем ТЧ "Температура" нашего нового объекта
НоваяСтрока = Температура.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
НоваяСтрока.ДатаВыгрузкиРН = НачалоДня(ТекущаяДата());
КонецЦикла;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
При таком подходе пользователь сразу же при открытии формы увидит полностью заполненную табличную часть. При этом база данных не нагружается лишними транзакциями записи, а код полностью соответствует стандартам разработки фирмы «1С».
---
Бывают ситуации, когда заполнение табличной части должно происходить непосредственно перед записью документа и зависеть от реквизитов, которые пользователь заполнил в шапке вручную (например, выбранный склад, организация или контрагент). Так как документ еще не записан, мы по-прежнему не можем делать запрос по условию ГДЕ Ссылка = &Ссылка.
Выясним, как обойти это ограничение. Мы можем передавать значения реквизитов шапки в параметры запроса напрямую из оперативной памяти (из свойств ЭтотОбъект):
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Проверяем, что это новый документ и его табличная часть еще не заполнялась
Если ЭтоНовый() И Температура.Количество() = 0 Тогда
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПрогнозыТемператур.Значение КАК Значение
|ИЗ
| РегистрСведений.ПрогнозыТемператур КАК ПрогнозыТемператур
|ГДЕ
| ПрогнозыТемператур.Организация = &Организация
| И ПрогнозыТемператур.Склад = &Склад";
// Передаем значения реквизитов напрямую из памяти объекта
Запрос.УстановитьПараметр("Организация", ЭтотОбъект.Организация);
Запрос.УстановитьПараметр("Склад", ЭтотОбъект.Склад);
Результат = Запрос.Выполнить();
Если Не Результат.Пустой() Тогда
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
НоваяСтрока = Температура.Добавить();
НоваяСтрока.Значение = Выборка.Значение;
КонецЦикла;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
---
Рассмотрим более сложный сценарий. Что делать, если алгоритм заполнения новой табличной части зависит от анализа строк другой табличной части этого же (еще не сохраненного в базе) документа? Например, нам нужно проанализировать список товаров из ТЧ Товары и на основе этих данных заполнить ТЧ Температура.
Поскольку в СУБД строк нашей таблицы товаров еще нет, мы должны передать существующую в памяти таблицу значений в запрос в качестве параметра и поместить ее во временную таблицу (ВТ). Проанализируем, как это реализовать:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
Если Температура.Количество() = 0 Тогда
// 1. Выгружаем ТЧ "Товары" из памяти в обычную Таблицу Значений
ТаблицаТоваров = Товары.Выгрузить();
// 2. Инициализируем менеджер временных таблиц для запроса
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
// Передаем ТЗ как параметр запроса
Запрос.Текст =
"ВЫБРАТЬ
| ВТ_Товары.Номенклатура КАК Номенклатура,
| ВТ_Товары.Количество КАК Количество
|ПОМЕСТИТЬ ВТ_ТоварыИзПамяти
|ИЗ
| &ТаблицаТоваров КАК ВТ_Товары
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_ТоварыИзПамяти.Номенклатура КАК Номенклатура,
| УсловияХранения.ТемпературныйРежим КАК ТемпературныйРежим
|ИЗ
| ВТ_ТоварыИзПамяти КАК ВТ_ТоварыИзПамяти
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.УсловияХранения КАК УсловияХранения
| ПО ВТ_ТоварыИзПамяти.Номенклатура = УсловияХранения.Номенклатура";
Запрос.УстановитьПараметр("ТаблицаТоваров", ТаблицаТоваров);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
НоваяСтрока = Температура.Добавить();
НоваяСтрока.Номенклатура = Выборка.Номенклатура;
НоваяСтрока.Режим = Выборка.ТемпературныйРежим;
КонецЦикла;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Использование временных таблиц для данных из оперативной памяти — мощный инструмент разработчика 1С, который позволяет выполнять сложные соединения и расчеты на уровне СУБД без необходимости предварительной физической записи документа в базу.
---
Иногда возникают специфические бизнес-требования, когда ссылку на создаваемый документ необходимо знать заранее. Например, если в процессе заполнения документа нам нужно параллельно записать данные в независимый регистр сведений, где в качестве измерения выступает ссылка на наш еще не записанный документ.
Для решения этой задачи в платформу встроен механизм предварительного резервирования GUID. Сделать это можно с помощью метода УстановитьСсылкуНового():
Если ЭтотОбъект.ЭтоНовый() Тогда
// Генерируем новый Уникальный Идентификатор и получаем пустую ссылку нужного типа
НоваяСсылка = Документы.РеализацияТоваровУслуг.ПолучитьСсылку(Новый УникальныйИдентификатор);
// Назначаем эту ссылку объекту. База данных применит её при физической записи
ЭтотОбъект.УстановитьСсылкуНового(НоваяСсылка);
КонецЕсли;
// Теперь переменную "НоваяСсылка" можно передавать в другие процедуры и функции
// для связывания подчиненных объектов в рамках одной транзакции.
Важное примечание: Этот метод решает проблему связывания объектов в памяти, но он не позволяет делать запросы к таблице этого документа в БД. Ссылка зарезервирована, но физическая запись в таблице СУБД появится только после успешного завершения транзакции записи объекта.
Для создания надежных и быстродействующих решений в 1С всегда следуйте золотым правилам архитектуры:
ОбработкаПроведения.ОбработкаЗаполнения и считывайте данные из параметра ДанныеЗаполнения.