changes
This commit is contained in:
@@ -3,13 +3,22 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
|
||||
from odoo import api, models, _
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = 'account.move'
|
||||
|
||||
# Mirrors the SO-side related field. See sale_order.py for the
|
||||
# rationale (dotted refs in view modifiers are fragile + hold lives
|
||||
# on the commercial partner).
|
||||
x_fc_partner_account_hold = fields.Boolean(
|
||||
string='Customer on Account Hold',
|
||||
related='partner_id.commercial_partner_id.x_fc_account_hold',
|
||||
store=True, readonly=True,
|
||||
)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
"""Auto-inherit payment terms + customer PO# at creation time.
|
||||
@@ -55,17 +64,16 @@ class AccountMove(models.Model):
|
||||
"""
|
||||
for move in self:
|
||||
if move.move_type in ('out_invoice', 'out_refund') and move.partner_id:
|
||||
if move.partner_id.x_fc_account_hold:
|
||||
is_manager = self.env.user.has_group(
|
||||
'fusion_plating.group_fusion_plating_manager'
|
||||
)
|
||||
hold_partner = move.partner_id.commercial_partner_id
|
||||
if hold_partner.x_fc_account_hold:
|
||||
is_manager = self.env['res.partner']._fp_user_can_override_account_hold()
|
||||
if not is_manager:
|
||||
raise UserError(_(
|
||||
'Cannot post invoice — customer "%s" is on account hold.\n'
|
||||
'Reason: %s\n\n'
|
||||
'Contact a manager to override.'
|
||||
) % (move.partner_id.name,
|
||||
move.partner_id.x_fc_account_hold_reason or 'No reason specified'))
|
||||
) % (hold_partner.name,
|
||||
hold_partner.x_fc_account_hold_reason or 'No reason specified'))
|
||||
if not move.invoice_payment_term_id:
|
||||
raise UserError(_(
|
||||
'Cannot post invoice "%s" — no payment terms set.\n\n'
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
|
||||
from odoo import fields, models
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
@@ -14,6 +14,25 @@ class ResPartner(models.Model):
|
||||
string='Account Hold', tracking=True,
|
||||
help='When active, blocks SO confirmation, invoicing, and shipping.',
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _fp_user_can_override_account_hold(self):
|
||||
"""True when the current user is allowed to override an account hold.
|
||||
|
||||
Plating Manager OR Plating Administrator qualifies. Administrator
|
||||
is checked explicitly (in addition to the implied chain) because
|
||||
Odoo's ``implied_ids`` cascade does NOT reliably propagate to
|
||||
existing users on module upgrade — admin (uid 1) typically lands
|
||||
in Administrator only, with no Manager membership. Without this
|
||||
defensive check, the highest-privileged user can't bypass holds.
|
||||
|
||||
See CLAUDE.md "Implied group cascade" rule.
|
||||
"""
|
||||
user = self.env.user
|
||||
return (
|
||||
user.has_group('fusion_plating.group_fusion_plating_manager')
|
||||
or user.has_group('fusion_plating.group_fusion_plating_administrator')
|
||||
)
|
||||
x_fc_account_hold_reason = fields.Text(string='Hold Reason')
|
||||
x_fc_account_hold_date = fields.Datetime(
|
||||
string='Hold Date', help='When the hold was placed.',
|
||||
|
||||
@@ -15,6 +15,18 @@ _logger = logging.getLogger(__name__)
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = 'sale.order'
|
||||
|
||||
# Explicit related field — dotted refs like `partner_id.x_fc_account_hold`
|
||||
# in `invisible=` modifiers are fragile in Odoo 19 (the related field
|
||||
# has to be in the record cache for the evaluator). Surfacing it as a
|
||||
# plain field on sale.order makes the banner condition deterministic.
|
||||
# We resolve through `commercial_partner_id` so a hold placed on the
|
||||
# company also blocks SOs entered against any of its child contacts.
|
||||
x_fc_partner_account_hold = fields.Boolean(
|
||||
string='Customer on Account Hold',
|
||||
related='partner_id.commercial_partner_id.x_fc_account_hold',
|
||||
store=True, readonly=True,
|
||||
)
|
||||
|
||||
@api.onchange('partner_id')
|
||||
def _onchange_partner_id_invoice_strategy(self):
|
||||
"""Auto-fill plating defaults from customer profile.
|
||||
@@ -119,24 +131,27 @@ class SaleOrder(models.Model):
|
||||
) % {'so': order.name})
|
||||
|
||||
# --- Account hold check ---
|
||||
if order.partner_id.x_fc_account_hold:
|
||||
is_manager = self.env.user.has_group(
|
||||
'fusion_plating.group_fusion_plating_manager'
|
||||
)
|
||||
# Hold lives on the commercial_partner (the company). Resolve
|
||||
# through that so a hold on the parent applies to every child
|
||||
# contact too — typical case is "all of Acme is on hold", not
|
||||
# "specifically the AP clerk's contact card".
|
||||
hold_partner = order.partner_id.commercial_partner_id
|
||||
if hold_partner.x_fc_account_hold:
|
||||
is_manager = self.env['res.partner']._fp_user_can_override_account_hold()
|
||||
if not is_manager:
|
||||
raise UserError(_(
|
||||
'Cannot confirm — customer "%s" is on account hold.\n'
|
||||
'Reason: %s\n\n'
|
||||
'Contact a manager to override.'
|
||||
) % (order.partner_id.name,
|
||||
order.partner_id.x_fc_account_hold_reason or 'No reason specified'))
|
||||
) % (hold_partner.name,
|
||||
hold_partner.x_fc_account_hold_reason or 'No reason specified'))
|
||||
else:
|
||||
order.message_post(
|
||||
body=_(
|
||||
'Warning: Customer "%s" is on account hold (reason: %s). '
|
||||
'Order confirmed by manager override.'
|
||||
) % (order.partner_id.name,
|
||||
order.partner_id.x_fc_account_hold_reason or 'N/A'),
|
||||
) % (hold_partner.name,
|
||||
hold_partner.x_fc_account_hold_reason or 'N/A'),
|
||||
)
|
||||
|
||||
res = super().action_confirm()
|
||||
|
||||
Reference in New Issue
Block a user