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

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

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

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

Анализ задачи: Что именно нам нужно объединить?

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

Имеется табличная часть со следующими колонками и данными:

  1. Номенклатура - Поставщик - Заказано - ЕстьНаСкладе - НужноЗаказать - ЦенаЗакупки
  2. Стул - Поставщик1 - 20 - 10 - 10 - 300
  3. Стол - Поставщик1 - 30 - 25 - 10 - 300

Требуется объединить строки с одинаковым поставщиком таким образом, чтобы получить:

  1. Стул, Стол - Поставщик1 - 20, 30 - 10, 25 - 10, 10 - 300, 300

Обратите внимание, что требование пользователя не сводится к простому суммированию числовых полей (20+30=50, 10+25=35 и т.д.), а предполагает конкатенацию всех значений соответствующих колонок через запятую. Это достаточно нетипичный запрос, поскольку обычно для числовых полей выполняется агрегация (сумма, среднее, максимум), а не строковое объединение. Однако мы рассмотрим именно такой подход, поскольку он был заявлен как желаемый результат.

Исходный код пользователя для поиска дублей, который показывает логику проверки:


ТЗДок = ЭтотОбъект.ТЧ.Выгрузить();
ТЗДок.Колонки.Добавить("КоличествоДублей");
ТЗДок.ЗаполнитьЗначения(1,"КоличествоДублей");
ТЗДок.Свернуть("Поставщик","КоличествоДублей");

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

Этот код успешно находит дубликаты по поставщику, но не выполняет их объединение. Наша задача - развить эту логику до полноценной консолидации данных.

Общий подход к решению: Использование временной таблицы запроса и встроенного языка

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

  1. Выгрузка табличной части в ТаблицуЗначений: Это позволяет нам работать с данными вне контекста объекта документа, что часто удобнее и безопаснее.
  2. Использование ТаблицыЗначений как источника данных для запроса: Запрос 1С обладает мощными инструментами для группировки и предварительной агрегации данных.
  3. Группировка в запросе: Мы будем группировать данные по колонке Поставщик, чтобы получить уникальный список поставщиков.
  4. Детальная агрегация на встроенном языке: Поскольку запросы 1С не поддерживают прямую строковую конкатенацию с разделителем (аналог `GROUP_CONCAT` в SQL), нам потребуется пройти по уникальным группам (поставщикам) и собрать необходимые данные (строки, числа) в массивы, а затем объединить их с помощью функции СтрСоединить().
  5. Загрузка результата обратно в табличную часть: После формирования агрегированных данных, мы очистим исходную табличную часть и загрузим в нее новые, объединенные строки.

Рассмотрим каждый шаг подробнее.

Детальная реализация: Консолидация данных по поставщику

Как мы уже выяснили, стандартные методы агрегации, такие как метод Свернуть() объекта ТаблицаЗначений или агрегатные функции запроса типа СУММА(), не подходят для нашей задачи в полной мере, так как они не могут конкатенировать строковые и числовые значения в требуемом формате "20, 30". Поэтому мы будем комбинировать запрос для получения уникальных группировок и обход данных на встроенном языке.

Шаг 1: Выгрузка табличной части в ТаблицуЗначений

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


// Предположим, что ТЧ - это табличная часть документа, например, Объект.Товары
// Колонки: Номенклатура (Ссылка), Поставщик (Ссылка), Заказано (Число), ЕстьНаСкладе (Число), НужноЗаказать (Число), ЦенаЗакупки (Число)

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

Шаг 2: Создание структуры для объединенных данных

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


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

Шаг 3: Получение списка уникальных поставщиков

Для того чтобы эффективно обрабатывать группы строк, нам сначала нужно получить список всех уникальных поставщиков, присутствующих в табличной части. Это можно сделать с помощью запроса к ранее выгруженной ТаблицеЗначений, используя ключевое слово РАЗЛИЧНЫЕ.


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

Шаг 4: Итерация по уникальным поставщикам и сбор агрегированных данных

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


// Перебираем каждого уникального поставщика и собираем для него агрегированные данные.
Для Каждого СтрокаПоставщик Из УникальныеПоставщики Цикл
    ТекущийПоставщик = СтрокаПоставщик.Поставщик;

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

    // Инициализируем переменные для сбора данных в строки и для суммирования чисел.
    СписокНоменклатуры = Новый Массив;
    СписокЗаказано = Новый Массив;
    СписокЕстьНаСкладе = Новый Массив;
    СписокНужноЗаказать = Новый Массив;
    СписокЦенаЗакупки = Новый Массив;

    // Инициализируем переменные для суммирования числовых значений.
    СуммаЗаказано = 0;
    СуммаЕстьНаСкладе = 0;
    СуммаНужноЗаказать = 0;
    СуммаЦенаЗакупки = 0;

    // Перебираем каждую строку, найденную для текущего поставщика.
    Для Каждого СтрокаТЧ Из НайденныеСтрокиПоПоставщику Цикл
        // Шаг 4.1: Сбор строковых и ссылочных полей для конкатенации
        // Для Номенклатуры, которая является ссылкой, мы будем брать ее наименование.
        СписокНоменклатуры.Добавить(СтрокаТЧ.Номенклатура.НаименованиеПолное);

        // Шаг 4.2: Сбор числовых полей для конкатенации и суммирования
        СписокЗаказано.Добавить(СтрокаТЧ.Заказано);
        СписокЕстьНаСкладе.Добавить(СтрокаТЧ.ЕстьНаСкладе);
        СписокНужноЗаказать.Добавить(СтрокаТЧ.НужноЗаказать);
        СписокЦенаЗакупки.Добавить(СтрокаТЧ.ЦенаЗакупки);

        // Дополнительно суммируем числовые значения, если они могут быть нужны.
        СуммаЗаказано     = СуммаЗаказано + СтрокаТЧ.Заказано;
        СуммаЕстьНаСкладе = СуммаЕстьНаСкладе + СтрокаТЧ.ЕстьНаСкладе;
        СуммаНужноЗаказать = СуммаНужноЗаказать + СтрокаТЧ.НужноЗаказать;
        // Для цены закупки сумма не всегда логична, часто требуется среднее или первое значение.
        // Однако, следуя примеру пользователя, мы ее просуммируем.
        СуммаЦенаЗакупки = СуммаЦенаЗакупки + СтрокаТЧ.ЦенаЗакупки;
    КонецЦикла;

    // Шаг 4.3: Формируем новую строку в результирующей ТаблицеЗначений.
    НоваяСтрока = РезультатОбъединенияТЗ.Добавить();
    НоваяСтрока.Поставщик = ТекущийПоставщик;

    // Объединяем собранные массивы в строки с разделителем ", ".
    НоваяСтрока.НоменклатураСтрока   = СтрСоединить(СписокНоменклатуры, ", ");
    НоваяСтрока.ЗаказаноСтрока       = СтрСоединить(СписокЗаказано, ", ");
    НоваяСтрова.ЕстьНаСкладеСтрока   = СтрСоединить(СписокЕстьНаСкладе, ", ");
    НоваяСтрока.НужноЗаказатьСтрока  = СтрСоединить(СписокНужноЗаказать, ", ");
    НоваяСтрока.ЦенаЗакупкиСтрока    = СтрСоединить(СписокЦенаЗакупки, ", ");

    // Заполняем также суммарные поля.
    НоваяСтрока.ЗаказаноСумма       = СуммаЗаказано;
    НоваяСтрока.ЕстьНаСкладеСумма   = СуммаЕстьНаСкладе;
    НоваяСтрока.НужноЗаказатьСумма  = СуммаНужноЗаказать;
    НоваяСтрока.ЦенаЗакупкиСумма    = СуммаЦенаЗакупки;
КонецЦикла;

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

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


// Шаг 6: Очищаем исходную табличную часть документа и загружаем в нее объединенные данные.
ЭтотОбъект.ТЧ.Очистить();

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

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

    // Внимание! Если колонка "Номенклатура" в ТЧ документа имеет тип "Ссылка",
    // то мы не можем напрямую записать туда строку "Стул, Стол".
    // Если вам нужно именно такое представление, то в метаданных ТЧ документа
    // должна быть колонка строкового типа (например, "ПереченьНоменклатуры").
    // В данном примере мы предполагаем, что если Номенклатура - ссылка,
    // то агрегировать ее таким образом в саму колонку Номенклатура документа не имеет смысла.
    // Поэтому, для упрощения, мы покажем заполнение суммарных числовых полей.

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

// Загружаем полученную ТЗ обратно в табличную часть документа
ЭтотОбъект.ТЧ.Загрузить(НоваяТЧДокумента);

// Можно также вывести РезультатОбъединенияТЗ в форму или в сообщение пользователю,
// если нужно показать объединенные данные в строковом формате перед записью.
// Например:
// Сообщить("Объединенные данные:");
// РезультатОбъединенияТЗ.ВыбратьСтроку();

Важный момент: Если вам действительно нужно, чтобы в колонке Номенклатура (или любой другой ссылочной/числовой колонке) в табличной части документа отображалось строковое значение типа "Стул, Стол" или "20, 30", то вам необходимо убедиться, что соответствующая колонка в метаданных табличной части документа имеет тип Строка достаточной длины. В противном случае, вы не сможете загрузить строковые значения в колонку ссылочного или числового типа. В этом случае, возможно, стоит добавить в табличную часть дополнительные колонки, специально предназначенные для хранения агрегированных строковых представлений.

Альтернативный подход: Использование метода `Свернуть()`

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


// Пример использования метода Свернуть() для суммирования
ПереходнаяТЗ = ЭтотОбъект.ТЧ.Выгрузить();

// Сворачиваем по поставщику, суммируя числовые поля.
// Колонки группировки: "Поставщик"
// Колонки суммирования: "Заказано", "ЕстьНаСкладе", "НужноЗаказать", "ЦенаЗакупки"
ПереходнаяТЗ.Свернуть("Поставщик", "Заказано,ЕстьНаСкладе,НужноЗаказать,ЦенаЗакупки");

// После выполнения этого метода, ПереходнаяТЗ будет содержать уникальные строки
// по Поставщику, а числовые колонки будут просуммированы.

// Загружаем результат обратно в табличную часть документа.
ЭтотОбъект.ТЧ.Очистить();
ЭтотОбъект.ТЧ.Загрузить(ПереходнаяТЗ);

Ограничения метода `Свернуть()`:

  1. Метод Свернуть() автоматически суммирует только числовые значения. Он не умеет конкатенировать строковые или ссылочные значения через запятую, как того требовал пользователь в данном случае.
  2. Колонки, не вошедшие ни в список группировки, ни в список суммирования, будут удалены из таблицы значений. Это означает, что колонка Номенклатура, если она не является группирующей или суммирующей, будет утеряна.

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

Бизнес-контекст: Консолидация заказов поставщику

Описанный механизм объединения строк является краеугольным камнем для процесса консолидации заказов поставщику. Часто в бизнесе возникает ситуация, когда на основании нескольких заказов покупателей (или просто потребностей в номенклатуре) необходимо сформировать один сводный заказ поставщику. Это оптимизирует процесс закупок, снижает издержки и упрощает логистику.

Типовые конфигурации 1С (такие как "1С:Управление торговлей", "1С:Комплексная автоматизация", "1С:Управление нашей фирмой") обычно предоставляют механизмы для консолидации потребностей, которые могут включать в себя:

  1. Автоматическое формирование заказов поставщикам: Система может анализировать текущие остатки, заказы покупателей и планируемые поступления, чтобы автоматически предлагать создать заказы поставщикам.
  2. Группировка по поставщику: При формировании сводного заказа номенклатура, закупаемая у одного и того же поставщика, автоматически группируется, а ее количество суммируется.
  3. Учет минимальных партий и сроков поставки: Более сложные механизмы могут учитывать дополнительные условия закупок.

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

Заключение

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

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

← На главную