From 00d9e89f3d9300b4cc8b831876fc72befcea18ca Mon Sep 17 00:00:00 2001 From: "Danny W. Adair" Date: Fri, 24 Feb 2023 15:47:20 +1300 Subject: [PATCH] [FIX] account_payment_order: Grouped partial reconcile When using a mode with "Group Transactions in Payment Orders" in a payment order with multiple bills from the same supplier, if e.g. the first bill's payment amount is reduced, the reduction was applied to the last bill instead of the specified one. --- .../models/account_payment_order.py | 39 +++++++++++-- .../tests/test_payment_order_outbound.py | 56 +++++++++++++++++++ 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/account_payment_order/models/account_payment_order.py b/account_payment_order/models/account_payment_order.py index d99053eee..9d11408fc 100644 --- a/account_payment_order/models/account_payment_order.py +++ b/account_payment_order/models/account_payment_order.py @@ -417,15 +417,42 @@ class AccountPaymentOrder(models.Model): return action def generated2uploaded(self): + """Post payments and reconcile against source journal items + + Partially reconcile payments that don't match their source journal items, + then reconcile the rest in one go. + """ self.payment_ids.action_post() # Perform the reconciliation of payments and source journal items for payment in self.payment_ids: - ( - payment.payment_line_ids.move_line_id - + payment.move_id.line_ids.filtered( - lambda x: x.account_id == payment.destination_account_id - ) - ).reconcile() + payment_move_line_id = payment.move_id.line_ids.filtered( + lambda x: x.account_id == payment.destination_account_id + ) + apr = self.env["account.partial.reconcile"] + excl_pay_lines = self.env["account.payment.line"] + for line in payment.payment_line_ids: + if not line.move_line_id: + continue + if line.amount_currency != -line.move_line_id.amount_residual_currency: + if line.move_line_id.amount_residual_currency < 0: + debit_move_id = payment_move_line_id.id + credit_move_id = line.move_line_id.id + else: + debit_move_id = line.move_line_id.id + credit_move_id = payment_move_line_id.id + apr.create( + { + "debit_move_id": debit_move_id, + "credit_move_id": credit_move_id, + "amount": abs(line.amount_company_currency), + "debit_amount_currency": abs(line.amount_currency), + "credit_amount_currency": abs(line.amount_currency), + } + ) + excl_pay_lines |= line + pay_lines = payment.payment_line_ids - excl_pay_lines + if pay_lines: + (pay_lines.move_line_id + payment_move_line_id).reconcile() self.write( {"state": "uploaded", "date_uploaded": fields.Date.context_today(self)} ) diff --git a/account_payment_order/tests/test_payment_order_outbound.py b/account_payment_order/tests/test_payment_order_outbound.py index f80379841..8c1fad44f 100644 --- a/account_payment_order/tests/test_payment_order_outbound.py +++ b/account_payment_order/tests/test_payment_order_outbound.py @@ -305,6 +305,62 @@ class TestPaymentOrderOutbound(TestPaymentOrderOutboundBase): fields.Date.context_today(outbound_order), ) + def test_partial_reconciliation(self): + """ + Confirm both supplier invoices + Add invoices to payment order + Reduce payment amount of first invoice from 100 to 80 + Take payment order all the way to uploaded + Confirm 80 reconciled with first, not second invoice + + generated2uploaded() does partial reconciliation of non-matching + line amounts before running .reconcile() against the remaining + matching line amounts. + """ + # Open both invoices + self.invoice.action_post() + self.invoice_02.action_post() + + # Add to payment order using the wizard + self.env["account.invoice.payment.line.multi"].with_context( + active_model="account.move", + active_ids=self.invoice.ids + self.invoice_02.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), 2) + self.assertFalse(payment_order.payment_ids) + + # Reduce payment of first invoice from 100 to 80 + first_payment_line, second_payment_line = payment_order.payment_line_ids + first_payment_line.write({"amount_currency": 80.0}) + + # Open payment order + payment_order.draft2open() + + # Confirm single payment (grouped - two invoices one partner) + self.assertEqual(payment_order.payment_count, 1) + + # Generate and upload + payment_order.open2generated() + payment_order.generated2uploaded() + + self.assertEqual(payment_order.state, "uploaded") + with self.assertRaises(UserError): + payment_order.unlink() + + # Confirm payments were reconciled against correct invoices + self.assertEqual(first_payment_line.amount_currency, 80.0) + self.assertEqual( + first_payment_line.move_line_id.amount_residual_currency, -20.0 + ) + self.assertEqual(second_payment_line.amount_currency, 100.0) + self.assertEqual(second_payment_line.move_line_id.amount_residual_currency, 0.0) + def test_supplier_refund(self): """ Confirm the supplier invoice