from odoo import api, fields, models class AccountMove(models.Model): _inherit = "account.move" last_payment_date = fields.Date( compute="_compute_last_payment_info", store=True, copy=False, help="Date of the most recent payment reconciled with this " "invoice, whatever the reconciliation path (explicit payment " "or direct bank statement line reconciliation).", ) last_payment_method = fields.Char( compute="_compute_last_payment_info", store=True, copy=False, help="Payment method of the most recent payment reconciled with this invoice.", ) @api.depends( "line_ids.matched_debit_ids.debit_move_id.date", "line_ids.matched_debit_ids.debit_move_id.move_id.origin_payment_id.payment_method_line_id.name", "line_ids.matched_debit_ids.debit_move_id.move_id.statement_line_id", "line_ids.matched_credit_ids.credit_move_id.date", "line_ids.matched_credit_ids.credit_move_id.move_id.origin_payment_id.payment_method_line_id.name", "line_ids.matched_credit_ids.credit_move_id.move_id.statement_line_id", ) def _compute_last_payment_info(self): """Compute the last real payment date and method. Only customer and vendor invoices carry the information; every other move type gets both fields cleared. Candidates are the reconciliation counterpart lines of the receivable/payable lines (exchange difference moves excluded). The most recent candidate wins; on equal dates the first candidate found wins. """ for move in self: if move.move_type not in ("in_invoice", "out_invoice"): move.last_payment_date = False move.last_payment_method = False continue candidates = move._get_last_payment_candidates() if not candidates: move.last_payment_date = False move.last_payment_method = False else: latest_date, latest_method = max( candidates, key=lambda candidate: candidate[0] ) move.last_payment_date = latest_date move.last_payment_method = latest_method def _get_last_payment_candidates(self): """Collect the candidate real payments for this invoice. A candidate is a ``(date, method)`` tuple built from each reconciliation counterpart line: - counterpart move with ``origin_payment_id``: explicit payment, dated with the payment date, method from the payment method line; - counterpart move with ``statement_line_id``: direct bank statement reconciliation, dated with the counterpart move date, method fixed to the bank transfer label; - any other manual reconciliation is ignored. :return: list of ``(date, method)`` tuples, possibly empty. """ self.ensure_one() invoice_partials, exchange_diff_moves = self._get_reconciled_invoices_partials() candidates = [] for _partial, _amount, counterpart_line in invoice_partials: if counterpart_line.move_id.id in exchange_diff_moves: continue payment = counterpart_line.move_id.origin_payment_id if payment: candidates.append((payment.date, payment.payment_method_line_id.name)) elif counterpart_line.move_id.statement_line_id: candidates.append( (counterpart_line.move_id.date, self.env._("Bank Transfer")) ) return candidates