Initial commit: Odoo 18.0-20251222 extra-addons
This commit is contained in:
3
account_invoice_pricelist/models/__init__.py
Normal file
3
account_invoice_pricelist/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import account_move
|
||||
215
account_invoice_pricelist/models/account_move.py
Normal file
215
account_invoice_pricelist/models/account_move.py
Normal file
@@ -0,0 +1,215 @@
|
||||
# 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,
|
||||
)
|
||||
Reference in New Issue
Block a user