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

115 lines
5.1 KiB
Python

# -*- coding: utf-8 -*-
import logging
from odoo import models, _
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
class StockMove(models.Model):
_inherit = 'stock.move'
def _get_sale_order_partner(self):
"""
Helper method to traverse from stock move to sale order partner.
This method follows the relationship chain:
stock.move -> sale.order.line -> sale.order -> res.partner
Returns:
res.partner recordset or False if no partner found
"""
self.ensure_one()
# Check if this stock move is linked to a sale order line
if hasattr(self, 'sale_line_id') and self.sale_line_id:
# Get the sale order from the sale line
sale_order = self.sale_line_id.order_id
if sale_order and sale_order.partner_id:
return sale_order.partner_id
return False
def _get_accounting_data_for_valuation(self):
"""
Override to inject customer-specific expense account for COGS entries.
This method is called during stock move valuation to determine which accounts
to use for the accounting entries. We intercept it to check if the customer
(from the linked sale order) has a specific expense account defined.
Includes error handling to ensure graceful fallback to standard Odoo behavior.
Returns:
tuple: (journal_id, acc_src, acc_dest, acc_valuation)
Raises:
UserError: If customer account is invalid (inactive or wrong company)
"""
try:
# Get the standard accounting data from parent method
# Returns tuple: (journal_id, acc_src, acc_dest, acc_valuation)
journal_id, acc_src, acc_dest, acc_valuation = super()._get_accounting_data_for_valuation()
try:
# Try to get the customer from the sale order
partner = self._get_sale_order_partner()
if partner:
try:
# This may raise UserError if account is invalid
customer_expense_account = partner._get_customer_expense_account()
if customer_expense_account:
# Replace the expense account (acc_dest for outgoing moves)
# In Odoo's stock accounting, for outgoing moves (delivery to customer):
# - acc_src is the stock valuation account (asset)
# - acc_dest is the expense account (COGS)
# We want to replace acc_dest with the customer-specific expense account
# Check if this is an outgoing move (delivery to customer)
if self._is_out():
_logger.debug(
"Using customer-specific expense account %s for partner %s on stock move %s",
customer_expense_account.code, partner.name, self.name
)
acc_dest = customer_expense_account.id
except UserError:
# Re-raise UserError to prevent stock move processing with invalid account
raise
except Exception as e:
# Log unexpected errors and continue with standard accounting data
_logger.warning(
"Error determining customer expense account for partner %s (ID: %s) on stock move %s: %s. "
"Using product category expense account.",
partner.name, partner.id, self.name, str(e)
)
# Continue with standard acc_dest
except UserError:
# Re-raise UserError as-is
raise
except Exception as e:
# Log errors in partner retrieval and continue with standard accounting data
_logger.warning(
"Error retrieving sale order partner for stock move %s: %s. "
"Using product category expense account.",
self.name, str(e)
)
# Continue with standard acc_dest
return journal_id, acc_src, acc_dest, acc_valuation
except UserError:
# Re-raise UserError as-is
raise
except Exception as e:
# Log any unexpected errors in the overall method and fall back to standard behavior
_logger.error(
"Unexpected error in _get_accounting_data_for_valuation for stock move %s: %s. "
"Falling back to standard Odoo behavior.",
self.name, str(e)
)
# Fall back to standard Odoo logic
return super()._get_accounting_data_for_valuation()