From 6b979c6f64bf9bcb923de855760f4d780696c072 Mon Sep 17 00:00:00 2001 From: roen-odoo Date: Mon, 25 Mar 2024 14:09:12 +0000 Subject: [PATCH] [FIX] point_of_sale: correctly reconcile reversed payments Current behavior: When an order is invoided after the session has been closed, a reversed payment is created. This payment is not reconciled correctly with the invoice. This is creating an aged receivable for the partner. Steps to reproduce: - Change the bank payment method to "Identify customer" - Create an order in the PoS and pay with bank and specify a partner - Close the session - Open the session again, and create an invoice for the order - Go to the accounting module and look for the aged receivable report you should see some entries under the partner you selected. - You can also go to the partner form and see that he has some due invoices. opw-3678298 correct partner closes odoo/odoo#162653 X-original-commit: 31aff389c4c9d01be2f960a0ae134ff658e802c9 Related: odoo/enterprise#61158 Signed-off-by: Joseph Caburnay (jcb) Signed-off-by: Robin Engels (roen) --- addons/point_of_sale/models/pos_order.py | 18 +-- addons/point_of_sale/models/pos_payment.py | 12 +- .../tests/test_point_of_sale_flow.py | 106 +----------------- 3 files changed, 24 insertions(+), 112 deletions(-) diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index d4f072f6f65..f63f3fd2d9d 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -848,11 +848,15 @@ class PosOrder(models.Model): }) reversal_entry.action_post() - # Reconcile the new receivable line with the lines from the payment move. pos_account_receivable = self.company_id.account_default_pos_receivable_account_id - reversal_entry_receivable = reversal_entry.line_ids.filtered(lambda l: l.account_id == pos_account_receivable) - payment_receivable = payment_moves.line_ids.filtered(lambda l: l.account_id == pos_account_receivable) - (reversal_entry_receivable | payment_receivable).reconcile() + account_receivable = self.payment_ids.payment_method_id.receivable_account_id + reversal_entry_receivable = reversal_entry.line_ids.filtered(lambda l: l.account_id in (pos_account_receivable + account_receivable)) + payment_receivable = payment_moves.line_ids.filtered(lambda l: l.account_id in (pos_account_receivable + account_receivable)) + lines_to_reconcile = defaultdict(lambda: self.env['account.move.line']) + for line in (reversal_entry_receivable | payment_receivable): + lines_to_reconcile[line.account_id] |= line + for line in lines_to_reconcile.values(): + line.filtered(lambda l: not l.reconciled).reconcile() def action_pos_order_invoice(self): if len(self.company_id) > 1: @@ -882,7 +886,7 @@ class PosOrder(models.Model): new_move.sudo().with_company(order.company_id).with_context(skip_invoice_sync=True)._post() moves += new_move - payment_moves = order._apply_invoice_payments() + payment_moves = order._apply_invoice_payments(order.session_id.state == 'closed') # Send and Print if self.env.context.get('generate_pdf', True): @@ -913,9 +917,9 @@ class PosOrder(models.Model): def action_pos_order_cancel(self): return self.write({'state': 'cancel'}) - def _apply_invoice_payments(self): + def _apply_invoice_payments(self, is_reverse=False): receivable_account = self.env["res.partner"]._find_accounting_partner(self.partner_id).with_company(self.company_id).property_account_receivable_id - payment_moves = self.payment_ids.sudo().with_company(self.company_id)._create_payment_moves() + payment_moves = self.payment_ids.sudo().with_company(self.company_id)._create_payment_moves(is_reverse) if receivable_account.reconcile: invoice_receivables = self.account_move.line_ids.filtered(lambda line: line.account_id == receivable_account and not line.reconciled) if invoice_receivables: diff --git a/addons/point_of_sale/models/pos_payment.py b/addons/point_of_sale/models/pos_payment.py index 15d94f77c67..35d2b468601 100644 --- a/addons/point_of_sale/models/pos_payment.py +++ b/addons/point_of_sale/models/pos_payment.py @@ -63,7 +63,7 @@ class PosPayment(models.Model): def export_for_ui(self): return self.mapped(self._export_for_ui) if self else [] - def _create_payment_moves(self): + def _create_payment_moves(self, is_reverse=False): result = self.env['account.move'] for payment in self: order = payment.pos_order_id @@ -87,9 +87,17 @@ class PosPayment(models.Model): 'partner_id': accounting_partner.id, 'move_id': payment_move.id, }, amounts['amount'], amounts['amount_converted']) + is_split_transaction = payment.payment_method_id.split_transactions + if is_split_transaction and is_reverse: + reversed_move_receivable_account_id = accounting_partner.with_company(order.company_id).property_account_receivable_id.id + elif is_reverse: + reversed_move_receivable_account_id = payment.payment_method_id.receivable_account_id.id or self.company_id.account_default_pos_receivable_account_id.id + else: + reversed_move_receivable_account_id = self.company_id.account_default_pos_receivable_account_id.id debit_line_vals = pos_session._debit_amounts({ - 'account_id': pos_session.company_id.account_default_pos_receivable_account_id.id, + 'account_id': reversed_move_receivable_account_id, 'move_id': payment_move.id, + 'partner_id': accounting_partner.id if is_split_transaction and is_reverse else False, }, amounts['amount'], amounts['amount_converted']) self.env['account.move.line'].create([credit_line_vals, debit_line_vals]) payment_move._post() diff --git a/addons/point_of_sale/tests/test_point_of_sale_flow.py b/addons/point_of_sale/tests/test_point_of_sale_flow.py index 3b1023831ae..73b5472f3b4 100644 --- a/addons/point_of_sale/tests/test_point_of_sale_flow.py +++ b/addons/point_of_sale/tests/test_point_of_sale_flow.py @@ -1473,9 +1473,9 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): # Check 2: Reconciliation # The invoice receivable should be reconciled with the payment receivable of the same account. - invoice_receivable_line = invoice.line_ids.filtered(lambda line: line.account_id == self.company_data['default_account_receivable']) - payment_receivable_line = payment.line_ids.filtered(lambda line: line.account_id == self.company_data['default_account_receivable']) - self.assertEqual(invoice_receivable_line.matching_number, payment_receivable_line.matching_number) + invoice_receivable_line = invoice.line_ids.filtered(lambda line: line.account_id == self.company_data['default_account_receivable'] and line.reconciled) + payment_receivable_line = payment.line_ids.filtered(lambda line: line.account_id == self.company_data['default_account_receivable'] and line.reconciled and line.matching_number == invoice_receivable_line.matching_number) + self.assertTrue(payment_receivable_line) # The payment receivable (POS) is reconciled with the closing entry receivable (POS) payment_receivable_pos_line = payment.line_ids.filtered(lambda line: line.account_id == self.company_data['company'].account_default_pos_receivable_account_id) misc_receivable_pos_line = misc_reversal_entry.line_ids.filtered(lambda line: line.account_id == self.company_data['company'].account_default_pos_receivable_account_id) @@ -1787,106 +1787,6 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): self.assertEqual(credit_notes.ref, "Reversal of: "+invoices.name) self.assertEqual(credit_notes.reversed_entry_id.id, invoices.id) - def test_invoicing_after_closing_session(self): - """ Test that an invoice can be created after the session is closed """ - #create customer account payment method - self.customer_account_payment_method = self.env['pos.payment.method'].create({ - 'name': 'Customer Account', - 'split_transactions': True, - }) - - self.product1 = self.env['product.product'].create({ - 'name': 'Product A', - 'type': 'product', - 'categ_id': self.env.ref('product.product_category_all').id, - }) - self.partner1.write({'parent_id': self.env['res.partner'].create({'name': 'Parent'}).id}) - - #add customer account payment method to pos config - self.pos_config.write({ - 'payment_method_ids': [(4, self.customer_account_payment_method.id, 0)], - }) - # change the currency of PoS config - (self.currency_data['currency'].rate_ids | self.company.currency_id.rate_ids).unlink() - self.env['res.currency.rate'].create({ - 'rate': 0.5, - 'currency_id': self.currency_data['currency'].id, - 'name': datetime.today().date(), - }) - self.pos_config.journal_id.write({ - 'currency_id': self.currency_data['currency'].id - }) - other_pricelist = self.env['product.pricelist'].create({ - 'name': 'Public Pricelist Other', - 'currency_id': self.currency_data['currency'].id, - }) - self.pos_config.write({ - 'pricelist_id': other_pricelist.id, - 'available_pricelist_ids': [(6, 0, other_pricelist.ids)], - }) - self.pos_config.open_ui() - current_session = self.pos_config.current_session_id - - # create pos order - order = self.PosOrder.create({ - 'company_id': self.env.company.id, - 'session_id': current_session.id, - 'partner_id': self.partner1.id, - 'lines': [(0, 0, { - 'name': "OL/0001", - 'product_id': self.product1.id, - 'price_unit': 6, - 'discount': 0, - 'qty': 1, - 'tax_ids': [[6, False, []]], - 'price_subtotal': 6, - 'price_subtotal_incl': 6, - })], - 'pricelist_id': self.pos_config.pricelist_id.id, - 'amount_paid': 6.0, - 'amount_total': 6.0, - 'amount_tax': 0.0, - 'amount_return': 0.0, - 'to_invoice': False, - 'last_order_preparation_change': '{}' - }) - - #pay for the order with customer account - payment_context = {"active_ids": order.ids, "active_id": order.id} - order_payment = self.PosMakePayment.with_context(**payment_context).create({ - 'amount': 2.0, - 'payment_method_id': self.cash_payment_method.id - }) - order_payment.with_context(**payment_context).check() - - payment_context = {"active_ids": order.ids, "active_id": order.id} - order_payment = self.PosMakePayment.with_context(**payment_context).create({ - 'amount': 4.0, - 'payment_method_id': self.customer_account_payment_method.id - }) - order_payment.with_context(**payment_context).check() - - # close session - current_session.action_pos_session_closing_control() - - # create invoice - order.action_pos_order_invoice() - #get journal entry that does the reverse payment, it the ref must contains Reversal - reverse_payment = self.env['account.move'].search([('ref', 'ilike', "Reversal")]) - original_payment = self.env['account.move'].search([('ref', '=', current_session.display_name)]) - original_customer_payment_entry = original_payment.line_ids.filtered(lambda l: l.account_id.account_type == 'asset_receivable') - reverser_customer_payment_entry = reverse_payment.line_ids.filtered(lambda l: l.account_id.account_type == 'asset_receivable') - - #check that both use the same account - self.assertEqual(len(reverser_customer_payment_entry), 2) - self.assertTrue(order.account_move.line_ids.partner_id == self.partner1.commercial_partner_id) - self.assertEqual(reverser_customer_payment_entry[0].balance, -4.0) - self.assertEqual(reverser_customer_payment_entry[1].balance, -8.0) - self.assertEqual(reverser_customer_payment_entry[0].amount_currency, -2.0) - self.assertEqual(reverser_customer_payment_entry[1].amount_currency, -4.0) - self.assertEqual(original_customer_payment_entry.account_id.id, reverser_customer_payment_entry.account_id.id) - self.assertEqual(reverser_customer_payment_entry.partner_id, original_customer_payment_entry.partner_id) - def test_order_total_subtotal_account_line_values(self): self.tax1 = self.env['account.tax'].create({ 'name': 'Tax 1',