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

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

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

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

Ошибочный подход: попытка чтения уже сформированного табличного документа

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

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

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

Разберем этот процесс по шагам.

  1. Сбор и сортировка исходных данных

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

    • ТипЭтапаЗакупки
    • ФактическаяДатаИлиДатаИсполнения
    • ЗначокСпособаЗакупки
    • ЗакупочнаяПроцедура

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

    
    // Предположим, у нас есть ТаблицаЗначений "СобытияЗакупок"
    СобытияЗакупок.Сортировать("ТипЭтапаЗакупки, ФактическаяДатаИлиДатаИсполнения");
    
  2. Группировка данных для удобного доступа

    После сортировки мы можем сгруппировать данные. Мы можем использовать коллекцию, такую как Соответствие (Map) или ДеревоЗначений (Tree of Values), чтобы удобно хранить сгруппированные данные. Идея состоит в том, чтобы для каждой уникальной комбинации "ЭтапЗакупки" + "ДатаСобытия" хранить список (или массив) всех относящихся к ней событий.

    Рассмотрим пример с использованием Соответствия:

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

    Теперь в переменной СгруппированныеСобытия у нас есть удобная структура: сначала группировка по этапу, затем по дате, и внутри — массив всех событий для этой даты и этапа.

  3. Формирование табличного документа на основе сгруппированных данных

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

    • Область для первой строки группы (заголовочная): Она будет содержать параметры ЭтапЗакупки и, возможно, дату, а также первое событие для данной даты и этапа.
    • Область для дополнительных строк событий: Эти области будут использоваться для вывода всех последующих событий, относящихся к той же группе "Этап-Дата", но уже без дублирования ЭтапЗакупки и даты.

    Рассмотрим, как мы можем итерировать по сгруппированным данным и выводить их:

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

    Важным аспектом является правильное оформление макета. Создадим две области:

    • ОбластьЭтапИПервоеСобытие: В этой области будут присутствовать параметры для ЭтапЗакупки, а также ячейки для событий дня (Событие1, Событие2 и т.д.).
    • ОбластьТолькоСобытие: В этой области будут присутствовать только ячейки для событий дня (Событие1, Событие2 и т.д.), а ячейка для ЭтапЗакупки будет либо пустой, либо отсутствовать, чтобы при выводе этой области она не перезаписывала или не дублировала данные об этапе.

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

    Пример (упрощенный) использования Объединить():

    
    // ... после цикла по событиям
    Если КоличествоСобытийНаДату > 1 Тогда
        // Получаем область, которую нужно объединить
        // Предполагаем, что переменные 'НачальнаяСтрокаОбъединения' и 'КонечнаяСтрокаОбъединения'
        // были сохранены в процессе вывода
        ОбластьДляОбъединения = ТабДок.Область(
            НачальнаяСтрокаОбъединения, 1, 
            КонечнаяСтрокаОбъединения, 1 // Колонка для ЭтапаЗакупки
        );
        ТабДок.Объединить(ОбластьДляОбъединения);
    КонецЕсли;
    

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

  5. Расшифровка полей

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

  6. Дополнительные рекомендации

    Избегайте жесткой привязки к дням недели в макете. Вместо Событие1, Событие2 и т.д. лучше использовать универсальные параметры, например, Событие[Дата], если это позволяет структура макета. В текущем же случае с фиксированными колонками на каждый день недели, подход с Событие1...Событие5 приемлем, но требует более сложной логики сопоставления фактической даты и дня недели, как в вашем коде.

    Оптимизация запросов. Если данных очень много, подумайте о том, чтобы выполнять группировку уже на уровне запроса с помощью выражений ВЫБРАТЬ РАЗРЕШЕННЫЕ и СГРУППИРОВАТЬ ПО, а затем уже получать детализированные данные для каждой группы. Это может значительно уменьшить объем данных, передаваемых на клиент, и ускорить формирование отчета.

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

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

← На главную