[FIX] point_of_sale: rounding of refund orders

Current behavior:
When a refund order is created, the amount is not rounded the same way
as the orginial order.

Steps to reproduce:
- Create a rouding method with 0.05 as rounding value, with the DOWN
  rounding method
- Create an order with a product of price 1.98
- Validate the order, the price should be 1.95
- Create a refund order for the previous order
- Validate the refund order, the price should be -1.95 but it's -2.00

Solution:
Always act like the order has a positive amount, and apply the rounding
the normal way. Then apply the sign of the original order.
If the remaining amount has a different sign than the original total,
we should invert the rounding method to apply a rounding in the same
"direction" as the original order.

For example :
- Original order is -1.98
- Rounding is applied and the total is -2.00
- After the payment, the remaining is -1.98 - -2.00 = 0.02
- Wich will be rounded to 0.05 wich is not correct, it should be rounded
  to 0.00. So we invert the rounding method to apply a rounding in the
  same "direction" as the original order. If we don't do this the total
  due would have been -1.95 instead of -2.00. And the change would have
  been 0.05 instead of 0.00.

opw-3106656

closes odoo/odoo#109422

X-original-commit: c92074e8ec5b4a11fa90f08a16c859bb72234863
Signed-off-by: Heinz Robin (rhe) <rhe@odoo.com>
Signed-off-by: Engels Robin (roen) <roen@odoo.com>
This commit is contained in:
roen-odoo
2023-01-09 15:28:58 +01:00
parent c7de0a1db7
commit 65c23b4bc7
4 changed files with 122 additions and 23 deletions
+15 -23
View File
@@ -3394,12 +3394,17 @@ export class Order extends PosModel {
? last_line.payment_method.is_cash_count == true
: false;
if (!only_cash || (only_cash && last_line_is_cash)) {
var rounding_method = this.pos.cash_rounding[0].rounding_method;
var remaining = this.get_total_with_tax() - this.get_total_paid();
var total = round_pr(remaining, this.pos.cash_rounding[0].rounding);
var sign = remaining > 0 ? 1.0 : -1.0;
var sign = this.get_total_with_tax() > 0 ? 1.0 : -1.0;
if(this.get_total_with_tax() < 0 && remaining > 0 || this.get_total_with_tax() > 0 && remaining < 0) {
rounding_method = rounding_method === "UP" ? "DOWN" : "UP";
}
remaining *= sign;
var total = round_pr(remaining, this.pos.cash_rounding[0].rounding);
var rounding_applied = total - remaining;
rounding_applied *= sign;
// because floor and ceil doesn't include decimals in calculation, we reuse the value of the half-up and adapt it.
if (utils.float_is_zero(rounding_applied, this.pos.currency.decimal_places)) {
// https://xkcd.com/217/
@@ -3408,29 +3413,16 @@ export class Order extends PosModel {
Math.abs(this.get_total_with_tax()) < this.pos.cash_rounding[0].rounding
) {
return 0;
} else if (
this.pos.cash_rounding[0].rounding_method === "UP" &&
rounding_applied < 0 &&
remaining > 0
) {
} else if(rounding_method === "UP" && rounding_applied < 0 && remaining > 0) {
rounding_applied += this.pos.cash_rounding[0].rounding;
} else if (
this.pos.cash_rounding[0].rounding_method === "UP" &&
rounding_applied > 0 &&
remaining < 0
) {
}
else if(rounding_method === "UP" && rounding_applied > 0 && remaining < 0) {
rounding_applied -= this.pos.cash_rounding[0].rounding;
} else if (
this.pos.cash_rounding[0].rounding_method === "DOWN" &&
rounding_applied > 0 &&
remaining > 0
) {
}
else if(rounding_method === "DOWN" && rounding_applied > 0 && remaining > 0){
rounding_applied -= this.pos.cash_rounding[0].rounding;
} else if (
this.pos.cash_rounding[0].rounding_method === "DOWN" &&
rounding_applied < 0 &&
remaining < 0
) {
}
else if(rounding_method === "DOWN" && rounding_applied < 0 && remaining < 0){
rounding_applied += this.pos.cash_rounding[0].rounding;
}
return sign * rounding_applied;
@@ -1,7 +1,9 @@
/** @odoo-module */
import { Chrome } from "@point_of_sale/../tests/tours/helpers/ChromeTourMethods";
import { ProductScreen } from "@point_of_sale/../tests/tours/helpers/ProductScreenTourMethods";
import { PaymentScreen } from "@point_of_sale/../tests/tours/helpers/PaymentScreenTourMethods";
import { TicketScreen} from "@point_of_sale/../tests/tours/helpers/TicketScreenTourMethods";
import { getSteps, startSteps } from "@point_of_sale/../tests/tours/helpers/utils";
import Tour from "web_tour.tour";
@@ -80,3 +82,55 @@ PaymentScreen.check.remainingIs("0.0");
PaymentScreen.check.changeIs("0.0");
Tour.register("PaymentScreenTour2", { test: true, url: "/pos/ui" }, getSteps());
startSteps();
ProductScreen.do.clickHomeCategory();
ProductScreen.exec.addOrderline('Product Test', '1');
ProductScreen.do.clickPayButton();
PaymentScreen.check.totalIs('2.00');
PaymentScreen.do.clickPaymentMethod('Cash');
PaymentScreen.check.remainingIs('0.0');
PaymentScreen.check.changeIs('0.0');
Chrome.do.clickTicketButton();
TicketScreen.do.clickNewTicket();
ProductScreen.exec.addOrderline('Product Test', '-1');
ProductScreen.do.clickPayButton();
PaymentScreen.check.totalIs('-2.00');
PaymentScreen.do.clickPaymentMethod('Cash');
PaymentScreen.check.remainingIs('0.0');
PaymentScreen.check.changeIs('0.0');
Tour.register('PaymentScreenRoundingUp', { test: true, url: '/pos/ui' }, getSteps());
startSteps();
ProductScreen.do.clickHomeCategory();
ProductScreen.exec.addOrderline('Product Test', '1');
ProductScreen.do.clickPayButton();
PaymentScreen.check.totalIs('1.95');
PaymentScreen.do.clickPaymentMethod('Cash');
PaymentScreen.check.remainingIs('0.0');
PaymentScreen.check.changeIs('0.0');
Chrome.do.clickTicketButton();
TicketScreen.do.clickNewTicket();
ProductScreen.exec.addOrderline('Product Test', '-1');
ProductScreen.do.clickPayButton();
PaymentScreen.check.totalIs('-1.95');
PaymentScreen.do.clickPaymentMethod('Cash');
PaymentScreen.check.remainingIs('0.0');
PaymentScreen.check.changeIs('0.0');
Tour.register('PaymentScreenRoundingDown', { test: true, url: '/pos/ui' }, getSteps());
@@ -185,6 +185,15 @@ class Check {
},
];
}
totalIs(amount) {
return [
{
content: `total is ${amount}`,
trigger: `.total:contains("${amount}")`,
run: () => {},
},
];
}
}
class Execute {
@@ -590,3 +590,47 @@ class TestUi(TestPointOfSaleHttpCommon):
self.main_pos_config.write({'payment_method_ids': [(6, 0, bank_pm.ids)]})
self.main_pos_config.open_ui()
self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'PaymentScreenTour2', login="accountman")
def test_rounding_up(self):
rouding_method = self.env['account.cash.rounding'].create({
'name': 'Rounding up',
'rounding': 0.05,
'rounding_method': 'UP',
})
self.env['product.product'].create({
'name': 'Product Test',
'available_in_pos': True,
'list_price': 1.98,
'taxes_id': False,
})
self.main_pos_config.write({
'rounding_method': rouding_method.id,
'cash_rounding': True,
})
self.main_pos_config.open_ui()
self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'PaymentScreenRoundingUp', login="accountman")
def test_rounding_down(self):
rouding_method = self.env['account.cash.rounding'].create({
'name': 'Rounding down',
'rounding': 0.05,
'rounding_method': 'DOWN',
})
self.env['product.product'].create({
'name': 'Product Test',
'available_in_pos': True,
'list_price': 1.98,
'taxes_id': False,
})
self.main_pos_config.write({
'rounding_method': rouding_method.id,
'cash_rounding': True,
})
self.main_pos_config.open_ui()
self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'PaymentScreenRoundingDown', login="accountman")