From fbd26bc384a37120bb1f2248a3adb624b35335f5 Mon Sep 17 00:00:00 2001 From: AaronHForgeFlow Date: Wed, 10 Mar 2021 13:24:34 +0100 Subject: [PATCH] [IMP] account_check_report: payment amount should consider current payment only not other payments [IMP] account_check_report: show amount residual at the payment date, not on todays date [IMP] account_check_report: show direct refund applied on invoices paid by the check e.g. 2 invoices and a refund for those 2 invoices should appear, even if the refund includes separate invoices [IMP]account_check_report: button invoices show all related invoices + direct refunds [IMP] account_check_report: add test coverage [IMP] account_check_report: do not print more than one line by invoice. Currently, if there are several receivable/payable lines, for example, due to payment terms the invoice appears multiple times --- account_check_report/__init__.py | 1 + account_check_report/models/__init__.py | 1 + .../models/account_payment.py | 92 ++++++ .../report/account_check_report.xml | 4 +- account_check_report/report/report_helper.py | 181 ++++++++--- account_check_report/tests/__init__.py | 1 + .../tests/test_account_check_report.py | 280 ++++++++++++++++++ 7 files changed, 521 insertions(+), 39 deletions(-) create mode 100644 account_check_report/models/__init__.py create mode 100644 account_check_report/models/account_payment.py create mode 100644 account_check_report/tests/__init__.py create mode 100644 account_check_report/tests/test_account_check_report.py diff --git a/account_check_report/__init__.py b/account_check_report/__init__.py index 4c4f242fa03b..59b4cb501816 100644 --- a/account_check_report/__init__.py +++ b/account_check_report/__init__.py @@ -1 +1,2 @@ from . import report +from . import models diff --git a/account_check_report/models/__init__.py b/account_check_report/models/__init__.py new file mode 100644 index 000000000000..ab350b87bb10 --- /dev/null +++ b/account_check_report/models/__init__.py @@ -0,0 +1 @@ +from . import account_payment diff --git a/account_check_report/models/account_payment.py b/account_check_report/models/account_payment.py new file mode 100644 index 000000000000..d2883a87b4b1 --- /dev/null +++ b/account_check_report/models/account_payment.py @@ -0,0 +1,92 @@ +# 2016-2024 ForgeFlow S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import api, models +from odoo.osv import expression + + +class AccountPayment(models.Model): + _inherit = "account.payment" + + @api.depends( + "move_id.line_ids.matched_debit_ids", "move_id.line_ids.matched_credit_ids" + ) + def _compute_stat_buttons_from_reconciliation(self): + res = super()._compute_stat_buttons_from_reconciliation() + for rec in self: + if rec.payment_type == "outbound": + open_action = rec.button_open_bills() + result_domain = open_action.get("domain", False) + if not result_domain: + return res + rec.reconciled_bills_count = len( + self.env["account.move"].search(result_domain) + ) + else: + open_action = rec.button_open_invoices() + result_domain = open_action.get("domain", False) + if not result_domain: + return res + rec.reconciled_invoices_count = len( + self.env["account.move"].search(result_domain) + ) + return res + + def get_direct_refunds(self): + if self.payment_type == "outbound": + move_lines = self.reconciled_bill_ids.mapped("line_ids") + else: + move_lines = self.reconciled_invoice_ids.mapped("line_ids") + rec_lines = move_lines.filtered( + lambda x: x.account_id.reconcile + and x.account_id == self.destination_account_id + and x.partner_id == self.partner_id + ) + # include direct refunds + if self.partner_type == "customer": + invoice_ids = rec_lines.mapped( + "matched_credit_ids.credit_move_id.full_reconcile_id." + "reconciled_line_ids.move_id" + ).filtered(lambda i: i.date <= self.date) + elif self.partner_type == "supplier": + invoice_ids = rec_lines.mapped( + "matched_debit_ids.debit_move_id.full_reconcile_id." + "reconciled_line_ids.move_id" + ).filtered(lambda i: i.date <= self.date) + # include other invoices where the payment was applied + invoice_ids += rec_lines.mapped( + "matched_credit_ids.credit_move_id.move_id" + ) + rec_lines.mapped("matched_debit_ids.debit_move_id.move_id") + if not invoice_ids: + return False + invoice_ids -= self.move_id + return invoice_ids + + def get_direct_refunds_domain(self): + invoices = self.get_direct_refunds() + if invoices: + return [("id", "in", invoices.ids)] + return [] + + def button_open_invoices(self): + res = super(AccountPayment, self).button_open_invoices() + if not res.get("domain", False): + return res + result_domain = res.get("domain", False) + direct_refunds_domain = self.get_direct_refunds_domain() + if direct_refunds_domain: + res["domain"] = expression.OR([result_domain, direct_refunds_domain]) + return res + + def button_open_bills(self): + """ + Include direct refunds, those are not linked to the payments + directly + """ + res = super(AccountPayment, self).button_open_bills() + if not res.get("domain", False): + return res + result_domain = res.get("domain", False) + direct_refunds_domain = self.get_direct_refunds_domain() + if direct_refunds_domain: + res["domain"] = expression.OR([result_domain, direct_refunds_domain]) + return res diff --git a/account_check_report/report/account_check_report.xml b/account_check_report/report/account_check_report.xml index 7f80668ef3f7..cc4e8acc4763 100644 --- a/account_check_report/report/account_check_report.xml +++ b/account_check_report/report/account_check_report.xml @@ -49,12 +49,12 @@ 0 + ) + # Test button invoices + res = payment.button_open_bills() + self.assertTrue(vendor_bill.id in res["domain"][1][2]) + + def test_00_several_payments(self): + """check the amount is correct on partial payments + and the line is not repeated if several payments involved""" + vendor_bill = self._create_vendor_bill(self.acc_expense) + vendor_bill.action_post() + vendor_bill2 = self._create_vendor_bill(self.acc_expense) + vendor_bill2.action_post() + payment = self._create_payment(amount=1) + payment2 = self._create_payment(amount=4) + payment_ml = payment.line_ids.filtered( + lambda l: l.account_id == self.acc_payable + ) + payment2_ml = payment2.line_ids.filtered( + lambda l: l.account_id == self.acc_payable + ) + vendor_bill_ml = vendor_bill.line_ids.filtered( + lambda l: l.account_id == self.acc_payable + ) + vendor_bill2_ml = vendor_bill2.line_ids.filtered( + lambda l: l.account_id == self.acc_payable + ) + # reconcile fully the first bill with 1st payment + # the rest with the 2nd payment 1 + 1.99 + (payment_ml + payment2_ml + vendor_bill_ml).reconcile() + # reconcile partially the 2nd payments with the second invoice ($ 2.01 from payment) + (payment2_ml + vendor_bill2_ml).reconcile() + # PAYMENT CHECK REPORT PAYMENT 1 + report_lines = self.env[ + "report.account_check_report.check_report" + ]._get_paid_lines(payment) + amls_in_report = reduce(lambda x, y: x + y, report_lines) + aml_list = [item[0] for item in amls_in_report] + # check only the first bill appears + self.assertIn( + vendor_bill.line_ids.filtered(lambda l: l.account_id == self.acc_payable), + aml_list, + ) + self.assertNotIn( + vendor_bill2.line_ids.filtered(lambda l: l.account_id == self.acc_payable), + aml_list, + ) + # check the values of the line in the report + paid_amount = self.env[ + "report.account_check_report.check_report" + ]._get_paid_amount_this_payment(payment, amls_in_report) + self.assertEqual(paid_amount, 1.0) + residual = self.env[ + "report.account_check_report.check_report" + ]._get_residual_amount(payment, amls_in_report) + self.assertEqual(residual, 0.0) + total_amount = self.env[ + "report.account_check_report.check_report" + ]._get_total_amount(payment, amls_in_report) + self.assertEqual(total_amount, 2.99) + # PAYMENT CHECK REPORT PAYMENT 2 + report_lines = self.env[ + "report.account_check_report.check_report" + ]._get_paid_lines(payment2) + amls_in_report = reduce(lambda x, y: x + y, report_lines) + aml_list = [item[0] for item in amls_in_report] + # check both bill appears + self.assertIn( + vendor_bill.line_ids.filtered(lambda l: l.account_id == self.acc_payable), + aml_list, + ) + self.assertIn( + vendor_bill2.line_ids.filtered(lambda l: l.account_id == self.acc_payable), + aml_list, + ) + # check the values of the line in the report + for aml in amls_in_report: + paid_amount = self.env[ + "report.account_check_report.check_report" + ]._get_paid_amount_this_payment(payment2, aml) + if aml == vendor_bill_ml: + self.assertAlmostEqual(paid_amount, 1.99, places=2) + else: + self.assertAlmostEqual(paid_amount, 2.01, places=2) + residual = self.env[ + "report.account_check_report.check_report" + ]._get_residual_amount(payment2, aml) + if aml == vendor_bill_ml: + self.assertEqual(residual, 0.0) + else: + self.assertAlmostEqual(residual, 0.98, places=2) + total_amount = self.env[ + "report.account_check_report.check_report" + ]._get_total_amount(payment2, aml) + self.assertAlmostEqual(total_amount, 2.99, places=2)