customer_cogs_expense_account/models/res_partner.py
2025-11-25 21:43:35 +07:00

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))