Скидка даёт разные копейки: как согласовать расчёт корзины
Разница в копейках возникает, когда корзина, платёжный расчёт и учётная система округляют скидку на разных этапах: по строкам или по заказу целиком. Сначала согласуйте основание, момент и метод округления, затем распределение общей скидки между позициями. Замена числового типа на точный убирает один класс погрешностей, но не выбирает за бизнес правило расчёта.
Начните с двух разных требований
Первое — как хранить и вычислять числа без нежелательной потери точности. Второе — какой результат компания считает правильным. Даже при идеальном десятичном представлении два последовательных округления могут дать не ту сумму, что одно округление в конце. Спор «на сервере всё посчитано точно» не решает вопрос, если правила этапов различаются.
В документации PostgreSQL numeric описан как тип для точных вычислений, а real и double precision — как приближённые. Это основание выбирать подходящее представление. Но тип данных не определяет, распространяется ли скидка на строку, единицу товара или весь заказ и когда именно нужно получить копейки.
Учебная корзина, в которой расходится одна копейка
Возьмём три вымышленные позиции А, Б и В: по одной единице ценой 11,05 рубля. Скидка 10 процентов действует на каждую. Для положительных чисел выберем округление половины вверх до копейки. Это учебные допущения для проверки программы, не цены студии и не требования к кассовому или налоговому расчёту.
Скидка одной строки до округления равна 1,105 рубля. Если округлить скидку каждой строки, получится по 1,11 рубля, всего 3,33. Из общей стоимости 33,15 к оплате останется 29,82. Если сначала вычислить 10 процентов всего заказа, получится 3,315; округлённая скидка составит 3,32, а к оплате —29,83. Оба результата получены точной арифметикой, но по разным правилам.
Поэтому перед разработкой интернет-магазина запишите выбранный вариант в постановке задачи. Нельзя объявить расхождение ошибкой конкретного экрана, пока неизвестно, какой порядок он должен выполнять. И нельзя прятать разницу ручной поправкой только в отображаемом итоге, оставляя противоречащие ему строки.
Если скидка считается по заказу, распределите её явно
Продолжим второй вариант: общая скидка 3,32. Для учебной модели распределим её пропорционально стоимости строк. Сначала округлим доли вниз до копеек, затем оставшиеся копейки назначим строкам с наибольшими дробными остатками. При равенстве используем устойчивый порядок А, Б, В. Это предложенный алгоритм примера, а не универсальное правило всех торговых систем.
| Учебная строка | Цена | 10% до округления | Скидка при расчёте по строкам | Распределение скидки заказа | К оплате во втором варианте |
|---|---|---|---|---|---|
| А | 11,05 | 1,105 | 1,11 | 1,11 | 9,94 |
| Б | 11,05 | 1,105 | 1,11 | 1,11 | 9,94 |
| В | 11,05 | 1,105 | 1,11 | 1,10 | 9,95 |
| Всего | 33,15 | 3,315 | 3,33 | 3,32 | 29,83 |
В последней строке «3,33» относится к первому способу, а «3,32» и «29,83» — ко второму. Смешивать их в одном результате нельзя. Во втором варианте скидки строк в сумме точно дают скидку заказа. Устойчивое правило разрешения равных остатков нужно для повторяемости: перестановка карточек на экране не должна произвольно менять распределение уже согласованного заказа.
Что именно округляется
Отдельно выберите, округляется скидка или сумма после скидки. Это не обязательно одно действие с тем же результатом. Также различайте цену единицы и сумму строки с количеством. Если сначала округлять каждую единицу, а затем умножать, итог может отличаться от расчёта для всей строки. В спецификации нужны формула и порядок, а не только фраза «два знака после запятой».
В модуле Decimal Python предусмотрены разные режимы округления и явное приведение к заданной точности. Это инструменты реализации, а не решение коммерческого правила. Например, выбранный режим половины вверх нельзя незаметно заменить округлением к чётному, потому что на некоторых значениях они дадут разные результаты.
Определите поведение недопустимых значений, нулевой суммы и строк, на которые скидка не распространяется. Не подставляйте ноль вместо неизвестной цены и не делите общую скидку между исключёнными позициями. Возврат, доставка и другие составляющие заказа требуют своих принятых условий. Учебный пример не определяет их автоматически.
Сохранённый заказ не пересчитывают по сегодняшним настройкам
Сохраните выбранное правило, исходные величины и распределённую скидку вместе с заказом. Когда прайс или настройка акции изменятся, старый итог должен оставаться объяснимым. Иначе поддержка увидит одну сумму в переписке, другую в текущем калькуляторе и третью во внешней системе. Полезен не только итог, но и основание каждой коррекции.
Для интеграции учётной системы через API согласуйте, что передаётся: исходная скидка, распределённые суммы или окончательные строки. У получателя не должно возникать скрытого второго расчёта по собственному варианту, если контракт требует принять уже согласованные значения. При расхождении нужен явный результат сверки, а не замена одной суммы другой.
Часть заказа ушла: что возвращать в учебной модели
В нашем втором варианте строке В назначено к оплате 9,95. Если процесс предусматривает отмену именно этой сохранённой строки без изменения условий остальных, её сумма берётся из прежнего распределения. Останется 19,88. Если вместо этого заново посчитать 10 процентов для двух исходных строк, получится 19,89. Такая разница не объясняется неточным числом: поменялся расчёт.
Реальные правила отмены, возврата и сохранения скидки нужно согласовать для продукта и применимых требований. Пример показывает необходимость связи обратного действия с исходной операцией, но не устанавливает обязательную сумму возврата в любой продаже. Если условия требуют пересмотра скидки, это должно быть самостоятельным объяснимым действием, а не случайным следствием открытия корзины.
Приёмка на наборе, который не округляется сам собой
- Подготовьте учебные суммы с дробной копейкой в скидке, одинаковыми остатками и разными количествами. Заранее рассчитайте ожидаемые строки и итоги.
- Для каждого примера укажите основание скидки, порядок действий и метод округления. Отдельно назовите исключённые позиции.
- Сверьте сумму распределённых скидок с итоговой скидкой и сумму строк к оплате с итогом заказа. Каждая копейка должна иметь объяснение.
- Повторите расчёт после перестановки строк, повторного открытия и сохранения. Не меняйте устойчивые идентификаторы ради сортировки экрана.
- Измените текущие настройки акции и откройте старый заказ. Проверьте сохранность его согласованной версии расчёта.
- В тестовой среде отмените часть заказа и выполните обмен с учётной системой. Сравните фактический результат с выбранной политикой, а не только визуальным итогом.
Такая постановка относится к автоматизации расчётов, а не к косметическому форматированию суммы. Передайте разработчику правила и проверенный набор вместе. Тогда разные экраны и интеграции будут воспроизводить одно согласованное решение, а расхождение можно будет локализовать по конкретному этапу.
Вопросы и ответы
На каком этапе выполнять округление скидки?
Это нужно определить в правилах продукта: по единице, строке или заказу и до либо после других действий. Разные этапы способны дать разные копейки даже при точных вычислениях.
Что округлять: скидку или сумму после скидки?
Выберите один согласованный порядок и проверьте его на пограничных значениях. Эти операции нельзя считать взаимозаменяемыми без проверки, особенно при нескольких строках и количестве больше единицы.
Можно ли проверить итог через decimal rounding calculator?
Калькулятор полезен для независимого расчёта отдельных действий. Но ему нужно задать тот же метод и порядок округления; проверка одной конечной суммы не подтверждает распределение скидки и обратные операции.