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..ab51c2ec6555 100644 --- a/account_check_report/report/account_check_report.xml +++ b/account_check_report/report/account_check_report.xml @@ -21,11 +21,13 @@ style="padding-right:100mm;float:right;" t-esc="'Date: {}'.format(_format_date_to_partner_lang(o.date, o.partner_id.id))" /> - + + +
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)