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

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

При проектировании сложных и динамических интерфейсов в 1С разработчики часто сталкиваются с необходимостью вывода данных в виде кросс-таблицы (так называемой «шахматки»). Яркий пример такой задачи: горизонтальный вывод номенклатуры в колонках таблицы формы, где на пересечении строк (например, с сотрудниками) и динамических колонок (с товарами) пользователь вводит количество или суммы.

Однако если просто добавить колонку на управляемую форму программно с помощью метода ИзменитьРеквизиты, то при записи документа введенные значения бесследно исчезнут. В этой статье мы подробно разберем, почему это происходит, как устроена клиент-серверная архитектура управляемых форм 1С в контексте динамических данных и как правильно реализовать сохранение этой информации в базу данных.

Почему динамически созданные колонки не сохраняются автоматически?

Прежде чем перейти к написанию кода, давайте разберем фундаментальное правило платформы 1С:Предприятие. Метаданные базы данных статичны во время работы пользователя. Вы не можете «на лету» изменить структуру физической таблицы SQL-базы данных и добавить туда новое поле, просто нажав кнопку в пользовательском режиме. Платформа делает это только в конфигураторе при реструктуризации базы данных.

Когда вы выполняете программный код по добавлению реквизитов формы:


нРеквизиты = Новый Массив;
нРеквизиты.Добавить(Новый РеквизитФормы("ИмяКолонки", Новый ОписаниеТипов("Число"), "Объект.ТабличнаяЧасть1"));
ИзменитьРеквизиты(нРеквизиты);

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

Архитектурное решение: Паттерн «Шахматка»

Чтобы обойти это ограничение и обеспечить удобный ввод данных с гарантированным сохранением, мы применим классический паттерн проектирования:

  1. В метаданных документа (в конфигураторе) мы создаем скрытую от пользователя табличную часть (назовем ее ВыданнаяНоменклатура) со стандартной «плоской» (двумерной) структурой: Сотрудник, Номенклатура, Количество.
  2. На управляемой форме мы создаем реквизит с типом ТаблицаЗначений (назовем его ТЗ_Формы). В нем по умолчанию будет только одна статическая колонка — Сотрудник.
  3. При открытии или чтении формы на сервере мы анализируем, какие колонки-товары нам нужно вывести (например, на основе данных другого документа-основания или остатков). Мы динамически создаем эти колонки в ТЗ_Формы, выводим их на экран и заполняем пересечения строк данными из нашей скрытой плоской табличной части.
  4. При записи документа мы выполняем обратную операцию: обходим нашу красивую таблицу на форме, собираем все заполненные ячейки и «сворачиваем» их обратно в плоскую табличную часть документа ВыданнаяНоменклатура для сохранения в базу данных.

Рассмотрим пошагово программную реализацию этой схемы.

Шаг 1. Инициализация формы и динамическое создание колонок

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

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

Для связи имени динамической колонки с реальной ссылкой на товар на форме мы создадим служебную таблицу значений — КартаКолонок (с колонками ИмяКолонки и Номенклатура).


&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
    
    // Сначала очищаем форму от старых динамических колонок (если они были созданы ранее)
    ОчиститьДинамическиеЭлементы();
    
    // Получаем массив или таблицу номенклатуры, которую нужно отобразить в виде колонок
    // Например, выберем ее запросом из документа-основания "Свод остатков"
    ТаблицаНоменклатуры = ПолучитьНоменклатуруДляВывода(ТекущийОбъект);
    
    МассивДобавляемыхРеквизитов = Новый Массив;
    
    Для Каждого СтрокаНом Из ТаблицаНоменклатуры Цикл
        
        СсылкаНаТовар = СтрокаНом.Номенклатура;
        
        // Преобразуем UUID ссылки в безопасное имя колонки
        УникальныйКод = СтрЗаменить(Строка(СсылкаНаТовар.УникальныйИдентификатор()), "-", "_");
        ИмяКолонки = "Номенклатура_" + УникальныйКод;
        
        // Регистрируем соответствие в карте колонок на форме
        НоваяСтрокаКарты = КартаКолонок.Добавить();
        НоваяСтрокаКарты.ИмяКолонки = ИмяКолонки;
        НоваяСтрокаКарты.Номенклатура = СсылкаНаТовар;
        
        // Создаем описание нового реквизита для нашей таблицы значений "ТЗ_Формы"
        НовыйРеквизит = Новый РеквизитФормы(
            ИмяКолонки, 
            Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)), 
            "ТЗ_Формы", 
            СсылкаНаТовар.Наименование
        );
        МассивДобавляемыхРеквизитов.Добавить(НовыйРеквизит);
        
    КонецЦикла;
    
    // Добавляем реквизиты в структуру формы
    ИзменитьРеквизиты(МассивДобавляемыхРеквизитов);
    
    // Теперь визуализируем эти колонки на форме в элементе "ТаблицаФормыОстатки"
    Для Каждого СтрокаКарты Из КартаКолонок Цикл
        
        НовыйЭлемент = Элементы.Добавить("ТЗ_Формы_" + СтрокаКарты.ИмяКолонки, Тип("ПолеФормы"), Элементы.ТаблицаФормыОстатки);
        НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
        НовыйЭлемент.ПутьКДанным = "ТЗ_Формы." + СтрокаКарты.ИмяКолонки;
        НовыйЭлемент.Заголовок = Строка(СтрокаКарты.Номенклатура);
        НовыйЭлемент.УстановитьДействие("ПриИзменении", "ДинамическоеПолеПриИзменении");
        
    КонецЦикла;
    
    // Заполняем ТЗ_Формы данными из скрытой плоской ТЧ документа
    ЗаполнитьТаблицуФормыИзТЧ(ТекущийОбъект);
    
КонецПроцедуры

Шаг 2. Транспонирование данных (из плоской ТЧ в динамическую таблицу формы)

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

Для обеспечения максимального быстродействия мы сначала выгрузим реквизит формы в полноценный объект ТаблицаЗначений на сервере с помощью метода РеквизитФормыВЗначение, быстро обработаем его в памяти и вернем обратно на форму.


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

Шаг 3. Обратное преобразование и сохранение данных в БД

Когда пользователь отредактировал количества в динамических колонках и нажал кнопку «Записать», нам необходимо выполнить обратную операцию. В обработчике события формы ПередЗаписьюНаСервере мы очистим плоскую табличную часть документа и заполним ее заново на основе заполненных ячеек динамической таблицы.


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

Шаг 4. Очистка динамических реквизитов (Защита от утечки памяти и ошибок)

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

Посмотрим, как выглядит безопасная процедура очистки элементов интерфейса и реквизитов формы:


&НаСервере
Процедура ОчиститьДинамическиеЭлементы()
    
    МассивУдаляемыхРеквизитов = Новый Массив;
    
    Для Каждого СтрокаКарты Из КартаКолонок Цикл
        
        // 1. Сначала удаляем визуальные элементы (поля ввода) с формы
        ИмяЭлемента = "ТЗ_Формы_" + СтрокаКарты.ИмяКолонки;
        ЭлементФормы = Элементы.Найти(ИмяЭлемента);
        Если ЭлементФормы <> Неопределено Тогда
            Элементы.Удалить(ЭлементФормы);
        КонецЕсли;
        
        // 2. Добавляем имя реквизита в массив на удаление из памяти формы
        МассивУдаляемыхРеквизитов.Добавить("ТЗ_Формы." + СтрокаКарты.ИмяКолонки);
        
    КонецЦикла;
    
    // Удаляем реквизиты из контекста формы
    Если МассивУдаляемыхРеквизитов.Количество() > 0 Тогда
        ИзменитьРеквизиты(, МассивУдаляемыхРеквизитов);
    КонецЕсли;
    
    // Очищаем вспомогательную карту колонок и саму ТЗ формы
    КартаКолонок.Очистить();
    ТЗ_Формы.Очистить();
    
КонецПроцедуры

Важные подводные камни и оптимизация производительности

При использовании паттерна «Шахматка» необходимо помнить о некоторых технических особенностях платформы 1С:

Применяя описанную технологию разделения представления (динамическая ТаблицаЗначений формы) и хранения данных (плоская Табличная часть документа в БД), мы получаем гибкий, современный и быстро работающий интерфейс, который полностью решает бизнес-задачу по вводу распределенных данных и гарантирует их надежное сохранение при работе в системе 1С.

← На главную