From 84c009416ec3222613196f0e7882eff3e4174c57 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Tue, 24 Feb 2026 03:31:14 -0500 Subject: [PATCH] feat: add fusion_odoo_fixes module for default Odoo patches - New standalone module to collect fixes for default Odoo behavior - Fix #1: account_followup never clears followup_next_action_date when invoices are paid, causing collection emails to fully-paid clients. Hooks into _invoice_paid_hook to auto-clear stale data. - Harden Fusion Accounting followup queries with amount_residual > 0 filter and add balance check before sending emails Co-authored-by: Cursor --- Fusion Accounting/models/followup.py | 8 +++ .../models/res_partner_followup.py | 4 +- .../wizard/followup_send_wizard.py | 9 ++++ fusion_odoo_fixes/__init__.py | 5 ++ fusion_odoo_fixes/__manifest__.py | 36 +++++++++++++ fusion_odoo_fixes/models/__init__.py | 5 ++ fusion_odoo_fixes/models/account_move.py | 54 +++++++++++++++++++ 7 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 fusion_odoo_fixes/__init__.py create mode 100644 fusion_odoo_fixes/__manifest__.py create mode 100644 fusion_odoo_fixes/models/__init__.py create mode 100644 fusion_odoo_fixes/models/account_move.py diff --git a/Fusion Accounting/models/followup.py b/Fusion Accounting/models/followup.py index e337057..4ccc108 100644 --- a/Fusion Accounting/models/followup.py +++ b/Fusion Accounting/models/followup.py @@ -237,6 +237,7 @@ class FusionFollowupLine(models.Model): ('account_id.account_type', '=', 'asset_receivable'), ('parent_state', '=', 'posted'), ('reconciled', '=', False), + ('amount_residual', '>', 0), ('date_maturity', '<', today), ]) line.overdue_amount = sum(overdue_lines.mapped('amount_residual')) @@ -281,6 +282,8 @@ class FusionFollowupLine(models.Model): of the current follow-up level, then advances the partner to the next level. + Skips sending if the partner no longer has any overdue balance. + :raises UserError: If no follow-up level is set. """ self.ensure_one() @@ -291,6 +294,11 @@ class FusionFollowupLine(models.Model): self.partner_id.display_name, )) + self._compute_overdue_values() + if self.overdue_amount <= 0: + self.followup_status = 'no_action_needed' + return True + level = self.followup_level_id partner = self.partner_id diff --git a/Fusion Accounting/models/res_partner_followup.py b/Fusion Accounting/models/res_partner_followup.py index 2ce0bc3..7ee8e42 100644 --- a/Fusion Accounting/models/res_partner_followup.py +++ b/Fusion Accounting/models/res_partner_followup.py @@ -76,7 +76,8 @@ class FusionPartnerFollowup(models.Model): """Return unpaid receivable move lines that are past due. Searches for posted, unreconciled journal items on receivable - accounts where the maturity date is earlier than today. + accounts where the maturity date is earlier than today and + there is still an outstanding balance. :returns: An ``account.move.line`` recordset. """ @@ -88,6 +89,7 @@ class FusionPartnerFollowup(models.Model): ('account_id.account_type', '=', 'asset_receivable'), ('parent_state', '=', 'posted'), ('reconciled', '=', False), + ('amount_residual', '>', 0), ('date_maturity', '<', today), ]) diff --git a/Fusion Accounting/wizard/followup_send_wizard.py b/Fusion Accounting/wizard/followup_send_wizard.py index 5014e5c..88d11f2 100644 --- a/Fusion Accounting/wizard/followup_send_wizard.py +++ b/Fusion Accounting/wizard/followup_send_wizard.py @@ -188,6 +188,15 @@ class FusionFollowupSendWizard(models.TransientModel): if not line: raise UserError(_("No follow-up record is linked to this wizard.")) + line._compute_overdue_values() + if line.overdue_amount <= 0: + line.followup_status = 'no_action_needed' + raise UserError(_( + "Partner '%s' no longer has any overdue balance. " + "Follow-up cancelled.", + line.partner_id.display_name, + )) + partner = line.partner_id # ---- Email ---- diff --git a/fusion_odoo_fixes/__init__.py b/fusion_odoo_fixes/__init__.py new file mode 100644 index 0000000..17afa9c --- /dev/null +++ b/fusion_odoo_fixes/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2025-2026 Nexa Systems Inc. +# License OPL-1 (Odoo Proprietary License v1.0) + +from . import models diff --git a/fusion_odoo_fixes/__manifest__.py b/fusion_odoo_fixes/__manifest__.py new file mode 100644 index 0000000..c0b0db8 --- /dev/null +++ b/fusion_odoo_fixes/__manifest__.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Copyright 2025-2026 Nexa Systems Inc. +# License OPL-1 (Odoo Proprietary License v1.0) + +{ + 'name': 'Fusion Odoo Fixes', + 'version': '19.0.1.0.0', + 'category': 'Technical', + 'summary': 'Patches and fixes for default Odoo behavior.', + 'description': """ + Fusion Odoo Fixes + ================= + + A standalone module that corrects known issues in default Odoo + modules. Each fix is self-contained and safe to install on any + Odoo 19 instance. + + Current Fixes + ------------- + 1. **Follow-up Cleanup on Payment** -- Odoo's account_followup + module never clears followup_next_action_date when invoices + are fully paid, causing collection emails to be sent to + clients with zero balance. This fix hooks into the payment + reconciliation flow and removes paid partners from the + follow-up queue automatically. + """, + 'author': 'Nexa Systems Inc.', + 'website': 'https://nexasystems.ca', + 'depends': [ + 'account', + ], + 'data': [], + 'installable': True, + 'auto_install': False, + 'license': 'OPL-1', +} diff --git a/fusion_odoo_fixes/models/__init__.py b/fusion_odoo_fixes/models/__init__.py new file mode 100644 index 0000000..822d412 --- /dev/null +++ b/fusion_odoo_fixes/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2025-2026 Nexa Systems Inc. +# License OPL-1 (Odoo Proprietary License v1.0) + +from . import account_move diff --git a/fusion_odoo_fixes/models/account_move.py b/fusion_odoo_fixes/models/account_move.py new file mode 100644 index 0000000..6cef0ad --- /dev/null +++ b/fusion_odoo_fixes/models/account_move.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# Copyright 2025-2026 Nexa Systems Inc. +# License OPL-1 (Odoo Proprietary License v1.0) +# +# FIX: account_followup never clears followup_next_action_date +# ------------------------------------------------------------ +# Odoo's account_followup module sets followup_next_action_date on +# res.partner when overdue invoices are detected, but never clears it +# when those invoices are paid. The daily cron re-evaluates status +# via SQL so it *usually* skips zero-balance partners, but the stale +# field causes partners to appear in follow-up lists and manual sends +# can still fire emails to fully-paid clients. +# +# This override hooks into _invoice_paid_hook (called by the core +# reconciliation engine) and clears the follow-up date when the +# partner has no remaining receivable balance. + +import logging + +from odoo import models + +_logger = logging.getLogger(__name__) + + +class AccountMove(models.Model): + _inherit = 'account.move' + + def _invoice_paid_hook(self): + super()._invoice_paid_hook() + + partner_model = self.env['res.partner'] + if 'followup_next_action_date' not in partner_model._fields: + return + + partners = self.mapped('partner_id.commercial_partner_id').filtered( + 'followup_next_action_date' + ) + if not partners: + return + + for partner in partners: + has_balance = self.env['account.move.line'].search_count([ + ('partner_id', '=', partner.id), + ('account_id.account_type', '=', 'asset_receivable'), + ('parent_state', '=', 'posted'), + ('reconciled', '=', False), + ('amount_residual', '>', 0), + ], limit=1) + if not has_balance: + partner.write({'followup_next_action_date': False}) + _logger.info( + "Cleared follow-up for partner %s (ID %s) -- no outstanding balance", + partner.name, partner.id, + )