# -*- 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()