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

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

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

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

Причина возникновения проблемы и анализ ошибки

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

В управляемом приложении контекст формы строго разграничен между клиентом и сервером. Реквизит Объект на форме имеет тип ДанныеФормыСтруктура. Когда мы пытаемся передать этот объект целиком во внешнюю обработку (которая сама по себе является сторонним программным контекстом) и применить к нему функцию ЗначениеВДанныеФормы(), платформа блокирует операцию в целях безопасности и сохранения целостности данных. Изменение корневого объекта чужой формы «на лету» запрещено.

Рассмотрим исходный ошибочный код, с которым столкнулся автор темы:


&НаКлиенте
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ОбъектыНазначенияМассив) Экспорт
    СортироватьНаСервере(ВладелецФормы.Объект);
КонецПроцедуры

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

В данном примере разработчик пытается преобразовать весь объект документа в прикладной объект ДокументОбъект, отсортировать его табличную часть и вернуть измененный объект назад в форму владельца. На строке выполнения метода ЗначениеВДанныеФормы платформа выбрасывает исключение, поскольку мы пытаемся перезаписать весь контекст внешней формы.

Анализ решений, предложенных на форуме

В ходе обсуждения участники предложили несколько вариантов решения проблемы. Рассмотрим их особенности и скрытые угрозы.

Вариант 1. Использование стандартного метода Сортировать()

Этот способ упомянут в Сообщении 1. Метод Сортировать() у объекта ДанныеФормыКоллекция (или ТаблицаЗначений) работает чрезвычайно быстро и просто:

Объект.Товары.Сортировать("Номенклатура Возр");

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

Вариант 2. Копирование через временную табличную часть (Сообщение 16)

Автор темы пришел к рабочему, на первый взгляд, решению. Он добавил во внешнюю обработку аналогичную табличную часть Товары и реализовал следующий алгоритм:

  1. На сервере выполняется запрос к базе данных, получающий строки уже записанного документа из СУБД с сортировкой по коду номенклатуры.
  2. Найденные строки загружаются во временную табличную часть самой обработки.
  3. На клиенте табличная часть документа-владельца очищается, и в нее циклом переносятся строки из обработки с помощью метода ЗаполнитьЗначенияСвойств().

Рассмотрим код этого решения:


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

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

  1. Уничтожение несохраненных данных пользователя. Поскольку запрос в процедуре ЗаполнитьТаблицуИзДокумента обращается напрямую к таблицам базы данных (ИЗ Документ.ИнвентаризацияТоваровНаСкладе.Товары), программа полностью игнорирует любые изменения, которые пользователь внес в форму документа перед нажатием кнопки сортировки. Если пользователь вручную добавил новые строки или изменил количество, а затем запустил обработку, все его правки будут безвозвратно утеряны, так как данные прочитаются из старой версии документа в БД.
  2. Невозможность работы с новыми документами. Если пользователь создал новый документ и еще не успел его записать, у объекта отсутствует ссылка в базе данных (ВладелецФормы.Объект.Ссылка будет пустой). Запрос вернет пустой результат, и табличная часть просто очистится.
  3. Крайне низкая производительность. Построчный обход коллекции на клиенте в цикле Для Каждого и ручное добавление строк через метод Добавить() создают колоссальную нагрузку на клиентский поток. При наличии в документе сотен или тысяч строк интерфейс программы зависнет на длительное время.

Оптимальное и безопасное решение задачи

Для того чтобы реализовать быструю сортировку без потери несохраненных данных пользователя и избежать ошибок контекста, мы должны применить правильную методику работы с управляемыми формами. Сформулируем основные правила:

Разберем детальный алгоритм реализации этого подхода по шагам.

Шаг 1. Создание клиентской процедуры-обработчика

На клиенте мы получаем доступ к табличной части формы владельца и передаем ее в качестве параметра в серверную процедуру. Обратите внимание, что серверную процедуру желательно объявить с директивой компиляции &НаСервереБезКонтекста, чтобы не передавать на сервер контекст самой внешней обработки, повышая общую скорость вызова.


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

Шаг 2. Разработка серверной процедуры сортировки

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


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

Преимущества оптимального подхода

Рассмотрим, почему предложенное решение является эталонным для систем на платформе 1С:Предприятие 8.3:

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

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

← На главную