first commit
This commit is contained in:
commit
f75fd84af0
1
__init__.py
Normal file
1
__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import models
|
||||
16
__manifest__.py
Normal file
16
__manifest__.py
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "Vendor Bill Price Edit",
|
||||
"version": "17.0.1.0.0",
|
||||
"summary": "Allow editing vendor bill tax-exclusive and tax-inclusive amounts with automatic price unit recomputation.",
|
||||
"license": "GPL-3",
|
||||
"author": "Suherdy Yacob",
|
||||
"category": "Accounting",
|
||||
"depends": [
|
||||
"account"
|
||||
],
|
||||
"data": [
|
||||
"views/account_move_views.xml"
|
||||
],
|
||||
"application": False,
|
||||
"installable": True
|
||||
}
|
||||
BIN
__pycache__/__init__.cpython-310.pyc
Normal file
BIN
__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
1
models/__init__.py
Normal file
1
models/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import account_move_line
|
||||
BIN
models/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
models/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/account_move_line.cpython-310.pyc
Normal file
BIN
models/__pycache__/account_move_line.cpython-310.pyc
Normal file
Binary file not shown.
147
models/account_move_line.py
Normal file
147
models/account_move_line.py
Normal file
@ -0,0 +1,147 @@
|
||||
from odoo import api, fields, models
|
||||
from odoo.tools.float_utils import float_is_zero
|
||||
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = "account.move.line"
|
||||
|
||||
price_subtotal = fields.Monetary(
|
||||
string="Subtotal",
|
||||
compute="_compute_totals",
|
||||
store=True,
|
||||
readonly=False,
|
||||
currency_field="currency_id",
|
||||
)
|
||||
price_total = fields.Monetary(
|
||||
string="Total",
|
||||
compute="_compute_totals",
|
||||
store=True,
|
||||
readonly=False,
|
||||
currency_field="currency_id",
|
||||
)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def _is_vendor_bill_price_editable_line(self):
|
||||
self.ensure_one()
|
||||
return (
|
||||
self.move_id.move_type in ("in_invoice", "in_refund")
|
||||
and self.move_id.state == "draft"
|
||||
and self.display_type == "product"
|
||||
and not float_is_zero(self.quantity or 0.0, precision_rounding=(self.product_uom_id or self.product_id.uom_id or self.env.ref("uom.product_uom_unit")).rounding)
|
||||
and not self.env.context.get("skip_vendor_bill_price_edit")
|
||||
)
|
||||
|
||||
def _get_price_edit_currency(self):
|
||||
return self.currency_id or self.company_currency_id or self.env.company.currency_id
|
||||
|
||||
def _compute_amount_from_current_price_unit(self, target_key):
|
||||
self.ensure_one()
|
||||
currency = self._get_price_edit_currency()
|
||||
discount_factor = 1 - (self.discount or 0.0) / 100.0
|
||||
qty = self.quantity or 0.0
|
||||
price_after_discount = self.price_unit * discount_factor
|
||||
taxes = self.tax_ids
|
||||
partner = self.partner_id or self.move_id.partner_id
|
||||
if taxes:
|
||||
res = taxes.compute_all(
|
||||
price_after_discount,
|
||||
quantity=qty,
|
||||
currency=currency,
|
||||
product=self.product_id,
|
||||
partner=partner,
|
||||
is_refund=self.is_refund,
|
||||
)
|
||||
return res["total_included" if target_key == "total_included" else "total_excluded"]
|
||||
return price_after_discount * qty
|
||||
|
||||
def _compute_price_unit_from_target_amount(self, target_amount, target_key):
|
||||
"""Return a price_unit producing the target amount.
|
||||
|
||||
:param target_amount: desired subtotal/total value (sign included)
|
||||
:param target_key: 'total_excluded' or 'total_included'
|
||||
"""
|
||||
currency = self._get_price_edit_currency()
|
||||
qty = self.quantity or 0.0
|
||||
if float_is_zero(qty, precision_rounding=currency.rounding or 0.01):
|
||||
return False
|
||||
|
||||
discount_factor = 1 - (self.discount or 0.0) / 100.0
|
||||
if float_is_zero(discount_factor, precision_digits=6):
|
||||
return False
|
||||
|
||||
partner = self.partner_id or self.move_id.partner_id
|
||||
taxes = self.tax_ids
|
||||
target_abs = abs(target_amount)
|
||||
sign = 1 if target_amount >= 0 else -1
|
||||
|
||||
def compute_total(base_price):
|
||||
if taxes:
|
||||
res = taxes.compute_all(
|
||||
base_price,
|
||||
quantity=qty,
|
||||
currency=currency,
|
||||
product=self.product_id,
|
||||
partner=partner,
|
||||
is_refund=self.is_refund,
|
||||
)
|
||||
value = res["total_included" if target_key == "total_included" else "total_excluded"]
|
||||
else:
|
||||
value = base_price * qty
|
||||
return abs(value)
|
||||
|
||||
if float_is_zero(target_abs, precision_rounding=currency.rounding or 0.01):
|
||||
base_after_discount = 0.0
|
||||
else:
|
||||
price_guess = abs(self.price_unit * discount_factor)
|
||||
per_unit_target = target_abs / max(qty, 1.0)
|
||||
high = max(price_guess * 2.0, per_unit_target * 2.0, 1.0)
|
||||
low = 0.0
|
||||
base_after_discount = None
|
||||
for _ in range(40):
|
||||
mid = (low + high) / 2.0
|
||||
computed = compute_total(sign * mid)
|
||||
if abs(computed - target_abs) <= (currency.rounding or 0.01):
|
||||
base_after_discount = mid
|
||||
break
|
||||
if computed > target_abs:
|
||||
high = mid
|
||||
else:
|
||||
low = mid
|
||||
if base_after_discount is None:
|
||||
base_after_discount = mid
|
||||
|
||||
return (sign * base_after_discount) / discount_factor
|
||||
|
||||
def _apply_manual_price_edit(self, field_name):
|
||||
self.ensure_one()
|
||||
if not self._is_vendor_bill_price_editable_line():
|
||||
return
|
||||
|
||||
currency = self._get_price_edit_currency()
|
||||
target_key = "total_excluded" if field_name == "price_subtotal" else "total_included"
|
||||
target_value = self[field_name]
|
||||
current_amount = self._compute_amount_from_current_price_unit(target_key)
|
||||
if currency.compare_amounts(target_value, current_amount) == 0:
|
||||
return
|
||||
|
||||
new_price_unit = self._compute_price_unit_from_target_amount(target_value, target_key)
|
||||
if new_price_unit is False:
|
||||
return
|
||||
self.with_context(skip_vendor_bill_price_edit=True).price_unit = new_price_unit
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Onchanges
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@api.onchange("price_subtotal")
|
||||
def _onchange_vendor_bill_price_subtotal(self):
|
||||
for line in self:
|
||||
line._apply_manual_price_edit("price_subtotal")
|
||||
|
||||
@api.onchange("price_total")
|
||||
def _onchange_vendor_bill_price_total(self):
|
||||
for line in self:
|
||||
line._apply_manual_price_edit("price_total")
|
||||
32
views/account_move_views.xml
Normal file
32
views/account_move_views.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="view_move_form_vendor_bill_price_edit" model="ir.ui.view">
|
||||
<field name="name">account.move.form.vendor.bill.price.edit</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_move_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//page[@id='invoice_tab']//field[@name='invoice_line_ids']" position="attributes">
|
||||
<attribute name="context">
|
||||
{
|
||||
'default_move_type': context.get('default_move_type'),
|
||||
'journal_id': journal_id,
|
||||
'default_partner_id': commercial_partner_id,
|
||||
'default_currency_id': currency_id or company_currency_id,
|
||||
'default_display_type': 'product',
|
||||
'quick_encoding_vals': quick_encoding_vals,
|
||||
'vendor_bill_price_edit': True
|
||||
}
|
||||
</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//page[@id='invoice_tab']//field[@name='invoice_line_ids']//tree//field[@name='price_subtotal']" position="attributes">
|
||||
<attribute name="attrs">{'readonly': ['|', ('parent.state', '!=', 'draft'), ('parent.move_type', 'not in', ('in_invoice', 'in_refund'))]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//page[@id='invoice_tab']//field[@name='invoice_line_ids']//tree//field[@name='price_total']" position="attributes">
|
||||
<attribute name="column_invisible">False</attribute>
|
||||
<attribute name="attrs">{'readonly': ['|', ('parent.state', '!=', 'draft'), ('parent.move_type', 'not in', ('in_invoice', 'in_refund'))]}</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
Loading…
Reference in New Issue
Block a user