gifts2017

Дерево Осипова - подход к написанию отчетов

Опубликовал Осипов Сергей (fixin) в раздел Программирование - Практика программирования

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

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

Можно, конечно использовать внешние средства для построения отчеты - наборы вроде Crystal Reports или инструментов попроще вроде сводных таблиц Excel. Но пользователю часто более удобно работать внутри родной программы учета, да и внешние инструменты стоят денег и вызывают проблемы согласования.

Поэтому часто программисту приходится делать отчеты встроенными средствами. Не знаю, как в других системах, а в 1С для вывода отчетов в Excel-подобную таблицу, состоящую из ячеек, у программиста есть только три инструмента:

1. Вывод подготовленной целой строки с переводом на следующую строку или ее части без перевода на следующую строку.
2. Непосредственная работа с ячейкой, указанной координатами.

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

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

Структура отчета

Рассмотрим для примера достаточно сложный перекрестный отчет:

Описание: R:ВебМастерfixin.htmlarticles1s_treerepcomplex_report.gif

В отчете показано, сколько товаров продано каким клиентам с каких складов в каких городах. Склады указаны по их номерам, таже дополнительно приводится адрес склада. Итоги указаны по товарам и общий итог по каждому складу.

Исходные данные для отчета - таблица вида (Товар, Клиент, Склад, АдресСклада, Продано). С получением данных проблем никаких нет. Т.е. внимание программиста сосредотачивается на выводе отчета и программирование вывода занимает достаточно много времени. Попробуем его сократить и свести к минимуму.

Красной рамкой на рисунке обведена печатная форма отчета, какой ее видит пользователь. Слева и сверху от рамки - новая система координат, разработанная мной. Система координат представляет собой два дерева - левое и верхнее. Дерево состоит из подчиненных друг другу узлов.

Узлы могут подчиняться друг другу только тремя способами (темный цвет - родитель):

Описание: R:ВебМастерfixin.htmlarticles1s_treerepkindsofchalign.gif

1. Все дети находятся внизу родителя.
2. Все дети находятся над родителем.
3. Все дети находятся под родителем.

Такая схема подчинения указана для левого дерева, для верхнего дерева аналогично:

1. Все дети находятся под родителем.
2. Все дети находятся слева от родителя.
3. Все дети находятся справа от родителя.

Теперь для указания ячейки мне достаточно указать левый и верхний узел. При этом будет выбрана ячейка, которая состоит из различного числа базовых клеток. Например (Товар1, Город1) = 2 клетки, (Товар1, Склад1) = 1 клетка. Если ячейка состоит из нескольких клеток, то при внесении в нее значения клетки объединяются.

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

А дальше мы посмотрим, как такая простая схема навигации по отчету позволит существенно упростить программирование отчетов.

Объекты доступа

Формализируем и опишем функционал объекта, который позволит нам организовать подобную схему навигации.

Объект дерево (Tree)

Состоит из объектов:
1. Левый(Left) и Верхний(Top) типа Узел(Node) - левый и верхний корневые узлы деревьев
2. Ячейки(Cells)

Объект Узел(Node)

Содержит список объектов типа Узел(Nodes).

Содержит:
1. Ссылку на своего родителя Родитель(Parent) или пустую ссылку у левой и правой ветвей.
2. Список своих детей - подчиненных узлов Дети(Childrens)
3. Способ подчинения детей - ПодчинениеДетей(ChildAlign)
4. Флажок невидимости - Спрятан(Hide) - истина указывает, что узел не выводится (по умолчанию ложь).

Объект СписокУзлов(NodeList)

Содержит список объектов типа Узел(Nodes). Используется для групповой работы с узлами.

Объект Ареал(Areal)

Представляет собой некое подмножество всех ячеек Cells.
Содержит левый/верхний узлы или левый/верхний список узлов, которыми это множество ячеек задано.
Свойства называются Левый(Left) и Верхний(Top).

Используется для итогов, форматирования дизайна ячеек, автозаполнения из таблицы и прочих целей.

Объект Ячейки(Cells)

Содержит двумерный массив всех ячеек дерева Ячейка(Cell).
Первая координата - узел левого дерева, вторая - узел верхнего дерева.
Можно перебрать все ячейки, а можно получить доступ к ним по координатам узлов.
Содержит координаты левого/верхнего узлов или левого/верхнего списоков узлов, которыми это множество ячеек задано.

Методы доступа:
1. Оператор [i, j] - доступ к ячейку по узлу левого дерева i и верхнего дерева j.

Объект Ячейка(Cell)

Содержит:
1. Значение (Value) - значение хранимое в ячейке
2. Текст (Text) - текстовое представление значения
3. Идентификатор (ID) - уникальный в пределах подчинения ключ объекта (для поиска среди детей), использовать необязательно.
4. Вычисляемые координаты Кол(X),Стр(Y) и размеры Ширина(SizeX), Высота(SizeY) ячейки (в базовых клетках).

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

Программирование отчета

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

Сначала создадим само дерево.

Tree=New Tree

Создадим фиксированные узлы для левого дерева:

Tree.Left.Add(ID="ГрГород")
Tree.Left.Add(ID="ГрСклад")
Tree.Left.Add(ID="ГрАдрес")
Tree.Left.Add(ID="ОбщЛев")
Tree.Left.Add(ID="ИтогСтрок")

Создадим фиксированные узлы для правого дерева:

Tree.Left.Add(ID="Гр1")
Tree.Left.Add(ID="ГрСклад")
Tree.Left.Add(ID="ГрАдрес")
Tree.Left.Add(ID="ОбщЛев")
Tree.Left.Add(ID="ИтогСтрок")

ОбщЛев и ОбщВерх - это узлы, у которых будет заранее неизвестное количество детей.
Left и RightNode заданы по умолчанию при создании дерева.
Для исполнения дизайна нам пришлось сделать у Гр1 два подчиненных узла 0 и Гр2. Так можно организовать вывод клиента со сдвиегом.
В принципе, над списком можно предусмотреть и операции вставки, но нам достаточно только метода Add(Добавить).

Далее нарисуем шапку таблицы:

1. Нарисуем товары

For Each Unique Товар из Продажи do
    Node=Tree.Left.Get(ID="ОбщЛев").Add(Value=Товар)
    Cell= Cells[Node][Tree.Top.Get(ID="Гр1")]
    Cell.Value=Товар
Next

У отчета появилась шапка по товарам - список товаров слева.

2. Нарисуем заголовки по городам

For Each Unique Город из Продажи do
    Node=Tree.Top.Get(ID="ОбщВерх").Add(Value=Город)
    Cell=Cells[Tree.Top.Get(ID="ГрГород")][Node]
    Cell.Value=Город
Next

У отчета появилась шапка по городам - список товаров сверху.

3. Будем обрабатывать всю таблицу продаж, по ходу создавая узлы для существующих клиентов, складов и рисуя шапку.

For Each Прод из Продажи do
    ClientNode=Tree.Left.Get(ID="ОбщЛев").Get(Value=Прод.Товар).Create(Value=Прод.Клиент)
    if (ClientNode.JustCreated)
        Cell[ClientNode][Tree.Top.Get(ID="Гр1").Get(ID="Гр2")].Value=Прод.Клиент
    StoreNode=Tree.Top.Get(ID="ОбщЛев").Get(Value=Прод.Город).Create(Value=Прод.Склад)
    if (StoreNode.JustCreated)
        Cell[Tree.Top.Get(ID="ГрГород")")][StoreNode].Value=Прод.Склад
    Cell[ClientNode][StoreNode].Value=Прод.Продано
Next

Метод Create создает узел только тогда, когда не нашел узел по заданным условиям поиска. Первый вызов функции JustCreated возвращает истину, последующие - ложь. Это нужно, чтобы рисовать шапку только один раз.

У отчета появилась шапка по клиентам слева и по складам сверху. Также в ячейки на пересечении клиента и склада занесены цифры по количеству продаж.

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

6. Остальные ячейки (адреса складов, фиксированная шапка в левом верхнем углу, общий итог) здесь пропускаются и оставляются в качестве домашнего задания. Если пришлете код на псевдо-языке, я опубликую.

7. Запускается процедура рассчета ячеек дерева.

8. Дерево выводится по рассчитанным координатам средствами внутреннего языка системы. При этом производится установка нужного шрифта, цвета фона и прочих элементов оформления, согласно координатам ячеек.

Ускорение программирования

Облегчаем вывод

Итак, мы значительно облегчили себе работу, но это еще не все, что можно сделать. Нам все равно пришлось писать код для однообразных операций. Давайте избавимся от этого.

Попробуем заполнять ячейки по-другому.

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

Попробуем формализовать то, что мы делаем:

Исходная таблица - Таблица.
#ОбщЛев=Tree.Left.Get(ID="ОбщЛев")
#ОбщЛев=Tree.Top.Get(ID="ОбщВерх")
#Гр1=Tree.Top.Get(ID="Гр1")
#Гр2=Tree.Top.Get(ID="Гр1").Get(ID="Гр2")
#ГрГород=Tree.Left.Get(ID="ГрГород")
#ГрСклад=Tree.Left.Get(ID="ГрСклад")
#ГрАдрес=Tree.Left.Get(ID="ГрАдрес")
#Товар=Назначать по Таблица.Товар подчиненные узлу #ОбщЛев
#Клиент=Назначать по Таблица.Клиент подчиненные узлу #Товар
#Город=Назначать по Таблица.Город подчиненные узлу #ОбщВерх
#Склад=Назначать по Таблица.Склад подчиненные узлу #Город
После создания узлов для только что созданных узлов рисовать шапку:
#Товар - в шапку заносим Таблица.Товар в ячейку [#Товар][#Гр1]
#Клиент - в шапку заносим Таблица.Клиент в ячейку [#Клиент][#Гр2]
#Город - в шапку заносим Таблица.Город в ячейку [#ГрГород][#Город]
#Склад - в шапку заносим Таблица.Склад в ячейку [#ГрСклад][#Склад]
#Склад - в шапку заносим Таблица.АдресСклада в ячейку [#ГрАдрес][#Склад]

Таким образом, основной способ заполнения данных в таблицу формализован. Осталось соотнести его с псевдо-языком программирования.

Для псевдо-языка нужно указать список вычислимых в процессе прохода таблицы узлов (Товар, Клиент, Город, Склад).
Далее при создании каждого узла будет вызываться метод ПриСозданииУзла(OnCreateNode).
После прохождения строки таблицы (т.е. после создания всех динамических узлов) будет вызываться метод ПослеСозданияУзлов(AfterNodesCreate). Здесь можно будет установить шапку.
Т.е. необязательно писать еще один язык запросов, можно все сделать методами.
Подробнее как это сделать будет описано позже.

Облегчаем подсчет итогов

Применим ту же схему для подсчета итогов.
Формализуем задачу для примера:
#Товары=список узлов, который состоит из всех, родитель которых #ОбщЛев
#Города=список узлов, который состоит из всех, родитель которых #ОбщВерх
#Склады =список узлов, который состоит из всех, родитель которых в списке #Города
или #Склады можно получить, сделав отбор по узлам - те узлы, тип которых в Value - Склад

1. Вычислить новый итог для каждого #Товар из #Товары и каждого #Склад из #Склады, как Сумма(Среднее, Количество и т.п.) Ареала[все, подчиненные #Товар][#Склад].
2. Вычислить новый итог для каждого #Склад из #Склады, как Сумма(Среднее, Количество и т.п.) Ареала[#Товары][#Склад].

Опять же, реализация этих формальных условий позже, главное, что рассчет итогов можно формализовать.

Резюме

Предлагается очень интересный подход к написанию сложных отчетов простыми методами.
Реализация разрабатывается на vba-подобном языке 1С. Будут приветствоваться реализации на других языках. Предыдущая, более громоздкая схема уже реализована на 1С, но она слишком медленная и использует другую, хотя и похожую модель структуры отчета.
Приветствую любое сотрудничество.

P.S.:

На момент написания статьи СКД и 1с8 еще не существовало, но метод навигации по отчету представляется интересным, особенно для нетривиальных отчетов, которых нельзя реализовать на СКД.

 

 

См. также

Подписаться Добавить вознаграждение

Комментарии

0. Осипов Сергей (fixin) 14.02.12 13:58
Дерево Осипова - это на данный момент скорее теоретический, чем практический подход к программированию сложных отчетов. Он будет полезен всем, кто пишет сложные отчеты. Использование этого инструмента позволит писать отчеты легко, независимо от их сложности.

Перейти к публикации

1. Serj (Serj1C) 14.02.12 13:58
> На момент написания статьи СКД и 1с8 еще не существовало
что ж вы "старье" выкладываете?
> на данный момент скорее теоретический, чем практический подход
тем более до сих пор теоретический
> особенно для нетривиальных отчетов, которых нельзя реализовать на СКД
можно собрать данные в таблицу значений и вывести через набор данных Объект.
2. Осипов Сергей (fixin) 14.02.12 14:08
(1) я выкладываю не утратившее актуальности старье.
Мы тут как-то с лунсом уже обсуждали на дубовом, существуют ли отчеты, которые нельзя вывести в СКД.
Мое мнение, что существуют и их много. Это запросы со сложными группировками и итогам, где пригодилась бы прямая навигация, описываемая в данной статье.
Можно ли все отчеты написать на СКД?
3. kolubo (Andle) 14.02.12 16:03
Спасибо, очень интересный и полезный подход. А картинки только я не вижу?
4. Осипов Сергей (fixin) 14.02.12 16:59
5. Александр Лыткин (TrinitronOTV) 15.02.12 07:17
интересная публикация, спасибо автору
6. Александр Рытов (Арчибальд) 15.02.12 08:37
"Верной дорогой идете, товарищи"
7. Осипов Сергей (fixin) 15.02.12 10:43
8. Александр Рытов (Арчибальд) 15.02.12 11:51
(7) Специально для буквоедов переформулирую: Идея хорошая, заслуживает дальнейшего развития
;)
10. Сергей Лунев (luns) 15.02.12 15:01
(2) было дело.
в чистом виде на СКД конечно все не напишешь.
но тз + скд решает все проблемы.
11. rimma_n (rimma_n) 15.02.12 15:39
12. Осипов Сергей (fixin) 15.02.12 16:21
(10) не уверен, постараюсь привести пример в ветке на форуме.
(8) меня тоже прикалывает. полная навигация по отчету - это нечто...
13. aaa aaa (a_a) 16.02.12 08:06
согласен что не всё можно сделать на СКД, но это скорее теория чем практика
14. Степашка Никулин (Styvi) 16.02.12 11:40
Готов согласиться, что есть отчёты, которые в СКД даже начинать не хочется - всё равно вернёшься к обработкам "по наитию"...
Но вот читаю, и вижу, что пока ещё недостаточно просто укладывается в голове эта формализация - как-то проще кажется действительно собрать все данные в одну осмысленную таблицу, а потом выложитье ё одним разом, последовательно группируя нужные поля и столбцы...
...
15. Dima Dima (dumal) 16.02.12 23:45
Хм, а я-то, грешным делом, думал, что в этой области уже все придумано. Ваша статья, несомненно, является ценным вкладом в копилку знаний. Спасибо
16. Анатолий Ситников (acsent) 19.02.12 00:21
(10) да вот нет
До сих пор СКД криво работает с несколькими группировками колонками
17. Oleg . (oaf_is) 20.02.12 21:04
ХМ, если "...заглянуть поверх голов...", то Вы хотите ДЛЯ ОПИСАНИЯ ТАБЛИЦ НАПИСАТЬ ДИАЛЕКТ языка типа ПРОЛОГ? (итерпритатор/макроязык/кроскомпилятор):) Там есть схожие подходы: попытка трансформации кода из процедурного языка (читай машинно ориентированного ) в "Сруктурированно-Человевеческий" для чего в вводят как в философии понятия и методы "определения", "аксиомы", "законы"(выводы).Программа здесь - выводы на основе примененных к данным определений и аксиом, выводов.
+, Спасибо. Приятно удивило :)... Все же если не снаружи, то внутри мы остаёмся такИми как есть.:(
18. Осипов Сергей (fixin) 20.02.12 21:13
(17) не, язык придумывать не хочу. Простая библиотека для предоставления простой навигации в макете отчета. Наверх такой навигации можно хоть СКД замутить. Суть в том, что мы можем легко получить нужную ячейку по ее адресу
19. Игор Мудрицкий (Zas1402) 21.02.12 10:39
(2) fixin, большое спасибо очень помогли
20. Осипов Сергей (fixin) 21.02.12 11:15
(19) в чем помог? Похоже на накрутку СМ.
21. Antonio Ant (antek) 22.02.12 02:24
Жесть какая. Интересно что натолкнуло на мысль использовать такой своеобразный построитель отчетов
22. Осипов Сергей (fixin) 22.02.12 10:36
(21) он был разработан на 1с7, когда еще не было ПО и СКД. Но подход имхо красивый и актуален по сей день.
23. Дима Димыч (kazna2011) 22.02.12 12:07
Попробуем формализовать то, что мы делаем:
Исходная таблица - Таблица.
#ОбщЛев=Tree.Left.Get(ID="ОбщЛев")
#ОбщЛев=Tree.Top.Get(ID="ОбщВерх")
#Гр1=Tree.Top.Get(ID="Гр1")
#Гр2=Tree.Top.Get(ID="Гр1").Get(ID="Гр2")
#ГрГород=Tree.Left.Get(ID="ГрГород")
#ГрСклад=Tree.Left.Get(ID="ГрСклад")
#ГрАдрес=Tree.Left.Get(ID="ГрАдрес")
#Товар=Назначать по Таблица.Товар подчиненные узлу #ОбщЛев
#Клиент=Назначать по Таблица.Клиент подчиненные узлу #Товар
#Город=Назначать по Таблица.Город подчиненные узлу #ОбщВерх
#Склад=Назначать по Таблица.Склад подчиненные узлу #Город
После создания узлов для только что созданных узлов рисовать шапку:
#Товар - в шапку заносим Таблица.Товар в ячейку [#Товар][#Гр1]
#Клиент - в шапку заносим Таблица.Клиент в ячейку [#Клиент][#Гр2]
#Город - в шапку заносим Таблица.Город в ячейку [#ГрГород][#Город]
#Склад - в шапку заносим Таблица.Склад в ячейку [#ГрСклад][#Склад]
#Склад - в шапку заносим Таблица.АдресСклада в ячейку [#ГрАдрес][#Склад]
24. Эльвира Соцкова (ela) 25.02.12 20:19
26. a a (andy2011) 26.02.12 23:44
если мне память не изменяет, лет эдак 8-9-10 назад уже что-то такое было .
некто провозгласивший себя Гений1с писал такое же.
стиль изложения - тот же.
суть - та же.

разве ничего за 10 лет не поменялось в подходах ? :))))))
27. Осипов Сергей (fixin) 27.02.12 08:23
(26) гений 1с = фиксин. по вашему вопросу - зачем менять хорошие добрые старые вещи?
28. Сергей Клевакин (tulaka) 28.02.12 11:19
как не странно очень актуально
29. Владимир Тимофеев (vladtimof) 13.03.12 14:23
интересная публикация, спасибо
30. Алекс Ю (AlexO) 22.03.12 12:30
вот это
Узлы могут подчиняться друг другу только тремя способами (темный цвет - родитель):
1. Все дети находятся внизу родителя.

не понял: как родитель - слева, а дети - вдруг внизу??
Для верхнего дерева
Такая схема подчинения указана для левого дерева, для верхнего дерева аналогично:
1. Все дети находятся под родителем.
2. Все дети находятся слева от родителя.
3. Все дети находятся справа от родителя.

без картинки вообще не понял ))
_____________________________________
вообще, сам метод "узлов", представленный в статье, аналогичен тому, как в ПФ разбиваем лист на области и по "родителю" (подвал, шапка и т.д.) получаем нужную область ("узел") и выводим параметры и данные...
31. Осипов Сергей (fixin) 22.03.12 13:29
(30) дык есть же картинки. Есть... Они у вас не показываются что ле?
32. Алекс Ю (AlexO) 22.03.12 14:38
(31) fixin,
нет, ну эти есть ))
т.е. родители-дети связь такая: Верхний узел - Гр1 - Город1 ?
33. Осипов Сергей (fixin) 22.03.12 18:11
(32) Верхний узел корень.
У него есть дети: Гр1 и ОбщВерх

У Гр1 дети: 0 и Гр2.
ОбщВерх имеет детей: Город1, Город2, город3.

У Город1 дети: Склад1, Склад2
У Город2 дети: Склад1

Причем Склад1 города1 и Склад2 города2 - это разные узлы, хоть и называются одинаково.
34. Алекс Ю (AlexO) 23.03.12 12:26
(33) fixin,
- это "все дети внизу родителя."
а слева и справа для ВЕРХНЕГО дерева как будет тогда?
35. Осипов Сергей (fixin) 23.03.12 12:54
(34) уточни, ты о чем? выводимый отчет - в красной рамке, слева и сверху нарисована адресация ячеек, она в отчет не выводится.
для любой ячейки отчета можно получить адрес.
36. Алекс Ю (AlexO) 23.03.12 14:17
(35) fixin,
тьак меня вот это и смутило:
Такая схема подчинения указана для левого дерева, для верхнего дерева аналогично:
1. Все дети находятся под родителем.
2. Все дети находятся слева от родителя.
3. Все дети находятся справа от родителя.

- как выглядит аналогичная схема с левым и правым расположением потомков для верхнего дерева?
я, собственно, для этого и просил картинку такого расположения )
37. Alex (AlexBugs) 28.10.12 09:48
А как в таких отчетах получать итоги про группа товаров/контрагентов? :)Есть ли примеры реализации на 7.7?
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа