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

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

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

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

Выясним причину: почему изменения не видны на открытой форме

Проанализируем типовую ошибку в коде обработки кнопки формы:


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

Когда мы вызываем метод Ссылка.ПолучитьОбъект(), платформа 1С считывает данные из базы и создает в оперативной памяти новый независимый экземпляр объекта. Все изменения, внесенные в ТЧДок и сохраненные через ТЧДок.Записать(), попадают в базу данных, но никак не затрагивают реквизиты открытой формы (ЭтотОбъект).

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

Решение 1. Прямая работа с табличной частью формы

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

Рассмотрим базовый принцип обновления строк:


Для Каждого Стр Из Товары Цикл
    Если Стр.Цена = 0 Тогда
        // Подставляем новую цену и пересчитываем сумму
        Стр.Цена = НоваяЦена;
        Стр.Сумма = Стр.Количество * Стр.Цена;
    КонецЕсли;
КонецЦикла;

// Сигнализируем форме, что данные изменились и их нужно сохранить при закрытии
Модифицированность = Истина;

При таком подходе пользователь сразу видит новые цены на экране, а документ переходит в состояние измененного (появляется звездочка в заголовке окна), что позволяет пользователю самому решить — сохранить изменения или отменить их.

Решение 2. Оптимизация запроса (убираем запрос из цикла)

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

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

Разберем по шагам правильный алгоритм получения цен:

  1. Выгрузим табличную часть формы во временную таблицу или передадим её в параметр запроса.
  2. Сформируем выборку цен из регистра сведений ЦеныНоменклатуры.СрезПоследних с соединением по номенклатуре и характеристике.
  3. Обновим строки табличной части формы полученными из запроса данными.

Посмотрим на пример реализации единого запроса:


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

Запрос.УстановитьПараметр("Дата", Дата);
Запрос.УстановитьПараметр("ТипЦен", Справочники.ТипыЦенНоменклатуры.НайтиПоКоду("УТ0000270"));
Запрос.УстановитьПараметр("ТаблицаТоваров", Товары.Выгрузить());

РезультатЗапроса = Запрос.Выполнить().Выгрузить();

// 2. Обновляем строки в табличной части формы
ОтборНулевых = Новый Структура("Цена", 0);
НайденныеСтроки = Товары.НайтиСтроки(ОтборНулевых);

Для Каждого Стр Из НайденныеСтроки Цикл
    ОтборПоТовару = Новый Структура();
    ОтборПоТовару.Вставить("Номенклатура", Стр.Номенклатура);
    ОтборПоТовару.Вставить("ХарактеристикаНоменклатуры", Стр.ХарактеристикаНоменклатуры);
    
    СтрокиСЦеной = РезультатЗапроса.НайтиСтроки(ОтборПоТовару);
    Если СтрокиСЦеной.Количество() > 0 И СтрокиСЦеной[0].Цена > 0 Тогда
        Стр.Цена = СтрокиСЦеной[0].Цена;
        Стр.Сумма = Стр.Количество * Стр.Цена;
    КонецЕсли;
КонецЦикла;

Модифицированность = Истина;

Подводя итоги

Чтобы интерактивная обработка табличной части работала быстро и корректно:

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