Прямой ответ
Скидка применяется к цене по фиксированному порядку, поэтому итоговая цена зависит от последовательности применения, а не только от величины скидок.
| Параметр скидки | Что определяет |
|---|---|
| Тип | Процент либо фиксированная сумма |
| Порядок | Последовательность применения скидок |
| База | Значение, от которого считается скидка |
| Ограничение | Минимальная цена либо предел скидки |
from decimal import Decimal, ROUND_HALF_UP
price = Decimal("1000.00")
price = price * (1 - Decimal("0.10")) # скидка 10 процентов
price = price - Decimal("50.00") # скидка 50 единиц
final = price.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
Порядок применения определяет базу каждой следующей скидки, поэтому итог различается при изменении последовательности. Об округлении — в статье про округление денежных значений.
Тип скидки и база расчёта
Тип скидки определяет способ расчёта, поэтому процент и фиксированная сумма применяются к разным значениям.
Процентная скидка. Значение скидки вычисляется умножением базы на долю, поэтому величина зависит от цены позиции.
Фиксированная скидка. Значение скидки вычитается из цены, поэтому величина не зависит от цены позиции.
База расчёта. Значение, от которого считается скидка, определяется порядком применения, поэтому база меняется после предыдущей скидки.
Практическое следствие: процентная скидка, применённая после фиксированной, даёт меньшее значение, чем при обратном порядке, поскольку база сокращается.
Отдельный аспект — скидка на позицию и на заказ. Скидка на позицию применяется к цене каждой позиции, скидка на заказ распределяется между позициями, поэтому распределение требует правила и проверки суммы частей.
Порядок применения и накопление
Порядок применения скидок определяет базу каждой следующей скидки, поэтому итоговая цена зависит от последовательности.
Последовательное применение. Каждая скидка применяется к результату предыдущей, поэтому база сокращается и накопление не является простым сложением.
Порядок процентных скидок. Две процентные скидки дают итог, не равный сумме долей, поскольку вторая применяется к сокращённой базе.
Фиксация порядка. Порядок применения фиксируется в расчёте, поэтому итоговая цена воспроизводится и не зависит от порядка вычисления.
Практическое следствие: две скидки по десять процентов дают итог меньше, чем скидка в двадцать процентов, поскольку вторая применяется к сокращённой базе.
Ограничения и проверка итога
Ограничение определяет минимальную цену либо предел скидки, поэтому итоговая цена не выходит за установленные границы.
Минимальная цена. Итоговая цена не опускается ниже установленного предела, поэтому скидка ограничивается значением предела.
Предел скидки. Величина скидки не превышает установленного предела, поэтому итоговая цена сохраняется выше минимального значения.
Проверка итога. Итоговая цена сравнивается с расчётной, поэтому расхождение указывает на ошибку порядка либо на неверную базу.
Практическое следствие: расчёт без ограничения даёт итоговую цену ниже минимального значения, поэтому условие ограничения проверяется явно.
О хранении цен — в статье про деньги в приложении; о расчёте налога от итоговой цены — в статье про расчёт НДС.
Практика: расчёт скидок
1. Фиксируйте порядок применения скидок. Итоговая цена воспроизводится и не зависит от порядка вычисления.
2. Определяйте базу каждой скидки явно. База определяется результатом предыдущей скидки, поэтому величина зависит от порядка.
3. Учитывайте накопление процентных скидок. Две процентные скидки не равны сумме долей, поскольку база сокращается.
4. Разделяйте скидку на позицию и на заказ. Скидка на заказ распределяется между позициями по правилу.
5. Проверяйте сумму частей при распределении скидки на заказ. Сумма скидок позиций совпадает со скидкой заказа.
6. Применяйте ограничение минимальной цены. Итоговая цена не опускается ниже установленного предела.
7. Округляйте после полного расчёта. Промежуточные значения сохраняют точность, поэтому итог не накапливает погрешность.
Рекомендации:
- Не складывайте доли процентных скидок. Итог определяется последовательным применением, поэтому сумма долей не равна фактической скидке.
- Учитывайте порядок при смешении типов скидок. Фиксированная и процентная скидка дают разный итог в зависимости от порядка.
- Сопоставляйте правило распределения с требованием отчётности. Скидка на заказ распределяется между позициями по фиксированному правилу.
- Фиксируйте ограничения в настройках расчёта. Минимальная цена и предел скидки применяются единообразно.
- Пересматриваем порядок при добавлении типа скидки. Новая скидка требует места в последовательности применения.
Практический сценарий: итоговая цена различалась при одном наборе скидок
Контекст: одинаковый набор скидок давал разную итоговую цену в двух местах расчёта, при этом величины скидок совпадали.
- Диагностика. Проверка показала, что в одном месте процентная скидка применялась до фиксированной, а в другом — после. База процентной скидки различалась, поэтому итоговая цена расходилась на несколько единиц.
- Причина. Порядок применения скидок не был зафиксирован, поэтому каждая часть системы применяла скидки в собственном порядке.
- Действия. (а) Зафиксировали единый порядок применения скидок в настройках расчёта, чтобы последовательность не зависела от места вычисления; (б) вынесли расчёт итоговой цены в одну функцию, чтобы обе части системы использовали общий код; (в) добавили проверку итоговой цены против расчётной, чтобы расхождение обнаруживалось; (г) применили ограничение минимальной цены, чтобы итог не опускался ниже предела; (д) перенесли округление в конец расчёта, чтобы промежуточные значения сохраняли точность.
- Результат. Итоговая цена совпадает в обеих частях системы, поскольку порядок применения зафиксирован и расчёт выполняется общим кодом.
Вывод: порядок применения скидок определяет базу каждой следующей скидки, поэтому итоговая цена зависит от последовательности. Фиксация порядка и общий код расчёта устраняют расхождение между частями системы.
FAQ
Почему две скидки по 10 процентов не равны скидке в 20 процентов? Вторая скидка применяется к сокращённой базе, поэтому накопление не является простым сложением долей.
Влияет ли порядок применения скидок на итог? Да, порядок определяет базу каждой следующей скидки, поэтому итоговая цена зависит от последовательности.
Как распределять скидку на заказ между позициями? По фиксированному правилу с проверкой суммы частей. Сумма скидок позиций должна совпадать со скидкой заказа.
Зачем ограничивать минимальную цену? Расчёт без ограничения даёт итоговую цену ниже установленного предела, поэтому условие ограничения проверяется явно.
Источники: Python decimal, PostgreSQL numeric types, PostgreSQL conditional expressions — ссылки в шапке.