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