[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:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user