115 lines
5.1 KiB
Python
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()
|