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