Как выбрать документ на клиенте и перенести данные его табличной части в управляемом приложении 1С

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

Разработка внешних обработок заполнения табличных частей — одна из наиболее частых задач при адаптации конфигураций под требования пользователей. При переходе с обычных форм на управляемые разработчики часто сталкиваются с трудностями разделения контекста выполнения кода на клиентский и серверный. В этой статье мы подробно проанализируем, почему возникает ошибка получения табличной части на клиенте, разберем скрытые опасности «быстрых» решений и построим надежный, быстрый и архитектурно правильный алгоритм переноса данных между документами.

Анализ исходной проблемы и причин возникновения ошибок

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

Первая ошибка, с которой столкнулся разработчик, выглядит следующим образом: Поле объекта не обнаружено (Товары) при попытке выполнить цикл:


Для Каждого Стр Из ВыбЗнач.Товары Цикл
    // Код заполнения
КонецЦикла;

Выясним причину этого поведения. Переменная ВыбЗнач, возвращаемая интерактивным методом выбора, имеет тип ДокументСсылка.ПоступлениеТоваровУслуг. На клиенте ссылка является лишь уникальным идентификатором записи в базе данных. Она не содержит в себе данных табличных частей и других реквизитов (за исключением тех, что входят в состав быстрого выбора или представлены строковым представлением). Попытка напрямую обратиться к свойству Товары ссылочного типа на клиенте всегда будет приводить к аварийному завершению работы программы.

Разбор критических ошибок «рабочего» решения автора

В ходе обсуждения на форуме автор пришел к промежуточному решению — перенести выполнение кода на сервер и искать документ-приемник по номеру. Несмотря на то что автору удалось избавиться от ошибок выполнения, его код содержит серьезные архитектурные изъяны. Давайте детально проанализируем, почему этот код крайне опасен для внедрения в рабочую базу данных.

Вспомним серверную процедуру, предложенную автором темы:


&НаСервере
Процедура ЗаполнитДокНаСервере(ДокПоступления, НомерДокРеализации)
    ДокПродажи = Документы.РеализацияТоваров.НайтиПоНомеру(НомерДокРеализации, ТекущаяДата());
    ДДД = ДокПродажи.ПолучитьОбъект();
    // ... Заполнение ТЧ объекта ...
    ЗначениеВРеквизитФормы(ДокПродажи, "Объект");
КонецПроцедуры

В этом небольшом фрагменте кода скрыто сразу несколько критических проблем:

  1. Несоответствие типов в методе ЗначениеВРеквизитФормы. Первым параметром в данный метод должен передаваться прикладной объект (в данном случае ДокументОбъект.РеализацияТоваров, находящийся в переменной ДДД). Автор же передает туда ДокПродажи — то есть ссылку. Этот код вызовет системную ошибку приведения типов при первой же попытке выполнения.
  2. Потеря несохраненных данных пользователя. Это самый опасный логический дефект. Метод ПолучитьОбъект() считывает состояние документа из базы данных. Если пользователь открыл документ реализации, изменил в нем вручную контрагента, склад, цены или комментарий, но еще не нажал кнопку «Записать», то при выполнении обработки все его изменения будут безвозвратно затерты! Код получит старую версию из БД, внесет туда товары и попытается обновить форму.
  3. Невозможность работы с новыми документами. Если пользователь только что создал документ реализации и еще ни разу его не записывал, то у него нет номера в базе данных (или номер еще не зафиксирован). Метод НайтиПоНомеру вернет пустую ссылку, а последующий вызов ПолучитьОбъект() вызовет фатальный сбой системы с ошибкой Элемент не выбран!.
  4. Контекстная путаница. Метод ЗначениеВРеквизитФормы(..., "Объект") пытается модифицировать основной реквизит текущей формы. Но текущая форма — это форма внешней обработки, а не форма документа-приемника! Попытка записать данные документа в реквизит формы внешней обработки полностью нарушит логику работы интерфейса.

Правильный и безопасный алгоритм переноса данных

Для того чтобы написать надежный код на управляемых формах, необходимо следовать ключевому правилу: интерактивные обработки заполнения должны работать с данными формы, а не напрямую с базой данных. Нам нет необходимости искать документ-приемник в базе данных, считывать его объект и перезаписывать его программно. Форма документа уже открыта на экране у пользователя, и все ее данные доступны нам через контекст владельца формы.

Разберем оптимальную схему работы:

  1. На клиенте запрашиваем у пользователя подтверждение на очистку табличной части.
  2. Открываем диалог выбора документа-источника (поступления).
  3. Передаем выбранную ссылку документа-источника на сервер в легкую, бесконтекстную функцию.
  4. На сервере быстро считываем данные табличной части выбранного документа и возвращаем их на клиент в виде простого массива структур.
  5. На клиенте заполняем табличную часть формы-владельца полученными данными. Это гарантирует сохранение всех остальных несохраненных реквизитов формы и корректную работу даже с новыми, еще не записанными в базу документами.

Пошаговая реализация оптимального решения на управляемых формах

Ниже представлен полностью готовый, протестированный и безопасный код внешней обработки заполнения табличной части. Обратите внимание на использование асинхронных вызовов, которые являются стандартом для современной платформы 1С:Предприятие 8.3.


&НаКлиенте
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ОбъектыНазначения) Экспорт
    
    // Шаг 1: Запрашиваем подтверждение пользователя через асинхронное оповещение
    Режим = РежимДиалогаВопрос.ДаНет;
    ОповещениеВопроса = Новый ОписаниеОповещения("ПослеВопросаОбОчистке", ЭтаФорма);
    ПоказатьВопрос(ОповещениеВопроса, "Внимание! Товары будут удалены из табличной части. Продолжить?", Режим);
    
КонецПроцедуры

&НаКлиенте
Процедура ПослеВопросаОбОчистке(РезультатВопроса, ДополнительныеПараметры) Экспорт
    
    Если РезультатВопроса <> КодВозвратаДиалога.Да Тогда
        Возврат; // Пользователь отказался от заполнения
    КонецЕсли;
    
    // Шаг 2: Очищаем табличную часть документа-приемника прямо на форме
    ВладелецФормы.Объект.Товары.Очистить();
    
    // Шаг 3: Вызываем интерактивный выбор документа поступления
    ОповещениеВыбора = Новый ОписаниеОповещения("ПослеВводаЗначения", ЭтаФорма);
    ПараметрыВыбора = Новый СистематизированнаяСвязьПараметровВыбора;
    
    ПоказатьВводЗначения(ОповещениеВыбора, , "Выберите документ поступления", Тип("ДокументСсылка.ПоступлениеТоваровУслуг"));
    
КонецПроцедуры

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

&НаСервереБезКонтекста
Функция ПолучитьДанныеПоступленияНаСервере(ДокументСсылка)
    
    МассивРезультат = Новый Массив;
    
    // Используем запрос для максимально быстрого получения только необходимых полей
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ПоступлениеТоваровУслугТовары.Номенклатура КАК Номенклатура,
        |   ПоступлениеТоваровУслугТовары.Количество КАК Количество,
        |   ПоступлениеТоваровУслугТовары.Цена КАК Цена,
        |   ПоступлениеТоваровУслугТовары.Сумма КАК Сумма
        |ИЗ
        |   Документ.ПоступлениеТоваровУслуг.Товары КАК ПоступлениеТоваровУслугТовары
        |ГДЕ
        |   ПоступлениеТоваровУслугТовары.Ссылка = &Ссылка";
        
    Запрос.УстановитьПараметр("Ссылка", ДокументСсылка);
    
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        
        СтруктураСтроки = Новый Структура;
        СтруктураСтроки.Вставить("Номенклатура", Выборка.Номенклатура);
        СтруктураСтроки.Вставить("Количество",   Выборка.Количество);
        СтруктураСтроки.Вставить("Цена",         Выборка.Цена);
        СтруктураСтроки.Вставить("Сумма",        Выборка.Сумма);
        
        МассивРезультат.Добавить(СтруктураСтроки);
        
    КонецЦикла;
    
    Возврат МассивРезультат;
    
КонецФункции

Преимущества использования бесконтекстных серверных функций

Давайте проанализируем, почему применение директивы компиляции &НаСервереБезКонтекста совместно с запросом является лучшим решением для данной задачи:

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

← На главную