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

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

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

Подготовка исходных данных

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

  1. Контрагент: для группировки по строкам.
  2. Номенклатура: для детальных записей.
  3. Количество: для детальных записей (если нужно).
  4. НомерЗаказа: для детальных записей.
  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: Использование Системы Компоновки Данных (СКД)

СКД — это мощный инструмент в 1С для построения отчетов, который позволяет декларативно описать структуру отчета, его данные, группировки и макеты. Для нашей задачи СКД подходит идеально, так как она специально разработана для создания кросс-табличных отчетов.

Шаг 1: Создание схемы компоновки данных

  1. Откройте или создайте внешний отчет (обработка) и добавьте к нему схему компоновки данных.
  2. Перейдите на вкладку «Наборы данных». Мы можем использовать либо «Набор данных – Объект», если наши данные уже собраны в одну ТаблицуЗначений (как в примере выше), либо «Набор данных – Запрос», если мы хотим получить данные запросом напрямую из базы данных или из временных таблиц.
  3. Если используем «Набор данных – Объект»:

    Создайте новый набор данных типа «Объект». В качестве имени объекта укажите имя нашей объединенной ТаблицыЗначений, например, ОбщаяТЗ. СКД автоматически считает поля из этой таблицы.

    Например, в модуле объекта отчета (обработки) в обработчике события ПриКомпоновкеРезультата() мы можем передать нашу ОбщаяТЗ в качестве параметра:

    
    Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
        СтандартнаяОбработка = Ложь;
    
        КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
        Настройки = КомпоновщикНастроек.ПолучитьНастройки(); // Получаем настройки из формы отчета
    
        // Здесь мы передаем нашу объединенную ТаблицуЗначений в СКД
        ОбщаяТЗ = ОбъединитьТЗ(); // Вызов функции, которая формирует нашу общую ТЗ
    
        НаборДанныхОбъект = Настройки.ДоступныеНастройки.Элементы.Найти("ОбщаяТЗ"); // Имя набора данных в СКД
        Если НаборДанныхОбъект <> Неопределено Тогда
            НаборДанныхОбъект.Объект = ОбщаяТЗ;
        КонецЕсли;
    
        МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
    
        ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
        ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки);
    
        ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
        ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
    
        ПроцессорВывода.НачатьВывод();
        Пока Истина Цикл
            ЭлементРезультата = ПроцессорКомпоновки.Следующий();
            Если ЭлементРезультата = Неопределено Тогда
                Прервать;
            КонецЕсли;
            ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
        КонецЦикла;
        ПроцессорВывода.ЗакончитьВывод();
    КонецПроцедуры
    
  4. Если используем «Набор данных – Запрос»:

    В тексте запроса вы можете получить данные из временных таблиц или напрямую из базы данных. Если у вас было 5 ТЗ, вы можете сначала загрузить их во временные таблицы с помощью МенеджерВременныхТаблиц, а затем объединить их запросом.

    
    // Пример запроса, если у нас уже есть поле ДеньНедели
    ВЫБРАТЬ
        ТЗ.Контрагент,
        ТЗ.Номенклатура,
        ТЗ.Количество,
        ТЗ.НомерЗаказа,
        ТЗ.ДеньНедели
    ИЗ
        ОбъединеннаяТЗ КАК ТЗ // Это может быть временная таблица или виртуальная таблица
    

    После создания набора данных перейдите на вкладку «Поля» и убедитесь, что все необходимые поля определены, а также укажите их роли (измерение, ресурс).

Шаг 2: Настройка структуры отчета (Таблица)

  1. Перейдите на вкладку «Настройки».
  2. Добавьте новую структуру отчета типа «Таблица» (она же кросс-таблица).
  3. Настройте группировки строк:
    • В области «Строки» структуры «Таблица» добавьте группировку по полю Контрагент.
    • Для этой группировки по контрагенту нам нужно вывести количество заказов. Для этого мы можем создать ресурс. Перейдите на вкладку «Ресурсы» в СКД, добавьте новый ресурс, например, КоличествоЗаказов. В качестве выражения для ресурса укажите КОЛИЧЕСТВО(Различные НомерЗаказа) или СУММА(Выбор Когда Не НомерЗаказа ЕСТЬ NULL Тогда 1 Иначе 0 Конец).
    • Вернитесь в настройки группировки Контрагент и добавьте в выбранные поля наш ресурс КоличествоЗаказов.
    • Теперь нам нужно вывести детальные записи Номенклатура и НомерЗаказа. Для этого добавьте еще одну группировку в строках ПОД группировкой Контрагент, но сделайте её «Детальные записи». В выбранные поля для этой детальной группировки укажите Номенклатура и НомерЗаказа.
  4. Настройте группировки колонок:
    • В области «Колонки» структуры «Таблица» добавьте группировку по полю ДеньНедели.
    • Убедитесь, что порядок дней недели корректен. Если ДеньНедели — это строковое поле, вы можете использовать вкладку «Параметры и ограничения» для упорядочивания дней недели (например, через список значений или выражение).

Шаг 3: Настройка вычисляемых полей и ресурсов для колонок

Для каждого пересечения строки (Контрагент) и колонки (ДеньНедели) нам нужно вывести наши детальные записи. СКД по умолчанию выводит ресурсы в ячейках пересечения. Однако нам нужно вывести не просто сумму, а список номенклатуры и номеров заказов, причём «плотно» друг под другом.

Для достижения этого, мы можем использовать следующий подход:

  1. Создание вычисляемых полей (или ресурсов) для вывода детальных данных в каждой ячейке:

    Нам потребуется создать вычисляемое поле, которое будет собирать все детальные записи для конкретного контрагента и дня недели. Это сложный момент, поскольку СКД по умолчанию не предназначена для вывода списка значений в одной ячейке кросс-таблицы без использования макетов. Мы можем имитировать это, но для полной гибкости нам понадобятся макеты.

    В рамках стандартных настроек СКД, без использования пользовательских макетов для ячеек, мы можем вывести один ресурс в каждой ячейке пересечения. Например, количество заказов за день для данного контрагента. Вывод детальных записей "Номенклатура \ Номер заказа" под каждым контрагентом в *разных* колонках с "плотным" расположением - это то место, где стандартная СКД начинает испытывать трудности без программной доработки или очень сложных макетов.

  2. Использование макетов для группировок (для сложного вывода):

    Если нам нужно вывести в одной ячейке пересечения Контрагент - ДеньНедели не просто ресурс (например, количество), а список детальных записей (Номенклатура, НомерЗаказа) в несколько строк, то мы должны использовать пользовательские макеты.

    Перейдите на вкладку «Макеты» в СКД.

    • Добавьте новый макет для «Ячейки» пересечения группировок Контрагент и ДеньНедели.
    • В этом макете вы можете разместить несколько параметров, например ПараметрНоменклатура1, ПараметрНомерЗаказа1, ПараметрНоменклатура2, ПараметрНомерЗаказа2 и т.д. Но это статично. Более гибкий подход — использование текстового поля с HTML-разметкой или функцией ВычислитьВыражениеСГруппировкойМассив().
    • Использование ВычислитьВыражениеСГруппировкойМассив(): это функция позволяет собрать значения из детальных записей в массив, который затем можно вывести в виде строки.
    • На вкладке «Ресурсы» создайте ресурс, например, ДетальныеЗаписиДня:

      
      ВычислитьВыражениеСГруппировкойМассив("Номенклатура + "" "" + НомерЗаказа", "Номенклатура, НомерЗаказа", "Контрагент, ДеньНедели")
      

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

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

      Для задачи "плотного" вывода детальных записей в колонках (как в примере "Диван А3", а следующий контрагент напротив следующей строки), СКД в чистом виде не подходит без программной доработки или очень сложных пользовательских макетов, которые будут динамически менять количество строк в ячейках. Обычно, кросс-таблицы СКД работают с фиксированной высотой строк и столбцов на уровне детальных записей.

Шаг 4: Особенности "плотного" вывода в СКД

Требование "в колонках таблицы строки пропускать нельзя" и "если во вторник будет еще один контрагент, он должен выводиться не напротив Сидорова, а напротив Диван А3" крайне сложно реализовать стандартными средствами СКД для кросс-таблиц. Кросс-таблица СКД генерирует таблицу, где каждая строка соответствует одной строковой группировке, и каждая колонка - одной колоночной группировке. Детальные записи внутри ячейки кросс-таблицы обычно "растягивают" высоту этой ячейки, но не влияют на "выравнивание" следующих группировок. Это означает, что если у "Иванова" в Понедельник 3 записи, а у "Петрова" во Вторник 2 записи, то следующая строка (например, "Сидоров") будет начинаться на уровне строки "Петрова", а не на уровне третьей записи "Иванова".

Для достижения "плотного" вывода в СКД придется прибегнуть к следующим приемам, которые, однако, сильно усложняют схему:

  1. Объединение всех детальных записей в одну строку текста для каждой ячейки:

    Используйте вычисляемое поле с функцией ВычислитьВыражениеСГруппировкойМассив() для сбора всех номенклатур и номеров заказов в массив для каждого пересечения "Контрагент"-"ДеньНедели". Затем объедините элементы этого массива в одну строку с разделителем перевода строки (Символы.ПС). Таким образом, каждая ячейка будет содержать многострочный текст.

    
    // Ресурс или вычисляемое поле для одной ячейки кросс-таблицы
    СтрокаЗаказовДляЯчейки =
    ВЫБОР
        КОГДА (ВычислитьВыражениеСГруппировкойМассив("Номенклатура + "" "" + НомерЗаказа", "Номенклатура, НомерЗаказа", "Контрагент, ДеньНедели") ЕСТЬ NULL)
        ТОГДА ""
        ИНАЧЕ СтрСоединить(ВычислитьВыражениеСГруппировкойМассив("Номенклатура + "" "" + НомерЗаказа", "Номенклатура, НомерЗаказа", "Контрагент, ДеньНедели"), Символы.ПС)
    КОНЕЦ
    

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

  2. Программная постобработка результата СКД:

    Можно сгенерировать табличный документ с помощью СКД, а затем программно пройти по нему и "сжать" строки, перемещая данные. Однако это крайне сложный и неэффективный путь, сводящий на нет преимущества СКД.

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

Подход 2: Программное формирование табличного документа

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

Шаг 1: Подготовка данных

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

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


Функция ПодготовитьДанныеДляВывода(ОбщаяТЗ)
    ДанныеПоКонтрагентам = Новый ТаблицаЗначений;
    ДанныеПоКонтрагентам.Колонки.Добавить("Контрагент");

    МассивДней = Новый Массив();
    МассивДней.Добавить("Понедельник");
    МассивДней.Добавить("Вторник");
    МассивДней.Добавить("Среда");
    МассивДней.Добавить("Четверг");
    МассивДней.Добавить("Пятница");

    Для Каждого ДеньТек Из МассивДней Цикл
        ДанныеПоКонтрагентам.Колонки.Добавить(ДеньТек, Новый ОписаниеТипов("Массив")); // Массив для детальных записей дня
        ДанныеПоКонтрагентам.Колонки.Добавить(ДеньТек + "КоличествоЗаказов", Новый ОписаниеТипов("Число")); // Количество заказов за день
    КонецЦикла;

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

    // Лучше пройтись по контрагентам и собрать данные вручную
    УникальныеКонтрагенты = ОбщаяТЗ.ВыгрузитьКолонку("Контрагент", Истина); // Истина - уникальные значения

    Для Каждого КонтрагентТек Из УникальныеКонтрагенты Цикл
        НоваяСтрокаКонтрагента = ДанныеПоКонтрагентам.Добавить();
        НоваяСтрокаКонтрагента.Контрагент = КонтрагентТек;

        Для Каждого ДеньТек Из МассивДней Цикл
            // Отфильтруем данные для текущего контрагента и дня
            МассивДетальныхЗаписей = Новый Массив();
            КоличествоЗаказовДня = 0;
            
            Для Каждого СтрокаТЗ Из ОбщаяТЗ Цикл
                Если СтрокаТЗ.Контрагент = КонтрагентТек И СтрокаТЗ.ДеньНедели = ДеньТек Тогда
                    ДетальнаяЗапись = Новый Структура;
                    ДетальнаяЗапись.Вставить("Номенклатура", СтрокаТЗ.Номенклатура);
                    ДетальнаяЗапись.Вставить("НомерЗаказа", СтрокаТЗ.НомерЗаказа);
                    МассивДетальныхЗаписей.Добавить(ДетальнаяЗапись);
                    КоличествоЗаказовДня = КоличествоЗаказовДня + 1; // Просто считаем строки, можно использовать уникальный НомерЗаказа
                КонецЕсли;
            КонецЦикла;

            НоваяСтрокаКонтрагента[ДеньТек] = МассивДетальныхЗаписей;
            НоваяСтрокаКонтрагента[ДеньТек + "КоличествоЗаказов"] = КоличествоЗаказовДня;
        КонецЦикла;
    КонецЦикла;

    Возврат ДанныеПоКонтрагентам;
КонецФункции

Шаг 2: Создание и заполнение ТабличногоДокумента

  1. Инициализация:

    Создадим новый объект ТабличныйДокумент.

    
    ТабДок = Новый ТабличныйДокумент;
    ТабДок.Очистить();
    
  2. Определение макета:

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

    
    Макет = ПолучитьМакет("МакетОтчета"); // Например, макет называется "МакетОтчета"
    

    В макете могут быть области:

    • ОбластьЗаголовокДней: для вывода дней недели.
    • ОбластьКонтрагент: для вывода Контрагент и его количества заказов.
    • ОбластьДетальнаяЗапись: для вывода Номенклатура и НомерЗаказа.
    • ОбластьПустаяСтрока: если нужно заполнить пустотой, чтобы выровнять колонки.
  3. Вывод заголовков дней недели:

    Первая строка нашего отчета — это дни недели. Мы выведем их из списка.

    
    // Заголовки дней недели
    СтрокаЗаголовков = Макет.ПолучитьОбласть("ЗаголовокДней"); // Если есть такая область в макете
    Если СтрокаЗаголовков = Неопределено Тогда
        // Если макета нет, создаем программно
        ТабДок.ВывестиСтроку(); // Переходим на новую строку
        ТабДок.Области.Получить(ТабДок.ВысотаТаблицы, 1, ТабДок.ВысотаТаблицы, 1).Текст = ""; // Пустая ячейка для Контрагента
        ТабДок.Области.Получить(ТабДок.ВысотаТаблицы, 1, ТабДок.ВысотаТаблицы, 1).ШиринаКолонки = 20;
    
        МассивДней = Новый Массив();
        МассивДней.Добавить("Понедельник");
        МассивДней.Добавить("Вторник");
        МассивДней.Добавить("Среда");
        МассивДней.Добавить("Четверг");
        МассивДней.Добавить("Пятница");
                
        ИндексКолонки = 2; // Начинаем со второй колонки
        Для Каждого ДеньТек Из МассивДней Цикл
            ОбластьДня = ТабДок.Области.Получить(ТабДок.ВысотаТаблицы, ИндексКолонки, ТабДок.ВысотаТаблицы, ИндексКолонки);
            ОбластьДня.Текст = ДеньТек;
            ОбластьДня.Параметры.День = ДеньТек; // Если используем макет с параметрами
            ОбластьДня.ШиринаКолонки = 30; // Устанавливаем ширину колонки
            ОбластьДня.ГоризонтальноеПоложение = ГоризонтальноеПоложение.Центр;
            ОбластьДня.ВертикальноеПоложение = ВертикальноеПоложение.Центр;
            ОбластьДня.Объединить(ТабДок.ВысотаТаблицы, ИндексКолонки, ТабДок.ВысотаТаблицы, ИндексКолонки); // Объединение на одну ячейку
            ИндексКолонки = ИндексКолонки + 1;
        КонецЦикла;
        ТабДок.Присоединить(); // Завершаем вывод строки
    Иначе
        ИндексКолонки = 2; // Начинаем со второй колонки
        Для Каждого ДеньТек Из МассивДней Цикл
            СтрокаЗаголовков.Параметры[ДеньТек] = ДеньТек; // Пример для макета с параметрами "Понедельник", "Вторник" и т.д.
            // Или если макет универсальный, просто задаем текст ячейки
            // СтрокаЗаголовков.Области.Получить(1, ИндексКолонки).Текст = ДеньТек;
            ИндексКолонки = ИндексКолонки + 1;
        КонецЦикла;
        ТабДок.Вывести(СтрокаЗаголовков);
    КонецЕсли;
    
  4. Вывод данных по контрагентам (основной цикл):

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

    
    // Подготовленные данные
    ПодготовленныеДанные = ПодготовитьДанныеДляВывода(ОбщаяТЗ);
            
    // Массив, который будет хранить текущие индексы строк для каждого дня недели
    // чтобы отслеживать "плотный" вывод
    ИндексыСтрокДляДней = Новый Соответствие;
    Для Каждого ДеньТек Из МассивДней Цикл
        ИндексыСтрокДляДней.Вставить(ДеньТек, 0); // Начинаем с 0-й записи для каждого дня
    КонецЦикла;
    
    // Определяем начальную высоту табличного документа после заголовков
    НачальнаяВысотаТабДока = ТабДок.ВысотаТаблицы;
    
    // Проходим по каждому контрагенту
    Для Каждого СтрокаКонтрагента Из ПодготовленныеДанные Цикл
        КонтрагентТек = СтрокаКонтрагента.Контрагент;
    
        // Найдем максимальное количество детальных записей для текущего контрагента по всем дням
        МаксСтрокДляКонтрагента = 0;
        Для Каждого ДеньТек Из МассивДней Цикл
            МассивДетальныхЗаписей = СтрокаКонтрагента[ДеньТек];
            Если МассивДетальныхЗаписей.Количество() > МаксСтрокДляКонтрагента Тогда
                МаксСтрокДляКонтрагента = МассивДетальныхЗаписей.Количество();
            КонецЕсли;
        КонецЦикла;
    
        // Если у контрагента вообще нет данных ни по одному дню, пропускаем его.
        Если МаксСтрокДляКонтрагента = 0 Тогда
            Продолжить;
        КонецЕсли;
    
        // Высота, до которой нужно "дотянуть" текущего контрагента
        ТекущаяВысотаГруппировки = ТабДок.ВысотаТаблицы;
        Если ТабДок.ВысотаТаблицы > НачальнаяВысотаТабДока Тогда
            ТекущаяВысотаГруппировки = ТабДок.ВысотаТаблицы;
        КонецЕсли;
    
        // Выводим информацию по контрагенту (заголовок группировки)
        // Он всегда выводится в первой строке группировки
        ОбластьКонтрагент = Макет.ПолучитьОбласть("Контрагент"); // Область для вывода контрагента
        Если ОбластьКонтрагент = Неопределено Тогда
            ОбластьКонтрагент = ТабДок.Области.Получить(ТекущаяВысотаГруппировки + 1, 1); // Первая ячейка в первой колонке
            ОбластьКонтрагент.Текст = КонтрагентТек + " " + СтрокаКонтрагента.ПонедельникКоличествоЗаказов;
            ОбластьКонтрагент.ШиринаКолонки = 20;
        Иначе
            ОбластьКонтрагент.Параметры.Контрагент = КонтрагентТек;
            ОбластьКонтрагент.Параметры.КоличествоЗаказовПонедельник = СтрокаКонтрагента.ПонедельникКоличествоЗаказов;
            // Параметры для макета: Контрагент, КоличествоЗаказовДня
            ТабДок.Вывести(ОбластьКонтрагент, ТекущаяВысотаГруппировки + 1, 1);
        КонецЕсли;
                
        // Запоминаем текущую высоту ТабДока, чтобы от нее выводить детальные записи
        ВысотаНачалаДетальныхЗаписей = ТабДок.ВысотаТаблицы; // Или ТекущаяВысотаГруппировки + 1 если контрагент занимает 1 строку
    
        // Цикл для вывода детальных записей
        Для И = 0 По МаксСтрокДляКонтрагента - 1 Цикл
            ТекущаяСтрокаВывода = ВысотаНачалаДетальныхЗаписей + И; // Номер строки в ТабДоке
    
            // Область для первой колонки (может быть пустой или содержать продолжение контрагента)
            // Если у нас несколько строк детальных записей, то область Контрагента может быть объединена
            // или последующие строки в первой колонке будут пустыми.
            Если И > 0 Тогда // Для всех строк, кроме первой после заголовка контрагента
                ОбластьПустота = Макет.ПолучитьОбласть("ПустаяЯчейка"); // Область для пустой ячейки, если есть
                Если ОбластьПустота = Неопределено Тогда
                    // Если макета нет, создаем программно
                    ОбластьПустота = ТабДок.Области.Получить(ТекущаяСтрокаВывода, 1);
                    ОбластьПустота.Текст = "";
                КонецЕсли;
                ТабДок.Вывести(ОбластьПустота, ТекущаяСтрокаВывода, 1);
            КонецЕсли;
    
            ИндексКолонкиДня = 2; // Начинаем со второй колонки для дней недели
            Для Каждого ДеньТек Из МассивДней Цикл
                МассивДетальныхЗаписей = СтрокаКонтрагента[ДеньТек];
    
                Если И < МассивДетальныхЗаписей.Количество() Тогда
                    // Выводим детальную запись, если она есть
                    ДетальнаяЗапись = МассивДетальныхЗаписей[И];
                    ОбластьДетальная = Макет.ПолучитьОбласть("ДетальнаяЗапись"); // Область для детальной записи
                    Если ОбластьДетальная = Неопределено Тогда
                        // Если макета нет, создаем программно
                        ОбластьДетальная = ТабДок.Области.Получить(ТекущаяСтрокаВывода, ИндексКолонкиДня);
                        ОбластьДетальная.Текст = ДетальнаяЗапись.Номенклатура + " " + ДетальнаяЗапись.НомерЗаказа;
                        ОбластьДетальная.ШиринаКолонки = 30;
                    Иначе
                        ОбластьДетальная.Параметры.Номенклатура = ДетальнаяЗапись.Номенклатура;
                        ОбластьДетальная.Параметры.НомерЗаказа = ДетальнаяЗапись.НомерЗаказа;
                        ТабДок.Вывести(ОбластьДетальная, ТекущаяСтрокаВывода, ИндексКолонкиДня);
                    КонецЕсли;
                Иначе
                    // Если детальных записей для этого дня больше нет, выводим пустую ячейку
                    ОбластьПустота = Макет.ПолучитьОбласть("ПустаяЯчейка"); // Область для пустой ячейки
                    Если ОбластьПустота = Неопределено Тогда
                        ОбластьПустота = ТабДок.Области.Получить(ТекущаяСтрокаВывода, ИндексКолонкиДня);
                        ОбластьПустота.Текст = "";
                        ОбластьПустота.ШиринаКолонки = 30;
                    КонецЕсли;
                    ТабДок.Вывести(ОбластьПустота, ТекущаяСтрокаВывода, ИндексКолонкиДня);
                КонецЕсли;
                ИндексКолонкиДня = ИндексКолонкиДня + 1;
            КонецЦикла; // По дням недели
            // Присоединяем текущую строку, чтобы подготовиться к следующей итерации
            ТабДок.Присоединить(); 
        КонецЦикла; // По детальным записям (И)
    КонецЦикла; // По контрагентам
    
    ТабДок.Показать();
    

Шаг 3: Объяснение "плотного" вывода

В приведенном программном коде мы реализуем "плотный" вывод следующим образом:

  1. Для каждого контрагента мы сначала определяем максимальное количество строк детальных записей, которое потребуется для вывода данных этого контрагента в любой из колонок дней недели (МаксСтрокДляКонтрагента).
  2. Затем мы выводим заголовок контрагента (Контрагент \ Количество заказов) в первой колонке в первой строке этой группировки.
  3. Далее мы запускаем цикл от 0 до МаксСтрокДляКонтрагента - 1. Каждая итерация этого цикла соответствует одной "логической" строке в табличном документе для текущего контрагента.
  4. Внутри этого цикла, для каждой "логической" строки и для каждого дня недели, мы проверяем:
    • Есть ли детальная запись для текущего дня недели и текущего индекса И?
    • Если есть, мы выводим эту детальную запись в соответствующую ячейку.
    • Если нет (т.е., И больше, чем количество детальных записей для данного дня), мы выводим пустую ячейку. Это гарантирует, что все колонки будут иметь одинаковую высоту для данного "блока" контрагента, и не будет визуальных пропусков.
  5. После вывода всех "логических" строк для текущего контрагента, курсор табличного документа автоматически переходит на следующую доступную строку, и следующий контрагент будет выведен сразу после последнего элемента предыдущего, без лишних пустых строк, которые могли бы образоваться из-за разной высоты колонок.

Общие рекомендации и сравнение подходов

В нашем случае, требование к "плотному" выводу с динамическим смещением следующих группировок, делает программное формирование табличного документа наиболее подходящим и гибким решением. Мы можем четко контролировать, куда и что выводить, обеспечивая желаемое выравнивание.

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

← На главную