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

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

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

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

Анализ задачи и архитектурные особенности

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

  1. Разное количество операций: по одной скважине количество демонтажей может не совпадать с количеством монтажей (например, новый насос установлен, но предыдущий демонтаж не фиксировался, либо наоборот).
  2. Параллельное отображение: данные монтажа и демонтажа должны выводиться в одной строке отчета бок о бок.
  3. Форматирование шапки и строк: ячейки с номером по порядку и номером скважины должны объединяться по высоте всей группы движений и иметь вертикальное центрирование.

Способ 1. Формирование таблицы вывода с помощью Таблиц Значений и объединение ячеек

Разберем по шагам классический алгоритм: сначала выбираем данные из регистра, разделяем их на два потока (монтаж и демонтаж), сопоставляем по скважинам и выводим в табличный документ с динамическим объединением областей.

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


// 1. Инициализация таблиц для разделения потоков
ТЗД = Новый ТаблицаЗначений;
ТЗД.Колонки.Добавить("НомерСкважины");
ТЗД.Колонки.Добавить("ДНомерНасоса");
ТЗД.Колонки.Добавить("ДДатаМД");
ТЗД.Колонки.Добавить("ДЧасы");

ТЗМ = Новый ТаблицаЗначений;
ТЗМ.Колонки.Добавить("НомерСкважины");
ТЗМ.Колонки.Добавить("МНомерНасоса");
ТЗМ.Колонки.Добавить("МДатаМД");
ТЗМ.Колонки.Добавить("МЧасы");

ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("НомерСкважины");
ТЗ.Колонки.Добавить("ДНомерНасоса");
ТЗ.Колонки.Добавить("ДДатаМД");
ТЗ.Колонки.Добавить("ДЧасы");
ТЗ.Колонки.Добавить("МНомерНасоса");
ТЗ.Колонки.Добавить("МДатаМД");
ТЗ.Колонки.Добавить("МЧасы");

// 2. Выборка данных из регистра
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   Насосы.НомерСкважины КАК НомерСкважины,
    |   Насосы.Монтаж КАК Монтаж,
    |   Насосы.НомерНасоса КАК НомерНасоса,
    |   Насосы.ДатаМонтажаДемонтажа КАК ДатаМД,
    |   Насосы.Часы КАК Часы
    |ИЗ
    |   РегистрСведений.Насосы КАК Насосы
    |УПОРЯДОЧИТЬ ПО
    |   НомерСкважины,
    |   ДатаМД";

ЗапросТЗ = Запрос.Выполнить().Выгрузить();

// 3. Разделение на монтажи и демонтажи
Для Каждого Стр Из ЗапросТЗ Цикл
    Если НЕ Стр.Монтаж Тогда
        НЗ = ТЗД.Добавить();
        ЗаполнитьЗначенияСвойств(НЗ, Стр);
        НЗ.ДНомерНасоса = Стр.НомерНасоса;
        НЗ.ДДатаМД      = Стр.ДатаМД;
        НЗ.ДЧасы        = Стр.Часы;
    Иначе
        НЗ = ТЗМ.Добавить();
        ЗаполнитьЗначенияСвойств(НЗ, Стр);
        НЗ.МНомерНасоса = Стр.НомерНасоса;
        НЗ.МДатаМД      = Стр.ДатаМД;
        НЗ.МЧасы        = Стр.Часы;
    КонецЕсли;
КонецЦикла;

ТабСкважины = ЗапросТЗ.Скопировать();
ТабСкважины.Свернуть("НомерСкважины");

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

Вывод в табличный документ и динамическое объединение строк

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


ТабДокумент = Новый ТабличныйДокумент;
Макет        = ПолучитьМакет("МакетОтчета");
ОблШапка     = Макет.ПолучитьОбласть("Шапка");
ОблДанные    = Макет.ПолучитьОбласть("Данные");

ТабДокумент.Вывести(ОблШапка);

Ном = 0;
СтарыйНомерСкважины = "";
НачальнаяСтрокаГруппы = 0;

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

// Объединяем последнюю группу после завершения цикла
Если НачальнаяСтрокаГруппы > 0 Тогда
    КонечнаяСтрокаГруппы = ТабДокумент.ВысотаТаблицы;
    Если КонечнаяСтрокаГруппы > НачальнаяСтрокаГруппы Тогда
        ОбластьНом = ТабДокумент.Область(НачальнаяСтрокаГруппы, 1, КонечнаяСтрокаГруппы, 1);
        ОбластьНом.Объединить();
        ОбластьНом.ВертикальноеПоложение = ВертикальноеПоложение.Центр;
        
        ОбластьСкважина = ТабДокумент.Область(НачальнаяСтрокаГруппы, 2, КонечнаяСтрокаГруппы, 2);
        ОбластьСкважина.Объединить();
        ОбластьСкважина.ВертикальноеПоложение = ВертикальноеПоложение.Центр;
    КонецЕсли;
КонецЕсли;

ТабДокумент.ОтображатьСетку = Истина;
ТабДокумент.ТолькоПросмотр = Истина;
ТабДокумент.Показать();

Способ 2. Оптимизированный запрос с параллельным соединением строк

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

Посмотрим на пример запроса:


ВЫБРАТЬ
    РС.НомерСкважины КАК НомерСкважины,
    РС.НомерНасоса КАК НомерНасоса,
    РС.ДатаМонтажаДемонтажа КАК ДатаМД,
    РС.Часы КАК Часы,
    КОЛИЧЕСТВО(РС_Пред.НомерНасоса) КАК НомерСтрокиВнутри
ПОМЕСТИТЬ ВТ_Демонтаж
ИЗ
    РегистрСведений.Насосы КАК РС
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Насосы КАК РС_Пред
        ПО РС.НомерСкважины = РС_Пред.НомерСкважины
        И РС.Монтаж = ЛОЖЬ
        И РС_Пред.Монтаж = ЛОЖЬ
        И РС.ДатаМонтажаДемонтажа >= РС_Пред.ДатаМонтажаДемонтажа
ГДЕ
    РС.Монтаж = ЛОЖЬ
СГРУППИРОВАТЬ ПО
    РС.НомерСкважины,
    РС.НомерНасоса,
    РС.ДатаМонтажаДемонтажа,
    РС.Часы
;

ВЫБРАТЬ
    РС.НомерСкважины КАК НомерСкважины,
    РС.НомерНасоса КАК НомерНасоса,
    РС.ДатаМонтажаДемонтажа КАК ДатаМД,
    РС.Часы КАК Часы,
    КОЛИЧЕСТВО(РС_Пред.НомерНасоса) КАК НомерСтрокиВнутри
ПОМЕСТИТЬ ВТ_Монтаж
ИЗ
    РегистрСведений.Насосы КАК РС
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Насосы КАК РС_Пред
        ПО РС.НомерСкважины = РС_Пред.НомерСкважины
        И РС.Монтаж = ИСТИНА
        И РС_Пред.Монтаж = ИСТИНА
        И РС.ДатаМонтажаДемонтажа >= РС_Пред.ДатаМонтажаДемонтажа
ГДЕ
    РС.Монтаж = ИСТИНА
СГРУППИРОВАТЬ ПО
    РС.НомерСкважины,
    РС.НомерНасоса,
    РС.ДатаМонтажаДемонтажа,
    РС.Часы
;

ВЫБРАТЬ
    ЕСТЬNULL(ВТ_Демонтаж.НомерСкважины, ВТ_Монтаж.НомерСкважины) КАК НомерСкважины,
    ВТ_Демонтаж.НомерНасоса КАК ДНомерНасоса,
    ВТ_Демонтаж.ДатаМД КАК ДДатаМД,
    ВТ_Демонтаж.Часы КАК ДЧасы,
    ВТ_Монтаж.НомерНасоса КАК МНомерНасоса,
    ВТ_Монтаж.ДатаМД КАК МДатаМД,
    ВТ_Монтаж.Часы КАК МЧасы
ИЗ
    ВТ_Демонтаж КАК ВТ_Демонтаж
    ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Монтаж КАК ВТ_Монтаж
        ПО ВТ_Демонтаж.НомерСкважины = ВТ_Монтаж.НомерСкважины
        И ВТ_Демонтаж.НомерСтрокиВнутри = ВТ_Монтаж.НомерСтрокиВнутри
УПОРЯДОЧИТЬ ПО
    НомерСкважины

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

Оформление границ и центрирование

Чтобы итоговый табличный документ выглядел аккуратно и легко читался, обратим внимание на программную настройку границ и выравнивания:

  1. Центрирование по вертикали: свойство ячейки или диапазона ВертикальноеПоложение = ВертикальноеПоложение.Центр гарантирует, что при объединении нескольких строк текст скважины расположится ровно посередине блока.
  2. Оформление контура группы: для обводки группы скважины толстой рамкой используется метод Область.Обвести() либо явная установка свойства ГраницаСнизу у последней строки группы:
    
    Граница = Новый Линия(ТипЛинииЯчейкиТабличногоДокумента.Сплошная, 2);
    ОбластьГруппы = ТабДокумент.Область(НачальнаяСтрокаГруппы, 1, КонечнаяСтрокаГруппы, ТабДокумент.ШиринаТаблицы);
    ОбластьГруппы.Обвести(Граница, Граница, Граница, Граница);
    

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

← На главную