В практике разработки на платформе 1С часто возникает задача создания «умных» интерфейсов, где количество колонок в документе заранее неизвестно и зависит от выбора пользователя. Например, когда нужно вывести список клиентов в строках, а в колонках отобразить различные группы номенклатуры, причем внутри каждой группы должны быть свои показатели: цена, количество и остаток. В этой статье мы подробно разберем, как реализовать такой механизм в управляемых формах, обеспечив корректное отображение и надежную запись данных в базу.
Прежде чем приступать к написанию кода для формы, проанализируем, как эти данные будут храниться. Платформа 1С не позволяет динамически менять структуру таблиц в базе данных (SQL). Поэтому мы пойдем по пути разделения хранения и визуализации.
Для реализации задачи создадим в документе скрытую табличную часть (назовем ее ДанныеДинамическихКолонок), которая будет иметь «плоскую» структуру:
Клиент — ссылка на справочник клиентов;НоменклатурнаяГруппа — ссылка на группу номенклатуры;ИмяПоказателя — строка (например, «Цена» или «Количество»);Значение — число (или другой нужный тип).Такой подход позволит нам хранить любое количество динамических данных в стандартном объекте, а при открытии формы программно «разворачивать» их в широкую таблицу, удобную для пользователя.
Чтобы форма «увидела» новые колонки, нам необходимо использовать метод ИзменитьРеквизиты(). Выясним причину, по которой это делается на сервере: реквизиты формы являются частью метаданных объекта формы, и их изменение требует пересборки структуры формы.
Рассмотрим пример кода, который создает реквизиты для каждой группы номенклатуры из списка значений:
// Процедура на сервере
&НаСервере
Процедура ДобавитьРеквизитыНаФорму(СписокГрупп)
МассивДобавляемыхРеквизитов = Новый Массив;
Для Каждого ЭлементСписка Из СписокГрупп Цикл
ИмяГруппы = "Группа_" + СтрЗаменить(Строка(ЭлементСписка.Значение.УникальныйИдентификатор()), "-", "_");
// Создаем реквизит для цены
МассивДобавляемыхРеквизитов.Добавить(Новый РеквизитФормы(ИмяГруппы + "_Цена", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)), "Объект.Товары"));
// Создаем реквизит для количества
МассивДобавляемыхРеквизитов.Добавить(Новый РеквизитФормы(ИмяГруппы + "_Кол", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 0)), "Объект.Товары"));
КонецЦикла;
ИзменитьРеквизиты(МассивДобавляемыхРеквизитов);
КонецПроцедуры
Важно помнить, что третий параметр конструктора РеквизитФормы указывает на путь к данным (в нашем случае это основная табличная часть документа Объект.Товары).
После того как данные (реквизиты) созданы, их нужно отобразить. Проанализируем ситуацию: автору требуется, чтобы показатели «Цена» и «Количество» визуально находились под общим заголовком номенклатурной группы. Для этого мы будем использовать ГруппаФормы с видом ГруппаКолонок.
Разберем процесс создания элементов по шагам:
&НаСервере
Процедура СоздатьЭлементыФормы(СписокГрупп)
ТаблицаФормы = Элементы.Товары;
Для Каждого ЭлементСписка Из СписокГрупп Цикл
ИмяГруппыМета = "Группа_" + СтрЗаменить(Строка(ЭлементСписка.Значение.УникальныйИдентификатор()), "-", "_");
// Создаем группу колонок для визуального объединения
ИмяЭлементаГруппа = "ГруппаЗаголовок_" + ИмяГруппыМета;
НоваяГруппа = Элементы.Добавить(ИмяЭлементаГруппа, Тип("ГруппаФормы"), ТаблицаФормы);
НоваяГруппа.Вид = ВидГруппыФормы.ГруппаКолонок;
НоваяГруппа.Заголовок = Строка(ЭлементСписка.Значение);
НоваяГруппа.Группировка = ГруппировкаКолонок.Горизонтальная;
// Добавляем поле "Цена"
ИмяПоляЦена = ИмяГруппыМета + "_Цена";
НовоеПолеЦена = Элементы.Добавить(ИмяПоляЦена, Тип("ПолеФормы"), НоваяГруппа);
НовоеПолеЦена.Вид = ВидПоляФормы.ПолеВвода;
НовоеПолеЦена.ПутьКДанным = "Объект.Товары." + ИмяПоляЦена;
НовоеПолеЦена.Заголовок = "Цена";
// Добавляем поле "Количество"
ИмяПоляКол = ИмяГруппыМета + "_Кол";
НовоеПолеКол = Элементы.Добавить(ИмяПоляКол, Тип("ПолеФормы"), НоваяГруппа);
НовоеПолеКол.Вид = ВидПоляФормы.ПолеВвода;
НовоеПолеКол.ПутьКДанным = "Объект.Товары." + ИмяПоляКол;
НовоеПоleКол.Заголовок = "Кол-во";
// Назначаем обработчик изменения
НовоеПолеЦена.УстановитьДействие("ПриИзменении", "ДинамическоеПолеПриИзменении");
НовоеПолеКол.УстановитьДействие("ПриИзменении", "ДинамическоеПолеПриИзменении");
КонецЦикла;
КонецПроцедуры
Когда пользователь вводит данные в динамическую колонку, нам нужно понять, к какой номенклатурной группе относится это значение. Выясним, как это сделать в общем обработчике события ПриИзменении.
Для этого мы проанализируем имя элемента, вызвавшего событие:
&НаКлиенте
Процедура ДинамическоеПолеПриИзменении(Элемент)
ТекущаяСтрока = Элементы.Товары.ТекущиеДанные;
ИмяЭлемента = Элемент.Имя; // Например: "Группа_UUID_Цена"
// Здесь можно выполнить дополнительные расчеты, например, суммы
// Сумма = ТекущаяСтрока[ИмяГруппы + "_Цена"] * ТекущаяСтрока[ИмяГруппы + "_Кол"];
КонецПроцедуры
Важный момент: при записи документа нам нужно выполнить обратную операцию — собрать данные из всех динамических колонок каждой строки и записать их в нашу «плоскую» табличную часть ДанныеДинамическихКолонок или напрямую в регистр сведений.
Рассмотрим подробнее процесс записи данных в регистр сведений. Это лучше всего делать в процедуре ПередЗаписьюНаСервере. Мы перебираем строки основной табличной части и для каждой строки обходим список динамических колонок.
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
// Сначала очистим старые записи в регистре, если они привязаны к документу
НаборЗаписей = РегистрыСведений.ЦеныПоКлиентам.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(ТекущийОбъект.Ссылка);
Для Каждого СтрокаТовары Из ТекущийОбъект.Товары Цикл
Для Каждого Группа Из СписокНоменклатуры Цикл
ИмяРеквизита = "Группа_" + СтрЗаменить(Строка(Группа.Значение.УникальныйИдентификатор()), "-", "_") + "_Цена";
// Получаем значение из динамического реквизита строки
ЗначениеЦены = СтрокаТовары[ИмяРеквизита];
Если ЗначениеЦены <> 0 Тогда
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Период = ТекущийОбъект.Дата;
НоваяЗапись.Клиент = СтрокаТовары.Клиент;
НоваяЗапись.НоменклатурнаяГруппа = Группа.Значение;
НоваяЗапись.Цена = ЗначениеЦены;
КонецЕсли;
КонецЦикла;
КонецЦикла;
НаборЗаписей.Записать();
КонецПроцедуры
При реализации динамических колонок следует учитывать несколько нюансов, которые помогут избежать ошибок и замедления работы системы:
ИзменитьРеквизиты с массивом удаляемых имен.ИзменитьРеквизиты инициирует полное обновление контекста формы. Не вызывайте его слишком часто (например, при каждом клике). Лучше всего делать это один раз при нажатии кнопки «Заполнить».УстановкаЦенНоменклатуры в типовых конфигурациях (ERP, УТ 11). Там используется механизм компоновки данных для формирования «шахматки», что является еще более продвинутым, но и сложным способом.Используя предложенную методику со скрытой табличной частью и программным созданием элементов, вы сможете создавать гибкие и интуитивно понятные инструменты для пользователей, сохраняя при этом чистоту архитектуры базы данных 1С.