# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Plating product family. from odoo import api, fields, models class ResPartner(models.Model): _inherit = 'res.partner' # ===== Account hold (existing) ============================================ x_fc_account_hold = fields.Boolean( 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 # Phase G: fixed audit-finding-11 — old code referenced # 'fusion_plating.group_fusion_plating_administrator', an xmlid # that never existed, so the gate always returned False. Replaced # with group_fp_manager which transitively implies Owner via # implied_ids in Phase A's diamond hierarchy. return user.has_group('fusion_plating.group_fp_manager') 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.', ) x_fc_account_hold_by_id = fields.Many2one( 'res.users', string='Hold Placed By', ) # ===== Plating Defaults (cascade onto every new SO for this customer) ===== # The estimator sets these once on the customer record; they pre-fill # invoice strategy, delivery method, and deadlines on every new SO so # repeat customers don't need re-typing the same values each order. # Tax type lives on `property_account_position_id` (Odoo native fiscal # position) and payment terms on `property_payment_term_id` — both are # surfaced on the same Plating Defaults tab in the partner form. x_fc_default_invoice_strategy = fields.Selection( [('deposit', 'Deposit'), ('progress', 'Progress Billing'), ('net_terms', 'Net Terms'), ('cod_prepay', 'COD / Prepay')], string='Default Invoice Strategy', help='Pre-fills the SO invoice strategy when this customer is selected. ' 'The estimator can still override per order.', ) x_fc_default_deposit_percent = fields.Float( string='Default Deposit %', help='Used when invoice strategy is "Deposit". e.g. 50.0 for 50%.', ) x_fc_default_delivery_method = fields.Selection( [('local_delivery', 'Local Delivery'), ('shipping_partner', 'Shipping Partner'), ('customer_pickup', 'Customer Pickup')], string='Default Delivery Method', help='Pre-fills the SO delivery method when this customer is selected.', ) # Lead-time defaults are expressed as offsets FROM the SO's planned-start # date so they track real production schedules, not just "today + N". # If planned_start is unset on the SO, the cascade falls back to today. x_fc_default_internal_deadline_days = fields.Integer( string='Internal Deadline (+ days from start)', help='Pre-fills SO internal deadline as planned_start_date + this ' 'many days. e.g. 5 means "ship five days after we start".', ) x_fc_default_customer_deadline_days = fields.Integer( string='Customer Deadline (+ days from start)', help='Pre-fills the customer-facing commitment date as ' 'planned_start_date + this many days.', )