Как обновить табличную часть документа из формы внешней обработки заполнения?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке внешних обработок заполнения документов часто возникает задача: пользователь открывает форму обработки, производит вычисления или подбор данных, а затем результат должен попасть в табличную часть документа-владельца (например, при заполнении табличной части по данным из других документов). Казалось бы, задача тривиальная, но многие разработчики сталкиваются с тем, что данные в отладчике изменяются, а на экране в форме документа ничего не происходит, либо возникают ошибки доступа.

Разберем детально, почему так происходит, какие ограничения накладывает платформа 1С:Предприятие на работу с формой владельца, и рассмотрим правильные способы решения этой задачи.

Суть проблемы: контекст и защита данных формы

Рассмотрим ситуацию, описанную в теме. Мы находимся в форме внешней обработки. У этой формы есть свойство ВладелецФормы, которое ссылается на открытую форму документа. Такое взаимодействие является частным случаем использования на форме одного объекта функциональности другого. Мы хотим добавить строки в табличную часть этого документа.

Первая мысль, которая приходит в голову — передать объект владельца на сервер, изменить его там и вернуть обратно. Или попытаться присвоить измененные данные напрямую. Однако, при попытке выполнить код вида:


// Ошибочный подход
ТекОбъект = ВладелецФормы.Объект;
ЗаполнитьНаСервере(ТекОбъект); 
ВладелецФормы.Объект = ТекОбъект; // Здесь возникнет ошибка

Мы получим ошибку: «Нельзя изменять поле, содержащее объект данных формы». Почему это происходит?

  1. Проекция данных: Свойство Объект управляемой формы — это не просто структура данных, это сложный механизм (ДанныеФормыСтруктура), который жестко связан с элементами интерфейса и механизмом отслеживания изменений (модифицированности). Платформа запрещает "грубую" подмену этого объекта целиком, чтобы не нарушить связи с UI.
  2. Изоляция контекста: Когда мы передаем ВладелецФормы.Объект на сервер обработки, происходит сериализация. Мы работаем с копией данных. Изменения этой копии на сервере обработки никак не влияют на оригинал, находящийся в контексте формы документа, пока мы явно не вернем их на клиент и не применим.

Также неэффективен метод ЭтаФорма.Прочитать() (или обращение к методу чтения владельца), если данные еще не записаны в базу данных. Метод Прочитать() заново загружает данные из БД, затирая все, что мы пытались добавить программно в памяти.

Решение 1: Заполнение на клиенте (Наиболее надежный способ для UI)

Самый простой и надежный способ обновить интерфейс владельца без ошибок — производить изменение данных непосредственно на клиенте. Платформа 1С позволяет добавлять строки в ДанныеФормыКоллекция (табличную часть) на клиенте, и интерфейс при этом обновляется мгновенно.

Алгоритм действий должен быть следующим:

  1. На сервере обработки мы подготавливаем данные (например, делаем запрос, расчеты) и помещаем их во временную структуру (Массив структур или Таблица значений).
  2. Возвращаем эти "сырые" данные на клиент в форму обработки.
  3. На клиенте перебираем полученные данные и построчно добавляем их в ВладелецФормы.Объект.

Рассмотрим пример реализации:


&НаКлиенте
Процедура КомандаЗаполнить(Команда)
    
    // 1. Получаем готовые данные с сервера обработки
    ДанныеДляЗаполнения = ПолучитьДанныеНаСервере();
    
    // 2. Обращаемся к владельцу формы
    ФормаДокумента = ВладелецФормы;
    ОбъектДокумента = ФормаДокумента.Объект;
    
    // 3. Заполняем табличную часть на клиенте
    Для Каждого СтрокаДанных Из ДанныеДляЗаполнения Цикл
        
        // Добавляем новую строку в ТЧ документа
        НоваяСтрока = ОбъектДокумента.Товары.Добавить();
        
        // Заполняем свойства
        НоваяСтрока.Номенклатура = СтрокаДанных.Номенклатура;
        НоваяСтрока.Количество   = СтрокаДанных.Количество;
        НоваяСтрока.Цена         = СтрокаДанных.Цена;
        
        // Можно вызвать пересчет сумм, если он вынесен в общие модули или доступен
        // НоваяСтрока.Сумма = НоваяСтрока.Количество * НоваяСтрока.Цена;
        
    КонецЦикла;
    
    // 4. Уведомляем форму о модификации, чтобы загорелась "звездочка"
    ФормаДокумента.Модифицированность = Истина;
    
    // Закрываем обработку, если нужно
    ЭтаФорма.Закрыть();
    
КонецПроцедуры

&НаСервере
Функция ПолучитьДанныеНаСервере()
    
    // Здесь выполняем запросы к БД
    Результат = Новый Массив;
    
    // Пример заполнения массива структур
    // В реальной задаче здесь будет Запрос.Выполнить().Выгрузить() и обход выборки
    Стр = Новый Структура("Номенклатура, Количество, Цена");
    Стр.Номенклатура = Справочники.Номенклатура.НайтиПоКоду("00001");
    Стр.Количество = 10;
    Стр.Цена = 100;
    Результат.Добавить(Стр);
    
    Возврат Результат;
    
КонецФункции

Этот метод хорош тем, что он не требует изменения конфигурации документа и работает через внешнюю обработку "на лету".

Решение 2: Использование механизма оповещений (Архитектурно правильный)

Если у вас есть возможность вносить минимальные изменения в форму документа (или использовать расширение конфигурации для формы документа), то более правильным с точки зрения архитектуры будет механизм ОповеститьОВыборе.

Суть метода: форма обработки не должна "лезть" в данные владельца. Она должна лишь вернуть результат своей работы, а владелец сам решит, как его обработать.

Шаг 1. В форме обработки:


&НаКлиенте
Процедура КомандаПеренестиВДокумент(Команда)
    
    Данные = ПолучитьДанныеНаСервере(); // Та же функция, что и выше
    
    // Отправляем данные владельцу и закрываем форму
    ОповеститьОВыборе(Данные);
    
КонецПроцедуры

Шаг 2. В форме документа (или в расширении формы документа):

Необходимо переопределить событие ОбработкаВыбора.


&НаКлиенте
Процедура ОбработкаВыбора(ВыбранноеЗначение, ИсточникВыбора)
    
    // Проверяем, что это пришло от нашей обработки
    // Можно проверять тип значения или источник
    Если ТипЗнч(ВыбранноеЗначение) = Тип("Массив") Тогда
        
        Для Каждого Элемент Из ВыбранноеЗначение Цикл
             НоваяСтрока = Объект.Товары.Добавить();
             ЗаполнитьЗначенияСвойств(НоваяСтрока, Элемент);
        КонецЦикла;
        
    КонецЕсли;
    
КонецПроцедуры

При открытии формы обработки нужно указать владельца: ОткрытьФорму("ВнешняяОбработка.МояОбработка.Форма", Параметры, ЭтаФорма). Третий параметр ЭтаФорма делает документ владельцем, который и поймает оповещение о выборе.

Решение 3: Для типовых конфигураций (БСП)

Если вы работаете с современными типовыми конфигурациями (УТ 11, ERP, КА, БП 3.0), там внедрена подсистема "Заполнение объектов". В этом случае вам часто даже не нужно создавать форму обработки.

Достаточно в модуле объекта обработки (не формы!) реализовать экспортную процедуру ВыполнитьКоманду. Упростить эту задачу может шаблон дополнительной обработки для БСП. Платформа сама передаст вам массив объектов (документов), которые нужно заполнить.


// В модуле объекта внешней обработки

Процедура ВыполнитьКоманду(ИдентификаторКоманды, ОбъектыНазначения, ПараметрыВыполнения) Экспорт
    
    Для Каждого ДокументСсылка Из ОбъектыНазначения Цикл
        
        // Получаем объект документа для изменения
        ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
        
        // Меняем табличную часть
        НоваяСтрока = ДокументОбъект.Товары.Добавить();
        НоваяСтрока.Количество = 5;
        
        // Записываем документ
        ДокументОбъект.Записать();
        
    КонецЦикла;
    
КонецПроцедуры

Однако этот метод подразумевает запись документа в базу данных. Если же задача стоит именно в заполнении формы перед записью (чтобы пользователь мог проверить данные), то возвращаемся к Решению 1.

Важные нюансы

Разберем еще несколько моментов, которые помогут избежать ошибок:

Резюме

Для реализации задачи заполнения табличной части документа из формы обработки без записи документа в БД, наиболее эффективным является построчное добавление данных в коллекцию ВладелецФормы.Объект на стороне клиента.

← На главную