При работе в 1С часто возникает задача по консолидации данных в табличной части документа. Например, когда у нас есть несколько позиций номенклатуры, заказанных у одного и того же поставщика, и мы хотим объединить их в единую строку или набор строк для формирования сводного заказа поставщику. Это позволяет упростить работу, уменьшить количество строк в документах и повысить читаемость информации.
В этой статье мы подробно разберем, как реализовать подобное объединение строк в табличной части на управляемых формах 1С, учитывая как агрегацию числовых значений, так и специфическую конкатенацию строковых и ссылочных данных, как это было запрошено в исходной теме форума.
Представим, что у нас есть табличная часть документа, которая формируется, например, на основе нескольких заказов покупателей. В этой табличной части могут быть строки, относящиеся к одному и тому же поставщику, но с разной номенклатурой и количеством. Изначальная задача, поставленная пользователем на форуме, была следующей:
Имеется табличная часть со следующими колонками и данными:
Требуется объединить строки с одинаковым поставщиком таким образом, чтобы получить:
Обратите внимание, что требование пользователя не сводится к простому суммированию числовых полей (20+30=50, 10+25=35 и т.д.), а предполагает конкатенацию всех значений соответствующих колонок через запятую. Это достаточно нетипичный запрос, поскольку обычно для числовых полей выполняется агрегация (сумма, среднее, максимум), а не строковое объединение. Однако мы рассмотрим именно такой подход, поскольку он был заявлен как желаемый результат.
Исходный код пользователя для поиска дублей, который показывает логику проверки:
ТЗДок = ЭтотОбъект.ТЧ.Выгрузить();
ТЗДок.Колонки.Добавить("КоличествоДублей");
ТЗДок.ЗаполнитьЗначения(1,"КоличествоДублей");
ТЗДок.Свернуть("Поставщик","КоличествоДублей");
Для каждого Стр Из ТЗДок Цикл
Если Стр.КоличествоДублей > 1 тогда
Отбор = Новый Структура("Поставщик", Стр.Поставщик);
НайденыеСтроки = ЭтотОбъект.ТЧ.НайтиСтроки(Отбор);
Если НайденыеСтроки.Количество>0 Тогда
Для Сч=1 По НайденыеСтроки.Количество-1 Цикл
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Одинаковый поставщик"+НайденыеСтроки[Сч].Поставщик;
Сообщение.Сообщить();
КонецЦикла;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Этот код успешно находит дубликаты по поставщику, но не выполняет их объединение. Наша задача - развить эту логику до полноценной консолидации данных.
Для решения задачи объединения строк, особенно когда требуется сложная логика агрегации (например, конкатенация строк), мы можем применить следующий общий подход, который был предложен в обсуждении на форуме:
Поставщик, чтобы получить уникальный список поставщиков.СтрСоединить().Рассмотрим каждый шаг подробнее.
Как мы уже выяснили, стандартные методы агрегации, такие как метод Свернуть() объекта ТаблицаЗначений или агрегатные функции запроса типа СУММА(), не подходят для нашей задачи в полной мере, так как они не могут конкатенировать строковые и числовые значения в требуемом формате "20, 30". Поэтому мы будем комбинировать запрос для получения уникальных группировок и обход данных на встроенном языке.
Первым делом, для удобства работы и изоляции от объекта документа, мы выгружаем содержимое нашей табличной части (например, Объект.ТЧ) в объект ТаблицаЗначений.
// Предположим, что ТЧ - это табличная часть документа, например, Объект.Товары
// Колонки: Номенклатура (Ссылка), Поставщик (Ссылка), Заказано (Число), ЕстьНаСкладе (Число), НужноЗаказать (Число), ЦенаЗакупки (Число)
// Выгружаем табличную часть в ТаблицуЗначений для удобства обработки.
// Это позволит нам работать с копией данных, не изменяя исходную ТЧ напрямую до завершения обработки.
ПереходнаяТЗ = ЭтотОбъект.ТЧ.Выгрузить();
Нам потребуется временная таблица значений, в которую мы будем собирать наши агрегированные данные. Важно заранее определить, какие колонки будут в этой таблице и какого типа они должны быть. В нашем случае, помимо колонки Поставщик, все остальные поля будут строковыми, так как мы будем хранить в них конкатенированные значения. Также мы можем добавить суммарные числовые поля для гибкости, если в будущем понадобится работать с суммами.
// Создаем новую ТаблицуЗначений для хранения объединенных данных.
// Эта ТЗ будет содержать уникальные строки по поставщикам с агрегированными данными.
РезультатОбъединенияТЗ = Новый ТаблицаЗначений;
// Колонка для поставщика - это ссылка на объект, поэтому оставляем ее соответствующего типа.
РезультатОбъединенияТЗ.Колонки.Добавить("Поставщик", Новый ОписаниеТипов("СправочникСсылка.Контрагенты")); // Или соответствующий тип ссылки на поставщика
// Для всех остальных колонок, где нужна конкатенация, создаем строковые колонки.
РезультатОбъединенияТЗ.Колонки.Добавить("НоменклатураСтрока", Новый ОписаниеТипов("Строка"));
РезультатОбъединенияТЗ.Колонки.Добавить("ЗаказаноСтрока", Новый ОписаниеТипов("Строка"));
РезультатОбъединенияТЗ.Колонки.Добавить("ЕстьНаСкладеСтрока", Новый ОписаниеТипов("Строка"));
РезультатОбъединенияТЗ.Колонки.Добавить("НужноЗаказатьСтрока", Новый ОписаниеТипов("Строка"));
РезультатОбъединенияТЗ.Колонки.Добавить("ЦенаЗакупкиСтрока", Новый ОписаниеТипов("Строка"));
// Добавляем также суммарные значения, если они могут понадобиться в дальнейшем для расчетов
РезультатОбъединенияТЗ.Колонки.Добавить("ЗаказаноСумма", Новый ОписаниеТипов("Число",,,Новый КвалификаторыЧисла(15,3)));
РезультатОбъединенияТЗ.Колонки.Добавить("ЕстьНаСкладеСумма", Новый ОписаниеТипов("Число",,,Новый КвалификаторыЧисла(15,3)));
РезультатОбъединенияТЗ.Колонки.Добавить("НужноЗаказатьСумма", Новый ОписаниеТипов("Число",,,Новый КвалификаторыЧисла(15,3)));
// Для цены закупки сумма может быть не всегда логична, но мы ее добавим для полноты примера.
РезультатОбъединенияТЗ.Колонки.Добавить("ЦенаЗакупкиСумма", Новый ОписаниеТипов("Число",,,Новый КвалификаторыЧисла(15,3)));
Для того чтобы эффективно обрабатывать группы строк, нам сначала нужно получить список всех уникальных поставщиков, присутствующих в табличной части. Это можно сделать с помощью запроса к ранее выгруженной ТаблицеЗначений, используя ключевое слово РАЗЛИЧНЫЕ.
// Получаем список уникальных поставщиков из исходной ТЗ.
// Для этого используем запрос к ТаблицеЗначений.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ТЗ.Поставщик КАК Поставщик
|ИЗ
| &ПереходнаяТЗ КАК ТЗ";
Запрос.УстановитьПараметр("ПереходнаяТЗ", ПереходнаяТЗ);
УникальныеПоставщики = Запрос.Выполнить().Выгрузить();
Это центральный шаг, где мы будем перебирать каждого уникального поставщика. Для каждого поставщика мы найдем все соответствующие строки в исходной ПереходнаяТЗ, соберем их значения в массивы, а затем объединим эти массивы в строки с разделителем. Также мы будем параллельно суммировать числовые значения, если это потребуется для других целей.
// Перебираем каждого уникального поставщика и собираем для него агрегированные данные.
Для Каждого СтрокаПоставщик Из УникальныеПоставщики Цикл
ТекущийПоставщик = СтрокаПоставщик.Поставщик;
// Отбираем все строки исходной ТЗ, относящиеся к текущему поставщику.
// Метод НайтиСтроки() объекта ТаблицаЗначений позволяет быстро получить массив строк.
Отбор = Новый Структура("Поставщик", ТекущийПоставщик);
НайденныеСтрокиПоПоставщику = ПереходнаяТЗ.НайтиСтроки(Отбор);
// Инициализируем переменные для сбора данных в строки и для суммирования чисел.
СписокНоменклатуры = Новый Массив;
СписокЗаказано = Новый Массив;
СписокЕстьНаСкладе = Новый Массив;
СписокНужноЗаказать = Новый Массив;
СписокЦенаЗакупки = Новый Массив;
// Инициализируем переменные для суммирования числовых значений.
СуммаЗаказано = 0;
СуммаЕстьНаСкладе = 0;
СуммаНужноЗаказать = 0;
СуммаЦенаЗакупки = 0;
// Перебираем каждую строку, найденную для текущего поставщика.
Для Каждого СтрокаТЧ Из НайденныеСтрокиПоПоставщику Цикл
// Шаг 4.1: Сбор строковых и ссылочных полей для конкатенации
// Для Номенклатуры, которая является ссылкой, мы будем брать ее наименование.
СписокНоменклатуры.Добавить(СтрокаТЧ.Номенклатура.НаименованиеПолное);
// Шаг 4.2: Сбор числовых полей для конкатенации и суммирования
СписокЗаказано.Добавить(СтрокаТЧ.Заказано);
СписокЕстьНаСкладе.Добавить(СтрокаТЧ.ЕстьНаСкладе);
СписокНужноЗаказать.Добавить(СтрокаТЧ.НужноЗаказать);
СписокЦенаЗакупки.Добавить(СтрокаТЧ.ЦенаЗакупки);
// Дополнительно суммируем числовые значения, если они могут быть нужны.
СуммаЗаказано = СуммаЗаказано + СтрокаТЧ.Заказано;
СуммаЕстьНаСкладе = СуммаЕстьНаСкладе + СтрокаТЧ.ЕстьНаСкладе;
СуммаНужноЗаказать = СуммаНужноЗаказать + СтрокаТЧ.НужноЗаказать;
// Для цены закупки сумма не всегда логична, часто требуется среднее или первое значение.
// Однако, следуя примеру пользователя, мы ее просуммируем.
СуммаЦенаЗакупки = СуммаЦенаЗакупки + СтрокаТЧ.ЦенаЗакупки;
КонецЦикла;
// Шаг 4.3: Формируем новую строку в результирующей ТаблицеЗначений.
НоваяСтрока = РезультатОбъединенияТЗ.Добавить();
НоваяСтрока.Поставщик = ТекущийПоставщик;
// Объединяем собранные массивы в строки с разделителем ", ".
НоваяСтрока.НоменклатураСтрока = СтрСоединить(СписокНоменклатуры, ", ");
НоваяСтрока.ЗаказаноСтрока = СтрСоединить(СписокЗаказано, ", ");
НоваяСтрова.ЕстьНаСкладеСтрока = СтрСоединить(СписокЕстьНаСкладе, ", ");
НоваяСтрока.НужноЗаказатьСтрока = СтрСоединить(СписокНужноЗаказать, ", ");
НоваяСтрока.ЦенаЗакупкиСтрока = СтрСоединить(СписокЦенаЗакупки, ", ");
// Заполняем также суммарные поля.
НоваяСтрока.ЗаказаноСумма = СуммаЗаказано;
НоваяСтрока.ЕстьНаСкладеСумма = СуммаЕстьНаСкладе;
НоваяСтрока.НужноЗаказатьСумма = СуммаНужноЗаказать;
НоваяСтрока.ЦенаЗакупкиСумма = СуммаЦенаЗакупки;
КонецЦикла;
После того как РезультатОбъединенияТЗ будет заполнен, нам нужно очистить исходную табличную часть документа и загрузить в нее новые данные. Здесь есть важный нюанс: если в исходной табличной части колонки Номенклатура, Заказано и другие имеют определенные типы (например, Номенклатура — это СправочникСсылка.Номенклатура, а Заказано — Число), то просто загрузить строковые конкатенированные значения в эти колонки не получится. Нам нужно либо изменить типы колонок в метаданных документа (что не всегда возможно или желательно), либо загрузить только суммарные значения, либо создать новые строковые колонки в табличной части для хранения конкатенированных данных. В нашем примере мы покажем, как загрузить суммарные значения, а также упомянем, как могли бы быть использованы строковые агрегаты.
// Шаг 6: Очищаем исходную табличную часть документа и загружаем в нее объединенные данные.
ЭтотОбъект.ТЧ.Очистить();
// Создаем пустую ТаблицуЗначений с такой же структурой, как ТЧ документа.
// Это поможет нам преобразовать РезультатОбъединенияТЗ в нужный формат.
НоваяТЧДокумента = ЭтотОбъект.ТЧ.Выгрузить();
НоваяТЧДокумента.Очистить(); // Очищаем, так как структура уже скопирована
// Перебираем объединенные строки и заполняем новую ТЗ для загрузки в документ.
Для Каждого СтрокаОбъединения Из РезультатОбъединенияТЗ Цикл
НоваяСтрокаТЧ = НоваяТЧДокумента.Добавить();
НоваяСтрокаТЧ.Поставщик = СтрокаОбъединения.Поставщик;
// Внимание! Если колонка "Номенклатура" в ТЧ документа имеет тип "Ссылка",
// то мы не можем напрямую записать туда строку "Стул, Стол".
// Если вам нужно именно такое представление, то в метаданных ТЧ документа
// должна быть колонка строкового типа (например, "ПереченьНоменклатуры").
// В данном примере мы предполагаем, что если Номенклатура - ссылка,
// то агрегировать ее таким образом в саму колонку Номенклатура документа не имеет смысла.
// Поэтому, для упрощения, мы покажем заполнение суммарных числовых полей.
// Заполняем числовые поля суммарными значениями.
// Если в ТЧ документа есть колонки "Заказано", "ЕстьНаСкладе", "НужноЗаказать", "ЦенаЗакупки" числового типа,
// то сюда можно записать агрегированные суммы.
НоваяСтрокаТЧ.Заказано = СтрокаОбъединения.ЗаказаноСумма;
НоваяСтрокаТЧ.ЕстьНаСкладе = СтрокаОбъединения.ЕстьНаСкладеСумма;
НоваяСтрокаТЧ.НужноЗаказать = СтрокаОбъединения.НужноЗаказатьСумма;
// Цена закупки: если она агрегирована суммой, это может быть неверно.
// Возможно, вам понадобится взять среднее, минимальное, максимальное значение или первое попавшееся.
// Например:
// НоваяСтрокаТЧ.ЦенаЗакупки = СтрокаОбъединения.ЦенаЗакупкиСумма / НайденныеСтрокиПоПоставщику.Количество(); // Для средней цены
НоваяСтрокаТЧ.ЦенаЗакупки = СтрокаОбъединения.ЦенаЗакупкиСумма; // Следуя примеру
КонецЦикла;
// Загружаем полученную ТЗ обратно в табличную часть документа
ЭтотОбъект.ТЧ.Загрузить(НоваяТЧДокумента);
// Можно также вывести РезультатОбъединенияТЗ в форму или в сообщение пользователю,
// если нужно показать объединенные данные в строковом формате перед записью.
// Например:
// Сообщить("Объединенные данные:");
// РезультатОбъединенияТЗ.ВыбратьСтроку();
Важный момент: Если вам действительно нужно, чтобы в колонке Номенклатура (или любой другой ссылочной/числовой колонке) в табличной части документа отображалось строковое значение типа "Стул, Стол" или "20, 30", то вам необходимо убедиться, что соответствующая колонка в метаданных табличной части документа имеет тип Строка достаточной длины. В противном случае, вы не сможете загрузить строковые значения в колонку ссылочного или числового типа. В этом случае, возможно, стоит добавить в табличную часть дополнительные колонки, специально предназначенные для хранения агрегированных строковых представлений.
Если бы задача заключалась исключительно в суммировании числовых значений для дублирующихся строк, то метод Свернуть() объекта ТаблицаЗначений был бы более простым и лаконичным решением. Он автоматически группирует строки по указанным колонкам и суммирует значения в других указанных числовых колонках.
// Пример использования метода Свернуть() для суммирования
ПереходнаяТЗ = ЭтотОбъект.ТЧ.Выгрузить();
// Сворачиваем по поставщику, суммируя числовые поля.
// Колонки группировки: "Поставщик"
// Колонки суммирования: "Заказано", "ЕстьНаСкладе", "НужноЗаказать", "ЦенаЗакупки"
ПереходнаяТЗ.Свернуть("Поставщик", "Заказано,ЕстьНаСкладе,НужноЗаказать,ЦенаЗакупки");
// После выполнения этого метода, ПереходнаяТЗ будет содержать уникальные строки
// по Поставщику, а числовые колонки будут просуммированы.
// Загружаем результат обратно в табличную часть документа.
ЭтотОбъект.ТЧ.Очистить();
ЭтотОбъект.ТЧ.Загрузить(ПереходнаяТЗ);
Ограничения метода `Свернуть()`:
Свернуть() автоматически суммирует только числовые значения. Он не умеет конкатенировать строковые или ссылочные значения через запятую, как того требовал пользователь в данном случае.Номенклатура, если она не является группирующей или суммирующей, будет утеряна.Из-за этих ограничений, для выполнения специфического требования по строковой конкатенации, метод Свернуть() не является полноценным решением, и более гибкий подход с запросами и обходом на встроенном языке становится предпочтительным.
Описанный механизм объединения строк является краеугольным камнем для процесса консолидации заказов поставщику. Часто в бизнесе возникает ситуация, когда на основании нескольких заказов покупателей (или просто потребностей в номенклатуре) необходимо сформировать один сводный заказ поставщику. Это оптимизирует процесс закупок, снижает издержки и упрощает логистику.
Типовые конфигурации 1С (такие как "1С:Управление торговлей", "1С:Комплексная автоматизация", "1С:Управление нашей фирмой") обычно предоставляют механизмы для консолидации потребностей, которые могут включать в себя:
Если типовой функционал конфигурации не позволяет реализовать специфическую логику агрегации, такую как конкатенация значений, описанная в этой статье доработка может быть интегрирована в процесс формирования документов "Заказ поставщику". Например, после отбора всех необходимых позиций из разных источников (заказов покупателей, потребностей), можно применить этот алгоритм для предобработки данных перед записью в табличную часть нового документа "Заказ поставщику".
Мы рассмотрели детальный подход к объединению строк в табличной части документа 1С по одинаковому поставщику, ориентируясь на нестандартное требование по конкатенации всех значений колонок. Этот метод включает в себя выгрузку данных в ТаблицуЗначений, использование запроса для получения уникальных группировок, и последующую сложную агрегацию на встроенном языке 1С с применением массивов и функции СтрСоединить(). Мы также обсудили нюансы загрузки агрегированных данных обратно в табличную часть документа, особенно касающиеся преобразования типов данных.
Несмотря на то, что это решение более объемно, чем простое использование метода Свернуть(), оно обеспечивает необходимую гибкость для реализации любых специфических требований к агрегации данных. Всегда помните о бизнес-цели такой консолидации и выбирайте наиболее подходящий способ агрегации (суммирование, конкатенация, усреднение и т.д.), исходя из потребностей пользователя и логики учета.