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