Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions sale_order_line_base_price_and_has_discount/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
=====================================
Sale Order Line Base And Has Discount
=====================================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:6c1637150f2c652f2bb11cce96c59bddf6dec18dd6b7c48e1f8e1f466bd0171d
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github
:target: https://github.com/OCA/sale-workflow/tree/18.0/sale_order_line_base_price_and_has_discount
:alt: OCA/sale-workflow
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/sale-workflow-18-0/sale-workflow-18-0-sale_order_line_base_price_and_has_discount
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&target_branch=18.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module allows to display the original price before pricelist
application. This can be completed by the
'sale_report_crossed_out_original_price' module to display crossed
prices on sale order report.

In some cases, prices are based on several pricelists based on formulas
and we want to display the base price in that case too. This beahaviour
is configurable.

|image|

.. |image| image:: https://raw.githubusercontent.com/OCA/sale-workflow/18.0/sale_order_line_base_price_and_has_discount/static/description/base_price.png

**Table of contents**

.. contents::
:local:

Use Cases / Context
===================

To retrieve the same behaviour as on e-commerce website, users want to
display the original price on sale order lines when price list are
computing a discounted price.

Configuration
=============

- In the Sale Order form, enable the display of 'Base Price' field.
- Go to Sales > Configuration > Settings > Pricing > Pricelists and
choose a base price computation method.
- By default, the base price will reflect the computation from discount
list price only.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/sale-workflow/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/sale-workflow/issues/new?body=module:%20sale_order_line_base_price_and_has_discount%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* ACSONE SA/NV

Contributors
------------

- Denis Roussel [email protected]

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

.. |maintainer-rousseldenis| image:: https://github.com/rousseldenis.png?size=40px
:target: https://github.com/rousseldenis
:alt: rousseldenis

Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-rousseldenis|

This module is part of the `OCA/sale-workflow <https://github.com/OCA/sale-workflow/tree/18.0/sale_order_line_base_price_and_has_discount>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
2 changes: 2 additions & 0 deletions sale_order_line_base_price_and_has_discount/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from .hooks import pre_init_hook
18 changes: 18 additions & 0 deletions sale_order_line_base_price_and_has_discount/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2025 ACSONE SA/NV
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

{
"name": "Sale Order Line Base And Has Discount",
"summary": """This module allows to put base price and has_discount fields on
sale order line""",
"version": "18.0.1.0.0",
"license": "AGPL-3",
"maintainers": ["rousseldenis"],
"author": "ACSONE SA/NV,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/sale-workflow",
"depends": [
"sale",
],
"data": ["views/sale_order.xml", "views/res_config_settings.xml"],
"pre_init_hook": "pre_init_hook",
}
20 changes: 20 additions & 0 deletions sale_order_line_base_price_and_has_discount/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2026 ACSONE SA/NV
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from openupgradelib import openupgrade


def pre_init_hook(env):
fields_spec = [
(
"base_price",
"sale.order.line",
False,
"float",
"float",
"sale_order_line_base_price_and_has_discount",
)
]
openupgrade.add_fields(env, field_spec=fields_spec)

# Don't fill in the base price as it is impossible to be
# sure of the pricelists configuration before.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import sale_order_line
from . import product_pricelist_item
from . import res_company
from . import res_config_settings
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright 2025 ACSONE SA/NV
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, models


class ProductPricelistItem(models.Model):
_inherit = "product.pricelist.item"

@api.model
def _get_compute_price_methods_for_base_price_pricelist(self):
return ["percentage", "formula"]

def _compute_price_before_pricelist(self, *args, **kwargs):
"""Compute the base price of the lowest pricelist rule,
taking into account percentage and formula items
"""
pricelist_item = self
methods = self._get_compute_price_methods_for_base_price_pricelist()
while pricelist_item.base == "pricelist":
rule_id = pricelist_item.base_pricelist_id._get_product_rule(
*args, **kwargs
)
rule_pricelist_item = self.env["product.pricelist.item"].browse(rule_id)
if rule_pricelist_item and rule_pricelist_item.compute_price in methods:
pricelist_item = rule_pricelist_item
else:
break

return pricelist_item._compute_base_price(*args, **kwargs)
19 changes: 19 additions & 0 deletions sale_order_line_base_price_and_has_discount/models/res_company.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2025 ACSONE SA/NV
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import fields, models


class ResCompany(models.Model):
_inherit = "res.company"

display_base_price_method = fields.Selection(
selection=[
("discount", "Discounts only"),
("discount_formula", "Discount and Formula"),
],
default="discount",
required=True,
help="Choose the method to display the base price "
"(to include or not formula based pricelists)",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2025 ACSONE SA/NV
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import fields, models


class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"

display_base_price_method = fields.Selection(
related="company_id.display_base_price_method",
readonly=False,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Copyright 2025 ACSONE SA/NV
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
from odoo.tools import float_compare


class SaleOrderLine(models.Model):
_inherit = "sale.order.line"

has_discount_price = fields.Boolean(
compute="_compute_has_discount_price",
)
base_price = fields.Float(
compute="_compute_base_price",
digits="Product Price",
store=True,
precompute=True,
)
base_price_discount = fields.Float(
string="Base Price Disc. %",
digits="Discount",
compute="_compute_base_price_discount",
)

@api.depends("base_price", "price_unit", "has_discount_price")
def _compute_base_price_discount(self):
lines_with_product = self.filtered("has_discount_price")
for line in lines_with_product:
line.base_price_discount = (
((line.base_price - line.price_unit) / line.base_price) * 100
if line.base_price
else 0.0
)
(self - lines_with_product).base_price_discount = 0.0

@api.depends("price_unit", "base_price")
def _compute_has_discount_price(self):
"""
Computing the boolean field based on :

- base price
- price unit
"""
lines_with_product = self.filtered("base_price")
precision = self.env["decimal.precision"].precision_get("Product Price")
for line in lines_with_product:
pricelist_price = line.price_unit
base_price = line.base_price
line.has_discount_price = bool(
float_compare(pricelist_price, base_price, precision_digits=precision)
< 0
)
(self - lines_with_product).has_discount_price = False

@api.depends("price_unit", "product_id")
def _compute_base_price(self):
lines_with_product = self.filtered("product_id")
for line in lines_with_product:
if line.company_id.display_base_price_method == "discount":
base_price = line._get_pricelist_price_before_discount()
else:
base_price = line._get_pricelist_price_before_pricelist()
line.base_price = base_price
(self - lines_with_product).base_price = False

def _get_pricelist_price_before_pricelist(self):
"""Compute the price used as base for the pricelist price computation.

:return: the product sales price in the order currency (without taxes)
:rtype: float
"""
self.ensure_one()
self.product_id.ensure_one()

return self.pricelist_item_id._compute_price_before_pricelist(
product=self.product_id.with_context(**self._get_product_price_context()),
quantity=self.product_uom_qty or 1.0,
uom=self.product_uom,
date=self._get_order_date(),
currency=self.currency_id,
)
3 changes: 3 additions & 0 deletions sale_order_line_base_price_and_has_discount/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- In the Sale Order form, enable the display of 'Base Price' field.
- Go to Sales > Configuration > Settings > Pricing > Pricelists
and choose a base price computation method.
- By default, the base price will reflect the computation from discount
list price only.
3 changes: 3 additions & 0 deletions sale_order_line_base_price_and_has_discount/readme/CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
To retrieve the same behaviour as on e-commerce website, users want to display
the original price on sale order lines when price list are computing a discounted
price.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Denis Roussel <[email protected]>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
This module allows to display the original price before pricelist application.
This can be completed by the 'sale_report_crossed_out_original_price' module to
display crossed prices on sale order report.

In some cases, prices are based on several pricelists based on formulas and
we want to display the base price in that case too. This beahaviour is
configurable.

![image](../static/description/base_price.png)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading