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

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

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

В этой статье мы подробно разберем, как обеспечить «живучесть» таких данных, рассмотрим механизмы их сохранения при записи объекта и алгоритмы автоматического заполнения при открытии формы.

Проблема программных реквизитов формы

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


ДобавляемыеРеквизиты = Новый Массив;
ОписаниеТипаЧисло = Новый ОписаниеТипов("Число",,, Новый КвалификаторыЧисла(15, 2));
ДобавляемыеРеквизиты.Добавить(Новый РеквизитФормы("ЦенаПоСоглашению", ОписаниеТипаЧисло, "Объект.Товары", "Цена по соглашению"));

Форма.ИзменитьРеквизиты(ДобавляемыеРеквизиты);

Элемент = Форма.Элементы.Добавить("ЦенаПоСоглашению", Тип("ПолеФормы"), Форма.Элементы.Товары);
Элемент.Вид = ВидПоляФормы.ПолеВвода;
Элемент.ПутьКДанным = "Объект.Товары.ЦенаПоСоглашению";

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

Решение 1: Использование расширений конфигурации

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

  1. Добавьте документ в расширение.
  2. В расширении добавьте нужный реквизит в табличную часть.
  3. Вынесите его на форму.

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

Решение 2: Хранение данных во внешнем Регистре сведений

Если по каким-то причинам использование расширений невозможно (например, старая версия платформы или специфические требования), мы можем использовать вспомогательный Регистр сведений. Разберем этот алгоритм по шагам.

Шаг 1. Создание регистра. Создадим непериодический независимый регистр сведений (например, ДополнительныеДанныеСтрокТЧ). Измерения: ДокументСсылка (тип документа), КлючСвязиСтроки (Число или УникальныйИдентификатор). Ресурс: ЗначениеПоля (нужный вам тип).

Шаг 2. Заполнение данных при чтении. В модуле формы в обработчике ПриЧтенииНаСервере нам нужно запросить данные из регистра и подставить их в нашу программную колонку.


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

Шаг 3. Сохранение данных. В обработчике ПередЗаписьюНаСервере или в модуле объекта необходимо записать данные из ТЧ в регистр.


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

Важное примечание: Использование НомерСтроки в качестве ключа связи опасно, так как при сортировке или удалении строк номера меняются. Рекомендуем добавить в ТЧ скрытый реквизит метаданных ИдентификаторСтроки (тип Строка/Число), который будет заполняться один раз при добавлении строки и никогда не меняться.

Решение 3: Сериализация в скрытый технический реквизит

Если вы хотите хранить данные внутри самого документа, но не хотите создавать много новых полей, можно создать один реквизит ТЧ ДополнительныеСведения с типом Строка (неограниченной длины). Проанализируем, как использовать этот подход.

В этот реквизит мы будем записывать структуру данных в формате JSON или в виде простого соответствия.

Пример сохранения: Перед записью мы собираем все программные данные строки в структуру, превращаем её в строку и записываем в скрытое поле.


Для Каждого СтрокаТЧ Из Объект.Товары Цикл
    ДанныеДляХранения = Новый Структура;
    ДанныеДляХранения.Вставить("ЦенаПоСоглашению", СтрокаТЧ.ЦенаПоСоглашению);
    
    // Сериализация в JSON
    ЗаписьJSON = Новый ЗаписьJSON;
    ЗаписьJSON.УстановитьСтроку();
    ЗаписатьJSON(ЗаписьJSON, ДанныеДляХранения);
    СтрокаТЧ.ДополнительныеСведения = ЗаписьJSON.Закрыть();
КонецЦикла;

Пример загрузки: При открытии формы мы выполняем обратную операцию — десериализацию.


Для Каждого СтрокаТЧ Из Объект.Товары Цикл
    Если ЗначениеЗаполнено(СтрокаТЧ.ДополнительныеСведения) Тогда
        ЧтениеJSON = Новый ЧтениеJSON;
        ЧтениеJSON.УстановитьСтроку(СтрокаТЧ.ДополнительныеСведения);
        Данные = ПрочитатьJSON(ЧтениеJSON);
        СтрокаТЧ.ЦенаПоСоглашению = Данные.ЦенаПоСоглашению;
        ЧтениеJSON.Закрыть();
    КонецЕсли;
КонецЦикла;

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

Итоги и рекомендации

Мы рассмотрели несколько способов решения проблемы. Подведем итог, какой метод выбрать в зависимости от ситуации:

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

← На главную