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