При разработке в среде 1С:Предприятие 8 на управляемых формах часто возникает задача открыть форму нового, еще не записанного документа (например, Документ.ПеремещениеТоваров) и заполнить его табличную часть данными, сформированными во внешней обработке или другой форме. Для этого стандартно используется метод ОткрытьФорму() с передачей структуры ЗначенияЗаполнения.
Однако на практике разработчики сталкиваются с серьезным препятствием: на клиенте табличные данные представлены типом ДанныеФормыКоллекция. Когда эти данные передаются на сервер в процедуру ОбработкаЗаполнения, платформа не преобразует их автоматически в полноценную ТаблицуЗначений. Если в типовом коде конфигурации (например, в УТ 11.4, КА или ERP) присутствует проверка колонок таблицы или обращение к методам, специфичным для ТЗ, система выдает ошибку: «Поле объекта не обнаружено (Колонки)».
В этой статье мы подробно разберем, почему возникает данная проблема, рассмотрим внутренние механизмы платформы 1С и изучим несколько надежных способов обхода этого ограничения без изменения типовой конфигурации.
Проанализируем клиент-серверное взаимодействие в 1С. Объект ТаблицаЗначений является мутабельным типом данных. Это значит, что он может изменяться, и платформа принципиально запрещает передачу таких объектов напрямую между клиентом и сервером. На стороне клиента в управляемом интерфейсе вместо таблицы значений всегда используется ее проекция — ДанныеФормыКоллекция.
Когда мы вызываем код открытия формы на клиенте:
ПараметрыФормы = Новый Структура("ЗначенияЗаполнения", ПараметрыЗаполнения);
ОткрытьФорму("Документ.ПеремещениеТоваров.ФормаОбъекта", ПараметрыФормы);
Платформа передает структуру ПараметрыЗаполнения на сервер, где инициализируется новый объект документа и вызывается событие ОбработкаЗаполнения. Проблема заключается в том, что свойство Товары внутри структуры ДанныеЗаполнения на сервере сохраняет тип ДанныеФормыКоллекция (или преобразуется в массив структур).
Давайте посмотрим на пример типового кода в УТ 11, который приводит к ошибке:
Функция ДокументОснованиеПриЗаполнении(ДанныеЗаполнения)
Если ТипЗнч(ДанныеЗаполнения) = Тип("Структура")
И ДанныеЗаполнения.Свойство("Товары")
И ДанныеЗаполнения.Товары.Колонки.Найти("ЗаказНаПеремещение") <> Неопределено Тогда
Возврат ДанныеЗаполнения.Товары[0].ЗаказНаПеремещение;
КонецЕсли;
Возврат Неопределено;
КонецФункции
Поскольку ДанныеЗаполнения.Товары имеет тип ДанныеФормыКоллекция, попытка обратиться к свойству Колонки вызывает аварийное завершение работы программы. У этого типа просто нет коллекции Колонки, в отличие от ТаблицыЗначений.
Разберем по шагам, как решить эту проблему, сохранив типовую конфигурацию на поддержке.
Самый элегантный и современный способ — использовать механизм расширений конфигурации. Мы можем перехватить управление непосредственно перед выполнением типовой процедуры ОбработкаЗаполнения в модуле объекта документа и преобразовать коллекцию в полноценную таблицу значений.
Для реализации этого решения выполним следующие действия:
ПеремещениеТоваров и его модуль объекта.&Перед для процедуры ОбработкаЗаполнения.Напишем следующий код в расширении:
&Перед("ОбработкаЗаполнения")
Процедура Расш_ПередОбработкаЗаполнения(ДанныеЗаполнения, ТекстЗаполнения, СтандартнаяОбработка)
Если ТипЗнч(ДанныеЗаполнения) = Тип("Структура")
И ДанныеЗаполнения.Свойство("Товары") Тогда
// Проверяем, если пришла коллекция форм, конвертируем ее в ТЗ
Если ТипЗнч(ДанныеЗаполнения.Товары) = Тип("ДанныеФормыКоллекция") Тогда
// Преобразуем ДанныеФормыКоллекция в полноценную Таблицу Значений
ТаблицаТоваров = ДанныеЗаполнения.Товары.Выгрузить();
// Подменяем свойство в структуре заполнения на настоящую ТЗ
ДанныеЗаполнения.Вставить("Товары", ТаблицаТоваров);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Как это работает: Метод Выгрузить() у объекта типа ДанныеФормыКоллекция работает на сервере и возвращает полноценную ТаблицуЗначений со всей структурой колонок и данными. Мы подменяем элемент структуры ДанныеЗаполнения перед тем, как управление перейдет к типовому коду. В результате типовая функция успешно находит свойство Колонки и отрабатывает без ошибок.
Если по каким-то причинам вы не хотите затрагивать модуль объекта документа через расширение (например, правила корпоративной разработки запрещают доработку бизнес-логики проведения и заполнения объектов), мы можем перенести логику в модуль управляемой формы документа.
Проанализируем этот сценарий по шагам:
Шаг 1. Формирование и отправка данных из вызывающей формы (обработки)
В модуле вызывающей формы на сервере мы помещаем подготовленную таблицу значений во временное хранилище и передаем полученный адрес на клиент:
&НаСервере
Функция ПодготовитьДанныеВоВременномХранилище()
// Создаем и заполняем таблицу значений
ТабТовары = Новый ТаблицаЗначений;
ТабТовары.Колонки.Добавить("Номенклатура");
ТабТовары.Колонки.Добавить("Количество");
ТабТовары.Колонки.Добавить("ЗаказНаПеремещение");
// Заполняем строки...
НоваяСтрока = ТабТовары.Добавить();
НоваяСтрока.Номенклатура = Справочники.Номенклатура.НайтиПоКоду("000001");
НоваяСтрока.Количество = 10;
// Помещаем ТЗ в хранилище, привязывая к Уникальному Идентификатору формы
Возврат ПоместитьВоВременноеХранилище(ТабТовары, УникальныйИдентификатор);
КонецФункции
&НаКлиенте
Процедура ОткрытьДокументПеремещения()
АдресХранилища = ПодготовитьДанныеВоВременномХранилище();
// Передаем адрес хранилища через параметры формы под произвольным ключом
ПараметрыФормы = Новый Структура;
ПараметрыФормы.Вставить("АдресВременногоХранилищаТоваров", АдресХранилища);
ОткрытьФорму("Документ.ПеремещениеТоваров.ФормаОбъекта", ПараметрыФормы);
КонецПроцедуры
Шаг 2. Прием данных в расширении формы документа
Добавим в наше расширение форму документа ПеремещениеТоваров (обычно это ФормаОбъекта). Создадим обработчик события ПриСозданииНаСервере с аннотацией &После:
&После("ПриСозданииНаСервере")
Процедура Расш_ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Проверяем наличие нашего уникального параметра
Если Параметры.Свойство("АдресВременногоХранилищаТоваров") Тогда
// Получаем ТЗ из временного хранилища
ТаблицаИзХранилища = ПолучитьИзВременногоХранилища(Параметры.АдресВременногоХранилищаТоваров);
Если ТипЗнч(ТаблицаИзХранилища) = Тип("ТаблицаЗначений") Тогда
// Напрямую заполняем табличную часть формы документа
Объект.Товары.Загрузить(ТаблицаИзХранилища);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Этот способ полностью изолирует логику заполнения внутри контекста формы, не затрагивая транзакционные механизмы создания объекта на сервере базы данных.
В ходе обсуждения на форуме был упомянут очень интересный и малоизвестный лайфхак платформы 1С. Обычно попытка написать на клиенте Новый ТаблицаЗначений вызывает ошибку компиляции кода. Однако платформу можно обойти, используя объект ОписаниеТипов:
ОписаниеТЗ = Новый ОписаниеТипов("ТаблицаЗначений");
ТабЗначений = ОписаниеТЗ.ПривестиЗначение();
Проанализируем этот прием: Действительно, этот код успешно скомпилируется и выполнится в тонком клиенте. Переменная ТабЗначений получит тип ТаблицаЗначений. Вы даже сможете добавлять в нее строки и колонки на клиенте.
Но внимание! Данный лайфхак не решает проблему автора темы. Как только вы попытаетесь передать эту «клиентскую» таблицу значений в параметры метода ОткрытьФорму() или в любой другой серверный вызов, платформа выдаст стандартное исключение:
«Ошибка передачи данных между клиентом и сервером. Значение недопустимого типа (мутабельное значение)».
Это связано с тем, что физическая природа таблицы значений на клиенте не меняется — она остается сложным мутабельным объектом, не подлежащим сериализации для передачи по каналам связи между клиентским приложением и сервером приложений 1С.
При передаче табличных данных в параметры заполнения форм документов всегда учитывайте специфику типов данных управляемого интерфейса:
ДанныеФормыКоллекция.ОбработкаЗаполнения жестко требует тип ТаблицаЗначений, используйте расширение модуля объекта с методом Выгрузить() для безопасного преобразования типов на сервере.ПриСозданииНаСервере.Используя предложенные нами решения, вы сможете сохранить конфигурацию полностью типовой, обеспечив стабильную работу программы и простоту последующих обновлений.