При работе со списками документов или справочников в 1С:Предприятии пользователям часто требуется быстро проанализировать группу записей. Например, быстро узнать среднюю цену товаров в нескольких выделенных документах. Поскольку динамический список считывает данные порциями и предназначен в первую очередь для отображения, задача расчета агрегатных показателей по произвольно выделенным строкам имеет свои технические особенности.
В этой статье мы подробно разберем, как реализовать этот функционал программно, проанализируем различные подходы (клиентский и серверный), изучим подводные камни кэширования данных и рассмотрим стандартные альтернативные методы платформы, не требующие написания кода.
Наиболее производительным, надежным и архитектурно правильным решением является передача массива идентификаторов выделенных строк на сервер и выполнение запроса к базе данных. В динамическом списке документов выделенными строками являются ссылки на эти документы. Передав их на сервер, мы можем одним простым запросом моментально рассчитать точную среднюю цену по табличным частям.
Разберем этот метод по шагам:
Посмотрим на пример реализации кода на форме списка документов:
&НаКлиенте
Процедура ВычислитьСреднююЦенуВыделенных(Команда)
ВыделенныеСтроки = Элементы.Список.ВыделенныеСтроки;
Если ВыделенныеСтроки.Количество() = 0 Тогда
ПоказатьПредупреждение(, "Не выделено ни одной строки!");
Возврат;
КонецЕсли;
// Передаем массив ссылок на сервер для расчета
СредняяЦена = ПолучитьСреднююЦенуНаСервере(ВыделенныеСтроки);
// Выводим результат пользователю
ПоказатьОповещениеПользователя("Расчет окончен", ,
"Средняя цена выделенных документов: " + Формат(СредняяЦена, "ЧЦ=15; ЧДЦ=2"));
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьСреднююЦенуНаСервере(МассивДокументов)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СРЕДНЕЕ(ДокументТЧ.Цена) КАК СредняяЦена
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК ДокументТЧ
|ГДЕ
| ДокументТЧ.Ссылка В(&МассивДокументов)";
Запрос.УстановитьПараметр("МассивДокументов", МассивДокументов);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() И ЗначениеЗаполнено(Выборка.MediumPrice) Тогда
Возврат Выборка.СредняяЦена;
КонецЕсли;
Возврат 0;
КонецФункции
Преимущества серверного подхода: этот метод работает мгновенно даже при выделении сотен и тысяч документов (например, через сочетание клавиш Ctrl + A). Он абсолютно не зависит от того, видима ли колонка цены на клиенте у конкретного пользователя, и не вызывает зависания интерфейса из-за передачи больших объемов данных.
Иногда требуется вычислять среднюю цену или сумму динамически сразу при выделении строк пользователем (без нажатия дополнительных кнопок). Для этого можно использовать клиентский обход выделенных строк. Однако делать расчет напрямую в событии ПриАктивизацииСтроки не рекомендуется, так как оно срабатывает при каждом перемещении курсора, что может вызвать «фризы» интерфейса.
Для оптимизации процесса мы применим обработчик ожидания. Это позволит выполнять расчет с небольшой задержкой (дебаунсом) только после того, как пользователь завершил выделение строк.
Рассмотрим пример реализации клиентского расчета:
&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
// Подключаем однократный обработчик ожидания с задержкой 0.1 секунды
ПодключитьОбработчикОжидания("РассчитатьСреднююЦенуКлиент", 0.1, Истина);
КонецПроцедуры
&НаКлиенте
Процедура РассчитатьСреднююЦенуКлиент()
ВыделенныеСтроки = Элементы.Список.ВыделенныеСтроки;
КоличествоВыделено = ВыделенныеСтроки.Количество();
Если КоличествоВыделено = 0 Тогда
ИтоговаяСредняяЦена = 0;
Возврат;
КонецЕсли;
ОбщаяСумма = 0;
КоличествоУспешноПолученных = 0;
Для Каждого ИдентификаторСтроки Из ВыделенныеСтроки Цикл
ДанныеСтроки = Элементы.Список.ДанныеСтроки(ИдентификаторСтроки);
// Важно проверить на Неопределено (данные могут быть не кэшированы на клиенте)
Если ДанныеСтроки <> Неопределено Тогда
ОбщаяСумма = ОбщаяСумма + ДанныеСтроки.Цена;
КоличествоУспешноПолученных = КоличествоУспешноПолученных + 1;
КонецЕсли;
КонецЦикла;
Если КоличествоУспешноПолученных > 0 Тогда
ИтоговаяСредняяЦена = ОбщаяСумма / КоличествоУспешноПолученных;
Иначе
ИтоговаяСредняяЦена = 0;
EndIf;
// Здесь код вывода значения на форму, например, в декорацию или авто-поле
КонецПроцедуры
Важные ограничения клиентского метода ДанныеСтроки():
ДанныеСтроки() вернет значение Неопределено для некэшированных строк. Соответственно, расчет по ним выполнен не будет.Цена), обязательно должен присутствовать на клиенте. Если пользователь скроет эту колонку через настройку формы, код выдаст ошибку. Чтобы этого избежать, обязательно перейдите в свойства динамического списка в конфигураторе и для нужного реквизита установите свойство «Использовать всегда».Часто разработчики тратят время на написание кода там, где платформа 1С уже предоставляет готовые и удобные инструменты для пользователей. Проанализируем штатные механизмы интерактивного анализа выделенных строк:
Ctrl + C, открыть Microsoft Excel или Google Таблицы и вставить данные через Ctrl + V. Данные переносятся в виде полноценной таблицы, где можно мгновенно применить любые формулы расчета.Проанализируем распространенную ошибку начинающих разработчиков при работе с выделенными строками. В некоторых старых решениях можно встретить попытку получить данные строк через свойство списка ТекущиеДанные, циклически перезаписывая свойство списка ТекущаяСтрока = ИдентификаторСтроки внутри цикла обхода выделенных строк.
Так делать категорически нельзя! Программное изменение активной строки (свойство ТекущаяСтрока) во время цикла мгновенно сбрасывает множественное выделение, которое пользователь делал вручную (с зажатым Ctrl или Shift). В итоге алгоритм собьется, а пользователь потеряет выделенные строки.
Если вам действительно необходимо временно изменить активную строку в интерфейсе, предварительно выгрузите массив ВыделенныеСтроки во временную переменную-буфер, проведите все манипуляции, а затем верните сохраненный массив обратно в элемент формы.