При разработке пользовательских обработок и команд на форме документов разработчики часто сталкиваются с ситуацией: код обработки отрабатывает без ошибок, данные записываются в базу, но на экранах пользователей табличная часть остаётся неизменной. Чтобы увидеть обновленные цены или суммы, приходится переоткрывать документ.
В этой статье разберем, почему возникает эта проблема, как правильно обновлять данные табличной части непосредственно на открытой форме и как оптимизировать алгоритм, избавившись от запросов внутри цикла.
Проанализируем типовую ошибку в коде обработки кнопки формы:
ТЧДок = Ссылка.ПолучитьОбъект();
Для Каждого Стр Из ТЧДок.Товары Цикл
// Выполнение запроса и пересчет...
КонецЦикла;
ТЧДок.Записать();
Когда мы вызываем метод Ссылка.ПолучитьОбъект(), платформа 1С считывает данные из базы и создает в оперативной памяти новый независимый экземпляр объекта. Все изменения, внесенные в ТЧДок и сохраненные через ТЧДок.Записать(), попадают в базу данных, но никак не затрагивают реквизиты открытой формы (ЭтотОбъект).
Открытая форма работает со своим собственным локальным контекстом и коллекцией строк. Она не считывает автоматически изменения из базы данных при записи стороннего объекта. Вызов метода перезаписи формы Прочитать() сбросил бы несохраненные действия пользователя, поэтому правильный подход состоит в том, чтобы менять данные напрямую в коллекции строк формы.
Чтобы изменения моментально отображались на экране без закрытия документа и без лишней перезаписи в базу, необходимо работать напрямую с коллекцией строк формы Товары и установить признак модифицированности формы.
Рассмотрим базовый принцип обновления строк:
Для Каждого Стр Из Товары Цикл
Если Стр.Цена = 0 Тогда
// Подставляем новую цену и пересчитываем сумму
Стр.Цена = НоваяЦена;
Стр.Сумма = Стр.Количество * Стр.Цена;
КонецЕсли;
КонецЦикла;
// Сигнализируем форме, что данные изменились и их нужно сохранить при закрытии
Модифицированность = Истина;
При таком подходе пользователь сразу видит новые цены на экране, а документ переходит в состояние измененного (появляется звездочка в заголовке окна), что позволяет пользователю самому решить — сохранить изменения или отменить их.
Вторая распространенная ошибка — выполнение обращения к базе данных (запроса) внутри цикла по строкам табличной части. Выполнение запроса на каждой итерации цикла существенно замедляет работу программы, особенно при большом количестве строк в документе.
Чтобы ускорить обработку, сформируем единый запрос, который за один раз получит актуальные цены для всех необходимых товаров с помощью временных таблиц.
Разберем по шагам правильный алгоритм получения цен:
ЦеныНоменклатуры.СрезПоследних с соединением по номенклатуре и характеристике.Посмотрим на пример реализации единого запроса:
// 1. Подготавливаем таблицу товаров с нулевой ценой для передачи в запрос
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТЧТовары.Номенклатура КАК Номенклатура,
| ТЧТовары.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры
|ПОМЕСТИТЬ ВТ_ТоварыБезЦен
|ИЗ
| &ТаблицаТоваров КАК ТЧТовары
|ГДЕ
| ТЧТовары.Цена = 0
|;
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Товары.Номенклатура КАК Номенклатура,
| ВТ_Товары.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
| ЕСТЬNULL(СрезЦен.Цена, 0) КАК Цена
|ИЗ
| ВТ_ТоварыБезЦен КАК ВТ_Товары
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
| &Дата,
| ТипЦен = &ТипЦен) КАК СрезЦен
| ПО ВТ_Товары.Номенклатура = СрезЦен.Номенклатура
| И ВТ_Товары.ХарактеристикаНоменклатуры = СрезЦен.ХарактеристикаНоменклатуры";
Запрос.УстановитьПараметр("Дата", Дата);
Запрос.УстановитьПараметр("ТипЦен", Справочники.ТипыЦенНоменклатуры.НайтиПоКоду("УТ0000270"));
Запрос.УстановитьПараметр("ТаблицаТоваров", Товары.Выгрузить());
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
// 2. Обновляем строки в табличной части формы
ОтборНулевых = Новый Структура("Цена", 0);
НайденныеСтроки = Товары.НайтиСтроки(ОтборНулевых);
Для Каждого Стр Из НайденныеСтроки Цикл
ОтборПоТовару = Новый Структура();
ОтборПоТовару.Вставить("Номенклатура", Стр.Номенклатура);
ОтборПоТовару.Вставить("ХарактеристикаНоменклатуры", Стр.ХарактеристикаНоменклатуры);
СтрокиСЦеной = РезультатЗапроса.НайтиСтроки(ОтборПоТовару);
Если СтрокиСЦеной.Количество() > 0 И СтрокиСЦеной[0].Цена > 0 Тогда
Стр.Цена = СтрокиСЦеной[0].Цена;
Стр.Сумма = Стр.Количество * Стр.Цена;
КонецЕсли;
КонецЦикла;
Модифицированность = Истина;
Чтобы интерактивная обработка табличной части работала быстро и корректно:
Ссылка.ПолучитьОбъект() внутри кода формы для изменения текущих данных — работайте напрямую с реквизитами формы (например, Товары)..Записать() без необходимости, используйте флаг Модифицированность = Истина.Запрос.Выполнить() внутри циклов. Выгружайте данные во временную таблицу и запрашивайте срез цен за один шаг.