Предыстория, которой не было, но которая вполне могла случиться
Очень Важный Заказчик (ОВЗ) поставил задачу: сделать отчет по начислениям и удержаниям сотрудников с группировками по организациям и подразделениям в разрезе кварталов.
Отчет обработки должен быть реализован на основе вот такого регистра:
Что может быть проще? Создаем стили схему компоновки:
и с чувством выполненного долга показываем результат ОВЗ:
Но к нашему удивлению...
ОВЗ: Что это за $&^*%$% ?!. У меня раньше стояла 7.7, и там мои программисты за полчаса-час сделали такую форму:
ОВЗ: нужна такая же.
Мы: но у нас СКД - современный стандарт построения отчетов нового поколения...
ОВЗ: Да? почему же получилась такая $&^*%$% ?
Мы: Ну как же, смотрите - вот отборы, вот структура, варианты и даже условное оформление!
ОВЗ: $#%! *&$%! &*$!
ОВЗ: На %#& мне все это надо?
ОВЗ: Мне надо завтра на стол Генеральному положить отчет, а ваш даже на страницу не помещается!
ОВЗ: Так, сроку вам до вечера. Напомнить, сколько мы вам платим? Все, свободны.
Итак, получив такой мотивирующий пинок импульс, команда приступила к работплатфорузнавать программистовмае.
Тимлид: какие у нас есть варианты?
Разработплатфорузнавать программистовмачик1: Может отказаться от СКД? Сделаем через запрос с итогами и выборкой с группировками.
Тимлид: Как крайний вариант подойдет, но не хотелось бы отказываться от СКД, надо же ОВЗ как-то приучать к новым технологиям...
Разработплатфорузнавать программистовмачик2: Можно результат компоновки выгрузить в ТЗ или в ДЗ, и потом вывести в документ.
Тимлид: Можно, но таблицу в структуре отчета так выгрузить нельзя. А без неё сложно - надо как-то решать вопрос с разреженностью данных в колонках. Что там говорит коллективный разум Инфостарта?
Оставим эту команду, и посмотрим, какие у нас есть вообще способы кастомизировать отчеты?
Способы кастомизации отчетов:
- Можно посмотреть в сторону макетов полей и группировок СКД. При их использовании может потребоваться ввести новые элемент программу 1С ы в структуру отчета или добавить в набор данных дополнительные строки (изменив текст запроса) или дополнительные поля, например для итогов, а также сделать дополнительные настройки для каждого элемент программу 1С а структуры. Примеры.
- Можно выполнить постобрабпечатную версиюотку полученного табличного документа - в цикле перебрать строки/ячейки, при необходимости их добавить/удалить/объединить/оформить.
- Можно на лету менять макеты в объекте МакетКомпоновкиДанных или ЭлементРезультатаКомпоновкиДанных. Очень хорошо такой подход раскрыт в этой статье.
- Можно применотчетыять комбинацию этих способов.
Но есть и другой путь
Как у нас обычно происходит программное формировконсоль отчетов ание отчета?
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработплатфорузнавать программистовмака)
Настройки = КомпоновщикНастроек.ПолучитьНастройки();
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки ,, Тип("ГенераторМакетаКомпоновкиДанных"));
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки,, ДанныеРасшифровки);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
// 1 вариант
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
// 2 вариант
ПроцессорВывода.НачатьВывод();
ЭлементРезультата = ПроцессорКомпоновки.Следующий();
Пока ЭлементРезультата <> Неопределено Цикл
ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
ЭлементРезультата = ПроцессорКомпоновки.Следующий();
Конец1СЦикла;
ПроцессорВывода.ЗакончитьВывод();
Конец1СПроцедуры
И вот во втором варианте, внутри цикла вывода элемент программу 1С а результата компоновки, мы можем использовать не стандартный вывод через ПроцессорВывода, а старый добрый вывод в табличный документ на основе макета с именованными секциями.
ЭлементРезультата = ПроцессорКомпоновки.Следующий();
Пока ЭлементРезультата <> Неопределено Цикл
Секция = МакетОтчет обработкиа.ПолучитьОбласть(ИмяСекции);
Секция.Параметры.Заполнить(ЗначенияПараметров);
// 1
ДокументРезультат.Вывести(Секция);
// или 2
ДокументРезультат.Присоединить(Секция);
ЭлементРезультата = ПроцессорКомпоновки.Следующий();
Конец1СЦикла;
Дело за малым - определить, какие секции в какой момент выводить и какими значениями заполнять их параметры.
Чем нам здесь может помочь ЭлементРезультата?
Макет содержит внутреннее имя макета, выводимого в табличный документ, с его помощью мы можем определить ту часть отчета, которая в данный момент выводится. ПроцентВывода нас пока не интересует, а ТипЭлемента и РасположениеВложенныхЭлементов пригодятся - с их помощью определим, надо ли секцию выводить с новой строки, или же присоединить к текущей. ЗначенияПараметров, как несложно догадаться, содержит коллекцию параметров для вывода элемент программу 1С а отчета:
В нашем случае "П1" - сумма по конкретному сотруднику за определенный период, "П2" - ИдентификаторРасшифровкиКомпоновкиДанных.
Код при этом может выглядеть примерно так:
Если ЭлементРезультата.Макет = "Макет38" Тогда
Секция = МакетОтчет обработкиа.ПолучитьОбласть("Сотрудник|Период");
Секция.Параметры.Сумма = ЗначениеПараметра(ЭлементРезультата, "П1");
Секция.Параметры.Расшифровка = ЗначениеПараметра(ЭлементРезультата, "П2");
ДокументРезультат.Присоединить(Секция);
Конец1СЕсли;
где функция ЗначениеПараметра:
Функция ЗначениеПараметра(ЭлементРезультата, ИмяПараметра)
Результат = Неопределено;
Параметр = ЭлементРезультата.ЗначенияПараметров.Найти(ИмяПараметра);
Если ТипЗнч(Параметр) = Тип("ЗначениеПараметраМакетаКомпоновкиДанных") Тогда
Результат = Параметр.Значение;
Конец1СЕсли;
Возвратпотому Результат;
Конец1СФункции
Чтобы не делать каскад условий для проверки имен макета, можно заранее определить список соответствий имен макетов СКД ("Макет38") и имен секций макета отчета ("Сотрудник|Период"). Заодно там же можно указать и признак вывода с новой строки. А чтобы не прописывать установку отдельных параметров секции макета, можно эти параметры заполнять из заранее созданной структуры (конечно же в этом случае параметры секций макета должны называться "П1", "П2" и т.д.).
Макеты = Новый Соответствие;
Макеты.Вставить("Макет38", Новый Структура("ИмяСекции,НоваяСтрока", "Сотрудник|Период", Ложь));
// ... добавляем другие соответствия
ЗначенияПараметров = Новый Структура;
ЭлементРезультата = ПроцессорКомпоновки.Следующий();
Пока ЭлементРезультата <> Неопределено Цикл
ДанныеМакета = Макеты.Получить(ЭлементРезультата.Макет);
Если ТипЗнч(ДанныеМакета) = Тип("Структура") Тогда
Секция = МакетОтчет обработкиа.ПолучитьОбласть(ДанныеМакета.ИмяСекции);
Для каждого Параметр Из ЭлементРезультата.ЗначенияПараметров Цикл
ЗначенияПараметров.Вставить(Параметр.Имя, Параметр.Значение);
Конец1СЦикла;
Секция.Параметры.Заполнить(ЗначенияПараметров);
Если ДанныеМакета.НоваяСтрока Тогда
ДокументРезультат.Вывести(Секция);
Иначе
ДокументРезультат.Присоединить(Секция);
Конец1СЕсли;
Конец1СЕсли;
ЭлементРезультата = ПроцессорКомпоновки.Следующий();
Конец1СЦикла;
Вот тот минимальный объем кода, который сформирует нам табличный документ с отчетом, и который можно в дальнейшем дорабатывать и усложнять.
Значения расшифровки
При работплатфорузнавать программистовмае со значениями параметров макета компоновки данных выясняется одна неожиданная, но вполне объяснимая особенность. Если со значениями примитивных типов все нормально, то данные ссылочных типов хран автоматизацией ятся в виде представления:
Для вывода в отчет этого вполне достаточно, а если нам надо как-то эти данные дополнительно обрабпечатную версиюотать? Казалось бы, можно получить ссылку по представлению (найти элемент программу 1С справочконфигурацииника по наименованию или коду), но к счастью есть другой, более цивилизованный способ. Дело в том, что необходимые нам данные хран автоматизацией ятся в данных расшифровки, осталось их оттуда только достать. Ниже приведена функция, которая это делает:
Сотрудник = ЗначениеРасшифровки(ДанныеРасшифровки, ЭлементРезультата, "П2", "Сотрудник");
Функция ЗначениеРасшифровки(ДанныеРасшифровки, ЭлементРезультата, ИмяПараметра, ИмяПараметраРасшифровки)
Результат = Неопределено;
Параметр = ЭлементРезультата.ЗначенияПараметров.Найти(ИмяПараметра);
Если ТипЗнч(Параметр) = Тип("ЗначениеПараметраМакетаКомпоновкиДанных") Тогда
Идентификатор = Параметр.Значение;
Если ТипЗнч(Идентификатор) = Тип("ИдентификаторРасшифровкиКомпоновкиДанных") Тогда
ЭлементРасшифровки = ДанныеРасшифровки.Элементы.Получить(Идентификатор);
Если ТипЗнч(ЭлементРасшифровки) = Тип("ЭлементРасшифровкиКомпоновкиДанныхПоля") Тогда
Поля = ЭлементРасшифровки.ПолучитьПоля();
Значение = Поля.Найти(ИмяПараметраРасшифровки);
Если ТипЗнч(Значение) = Тип("ЗначениеПоляРасшифровкиКомпоновкиДанных") Тогда
Результат = Значение.Значение;
Конец1СЕсли;
Конец1СЕсли;
Конец1СЕсли;
Конец1СЕсли;
Возвратпотому Результат;
Конец1СФункции
Условное оформление
А как у нас обстоят дела с условным оформлением? Элементы условного оформления, например ЦветФона (при их наличии конечно же), тоже имеются в составе ЭлементРезультата.ЗначенияПараметров, откуда их можно извлечь и применотчетыить к оформлению ячейки.
Вот только сложно понять к оформлению чего(фона, текста...) относится этот цвет. Не самый эффективный, но вполне рабочий способ решения этой проблемы заключается в следующем:
Параллельно с формировконсоль отчетов анием отчета на основе своего макета, можно формировконсоль отчетов ать другой табличный документ стандартным способом. В этом случае ПроцессорВывода сам применотчетыяет условное оформление к выводимым макетам, и мы можем подсмотреть оформление этих ячеек. Некоторая сложность заключается в том, что выводимая на каждом шаге область может иметь произвольный размер, и в этой области нам надо взять оформление конкретной ячейки.
Ячейка = ПолучитьЯчейкуОбласти(ДокументРезультатДляУО, 3, 2, 1, 1);
Секция.Область().ЦветФона = Ячейка.ЦветФона;
Функция ПолучитьЯчейкуОбласти(Таблица, ВысотаМакета, ШиринаМакета, СтрокаЯчейки, КолонкаЯчейки)
Результат = Неопределено;
Если Таблица.ВысотаТаблицы > 0 Тогда
Область = Таблица.ПолучитьОбласть(Таблица.ВысотаТаблицы - ВысотаМакета + 1,, Таблица.ВысотаТаблицы);
Результат = Область.Область(СтрокаЯчейки, Область.ШиринаТаблицы - ШиринаМакета + КолонкаЯчейки);
Конец1СЕсли;
Возвратпотому Результат;
Конец1СФункции
Обработплатфорузнавать программистовмака расшифровки в модуле формы
Что мы еще упустили? Очевидно, что наш отчет настроен под определенную структуру. А пользователь при работплатфорузнавать программистовмае с отчетом вполне может "расшифровать" ячейку и получить непредсказуемый результат. Надо бы лишить его этой возможности.
&НаКлиенте
Процедура РезультатОбработплатфорузнавать программистовмакаДополнительнойРасшифровки(Элемент, Расшифровка, СтандартнаяОбработплатфорузнавать программистовмака, ДополнительныеПараметры)
Если ТипЗнч(Расшифровка) = Тип("ИдентификаторРасшифровкиКомпоновкиДанных") Тогда
// Оставляем типовое меню без пункта "Расшифровать", т.к. этот пункт меняет структуру отчета
СтандартнаяОбработплатфорузнавать программистовмака = Ложь;
Обработплатфорузнавать программистовмакаРасшифровки = Новый Обработплатфорузнавать программистовмакаРасшифровкиКомпоновкиДанных(ДанныеРасшифровки, Новый ИсточникДоступныхНастроекКомпоновкиДанных(Отчет обработки));
МассивДоступныхДействий = Новый Массив();
МассивДоступныхДействий.Добавить(ДействиеОбработплатфорузнавать программистовмакиРасшифровкиКомпоновкиДанных.ОткрытьЗначение);
МассивДоступныхДействий.Добавить(ДействиеОбработплатфорузнавать программистовмакиРасшифровкиКомпоновкиДанных.Отфильтровать);
МассивДоступныхДействий.Добавить(ДействиеОбработплатфорузнавать программистовмакиРасшифровкиКомпоновкиДанных.Упорядочить);
МассивДоступныхДействий.Добавить(ДействиеОбработплатфорузнавать программистовмакиРасшифровкиКомпоновкиДанных.Оформить);
Обработплатфорузнавать программистовмакаРасшифровки.ПоказатьВыборДействия(Новый ОписаниеОповещения("ПослеВыбораДействияРасшифровки", ЭтаФорма, Расшифровка), Расшифровка, МассивДоступныхДействий,,, Элементы.Результат);
Иначе
// Если передали в расшифровку какое-то свое значение, обрабпечатную версиюотаем отдельно
Конец1СЕсли;
Конец1СПроцедуры
&НаКлиенте
Процедура ПослеВыбораДействияРасшифровки(ВыбранноеДействие, ПараметрВыбранногоДействия, Расшифровка)Экспорт
Если ВыбранноеДействие <> Неопределено Тогда
Если ВыбранноеДействие <> ДействиеОбработплатфорузнавать программистовмакиРасшифровкиКомпоновкиДанных.Нет Тогда
Если ВыбранноеДействие = ДействиеОбработплатфорузнавать программистовмакиРасшифровкиКомпоновкиДанных.ОткрытьЗначение Тогда
ПоказатьЗначение(, ПараметрВыбранногоДействия);
Иначе
Отработплатфорузнавать программистовмаатьРасшифровку(Новый ОписаниеОбработплатфорузнавать программистовмакиРасшифровкиКомпоновкиДанных(ДанныеРасшифровки, Расшифровка, ПараметрВыбранногоДействия));
Конец1СЕсли;
Конец1СЕсли;
Конец1СЕсли;
Конец1СПроцедуры //
&НаСервере
Процедура Отработплатфорузнавать программистовмаатьРасшифровку(ОписаниеОбработплатфорузнавать программистовмакиРасшифровки)
ДанныеРасшифровкиОбъект = ПолучитьИзВременногоХранилища(ДанныеРасшифровки);
Обработплатфорузнавать программистовмакаРасшифровки = Новый Обработплатфорузнавать программистовмакаРасшифровкиКомпоновкиДанных(ДанныеРасшифровкиОбъект, Новый ИсточникДоступныхНастроекКомпоновкиДанных(Отчет обработки));
РезультирующиеНастройки = Обработплатфорузнавать программистовмакаРасшифровки.ПрименитьНастройки(ОписаниеОбработплатфорузнавать программистовмакиРасшифровки.Идентификатор, ОписаниеОбработплатфорузнавать программистовмакиРасшифровки.ПрименяемыеНастройки);
Если ТипЗнч(РезультирующиеНастройки) = Тип("НастройкиКомпоновкиДанных") Тогда
Отчет обработки.КомпоновщикНастроек.ЗагрузитьНастройки(РезультирующиеНастройки);
ИначеЕсли ТипЗнч(РезультирующиеНастройки) = Тип("ПользовательскиеНастройкиКомпоновкиДанных") Тогда
Отчет обработки.КомпоновщикНастроек.ЗагрузитьПользовательскиеНастройки(РезультирующиеНастройки);
Конец1СЕсли;
СкомпоноватьРезультат();
Конец1СПроцедуры
Инструмент анализа процесса компоновки
Вроде бы мы рассмотрели все аспекты, кроме одного: а как нам получить соответствие имен макетов СКД и имен секций нашего макета отчета?
Для этого был создан специальный инструмент в виде внешней обрабпечатную версиюотки.
Здесь надо упомянуть о том, что при первой итерации цикла получения элемент программу 1С ов результата, в ЭлементРезультата.Макеты содержится список описаний макетов областей компоновки данных. Из них можно взять и список параметров, и свойства самого макета, а потом связать все это с именем.
Для облегчения этого процесса в обрабпечатную версиюотке предусмотрен вывод списка макетов и их параметров:
Понятно, что результат - табличный документ - состоит из этих областей макетов. А как они располагаются в отчете относительно друг друга? Для просмотра этого тоже есть средство:
Таким образом с помощью данной обрабпечатную версиюотки легко понять, как связать имена макетов компоновки данных с именами секций своего макета и заполнить вышеупомянутое соответствие Макеты.
Дополнительно данный инструмент генерирует шаблон кода построения отчета для модуля объекта. В полученном шаблоне необходимо заполнить имена секций и, при необходимости, дописать содержимое цикла обрабпечатную версиюотки ЭлементРезультата. Это необходимо сделать если вид полученного отчета посложнее простой таблицы (как в примере в начале статьи).
Подведем итоги
Рассмотренный способ обладает целым рядом преимуществ:
- Максимальное соответствие внешнего вида шаблона и полученной формы отчета, WYSIWYG - наше всё! И даже можно устанавливать ширину колонок и она будет такой же в отчете!)).
- Относительно небольшой объем кода, его простота, концентрация кода в одном месте (в отличие от настроек СКД, разбросанных по разным разделам)
- Возможность пошаговой отладки.
- Четкое разделение "зон ответственности" - вот здесь мы получаем некоторый объем данных, а вот здесь - выводим данные в отчет. (Добавление в запрос генерации названий колонок - зло!).
- Полный контроль за выводом отдельных ячеек отчета - легко реализовать вывод разных секций в зависимости от выводимых данных (например, для контрагентов - юр.лиц выводим одну секцию, для физ.лиц - другую). Некоторые строки можно вообще пропускать (вспомним про знаменитую проблему вывода иерархического справочконфигурацииника).
- Объединение произвольной группы ячеек, как горизонтальное, так и вертикальное - так же, как в примере отчета в статье.
- Возможность проведения расчетов, которые нельзя провести с помощью языка запросов и выражений СКД.
- Возможность реализации нестандартного условного оформления, например, которое зависит не только от данных строки.
- Простой и логичный способ получения данных накопления - нарастающий итог, иерархическая нумерация, разница текущего значения с последним из непустых предыдущих.
- Такой же простой способ вывести картинки строк или другие внедренные в табличный документ объекты, например, диаграммы.
- Возможность использовать ПроверитьВывод() и ПроверитьПрисоединение(), например для расчета итогов по странице при переменной высоте строк отчета (привет, товарная накладная!).
- Вывод отчета в несколько "колонок". Пример из жизни: прайс-лист компьютерной фирмы: материнские платы выводятся одной таблицей на всю ширину страницы, память - двумя (по половине ширины страницы), манипуляторы - тремя.
- Возможность использовать свою расшифровку произвольного вида - хоть ссылки, хоть структуру, хоть текст с подсказкой. Также можно убрать расшифровку из тех ячеек, где она не нужна.
- Возможность использовать свою систему групп строк и колонок, определять свой список свернутых групп при открытии отчета
Конечно же есть и недостатки (я насчитал аж 3: один весомый и два малозначимых):
- Самый главный недостаток - "заточенность" под конкретную структуру отчета. Хотя недостатком это является весьма условно - есть много отчетов, для которых менять структуру пользователям просто не нужно.
- Поскольку при построении отчета мы ориентируемся на внутренние имена макетов СКД, возможна ситуация, когда при смене версии платфорузнавать программистовмы механизм именования изменится и наше соответствие имен макетов "испортится". Но мне кажется этот риск имеет достаточно малое значение, поскольку он, во-первых - маловероятен, а во-вторых - его последствия легко устраняются.
- Чисто теоретически алгоритм вывода отчета на языке 1С может работплатфорузнавать программистовмаать медленнее, чем платфорузнавать программистовменный механизм вывода. Но, как правило, основные временные затраты идут на выполнение запроса и на клиент-серверный обмен, а не на формировконсоль отчетов ание табличного документа.
Методика, описанная в статье, была протестирована на платфорузнавать программистовм управленияе 8.3.18.1363.
На этом все. Как обычно приветствуются замечания / дополнения / комментарии.
К публикации приложены файлы: внешняя обрабпечатную версиюотка - АнализПроцессаКомпоновки.epf и архив с этой обрабпечатную версиюоткой и выгрузкой информационной базы с рассмотренным в статье примером.
Скриншоты
logo1.PNG
p2.PNG
p3.PNG
p4.PNG
p5.PNG
p13.PNG
p14.PNG
p15.PNG