Squashed commit of the following: commit5adce1fd15Author: 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. commit91c52af6f4Author: 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
108 lines
3.9 KiB
Python
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
|