В практике автоматизации торговых операций на платформе 1С часто возникает задача реализации сложных систем лояльности. Нередко руководство ставит задачу внедрения «ступенчатых» или последовательных скидок, когда каждая последующая скидка применяется не к начальной цене товара, а к сумме, уже уменьшенной предыдущей скидкой. При этом в системе может существовать огромный массив исторического кода, завязанного на один единственный реквизит ПроцентСкидки. Чтобы не переписывать тысячи строк кода в универсальных модулях, наиболее рациональным решением является расчет так называемого «эффективного процента».
В данной статье мы подробно разберем математическую логику этого процесса, рассмотрим программную реализацию на языке 1С и проанализируем основные риски, связанные с точностью вычислений.
Прежде всего, проанализируем, почему нельзя просто складывать проценты. Рассмотрим классический пример: у товара есть основная скидка 5% и дополнительная скидка 2%. Если их просто сложить, мы получим 7%. Однако при последовательном применении логика иная:
Если бы мы применили прямую скидку 7%, результат был бы: 1000 - 7% = 930 руб. Разница в 1 рубль кажется незначительной для одной единицы товара, но в масштабах крупного ритейла она превращается в огромные суммы. Наша задача — найти такой процент Х, который при однократном применении к 1000 даст 931.
Формула расчета: 1000 - Х% = 931 => Х = 6.9%. Таким образом, последовательные скидки 5% и 2% эквивалентны одной скидке в 6,9%.
Рассмотрим ситуацию, когда дополнительные скидки хранятся в табличной части карточки товара или документа. Нам необходимо обойти эту таблицу и последовательно рассчитать остаток суммы, чтобы в итоге выйти на итоговый коэффициент. Разберем по шагам, как это реализовать в коде:
Функция РассчитатьЭффективныйПроцент(ИсходнаяЦена, ТаблицаСкидок)
Перем ТекущаяЦена;
ТекущаяЦена = ИсходнаяЦена;
// Последовательно применяем каждую скидку из таблицы
Для Каждого СтрокаСкидки Из ТаблицаСкидок Цикл
// Рассчитываем сумму скидки от текущей (уже уменьшенной) цены
СуммаЭтойСкидки = ТекущаяЦена * (СтрокаСкидки.Процент / 100);
ТекущаяЦена = ТекущаяЦена - СуммаЭтойСкидки;
КонецЦикла;
// Вычисляем итоговый эффективный процент
Если ИсходнаяЦена = 0 Тогда
Возврат 0;
КонецЕсли;
ЭффективныйПроцент = (1 - ТекущаяЦена / ИсходнаяЦена) * 100;
Возврат ЭффективныйПроцент;
КонецФункции
Этот метод интуитивно понятен, но он требует наличия некой «базовой цены» для расчета. Однако, как правильно заметили эксперты, математически итоговый процент не зависит от самой цены, он зависит только от коэффициентов скидок.
Проанализируем более изящный способ, который не требует оперировать суммами. Каждая скидка d уменьшает цену до уровня (1 - d/100) от предыдущего значения. Чтобы получить итоговый множитель, нам достаточно перемножить все такие коэффициенты. Рассмотрим пример реализации, который можно использовать при записи карточки товара:
// Допустим, скидки хранятся в табличной части "ДополнительныеСкидки"
// с реквизитом "ЗначениеПроцента"
КоэффициентОстатка = 1;
Для Каждого Строка Из ДополнительныеСкидки Цикл
// Преобразуем процент в коэффициент (например, 5% -> 0.95)
ТекущийКоэффициент = (100 - Строка.ЗначениеПроцента) / 100;
КоэффициентОстатка = КоэффициентОстатка * ТекущийКоэффициент;
КонецЦикла;
// Итоговый эффективный процент
ЭффективныйПроцент = (1 - КоэффициентОстатка) * 100;
// Записываем результат в скрытое поле для использования в типовых расчетах
ЭтотОбъект.ПроцентПостояннойСкидки = ЭффективныйПроцент;
Этот подход гораздо быстрее и чище с точки зрения математики. Он позволяет вычислить итоговое значение, имея только массив процентов.
Выясним одну критическую деталь: точность хранения данных в 1С. Стандартный реквизит типа Число для скидок обычно имеет точность 2 знака после запятой (например, Число(5, 2)). При работе со сложными процентами этого может быть недостаточно.
Проанализируем ситуацию: если в результате вычислений мы получили эффективный процент 6,8975%, а реквизит округлит его до 6,90%, то при больших суммах документов возникнет расхождение в копейках. Рассмотрим рекомендации по минимизации этой ошибки:
Процента (например, 6.9), лучше хранить КоэффициентЛояльности (например, 0.931). Умножение цены на коэффициент всегда точнее, чем вычитание процента.Цена * Количество не равно Сумме.Рассмотрим, как внедрить это решение с минимальным вмешательством в типовые модули. Вместо того чтобы менять логику в формах документов или глобальных процедурах расчета цен, лучше всего выполнять пересчет «эффективного процента» в модуле объекта товара в обработчике ПередЗаписью.
Таким образом, пользователь во фронт-офисе продолжает видеть привычное поле «Скидка», но оно заполняется автоматически на основе детальной таблицы скидок. Все существующие отчеты, печатные формы и алгоритмы расчета в чеках подхватят это значение автоматически, так как имя реквизита не изменилось.
Посмотрим на финальный алгоритм действий:
Номенклатура табличную часть для хранения детальных скидок.ПередЗаписью или изменяем модуль объекта.Этот метод позволяет соблюсти требования руководства по внедрению сложной маркетинговой политики и одновременно сохранить стабильность работы программного кода, накопленного годами разработки.