При работе в 1С мы часто сталкиваемся с необходимостью представить данные в табличном документе таким образом, чтобы они были не только информативны, но и удобны для восприятия. Одна из таких сложных задач — это вывод данных из различных источников, например, нескольких таблиц значений (ТЗ) или одного запроса с различными группировками, в виде кросс-таблицы. При этом данные должны быть сгруппированы по одному полю (например, «Контрагент») в строках, а по другому полю (например, «День недели») — в колонках. Более того, часто требуется обеспечить «плотный» вывод, когда строки в колонках не пропускаются, даже если в одних колонках данных больше, чем в других. Давайте подробно разберем, как реализовать подобный вывод в табличный документ 1С, используя два основных подхода: Систему Компоновки Данных (СКД) и программное формирование.
Прежде чем приступить к формированию отчета, мы должны убедиться, что наши исходные данные подготовлены надлежащим образом. В нашем случае, как автор исходного вопроса, мы имеем либо 5 отдельных таблиц значений, либо одну таблицу значений, в которой данные уже разделены по дням недели. Для упрощения дальнейшей работы, и особенно при использовании СКД, крайне желательно объединить все исходные данные в одну универсальную таблицу значений или временную таблицу запроса. Эта объединенная таблица должна содержать как минимум следующие поля:
Контрагент: для группировки по строкам.Номенклатура: для детальных записей.Количество: для детальных записей (если нужно).НомерЗаказа: для детальных записей.ДатаЗаказа или ДеньНедели: для группировки по колонкам. Если есть только ДатаЗаказа, мы можем извлечь из неё день недели.Если у нас изначально 5 ТЗ (например, ТЗПонедельник, ТЗВторник и т.д.), мы можем программно объединить их в одну, добавив поле ДеньНедели:
Функция ОбъединитьТЗ()
ОбщаяТЗ = Новый ТаблицаЗначений;
ОбщаяТЗ.Колонки.Добавить("Контрагент");
ОбщаяТЗ.Колонки.Добавить("Номенклатура");
ОбщаяТЗ.Колонки.Добавить("Количество");
ОбщаяТЗ.Колонки.Добавить("НомерЗаказа");
ОбщаяТЗ.Колонки.Добавить("ДеньНедели");
МассивДней = Новый Массив();
МассивДней.Добавить("Понедельник");
МассивДней.Добавить("Вторник");
МассивДней.Добавить("Среда");
МассивДней.Добавить("Четверг");
МассивДней.Добавить("Пятница");
Для Каждого ДеньТек Из МассивДней Цикл
// Предполагаем, что у нас есть функция, которая возвращает ТЗ для конкретного дня
ТЗДляДня = ПолучитьТЗДляДня(ДеньТек);
Для Каждого СтрокаТЗ Из ТЗДляДня Цикл
НоваяСтрока = ОбщаяТЗ.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаТЗ);
НоваяСтрока.ДеньНедели = ДеньТек;
КонецЦикла;
КонецЦикла;
Возврат ОбщаяТЗ;
КонецФункции
// Пример функции для получения ТЗ (в реальности она будет брать данные из ваших 5 ТЗ)
Функция ПолучитьТЗДляДня(День)
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Контрагент");
ТЗ.Колонки.Добавить("Номенменклатура");
ТЗ.Колонки.Добавить("Количество");
ТЗ.Колонки.Добавить("НомерЗаказа");
Если День = "Понедельник" Тогда
Строка = ТЗ.Добавить(); Строка.Контрагент = "Иванов"; Строка.Номенклатура = "Шкаф А1"; Строка.Количество = 1; Строка.НомерЗаказа = "З0000001";
Строка = ТЗ.Добавить(); Строка.Контрагент = "Иванов"; Строка.Номенклатура = "Комод А2"; Строка.Количество = 1; Строка.НомерЗаказа = "З0000002";
Строка = ТЗ.Добавить(); Строка.Контрагент = "Иванов"; Строка.Номенклатура = "Диван А3"; Строка.Количество = 1; Строка.НомерЗаказа = "З0000003";
Строка = ТЗ.Добавить(); Строка.Контрагент = "Сидоров"; Строка.Номенклатура = "Комод А6"; Строка.Количество = 1; Строка.НомерЗаказа = "З0000006";
ИначеЕсли День = "Вторник" Тогда
Строка = ТЗ.Добавить(); Строка.Контрагент = "Петров"; Строка.Номенклатура = "Диван А4"; Строка.Количество = 1; Строка.НомерЗаказа = "З0000004";
Строка = ТЗ.Добавить(); Строка.Контрагент = "Петров"; Строка.Номенклатура = "Кровать А5"; Строка.Количество = 1; Строка.НомерЗаказа = "З0000005";
// Предположим, что во вторник у Сидорова нет заказов.
ИначеЕсли День = "Среда" Тогда
Строка = ТЗ.Добавить(); Строка.Контрагент = "Кириллов"; Строка.Номенклатура = "Кухня А7"; Строка.Количество = 1; Строка.НомерЗаказа = "З0000007";
КонецЕсли;
Возврат ТЗ;
КонецФункции
СКД — это мощный инструмент в 1С для построения отчетов, который позволяет декларативно описать структуру отчета, его данные, группировки и макеты. Для нашей задачи СКД подходит идеально, так как она специально разработана для создания кросс-табличных отчетов.
ТаблицуЗначений (как в примере выше), либо «Набор данных – Запрос», если мы хотим получить данные запросом напрямую из базы данных или из временных таблиц.Создайте новый набор данных типа «Объект». В качестве имени объекта укажите имя нашей объединенной ТаблицыЗначений, например, ОбщаяТЗ. СКД автоматически считает поля из этой таблицы.
Например, в модуле объекта отчета (обработки) в обработчике события ПриКомпоновкеРезультата() мы можем передать нашу ОбщаяТЗ в качестве параметра:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
Настройки = КомпоновщикНастроек.ПолучитьНастройки(); // Получаем настройки из формы отчета
// Здесь мы передаем нашу объединенную ТаблицуЗначений в СКД
ОбщаяТЗ = ОбъединитьТЗ(); // Вызов функции, которая формирует нашу общую ТЗ
НаборДанныхОбъект = Настройки.ДоступныеНастройки.Элементы.Найти("ОбщаяТЗ"); // Имя набора данных в СКД
Если НаборДанныхОбъект <> Неопределено Тогда
НаборДанныхОбъект.Объект = ОбщаяТЗ;
КонецЕсли;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.НачатьВывод();
Пока Истина Цикл
ЭлементРезультата = ПроцессорКомпоновки.Следующий();
Если ЭлементРезультата = Неопределено Тогда
Прервать;
КонецЕсли;
ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
КонецЦикла;
ПроцессорВывода.ЗакончитьВывод();
КонецПроцедуры
В тексте запроса вы можете получить данные из временных таблиц или напрямую из базы данных. Если у вас было 5 ТЗ, вы можете сначала загрузить их во временные таблицы с помощью МенеджерВременныхТаблиц, а затем объединить их запросом.
// Пример запроса, если у нас уже есть поле ДеньНедели
ВЫБРАТЬ
ТЗ.Контрагент,
ТЗ.Номенклатура,
ТЗ.Количество,
ТЗ.НомерЗаказа,
ТЗ.ДеньНедели
ИЗ
ОбъединеннаяТЗ КАК ТЗ // Это может быть временная таблица или виртуальная таблица
После создания набора данных перейдите на вкладку «Поля» и убедитесь, что все необходимые поля определены, а также укажите их роли (измерение, ресурс).
Контрагент.КоличествоЗаказов. В качестве выражения для ресурса укажите КОЛИЧЕСТВО(Различные НомерЗаказа) или СУММА(Выбор Когда Не НомерЗаказа ЕСТЬ NULL Тогда 1 Иначе 0 Конец).Контрагент и добавьте в выбранные поля наш ресурс КоличествоЗаказов.Номенклатура и НомерЗаказа. Для этого добавьте еще одну группировку в строках ПОД группировкой Контрагент, но сделайте её «Детальные записи». В выбранные поля для этой детальной группировки укажите Номенклатура и НомерЗаказа.ДеньНедели.ДеньНедели — это строковое поле, вы можете использовать вкладку «Параметры и ограничения» для упорядочивания дней недели (например, через список значений или выражение).Для каждого пересечения строки (Контрагент) и колонки (ДеньНедели) нам нужно вывести наши детальные записи. СКД по умолчанию выводит ресурсы в ячейках пересечения. Однако нам нужно вывести не просто сумму, а список номенклатуры и номеров заказов, причём «плотно» друг под другом.
Для достижения этого, мы можем использовать следующий подход:
Нам потребуется создать вычисляемое поле, которое будет собирать все детальные записи для конкретного контрагента и дня недели. Это сложный момент, поскольку СКД по умолчанию не предназначена для вывода списка значений в одной ячейке кросс-таблицы без использования макетов. Мы можем имитировать это, но для полной гибкости нам понадобятся макеты.
В рамках стандартных настроек СКД, без использования пользовательских макетов для ячеек, мы можем вывести один ресурс в каждой ячейке пересечения. Например, количество заказов за день для данного контрагента. Вывод детальных записей "Номенклатура \ Номер заказа" под каждым контрагентом в *разных* колонках с "плотным" расположением - это то место, где стандартная СКД начинает испытывать трудности без программной доработки или очень сложных макетов.
Если нам нужно вывести в одной ячейке пересечения Контрагент - ДеньНедели не просто ресурс (например, количество), а список детальных записей (Номенклатура, НомерЗаказа) в несколько строк, то мы должны использовать пользовательские макеты.
Перейдите на вкладку «Макеты» в СКД.
Контрагент и ДеньНедели.ПараметрНоменклатура1, ПараметрНомерЗаказа1, ПараметрНоменклатура2, ПараметрНомерЗаказа2 и т.д. Но это статично. Более гибкий подход — использование текстового поля с HTML-разметкой или функцией ВычислитьВыражениеСГруппировкойМассив().ВычислитьВыражениеСГруппировкойМассив(): это функция позволяет собрать значения из детальных записей в массив, который затем можно вывести в виде строки.На вкладке «Ресурсы» создайте ресурс, например, ДетальныеЗаписиДня:
ВычислитьВыражениеСГруппировкойМассив("Номенклатура + "" "" + НомерЗаказа", "Номенклатура, НомерЗаказа", "Контрагент, ДеньНедели")
Это выражение соберет все пары Номенклатура + НомерЗаказа для каждого пересечения Контрагент-ДеньНедели в массив. Затем в макете ячейки или через настройку формата поля можно будет вывести этот массив, объединив его элементы, например, символом новой строки.
Важно: СКД не имеет встроенного механизма для "плотного" вывода нескольких строк данных в одной ячейке кросс-таблицы таким образом, чтобы эти строки определяли общую высоту строки группировки. Обычно, если мы используем макеты для детальных записей внутри группировки, то каждая детальная запись будет занимать отдельную строку в своем столбце, что приведет к пропускам. Чтобы избежать этого, мы должны либо максимально использовать вычисляемые поля, объединяя все данные для одной ячейки в одну строку (например, через разделители), либо использовать программное формирование.
Для задачи "плотного" вывода детальных записей в колонках (как в примере "Диван А3", а следующий контрагент напротив следующей строки), СКД в чистом виде не подходит без программной доработки или очень сложных пользовательских макетов, которые будут динамически менять количество строк в ячейках. Обычно, кросс-таблицы СКД работают с фиксированной высотой строк и столбцов на уровне детальных записей.
Требование "в колонках таблицы строки пропускать нельзя" и "если во вторник будет еще один контрагент, он должен выводиться не напротив Сидорова, а напротив Диван А3" крайне сложно реализовать стандартными средствами СКД для кросс-таблиц. Кросс-таблица СКД генерирует таблицу, где каждая строка соответствует одной строковой группировке, и каждая колонка - одной колоночной группировке. Детальные записи внутри ячейки кросс-таблицы обычно "растягивают" высоту этой ячейки, но не влияют на "выравнивание" следующих группировок. Это означает, что если у "Иванова" в Понедельник 3 записи, а у "Петрова" во Вторник 2 записи, то следующая строка (например, "Сидоров") будет начинаться на уровне строки "Петрова", а не на уровне третьей записи "Иванова".
Для достижения "плотного" вывода в СКД придется прибегнуть к следующим приемам, которые, однако, сильно усложняют схему:
Используйте вычисляемое поле с функцией ВычислитьВыражениеСГруппировкойМассив() для сбора всех номенклатур и номеров заказов в массив для каждого пересечения "Контрагент"-"ДеньНедели". Затем объедините элементы этого массива в одну строку с разделителем перевода строки (Символы.ПС). Таким образом, каждая ячейка будет содержать многострочный текст.
// Ресурс или вычисляемое поле для одной ячейки кросс-таблицы
СтрокаЗаказовДляЯчейки =
ВЫБОР
КОГДА (ВычислитьВыражениеСГруппировкойМассив("Номенклатура + "" "" + НомерЗаказа", "Номенклатура, НомерЗаказа", "Контрагент, ДеньНедели") ЕСТЬ NULL)
ТОГДА ""
ИНАЧЕ СтрСоединить(ВычислитьВыражениеСГруппировкойМассив("Номенклатура + "" "" + НомерЗаказа", "Номенклатура, НомерЗаказа", "Контрагент, ДеньНедели"), Символы.ПС)
КОНЕЦ
Этот ресурс вы будете выводить в ячейке пересечения Контрагент - ДеньНедели. Тогда каждая ячейка будет динамически изменять свою высоту, но это не изменит выравнивание следующих группировок Контрагентов. Следующий контрагент все равно начнется на уровне окончания предыдущего контрагента в первой колонке.
Можно сгенерировать табличный документ с помощью СКД, а затем программно пройти по нему и "сжать" строки, перемещая данные. Однако это крайне сложный и неэффективный путь, сводящий на нет преимущества СКД.
Вывод по СКД: СКД отлично подходит для формирования кросс-таблиц с агрегатными значениями или многострочным текстом в ячейках. Однако требование "плотного" вывода, когда последующие группировки начинаются не с новой логической строки отчета, а с физической строки, следующей за последней детальной записью предыдущей группировки в любой колонке, не реализуется стандартными средствами СКД. Для такой сложной компоновки мы рекомендуем программное формирование табличного документа.
Программное формирование табличного документа дает нам полный контроль над каждой ячейкой, ее содержимым, форматированием и расположением. Это более трудоемкий, но и более гибкий подход, который позволит нам решить задачу "плотного" вывода.
Как мы уже обсуждали, нам нужна одна объединенная ТаблицаЗначений со всеми данными и полем ДеньНедели. Дополнительно, для "плотного" вывода, нам нужно будет знать максимальное количество детальных записей для каждого контрагента в любой из колонок дней.
Важный шаг: Агрегируйте данные по контрагенту и дню недели так, чтобы получить массивы детальных записей.
Функция ПодготовитьДанныеДляВывода(ОбщаяТЗ)
ДанныеПоКонтрагентам = Новый ТаблицаЗначений;
ДанныеПоКонтрагентам.Колонки.Добавить("Контрагент");
МассивДней = Новый Массив();
МассивДней.Добавить("Понедельник");
МассивДней.Добавить("Вторник");
МассивДней.Добавить("Среда");
МассивДней.Добавить("Четверг");
МассивДней.Добавить("Пятница");
Для Каждого ДеньТек Из МассивДней Цикл
ДанныеПоКонтрагентам.Колонки.Добавить(ДеньТек, Новый ОписаниеТипов("Массив")); // Массив для детальных записей дня
ДанныеПоКонтрагентам.Колонки.Добавить(ДеньТек + "КоличествоЗаказов", Новый ОписаниеТипов("Число")); // Количество заказов за день
КонецЦикла;
// Сгруппируем исходные данные
ГруппированныеДанные = ОбщаяТЗ.Скопировать();
ГруппированныеДанные.Свернуть("Контрагент, ДеньНедели", "Номенклатура, Количество, НомерЗаказа"); // Свернуть не совсем подходит, нужно собрать массив.
// Лучше пройтись по контрагентам и собрать данные вручную
УникальныеКонтрагенты = ОбщаяТЗ.ВыгрузитьКолонку("Контрагент", Истина); // Истина - уникальные значения
Для Каждого КонтрагентТек Из УникальныеКонтрагенты Цикл
НоваяСтрокаКонтрагента = ДанныеПоКонтрагентам.Добавить();
НоваяСтрокаКонтрагента.Контрагент = КонтрагентТек;
Для Каждого ДеньТек Из МассивДней Цикл
// Отфильтруем данные для текущего контрагента и дня
МассивДетальныхЗаписей = Новый Массив();
КоличествоЗаказовДня = 0;
Для Каждого СтрокаТЗ Из ОбщаяТЗ Цикл
Если СтрокаТЗ.Контрагент = КонтрагентТек И СтрокаТЗ.ДеньНедели = ДеньТек Тогда
ДетальнаяЗапись = Новый Структура;
ДетальнаяЗапись.Вставить("Номенклатура", СтрокаТЗ.Номенклатура);
ДетальнаяЗапись.Вставить("НомерЗаказа", СтрокаТЗ.НомерЗаказа);
МассивДетальныхЗаписей.Добавить(ДетальнаяЗапись);
КоличествоЗаказовДня = КоличествоЗаказовДня + 1; // Просто считаем строки, можно использовать уникальный НомерЗаказа
КонецЕсли;
КонецЦикла;
НоваяСтрокаКонтрагента[ДеньТек] = МассивДетальныхЗаписей;
НоваяСтрокаКонтрагента[ДеньТек + "КоличествоЗаказов"] = КоличествоЗаказовДня;
КонецЦикла;
КонецЦикла;
Возврат ДанныеПоКонтрагентам;
КонецФункции
Создадим новый объект ТабличныйДокумент.
ТабДок = Новый ТабличныйДокумент;
ТабДок.Очистить();
Мы можем использовать макет табличного документа, заранее созданный в конфигураторе, чтобы упростить форматирование. Макет может содержать области для заголовков, группировок, детальных записей.
Макет = ПолучитьМакет("МакетОтчета"); // Например, макет называется "МакетОтчета"
В макете могут быть области:
ОбластьЗаголовокДней: для вывода дней недели.ОбластьКонтрагент: для вывода Контрагент и его количества заказов.ОбластьДетальнаяЗапись: для вывода Номенклатура и НомерЗаказа.ОбластьПустаяСтрока: если нужно заполнить пустотой, чтобы выровнять колонки.Первая строка нашего отчета — это дни недели. Мы выведем их из списка.
// Заголовки дней недели
СтрокаЗаголовков = Макет.ПолучитьОбласть("ЗаголовокДней"); // Если есть такая область в макете
Если СтрокаЗаголовков = Неопределено Тогда
// Если макета нет, создаем программно
ТабДок.ВывестиСтроку(); // Переходим на новую строку
ТабДок.Области.Получить(ТабДок.ВысотаТаблицы, 1, ТабДок.ВысотаТаблицы, 1).Текст = ""; // Пустая ячейка для Контрагента
ТабДок.Области.Получить(ТабДок.ВысотаТаблицы, 1, ТабДок.ВысотаТаблицы, 1).ШиринаКолонки = 20;
МассивДней = Новый Массив();
МассивДней.Добавить("Понедельник");
МассивДней.Добавить("Вторник");
МассивДней.Добавить("Среда");
МассивДней.Добавить("Четверг");
МассивДней.Добавить("Пятница");
ИндексКолонки = 2; // Начинаем со второй колонки
Для Каждого ДеньТек Из МассивДней Цикл
ОбластьДня = ТабДок.Области.Получить(ТабДок.ВысотаТаблицы, ИндексКолонки, ТабДок.ВысотаТаблицы, ИндексКолонки);
ОбластьДня.Текст = ДеньТек;
ОбластьДня.Параметры.День = ДеньТек; // Если используем макет с параметрами
ОбластьДня.ШиринаКолонки = 30; // Устанавливаем ширину колонки
ОбластьДня.ГоризонтальноеПоложение = ГоризонтальноеПоложение.Центр;
ОбластьДня.ВертикальноеПоложение = ВертикальноеПоложение.Центр;
ОбластьДня.Объединить(ТабДок.ВысотаТаблицы, ИндексКолонки, ТабДок.ВысотаТаблицы, ИндексКолонки); // Объединение на одну ячейку
ИндексКолонки = ИндексКолонки + 1;
КонецЦикла;
ТабДок.Присоединить(); // Завершаем вывод строки
Иначе
ИндексКолонки = 2; // Начинаем со второй колонки
Для Каждого ДеньТек Из МассивДней Цикл
СтрокаЗаголовков.Параметры[ДеньТек] = ДеньТек; // Пример для макета с параметрами "Понедельник", "Вторник" и т.д.
// Или если макет универсальный, просто задаем текст ячейки
// СтрокаЗаголовков.Области.Получить(1, ИндексКолонки).Текст = ДеньТек;
ИндексКолонки = ИндексКолонки + 1;
КонецЦикла;
ТабДок.Вывести(СтрокаЗаголовков);
КонецЕсли;
Это самая сложная часть, где нам нужно учесть "плотный" вывод. Мы будем итерировать по контрагентам, а внутри каждого контрагента — по строкам детальных записей, обеспечивая выравнивание по максимальной высоте.
// Подготовленные данные
ПодготовленныеДанные = ПодготовитьДанныеДляВывода(ОбщаяТЗ);
// Массив, который будет хранить текущие индексы строк для каждого дня недели
// чтобы отслеживать "плотный" вывод
ИндексыСтрокДляДней = Новый Соответствие;
Для Каждого ДеньТек Из МассивДней Цикл
ИндексыСтрокДляДней.Вставить(ДеньТек, 0); // Начинаем с 0-й записи для каждого дня
КонецЦикла;
// Определяем начальную высоту табличного документа после заголовков
НачальнаяВысотаТабДока = ТабДок.ВысотаТаблицы;
// Проходим по каждому контрагенту
Для Каждого СтрокаКонтрагента Из ПодготовленныеДанные Цикл
КонтрагентТек = СтрокаКонтрагента.Контрагент;
// Найдем максимальное количество детальных записей для текущего контрагента по всем дням
МаксСтрокДляКонтрагента = 0;
Для Каждого ДеньТек Из МассивДней Цикл
МассивДетальныхЗаписей = СтрокаКонтрагента[ДеньТек];
Если МассивДетальныхЗаписей.Количество() > МаксСтрокДляКонтрагента Тогда
МаксСтрокДляКонтрагента = МассивДетальныхЗаписей.Количество();
КонецЕсли;
КонецЦикла;
// Если у контрагента вообще нет данных ни по одному дню, пропускаем его.
Если МаксСтрокДляКонтрагента = 0 Тогда
Продолжить;
КонецЕсли;
// Высота, до которой нужно "дотянуть" текущего контрагента
ТекущаяВысотаГруппировки = ТабДок.ВысотаТаблицы;
Если ТабДок.ВысотаТаблицы > НачальнаяВысотаТабДока Тогда
ТекущаяВысотаГруппировки = ТабДок.ВысотаТаблицы;
КонецЕсли;
// Выводим информацию по контрагенту (заголовок группировки)
// Он всегда выводится в первой строке группировки
ОбластьКонтрагент = Макет.ПолучитьОбласть("Контрагент"); // Область для вывода контрагента
Если ОбластьКонтрагент = Неопределено Тогда
ОбластьКонтрагент = ТабДок.Области.Получить(ТекущаяВысотаГруппировки + 1, 1); // Первая ячейка в первой колонке
ОбластьКонтрагент.Текст = КонтрагентТек + " " + СтрокаКонтрагента.ПонедельникКоличествоЗаказов;
ОбластьКонтрагент.ШиринаКолонки = 20;
Иначе
ОбластьКонтрагент.Параметры.Контрагент = КонтрагентТек;
ОбластьКонтрагент.Параметры.КоличествоЗаказовПонедельник = СтрокаКонтрагента.ПонедельникКоличествоЗаказов;
// Параметры для макета: Контрагент, КоличествоЗаказовДня
ТабДок.Вывести(ОбластьКонтрагент, ТекущаяВысотаГруппировки + 1, 1);
КонецЕсли;
// Запоминаем текущую высоту ТабДока, чтобы от нее выводить детальные записи
ВысотаНачалаДетальныхЗаписей = ТабДок.ВысотаТаблицы; // Или ТекущаяВысотаГруппировки + 1 если контрагент занимает 1 строку
// Цикл для вывода детальных записей
Для И = 0 По МаксСтрокДляКонтрагента - 1 Цикл
ТекущаяСтрокаВывода = ВысотаНачалаДетальныхЗаписей + И; // Номер строки в ТабДоке
// Область для первой колонки (может быть пустой или содержать продолжение контрагента)
// Если у нас несколько строк детальных записей, то область Контрагента может быть объединена
// или последующие строки в первой колонке будут пустыми.
Если И > 0 Тогда // Для всех строк, кроме первой после заголовка контрагента
ОбластьПустота = Макет.ПолучитьОбласть("ПустаяЯчейка"); // Область для пустой ячейки, если есть
Если ОбластьПустота = Неопределено Тогда
// Если макета нет, создаем программно
ОбластьПустота = ТабДок.Области.Получить(ТекущаяСтрокаВывода, 1);
ОбластьПустота.Текст = "";
КонецЕсли;
ТабДок.Вывести(ОбластьПустота, ТекущаяСтрокаВывода, 1);
КонецЕсли;
ИндексКолонкиДня = 2; // Начинаем со второй колонки для дней недели
Для Каждого ДеньТек Из МассивДней Цикл
МассивДетальныхЗаписей = СтрокаКонтрагента[ДеньТек];
Если И < МассивДетальныхЗаписей.Количество() Тогда
// Выводим детальную запись, если она есть
ДетальнаяЗапись = МассивДетальныхЗаписей[И];
ОбластьДетальная = Макет.ПолучитьОбласть("ДетальнаяЗапись"); // Область для детальной записи
Если ОбластьДетальная = Неопределено Тогда
// Если макета нет, создаем программно
ОбластьДетальная = ТабДок.Области.Получить(ТекущаяСтрокаВывода, ИндексКолонкиДня);
ОбластьДетальная.Текст = ДетальнаяЗапись.Номенклатура + " " + ДетальнаяЗапись.НомерЗаказа;
ОбластьДетальная.ШиринаКолонки = 30;
Иначе
ОбластьДетальная.Параметры.Номенклатура = ДетальнаяЗапись.Номенклатура;
ОбластьДетальная.Параметры.НомерЗаказа = ДетальнаяЗапись.НомерЗаказа;
ТабДок.Вывести(ОбластьДетальная, ТекущаяСтрокаВывода, ИндексКолонкиДня);
КонецЕсли;
Иначе
// Если детальных записей для этого дня больше нет, выводим пустую ячейку
ОбластьПустота = Макет.ПолучитьОбласть("ПустаяЯчейка"); // Область для пустой ячейки
Если ОбластьПустота = Неопределено Тогда
ОбластьПустота = ТабДок.Области.Получить(ТекущаяСтрокаВывода, ИндексКолонкиДня);
ОбластьПустота.Текст = "";
ОбластьПустота.ШиринаКолонки = 30;
КонецЕсли;
ТабДок.Вывести(ОбластьПустота, ТекущаяСтрокаВывода, ИндексКолонкиДня);
КонецЕсли;
ИндексКолонкиДня = ИндексКолонкиДня + 1;
КонецЦикла; // По дням недели
// Присоединяем текущую строку, чтобы подготовиться к следующей итерации
ТабДок.Присоединить();
КонецЦикла; // По детальным записям (И)
КонецЦикла; // По контрагентам
ТабДок.Показать();
В приведенном программном коде мы реализуем "плотный" вывод следующим образом:
МаксСтрокДляКонтрагента).Контрагент \ Количество заказов) в первой колонке в первой строке этой группировки.0 до МаксСтрокДляКонтрагента - 1. Каждая итерация этого цикла соответствует одной "логической" строке в табличном документе для текущего контрагента.И?И больше, чем количество детальных записей для данного дня), мы выводим пустую ячейку. Это гарантирует, что все колонки будут иметь одинаковую высоту для данного "блока" контрагента, и не будет визуальных пропусков.В нашем случае, требование к "плотному" выводу с динамическим смещением следующих группировок, делает программное формирование табличного документа наиболее подходящим и гибким решением. Мы можем четко контролировать, куда и что выводить, обеспечивая желаемое выравнивание.
Перед началом разработки всегда проводите тщательный анализ требований к отчету. Если есть возможность использовать СКД, начинайте с неё. Если же возникают сложности с макетом, которые СКД не может решить элегантно, переходите к программному формированию. Мы рассмотрели оба пути, чтобы вы могли выбрать наиболее подходящий для вашей конкретной ситуации.