Как раз очень кстати у меня есть заказ по написанию такого отчета, который нужен к завтраму. В общих словах, имеется модифицированная программа «1С:Бухгалтерия 7.7», в которой ведется учет расхода материалов в мебельном производстве. Директор пожелал видеть этот расход в разрезе групп материалов, заказчиков и дней выпуска. Самое смешное, что выпущенная продукция его почему-то не интересует. Они ее вообще обобщают: мебель, резка, еще что-то. Впрочем, какая разница, что у меня за задача! Сейчас мы обсуждаем универсальную технологию быстрого написания отчета с произвольным количеством группировок. Поэтому, поехали!
1. Суть метода
Определяем одну или несколько базовых группировок и делаем самый подробный запрос по ним. Чаще всего, базовой группировкой, из которой можно все достать, будет документ и (или) строка документа. Данные запроса выгружаем в таблицу. Для каждого уровня группировок заводим новую таблицу, выгружаем в нее данные из основной, сворачиваем так, как нам надо, сортируем. Строим отчет простым обходом базовой таблицы, отслеживая изменения значения полей группировок, и, когда нужно, выводим итоговые секции.
Возможно, метод не самый оптимальный, но достаточно простой, универсальный и быстрый.
2. Создаем форму отчета.
Это просто. Чтобы особо не возиться, можно взять «кусочки» из отчетов «1С:Торговля и склад 7.7». На форму отчета устанавливаем выбор периода, поля для фиксированных значений отдельных группировок и список самих группировок. Функции кнопок сдвига списка также забираем из типового отчета, если писать их лень.
В моем случае список будет выглядеть так:
Группировки.ДобавитьЗначение("Группа","Группа материалов"); Группировки.ДобавитьЗначение("Материал","Материал"); Группировки.ДобавитьЗначение("Контрагент","Заказчик"); Группировки.ДобавитьЗначение("ДатаДок","Дата"); Группировки.ДобавитьЗначение("ТекущийДокумент","Документ");
3. Строим основной запрос в процедуре «Сформировать»
Для этого лучше воспользоваться конструктором. Особо мудрить не стоит. Конструктор поможет избежать рутины, но определять все с его помощью было бы неправильно, потому что реквизиты запроса и, условия зависят, в общем-то, оттого, что было выбрано и пользователем отмечено в форм, и от вида группировок запроса. Мы их доопредилим позже.
А пока у меня получилось следующее:
"//{{ЗАПРОС(Сформировать) |Период с ДатаНачала по ДатаКонца; |Обрабатывать НеПомеченныеНаУдаление; |ТекущийДокумент = Документ.ТребованиеНакладная.ТекущийДокумент; |ДатаДок = Документ.ТребованиеНакладная.ДатаДок; |Материал = Документ.ТребованиеНакладная.Материал; |Группа = Документ.ТребованиеНакладная.Материал.Родитель; |Цена = Документ.ТребованиеНакладная.Цена; |Количество = Документ.ТребованиеНакладная.КоличествоОтпущено; |ВидПеремещения = Документ.ТребованиеНакладная.ВидПеремещения; |Контрагент = Документ.ТребованиеНакладная.Накладная.Контрагент; |Группировка ТекущийДокумент; |Группировка СтрокаДокумента; |Условие(ВидПеремещения = 0); |"//}}ЗАПРОС
Мне вообще крупно повезло: даже не приходится возиться с бухгалтерскими итогами (кстати, по настоянию бухши!) У них в программе цены в строках документов соответствуют средним суммам по проводкам, и в накладной по списанию материалов хранится ссылка на реализацию. (Сам делал так, самому смешно!)
Текст запроса присвоим одноименной переменной. Также нам понадобится пустая строка СтрШапки.
4. Добавляем в запрос условия отбора.
Нам понадобится процедурка типа такой:
Процедура ДобавитьУсловие(ТекстЗапроса,Рекв,Имя,Слово,СтрШапки=0,Знак="=") ТекстЗапроса=ТекстЗапроса+"Условие("+Имя+Знак+"Выб"+Имя+");"; Если СтрШапки<>0 Тогда СтрШапки=СтрШапки+?(СтрШапки="","",РазделительСтрок)+ "Отбор по "+Слово+": """+СокрЛП(Рекв.Наименование)+""""; КонецЕсли; КонецПроцедуры // ДобавитьУсловие()
Она позволяем добавить в текст запроса проверку условия и одновременно формирует строку выбранных условий, которую потом мы поместим в шапку нашего отчета.
Помести под определением первичного текста нашего запроса проверку выбранных условий по типу:
Если ВыбИМЯ.Выбран()=1 Тогда
ДобавитьУсловие(ТекстЗапроса,Выб … ,"ИМЯ","РАСШИФРОВКА",СтрШапки);
КонецЕсли;
В моем случае
СтрШапки=""; Если ВыбМатериал.Выбран()=1 Тогда Если ВыбМатериал.ЭтоГруппа()=1 Тогда ВыбГруппа=ВыбМатериал; ДобавитьУсловие(ТекстЗапроса,ВыбГруппа,"Группа","группе материалов",СтрШапки); Иначе ДобавитьУсловие(ТекстЗапроса,ВыбМатериал,"Материал","материалу",СтрШапки); КонецЕсли; КонецЕсли; Если ВыбКонтрагент.Выбран()=1 Тогда ДобавитьУсловие(ТекстЗапроса,ВыбКонтрагент,"Контрагент","контрагенту",СтрШапки); КонецЕсли;
5. Формируем базовую таблицу
Вставляем вполне стандартный кусок, не забыв прежде определить переменную Т:
З=СоздатьОбъект("Запрос");
Если З.Выполнить(ТекстЗапроса)=0 Тогда
Сообщить("Какая-то ошибка в запросе!");
Возврат;
КонецЕсли;
З.Выгрузить(Т,1,0);
Вот теперь стоит вспомнить, каких группировок не было в нашем запросе по каким-либо причинам и добавить их сейчас в таблицу. Делать это просто:
Если Помечен("ИМЯ_ГРУППИРОВКИ")=1 Тогда
Т.НоваяКолонка("ИМЯ_ГРУППИРОВКИ");
КонецЕсли;
Разумеется, эти новые колонки надо заполнить в цикле.
Функция «Помечен» должна быть определена выше. Она проверяет, была ли помечена в запросе та или иная группировка:
Функция Помечен(Имя) Для К=1 по Группировки.РазмерСписка() Цикл ИмяГр=Группировки.ПолучитьЗначение(К); Если ИмяГр=Имя Тогда Возврат Группировки.Пометка(К); КонецЕсли; КонецЦикла; Возврат 0; КонецФункции // Помечен()
В моем запросе не была определена одна переменная: «Сумма». Но это не группировка, поэтому я ее определяю без проверки:
Т.НоваяКолонка("Сумма","Число",15,2); Т.ВыбратьСтроки(); Пока Т.ПолучитьСтроку()=1 Цикл Т.Сумма=Т.Цена*Т.Количество; КонецЦикла;
6. Формируем таблицы по выбранным пользователем группировкам
Еще раз поясню, что это такое. Представим, что в моем примере имеется отмеченными всего две группировки: «Заказчик» и «Материал». Тогда печатная форма отчета должна состоять из итоговых строк по заказчикам в разрезе материалов. Для этого мне потребуется всего две таблицы. Первая – это базовая таблица, сформированная по запросу, а вторая – таблица, построенная на основе базовой, данные в которой (сумма и количество израсходованных материалов) свернуты по заказчикам. Соответственно, при построении отчета, когда совершается обход строк основной таблицы, если в текущей строке заказчик изменился, то это означает, что необходимо вывести результирующую строку по новому заказчику и продолжать обход.
Нам потребуется массив размером, соответствующим размеру списка группировок.
Посчитаем число отмеченных группировок:
ГрВсего=0; Для К=1 По Группировки.РазмерСписка() Цикл Если Группировки.Пометка(К)=0 Тогда Продолжить; КонецЕсли; ГрВсего=ГрВсего+1; КонецЦикла;
Также нам необходима строка «СуммыГруппировок» для свертки таблиц и список выбранных пользователем группировок на форме отчета.
Формировать результирующие таблицы по группировкам будем в цикле. Сформированные таблицы поместим в архив.
СуммыГруппировки="Сумма,Количество"; Группируем=""; Сортируем=""; СписокГруппировок=СоздатьОбъект("СписокЗначений"); Л=0; Для К=1 По Группировки.РазмерСписка() Цикл Если Группировки.Пометка(К)=0 Тогда Продолжить; КонецЕсли; Л=Л+1; Зн=Группировки.ПолучитьЗначение(К,Стр); СписокГруппировок.ДобавитьЗначение(Зн); Заголов=Заголов+?(Заголов="",""," / ")+Стр; Группируем=Группируем+?(Группируем="","",",")+Зн; Сортируем=Сортируем+?(Сортируем="","+",",+")+Зн; Если Л<ГрВсего Тогда Арх[Л]=СоздатьОбъект("ТаблицаЗначений"); Т.Выгрузить(Арх[Л],,,Группируем+","+СуммыГруппировки); Арх[Л].Свернуть(Группируем,СуммыГруппировки); Арх[Л].Сортировать(Сортируем); КонецЕсли; КонецЦикла;
Как видите, таблицы по группировкам спокойненько лежат в массиве, отсортированные как надо, ожидая своего часа.
Кстати, не забудем теперь сгруппировать и отсортировать саму базовую таблицу.
Т.Свернуть(Группируем,СуммыГруппировки); Т.Сортировать(Сортируем);
Мы готовы к заключительному этапу.
7. Рисуем печатную форму.
При рисовании печатной форму стоит помнить, что пользователь должен все-таки тоже что-то увидеть, если он по глупости отметил сразу все возможные группировки. Для этого стоит выделять цветом или отступом итоговые строки.
В моем варианте шаблон таблицы выглядит вот так:
Особо внимательные, думаю, заметили, где используются сформированные ранее строки «СтрШапки» и «Заголов»
Также в начало модуля необходимо вынести определения переменных, используемых в строках. У меня это:
Перем СуммаВСтроке, КоличествоВСтроке;
8. Выводим таблицу на экран
По идее, шаги 6 и 8 можно было бы оформлять в отдельную процедуру, потому что они вполне типизированы и могут использоваться практически без изменения во многих отчетах. Но мы для простоты иллюстрации не будем этого делать.
На заключительном шаге построения отчета с группировками реализуем обход по базовой таблице, выводя строки и группировки печатной формы.
Рассмотрим работу построителя отчета по индукции.
На самом первом шаге обхода таблицы, по ее первой строке выводим сперва данные по всем результирующим группировкам, начиная с самой верхней и до предпоследней. Текущие значения, соответствующие группировкам, сохраняем в архиве. Выводим значения по первой строке.
На каждой итерации цикла перед выводом значений по строке будем сравнивать значения реквизитов в строке таблицы с текущими значениями по каждой из группировок, с самой верхней до предпоследней. Если по какой-то из группировок под номером n значение изменилось, то выводим все секции таблицы, относящиеся к группировкам, начиная с этой, и до предпоследней. Новые значения по группировкам опять сохраняем.
В общем-то ничего сложного…
БылГр=СоздатьОбъект("СписокЗначений"); Таб=СоздатьОбъект("Таблица"); Таб.ИсходнаяТаблица("Таблица"); Таб.ВывестиСекцию("Шапка"); МаксКолвоСекций=6; Гр=""; ТекГр=1; Состояние("Постоентие таблицы ..."); Т.ВыбратьСтроки(); Пока Т.ПолучитьСтроку()=1 Цикл Для К=1 По ТекГр-1 Цикл Если ПроверитьГруппировку(Т,СписокГруппировок,БылГр,К)=0 Тогда ТекГр=К; Прервать; КонецЕсли; КонецЦикла; Пока ТекГр<ГрВсего Цикл СуммаГруппировки(Т,Арх[ТекГр],СписокГруппировок,ТекГр,ГрВсего); Гр=СписокГруппировок.ПолучитьЗначение(ТекГр,Стр); Инф=Т.ПолучитьЗначение(Т.НомерСтроки,Гр); Таб.ВывестиСекцию("Строка"+?(МаксКолвоСекций-ГрВсего>0, Строка(ТекГр+ МаксКолвоСекций -ГрВсего),ТекГр)); ЗапомнитьГруппировку(Т,СписокГруппировок,БылГр,ТекГр); ТекГр=ТекГр+1; КонецЦикла; Гр=СписокГруппировок.ПолучитьЗначение(ТекГр,Стр); Инф=Строка(Т.ПолучитьЗначение(Т.НомерСтроки,Гр)); СуммаГруппировки(Т,Т,СписокГруппировок,ТекГр,ГрВсего); Таб.ВывестиСекцию("Строка"); КонецЦикла;
На не хватает только проверяющей, сохраняющей значения и рассчитывающей сумму по группировкам процедур.
Функция ПроверитьГруппировку(Т,СпГрупп,БылГр,ТекГр) Если ТекГр=0 Тогда Возврат 1; КонецЕсли; Если ТекГр>БылГр.РазмерСписка() Тогда Возврат 1; КонецЕсли; Гр=СпГрупп.ПолучитьЗначение(ТекГр); Зн1=Т.ПолучитьЗначение(Т.НомерСтроки,Гр); Зн2=БылГр.ПолучитьЗначение(ТекГр); Если Зн1<>Зн2 Тогда БылГр.УстановитьЗначение(ТекГр,"явлопваптявлптбяват"); КонецЕсли; Возврат ?(Зн1=Зн2,1,0); КонецФункции // ПроверитьГруппировку(Т,БылГр,ТекГр) Процедура ЗапомнитьГруппировку(Т,СпГрупп,БылГр,ТекГр) Гр=СпГрупп.ПолучитьЗначение(ТекГр); Зн=Т.ПолучитьЗначение(Т.НомерСтроки,Гр); Если ТекГр>БылГр.РазмерСписка() Тогда БылГр.ДобавитьЗначение(Зн); Иначе БылГр.УстановитьЗначение(ТекГр,Зн); КонецЕсли; КонецПроцедуры //ЗапомнитьГруппировку(Т,БылГр,ТекГр) Процедура СуммаГруппировки(Т,ТабГр,СпГрупп,ТекГр,КолГр) Если ТекГр=КолГр Тогда СуммаВСтроке=Т.ПолучитьЗначение(Т.НомерСтроки,"Сумма"); КоличествоВСтроке=Т.ПолучитьЗначение(Т.НомерСтроки,"Количество"); Иначе Гр=СпГрупп.ПолучитьЗначение(1); Зн=Т.ПолучитьЗначение(Т.НомерСтроки,Гр); Поз=0; ТабГр.НайтиЗначение(Зн,Поз,Гр); Пока Поз<=ТабГр.КоличествоСтрок() Цикл Найд=0; Для К=2 По ТекГр Цикл Гр=СпГрупп.ПолучитьЗначение(К); Если ТабГр.ПолучитьЗначение(Поз,Гр)<>Т.ПолучитьЗначение(Т.НомерСтроки,Гр) Тогда Поз=Поз+1; Найд=1; Прервать; КонецЕсли; КонецЦикла; Если Найд=0 Тогда СуммаВСтроке=ТабГр.ПолучитьЗначение(Поз,"Сумма"); КоличествоВСтроке=ТабГр.ПолучитьЗначение(Поз,"Количество"); Прервать; КонецЕсли; КонецЦикла; КонецЕсли; КонецПроцедуры
Отчет готов!
Отчет, который я создавал сейчас вместе с вами, и который может быть легко использован, как шаблон, можно скачать вот здесь: //infostart.ru/projects/1460/
Надеюсь, предложенная технология поможет моим коллегам систематизировать и сократить время на разработку отчетов...