216 lines
7.3 KiB
Python
216 lines
7.3 KiB
Python
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
|
|
from odoo import _, api, fields, models
|
|
from odoo.exceptions import UserError
|
|
from odoo.tools import config
|
|
|
|
|
|
class AccountMove(models.Model):
|
|
_inherit = "account.move"
|
|
|
|
pricelist_id = fields.Many2one(
|
|
comodel_name="product.pricelist",
|
|
string="Pricelist",
|
|
compute="_compute_pricelist_id",
|
|
tracking=True,
|
|
store=True,
|
|
precompute=True,
|
|
readonly=False,
|
|
)
|
|
|
|
@api.constrains("pricelist_id", "currency_id")
|
|
def _check_currency(self):
|
|
if (
|
|
not config["test_enable"]
|
|
or (
|
|
config["test_enable"]
|
|
and self._context.get("force_check_currecy", False)
|
|
)
|
|
) and self.filtered(
|
|
lambda a: a.pricelist_id
|
|
and a.is_sale_document()
|
|
and a.pricelist_id.currency_id != a.currency_id
|
|
):
|
|
raise UserError(_("Pricelist and Invoice need to use the same currency."))
|
|
|
|
@api.depends("partner_id", "company_id")
|
|
def _compute_pricelist_id(self):
|
|
for invoice in self:
|
|
if (
|
|
invoice.partner_id
|
|
and invoice.is_sale_document()
|
|
and invoice.partner_id.property_product_pricelist
|
|
):
|
|
invoice.pricelist_id = invoice.partner_id.property_product_pricelist
|
|
|
|
@api.depends("pricelist_id")
|
|
def _compute_currency_id(self):
|
|
res = super()._compute_currency_id()
|
|
for invoice in self:
|
|
if (
|
|
invoice.is_sale_document()
|
|
and invoice.pricelist_id
|
|
and invoice.currency_id != invoice.pricelist_id.currency_id
|
|
):
|
|
invoice.currency_id = self.pricelist_id.currency_id
|
|
return res
|
|
|
|
def button_update_prices_from_pricelist(self):
|
|
self.filtered(
|
|
lambda r: r.state == "draft"
|
|
).invoice_line_ids._compute_price_unit()
|
|
|
|
|
|
class AccountMoveLine(models.Model):
|
|
_inherit = "account.move.line"
|
|
|
|
pricelist_item_id = fields.Many2one(
|
|
comodel_name="product.pricelist.item", compute="_compute_pricelist_item_id"
|
|
)
|
|
|
|
@api.depends("product_id", "product_uom_id", "quantity")
|
|
def _compute_pricelist_item_id(self):
|
|
for line in self:
|
|
if (
|
|
not line.product_id
|
|
or line.display_type != "product"
|
|
or not line.move_id.pricelist_id
|
|
):
|
|
line.pricelist_item_id = False
|
|
else:
|
|
line.pricelist_item_id = line.move_id.pricelist_id._get_product_rule(
|
|
line.product_id,
|
|
quantity=line.quantity or 1.0,
|
|
uom=line.product_uom_id,
|
|
date=line._get_move_date(),
|
|
)
|
|
|
|
def _get_move_date(self):
|
|
self.ensure_one()
|
|
return self.move_id.invoice_date
|
|
|
|
def _calculate_discount(self):
|
|
discount_enabled = self.env[
|
|
"product.pricelist.item"
|
|
]._is_discount_feature_enabled()
|
|
for line in self:
|
|
if not (line.move_id.pricelist_id and discount_enabled):
|
|
continue
|
|
|
|
line.discount = 0.0
|
|
|
|
if not line.pricelist_item_id._show_discount():
|
|
# No pricelist rule was found for the product
|
|
# therefore, the pricelist didn't apply any discount/change
|
|
# to the existing sales price.
|
|
continue
|
|
|
|
line = line.with_company(line.company_id)
|
|
pricelist_price = line._get_pricelist_price()
|
|
base_price = line._get_pricelist_price_before_discount()
|
|
|
|
if base_price != 0: # Avoid division by zero
|
|
discount = (base_price - pricelist_price) / base_price * 100
|
|
if (discount > 0 and base_price > 0) or (
|
|
discount < 0 and base_price < 0
|
|
):
|
|
# only show negative discounts if price is negative
|
|
# otherwise it's a surcharge which shouldn't be shown
|
|
# to the customer
|
|
line.discount = discount
|
|
|
|
@api.depends("quantity")
|
|
def _compute_price_unit(self):
|
|
res = super()._compute_price_unit()
|
|
for line in self:
|
|
line = line.with_company(line.company_id)
|
|
if not line.move_id.pricelist_id:
|
|
continue
|
|
if not line.product_uom_id or not line.product_id:
|
|
line.price_unit = 0.0
|
|
else:
|
|
price = line._get_display_price()
|
|
line.with_context(
|
|
check_move_validity=False
|
|
).price_unit = line.product_id._get_tax_included_unit_price_from_price(
|
|
price,
|
|
product_taxes=line.product_id.taxes_id.filtered(
|
|
lambda tax, line=line: tax.company_id == line.env.company
|
|
),
|
|
fiscal_position=line.move_id.fiscal_position_id,
|
|
)
|
|
return res
|
|
|
|
def _get_display_price(self):
|
|
"""Compute the displayed unit price for a given line.
|
|
|
|
Overridden in custom flows:
|
|
* where the price is not specified by the pricelist
|
|
* where the discount is not specified by the pricelist
|
|
|
|
Note: self.ensure_one()
|
|
"""
|
|
self.ensure_one()
|
|
|
|
if self.product_id.type == "combo":
|
|
return 0 # The display price of a combo line should always be 0.
|
|
return self._get_display_price_ignore_combo()
|
|
|
|
def _get_display_price_ignore_combo(self):
|
|
"""This helper method allows to compute the display price of a SOL,
|
|
while ignoring combo logic.
|
|
|
|
I.e. this method returns the display price of a SOL as if it were neither
|
|
a combo line nor a combo item line.
|
|
"""
|
|
self.ensure_one()
|
|
|
|
pricelist_price = self._get_pricelist_price()
|
|
|
|
if not self.pricelist_item_id._show_discount():
|
|
# No pricelist rule found => no discount from pricelist
|
|
return pricelist_price
|
|
|
|
base_price = self._get_pricelist_price_before_discount()
|
|
|
|
self._calculate_discount()
|
|
|
|
# negative discounts (= surcharge) are included in the display price
|
|
return max(base_price, pricelist_price)
|
|
|
|
def _get_pricelist_price(self):
|
|
"""Compute the price given by the pricelist for the given line information.
|
|
|
|
:return: the product price in the move currency (without taxes)
|
|
:rtype: float
|
|
"""
|
|
self.ensure_one()
|
|
self.product_id.ensure_one()
|
|
|
|
price = self.pricelist_item_id._compute_price(
|
|
product=self.product_id,
|
|
quantity=self.quantity or 1.0,
|
|
uom=self.product_uom_id,
|
|
date=self._get_move_date(),
|
|
currency=self.currency_id,
|
|
)
|
|
|
|
return price
|
|
|
|
def _get_pricelist_price_before_discount(self):
|
|
"""Compute the price used as base for the pricelist price computation.
|
|
|
|
:return: the product price in the move currency (without taxes)
|
|
:rtype: float
|
|
"""
|
|
self.ensure_one()
|
|
self.product_id.ensure_one()
|
|
|
|
return self.pricelist_item_id._compute_price_before_discount(
|
|
product=self.product_id,
|
|
quantity=self.quantity or 1.0,
|
|
uom=self.product_uom_id,
|
|
date=self._get_move_date(),
|
|
currency=self.currency_id,
|
|
)
|