Каталог решений - АВС-анализ и табличное программирование

АВС-анализ и табличное программирование

АВС-анализ и табличное программирование

В наличии

Представлен простейший алгоритм решения задачи АВС-анализа. На данном примере продемонстрирован метод табличного программирования, описанный Стивом Макконнеллом в книге «Совершенный код. Мастер-класс».

Категория:

Описание

Мне бы только
мой крошечный вклад внести
За короткую жизнь сплести
Хотя бы ниточку шёлка.

песня "Шелкопряд", автор Flёur

 

Когда знаешь — все просто

Николай Павлов, портал "Планета Эксель"

 

Всем привет!

Книга Стива Макконнелла «Совершенный код. Мастер-класс» является одной из настольных книг разработчика. До сих пор никто не адаптировал алгоритмы и приемы из книги под 1С-программирование. Про рекурсию и циклы все понятно. Про применение табличного программирования, описанного в главе 18 "Табличные методы", уже поймет не каждый. Я привожу примеры табличного программирования для всеобщего понимания такого приема разработки. С помощью этого способа можно элегантно писать алгоритмы: они становятся на порядок меньше, код становится легче масштабировать и адаптировать.

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

Алгоритм возьму отсюда ABC-анализ: характеристика, особенности и применение. Автор Никита Шуравин:

1. Выгружаем данные для анализа — показатели сумму продаж и прибыль за период. Тут же суммируем общую сумму продаж и общую прибыль. 

2. Рассчитываем столбец «Процент, %» — доля в общей выручке и отдельно доля в общей прибыли.

3. Делаем сортировку по долям или по величине показателя — от большего к меньшему. Сначала обрабатываем показатель "Сумма продаж", затем отдельно обрабатываем показатель "Прибыль".

4. Группируем строки с помощью нарастающего итога по столбцу "Процент, %", пока общая сумма по столбцу не приблизится к соответствующему заданному диапазону — сначала для категории А, затем для категории В и т.д.

В классической формулировке задаются три категории А, В и С. Если вы ограничитесь двумя категориями, то получите правило Парето. В общем случае, бывают ситуации, когда разбить на три категории множество объектов не получается — тогда можно выделить 4 категории для анализа. К слову сказать, клиентов я анализировал по 4 категориям в статье Про деньги фрилансера.

 

 

5. Сохраняем рассчитанные сведения в регистр сведений и выводим их по необходимости в АРМ или документах.

 

 

Пример разработан на платформе 1С:Предприятие 8.3 (8.3.20.2184), на демо-конфигурации "Управление торговлей", редакция 10.3 (10.3.73.1). 

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

Пример 1. Сравните вывод категорий в список товаров — как это было бы запрограммировано изначально:

Если Не ПустаяСтрока(Категория) Тогда
						
	Если Категория = "A" Тогда 
       Стр.Ячейки[Кат].ЦветФона = Новый Цвет(142,235,142); //зеленоватый
    ИначеЕсли Категория = "B" Тогда
		Стр.Ячейки[Кат].ЦветФона = Новый Цвет(255,255,92); //желтоватый
	Иначе
		Стр.Ячейки[Кат].ЦветФона = Новый Цвет(255,204,153); //красноватый
	КонецЕсли;

Иначе
	//белый цвет по умолчанию						
КонецЕсли;

Согласно табличному методу этот код выглядит вот так:

//заранее в модуле формы задаем таблицу соответствий: категория - цвет поля
СоответствиеЦветовКатегорий = Новый Соответствие;
СоответствиеЦветовКатегорий.Вставить("", Новый Цвет(255,255,255));
СоответствиеЦветовКатегорий.Вставить("A", Новый Цвет(142,235,142));
СоответствиеЦветовКатегорий.Вставить("B", Новый Цвет(255,255,92));
СоответствиеЦветовКатегорий.Вставить("Другие", Новый Цвет(255,204,153));
...
//Далее в алгоритме прорисовки пишем такой код
Категория = Выборка[Кат]; //определяем категорию 					
Стр.Ячейки[Кат].УстановитьТекст(Категория);	//выводим в ячейку категорию				
ЦветПоля = СоответствиеЦветовКатегорий.Получить(Категория); //определяем цвет поля  					

//задаем цвет поля
Стр.Ячейки[Кат].ЦветФона = ?(ЦветПоля<>Неопределено, ЦветПоля, СоответствиеЦветовКатегорий.Получить("Другие"));
						

Пример 2. Использование разрядности для отображения на форме значений процентов. Как это можно запрограммировать обычным способом:

ЭлементыФормы.ТекРезультат.Колонки.ПроцентСуммыПродаж.Формат = Формат("ЧЦ=19; ЧДЦ=" + РазрядностьПроцентов);
ЭлементыФормы.ТекРезультат.Колонки.ПроцентПрибыли.Формат = Формат("ЧЦ=19; ЧДЦ=" + РазрядностьПроцентов);

Как это выглядит с использованием табличного метода:

//Заранее в модуле формы задаем коллекцию колонок Процентов
МассивПроцентов = Новый Массив;
МассивПроцентов.Добавить("ПроцентСуммыПродаж");
МассивПроцентов.Добавить("ПроцентПрибыли");
...

//Далее в необходимом месте прописываем перебор колонок
Для Каждого Процент Из МассивПроцентов Цикл
	ЭлементыФормы.ТекРезультат.Колонки[Процент].Формат = Формат("ЧЦ=19; ЧДЦ=" + РазрядностьПроцентов);
КонецЦикла;

Пример 3. Как описать проверку на NULL или 0 итоговой суммы показателя? Далее показано как было и как стало:

Если Выборка.Следующий() Тогда
		
	ОбщаяСуммаПродаж 	= Выборка.СуммаПродаж;
	ОбщаяПрибыль 		= Выборка.Прибыль;

    //было 
    Если ОбщаяСуммаПродаж = NULL ИЛИ ОбщаяСуммаПродаж = 0 Тогда 
        ...
    КонецЕсли;
    
    Если ОбщаяПрибыль = NULL ИЛИ ОбщаяПрибыль = 0 Тогда
        ...
	КонецЕсли;
	
	//стало
	Если МассивНекорректныхЗначений.Найти(ОбщаяСуммаПродаж)<>Неопределено Тогда
	   ...		
	КонецЕсли;
		
	Если МассивНекорректныхЗначений.Найти(ОбщаяПрибыль)<>Неопределено Тогда
	   ...		
	КонецЕсли;

КонецЕсли;

//При этом заранее задаем в модуле формы коллекцию некорректных итоговых значений
МассивНекорректныхЗначений = Новый Массив;
МассивНекорректныхЗначений.Добавить(NULL);
МассивНекорректныхЗначений.Добавить(0);

Для 3-его примера было для двух показателей две проверки — всего четыре. Стало в итоге две проверки.  

При этом мы видим повторение проверок. Поэтому мы заводим очередную коллекцию МассивПоказателей и переписываем код следующим образом:

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

При этом заранее задаем коллекцию:

МассивПоказателей = Новый Массив;
МассивПоказателей.Добавить("СуммаПродаж");
МассивПоказателей.Добавить("Прибыль");

СоответствиеИменПоказателей = Новый Соответствие;
СоответствиеИменПоказателей.Вставить("СуммаПродаж", "по сумме продаж");
СоответствиеИменПоказателей.Вставить("Прибыль", "по прибыли");

Пример 4. Сам алгоритм из п.4 выше полностью построен на использовании табличного метода — когда мы заранее задаем коллекции, таблицы соответствия и далее их используем при обходе:

Процедура РассчитатьКатегории()
	
	Для Каждого ИмяДиапазона Из МассивТаблицДиапазонов Цикл
		
		ИмяКолонкиПоказатель 	= СоответствиеКолонокПоказатель.Получить(ИмяДиапазона);
		ИмяКолонкиПроцент 		= СоответствиеКолонокПроцент.Получить(ИмяДиапазона);
		ИмяКолонкиКатегория 	= СоответствиеКолонокКатегория.Получить(ИмяДиапазона);
		
		СоответствиеГраниц = Новый Соответствие; 	
		МассивГраниц = Новый Массив;
				
		Для Каждого Стр Из ЭтотОбъект[ИмяДиапазона] Цикл
			
			Граница = Стр.До;
			МассивГраниц.Добавить(Граница); 
			СоответствиеГраниц.Вставить(Граница, Стр.Категория);
			
		КонецЦикла;

		//теперь все показатели упорядочиваем		
		ТекРезультат.Сортировать(ИмяКолонкиПоказатель + " Убыв");		
		
		... 		
			
		К = 0; 
        Сумма = 0;
		 
		Для Каждого Граница Из МассивГраниц Цикл
			
			...
			
			Для Инд = К По ТекРезультат.Количество()-1 Цикл
				
				Стр = ТекРезультат[Инд];
				Процент = Стр[ИмяКолонкиПроцент];
				
				...				 
				
				Сумма = Сумма + Процент;				
				Если Сумма <= Граница Тогда
					Стр[ИмяКолонкиКатегория] = СоответствиеГраниц.Получить(Граница);
				Иначе 
					...
				КонецЕсли;		
				
			КонецЦикла;  
			
		КонецЦикла; 	
				
	КонецЦикла;
	
КонецПроцедуры

...

//Заранее в модуле формы задаем коллекции и таблицы соответствия
МассивТаблицДиапазонов = Новый Массив;
МассивТаблицДиапазонов.Добавить("ДиапазоныСуммыПродаж");
МассивТаблицДиапазонов.Добавить("ДиапазоныПрибыли");

СоответствиеИменДиапазонов = Новый Соответствие;
СоответствиеИменДиапазонов.Вставить("ДиапазоныСуммыПродаж", "по сумме продаж");
СоответствиеИменДиапазонов.Вставить("ДиапазоныПрибыли", "по прибыли");

СоответствиеКолонокПроцент = Новый Соответствие;
СоответствиеКолонокПроцент.Вставить("ДиапазоныСуммыПродаж", "ПроцентСуммыПродаж");
СоответствиеКолонокПроцент.Вставить("ДиапазоныПрибыли", "ПроцентПрибыли");

СоответствиеКолонокКатегория = Новый Соответствие;
СоответствиеКолонокКатегория.Вставить("ДиапазоныСуммыПродаж", "КатегорияПоСуммеПродаж");
СоответствиеКолонокКатегория.Вставить("ДиапазоныПрибыли", "КатегорияПоПрибыли");

СоответствиеКолонокПоказатель = Новый Соответствие;
СоответствиеКолонокПоказатель.Вставить("ДиапазоныСуммыПродаж", "ПоказательСуммаПродаж");
СоответствиеКолонокПоказатель.Вставить("ДиапазоныПрибыли", "ПоказательПрибыль");

В публикации представлена внешняя обработка, при этом для хранения категорий в регистре сведений вам потребуется создать регистр сведений (см. рис. 3 в ленте изображений). 

 

 

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

 

has been added to your cart:
Оформление заказа