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