gifts2017

Сложные отчеты для управляемых форм с использованием СКД: просто. На примере отчета ABC анализ номенклатуры, клиентов для УТ11

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

Не очень часто, но все же, иногда приходится сталкиваться с необходимостью создания отчета, который очень сложно скомпоновать на "чистом" СКД, в этом случае альтернативой может являться следующая последовательность действий для получения итогового отчета: 1. при помощи одной схемы компоновки данных получить необходимую выборку данных; 2. запрограммировать (на языке 1С) некое преобразование этих данных; 3. при помощи другой схемы компоновки данных - произвести вывод необходимой информации в табличный документ. Как оказалось эта процедура не слишком сложная, хотя и немного запутанная.

Когда запрос в СКД перестает "помещаться в голове" становится слишком громоздким (ЗиУП конечно не в счетSmile) и ситуацию не получается исправить различными фичами самой СКД, то можно использовать данный метод.

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

Общая идея:

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

2. Обрабатываем полученную на первом этапе выборку используя язык программирования 1С (это обычно нагляднее чем сделать тоже самое, например, в запросе, к тому же в этом случае можно снабдить текст комментариями, не боясь того что их "проглотит" конструктор запросов).

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

Проиллюстрируем на примере содания отчета ABC анализ номенклатуры, клиентов, уже построенного по этой методике:

1. Данные получаем при помощи СКД запросом к регистру Выручка и себестоимость продаж, будем использовать два варианта настройки (группировка по Номенклатуре и группировка по Клиентам)

2. Производим ABC анализ по методу, реализованонму в ТиС 7.7, и классическому методу ABC анализа

3. Выводим результаты используя другую СКД. 

Начнем пример:

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

Здесь намеренно используется простой (неоптимизированный) запрос:

ВЫБРАТЬ РАЗРЕШЕННЫЕ

ВИСПОбороты.КоличествоОборот КАК Количество,
ВИСПОбороты.СуммаВыручкиОборот КАК Выручка,  
ВИСПОбороты.СебестоимостьОборот + ВИСПОбороты.СуммаДополнительныхРасходовОборот КАК СебестоимостьСДР,
ВИСПОбороты.АналитикаУчетаНоменклатуры.Номенклатура КАК Номенклатура,  
ВИСПОбороты.АналитикаУчетаНоменклатуры.Склад КАК Склад,  ВИСПОбороты.СуммаВыручкиОборот - ВИСПОбороты.СебестоимостьОборот -    ВИСПОбороты.СуммаДополнительныхРасходовОборот КАК Прибыль,  
ВЫБОР       
    КОГДА ВИСПОбороты.СебестоимостьОборот + ВИСПОбороты.СуммаДополнительныхРасходовОборот = 0 
    ТОГДА 0
    ИНАЧЕ 100 * (ВИСПОбороты.СуммаВыручкиОборот - ВИСПОбороты.СебестоимостьОборот - ВИСПОбороты.СуммаДополнительныхРасходовОборот)
    / (ВИСПОбороты.СебестоимостьОборот + 
ВИСПОбороты.СуммаДополнительныхРасходовОборот) 
    КОНЕЦ КАК Прибыльность,   ВИСПОбороты.АналитикаУчетаПоПартнерам.Партнер КАК Партнер,  
ВИСПОбороты.АналитикаУчетаПоПартнерам.Организация КАК Организация,  
ВИСПОбороты.АналитикаУчетаПоПартнерам.Контрагент КАК Контрагент

ИЗ  РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты(&НачалоПериода, &КонецПериода, Авто,
  
    НЕ АналитикаУчетаПоПартнерам.Партнер = ЗНАЧЕНИЕ(Справочник.Партнеры.НашеПредприятие)) КАК ВИСПОбороты

Ресурсы:

Параметры:

Настройки:

Т.е. построена обычная СКД для выборки данных.

2. Добавляем основную форму отчета (удаляем с нее все элементы (это важно!)), и заново добавляем на нее Пользователские настройки компоновщика настроек отчета:

(если не отображаются названия колонок этой таблицы - включаем их отображение).

Наполняем содержимым форму (блок ABC):

Добавляем команду Сформировать и формируем соотвествующую кнопку.

3. Создаем СКД для вывода информации в табличный документ (добавляем соответствующий макет), описание полей:

 

Вычисляемые поля:

Ресурсы:

Вариант настроек БезГрупп:

Вариант настроек Классы:

 

Т.е. это тоже вполне обычная СКД, которая "заточена" под конкретные колонки таблицы значений, которую мы будем в нее передавать.

3. Наполняем содержимым команду Сформировать:

&НаКлиенте
Процедура Сформировать(Команда)  
    СформироватьНаСервере();
КонецПроцедуры

&НаСервере
Процедура СформироватьНаСервере()
   ОтчетОбъект = ДанныеФормыВЗначение(Отчет, Тип("ОтчетОбъект"));

   //Первый этап: получаем данные при помощи СКД

   СКД = ОтчетОбъект.СхемаКомпоновкиДанных;                      
   ПользовательскиеНастройки = Отчет.КомпоновщикНастроек.ПользовательскиеНастройки; //Используем пользовательские настройки с формы отчета;
   Отчет.КомпоновщикНастроек.ЗагрузитьНастройки(СКД.ВариантыНастроек[ОбъектАнализа].Настройки); //Используем нужный вариант
   Настройки = Отчет.КомпоновщикНастроек.Настройки;// Тут уже готовый вариант настроек
   //При необходимости корректируем поле группировки в соответствии с настройками
   //Это нужно когда выбрано значение СвойстоОбъектаАнализа
   //и анализ производится на основании значения реквизита соответствующего справочника
   Если ЗначениеЗаполнено(СвойствоОбъектаАнализа) Тогда
       ЭлементыПолейГруппировки = Настройки.Структура[0].ПоляГруппировки.Элементы;
       ЭлементыПолейГруппировки.Очистить();
       НовыйЭлементГруппировки = ЭлементыПолейГруппировки.Добавить(Тип("ПолеГруппировкиКомпоновкиДанных"));
       НовыйЭлементГруппировки.Поле = Новый ПолеКомпоновкиДанных(?(ОбъектАнализа = "Клиенты", "Партнер", ОбъектАнализа) + "." + СвойствоОбъектаАнализа);
       НовыйЭлементГруппировки.Использование = Истина;
  КонецЕсли;    
  //Копируем пользовательские настройки в основные настройки отчета
  Для Каждого ЭлементПользовательскихНастроек Из ПользовательскиеНастройки.Элементы Цикл
      Если ТипЗнч(ЭлементПользовательскихНастроек) = Тип("ЗначениеПараметраНастроекКомпоновкиДанных") Тогда
         ЭлементНастройки = Настройки.ПараметрыДанных.Элементы.Найти(ЭлементПользовательскихНастроек.Параметр);
         ЗаполнитьЗначенияСвойств(ЭлементНастройки, ЭлементПользовательскихНастроек);
      ИначеЕсли ТипЗнч(ЭлементПользовательскихНастроек) = Тип("ЭлементОтбораКомпоновкиДанных") Тогда
         ЗаполнитьЭлементОтбораПоИД(Настройки.Отбор.Элементы, ЭлементПользовательскихНастроек); //Процедура определена в модуле формы
      КонецЕсли;
  КонецЦикла;
  //^^^^^^ все готово, чтобы получить исходную выборку
  //Следующие 8 строк - получение выборки в ТаблицуЗначений ТЗ:
  КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
  МакетКомпоновки = КомпоновщикМакета.Выполнить(СКД, Настройки, , , Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));
  ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных;
  ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновки, , );
  ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;  
  ТЗ = Новый ТаблицаЗначений;  
  ПроцессорВывода.УстановитьОбъект(ТЗ);  
  ПроцессорВывода.Вывести(ПроцессорКомпоновкиДанных, Ложь);
  //Конец Первого этапа: Выборка уже есть в ТЗ

  //Второй этап: Необходимые преобразования, для последующего вывода на экран
  //Тут реализуется алгоритм расчета для ABC анализа, взятый из ТиС 7.7, и классический ABC анализ
  //Здесь: Последняя сторка ТЗ - строка итогов, запомним числовые значения итогов в Структуре  
  Итоги = Новый Структура();  
  Для Каждого КолонкаТЗ Из ТЗ.Колонки Цикл
     ЗначениеЭлементаТЗ = ТЗ[ТЗ.Количество() - 1][КолонкаТЗ.Имя];
     Если Не ТипЗнч(ЗначениеЭлементаТЗ) = Тип("Число") Тогда Продолжить; КонецЕсли;
     Итоги.Вставить(КолонкаТЗ.Имя, ЗначениеЭлементаТЗ);
  КонецЦикла;
  ТЗ.Удалить(ТЗ.Количество() - 1); //Удалим строку итогов из ТЗ
  ТЗ.Колонки.Добавить("ПроцентОтОбщего"); //Добавим новые колонки в ТЗ
 
ТЗ.Колонки.Добавить("Объект");    
  ПоказательСтрокой = ?(Показатель = "Выручка по отгрузке", "Выручка", ?(Показатель = "Прибыль по отгрузке", "Прибыль", "Прибыльность"));
  ОбъектАнализаСтрокой = ?(ОбъектАнализа = "Клиенты", "Партнер", ОбъектАнализа) + СокрЛП(СвойствоОбъектаАнализа);
  Для Каждого СтрокаТЗ Из ТЗ Цикл
     СтрокаТЗ.ПроцентОтОбщего = 100 * СтрокаТЗ[ПоказательСтрокой] / Итоги[ПоказательСтрокой];
     СтрокаТЗ.Объект = СтрокаТЗ[ОбъектАнализаСтрокой];  
  КонецЦикла;    
  //Обработка  
  Если СпособАнализа = 0 Тогда
     ПорядокСтрокой = ?(Порядок = "убывания", "Убыв", "Возр");
     ТЗ.Сортировать(ПоказательСтрокой + " " + ПорядокСтрокой);
     КоличествоНужных = Окр(ТЗ.Количество() * Мин(ПроцентОбъектов, 100) / 100);
     Для Индекс = КоличествоНужных + 1 По ТЗ.Количество() - 1 Цикл
        ТЗ[Индекс].Объект = "";
     КонецЦикла;  
  ИначеЕсли СпособАнализа = 1 Тогда
     ПорядокСтрокой = "Убыв";
     ТЗ.Сортировать(ПоказательСтрокой + " " + ПорядокСтрокой);
     ОставитьСумму = Итоги[ПоказательСтрокой] * Мин(ПроцентСумм, 100) / 100;
     НакопленоСумма = 0;
     Для Каждого СтрокаТЗ Из ТЗ Цикл
       Если НакопленоСумма > ОставитьСумму Тогда
         СтрокаТЗ.Объект = "";
       КонецЕсли;
       НакопленоСумма = НакопленоСумма + СтрокаТЗ[ПоказательСтрокой];
     КонецЦикла;
  Иначе
     ПорядокСтрокой = "Убыв";
     ТЗ.Сортировать(ПоказательСтрокой + " " + ПорядокСтрокой);
     ТЗ.Колонки.Добавить("Класс");
     НакопленоСумма = 0;
     СуммаA = Итоги[ПоказательСтрокой] * ПроцентA / 100;
     СуммаAB = СуммаA + Итоги[ПоказательСтрокой] * ПроцентB / 100;
     СуммаABC = СуммаAB + Итоги[ПоказательСтрокой] * ПроцентC / 100;   //Все остальное D
     ТекущийКласс = "A";
     Для Каждого СтрокаТЗ Из ТЗ Цикл
       СтрокаТЗ.Класс = ТекущийКласс;
       НакопленоСумма = НакопленоСумма + СтрокаТЗ[ПоказательСтрокой];
       Если ТекущийКласс = "A" И НакопленоСумма > СуммаA Тогда ТекущийКласс = "B";
       ИначеЕсли ТекущийКласс = "B" И НакопленоСумма > СуммаAB Тогда ТекущийКласс = "C";
       ИначеЕсли ТекущийКласс = "C" И НакопленоСумма > СуммаABC Тогда ТекущийКласс = "D";
       КонецЕсли;
     КонецЦикла;
  КонецЕсли;
  //Конец Второго этапа: ТЗ преобразована и готова к выводу на экран.

  //Третий этап: Вывод через СКД в табличный документ, расположенный на форме 
  Результат.Очистить(); //Чистим Табличный документ от старых данных   
  ВнешниеНаборыДанных = Новый Структура("ТЗ", ТЗ); //Готовим внешний набор который передадим СКД
  СКДВ = ОтчетОбъект.ПолучитьМакет("СКДДляВывода"); //Получаем СКД Для Вывода из макетов Отчета;
  //Выбор нужного варианта настроек:
  НастройкиВ = СКДВ.ВариантыНастроек[?(СпособАнализа = 2, "Классы", "БезГрупп")].Настройки; 
  //Настройка варианта графического отображения:
  НастройкиВ.Структура[1].Использование = (Диаграмма = "Гистограмма");
  НастройкиВ.Структура[1].Выбор.Элементы[0].Поле = Новый ПолеКомпоновкиДанных(ПоказательСтрокой);
  НастройкиВ.Структура[2].Использование = (Диаграмма = "Круговая");
  НастройкиВ.Структура[2].Выбор.Элементы[0].Поле = Новый ПолеКомпоновкиДанных(ПоказательСтрокой);    
  //Настройка сортировки:  
  ЭлементыСортировки = НастройкиВ.Порядок.Элементы;
  ЭлементыСортировки.Очистить();
  ЭлементСортировки = ЭлементыСортировки.Добавить(Тип("ЭлементПорядкаКомпоновкиДанных"));
  ЭлементСортировки.Поле = Новый ПолеКомпоновкиДанных(ПоказательСтрокой);
  ЭлементСортировки.ТипУпорядочивания = НаправлениеСортировкиКомпоновкиДанных[ПорядокСтрокой];
  ЭлементСортировки.Использование = Истина;
  //Чтобы в итоговом отчете была Расшифровка, придется предпринять ряд шагов:

  ДанныеРасшифровкиКомпоновкиДанныхВ = Новый ДанныеРасшифровкиКомпоновкиДанных; //Сюда будут складываться данные для расшифровки
  КомпоновщикМакетаВ = Новый КомпоновщикМакетаКомпоновкиДанных;
  МакетКомпоновкиВ = КомпоновщикМакетаВ.Выполнить(СКДВ, НастройкиВ, ДанныеРасшифровкиКомпоновкиДанныхВ); //не забываем: данные для расшифровки
  ПроцессорКомпоновкиДанныхВ = Новый ПроцессорКомпоновкиДанных;
  ПроцессорКомпоновкиДанныхВ.Инициализировать(МакетКомпоновкиВ, ВнешниеНаборыДанных, ДанныеРасшифровкиКомпоновкиДанныхВ, Истина); 
  //^^^^ Предпоследний параметр: данные для расшифровки
  //^^^^Последний параметр - возможность использования внешних функций в компоновке, хотя они и не используются
  //Следующие 4 строки - вывод ТЗ в табличный документ:
  ПроцессорВыводаВ = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
  ПроцессорВыводаВ.УстановитьДокумент(Результат);
  ПроцессорВыводаВ.ОтображатьПроцентВывода = Истина;
  ПроцессорВыводаВ.Вывести(ПроцессорКомпоновкиДанныхВ);  
  //Передача через элемент формы, т.е. запоминаем адрес хранилища в параметре формы ДанныеРасшифровки
  ДанныеРасшифровки = ПоместитьВоВременноеХранилище(ДанныеРасшифровкиКомпоновкиДанныхВ, ЭтаФорма.УникальныйИдентификатор);
  //Избавление от надписи... скорее всего можно побороть как-то еще.
  ОтображениеСостояния = ЭтаФорма.Элементы.Результат.ОтображениеСостояния;
 
 ОтображениеСостояния.Видимость = Ложь;
  ОтображениеСостояния.ДополнительныйРежимОтображения = ДополнительныйРежимОтображения.НеИспользовать;
  //Установка текущей страницы
  Элементы.Страницы.ТекущаяСтраница = Элементы.Результаты;
  //Конец третьего этапа, Табличный документ заполнен результатами расчета

КонецПроцедуры

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

Скачать файлы

Наименование Файл Версия Размер
ABCАнализНоменклатурыИлиКлиентов.erf 133
.erf 20,68Kb
30.10.13
133
.erf 1.007 20,68Kb Скачать

См. также

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

Комментарии

1. ivanov660 ivanov660 (ivanov660) 12.08.13 10:18
В некоторых случаях правильнее и проще будет использовать данные (ТЗ) не компоновки, а результата запроса. В этом случае также нет необходимости описывать поля как отметил автор, а достаточно использовать выгруженную таблицу как внешний набор данных для дальнейшей обработки. Хотя такой подход и применяется 1С-никами, к примеру, в УТ работа с источниками планирования.
2. mxm2 mxm2 (mxm2) 12.08.13 10:35
(1) ivanov660, Вы имеете ввиду первоначальную выборку данных? Если да, то в случае использования запроса, придется прописывать условия отборов (на форме располагать элементы), итоги и т.д. Что проще - спорный вопрос.
3. Яков Коган (Yashazz) 13.08.13 14:24
Я очень всяко делаю. Иногда СКД служит лишь для объявления компоновщика (отбор/сортировка на интерфейсе), иногда готовит полуфабрикат, выгоняя его в коллекцию нужного типа; иногда одна СКД вложена в другую.
Но в последнее время, с расширением возможностей языка выражений СКД, работы с коллекциями в вычисляемых полях и прочего, склоняюсь к мысли, что программная "прокладка" нужна всё реже. И вам советую.
Например, http://langslab.com/ebooks/skd/dcs-ch2/dcs-ex17 - покурите "ВычислитьВыражение" и его варианты.
Puk2; ViteG; ZanZiBar; Katik; zqzq; mxm2; +6 Ответить 1
4. mxm2 mxm2 (mxm2) 13.08.13 14:51
(3) Yashazz,
Например, http://langslab.com/ebooks/skd/dcs-ch2/dcs-ex17 - покурите "ВычислитьВыражение" и его варианты.

Спасибо за ссылку, весьма полезная инфа, раньше и не натыкался на такую, хотя и предполагал, что что-то подобное должно быть.
5. Александр Чернышов (HEKPOH) 13.08.13 19:47
Тоже использую описанную автором методику :)
6. Sergey A. (spaminfostart) 09.09.13 13:24
Понравилось оформление списка внизу формы. Реализовал у себя )
Прикрепленные файлы:
7. Sergey A. (spaminfostart) 11.09.13 10:45
У меня ошибка:
{Отчет.ABCАнализНомИлиКлиентов.Форма.ФормаОтчета.Форма(136)}: Поле объекта не обнаружено (Поле)
НастройкиВ.Структура[1].Выбор.Элементы[0].Поле = Новый ПолеКомпоновкиДанных(ПоказательСтрокой);

Какие поля у Вас установлены в Диаграммах?
8. mxm2 mxm2 (mxm2) 11.09.13 11:09
(7) spaminfostart, Поле задается динамически, в зависимости от выбранного показателя, в настройках графического представления предварительно указан один из возможных показателей, кажется это Выручка.
9. Sergey A. (spaminfostart) 11.09.13 11:10
Я так понял там нужно установить ЛЮБОЕ одно поле. А в коде мы всё равно его меняем.
10. mxm2 mxm2 (mxm2) 11.09.13 12:25
(9) spaminfostart, просто это гораздо проще чем его добавить в нулевую коллекцию...
11. Sergey A. (spaminfostart) 11.09.13 12:46
(105) Gazza, предлагаю упомянуть об этом в описании ;)
12. Sergey A. (spaminfostart) 12.09.13 08:13
(105) Gazza, если не затруднит, выложите пож модуль процедуры ЗаполнитьЭлементОтбораПоИД и другие, если есть связанные. А то я пока не могу скачать обработку.
13. Sergey A. (spaminfostart) 12.09.13 10:23
(105) Gazza, или ещё лучше - вышлите пож обработку по почте:
14. Sergey A. (spaminfostart) 17.09.13 14:50
16. Павел (sidka89) 24.10.13 13:26
спасибо за проделанную работу
17. Андрей (AnKonAlm) 16.02.14 14:21
18. Андрей Грушевский (ZanZiBar) 10.04.14 08:35
Спасибо огромное. Очень помогли))
20. Vlad Vlad (KVD77) 17.09.14 17:13
Тоже использую описанную автором методику, только в первом СКД поля описываю немного иначе, пример:

ВЫБРАТЬ
Значение(Справочник.<ИмяСправочника>.ПустаяСсылка) КАК Спр,
"" КАК СпрПолноеИмя,
Ложь КАК Выбран,
Значение(Документ.<ИмяСправочника>.ПустаяСсылка) КАК Док,
....
и т.д. по аналогии
21. andrey (c_andrey) 01.04.16 13:15
Скажите в обычном приложение этот отчет работать будет?
22. mxm2 mxm2 (mxm2) 01.04.16 15:04
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа