bemade-addons/account_credit_hold/models/res_partner.py
Marc Durepos 06584b381a Migrate account_credit_hold to 18.0
Squashed commit of the following:

commit 5adce1fd15
Author: Marc Durepos <marc@bemade.org>
Date:   Thu Sep 4 12:33:00 2025 -0400

    [MIG] 17.0..18.0 account_credit_hold

    Migrated module account_credit_hold from 17.0 to 18.0. Fixed views and
    models and wrote test cases to check functionality.

commit 91c52af6f4
Author: Marc Durepos <marc@bemade.org>
Date:   Thu Sep 4 11:38:37 2025 -0400

    Initial migration of account_credit_hold for testing in 18.0
2025-09-04 12:34:47 -04:00

108 lines
3.9 KiB
Python

from odoo import fields, models, api, _
from datetime import date
class Partner(models.Model):
_inherit = "res.partner"
postpone_hold_until = fields.Date(
string="Postpone Hold",
help="Grace period specific to this partner despite unpaid invoices.",
tracking=True,
)
hold_bg = fields.Boolean(
string="Hold (technical)",
compute="_compute_hold_bg",
store=True,
default=False,
compute_sudo=True,
tracking=True,
)
on_hold = fields.Boolean(
string="Account on Hold",
help="Client account is on hold for unpaid overdue invoices.",
compute="_compute_on_hold",
compute_sudo=True,
)
@api.depends("postpone_hold_until", "hold_bg", "commercial_partner_id.hold_bg")
def _compute_on_hold(self):
for rec in self:
# If the parent company is on hold, so are all its sub-contacts and subsidiaries
if rec.commercial_partner_id != rec and rec.commercial_partner_id.hold_bg:
if not (rec.commercial_partner_id.postpone_hold_until and rec.commercial_partner_id.postpone_hold_until > date.today()):
rec.on_hold = True
continue
# If there is no parent company or the parent is not on hold, we compute for ourselves
if rec.hold_bg and not (
rec.postpone_hold_until and rec.postpone_hold_until > date.today()
):
rec.on_hold = True
else:
rec.on_hold = False
@api.autovacuum
def _cleanup_expired_hold_postponements(self):
expired_holds = self.search([("postpone_hold_until", "<=", date.today())])
expired_holds.write({"postpone_hold_until": False})
def action_credit_hold(self):
for rec in self:
rec.hold_bg = True
rec.message_post(body=_("Placed on credit hold."))
def action_lift_credit_hold(self):
for rec in self:
rec.hold_bg = False
rec.message_post(body=_("Credit hold lifted."))
@api.model
def _get_first_followup_level(self):
return self.env["account_followup.followup.line"].search(
[("company_id", "parent_of", self.env.company.id)],
order="delay asc",
limit=1,
)
@api.depends("followup_status", "followup_line_id")
def _compute_hold_bg(self):
first_followup_level = self._get_first_followup_level()
for rec in self:
prev_hold_bg = rec.hold_bg
level = rec.followup_line_id
if rec.followup_status == "no_action_needed" and not level:
rec.hold_bg = False
else:
rec.hold_bg = prev_hold_bg
def _get_followup_report(self, options):
# Override to prevent hanging on PDF generation
# Just set minimal required options without generating the report
options.setdefault('attachment_ids', [])
options['report_attachment_id'] = False
def _execute_followup_partner(self, options=None):
# Check if we need to place on credit hold before expensive operations
should_hold = (
self.followup_status == "in_need_of_action" and
self.followup_line_id and
hasattr(self.followup_line_id, 'account_hold') and
self.followup_line_id.account_hold
)
# If this is just for credit hold and we don't need reports/emails, skip heavy operations
if options and options.get('credit_hold_only'):
if should_hold:
self.action_credit_hold()
return should_hold
# Otherwise run the full followup process
res = super()._execute_followup_partner(options)
# Apply credit hold after successful followup execution
if should_hold:
self.action_credit_hold()
return res