# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, fields, models, _ from odoo.exceptions import UserError class AccountMoveLine(models.Model): _inherit = 'account.move.line' # Override price_unit to use more decimal places price_unit = fields.Float( string='Unit Price', digits=(16, 6), # Use 6 decimal places instead of the default 2 ) @api.onchange('price_subtotal') def _onchange_price_subtotal(self): """ Recalculate price_unit when price_subtotal is manually edited. This method is triggered when a user modifies the price_subtotal field on a vendor bill line. It automatically calculates the unit price to maintain the exact entered subtotal amount. Requirements: 1.2, 1.3, 1.4, 3.1, 3.5, 5.3 """ for line in self: # Skip if not a vendor bill line or if in a computed context if line.move_id.move_type not in ('in_invoice', 'in_refund'): continue # Validate quantity is not zero if line.quantity == 0: raise UserError(_("Cannot calculate unit price: quantity must be greater than zero")) # Calculate price_unit from price_subtotal # Formula: price_unit = price_subtotal / quantity new_price_unit = line.price_subtotal / line.quantity # Set the price_unit - now with 6 decimal precision line.price_unit = new_price_unit @api.onchange('price_total') def _onchange_price_total(self): """ Recalculate price_unit when price_total is manually edited. This method is triggered when a user modifies the price_total field on a vendor bill line. It automatically calculates the unit price by first deriving the price_subtotal from the price_total (accounting for taxes), then calculating the unit price. Requirements: 2.2, 2.3, 2.5, 3.1, 3.3, 3.4, 3.5, 5.3 """ for line in self: # Skip if not a vendor bill line or if in a computed context if line.move_id.move_type not in ('in_invoice', 'in_refund'): continue # Validate quantity is not zero if line.quantity == 0: raise UserError(_("Cannot calculate unit price: quantity must be greater than zero")) # Handle case with no taxes: price_total equals price_subtotal if not line.tax_ids: new_price_unit = line.price_total / line.quantity line.price_unit = new_price_unit continue # Check if any taxes are price-included # For tax-included taxes, the price_unit already includes the tax has_price_included_tax = any(tax.price_include for tax in line.tax_ids) if has_price_included_tax: # For tax-included taxes, price_unit = price_total / quantity # because the tax is already included in the unit price new_price_unit = line.price_total / line.quantity line.price_unit = new_price_unit else: # For tax-excluded taxes, we need to calculate the tax factor # Use a temporary price_unit of 1.0 to get the tax multiplier tax_results = line.tax_ids.compute_all( price_unit=1.0, currency=line.currency_id, quantity=1.0, product=line.product_id, partner=line.move_id.partner_id ) # Calculate the tax factor (total_included / total_excluded) # This tells us the multiplier from subtotal to total if tax_results['total_excluded'] != 0: tax_factor = tax_results['total_included'] / tax_results['total_excluded'] else: tax_factor = 1.0 # Derive price_subtotal from price_total # Formula: price_subtotal = price_total / tax_factor derived_price_subtotal = line.price_total / tax_factor # Calculate price_unit from derived price_subtotal # Formula: price_unit = price_subtotal / quantity new_price_unit = derived_price_subtotal / line.quantity line.price_unit = new_price_unit