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