Repository: OCA/bank-payment

Issue:
[12.0][BUG] account_payment_sale
Hi
I have found a bug in module account_payment_sale, but I am not sure how to fix it nicely.
The payment_mode_id does not propagate from the sale order to the invoice. 
I guess the tests are a bit to naive, that is why they pass anyway.
Here we try to propagate the payment mode : https://github.com/OCA/bank-payment/blob/12.0/account_payment_sale/models/sale_order.py#L35
Here, the invoice is created with the right value (coming from the SO) : https://github.com/OCA/OCB/blob/12.0/addons/sale/models/sale.py#L521
And it is overriden here https://github.com/OCA/OCB/blob/12.0/addons/sale/models/sale.py#L570

I really don't get why they have refactored it this way, they create the invoice and then they override a lot of values...
And I do not really see a clean solution to solve this.
Any idea?


Discussion:
Oh, that's very ugly by Odoo...

I suppose we have to override `action_invoice_create` method, and write again after calling super the value of the payment mode if differs from the one in the sale order.
@florian-dacosta @pedrobaeza Anything new about this? The problem is the same with the [mandate](https://github.com/OCA/bank-payment/pull/533). 
I'm afraid the bug is out there and we need to do the commented hack.
@florian-dacosta Hi, any news about this bug?
Hi, I did not took the time to solve it, no.
Actually, I just found the bug when  migrating sale_automatic_workflow_payment_mode, but I do not use these modules in version 12 so it has not been my priority...
Also, I did not find a clean way to fix it...
@florian-dacosta thanks for the feedback! I'll try to fix it with the workaround proposed by @pedrobaeza.
I was wondering if it was possible to propose a refactore to Odoo... But I guess it won't happen so sadly, it seems like the unique solution.
But, I don't think it is good enought to just fix it in this module, as @ThomasBinsfeld said, the bug is the same with the mandate, and I guess potentially other modules.
We can't do this hack in all modules, or else it will be so performance greedy with several of the concerned modules installed...

There is a PR on odoo to help overriding here : https://github.com/odoo/odoo/pull/31529

It has to be fixed.
Fixed.

@florian-dacosta @pedrobaeza @sebalix Could you review it ?
@rousseldenis Thanks to take the time to try to fix it.
Let's see if Odoo accepts the hook...
fixing pr is merged
@florian-dacosta bugfix is merged in odoo. This should unlock https://github.com/OCA/sale-workflow/pull/786 as well.
@simahawk 
AFAIK, what was merged in Odoo was just a refactore, but there was no bug in Odoo.
The bug is in account_payment_sale and the refactore allow us to fix it.
So, now, we need to fix account_payment_sale and add a test to cover the failing use case.
It should be quite quick and easy to do, I don't really have time right now, but I'll see if I can do it in the next days/weeks...
In the worst case scenario, I'll do it during OCA days if nobody did this before!

Resolving patch:
diff --git a/account_payment_sale/models/sale_order.py b/account_payment_sale/models/sale_order.py
--- a/account_payment_sale/models/sale_order.py
+++ b/account_payment_sale/models/sale_order.py
@@ -33,3 +33,31 @@ def _prepare_invoice(self):
         """Copy bank partner from sale order to invoice"""
         vals = super()._prepare_invoice()
         return self._get_payment_mode_vals(vals)
+
+    def _finalize_invoices(self, invoices, references):
+        """
+        Invoked after creating invoices at the end of action_invoice_create.
+
+        We must override this method since the onchange on partner is called by
+        the base method and therefore will change the specific payment_mode set
+        on the SO if one is defined on the partner..
+
+        :param invoices: {group_key: invoice}
+        :param references: {invoice: order}
+        """
+        payment_vals_by_invoice = {}
+        for invoice in invoices.values():
+            payment_vals_by_invoice[invoice] = {
+                'payment_mode_id': invoice.payment_mode_id.id,
+                'partner_bank_id': invoice.partner_bank_id.id
+            }
+        res = super()._finalize_invoices(invoices, references)
+        for invoice in invoices.values():
+            payment_vals = payment_vals_by_invoice[invoice]
+            if invoice.payment_mode_id.id == payment_vals['payment_mode_id']:
+                payment_vals.pop("payment_mode_id")
+            if invoice.partner_bank_id.id == payment_vals["partner_bank_id"]:
+                payment_vals.pop("partner_bank_id")
+            if payment_vals:
+                invoice.write(payment_vals)
+        return res


Test patch:
diff --git a/account_payment_sale/tests/common.py b/account_payment_sale/tests/common.py
--- a/account_payment_sale/tests/common.py
+++ b/account_payment_sale/tests/common.py
@@ -30,6 +30,15 @@ def setUpClass(cls):
             'bank_account_link': 'fixed',
             'fixed_journal_id': cls.journal.id,
         })
+        cls.payment_mode_2 = cls.env["account.payment.mode"].create({
+            'name': 'test_mode_2',
+            'active': True,
+            'payment_method_id': cls.env.ref(
+                "account.account_payment_method_manual_in"
+            ).id,
+            'bank_account_link': 'fixed',
+            'fixed_journal_id': cls.journal.id,
+        })
         cls.base_partner = cls.env["res.partner"].create({
             'name': 'Dummy',
             'email': 'dummy@example.com',
diff --git a/account_payment_sale/tests/test_sale_order.py b/account_payment_sale/tests/test_sale_order.py
--- a/account_payment_sale/tests/test_sale_order.py
+++ b/account_payment_sale/tests/test_sale_order.py
@@ -5,57 +5,102 @@
 
 
 class TestSaleOrder(CommonTestCase):
-
-    def create_sale_order(self):
-        so_lines = [(0, 0, {
-            'name': p.name,
-            'product_id': p.id,
-            'product_uom_qty': 2,
-            'product_uom': p.uom_id.id,
-            'price_unit': p.list_price,
-        }) for (_, p) in self.products.items()]
-        so = self.env['sale.order'].create({
-            'partner_id': self.base_partner.id,
-            'partner_invoice_id': self.base_partner.id,
-            'partner_shipping_id': self.base_partner.id,
-            'order_line': so_lines,
-            'pricelist_id': self.env.ref('product.list0').id,
-        })
+    def create_sale_order(self, payment_mode=None):
+        so_lines = [
+            (
+                0,
+                0,
+                {
+                    "name": p.name,
+                    "product_id": p.id,
+                    "product_uom_qty": 2,
+                    "product_uom": p.uom_id.id,
+                    "price_unit": p.list_price,
+                },
+            )
+            for (_, p) in self.products.items()
+        ]
+        so = self.env["sale.order"].create(
+            {
+                "partner_id": self.base_partner.id,
+                "partner_invoice_id": self.base_partner.id,
+                "partner_shipping_id": self.base_partner.id,
+                "order_line": so_lines,
+                "pricelist_id": self.env.ref("product.list0").id,
+            }
+        )
         self.assertFalse(so.payment_mode_id)
         so.onchange_partner_id()
         self.assertEqual(
-            so.payment_mode_id,
-            self.base_partner.customer_payment_mode_id
+            so.payment_mode_id, self.base_partner.customer_payment_mode_id
         )
+        # force payment mode
+        if payment_mode:
+            so.payment_mode_id = payment_mode.id
         return so
 
+    def create_invoice_and_check(
+        self, order, expected_payment_mode, expected_partner_bank
+    ):
+        order.action_confirm()
+        order.action_invoice_create()
+        invoice = order.invoice_ids
+        self.assertEqual(len(invoice), 1)
+        self.assertEqual(invoice.payment_mode_id, expected_payment_mode)
+        self.assertEqual(invoice.partner_bank_id, expected_partner_bank)
+
     def test_sale_to_invoice_payment_mode(self):
+        """
+        Data:
+            A partner with a specific payment_mode
+            A sale order created with the payment_mode of the partner
+        Test case:
+            Create the invoice from the sale order
+        Expected result:
+            The invoice must be created with the payment_mode of the partner
+        """
         order = self.create_sale_order()
+        self.create_invoice_and_check(order, self.payment_mode, self.bank)
+
+    def test_sale_to_invoice_payment_mode_2(self):
+        """
+        Data:
+            A partner with a specific payment_mode
+            A sale order created with an other payment_mode
+        Test case:
+            Create the invoice from the sale order
+        Expected result:
+            The invoice must be created with the specific payment_mode
+        """
+        order = self.create_sale_order(payment_mode=self.payment_mode_2)
+        self.create_invoice_and_check(order, self.payment_mode_2, self.bank)
+
+    def test_sale_to_invoice_payment_mode_via_payment(self):
+        """
+        Data:
+            A partner with a specific payment_mode
+            A sale order created with an other payment_mode
+        Test case:
+            Create the invoice from sale.advance.payment.inv
+        Expected result:
+            The invoice must be created with the specific payment_mode
+        """
+        order = self.create_sale_order(payment_mode=self.payment_mode_2)
         context = {
-            "active_model": 'sale.order',
+            "active_model": "sale.order",
             "active_ids": [order.id],
             "active_id": order.id,
         }
-        order.with_context(context).action_confirm()
-        payment = self.env['sale.advance.payment.inv'].create({
-            'advance_payment_method': 'fixed',
-            'amount': 5,
-            'product_id': self.env.ref('sale.advance_product_0').id,
-        })
-        vals = order._get_payment_mode_vals({})
-        expected_vals = {
-            'payment_mode_id': self.payment_mode.id,
-            'partner_bank_id': self.bank.id,
-        }
-        self.assertEqual(vals, expected_vals)
+        order.action_confirm()
+        payment = self.env["sale.advance.payment.inv"].create(
+            {
+                "advance_payment_method": "fixed",
+                "amount": 5,
+                "product_id": self.env.ref("sale.advance_product_0").id,
+            }
+        )
         payment.with_context(context).create_invoices()
         invoice = order.invoice_ids
         self.assertEqual(len(invoice), 1)
-        self.assertEqual(
-            invoice.payment_mode_id.id,
-            expected_vals["payment_mode_id"]
-        )
-        self.assertEqual(
-            invoice.partner_bank_id.id,
-            expected_vals["partner_bank_id"]
-        )
+        self.assertEqual(invoice.payment_mode_id, self.payment_mode_2)
+        self.assertEqual(invoice.partner_bank_id, self.bank)