205 lines
9.0 KiB
Python
205 lines
9.0 KiB
Python
# -*- 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))
|