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 <cursoragent@cursor.com>
This commit is contained in:
@@ -237,6 +237,7 @@ class FusionFollowupLine(models.Model):
|
|||||||
('account_id.account_type', '=', 'asset_receivable'),
|
('account_id.account_type', '=', 'asset_receivable'),
|
||||||
('parent_state', '=', 'posted'),
|
('parent_state', '=', 'posted'),
|
||||||
('reconciled', '=', False),
|
('reconciled', '=', False),
|
||||||
|
('amount_residual', '>', 0),
|
||||||
('date_maturity', '<', today),
|
('date_maturity', '<', today),
|
||||||
])
|
])
|
||||||
line.overdue_amount = sum(overdue_lines.mapped('amount_residual'))
|
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
|
of the current follow-up level, then advances the partner to
|
||||||
the next level.
|
the next level.
|
||||||
|
|
||||||
|
Skips sending if the partner no longer has any overdue balance.
|
||||||
|
|
||||||
:raises UserError: If no follow-up level is set.
|
:raises UserError: If no follow-up level is set.
|
||||||
"""
|
"""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
@@ -291,6 +294,11 @@ class FusionFollowupLine(models.Model):
|
|||||||
self.partner_id.display_name,
|
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
|
level = self.followup_level_id
|
||||||
partner = self.partner_id
|
partner = self.partner_id
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,8 @@ class FusionPartnerFollowup(models.Model):
|
|||||||
"""Return unpaid receivable move lines that are past due.
|
"""Return unpaid receivable move lines that are past due.
|
||||||
|
|
||||||
Searches for posted, unreconciled journal items on receivable
|
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.
|
:returns: An ``account.move.line`` recordset.
|
||||||
"""
|
"""
|
||||||
@@ -88,6 +89,7 @@ class FusionPartnerFollowup(models.Model):
|
|||||||
('account_id.account_type', '=', 'asset_receivable'),
|
('account_id.account_type', '=', 'asset_receivable'),
|
||||||
('parent_state', '=', 'posted'),
|
('parent_state', '=', 'posted'),
|
||||||
('reconciled', '=', False),
|
('reconciled', '=', False),
|
||||||
|
('amount_residual', '>', 0),
|
||||||
('date_maturity', '<', today),
|
('date_maturity', '<', today),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
@@ -188,6 +188,15 @@ class FusionFollowupSendWizard(models.TransientModel):
|
|||||||
if not line:
|
if not line:
|
||||||
raise UserError(_("No follow-up record is linked to this wizard."))
|
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
|
partner = line.partner_id
|
||||||
|
|
||||||
# ---- Email ----
|
# ---- Email ----
|
||||||
|
|||||||
5
fusion_odoo_fixes/__init__.py
Normal file
5
fusion_odoo_fixes/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2025-2026 Nexa Systems Inc.
|
||||||
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||||
|
|
||||||
|
from . import models
|
||||||
36
fusion_odoo_fixes/__manifest__.py
Normal file
36
fusion_odoo_fixes/__manifest__.py
Normal file
@@ -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',
|
||||||
|
}
|
||||||
5
fusion_odoo_fixes/models/__init__.py
Normal file
5
fusion_odoo_fixes/models/__init__.py
Normal file
@@ -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
|
||||||
54
fusion_odoo_fixes/models/account_move.py
Normal file
54
fusion_odoo_fixes/models/account_move.py
Normal file
@@ -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,
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user