Как перенести данные из колонок табличной части в строки таблицы значений на управляемой форме в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В практике разработки на платформе 1С:Предприятие 8.3 часто возникает задача, связанная с представлением данных в удобном для пользователя виде. Классический пример — планирование показателей по периодам (например, по месяцам). В базе данных (в табличной части документа) такие показатели правильнее и надежнее хранить в нормализованном виде: каждая строка содержит аналитику (номенклатуру, категорию), дату периода и сумму. Однако пользователю гораздо удобнее видеть эти данные в виде кросс-таблицы (матрицы), где по строкам расположены категории, а по колонкам — месяцы выбранного периода.

Процесс преобразования строк табличной части в колонки таблицы на форме называется транспонированием (или денормализацией) данных. В этой статье мы подробно разберем, как правильно реализовать этот процесс на управляемой форме, проанализируем типичные ошибки начинающих разработчиков и напишем надежный, быстродействующий код для двустороннего переноса данных.

Анализ типичных ошибок начинающих разработчиков

Прежде чем перейти к написанию рабочего кода, давайте разберем основные ошибки, с которыми сталкиваются программисты при решении подобных задач на управляемых формах.

Ошибка 1: Обращение к элементам формы вместо реквизитов данных. Часто в коде можно встретить попытку вызвать методы поиска строк непосредственно у элементов интерфейса, например: Элементы.ТаблицаПроводственныеПланы.Найти(). Это приводит к ошибке «Метод объекта не обнаружен». На управляемой форме коллекции в ветке Элементы отвечают исключительно за визуальное отображение. Сами данные хранятся в реквизитах формы (которые имеют тип ДанныеФормыКоллекция, ДанныеФормыСтруктура и т.д.). Искать, добавлять и изменять данные нужно именно в реквизите формы, а элементы интерфейса лишь автоматически отобразят эти изменения.

Ошибка 2: Обход и сопоставление по индексам. Код вида НоваяСтрока[Сч] = Объект.Производство[Сч-1].Сумма крайне опасен. Он строится на предположении, что порядок строк в табличной части строго совпадает с порядком динамически созданных колонок. Если в документе по какой-то причине будет отсутствовать запись за один из месяцев (например, есть данные за январь и март, но нет за февраль), то все суммы сместятся влево, и данные за март запишутся в колонку февраля. Сопоставление данных должно выполняться строго по ключам (аналитика + конкретный период).

Ошибка 3: Некорректные имена динамических колонок. При создании колонок программно важно помнить, что имя колонки реквизита формы должно соответствовать правилам именования переменных в 1С. Оно не может содержать пробелов, точек, знаков препинания и не может начинаться с цифры. Если формировать имя колонки простым преобразованием даты в строку (например, "Январь 2026"), платформа выдаст ошибку. Правильный подход — создавать уникальное техническое имя колонки (например, М_2026_01), а понятное представление ("Январь 2026 г.") записывать в свойство Заголовок элемента формы.

Пошаговое руководство по правильному транспонированию данных

Рассмотрим детальный алгоритм решения задачи. Мы реализуем его на стороне сервера, так как динамическое изменение структуры реквизитов формы и сложные выборки данных возможны только в серверном контексте.

Наш алгоритм будет состоять из следующих шагов:

  1. Определение интервала планирования и программное создание колонок-периодов в реквизите формы.
  2. Визуальное создание элементов полей ввода на форме для отображения колонок.
  3. Получение уникального списка строк (категорий номенклатуры) с помощью запроса к табличной части документа.
  4. Преобразование реквизита формы во временную ТаблицаЗначений для быстрого сопоставления данных.
  5. Заполнение сумм по соответствующим ячейкам (поиск строки по категории и запись в колонку конкретного месяца).
  6. Загрузка готовой таблицы значений обратно в реквизит формы.

Давайте подробно разберем программную реализацию каждого шага.

Шаг 1 и 2: Динамическое создание колонок на форме

Для начала создадим вспомогательную функцию, которая будет генерировать безопасное и уникальное техническое имя для колонки на основе переданной даты начала месяца. Это гарантирует отсутствие ошибок компиляции при формировании структуры данных.


&НаСервере
Функция ПолучитьИмяКолонкиПериода(ДатаМесяца)
    // Функция формирует имя вида "М_2026_08" из даты 15.08.2026
    Возврат "М_" + Формат(ДатаМесяца, "ДФ=yyyy_MM");
КонецФункции

Теперь напишем основную процедуру, которая будет вычислять количество месяцев в выбранном периоде, создавать реквизиты формы и привязывать к ним элементы управления на форме. Обратите внимание, что мы используем метод ИзменитьРеквизиты(), который принимает массив новых реквизитов.


&НаСервере
Процедура ИнициализироватьДинамическуюТаблицу()

    НачалоПериода = Объект.НачалоПериода;
    КонецПериода  = Объект.КонецПериода;
    
    Если Не ЗначениеЗаполнено(НачалоПериода) Или Не ЗначениеЗаполнено(КонецПериода) Тогда
        Возврат;
    КонецЕсли;

    // 1. Формируем массив новых реквизитов формы для добавления
    НовыеРеквизиты = Новый Массив;
    ТекущийМесяц = НачалоМесяца(НачалоПериода);
    КонецПериодаВыгрузки = НачалоМесяца(КонецПериода);
    
    Пока ТекущийМесяц <= КонецПериодаВыгрузки Цикл
        ИмяКолонки = ПолучитьИмяКолонкиПериода(ТекущийМесяц);
        ЗаголовокКолонки = Формат(ТекущийМесяц, "ДФ='MMMM yyyy'");
        
        // Создаем описание типа "Число" для колонки суммы
        ОписаниеТипаСуммы = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
        
        // Добавляем новый реквизит в структуру таблицы "ТаблицаПрозводственныеПланы"
        НовыеРеквизиты.Добавить(Новый РеквизитФормы(
            ИмяКолонки, 
            ОписаниеТипаСуммы, 
            "ТаблицаПрозводственныеПланы", 
            ЗаголовокКолонки, 
            Истина
        ));
        
        ТекущийМесяц = ДобавитьМесяц(ТекущийМесяц, 1);
    КонецЦикла;
    
    // Применяем изменения структуры реквизитов формы на сервере
    ИзменитьРеквизиты(НовыеРеквизиты);

    // 2. Создаем визуальные элементы отображения на форме
    ТекущийМесяц = НачалоМесяца(НачалоПериода);
    Пока ТекущийМесяц <= КонецПериодаВыгрузки Цикл
        ИмяКолонки = ПолучитьИмяКолонкиПериода(ТекущийМесяц);
        ЗаголовокКолонки = Формат(ТекущийМесяц, "ДФ='MMMM yyyy'");
        
        // Добавляем поле ввода в визуальную таблицу на форме
        НовыйЭлемент = Элементы.Добавить(ИмяКолонки, Тип("ПолеФормы"), Элементы.ТаблицаПрозводственныеПланы);
        НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
        НовыйЭлемент.ПутьКДанным = "ТаблицаПрозводственныеПланы." + ИмяКолонки;
        НовыйЭлемент.Заголовок = ЗаголовокКолонки;
        НовыйЭлемент.Ширина = 12;
        
        ТекущийМесяц = ДобавитьМесяц(ТекущийМесяц, 1);
    КонецЦикла;

КонецПроцедуры

Шаг 3, 4 и 5: Заполнение таблицы данными из табличной части

Теперь переходим к самому главному — заполнению созданной таблицы. Для работы с данными на сервере мы преобразуем реквизит формы `ТаблицаПрозводственныеПланы` в полноценный объект ТаблицаЗначений при помощи метода ДанныеФормыВЗначение(). Это позволит нам задействовать мощный метод поиска строк Найти().

Для получения уникального списка категорий, которые станут строками нашей таблицы, мы выполним быстрый и легкий запрос к табличной части документа. Это гораздо эффективнее, чем ручной перебор строк с поиском дублей в цикле.


&НаСервере
Процедура ЗаполнитьДанныеИзТабличнойЧасти()

    // Преобразуем структуру данных формы в объект ТаблицаЗначений на Сервере
    ВременнаяТЗ = ДанныеФормыВЗначение(ТаблицаПрозводственныеПланы, Тип("ТаблицаЗначений"));
    ВременнаяТЗ.Очистить(); // Очищаем таблицу перед заполнением

    // Выбираем уникальные категории номенклатуры из табличной части текущего документа
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ РАЗЛИЧНЫЕ
        |	Производство.КатегорияНоменклатуры КАК КатегорияНоменклатуры
        |ИЗ
        |	Документ.ПроизводственныеПланы.Производство КАК Производство
        |ГДЕ
        |	Производство.Ссылка = &Ссылка";
    
    Запрос.УстановитьПараметр("Ссылка", Объект.Ссылка);
    РезультатЗапроса = Запрос.Выполнить();
    
    Если Не РезультатЗапроса.Пустой() Тогда
        Выборка = РезультатЗапроса.Выбрать();
        Пока Выборка.Следующий() Цикл
            // Добавляем строку для каждой уникальной категории
            НоваяСтрока = ВременнаяТЗ.Добавить();
            НоваяСтрока.КатегорияНоменклатуры = Выборка.КатегорияНоменклатуры;
        КонецЦикла;
    КонецЕсли;

    // Заполняем показатели сумм по колонкам месяцев
    Для Каждого СтрокаТЧ Из Объект.Производство Цикл
        
        // Ищем строку в нашей ТЗ по категории номенклатуры
        СтрокаТЗ = ВременнаяТЗ.Найти(СтрокаТЧ.КатегорияНоменклатуры, "КатегорияНоменклатуры");
        
        Если СтрокаТЗ <> Неопределено Тогда
            // Определяем целевое имя колонки на основе даты планирования из строки ТЧ
            ИмяКолонкиДляЗаписи = ПолучитьИмяКолонкиПериода(НачалоМесяца(СтрокаТЧ.ДатаПланирования));
            
            // Проверяем на всякий случай, существует ли такая колонка в структуре таблицы
            Если ВременнаяТЗ.Колонки.Найти(ИмяКолонкиДляЗаписи) <> Неопределено Тогда
                // Записываем сумму в соответствующую ячейку строки
                СтрокаТЗ[ИмяКолонкиДляЗаписи] = СтрокаТЧ.Сумма;
            КонецЕсли;
        КонецЕсли;
        
    КонецЦикла;

    // Возвращаем заполненную таблицу значений обратно на форму
    ЗначениеВДанныеФормы(ВременнаяТЗ, ТаблицаПрозводственныеПланы);

КонецПроцедуры

Где вызывать эти процедуры?

Для того чтобы при открытии существующего документа пользователь сразу видел заполненную красивую таблицу, вызовы процедур необходимо прописать в правильном обработчике событий формы. Идеальным местом для этого является стандартное событие ПриЧтенииНаСервере. Оно срабатывает при открытии формы существующего объекта, а также при его перечитывании.


&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
    
    // Сначала инициализируем колонки под период документа
    ИнициализироватьДинамическуюТаблицу();
    
    // Затем заполняем эти колонки данными из сохраненной ТЧ
    ЗаполнитьДанныеИзТабличнойЧасти();
    
КонецПроцедуры

Обратный процесс: сохранение данных из ТЗ в табличную часть документа

Для того чтобы интерфейс не просто показывал данные, но и позволял их редактировать, необходимо реализовать обратный процесс. При записи документа нам нужно прочитать данные из заполненной пользователем таблицы на форме и перенести их обратно в строки табличной части Производство.

Посмотрим на пример реализации этой процедуры, которую следует вызывать в обработчике формы ПередЗаписьюНаСервере.


&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)

    // Очищаем старые данные в ТЧ объекта, чтобы перезаписать их актуальными
    ТекущийОбъект.Производство.Очистить();
    
    // Преобразуем таблицу формы в объект ТаблицаЗначений на сервере
    ТаблицаФормы = ДанныеФормыВЗначение(ТаблицаПрозводственныеПланы, Tip("ТаблицаЗначений"));
    
    НачалоПериода = Объект.НачалоПериода;
    КонецПериода  = Объект.КонецПериода;
    
    // Обходим каждую строку нашей таблицы на форме
    Для Каждого СтрокаТЗ Из ТаблицаФормы Цикл
        
        // Для каждой категории обходим все колонки-месяцы в заданном интервале
        ТекущийМесяц = НачалоМесяца(НачалоПериода);
        Пока ТекущийМесяц <= НачалоМесяца(КонецПериода) Цикл
            
            ИмяКолонки = ПолучитьИмяКолонкиПериода(ТекущийМесяц);
            ЗначениеСуммы = СтрокаТЗ[ИмяКолонки];
            
            // Если пользователь заполнил сумму по этому месяцу, пишем ее в ТЧ
            Если ЗначениеЗаполнено(ЗначениеСуммы) И ЗначениеСуммы <> 0 Тогда
                
                НоваяСтрокаТЧ = ТекущийОбъект.Производство.Добавить();
                НоваяСтрокаТЧ.КатегорияНоменклатуры = СтрокаТЗ.КатегорияНоменклатуры;
                НоваяСтрокаТЧ.ДатаПланирования       = ТекущийМесяц;
                НоваяСтрокаТЧ.Сумма                  = ЗначениеСуммы;
                
            КонецЕсли;
            
            ТекущийМесяц = ДобавитьМесяц(ТекущийМесяц, 1);
        КонецЦикла;
        
    КонецЦикла;

КонецПроцедуры

Заключение и лучшие практики

Мы спроектировали и реализовали надежную систему динамического транспонирования данных для управляемых форм 1С. Рассмотренный нами алгоритм гарантирует:

← На главную