Как получить процент нарастающим итогом и настроить АВС-классификацию в СКД?

Программист 1С v8.3 (Управляемые формы) Управленческий учет IT и автоматизация бизнеса
← На главную

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

Решение 1: Использование встроенных функций языка выражений СКД

В современных версиях платформы 1С для получения процентов и классификации нарастающим итогом писать сложный код в общих модулях не обязательно. Все вычисления можно выполнить встроенным языком выражений СКД на вкладках «Вычисляемые поля» и «Ресурсы».

  1. Сумма нарастающим итогом.
    Осуществляется с помощью функции ВычислитьВыражение, где указываются границы расчета — от первой записи группировки до текущей. Формула ресурса будет выглядеть так:
    ВычислитьВыражение("Сумма(СуммаОборот)", , , "Первая", "Текущая")
  2. Процент нарастающим итогом.
    Для получения процента значение нарастающего итога необходимо разделить на сумму общего итога. Рассмотрим пример такого выражения:
    ВычислитьВыражение("Сумма(СуммаОборот)", , , "Первая", "Текущая") / ВычислитьВыражение("Сумма(СуммаОборот)", "ОбщийИтог") * 100
  3. Присвоение класса АВС.
    Если расчет процента выделить в отдельное вычисляемое поле (назовем его, например, ПроцентНарастающий), то класс можно вывести через стандартный условный оператор:
    ВЫБОР КОГДА ПроцентНарастающий <= 80 ТОГДА "A" КОГДА ПроцентНарастающий <= 95 ТОГДА "B" ИНАЧЕ "C" КОНЕЦ

Важные ограничения платформы при расчетах через СКД:

Во-первых, запрещено использование вложенных агрегатных функций (когда одна агрегатная функция берется от результата другой). Чтобы избежать ошибок при расчете сложных долей в ресурсах, промежуточные вычисления следует выносить на уровень «Вычисляемых полей».

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

Решение 2: Использование собственной функции в общем модуле

Иногда логика классификации требует более сложной программной обработки. В таком случае можно написать собственную функцию в общем модуле и вызывать её из выражений СКД. Проанализируем пример реализации функции АВС-классификации, которая передается через параметр типа ДанныеГрупповойОбработкиКомпоновкиДанных.

Добавим в общий модуль следующий код:


Функция ABCКлассификация(Данные) Экспорт
    Перем ТаблицаЗначений;

    Если Данные = Null Тогда
        Возврат Null;
    КонецЕсли;

    Если ТипЗнч(Данные) <> Тип("ДанныеГрупповойОбработкиКомпоновкиДанных") Тогда
        ВызватьИсключение "В функцию ABCКлассификация можно передавать только объект типа ДанныеГрупповойОбработкиКомпоновкиДанных";
    КонецЕсли;

    Если Не Данные.ВременныеДанныеОбработки.Свойство("ABCКлассификацияТаблицаЗначений", ТаблицаЗначений) Тогда
        // выполним расчет классификации один раз при первом вызове
        // а потом запомним выполненный расчет и дальше будем использовать его
        ТаблицаЗначений = Данные.Данные.Скопировать();
        ТаблицаЗначений.Колонки.Добавить("Номер", Новый ОписаниеТипов("Число"));
        
        Номер = 0;
        ОбщаяСумма = 0;
        Для Каждого СтрокаТаблицыЗначений Из ТаблицаЗначений Цикл
            СтрокаТаблицыЗначений.Номер = Номер;
            Номер = Номер + 1;
            Если СтрокаТаблицыЗначений[0] <> NULL Тогда
                ОбщаяСумма = ОбщаяСумма + СтрокаТаблицыЗначений[0];
            КонецЕсли;
        КонецЦикла;

        ТаблицаЗначений.Сортировать(ТаблицаЗначений.Колонки[0].Имя + " Убыв");
        ТаблицаЗначений.Индексы.Добавить("Номер");
        
        НакопленнаяСумма = 0;
        ИндексКлассаA = Неопределено;
        ИндексКлассаB = Неопределено;
        
        Для Каждого СтрокаТаблицыЗначений Из ТаблицаЗначений Цикл
            Если СтрокаТаблицыЗначений[0] <> NULL Тогда
                НакопленнаяСумма = НакопленнаяСумма + СтрокаТаблицыЗначений[0];
            КонецЕсли;
            
            Если ОбщаяСумма = 0 Тогда
                Процент = 1;
            Иначе
                Процент = НакопленнаяСумма / ОбщаяСумма;
            КонецЕсли;
            
            Если Процент > 0.75 Тогда
                Если ИндексКлассаA = Неопределено Тогда
                    ИндексКлассаA = ТаблицаЗначений.Индекс(СтрокаТаблицыЗначений);
                ИначеЕсли Процент > 0.90 Тогда
                    Если ИндексКлассаB = Неопределено Тогда
                        ИндексКлассаB = ТаблицаЗначений.Индекс(СтрокаТаблицыЗначений);
                    КонецЕсли;
                    Прервать;
                КонецЕсли;
            КонецЕсли;
        КонецЦикла;

        Данные.ВременныеДанныеОбработки.Вставить("ABCКлассификацияТаблицаЗначений", ТаблицаЗначений);
        Данные.ВременныеДанныеОбработки.Вставить("ABCКлассификацияИндексКлассаA", ИндексКлассаA);
        Данные.ВременныеДанныеОбработки.Вставить("ABCКлассификацияИндексКлассаB", ИндексКлассаB);
    КонецЕсли;

    Если Данные.ТекущийЭлемент = Неопределено Тогда
        // Итог по группировке.
        Возврат Null;
    Иначе
        Строка = ТаблицаЗначений.Найти(Данные.Данные.Индекс(Данные.ТекущийЭлемент), "Номер");
        Если Строка = Неопределено Тогда
            Возврат Null;
        Иначе
            Индекс = ТаблицаЗначений.Индекс(Строка);
            Если Индекс <= Данные.ВременныеДанныеОбработки.ABCКлассификацияИндексКлассаA Тогда
                Возврат 1; // Класс A
            ИначеЕсли Индекс <= Данные.ВременныеДанныеОбработки.ABCКлассификацияИндексКлассаB Тогда
                Возврат 2; // Класс B
            Иначе
                Возврат 3; // Класс C
            КонецЕсли;
        КонецЕсли;
    КонецЕсли;
КонецФункции

В выражении компоновки (например, в ресурсе или пользовательском поле) для получения класса мы будем использовать следующее выражение:

ABCКлассификация(ГрупповаяОбработка("Сумма(СуммаОборот)"))

Ошибки при использовании внешних функций в СКД

При попытке использовать собственную функцию разработчики часто сталкиваются с ошибкой: «Выражение не может быть вычислено». Выясним причину такого поведения. СКД умеет работать с внешними функциями, но для этого должны строго выполняться следующие условия:

  1. Функция должна располагаться в общем модуле. У этого модуля должны стоять флаги Сервер и/или Клиент (управляемое приложение).
  2. Функция должна быть объявлена с ключевым словом Экспорт.
  3. Если у общего модуля не установлен флаг Глобальный, вызов функции по имени напрямую приведет к ошибке. К таким функциям в выражениях СКД нужно обращаться строго через имя модуля, например: ИмяОбщегоМодуля.ABCКлассификация(...). Если модуль глобальный, можно обращаться напрямую.
  4. В случае, если отчет реализован как внешний файл, вызов любых экспортных функций может быть заблокирован системой безопасности 1С, если отчет исполняется в безопасном режиме.

Используя рассмотренные подходы, вы сможете гибко настроить вычисление нарастающих итогов и выполнить АВС-классификацию в любом отчете на базе СКД.

← На главную