107 lines
4.6 KiB
Python
107 lines
4.6 KiB
Python
# -*- 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, 10), # Use 10 decimal places for maximum precision
|
|
)
|
|
|
|
@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 10 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
|