[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
This commit is contained in:
Denis Roussel
2022-04-14 09:26:00 +02:00
committed by Pedro M. Baeza
parent 6a2621694b
commit f3eca92554
3 changed files with 196 additions and 5 deletions

View File

@@ -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

View File

@@ -11,6 +11,8 @@
* Angel Moya <angel.moya@domatix.com>
* Jose María Alzaga <jose.alzaga@aselcis.com>
* Meyomesse Gilles <meyomesse.gilles@gmail.com>
* Denis Roussel <denis.roussel@acsone.eu>
* `DynApps <https://www.dynapps.be>`_:
* Raf Ven <raf.ven@dynapps.be>

View File

@@ -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)