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,
)
)
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/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 @@
-
+
diff --git a/account_payment_order/tests/test_payment_order_outbound.py b/account_payment_order/tests/test_payment_order_outbound.py
index ad867f249..1bae28cb8 100644
--- a/account_payment_order/tests/test_payment_order_outbound.py
+++ b/account_payment_order/tests/test_payment_order_outbound.py
@@ -6,13 +6,13 @@ 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
@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,48 @@ class TestPaymentOrderOutbound(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):
def test_creation_due_date(self):
self.mode.variable_journal_ids = self.bank_journal
self.mode.group_lines = False
@@ -216,87 +258,159 @@ class TestPaymentOrderOutbound(AccountTestInvoicingCommon):
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.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()
+ )
- self.assertEqual(len(outbound_order.payment_line_ids), 1)
- self.assertEqual(
- outbound_order.payment_line_ids[0].date, date.today() + timedelta(days=8)
- )
+ 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
- # 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.refund.action_post()
- self.assertEqual(len(outbound_order.payment_line_ids), 2)
- self.assertEqual(outbound_order.payment_line_ids[1].date, False)
+ self.env["account.invoice.payment.line.multi"].with_context(
+ active_model="account.move", active_ids=self.invoice.ids
+ ).create({}).run()
- # 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()
+ payment_order = self.env["account.payment.order"].search(self.domain)
+ self.assertEqual(len(payment_order), 1)
- self.assertEqual(outbound_order.state, "uploaded")
- with self.assertRaises(UserError):
- outbound_order.unlink()
+ payment_order.write({"journal_id": self.bank_journal.id})
- bank_line = outbound_order.bank_line_ids
+ self.assertEqual(len(payment_order.payment_line_ids), 1)
- 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.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)