From 3bc92b0f102f97450490349fa3a926d41294f068 Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Sun, 16 Jan 2022 13:28:41 -0500 Subject: [PATCH 1/5] [IMP] account_payment_order: outbound inheritable + SavepointCase This commit split the tests for outbound payment in a base inheritable class that can be reused in other dependant modules, and the test of this module. similar to https://github.com/OCA/bank-payment/commit/86bd1a2525d45f4a2eb9146b4a0ef679068d3649 --- account_payment_order/tests/test_payment_order_outbound.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/account_payment_order/tests/test_payment_order_outbound.py b/account_payment_order/tests/test_payment_order_outbound.py index ad867f249..6b60bc611 100644 --- a/account_payment_order/tests/test_payment_order_outbound.py +++ b/account_payment_order/tests/test_payment_order_outbound.py @@ -12,7 +12,7 @@ from odoo.addons.account.tests.common import AccountTestInvoicingCommon @tagged("-at_install", "post_install") -class TestPaymentOrderOutbound(AccountTestInvoicingCommon): +class TestPaymentOrderOutboundBase(AccountTestInvoicingCommon): @classmethod def setUpClass(cls, chart_template_ref=None): super().setUpClass(chart_template_ref=chart_template_ref) @@ -87,6 +87,9 @@ class TestPaymentOrderOutbound(AccountTestInvoicingCommon): return invoice + +@tagged("-at_install", "post_install") +class TestPaymentOrderOutbound(TestPaymentOrderOutboundBase): def test_creation_due_date(self): self.mode.variable_journal_ids = self.bank_journal self.mode.group_lines = False From 784c6b9e61415ef9042c9a57396e63e235dbdced Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Mon, 17 Jan 2022 08:53:50 +0100 Subject: [PATCH 2/5] [FIX] account_payment_order: Proper indentation for test + make it work The test introduced in 7bdb286aa1a3d5beb8ee95193ed6eb4abc67ce1e is not valid, as its indentation made it to not be executed. Anyways, the content and the tested things are also not correct. --- .../tests/test_payment_order_outbound.py | 131 +++++++----------- 1 file changed, 48 insertions(+), 83 deletions(-) diff --git a/account_payment_order/tests/test_payment_order_outbound.py b/account_payment_order/tests/test_payment_order_outbound.py index 6b60bc611..ac6ff811a 100644 --- a/account_payment_order/tests/test_payment_order_outbound.py +++ b/account_payment_order/tests/test_payment_order_outbound.py @@ -219,87 +219,52 @@ class TestPaymentOrderOutbound(TestPaymentOrderOutboundBase): with self.assertRaises(ValidationError): outbound_order.date_scheduled = date.today() - timedelta(days=2) - -def test_manual_line_and_manual_date(self): - # Create payment order - outbound_order = self.env["account.payment.order"].create( - { - "date_prefered": "due", - "payment_type": "outbound", - "payment_mode_id": self.mode.id, - "journal_id": self.journal.id, - "description": "order with manual line", + def test_manual_line_and_manual_date(self): + # Create payment order + outbound_order = self.env["account.payment.order"].create( + { + "date_prefered": "due", + "payment_type": "outbound", + "payment_mode_id": self.mode.id, + "journal_id": self.bank_journal.id, + "description": "order with manual line", + } + ) + self.assertEqual(len(outbound_order.payment_line_ids), 0) + # Create a manual payment order line with custom date + vals = { + "order_id": outbound_order.id, + "partner_id": self.partner.id, + "communication": "manual line and manual date", + "currency_id": outbound_order.payment_mode_id.company_id.currency_id.id, + "amount_currency": 192.38, + "date": date.today() + timedelta(days=8), } - ) - self.assertEqual(len(outbound_order.payment_line_ids), 0) - # Create a manual payment order line with custom date - vals = { - "order_id": outbound_order.id, - "partner_id": self.env.ref("base.res_partner_4").id, - "partner_bank_id": self.env.ref("base.res_partner_4").bank_ids[0].id, - "communication": "manual line and manual date", - "currency_id": outbound_order.payment_mode_id.company_id.currency_id.id, - "amount_currency": 192.38, - "date": date.today() + timedelta(days=8), - } - self.env["account.payment.line"].create(vals) - - self.assertEqual(len(outbound_order.payment_line_ids), 1) - self.assertEqual( - outbound_order.payment_line_ids[0].date, date.today() + timedelta(days=8) - ) - - # Create a manual payment order line with normal date - vals = { - "order_id": outbound_order.id, - "partner_id": self.env.ref("base.res_partner_4").id, - "partner_bank_id": self.env.ref("base.res_partner_4").bank_ids[0].id, - "communication": "manual line", - "currency_id": outbound_order.payment_mode_id.company_id.currency_id.id, - "amount_currency": 200.38, - } - self.env["account.payment.line"].create(vals) - - self.assertEqual(len(outbound_order.payment_line_ids), 2) - self.assertEqual(outbound_order.payment_line_ids[1].date, False) - - # Open payment order - self.assertEqual(len(outbound_order.bank_line_ids), 0) - outbound_order.draft2open() - self.assertEqual(outbound_order.bank_line_count, 2) - self.assertEqual( - outbound_order.payment_line_ids[0].date, - outbound_order.payment_line_ids[0].bank_line_id.date, - ) - self.assertEqual( - outbound_order.payment_line_ids[1].date, - fields.Date.context_today(outbound_order), - ) - self.assertEqual( - outbound_order.payment_line_ids[1].bank_line_id.date, - fields.Date.context_today(outbound_order), - ) - # Generate and upload - outbound_order.open2generated() - outbound_order.generated2uploaded() - - self.assertEqual(outbound_order.state, "uploaded") - with self.assertRaises(UserError): - outbound_order.unlink() - - bank_line = outbound_order.bank_line_ids - - with self.assertRaises(UserError): - bank_line.unlink() - outbound_order.action_done_cancel() - self.assertEqual(outbound_order.state, "cancel") - outbound_order.cancel2draft() - outbound_order.unlink() - self.assertEqual( - len( - self.env["account.payment.order"].search( - [("description", "=", "order with manual line")] - ) - ), - 0, - ) + self.env["account.payment.line"].create(vals) + self.assertEqual(len(outbound_order.payment_line_ids), 1) + self.assertEqual( + outbound_order.payment_line_ids[0].date, date.today() + timedelta(days=8) + ) + # Create a manual payment order line with normal date + vals = { + "order_id": outbound_order.id, + "partner_id": self.partner.id, + "communication": "manual line", + "currency_id": outbound_order.payment_mode_id.company_id.currency_id.id, + "amount_currency": 200.38, + } + self.env["account.payment.line"].create(vals) + self.assertEqual(len(outbound_order.payment_line_ids), 2) + self.assertEqual(outbound_order.payment_line_ids[1].date, False) + # Open payment order + self.assertEqual(len(outbound_order.bank_line_ids), 0) + outbound_order.draft2open() + self.assertEqual(outbound_order.bank_line_count, 2) + self.assertEqual( + outbound_order.payment_line_ids[0].date, + outbound_order.payment_line_ids[0].bank_line_id.date, + ) + self.assertEqual(outbound_order.payment_line_ids[1].date, date.today()) + self.assertEqual( + outbound_order.payment_line_ids[1].bank_line_id.date, date.today() + ) From eedc6300b5209e5878c20fead926f35db3bd9e26 Mon Sep 17 00:00:00 2001 From: Denis Roussel Date: Thu, 14 Apr 2022 09:26:00 +0200 Subject: [PATCH 3/5] [IMP] account_payment_order: Better communication field management - Add a hook method to retrieve communication type and communication - Improve normal communication if there is a credit note If there is a credit note that partially cancel an invoice, the payment communication should be the combination of the invoice reference and the credit note one. - Remove not needed assert as parameter is required - Use the 'payment_reference' field if filled in - Add existing payment references to communication If some movements have been reconciled with the original invoice, their references should be added in communication too. e.g.: Manual credit notes - Don't duplicate communication reference --- .../models/account_move_line.py | 51 +++++- account_payment_order/readme/CONTRIBUTORS.rst | 2 + .../tests/test_payment_order_outbound.py | 148 +++++++++++++++++- 3 files changed, 196 insertions(+), 5 deletions(-) diff --git a/account_payment_order/models/account_move_line.py b/account_payment_order/models/account_move_line.py index 806745d9f..0e2a6ff6f 100644 --- a/account_payment_order/models/account_move_line.py +++ b/account_payment_order/models/account_move_line.py @@ -48,9 +48,44 @@ class AccountMoveLine(models.Model): else: ml.partner_bank_id = ml.partner_bank_id - def _prepare_payment_line_vals(self, payment_order): + def _get_linked_move_communication(self): + """ + This will collect the references from referral moves: + - Reversal moves + - Partial payments + """ self.ensure_one() - assert payment_order, "Missing payment order" + references = [] + # Build a recordset to gather moves from which references have already + # taken in order to avoid duplicates + reference_moves = self.env["account.move"].browse() + # If we have credit note(s) - reversal_move_id is a one2many + if self.move_id.reversal_move_id: + references.extend( + [ + move.payment_reference or move.ref + for move in self.move_id.reversal_move_id + if move.payment_reference or move.ref + ] + ) + reference_moves |= self.move_id.reversal_move_id + # Retrieve partial payments - e.g.: manual credit notes + for ( + _, + _, + payment_move_line, + ) in self.move_id._get_reconciled_invoices_partials(): + payment_move = payment_move_line.move_id + if payment_move not in reference_moves and ( + payment_move.payment_reference or payment_move.ref + ): + references.append(payment_move.payment_reference or payment_move.ref) + return references + + def _get_communication(self): + """ + Retrieve the communication string for the payment order + """ aplo = self.env["account.payment.line"] # default values for communication_type and communication communication_type = "normal" @@ -66,10 +101,18 @@ class AccountMoveLine(models.Model): self.move_id.move_type in ("in_invoice", "in_refund") and self.move_id.ref ): - communication = self.move_id.ref + communication = self.move_id.payment_reference or self.move_id.ref elif "out" in self.move_id.move_type: # Force to only put invoice number here - communication = self.move_id.name + communication = self.move_id.payment_reference or self.move_id.name + references = self._get_linked_move_communication() + if references: + communication += " " + " ".join(references) + return communication_type, communication + + def _prepare_payment_line_vals(self, payment_order): + self.ensure_one() + communication_type, communication = self._get_communication() if self.currency_id: currency_id = self.currency_id.id amount_currency = self.amount_residual_currency diff --git a/account_payment_order/readme/CONTRIBUTORS.rst b/account_payment_order/readme/CONTRIBUTORS.rst index 317e603ce..c6d4d1c03 100644 --- a/account_payment_order/readme/CONTRIBUTORS.rst +++ b/account_payment_order/readme/CONTRIBUTORS.rst @@ -11,6 +11,8 @@ * Angel Moya * Jose María Alzaga * Meyomesse Gilles +* Denis Roussel + * `DynApps `_: * Raf Ven diff --git a/account_payment_order/tests/test_payment_order_outbound.py b/account_payment_order/tests/test_payment_order_outbound.py index ac6ff811a..1bae28cb8 100644 --- a/account_payment_order/tests/test_payment_order_outbound.py +++ b/account_payment_order/tests/test_payment_order_outbound.py @@ -6,7 +6,7 @@ from datetime import date, datetime, timedelta from odoo import fields from odoo.exceptions import UserError, ValidationError -from odoo.tests import tagged +from odoo.tests import Form, tagged from odoo.addons.account.tests.common import AccountTestInvoicingCommon @@ -87,6 +87,45 @@ class TestPaymentOrderOutboundBase(AccountTestInvoicingCommon): return invoice + def _create_supplier_refund(self, move, manual=False): + if manual: + # Do the supplier refund manually + vals = { + "partner_id": self.partner.id, + "move_type": "in_refund", + "ref": move.ref, + "payment_mode_id": self.mode.id, + "invoice_date": fields.Date.today(), + "invoice_line_ids": [ + ( + 0, + None, + { + "product_id": self.env.ref("product.product_product_4").id, + "quantity": 1.0, + "price_unit": 90.0, + "name": "refund of 90.0", + "account_id": self.invoice_line_account.id, + }, + ) + ], + } + move = self.env["account.move"].create(vals) + return move + wizard = ( + self.env["account.move.reversal"] + .with_context(active_model="account.move", active_ids=move.ids) + .create( + { + "date_mode": "entry", + "refund_method": "refund", + "journal_id": move.journal_id.id, + } + ) + ) + wizard.reverse_moves() + return wizard.new_move_ids + @tagged("-at_install", "post_install") class TestPaymentOrderOutbound(TestPaymentOrderOutboundBase): @@ -268,3 +307,110 @@ class TestPaymentOrderOutbound(TestPaymentOrderOutboundBase): self.assertEqual( outbound_order.payment_line_ids[1].bank_line_id.date, date.today() ) + + def test_supplier_refund(self): + """ + Confirm the supplier invoice + Create a credit note based on that one with an inferior amount + Confirm the credit note + Create the payment order + The communication should be a combination of the invoice reference + and the credit note one + """ + self.invoice.action_post() + self.refund = self._create_supplier_refund(self.invoice) + with Form(self.refund) as refund_form: + refund_form.ref = "R1234" + with refund_form.invoice_line_ids.edit(0) as line_form: + line_form.price_unit = 75.0 + + self.refund.action_post() + + self.env["account.invoice.payment.line.multi"].with_context( + active_model="account.move", active_ids=self.invoice.ids + ).create({}).run() + + payment_order = self.env["account.payment.order"].search(self.domain) + self.assertEqual(len(payment_order), 1) + + payment_order.write({"journal_id": self.bank_journal.id}) + + self.assertEqual(len(payment_order.payment_line_ids), 1) + + self.assertEqual("F1242 R1234", payment_order.payment_line_ids.communication) + + def test_supplier_refund_reference(self): + """ + Confirm the supplier invoice + Set a payment referece + Create a credit note based on that one with an inferior amount + Confirm the credit note + Create the payment order + The communication should be a combination of the invoice payment reference + and the credit note one + """ + self.invoice.payment_reference = "F/1234" + self.invoice.action_post() + self.refund = self._create_supplier_refund(self.invoice) + with Form(self.refund) as refund_form: + refund_form.ref = "R1234" + with refund_form.invoice_line_ids.edit(0) as line_form: + line_form.price_unit = 75.0 + + self.refund.action_post() + + # The user add the outstanding payment to the invoice + invoice_line = self.invoice.line_ids.filtered( + lambda line: line.account_internal_type == "payable" + ) + refund_line = self.refund.line_ids.filtered( + lambda line: line.account_internal_type == "payable" + ) + (invoice_line | refund_line).reconcile() + + self.env["account.invoice.payment.line.multi"].with_context( + active_model="account.move", active_ids=self.invoice.ids + ).create({}).run() + + payment_order = self.env["account.payment.order"].search(self.domain) + self.assertEqual(len(payment_order), 1) + + payment_order.write({"journal_id": self.bank_journal.id}) + + self.assertEqual(len(payment_order.payment_line_ids), 1) + + self.assertEqual("F/1234 R1234", payment_order.payment_line_ids.communication) + + def test_supplier_manual_refund(self): + """ + Confirm the supplier invoice with reference + Create a credit note manually + Confirm the credit note + Reconcile move lines together + Create the payment order + The communication should be a combination of the invoice payment reference + and the credit note one + """ + self.invoice.action_post() + self.refund = self._create_supplier_refund(self.invoice, manual=True) + with Form(self.refund) as refund_form: + refund_form.ref = "R1234" + + self.refund.action_post() + + (self.invoice.line_ids + self.refund.line_ids).filtered( + lambda line: line.account_internal_type == "payable" + ).reconcile() + + self.env["account.invoice.payment.line.multi"].with_context( + active_model="account.move", active_ids=self.invoice.ids + ).create({}).run() + + payment_order = self.env["account.payment.order"].search(self.domain) + self.assertEqual(len(payment_order), 1) + + payment_order.write({"journal_id": self.bank_journal.id}) + + self.assertEqual(len(payment_order.payment_line_ids), 1) + + self.assertEqual("F1242 R1234", payment_order.payment_line_ids.communication) From 91421d30198a7adb6cc0fcddd2e14acea5e33e6c Mon Sep 17 00:00:00 2001 From: Bert Driehuis Date: Fri, 18 Mar 2022 17:00:57 +0100 Subject: [PATCH 4/5] [FIX] account_payment_order: Fallback to payment_ref if ref is undefined --- account_payment_order/report/account_payment_order.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/account_payment_order/report/account_payment_order.xml b/account_payment_order/report/account_payment_order.xml index 56ec2269f..bd77e9c64 100644 --- a/account_payment_order/report/account_payment_order.xml +++ b/account_payment_order/report/account_payment_order.xml @@ -101,7 +101,9 @@ - + From 676a9429f871af8546eee4d1ac57ca65ea167bae Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Tue, 8 Nov 2022 11:31:20 +0100 Subject: [PATCH 5/5] [IMP] account_payment_order: add link to payment order in chatter --- account_payment_order/i18n/account_payment_order.pot | 6 ++++-- account_payment_order/i18n/es.po | 11 +++++++++-- account_payment_order/models/account_move.py | 10 ++++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/account_payment_order/i18n/account_payment_order.pot b/account_payment_order/i18n/account_payment_order.pot index 266c326d7..ce1757686 100644 --- a/account_payment_order/i18n/account_payment_order.pot +++ b/account_payment_order/i18n/account_payment_order.pot @@ -17,14 +17,16 @@ msgstr "" #: code:addons/account_payment_order/models/account_move.py:0 #, python-format msgid "" -"%(count)d payment lines added to the existing draft payment order %(name)s." +"%(count)d payment lines added to the existing draft payment order %(name)s." msgstr "" #. module: account_payment_order #: code:addons/account_payment_order/models/account_move.py:0 #, python-format msgid "" -"%(count)d payment lines added to the new draft payment order %(name)s which " +"%(count)d payment lines added to the new draft payment order %(name)s, which " "has been automatically created." msgstr "" diff --git a/account_payment_order/i18n/es.po b/account_payment_order/i18n/es.po index 3e12028f9..a97c603bb 100644 --- a/account_payment_order/i18n/es.po +++ b/account_payment_order/i18n/es.po @@ -23,16 +23,23 @@ msgstr "" #: code:addons/account_payment_order/models/account_move.py:0 #, python-format msgid "" -"%(count)d payment lines added to the existing draft payment order %(name)s." +"%(count)d payment lines added to the existing draft payment order %(name)s." msgstr "" +"%(count)d líneas de pago añadidas a la orden existente %(name)s." #. module: account_payment_order #: code:addons/account_payment_order/models/account_move.py:0 #, python-format msgid "" -"%(count)d payment lines added to the new draft payment order %(name)s which " +"%(count)d payment lines added to the new draft payment order %(name)s, which " "has been automatically created." msgstr "" +"%(count)d líneas de pago añadidas a la nueva orden %(name)s, que " +"ha sido creada automáticamente." #. module: account_payment_order #: model_terms:ir.ui.view,arch_db:account_payment_order.print_account_payment_order_document diff --git a/account_payment_order/models/account_move.py b/account_payment_order/models/account_move.py index 917cb9d1e..0a04e895d 100644 --- a/account_payment_order/models/account_move.py +++ b/account_payment_order/models/account_move.py @@ -102,8 +102,11 @@ class AccountMove(models.Model): move.message_post( body=_( "%(count)d payment lines added to the new draft payment " - "order %(name)s which has been automatically created.", + "order %(name)s, which has been " + "automatically created.", count=count, + order_id=payorder.id, name=payorder.name, ) ) @@ -111,8 +114,11 @@ class AccountMove(models.Model): move.message_post( body=_( "%(count)d payment lines added to the existing draft " - "payment order %(name)s.", + "payment order " + "%(name)s.", count=count, + order_id=payorder.id, name=payorder.name, ) )