# -*- coding: utf-8 -*- import logging from odoo import api, fields, models, _ from odoo.exceptions import ValidationError, UserError _logger = logging.getLogger(__name__) class ResPartner(models.Model): _inherit = 'res.partner' property_account_income_customer_id = fields.Many2one( 'account.account', company_dependent=True, string='Customer Income Account', domain="[('account_type', '=', 'income'), ('deprecated', '=', False)]", help="This account will be used for revenue entries when selling to this customer. " "If not set, the income account from the product category will be used." ) property_account_expense_customer_id = fields.Many2one( 'account.account', company_dependent=True, string='Customer Expense Account (COGS)', domain="[('account_type', '=', 'expense'), ('deprecated', '=', False)]", help="This account will be used for COGS entries when selling to this customer. " "If not set, the expense account from the product category will be used." ) def _get_customer_income_account(self): """ Returns the customer-specific income account if defined. Validates that the account is active and usable. Returns: account.account recordset or False if not defined Raises: UserError: If the account is inactive or invalid """ self.ensure_one() try: account = self.property_account_income_customer_id if not account: return False # Runtime validation: Check if account is still active if account.deprecated: _logger.error( "Customer income account %s (ID: %s) for partner %s (ID: %s) is deprecated", account.code, account.id, self.name, self.id ) raise UserError(_( "The customer income account '%s' for customer '%s' is inactive. " "Please update the customer's accounting settings before creating invoices." ) % (account.display_name, self.name)) # Runtime validation: Check company match # In Odoo 18, account.account uses company_ids (Many2many) if account.company_ids and self.company_id and self.company_id not in account.company_ids: _logger.error( "Customer income account %s (Companies: %s) for partner %s (Company: %s) has company mismatch", account.code, account.company_ids.mapped('name'), self.name, self.company_id.name ) raise UserError(_( "The customer income account '%s' does not belong to company '%s'. " "Please update the customer's accounting settings." ) % (account.display_name, self.company_id.name)) return account except UserError: # Re-raise UserError as-is raise except Exception as e: # Log unexpected errors and return False to fall back to standard behavior _logger.warning( "Unexpected error retrieving customer income account for partner %s (ID: %s): %s. " "Falling back to product category account.", self.name, self.id, str(e) ) return False def _get_customer_expense_account(self): """ Returns the customer-specific expense account if defined. Validates that the account is active and usable. Returns: account.account recordset or False if not defined Raises: UserError: If the account is inactive or invalid """ self.ensure_one() try: account = self.property_account_expense_customer_id if not account: return False # Runtime validation: Check if account is still active if account.deprecated: _logger.error( "Customer expense account %s (ID: %s) for partner %s (ID: %s) is deprecated", account.code, account.id, self.name, self.id ) raise UserError(_( "The customer expense account '%s' for customer '%s' is inactive. " "Please update the customer's accounting settings before processing deliveries." ) % (account.display_name, self.name)) # Runtime validation: Check company match # In Odoo 18, account.account uses company_ids (Many2many) if account.company_ids and self.company_id and self.company_id not in account.company_ids: _logger.error( "Customer expense account %s (Companies: %s) for partner %s (Company: %s) has company mismatch", account.code, account.company_ids.mapped('name'), self.name, self.company_id.name ) raise UserError(_( "The customer expense account '%s' does not belong to company '%s'. " "Please update the customer's accounting settings." ) % (account.display_name, self.company_id.name)) return account except UserError: # Re-raise UserError as-is raise except Exception as e: # Log unexpected errors and return False to fall back to standard behavior _logger.warning( "Unexpected error retrieving customer expense account for partner %s (ID: %s): %s. " "Falling back to product category account.", self.name, self.id, str(e) ) return False @api.constrains('property_account_income_customer_id') def _check_income_account(self): """ Validate that the customer income account is of type 'income', not deprecated, and belongs to the correct company. """ for partner in self: account = partner.property_account_income_customer_id if account: # Check account type if account.account_type != 'income': raise ValidationError(_( "The selected income account '%s' must be of type 'Income'. " "Please select a valid income account." ) % account.display_name) # Check if account is deprecated if account.deprecated: raise ValidationError(_( "The selected income account '%s' is deprecated and cannot be used. " "Please select an active income account." ) % account.display_name) # Check company match in multi-company setup # In Odoo 18, account.account uses company_ids (Many2many) if account.company_ids and partner.company_id and partner.company_id not in account.company_ids: raise ValidationError(_( "The selected income account '%s' does not belong to company '%s'. " "Please select an account from the correct company." ) % (account.display_name, partner.company_id.name)) @api.constrains('property_account_expense_customer_id') def _check_expense_account(self): """ Validate that the customer expense account is of type 'expense', not deprecated, and belongs to the correct company. """ for partner in self: account = partner.property_account_expense_customer_id if account: # Check account type if account.account_type != 'expense': raise ValidationError(_( "The selected expense account '%s' must be of type 'Expense'. " "Please select a valid expense account." ) % account.display_name) # Check if account is deprecated if account.deprecated: raise ValidationError(_( "The selected expense account '%s' is deprecated and cannot be used. " "Please select an active expense account." ) % account.display_name) # Check company match in multi-company setup # In Odoo 18, account.account uses company_ids (Many2many) if account.company_ids and partner.company_id and partner.company_id not in account.company_ids: raise ValidationError(_( "The selected expense account '%s' does not belong to company '%s'. " "Please select an account from the correct company." ) % (account.display_name, partner.company_id.name))