В практике разработки на платформе 1С:Предприятие 8.3 часто возникает задача, связанная с представлением данных в удобном для пользователя виде. Классический пример — планирование показателей по периодам (например, по месяцам). В базе данных (в табличной части документа) такие показатели правильнее и надежнее хранить в нормализованном виде: каждая строка содержит аналитику (номенклатуру, категорию), дату периода и сумму. Однако пользователю гораздо удобнее видеть эти данные в виде кросс-таблицы (матрицы), где по строкам расположены категории, а по колонкам — месяцы выбранного периода.
Процесс преобразования строк табличной части в колонки таблицы на форме называется транспонированием (или денормализацией) данных. В этой статье мы подробно разберем, как правильно реализовать этот процесс на управляемой форме, проанализируем типичные ошибки начинающих разработчиков и напишем надежный, быстродействующий код для двустороннего переноса данных.
Прежде чем перейти к написанию рабочего кода, давайте разберем основные ошибки, с которыми сталкиваются программисты при решении подобных задач на управляемых формах.
Ошибка 1: Обращение к элементам формы вместо реквизитов данных. Часто в коде можно встретить попытку вызвать методы поиска строк непосредственно у элементов интерфейса, например: Элементы.ТаблицаПроводственныеПланы.Найти(). Это приводит к ошибке «Метод объекта не обнаружен». На управляемой форме коллекции в ветке Элементы отвечают исключительно за визуальное отображение. Сами данные хранятся в реквизитах формы (которые имеют тип ДанныеФормыКоллекция, ДанныеФормыСтруктура и т.д.). Искать, добавлять и изменять данные нужно именно в реквизите формы, а элементы интерфейса лишь автоматически отобразят эти изменения.
Ошибка 2: Обход и сопоставление по индексам. Код вида НоваяСтрока[Сч] = Объект.Производство[Сч-1].Сумма крайне опасен. Он строится на предположении, что порядок строк в табличной части строго совпадает с порядком динамически созданных колонок. Если в документе по какой-то причине будет отсутствовать запись за один из месяцев (например, есть данные за январь и март, но нет за февраль), то все суммы сместятся влево, и данные за март запишутся в колонку февраля. Сопоставление данных должно выполняться строго по ключам (аналитика + конкретный период).
Ошибка 3: Некорректные имена динамических колонок. При создании колонок программно важно помнить, что имя колонки реквизита формы должно соответствовать правилам именования переменных в 1С. Оно не может содержать пробелов, точек, знаков препинания и не может начинаться с цифры. Если формировать имя колонки простым преобразованием даты в строку (например, "Январь 2026"), платформа выдаст ошибку. Правильный подход — создавать уникальное техническое имя колонки (например, М_2026_01), а понятное представление ("Январь 2026 г.") записывать в свойство Заголовок элемента формы.
Рассмотрим детальный алгоритм решения задачи. Мы реализуем его на стороне сервера, так как динамическое изменение структуры реквизитов формы и сложные выборки данных возможны только в серверном контексте.
Наш алгоритм будет состоять из следующих шагов:
ТаблицаЗначений для быстрого сопоставления данных.Давайте подробно разберем программную реализацию каждого шага.
Для начала создадим вспомогательную функцию, которая будет генерировать безопасное и уникальное техническое имя для колонки на основе переданной даты начала месяца. Это гарантирует отсутствие ошибок компиляции при формировании структуры данных.
&НаСервере
Функция ПолучитьИмяКолонкиПериода(ДатаМесяца)
// Функция формирует имя вида "М_2026_08" из даты 15.08.2026
Возврат "М_" + Формат(ДатаМесяца, "ДФ=yyyy_MM");
КонецФункции
Теперь напишем основную процедуру, которая будет вычислять количество месяцев в выбранном периоде, создавать реквизиты формы и привязывать к ним элементы управления на форме. Обратите внимание, что мы используем метод ИзменитьРеквизиты(), который принимает массив новых реквизитов.
&НаСервере
Процедура ИнициализироватьДинамическуюТаблицу()
НачалоПериода = Объект.НачалоПериода;
КонецПериода = Объект.КонецПериода;
Если Не ЗначениеЗаполнено(НачалоПериода) Или Не ЗначениеЗаполнено(КонецПериода) Тогда
Возврат;
КонецЕсли;
// 1. Формируем массив новых реквизитов формы для добавления
НовыеРеквизиты = Новый Массив;
ТекущийМесяц = НачалоМесяца(НачалоПериода);
КонецПериодаВыгрузки = НачалоМесяца(КонецПериода);
Пока ТекущийМесяц <= КонецПериодаВыгрузки Цикл
ИмяКолонки = ПолучитьИмяКолонкиПериода(ТекущийМесяц);
ЗаголовокКолонки = Формат(ТекущийМесяц, "ДФ='MMMM yyyy'");
// Создаем описание типа "Число" для колонки суммы
ОписаниеТипаСуммы = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
// Добавляем новый реквизит в структуру таблицы "ТаблицаПрозводственныеПланы"
НовыеРеквизиты.Добавить(Новый РеквизитФормы(
ИмяКолонки,
ОписаниеТипаСуммы,
"ТаблицаПрозводственныеПланы",
ЗаголовокКолонки,
Истина
));
ТекущийМесяц = ДобавитьМесяц(ТекущийМесяц, 1);
КонецЦикла;
// Применяем изменения структуры реквизитов формы на сервере
ИзменитьРеквизиты(НовыеРеквизиты);
// 2. Создаем визуальные элементы отображения на форме
ТекущийМесяц = НачалоМесяца(НачалоПериода);
Пока ТекущийМесяц <= КонецПериодаВыгрузки Цикл
ИмяКолонки = ПолучитьИмяКолонкиПериода(ТекущийМесяц);
ЗаголовокКолонки = Формат(ТекущийМесяц, "ДФ='MMMM yyyy'");
// Добавляем поле ввода в визуальную таблицу на форме
НовыйЭлемент = Элементы.Добавить(ИмяКолонки, Тип("ПолеФормы"), Элементы.ТаблицаПрозводственныеПланы);
НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
НовыйЭлемент.ПутьКДанным = "ТаблицаПрозводственныеПланы." + ИмяКолонки;
НовыйЭлемент.Заголовок = ЗаголовокКолонки;
НовыйЭлемент.Ширина = 12;
ТекущийМесяц = ДобавитьМесяц(ТекущийМесяц, 1);
КонецЦикла;
КонецПроцедуры
Теперь переходим к самому главному — заполнению созданной таблицы. Для работы с данными на сервере мы преобразуем реквизит формы `ТаблицаПрозводственныеПланы` в полноценный объект ТаблицаЗначений при помощи метода ДанныеФормыВЗначение(). Это позволит нам задействовать мощный метод поиска строк Найти().
Для получения уникального списка категорий, которые станут строками нашей таблицы, мы выполним быстрый и легкий запрос к табличной части документа. Это гораздо эффективнее, чем ручной перебор строк с поиском дублей в цикле.
&НаСервере
Процедура ЗаполнитьДанныеИзТабличнойЧасти()
// Преобразуем структуру данных формы в объект ТаблицаЗначений на Сервере
ВременнаяТЗ = ДанныеФормыВЗначение(ТаблицаПрозводственныеПланы, Тип("ТаблицаЗначений"));
ВременнаяТЗ.Очистить(); // Очищаем таблицу перед заполнением
// Выбираем уникальные категории номенклатуры из табличной части текущего документа
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Производство.КатегорияНоменклатуры КАК КатегорияНоменклатуры
|ИЗ
| Документ.ПроизводственныеПланы.Производство КАК Производство
|ГДЕ
| Производство.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Объект.Ссылка);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Добавляем строку для каждой уникальной категории
НоваяСтрока = ВременнаяТЗ.Добавить();
НоваяСтрока.КатегорияНоменклатуры = Выборка.КатегорияНоменклатуры;
КонецЦикла;
КонецЕсли;
// Заполняем показатели сумм по колонкам месяцев
Для Каждого СтрокаТЧ Из Объект.Производство Цикл
// Ищем строку в нашей ТЗ по категории номенклатуры
СтрокаТЗ = ВременнаяТЗ.Найти(СтрокаТЧ.КатегорияНоменклатуры, "КатегорияНоменклатуры");
Если СтрокаТЗ <> Неопределено Тогда
// Определяем целевое имя колонки на основе даты планирования из строки ТЧ
ИмяКолонкиДляЗаписи = ПолучитьИмяКолонкиПериода(НачалоМесяца(СтрокаТЧ.ДатаПланирования));
// Проверяем на всякий случай, существует ли такая колонка в структуре таблицы
Если ВременнаяТЗ.Колонки.Найти(ИмяКолонкиДляЗаписи) <> Неопределено Тогда
// Записываем сумму в соответствующую ячейку строки
СтрокаТЗ[ИмяКолонкиДляЗаписи] = СтрокаТЧ.Сумма;
КонецЕсли;
КонецЕсли;
КонецЦикла;
// Возвращаем заполненную таблицу значений обратно на форму
ЗначениеВДанныеФормы(ВременнаяТЗ, ТаблицаПрозводственныеПланы);
КонецПроцедуры
Для того чтобы при открытии существующего документа пользователь сразу видел заполненную красивую таблицу, вызовы процедур необходимо прописать в правильном обработчике событий формы. Идеальным местом для этого является стандартное событие ПриЧтенииНаСервере. Оно срабатывает при открытии формы существующего объекта, а также при его перечитывании.
&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
// Сначала инициализируем колонки под период документа
ИнициализироватьДинамическуюТаблицу();
// Затем заполняем эти колонки данными из сохраненной ТЧ
ЗаполнитьДанныеИзТабличнойЧасти();
КонецПроцедуры
Для того чтобы интерфейс не просто показывал данные, но и позволял их редактировать, необходимо реализовать обратный процесс. При записи документа нам нужно прочитать данные из заполненной пользователем таблицы на форме и перенести их обратно в строки табличной части Производство.
Посмотрим на пример реализации этой процедуры, которую следует вызывать в обработчике формы ПередЗаписьюНаСервере.
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
// Очищаем старые данные в ТЧ объекта, чтобы перезаписать их актуальными
ТекущийОбъект.Производство.Очистить();
// Преобразуем таблицу формы в объект ТаблицаЗначений на сервере
ТаблицаФормы = ДанныеФормыВЗначение(ТаблицаПрозводственныеПланы, Tip("ТаблицаЗначений"));
НачалоПериода = Объект.НачалоПериода;
КонецПериода = Объект.КонецПериода;
// Обходим каждую строку нашей таблицы на форме
Для Каждого СтрокаТЗ Из ТаблицаФормы Цикл
// Для каждой категории обходим все колонки-месяцы в заданном интервале
ТекущийМесяц = НачалоМесяца(НачалоПериода);
Пока ТекущийМесяц <= НачалоМесяца(КонецПериода) Цикл
ИмяКолонки = ПолучитьИмяКолонкиПериода(ТекущийМесяц);
ЗначениеСуммы = СтрокаТЗ[ИмяКолонки];
// Если пользователь заполнил сумму по этому месяцу, пишем ее в ТЧ
Если ЗначениеЗаполнено(ЗначениеСуммы) И ЗначениеСуммы <> 0 Тогда
НоваяСтрокаТЧ = ТекущийОбъект.Производство.Добавить();
НоваяСтрокаТЧ.КатегорияНоменклатуры = СтрокаТЗ.КатегорияНоменклатуры;
НоваяСтрокаТЧ.ДатаПланирования = ТекущийМесяц;
НоваяСтрокаТЧ.Сумма = ЗначениеСуммы;
КонецЕсли;
ТекущийМесяц = ДобавитьМесяц(ТекущийМесяц, 1);
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Мы спроектировали и реализовали надежную систему динамического транспонирования данных для управляемых форм 1С. Рассмотренный нами алгоритм гарантирует:
ДанныеФормыВЗначение, SQL-запросы к табличным частям, индексированный поиск) минимизирует нагрузку на сервер СУБД и оперативную память сервера приложений.Элементы) и логики данных (коллекция Реквизиты) делает код понятным и простым для дальнейшей модернизации другими разработчиками.