diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index d30e3cc2cd7..87b1609f533 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -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: %s") % (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: %s") % (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 diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js index e5dca209f9a..ab7e74092be 100644 --- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js @@ -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) { diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index bed03da34e2..ec9910d946c 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -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(); }, diff --git a/addons/point_of_sale/static/src/xml/Screens/ReceiptScreen/OrderReceipt.xml b/addons/point_of_sale/static/src/xml/Screens/ReceiptScreen/OrderReceipt.xml index 62767a3c7a1..9950009ebff 100644 --- a/addons/point_of_sale/static/src/xml/Screens/ReceiptScreen/OrderReceipt.xml +++ b/addons/point_of_sale/static/src/xml/Screens/ReceiptScreen/OrderReceipt.xml @@ -127,9 +127,13 @@
- ROUNDED - + Rounding +
+
+ To Pay + +