[FIX] pos_cash_rounding: fix when multiple payment method

When a pos user combines payment methods with and without rounding, we
must be sure that the rounding is made by payment method and not only on
the total amount of the order.

As the total may change depending on the combination of payment method
used, we had to adapt the methods that checks that an order is paid, and
also propagate the new rounding made on the invoice.

We also don't allow anymore a user to validate an order with a payment
badly rounded.

closes odoo/odoo#69754

X-original-commit: 8cf38faec03d167a7509f0ad6e041a13e4f7ad53
Signed-off-by: pimodoo <pimodoo@users.noreply.github.com>
This commit is contained in:
Robin Heinz
2021-04-23 10:45:20 +00:00
parent ebc49bc143
commit eb5420da2c
4 changed files with 120 additions and 13 deletions
+79 -7
View File
@@ -370,6 +370,70 @@ class PosOrder(models.Model):
currency = self.currency_id
return currency.round(amount) if currency else amount
def _create_invoice(self, move_vals):
self.ensure_one()
new_move = self.env['account.move'].sudo().with_company(self.company_id).with_context(default_move_type=move_vals['move_type']).create(move_vals)
message = _("This invoice has been created from the point of sale session: <a href=# data-oe-model=pos.order data-oe-id=%d>%s</a>") % (self.id, self.name)
new_move.message_post(body=message)
if self.config_id.cash_rounding:
rounding_applied = float_round(self.amount_paid - self.amount_total,
precision_rounding=new_move.currency_id.rounding)
rounding_line = new_move.line_ids.filtered(lambda line: line.is_rounding_line)
if rounding_applied:
if rounding_applied > 0.0:
account_id = new_move.invoice_cash_rounding_id.loss_account_id.id
else:
account_id = new_move.invoice_cash_rounding_id.profit_account_id.id
if rounding_line:
if rounding_line.debit > 0:
rounding_line_difference = rounding_line.debit + rounding_applied
else:
rounding_line_difference = -rounding_line.credit + rounding_applied
if rounding_line_difference:
rounding_line.with_context(check_move_validity=False).write({
'debit': rounding_applied < 0.0 and -rounding_applied or 0.0,
'credit': rounding_applied > 0.0 and rounding_applied or 0.0,
'account_id': account_id,
'price_unit': rounding_applied,
})
existing_terms_line = new_move.line_ids.filtered(
lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))
if existing_terms_line.debit > 0:
existing_terms_line_new_val = float_round(
existing_terms_line.debit + rounding_line_difference,
precision_rounding=new_move.currency_id.rounding)
else:
existing_terms_line_new_val = float_round(
-existing_terms_line.credit + rounding_line_difference,
precision_rounding=new_move.currency_id.rounding)
existing_terms_line.write({
'debit': existing_terms_line_new_val > 0.0 and existing_terms_line_new_val or 0.0,
'credit': existing_terms_line_new_val < 0.0 and -existing_terms_line_new_val or 0.0,
})
new_move._recompute_payment_terms_lines()
else:
self.env['account.move.line'].create({
'debit': rounding_applied < 0.0 and -rounding_applied or 0.0,
'credit': rounding_applied > 0.0 and rounding_applied or 0.0,
'quantity': 1.0,
'amount_currency': rounding_applied,
'partner_id': new_move.partner_id.id,
'move_id': new_move.id,
'currency_id': new_move.currency_id if new_move.currency_id != new_move.company_id.currency_id else False,
'company_id': new_move.company_id.id,
'company_currency_id': new_move.company_id.currency_id.id,
'is_rounding_line': True,
'sequence': 9999,
'name': new_move.invoice_cash_rounding_id.name,
'account_id': account_id,
})
else:
if rounding_line:
rounding_line.unlink()
return new_move
def action_pos_order_paid(self):
self.ensure_one()
@@ -381,8 +445,20 @@ class PosOrder(models.Model):
else:
total = float_round(self.amount_total, precision_rounding=self.config_id.rounding_method.rounding, rounding_method=self.config_id.rounding_method.rounding_method)
if not float_is_zero(total - self.amount_paid, precision_rounding=self.currency_id.rounding):
isPaid = float_is_zero(total - self.amount_paid, precision_rounding=self.currency_id.rounding)
if not isPaid and not self.config_id.cash_rounding:
raise UserError(_("Order %s is not fully paid.", self.name))
elif not isPaid and self.config_id.cash_rounding:
currency = self.currency_id
if self.config_id.rounding_method.rounding_method == "HALF-UP":
maxDiff = currency.round(self.config_id.rounding_method.rounding / 2)
else:
maxDiff = currency.round(self.config_id.rounding_method.rounding)
diff = currency.round(self.amount_total - self.amount_paid)
if not abs(diff) < maxDiff:
raise UserError(_("Order %s is not fully paid.", self.name))
self.write({'state': 'paid'})
@@ -424,12 +500,8 @@ class PosOrder(models.Model):
raise UserError(_('Please provide a partner for the sale.'))
move_vals = order._prepare_invoice_vals()
new_move = moves.sudo()\
.with_company(order.company_id)\
.with_context(default_move_type=move_vals['move_type'])\
.create(move_vals)
message = _("This invoice has been created from the POS Order: <a href=# data-oe-model=pos.order data-oe-id=%d>%s</a>") % (order.id, order.name)
new_move.message_post(body=message)
new_move = order._create_invoice(move_vals)
order.write({'account_move': new_move.id, 'state': 'invoiced'})
new_move.sudo().with_company(order.company_id)._post()
moves += new_move
@@ -145,6 +145,15 @@ odoo.define('point_of_sale.PaymentScreen', function (require) {
this.render();
}
async validateOrder(isForceValidate) {
if(this.env.pos.config.cash_rounding) {
if(!this.env.pos.get_order().check_paymentlines_rounding()) {
this.showPopup('ErrorPopup', {
title: this.env._t('Rounding error in payment lines'),
body: this.env._t("The amount of your payment lines must be rounded to validate the transaction."),
});
return;
}
}
if (await this._isOrderValid(isForceValidate)) {
// remove pending payments before finalizing the validation
for (let line of this.paymentLines) {
+26 -4
View File
@@ -3062,6 +3062,10 @@ exports.Order = Backbone.Model.extend({
newPaymentline.set_amount(this.get_due());
this.paymentlines.add(newPaymentline);
this.select_paymentline(newPaymentline);
if(this.pos.config.cash_rounding){
this.selected_paymentline.set_amount(0);
this.selected_paymentline.set_amount(this.get_due());
}
return newPaymentline;
},
get_paymentlines: function(){
@@ -3298,12 +3302,13 @@ exports.Order = Backbone.Model.extend({
get_rounding_applied: function() {
if(this.pos.config.cash_rounding) {
const only_cash = this.pos.config.only_round_cash_method;
const has_cash = _.some(this.get_paymentlines(), function(pl) { return pl.payment_method.is_cash_count == true;});
const has_cash = this.selected_paymentline ? this.selected_paymentline.payment_method.is_cash_count == true: false;
if (!only_cash || (only_cash && has_cash)) {
var total = round_pr(this.get_total_with_tax(), this.pos.cash_rounding[0].rounding);
var remaining = this.get_total_with_tax() - this.get_total_paid();
var total = round_pr(remaining, this.pos.cash_rounding[0].rounding);
var sign = total > 0 ? 1.0 : -1.0;
var rounding_applied = total - (this.pos.config['iface_tax_included'] === "total"? this.get_subtotal(): this.get_total_with_tax());
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.decimals)){
@@ -3341,13 +3346,30 @@ exports.Order = Backbone.Model.extend({
return false;
},
is_paid: function(){
return this.get_due() <= 0;
return this.get_due() <= 0 && this.check_paymentlines_rounding();
},
is_paid_with_cash: function(){
return !!this.paymentlines.find( function(pl){
return pl.payment_method.is_cash_count;
});
},
check_paymentlines_rounding: function() {
if(this.pos.config.cash_rounding) {
var cash_rounding = this.pos.cash_rounding[0].rounding;
var default_rounding = this.pos.currency.rounding;
for(var id in this.get_paymentlines()) {
var line = this.get_paymentlines()[id];
var diff = round_pr(round_pr(line.amount, cash_rounding) - round_pr(line.amount, default_rounding), default_rounding);
if(diff && line.payment_method.is_cash_count) {
return false;
} else if(!this.pos.config.only_round_cash_method && diff) {
return false;
}
}
return true;
}
return true;
},
finalize: function(){
this.destroy();
},
@@ -127,9 +127,13 @@
</div>
<t t-if="receipt.total_rounded != receipt.total_with_tax">
<div class="pos-receipt-amount">
ROUNDED
<span t-esc="env.pos.format_currency(receipt.total_rounded)" class="pos-receipt-right-align"/>
Rounding
<span t-esc='env.pos.format_currency(receipt.rounding_applied)' class="pos-receipt-right-align"/>
</div>
<div class="pos-receipt-amount">
To Pay
<span t-esc='env.pos.format_currency(receipt.total_rounded)' class="pos-receipt-right-align"/>
</div>
</t>
<br/><br/>