Files
Odoo-18.0-20251222/account_invoice_pricelist/models/account_move.py
tocmo0nlord adbe430761
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
tests / Detect unreleased dependencies (push) Has been cancelled
tests / test with OCB (push) Has been cancelled
tests / test with Odoo (push) Has been cancelled
Initial commit: Odoo 18.0-20251222 extra-addons
2026-03-13 20:43:25 +00:00

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,
)