From 7eb742f6b83916dddd5cda9cd25ee8b916bc94b1 Mon Sep 17 00:00:00 2001 From: Joseph Caburnay Date: Tue, 28 Sep 2021 11:33:20 +0000 Subject: [PATCH] [FIX] point_of_sale: post diff on bank payment methods when closing the session 1. When the diff amount is for "combine" bank payment method, the account.move record used to represent the combine amount is modified to contain the diff amount. The journal entry is modified: FROM: ``` Debit Credit Outstanding combine_amount Receivable combine_amount ``` TO: ``` Debit Credit Outstanding combine_amount - diff_amount Receivable combine_amount Diff account diff_amount ``` 2. When the diff amount is for "split" bank payment method, an account.move moving the diff amount from Outstanding to Diff account is created. The journal entry is like: ``` Debit Credit Outstanding diff_amount Diff account diff_amount ``` closes odoo/odoo#77859 Signed-off-by: pimodoo --- addons/account/models/account_journal.py | 4 +- .../account/views/account_journal_views.xml | 4 +- addons/point_of_sale/models/pos_session.py | 143 +++++++++++++++--- .../static/src/js/Popups/ClosePosPopup.js | 12 +- .../static/src/xml/Popups/ClosePosPopup.xml | 5 +- 5 files changed, 139 insertions(+), 29 deletions(-) diff --git a/addons/account/models/account_journal.py b/addons/account/models/account_journal.py index e61666d08c0..213e4726381 100644 --- a/addons/account/models/account_journal.py +++ b/addons/account/models/account_journal.py @@ -629,9 +629,9 @@ class AccountJournal(models.Model): default_account_code = self.env['account.account']._search_new_account_code(company, digits, liquidity_account_prefix) default_account_vals = self._prepare_liquidity_account_vals(company, default_account_code, vals) vals['default_account_id'] = self.env['account.account'].create(default_account_vals).id - if journal_type == 'cash' and not has_profit_account: + if journal_type in ('cash', 'bank') and not has_profit_account: vals['profit_account_id'] = company.default_cash_difference_income_account_id.id - if journal_type == 'cash' and not has_loss_account: + if journal_type in ('cash', 'bank') and not has_loss_account: vals['loss_account_id'] = company.default_cash_difference_expense_account_id.id # === Fill missing refund_sequence === diff --git a/addons/account/views/account_journal_views.xml b/addons/account/views/account_journal_views.xml index 553dfd11753..4925f0a2bae 100644 --- a/addons/account/views/account_journal_views.xml +++ b/addons/account/views/account_journal_views.xml @@ -65,8 +65,8 @@ - - + + we redirect the user @@ -443,16 +453,72 @@ class PosSession(models.Model): return {'successful': True} - def _cannot_close_session(self): + def _create_diff_account_move_for_split_payment_method(self, payment_method, diff_amount): + self.ensure_one() + + get_diff_vals_result = self._get_diff_vals(payment_method.id, diff_amount) + if not get_diff_vals_result: + return + + source_vals, dest_vals = get_diff_vals_result + diff_move = self.env['account.move'].create({ + 'journal_id': payment_method.journal_id.id, + 'date': fields.Date.context_today(self), + 'ref': self._get_diff_account_move_ref(payment_method), + 'line_ids': [Command.create(source_vals), Command.create(dest_vals)] + }) + diff_move._post() + + def _get_diff_account_move_ref(self, payment_method): + return _('Closing difference in %s (%s)', payment_method.name, self.name) + + def _get_diff_vals(self, payment_method_id, diff_amount): + payment_method = self.env['pos.payment.method'].browse(payment_method_id) + diff_compare_to_zero = self.currency_id.compare_amounts(diff_amount, 0) + source_account = payment_method.outstanding_account_id or self.company_id.account_journal_payment_debit_account_id + destination_account = self.env['account.account'] + + if (diff_compare_to_zero > 0): + destination_account = payment_method.journal_id.profit_account_id + elif (diff_compare_to_zero < 0): + destination_account = payment_method.journal_id.loss_account_id + + if (diff_compare_to_zero == 0 or not source_account): + return False + + amounts = self._update_amounts({'amount': 0, 'amount_converted': 0}, {'amount': diff_amount}, self.stop_at) + source_vals = self._debit_amounts({'account_id': source_account.id}, amounts['amount'], amounts['amount_converted']) + dest_vals = self._credit_amounts({'account_id': destination_account.id}, amounts['amount'], amounts['amount_converted']) + return [source_vals, dest_vals] + + def _cannot_close_session(self, bank_payment_method_diffs=None): """ Add check in this method if you want to return or raise an error when trying to either post cash details or close the session. Raising an error will always redirect the user to the back end. It should return {'successful': False, 'message': str, 'redirect': bool} if we can't close the session """ + bank_payment_method_diffs = bank_payment_method_diffs or {} if any(order.state == 'draft' for order in self.order_ids): return {'successful': False, 'message': _("You cannot close the POS when orders are still in draft"), 'redirect': False} if self.state == 'closed': return {'successful': False, 'message': _("This session is already closed."), 'redirect': True} + if bank_payment_method_diffs: + no_loss_account = self.env['account.journal'] + no_profit_account = self.env['account.journal'] + for payment_method in self.env['pos.payment.method'].browse(bank_payment_method_diffs.keys()): + journal = payment_method.journal_id + compare_to_zero = self.currency_id.compare_amounts(bank_payment_method_diffs.get(payment_method.id), 0) + if compare_to_zero == -1 and not journal.loss_account_id: + no_loss_account |= journal + elif compare_to_zero == 1 and not journal.profit_account_id: + no_profit_account |= journal + message = '' + if no_loss_account: + message += _("Need loss account for the following journals to post the lost amount: %s\n", ', '.join(no_loss_account.mapped('name'))) + if no_profit_account: + message += _("Need profit account for the following journals to post the gained amount: %s", ', '.join(no_profit_account.mapped('name'))) + if message: + return {'successful': False, 'message': message, 'redirect': False} def get_closing_control_data(self): self.ensure_one() @@ -498,6 +564,7 @@ class PosSession(models.Model): 'other_payment_methods': [{ 'name': pm.name, 'amount': sum(orders.payment_ids.filtered(lambda p: p.payment_method_id == pm).mapped('amount')), + 'number': len(orders.payment_ids.filtered(lambda p: p.payment_method_id == pm)), 'id': pm.id, 'type': pm.type, } for pm in other_payment_method_ids], @@ -554,7 +621,7 @@ class PosSession(models.Model): propoerty_account = self.env['ir.property']._get('property_account_receivable_id', 'res.partner') return self.company_id.account_default_pos_receivable_account_id or propoerty_account or self.env['account.account'] - def _create_account_move(self, balancing_account=False, amount_to_balance=0): + def _create_account_move(self, balancing_account=False, amount_to_balance=0, bank_payment_method_diffs=None): """ Create account.move and account.move.line records for this session. Side-effects include: @@ -572,7 +639,7 @@ class PosSession(models.Model): }) self.write({'move_id': account_move.id}) - data = {} + data = {'bank_payment_method_diffs': bank_payment_method_diffs or {}} data = self._accumulate_amounts(data) data = self._create_non_reconciliable_move_lines(data) data = self._create_bank_payment_moves(data) @@ -792,12 +859,13 @@ class PosSession(models.Model): def _create_bank_payment_moves(self, data): combine_receivables_bank = data.get('combine_receivables_bank') split_receivables_bank = data.get('split_receivables_bank') + bank_payment_method_diffs = data.get('bank_payment_method_diffs') MoveLine = data.get('MoveLine') payment_method_to_receivable_lines = {} payment_to_receivable_lines = {} for payment_method, amounts in combine_receivables_bank.items(): combine_receivable_line = MoveLine.create(self._get_combine_receivable_vals(payment_method, amounts['amount'], amounts['amount_converted'])) - payment_receivable_line = self._create_combine_account_payment(payment_method, amounts) + payment_receivable_line = self._create_combine_account_payment(payment_method, amounts, diff_amount=bank_payment_method_diffs.get(payment_method.id) or 0) payment_method_to_receivable_lines[payment_method] = combine_receivable_line | payment_receivable_line for payment, amounts in split_receivables_bank.items(): @@ -805,6 +873,9 @@ class PosSession(models.Model): payment_receivable_line = self._create_split_account_payment(payment, amounts) payment_to_receivable_lines[payment] = split_receivable_line | payment_receivable_line + for bank_payment_method in self.payment_method_ids.filtered(lambda pm: pm.type == 'bank' and pm.split_transactions): + self._create_diff_account_move_for_split_payment_method(bank_payment_method, bank_payment_method_diffs.get(bank_payment_method.id) or 0) + data['payment_method_to_receivable_lines'] = payment_method_to_receivable_lines data['payment_to_receivable_lines'] = payment_to_receivable_lines return data @@ -821,7 +892,7 @@ class PosSession(models.Model): MoveLine.create(vals) return data - def _create_combine_account_payment(self, payment_method, amounts): + def _create_combine_account_payment(self, payment_method, amounts, diff_amount): outstanding_account = payment_method.outstanding_account_id or self.company_id.account_journal_payment_debit_account_id destination_account = self._get_receivable_account(payment_method) @@ -838,9 +909,29 @@ class PosSession(models.Model): 'pos_payment_method_id': payment_method.id, 'pos_session_id': self.id, }) + + diff_amount_compare_to_zero = self.currency_id.compare_amounts(diff_amount, 0) + if diff_amount_compare_to_zero != 0: + self._apply_diff_on_account_payment_move(account_payment, payment_method, diff_amount) + account_payment.action_post() return account_payment.move_id.line_ids.filtered(lambda line: line.account_id == account_payment.destination_account_id) + def _apply_diff_on_account_payment_move(self, account_payment, payment_method, diff_amount): + source_vals, dest_vals = self._get_diff_vals(payment_method.id, diff_amount) + outstanding_line = account_payment.move_id.line_ids.filtered(lambda line: line.account_id.id == source_vals['account_id']) + new_balance = outstanding_line.balance + diff_amount + new_balance_compare_to_zero = self.currency_id.compare_amounts(new_balance, 0) + account_payment.move_id.write({ + 'line_ids': [ + Command.create(dest_vals), + Command.update(outstanding_line.id, { + 'debit': new_balance_compare_to_zero > 0 and new_balance or 0.0, + 'credit': new_balance_compare_to_zero < 0 and -new_balance or 0.0 + }) + ] + }) + def _create_split_account_payment(self, payment, amounts): payment_method = payment.payment_method_id if not payment_method.journal_id: @@ -1318,6 +1409,17 @@ class PosSession(models.Model): }, } + def _get_other_related_moves(self): + # TODO This is not an ideal way to get the diff account.move's for + # the session. It would be better if there is a relation field where + # these moves are saved. + + # Unfortunately, the 'ref' of account.move is not indexed, so + # we are querying over the account.move.line because its 'ref' is indexed. + # And yes, we are only concern for split bank payment methods. + diff_lines_ref = [self._get_diff_account_move_ref(pm) for pm in self.payment_method_ids if pm.type == 'bank' and pm.split_transactions] + return self.env['account.move.line'].search([('ref', 'in', diff_lines_ref)]).mapped('move_id') + def _get_related_account_moves(self): pickings = self.picking_ids | self.order_ids.mapped('picking_ids') invoices = self.mapped('order_ids.account_move') @@ -1325,7 +1427,8 @@ class PosSession(models.Model): stock_account_moves = pickings.mapped('move_lines.account_move_ids') cash_moves = self.cash_register_id.line_ids.mapped('move_id') bank_payment_moves = self.bank_payment_ids.mapped('move_id') - return invoices | invoice_payments | self.move_id | stock_account_moves | cash_moves | bank_payment_moves + other_related_moves = self._get_other_related_moves() + return invoices | invoice_payments | self.move_id | stock_account_moves | cash_moves | bank_payment_moves | other_related_moves def _get_receivable_account(self, payment_method): """Returns the default pos receivable account if no receivable_account_id is set on the payment method.""" diff --git a/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js b/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js index e17e8406bb8..fa005a0ef16 100644 --- a/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js @@ -39,12 +39,12 @@ odoo.define('point_of_sale.ClosePosPopup', function(require) { // component state and refs definition const state = {notes: '', acceptClosing: false, payments: {}}; if (this.cashControl) { - state.payments[this.defaultCashDetails.id] = {counted: 0, difference: -this.defaultCashDetails.amount}; + state.payments[this.defaultCashDetails.id] = {counted: 0, difference: -this.defaultCashDetails.amount, number: 0}; } if (this.otherPaymentMethods.length > 0) { this.otherPaymentMethods.forEach(pm => { if (pm.type === 'bank') { - state.payments[pm.id] = {counted: this.env.pos.round_decimals_currency(pm.amount), difference: 0} + state.payments[pm.id] = {counted: this.env.pos.round_decimals_currency(pm.amount), difference: 0, number: pm.number} } }) } @@ -141,10 +141,13 @@ odoo.define('point_of_sale.ClosePosPopup', function(require) { args: [this.env.pos.pos_session.id, this.state.notes] }) try { + const bankPaymentMethodDiffPairs = this.otherPaymentMethods + .filter((pm) => pm.type == 'bank') + .map((pm) => [pm.id, this.state.payments[pm.id].difference]); response = await this.rpc({ model: 'pos.session', method: 'close_session_from_ui', - args: [this.env.pos.pos_session.id], + args: [this.env.pos.pos_session.id, bankPaymentMethodDiffPairs], }); if (!response.successful) { return this.handleClosingError(response); @@ -176,6 +179,9 @@ odoo.define('point_of_sale.ClosePosPopup', function(require) { window.location = '/web#action=point_of_sale.action_client_pos_menu'; } } + _getShowDiff(pm) { + return pm.type == 'bank' && pm.number !== 0; + } } ClosePosPopup.template = 'ClosePosPopup'; diff --git a/addons/point_of_sale/static/src/xml/Popups/ClosePosPopup.xml b/addons/point_of_sale/static/src/xml/Popups/ClosePosPopup.xml index ee4b934e9fd..04f215cebed 100644 --- a/addons/point_of_sale/static/src/xml/Popups/ClosePosPopup.xml +++ b/addons/point_of_sale/static/src/xml/Popups/ClosePosPopup.xml @@ -78,8 +78,9 @@ - - + + +