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

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

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

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

Перенос и агрегация данных (сворачивание) из ТаблицыЗначений в ТабличнуюЧасть

Представим ситуацию: у нас есть документ, в котором создаются заявки на продукцию. На форме документа присутствуют две таблицы. В первой таблице, которая является частью объекта документа (например, табличная часть "Продукция"), мы добавляем номенклатуру. Во второй таблице, которая является реквизитом формы типа ТаблицаЗначений, мы хотим видеть агрегированные данные, а затем перенести их обратно в табличную часть документа, возможно, с учетом сворачивания по определенным полям. Основная проблема заключается в том, как правильно переносить номенклатуру и другие данные, особенно если требуется сгруппировать несколько позиций по одному складу.

Давайте разберем процесс по шагам.

  1. Получение данных из исходной коллекции в объект ТаблицаЗначений.

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

    Если ваши исходные данные находятся в табличной части документа (например, Объект.Продукция), мы выгружаем их так:

    
    // Предполагаем, что "Продукция" - это имя табличной части документа
    МояТаблицаЗначений = Объект.Продукция.Выгрузить();
    

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

    
    // Предполагаем, что "ТЗНаФорме" - это имя реквизита формы с типом ТаблицаЗначений
    МояТаблицаЗначений = РеквизитФормыВЗначение("ТЗНаФорме");
    

    В этой операции мы получаем копию данных в оперативную память, с которой и будем работать.

  2. Сворачивание данных в ТаблицеЗначений.

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

    
    // Предполагаем, что в МояТаблицаЗначений есть колонки "Номенклатура", "Склад" и "Количество"
    // Сворачиваем по Номенклатуре и Складу, суммируя Количество
    МояТаблицаЗначений.Свернуть("Номенклатура, Склад", "Количество");
    

    Здесь:

    • Первый параметр "Номенклатура, Склад" — это строка с именами колонок, по которым будет происходить группировка. Все строки, имеющие одинаковые значения в этих колонках, будут объединены в одну.
    • Второй параметр "Количество" — это строка с именами колонок, значения которых будут суммироваться при сворачивании. Важно, чтобы эти колонки имели числовой тип. Если этот параметр опущен, будут просуммированы все числовые колонки таблицы.

    После выполнения этой операции, МояТаблицаЗначений будет содержать уникальные комбинации "Номенклатура + Склад" с просуммированным "Количеством".

  3. Загрузка свернутых данных обратно в табличную часть документа или обновление реквизита формы.

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

    Если мы хотим загрузить данные в табличную часть объекта документа:

    
    // Предполагаем, что "ПродукцияДокумента" - это имя целевой табличной части документа
    // Очищаем табличную часть перед загрузкой, если требуется
    Объект.ПродукцияДокумента.Очистить(); 
    // Загружаем свернутые данные
    Объект.ПродукцияДокумента.Загрузить(МояТаблицаЗначений);
    

    Если же мы работали с реквизитом формы типа ТаблицаЗначений и хотим обновить именно его на форме, используем функцию ЗначениеВРеквизитФормы():

    
    // Если МояТаблицаЗначений была получена из реквизита формы и теперь содержит свернутые данные
    // Обновляем реквизит формы
    ЗначениеВРеквизитФормы(МояТаблицаЗначений, "ТЗНаФорме");
    

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

    Работа с динамическими колонками "План" и "Факт"

    Вторая, более сложная часть задачи, заключается в следующем: после добавления номенклатуры в первую таблицу (табличная часть документа), во второй таблице (реквизит формы типа ТаблицаЗначений) должны динамически появляться группы колонок. Каждая группа колонок должна быть названа по наименованию номенклатуры и содержать внутри себя две колонки — "План" и "Факт". Количество таких групп колонок, соответственно, увеличивается в зависимости от количества выбранной номенклатуры. Основной вопрос: как записать и куда сохранить значения этих динамических колонок "План" и "Факт", чтобы при новом открытии документа таблица заполнялась сохраненными данными?

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

    Давайте рассмотрим подходы к решению этой задачи.

    1. Отображение динамических колонок на форме.

      На уровне формы, использование реквизита формы типа ТаблицаЗначений позволяет нам динамически добавлять колонки. Мы можем программно создавать колонки для каждой номенклатуры:

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

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

    2. Сохранение данных динамических колонок в базе данных.

      Это самый критичный момент. Поскольку табличная часть документа имеет фиксированную структуру, мы не можем просто добавить туда колонки типа "План_Номенклатура1". Рассмотрим несколько вариантов решения этой задачи.

      Вариант 1: Сериализация ТаблицыЗначений

      Один из возможных подходов — сохранение всей структуры ТаблицыЗначений (или только ее динамической части) в виде строки (например, в формате XML или JSON) в отдельном строковом реквизите документа. При открытии документа эта строка будет десериализоваться обратно в ТаблицуЗначений для отображения на форме.

      1. Хранение: Создаем в документе строковый реквизит с достаточно большой длиной (например, "ДанныеПланФактXML").
      2. Запись: Перед записью документа, мы сериализуем нашу ТаблицуЗначений в строку XML или JSON и записываем ее в этот строковый реквизит.
      3. Чтение: При открытии формы документа, если строковый реквизит не пуст, десериализуем его обратно в ТаблицуЗначений и используем для заполнения реквизита формы.
      
      // Пример сохранения (в обработчике ПередЗаписью или аналогичном):
      // Получаем ТаблицуЗначений с формы
      МояДинамическаяТЗ = РеквизитФормыВЗначение("ТЗНаФорме");
      
      // Сериализуем МояДинамическаяТЗ в строку XML
      // Используем стандартный СериализаторXDTO
      ЗаписьXML = Новый ЗаписьXML;
      ЗаписьXML.УстановитьСтроку();
      ФабрикаXDTO.ЗаписатьXML(ЗаписьXML, МояДинамическаяТЗ);
      СтрокаXML = ЗаписьXML.Закрыть();
      
      // Присваиваем строковому реквизиту объекта
      Объект.ДанныеПланФактXML = СтрокаXML;
      
      // Пример загрузки (в обработчике ПриОткрытии или ПриСозданииНаСервере):
      // Предполагаем, что Объект.ДанныеПланФактXML содержит XML-строку
      Если Не ПустаяСтрока(Объект.ДанныеПланФактXML) Тогда
          ЧтениеXML = Новый ЧтениеXML;
          ЧтениеXML.УстановитьСтроку(Объект.ДанныеПланФактXML);
          МояДинамическаяТЗ = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML);
      Иначе
          МояДинамическаяТЗ = Новый ТаблицаЗначений; // Если данных нет, создаем пустую ТЗ
          // Добавляем статичные колонки
          МояДинамическаяТЗ.Колонки.Добавить("НаименованиеСклада", Новый ОписаниеТипов("Строка"));
      КонецЕсли;
      
      // После загрузки/создания колонок, обновляем реквизит формы
      ЗначениеВРеквизитФормы(МояДинамическаяТЗ, "ТЗНаФорме");
      

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

      Вариант 2: "Плоская" структура данных (Рекомендованный)

      Это наиболее предпочтительный и стандартный для 1С подход, если данные требуют сохранения в базе и последующей обработки. Вместо динамических колонок, мы используем фиксированный набор колонок в табличной части документа, который представляет собой "плоскую" структуру.

      Например, ваша табличная часть документа может содержать следующие колонки:

      • Склад (тип: СправочникСсылка.Склады)
      • Номенклатура (тип: СправочникСсылка.Номенклатура)
      • План (тип: Число)
      • Факт (тип: Число)

      В этом случае, вместо одной строки для склада с динамическими колонками "План_Номенклатура1", "Факт_Номенклатура1", "План_Номенклатура2", "Факт_Номенклатура2", у вас будет несколько строк:

      Склад Номенклатура План Факт
      Столовая1 Номенклатура1 100 90
      Столовая1 Номенклатура2 50 45

      Преимущества:

      • Стандартно для 1С: Полностью соответствует архитектуре платформы.
      • Легко запросить: Данные легко извлекаются, фильтруются и агрегируются стандартными запросами 1С.
      • Отчетность: Удобно строить отчеты.
      • Масштабируемость: Не зависит от количества номенклатур — просто добавляются новые строки.

      Недостатки: Может потребовать дополнительной логики для преобразования этой "плоской" структуры в матричный вид на форме, если матричное представление критически важно для пользователя. Однако, обычно можно создать удобное представление и с использованием фиксированных колонок.

      
      // Пример заполнения такой табличной части документа
      // Объект.Продукция.Очистить(); // Очищаем, если нужно
      
      // Для каждой номенклатуры и склада
      // Предположим, у нас есть данные для Столовая1, Номенклатура1 (План 100, Факт 90)
      // и для Столовая1, Номенклатура2 (План 50, Факт 45)
      
      НоваяСтрока = Объект.Продукция.Добавить();
      НоваяСтрока.Склад = СсылкаНаСклад1; // Замените на реальную ссылку
      НоваяСтрока.Номенклатура = СсылкаНаНоменклатура1; // Замените на реальную ссылку
      НоваяСтрока.План = 100;
      НоваяСтрока.Факт = 90;
      
      НоваяСтрока = Объект.Продукция.Добавить();
      НоваяСтрока.Склад = СсылкаНаСклад1; // Замените на реальную ссылку
      НоваяСтрока.Номенклатура = СсылкаНаНоменклатура2; // Замените на реальную ссылку
      НоваяСтрока.План = 50;
      НоваяСтрока.Факт = 45;
      

      Вариант 3: Использование подчиненной табличной части или регистра сведений

      Этот вариант является развитием "плоской" структуры и применяется, когда данные очень объемны или имеют более сложные связи.

      1. Подчиненная табличная часть: Можно создать вторую табличную часть в документе, которая будет ссылаться на строки первой табличной части. Например, в первой табличной части "Продукция" мы добавляем номенклатуру. А во второй табличной части "ПланФактПоСкладам" для каждой строки из "Продукции" (или для каждой номенклатуры в целом) будут храниться записи с полями "Склад", "План", "Факт". Это удобно, когда одна номенклатура может иметь разные планы/факты для разных складов.
      2. Регистр сведений: Если данные должны быть доступны для запросов вне контекста конкретного документа или иметь свои собственные измерения (например, период), можно использовать регистр сведений. Это более сложное решение, но обеспечивает максимальную гибкость и производительность для анализа данных.

      Преимущества: Гибкость в моделировании данных, хорошая производительность при больших объемах, возможность строить комплексные отчеты. Недостатки: Усложнение структуры метаданных и логики работы с документом.

      Вариант 4: Комбинированный подход (Отображение динамически, хранение "плоско")

      Если для пользователей критически важно видеть матричное представление на форме, но данные должны храниться в "плоской" структуре (вариант 2), мы можем комбинировать подходы:

      1. При открытии формы: Выгружаем "плоские" данные из табличной части документа в ТаблицуЗначений. Затем программно добавляем необходимые динамические колонки "План_НоменклатураX", "Факт_НоменклатураX" в эту ТаблицуЗначений и перекладываем данные из "плоской" структуры в матричную. После этого обновляем реквизит формы ЗначениеВРеквизитФормы().
      2. При записи документа: Выгружаем матричные данные из реквизита формы ТаблицаЗначений. Затем преобразуем их обратно в "плоскую" структуру (строки табличной части "Склад", "Номенклатура", "План", "Факт") и записываем в табличную часть документа.

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

      Заключение

      Мы рассмотрели два основных аспекта работы с табличными данными в 1С: перенос и агрегация данных из ТаблицыЗначений в табличные части документа, а также работу с динамическими колонками "План" и "Факт".

      Для переноса и агрегации данных всегда используйте последовательность Выгрузить() -> Свернуть() -> Загрузить(), а также функции РеквизитФормыВЗначение() и ЗначениеВРеквизитФормы() для работы с реквизитами формы. Это обеспечит корректную работу с данными в оперативной памяти и их последующее сохранение.

      Что касается динамических колонок, то программное их добавление возможно только для объектов ТаблицаЗначений на форме. Для сохранения таких данных в базу данных настоятельно рекомендуется переходить к "плоской" структуре табличной части документа. Если матричное представление на форме является критичным, рассмотрите комбинированный подход с преобразованием данных между отображением на форме и хранением в базе. Это обеспечит стабильность, производительность и удобство дальнейшей работы с данными в системе 1С.

← На главную