From fa2fb9f7e3456b576ff981ecdd602f1f8f8d2f5c Mon Sep 17 00:00:00 2001
From: Joseph Caburnay <jcb@odoo.com>
Date: Mon, 10 May 2021 08:50:42 +0000
Subject: [PATCH] [FIX] point_of_sale: group invoice receivable lines by
 partner

Acronyms:

SRL : receivable lines in the session's account move that balances
the receivable lines in the session's invoices
IRL : receivable lines of the invoices in the pos session

When closing a pos session, a single account move is created to capture
all the transactions, but, invoices are kept (each is an account move).
During the construction of this single account move, SRLs are
reconciled with IRLs.

Prior to this commit, when closing a pos session, SRLs are grouped
by `property_account_receivable_id` of the partners in the invoices.
Because of this, SRLs are blind of the partners that they are linked
to. This causes issues on tracking the 'due' in the partners that are
invoiced using the pos application.

To fix the issue, we now group the SRLs by partner before reconciling
them the the IRLs.

Same concept is employed for receivable lines generated from split
cash payments, we also assigned partner to them if applicable.

closes odoo/odoo#70580

X-original-commit: ebb4f30
Signed-off-by: agr-odoo <agr-odoo@users.noreply.github.com>
---
 addons/point_of_sale/models/pos_session.py    | 27 ++++++----
 addons/point_of_sale/tests/common.py          |  9 +++-
 .../tests/test_pos_basic_config.py            | 50 +++++++++++++++++++
 3 files changed, 76 insertions(+), 10 deletions(-)

diff --git a/addons/point_of_sale/models/pos_session.py b/addons/point_of_sale/models/pos_session.py
index 73d5c85d1d5e..65ae1567a7da 100644
--- a/addons/point_of_sale/models/pos_session.py
+++ b/addons/point_of_sale/models/pos_session.py
@@ -457,7 +457,7 @@ class PosSession(models.Model):
 
             if order.is_invoiced:
                 # Combine invoice receivable lines
-                key = order.partner_id.property_account_receivable_id.id
+                key = order.partner_id
                 if self.config_id.cash_rounding:
                     invoice_receivables[key] = self._update_amounts(invoice_receivables[key], {'amount': order.amount_paid}, order.date_order)
                 else:
@@ -612,7 +612,7 @@ class PosSession(models.Model):
         split_cash_receivable_vals = defaultdict(list)
         for payment, amounts in split_receivables_cash.items():
             statement = statements_by_journal_id[payment.payment_method_id.cash_journal_id.id]
-            split_cash_statement_line_vals[statement].append(self._get_statement_line_vals(statement, payment.payment_method_id.receivable_account_id, amounts['amount'], payment.payment_date))
+            split_cash_statement_line_vals[statement].append(self._get_statement_line_vals(statement, payment.payment_method_id.receivable_account_id, amounts['amount'], date=payment.payment_date, partner=payment.pos_order_id.partner_id))
             split_cash_receivable_vals[statement].append(self._get_split_receivable_vals(payment, amounts['amount'], amounts['amount_converted']))
         # handle combine cash payments
         combine_cash_statement_line_vals = defaultdict(list)
@@ -651,12 +651,18 @@ class PosSession(models.Model):
 
         invoice_receivable_vals = defaultdict(list)
         invoice_receivable_lines = {}
-        for receivable_account_id, amounts in invoice_receivables.items():
-            invoice_receivable_vals[receivable_account_id].append(self._get_invoice_receivable_vals(receivable_account_id, amounts['amount'], amounts['amount_converted']))
-        for receivable_account_id, vals in invoice_receivable_vals.items():
+        for partner, amounts in invoice_receivables.items():
+            commercial_partner = partner.commercial_partner_id
+            account_id = commercial_partner.property_account_receivable_id.id
+            invoice_receivable_vals[commercial_partner].append(self._get_invoice_receivable_vals(account_id, amounts['amount'], amounts['amount_converted'], partner=commercial_partner))
+        for commercial_partner, vals in invoice_receivable_vals.items():
+            account_id = commercial_partner.property_account_receivable_id.id
             receivable_line = MoveLine.create(vals)
             if (not receivable_line.reconciled):
-                invoice_receivable_lines[receivable_account_id] = receivable_line
+                if account_id not in invoice_receivable_lines:
+                    invoice_receivable_lines[account_id] = receivable_line
+                else:
+                    invoice_receivable_lines[account_id] |= receivable_line
 
         data.update({'invoice_receivable_lines': invoice_receivable_lines})
         return data
@@ -805,11 +811,13 @@ class PosSession(models.Model):
         }
         return self._debit_amounts(partial_vals, amount, amount_converted)
 
-    def _get_invoice_receivable_vals(self, account_id, amount, amount_converted):
+    def _get_invoice_receivable_vals(self, account_id, amount, amount_converted, **kwargs):
+        partner = kwargs.get('partner', False)
         partial_vals = {
             'account_id': account_id,
             'move_id': self.move_id.id,
-            'name': 'From invoiced orders'
+            'name': 'From invoiced orders',
+            'partner_id': partner and partner.id or False,
         }
         return self._credit_amounts(partial_vals, amount, amount_converted)
 
@@ -851,7 +859,7 @@ class PosSession(models.Model):
         partial_args = {'account_id': out_account.id, 'move_id': self.move_id.id}
         return self._credit_amounts(partial_args, amount, amount_converted, force_company_currency=True)
 
-    def _get_statement_line_vals(self, statement, receivable_account, amount, date=False):
+    def _get_statement_line_vals(self, statement, receivable_account, amount, date=False, partner=False):
         return {
             'date': fields.Date.context_today(self, timestamp=date),
             'amount': amount,
@@ -859,6 +867,7 @@ class PosSession(models.Model):
             'statement_id': statement.id,
             'journal_id': statement.journal_id.id,
             'counterpart_account_id': receivable_account.id,
+            'partner_id': partner and self.env["res.partner"]._find_accounting_partner(partner).id
         }
 
     def _update_amounts(self, old_amounts, amounts_to_add, date, round=True, force_company_currency=False):
diff --git a/addons/point_of_sale/tests/common.py b/addons/point_of_sale/tests/common.py
index b0518136792a..cf51f9fe4e3f 100644
--- a/addons/point_of_sale/tests/common.py
+++ b/addons/point_of_sale/tests/common.py
@@ -258,7 +258,12 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
             'is_cash_count': True,
             'cash_journal_id': cls.company_data['default_journal_cash'].id,
         })
-        config.write({'payment_method_ids': [(4, cash_split_pm.id), (4, cash_payment_method.id), (4, bank_payment_method.id)]})
+        bank_split_pm = cls.env['pos.payment.method'].create({
+            'name': 'Split (Bank) PM',
+            'receivable_account_id': cls.pos_receivable_account.id,
+            'split_transactions': True,
+        })
+        config.write({'payment_method_ids': [(4, cash_split_pm.id), (4, bank_split_pm.id), (4, cash_payment_method.id), (4, bank_payment_method.id)]})
         return config
 
     @classmethod
@@ -506,6 +511,7 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
             * cash_pm : cash payment method of the session
             * bank_pm : bank payment method of the session
             * cash_split_pm : credit payment method of the session
+            * bank_split_pm : split bank payment method of the session
         """
         self.config.open_session_cb(check_coa=False)
         self.pos_session = self.config.current_session_id
@@ -514,3 +520,4 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
         self.cash_pm = self.pos_session.payment_method_ids.filtered(lambda pm: pm.is_cash_count and not pm.split_transactions)[:1]
         self.bank_pm = self.pos_session.payment_method_ids.filtered(lambda pm: not pm.is_cash_count and not pm.split_transactions)[:1]
         self.cash_split_pm = self.pos_session.payment_method_ids.filtered(lambda pm: pm.is_cash_count and pm.split_transactions)[:1]
+        self.bank_split_pm = self.pos_session.payment_method_ids.filtered(lambda pm: not pm.is_cash_count and pm.split_transactions)[:1]
diff --git a/addons/point_of_sale/tests/test_pos_basic_config.py b/addons/point_of_sale/tests/test_pos_basic_config.py
index 119d4c396bad..718f10f3a67d 100644
--- a/addons/point_of_sale/tests/test_pos_basic_config.py
+++ b/addons/point_of_sale/tests/test_pos_basic_config.py
@@ -534,3 +534,53 @@ class TestPoSBasicConfig(TestPoSCommon):
 
         rounding_line = session_account_move.line_ids.filtered(lambda line: line.name == 'Rounding line')
         self.assertAlmostEqual(rounding_line.credit, 0.03, msg='The credit should be equals to 0.03')
+
+    def test_correct_partner_on_invoice_receivables(self):
+        self.open_new_session()
+
+        # create orders
+        # each order with total amount of 100.
+        orders = []
+        # from 1st to 8th order: use the same customer (self.customer) but varies with is_invoiced and payment method.
+        orders.append(self.create_ui_order_data([(self.product1, 10)], payments=[(self.cash_pm, 100)], customer=self.customer, is_invoiced=True, uid='00100-010-0001'))
+        orders.append(self.create_ui_order_data([(self.product1, 10)], payments=[(self.bank_pm, 100)], customer=self.customer, is_invoiced=True, uid='00100-010-0002'))
+        orders.append(self.create_ui_order_data([(self.product1, 10)], payments=[(self.cash_split_pm, 100)], customer=self.customer, is_invoiced=True, uid='00100-010-0003'))
+        orders.append(self.create_ui_order_data([(self.product1, 10)], payments=[(self.bank_split_pm, 100)], customer=self.customer, is_invoiced=True, uid='00100-010-0004'))
+        orders.append(self.create_ui_order_data([(self.product1, 10)], payments=[(self.cash_pm, 100)], customer=self.customer, uid='00100-010-0005'))
+        orders.append(self.create_ui_order_data([(self.product1, 10)], payments=[(self.bank_pm, 100)], customer=self.customer, uid='00100-010-0006'))
+        orders.append(self.create_ui_order_data([(self.product1, 10)], payments=[(self.cash_split_pm, 100)], customer=self.customer, uid='00100-010-0007'))
+        orders.append(self.create_ui_order_data([(self.product1, 10)], payments=[(self.bank_split_pm, 100)], customer=self.customer, uid='00100-010-0008'))
+        # 9th and 10th orders for self.other_customer, both invoiced and paid by bank
+        orders.append(self.create_ui_order_data([(self.product1, 10)], payments=[(self.bank_pm, 100)], customer=self.other_customer, is_invoiced=True, uid='00100-010-0009'))
+        orders.append(self.create_ui_order_data([(self.product1, 10)], payments=[(self.bank_pm, 100)], customer=self.other_customer, is_invoiced=True, uid='00100-010-0010'))
+        # 11th order: invoiced to self.customer with bank payment method
+        orders.append(self.create_ui_order_data([(self.product1, 10)], payments=[(self.bank_pm, 100)], customer=self.customer, is_invoiced=True, uid='00100-010-0011'))
+
+        # sync orders
+        order = self.env['pos.order'].create_from_ui(orders)
+
+        # close the session
+        self.pos_session.action_pos_session_validate()
+
+        # self.customer's bank split payments
+        customer_pos_receivable_bank = self.pos_session.move_id.line_ids.filtered(lambda line: line.partner_id == self.customer and 'Split (Bank) PM' in line.name)
+        self.assertEqual(len(customer_pos_receivable_bank), 2, msg='there are 2 bank split payments from self.customer')
+        self.assertEqual(bool(customer_pos_receivable_bank.full_reconcile_id), False, msg="the pos (bank) receivable lines shouldn't be reconciled")
+
+        # self.customer's cash split payments
+        customer_pos_receivable_cash = self.pos_session.move_id.line_ids.filtered(lambda line: line.partner_id == self.customer and 'Split (Cash) PM' in line.name)
+        self.assertEqual(len(customer_pos_receivable_cash), 2, msg='there are 2 cash split payments from self.customer')
+        self.assertEqual(bool(customer_pos_receivable_cash.full_reconcile_id), True, msg="cash pos (cash) receivable lines should be reconciled")
+
+        # self.customer's invoice receivable counterpart
+        customer_invoice_receivable_counterpart = self.pos_session.move_id.line_ids.filtered(lambda line: line.partner_id == self.customer and 'From invoiced orders' in line.name)
+        self.assertEqual(len(customer_invoice_receivable_counterpart), 1, msg='there should one aggregated invoice receivable counterpart for self.customer')
+        self.assertEqual(bool(customer_invoice_receivable_counterpart.full_reconcile_id), True, msg='the aggregated receivable for self.customer should be reconciled')
+        self.assertEqual(customer_invoice_receivable_counterpart.balance, -500, msg='aggregated balance should be -500')
+
+        # self.other_customer also made invoiced orders
+        # therefore, it should also have aggregated receivable counterpart in the session's account_move
+        other_customer_invoice_receivable_counterpart = self.pos_session.move_id.line_ids.filtered(lambda line: line.partner_id == self.other_customer and 'From invoiced orders' in line.name)
+        self.assertEqual(len(other_customer_invoice_receivable_counterpart), 1, msg='there should one aggregated invoice receivable counterpart for self.other_customer')
+        self.assertEqual(bool(other_customer_invoice_receivable_counterpart.full_reconcile_id), True, msg='the aggregated receivable for self.other_customer should be reconciled')
+        self.assertEqual(other_customer_invoice_receivable_counterpart.balance, -200, msg='aggregated balance should be -200')
-- 
GitLab