# -*- coding: utf-8 -*- import logging from odoo import api, fields, models, _ from odoo.exceptions import UserError _logger = logging.getLogger(__name__) class AccountMoveLine(models.Model): _inherit = 'account.move.line' used_customer_specific_account = fields.Boolean( string='Used Customer Account', compute='_compute_account_source', store=True, help="Indicates if this line used a customer-specific account" ) account_source = fields.Char( string='Account Source', compute='_compute_account_source', store=True, help="Shows whether the account came from Customer or Product Category" ) @api.depends('move_id', 'move_id.partner_id', 'product_id', 'account_id') def _compute_account_source(self): """ Compute whether this line used a customer-specific account or product category account. This provides audit trail information for journal entries. """ for line in self: # Only compute for lines that have an account and are part of a customer invoice if not line.account_id or not line.move_id: line.used_customer_specific_account = False line.account_source = False continue # Check if this is a customer invoice (out_invoice or out_refund) if line.move_id.move_type not in ('out_invoice', 'out_refund'): line.used_customer_specific_account = False line.account_source = False continue # Get the partner from the move partner = line.move_id.partner_id if not partner: line.used_customer_specific_account = False line.account_source = False continue # Check if the partner has a customer income account defined customer_income_account = partner._get_customer_income_account() # If the line's account matches the customer income account, mark it if customer_income_account and line.account_id == customer_income_account: line.used_customer_specific_account = True line.account_source = 'Customer' else: line.used_customer_specific_account = False line.account_source = 'Product Category' def _compute_account_id(self): """ Override to inject customer-specific income account logic. This method is called to determine which account to use for invoice lines. We call the parent method first, then check if we should override with a customer-specific account. """ # Call parent to get standard account determination super()._compute_account_id() # Now override for product lines on customer invoices product_lines = self.filtered(lambda line: line.display_type == 'product' and line.move_id.is_invoice(True)) for line in product_lines: if line.product_id and line.move_id.is_sale_document(include_receipts=True): partner = line.move_id.partner_id if partner: try: customer_income_account = partner._get_customer_income_account() if customer_income_account: _logger.debug( "Using customer-specific income account %s for partner %s on invoice %s", customer_income_account.code, partner.name, line.move_id.name or 'draft' ) # Apply fiscal position mapping if applicable if line.move_id.fiscal_position_id: customer_income_account = line.move_id.fiscal_position_id.map_account(customer_income_account) line.account_id = customer_income_account except UserError: # Re-raise UserError to prevent invoice creation with invalid account raise except Exception as e: # Log unexpected errors and keep the standard account _logger.warning( "Error determining customer income account for partner %s (ID: %s) on invoice %s: %s. " "Using standard product category account.", partner.name, partner.id, line.move_id.name or 'draft', str(e) )