Skip to content

Commit 6807ee4

Browse files
committed
[ADD] sale_order_net_price_control
Controls the net price of the products when they are sold. It has to be higher than the last price on a purchase invoice and higher than the MAYM pricelist. Allows users who are in the 'Sell Products at Any Price' group to bypass all controls.
1 parent 86f1818 commit 6807ee4

18 files changed

Lines changed: 846 additions & 0 deletions
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
============================
2+
Sale Order Net Price Control
3+
============================
4+
5+
..
6+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
7+
!! This file is generated by oca-gen-addon-readme !!
8+
!! changes will be overwritten. !!
9+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
10+
!! source digest: sha256:483f44adb6b4c2214568d8f8ec242a00f09f9f58acccc118b3c838778e52cb32
11+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
12+
13+
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
14+
:target: https://odoo-community.org/page/development-status
15+
:alt: Beta
16+
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
17+
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
18+
:alt: License: LGPL-3
19+
.. |badge3| image:: https://img.shields.io/badge/github-solvosci%2Fslv--sale-lightgray.png?logo=github
20+
:target: https://github.com/solvosci/slv-sale/tree/17.0/sale_order_net_price_control
21+
:alt: solvosci/slv-sale
22+
23+
|badge1| |badge2| |badge3|
24+
25+
Controls the net price of the products when they are sold.
26+
It has to be higher than the last price on a purchase invoice and higher than the MAYM pricelist.
27+
Allows users who are in the 'Sell Products at Any Price' group to bypass all controls.
28+
29+
**Table of contents**
30+
31+
.. contents::
32+
:local:
33+
34+
Bug Tracker
35+
===========
36+
37+
Bugs are tracked on `GitHub Issues <https://github.com/solvosci/slv-sale/issues>`_.
38+
In case of trouble, please check there if your issue has already been reported.
39+
If you spotted it first, help us to smash it by providing a detailed and welcomed
40+
`feedback <https://github.com/solvosci/slv-sale/issues/new?body=module:%20sale_order_net_price_control%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
41+
42+
Do not contact contributors directly about support or help with technical issues.
43+
44+
Credits
45+
=======
46+
47+
Authors
48+
~~~~~~~
49+
50+
* Solvos
51+
52+
Contributors
53+
~~~~~~~~~~~~
54+
55+
* Iria Alonso <iria.alonso@solvos.es>
56+
57+
Maintainers
58+
~~~~~~~~~~~
59+
60+
This module is part of the `solvosci/slv-sale <https://github.com/solvosci/slv-sale/tree/17.0/sale_order_net_price_control>`_ project on GitHub.
61+
62+
You are welcome to contribute.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import models
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# © 2025 Solvos Consultoría Informática (<http://www.solvos.es>)
2+
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html
3+
{
4+
"name": "Sale Order Net Price Control",
5+
"summary": """
6+
Controls the net price of the products when they are sold.
7+
It has to be higher than the last price on a purchase invoice and higher than the MAYM pricelist.
8+
Allows users who are in the 'Sell Products at Any Price' group to bypass all controls.
9+
""",
10+
"author": "Solvos",
11+
"license": "LGPL-3",
12+
"version": "17.0.1.0.0",
13+
'category': "Operations/Sale",
14+
"website": "https://github.com/solvosci/slv-sale",
15+
"depends": ["sale"],
16+
"data": [
17+
"security/sale_order_security.xml",
18+
"views/res_config_settings_views.xml",
19+
"views/sale_order_views.xml",
20+
"views/product_template.xml",
21+
],
22+
'installable': True,
23+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Translation of Odoo Server.
2+
# This file contains the translation of the following modules:
3+
# * sale_order_net_price_control
4+
#
5+
msgid ""
6+
msgstr ""
7+
"Project-Id-Version: Odoo Server 17.0\n"
8+
"Report-Msgid-Bugs-To: \n"
9+
"POT-Creation-Date: 2025-06-10 11:44+0000\n"
10+
"PO-Revision-Date: 2025-06-10 11:44+0000\n"
11+
"Last-Translator: \n"
12+
"Language-Team: \n"
13+
"MIME-Version: 1.0\n"
14+
"Content-Type: text/plain; charset=UTF-8\n"
15+
"Content-Transfer-Encoding: \n"
16+
"Plural-Forms: \n"
17+
18+
#. module: sale_order_net_price_control
19+
#: model:ir.model.fields,field_description:sale_order_net_price_control.field_product_product__admits_offer_prices
20+
#: model:ir.model.fields,field_description:sale_order_net_price_control.field_product_template__admits_offer_prices
21+
#: model:ir.model.fields,field_description:sale_order_net_price_control.field_sale_order_line__admits_offer_prices
22+
msgid "Admits Offer Prices"
23+
msgstr "Admite precios de oferta"
24+
25+
#. module: sale_order_net_price_control
26+
#: model:ir.model,name:sale_order_net_price_control.model_res_company
27+
msgid "Companies"
28+
msgstr "Compañías"
29+
30+
#. module: sale_order_net_price_control
31+
#: model:ir.model,name:sale_order_net_price_control.model_res_config_settings
32+
msgid "Config Settings"
33+
msgstr "Ajustes de configuración"
34+
35+
#. module: sale_order_net_price_control
36+
#: model:ir.model.fields,field_description:sale_order_net_price_control.field_res_company__lowest_pricelist_id
37+
msgid "MAYM pricelist"
38+
msgstr "Tarifa"
39+
40+
#. module: sale_order_net_price_control
41+
#. odoo-python
42+
#: code:addons/sale_order_net_price_control/models/sale_order_line.py:0
43+
#, python-format
44+
msgid "Net price of '%s', '%.2f' must be higher that the MAYM pricelist '%s'"
45+
msgstr ""
46+
"El precio neto de '%s', '%.2f' debe ser mayor que el precio de la tarifa "
47+
"MAYM '%s'"
48+
49+
#. module: sale_order_net_price_control
50+
#. odoo-python
51+
#: code:addons/sale_order_net_price_control/models/sale_order_line.py:0
52+
#, python-format
53+
msgid ""
54+
"Net price of '%s', '%.2f' must be higher that the last purchase invoice "
55+
"price '%s'"
56+
msgstr ""
57+
"El precio neto de '%s', '%.2f' debe ser mayor que el precio de la última "
58+
"factura de compra '%s'"
59+
60+
#. module: sale_order_net_price_control
61+
#: model:ir.model.fields,field_description:sale_order_net_price_control.field_sale_order_line__offer_price
62+
msgid "Offer Price"
63+
msgstr "Precio de oferta"
64+
65+
#. module: sale_order_net_price_control
66+
#: model:ir.model.fields,field_description:sale_order_net_price_control.field_res_config_settings__lowest_pricelist_id
67+
#: model_terms:ir.ui.view,arch_db:sale_order_net_price_control.res_config_settings_view_form
68+
msgid "Pricelist"
69+
msgstr "Tarifa"
70+
71+
#. module: sale_order_net_price_control
72+
#: model:ir.model,name:sale_order_net_price_control.model_product_template
73+
msgid "Product"
74+
msgstr "Producto"
75+
76+
#. module: sale_order_net_price_control
77+
#: model_terms:ir.ui.view,arch_db:sale_order_net_price_control.res_config_settings_view_form
78+
msgid "Reference base pricelist for minimum price control"
79+
msgstr "Tarifa base de referencia para control de precios mínimos"
80+
81+
#. module: sale_order_net_price_control
82+
#: model:ir.model,name:sale_order_net_price_control.model_sale_order_line
83+
msgid "Sales Order Line"
84+
msgstr "Línea de pedido de venta"
85+
86+
#. module: sale_order_net_price_control
87+
#: model:res.groups,name:sale_order_net_price_control.group_sale_at_any_price
88+
msgid "Sell Products at Any Price"
89+
msgstr "Vender productos a cualquier precio"
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Translation of Odoo Server.
2+
# This file contains the translation of the following modules:
3+
# * sale_order_net_price_control
4+
#
5+
msgid ""
6+
msgstr ""
7+
"Project-Id-Version: Odoo Server 17.0\n"
8+
"Report-Msgid-Bugs-To: \n"
9+
"POT-Creation-Date: 2025-06-10 11:44+0000\n"
10+
"PO-Revision-Date: 2025-06-10 11:44+0000\n"
11+
"Last-Translator: \n"
12+
"Language-Team: \n"
13+
"MIME-Version: 1.0\n"
14+
"Content-Type: text/plain; charset=UTF-8\n"
15+
"Content-Transfer-Encoding: \n"
16+
"Plural-Forms: \n"
17+
18+
#. module: sale_order_net_price_control
19+
#: model:ir.model.fields,field_description:sale_order_net_price_control.field_product_product__admits_offer_prices
20+
#: model:ir.model.fields,field_description:sale_order_net_price_control.field_product_template__admits_offer_prices
21+
#: model:ir.model.fields,field_description:sale_order_net_price_control.field_sale_order_line__admits_offer_prices
22+
msgid "Admits Offer Prices"
23+
msgstr ""
24+
25+
#. module: sale_order_net_price_control
26+
#: model:ir.model,name:sale_order_net_price_control.model_res_company
27+
msgid "Companies"
28+
msgstr ""
29+
30+
#. module: sale_order_net_price_control
31+
#: model:ir.model,name:sale_order_net_price_control.model_res_config_settings
32+
msgid "Config Settings"
33+
msgstr ""
34+
35+
#. module: sale_order_net_price_control
36+
#: model:ir.model.fields,field_description:sale_order_net_price_control.field_res_company__lowest_pricelist_id
37+
msgid "MAYM pricelist"
38+
msgstr ""
39+
40+
#. module: sale_order_net_price_control
41+
#. odoo-python
42+
#: code:addons/sale_order_net_price_control/models/sale_order_line.py:0
43+
#, python-format
44+
msgid "Net price of '%s', '%.2f' must be higher that the MAYM pricelist '%s'"
45+
msgstr ""
46+
47+
#. module: sale_order_net_price_control
48+
#. odoo-python
49+
#: code:addons/sale_order_net_price_control/models/sale_order_line.py:0
50+
#, python-format
51+
msgid ""
52+
"Net price of '%s', '%.2f' must be higher that the last purchase invoice "
53+
"price '%s'"
54+
msgstr ""
55+
56+
#. module: sale_order_net_price_control
57+
#: model:ir.model.fields,field_description:sale_order_net_price_control.field_sale_order_line__offer_price
58+
msgid "Offer Price"
59+
msgstr ""
60+
61+
#. module: sale_order_net_price_control
62+
#: model:ir.model.fields,field_description:sale_order_net_price_control.field_res_config_settings__lowest_pricelist_id
63+
#: model_terms:ir.ui.view,arch_db:sale_order_net_price_control.res_config_settings_view_form
64+
msgid "Pricelist"
65+
msgstr ""
66+
67+
#. module: sale_order_net_price_control
68+
#: model:ir.model,name:sale_order_net_price_control.model_product_template
69+
msgid "Product"
70+
msgstr ""
71+
72+
#. module: sale_order_net_price_control
73+
#: model_terms:ir.ui.view,arch_db:sale_order_net_price_control.res_config_settings_view_form
74+
msgid "Reference base pricelist for minimum price control"
75+
msgstr ""
76+
77+
#. module: sale_order_net_price_control
78+
#: model:ir.model,name:sale_order_net_price_control.model_sale_order_line
79+
msgid "Sales Order Line"
80+
msgstr ""
81+
82+
#. module: sale_order_net_price_control
83+
#: model:res.groups,name:sale_order_net_price_control.group_sale_at_any_price
84+
msgid "Sell Products at Any Price"
85+
msgstr ""
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from . import sale_order_line
2+
from . import res_company
3+
from . import res_config_settings
4+
from . import product_template
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# © 2025 Solvos Consultoría Informática (<http://www.solvos.es>)
2+
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html
3+
4+
from odoo import models, fields
5+
6+
7+
class ProductTemplate(models.Model):
8+
_inherit = "product.template"
9+
10+
admits_offer_prices = fields.Boolean()
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# © 2025 Solvos Consultoría Informática (<http://www.solvos.es>)
2+
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html
3+
4+
from odoo import models, fields
5+
6+
7+
class ResCompany(models.Model):
8+
_inherit = "res.company"
9+
10+
lowest_pricelist_id = fields.Many2one(comodel_name='product.pricelist', string='MAYM pricelist')
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# © 2025 Solvos Consultoría Informática (<http://www.solvos.es>)
2+
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html
3+
4+
from odoo import models, fields
5+
6+
7+
class ResConfigSettings(models.TransientModel):
8+
_inherit = 'res.config.settings'
9+
10+
lowest_pricelist_id = fields.Many2one(related="company_id.lowest_pricelist_id", readonly=False, string='Pricelist')
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# © 2025 Solvos Consultoría Informática (<http://www.solvos.es>)
2+
# License LGPL-3 - See http://www.gnu.org/licenses/lgpl-3.0.html
3+
4+
from odoo import models, _, fields
5+
from odoo.exceptions import ValidationError
6+
7+
8+
class SaleOrderLine(models.Model):
9+
_inherit = "sale.order.line"
10+
11+
offer_price = fields.Boolean()
12+
admits_offer_prices = fields.Boolean(related='product_id.admits_offer_prices', store=True)
13+
14+
# FASE 1 desarrollo
15+
def _compute_amount(self):
16+
super()._compute_amount()
17+
# FASE 3 DEL DESARROLLO
18+
if not self.env.user.has_group('sale_order_net_price_control.group_sale_at_any_price'):
19+
20+
for record in self.filtered(lambda r: r.product_id and r.product_uom_qty > 0):
21+
22+
net_price_sale = record.price_subtotal / record.product_uom_qty
23+
24+
purchase_invoice = self.env['account.move'].search([
25+
('invoice_line_ids.product_id', '=', record.product_id.id),
26+
('invoice_line_ids.quantity', '>', 0),
27+
('move_type', '=', 'in_invoice'), # factura de compra
28+
('state', '=', 'posted'), # confirmado
29+
], limit=1, order='name desc')
30+
31+
if purchase_invoice:
32+
invoice_line = purchase_invoice.invoice_line_ids.filtered(
33+
lambda x: x.product_id.id == self.product_id.id
34+
)
35+
net_price_purchase = invoice_line.price_subtotal / invoice_line.quantity
36+
37+
if net_price_sale <= net_price_purchase:
38+
raise ValidationError(_(
39+
"Net price of '%s', '%.2f' must be higher that the last purchase invoice price '%s'"
40+
)%(record.product_template_id.name,net_price_sale,invoice_line.price_unit) )
41+
# FASE 4 DEL DESARROLLO - parte 1
42+
if not record.offer_price:
43+
if net_price_sale <= self._get_price_from_lowest_pricelist_id():
44+
raise ValidationError(_(
45+
"Net price of '%s', '%.2f' must be higher that the MAYM pricelist '%s'"
46+
)%(record.product_template_id.name,net_price_sale,self._get_price_from_lowest_pricelist_id()) )
47+
48+
# FASE 2 desarrollo
49+
def _get_price_from_lowest_pricelist_id(self):
50+
company = self._context.get("company_id", False) and self.env["res.company"].browse(
51+
self._context["company_id"]
52+
) or self.env.user.company_id
53+
54+
sale_order_obj = self.env['sale.order']
55+
self.order_id.mapped("order_line.tax_id")
56+
57+
order_line_vals = []
58+
for line in self:
59+
line_vals = {
60+
"product_id": line.product_id.id,
61+
"product_uom_qty": line.product_uom_qty,
62+
"order_id": False,
63+
}
64+
order_line_vals.append(line_vals)
65+
66+
so_vals = {
67+
"partner_id": self.order_id.partner_id.id,
68+
"pricelist_id": company.lowest_pricelist_id.id,
69+
"order_line": order_line_vals,
70+
}
71+
valued_order = sale_order_obj.new(so_vals)
72+
valued_order.order_line._compute_pricelist_item_id()
73+
74+
price = valued_order.order_line.price_unit
75+
discount = valued_order.order_line.discount
76+
return (price - (price * (discount/100)))

0 commit comments

Comments
 (0)