From 1b4fea834b994f388d369528f574392707ddc21a Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Tue, 21 Jan 2025 10:25:50 -0500 Subject: [PATCH 1/5] [ADD] product_list_price_from_pricelist: Automatically compute product list prices based on a pricelist. --- product_list_price_from_pricelist/README.rst | 119 +++++ product_list_price_from_pricelist/__init__.py | 1 + .../__manifest__.py | 14 + .../data/cron.xml | 15 + product_list_price_from_pricelist/i18n/es.po | 101 ++++ .../product_list_price_from_pricelist.pot | 90 ++++ .../models/__init__.py | 3 + .../models/product_pricelist.py | 142 ++++++ .../models/res_company.py | 20 + .../models/res_config_settings.py | 22 + .../readme/CONFIGURE.rst | 14 + .../readme/CONTRIBUTORS.rst | 4 + .../readme/DESCRIPTION.rst | 1 + .../readme/ROADMAP.rst | 2 + .../readme/USAGE.rst | 4 + .../static/description/icon.png | Bin 0 -> 10254 bytes .../static/description/index.html | 463 ++++++++++++++++++ .../tests/__init__.py | 1 + .../tests/test_pricelist.py | 363 ++++++++++++++ .../views/res_config_settings_views.xml | 66 +++ 20 files changed, 1445 insertions(+) create mode 100644 product_list_price_from_pricelist/README.rst create mode 100644 product_list_price_from_pricelist/__init__.py create mode 100644 product_list_price_from_pricelist/__manifest__.py create mode 100644 product_list_price_from_pricelist/data/cron.xml create mode 100644 product_list_price_from_pricelist/i18n/es.po create mode 100644 product_list_price_from_pricelist/i18n/product_list_price_from_pricelist.pot create mode 100644 product_list_price_from_pricelist/models/__init__.py create mode 100644 product_list_price_from_pricelist/models/product_pricelist.py create mode 100644 product_list_price_from_pricelist/models/res_company.py create mode 100644 product_list_price_from_pricelist/models/res_config_settings.py create mode 100644 product_list_price_from_pricelist/readme/CONFIGURE.rst create mode 100644 product_list_price_from_pricelist/readme/CONTRIBUTORS.rst create mode 100644 product_list_price_from_pricelist/readme/DESCRIPTION.rst create mode 100644 product_list_price_from_pricelist/readme/ROADMAP.rst create mode 100644 product_list_price_from_pricelist/readme/USAGE.rst create mode 100644 product_list_price_from_pricelist/static/description/icon.png create mode 100644 product_list_price_from_pricelist/static/description/index.html create mode 100644 product_list_price_from_pricelist/tests/__init__.py create mode 100644 product_list_price_from_pricelist/tests/test_pricelist.py create mode 100644 product_list_price_from_pricelist/views/res_config_settings_views.xml diff --git a/product_list_price_from_pricelist/README.rst b/product_list_price_from_pricelist/README.rst new file mode 100644 index 00000000000..85653877407 --- /dev/null +++ b/product_list_price_from_pricelist/README.rst @@ -0,0 +1,119 @@ +============================================ +Compute product sales price from a pricelist +============================================ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:8a1859f6320d0128db04e5a8467aafca747d73d520ea2f2124e46572b3ce98ef + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fproduct--attribute-lightgray.png?logo=github + :target: https://github.com/OCA/product-attribute/tree/16.0/product_list_price_from_pricelist + :alt: OCA/product-attribute +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/product-attribute-16-0/product-attribute-16-0-product_list_price_from_pricelist + :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/product-attribute&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module enables the automatic computation of a product's sale price based on the configuration of a pricelist. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +- Go to `Sales` -> `Products` -> `Pricelists`. +- Create a new pricelist and add at least one rule. +- Specify the product template or category for the rule. +- Set the `computation mode` and save + +**Note**: Ensure the minimum quantity is not great than 1 for the rule to apply effectively. + +- Go to `Sales` -> `Configuration` -> `Settings`. +- In the `Pricing` section, select the `Pricelist to compute sale price` created in the previous step. +- Optionally and only with a multi-company environment enabled, set the `Main company for compute sale price` to restrict the computation to a specific company. +- Save the configuration + +The module creates a cron job to update the product list price every day. The cron job is disabled by default. +To enable it, go to `Settings` -> `Technical` -> `Automation` -> `Scheduled Actions` and search for `Product sale price: Update price from pricelist`. + +Usage +===== + +**To update product prices according to the pricelist rules** + +- Stay in the settings configuration with the selected Pricelist. +- Click the **Update Product Prices** button to apply the rules and update the sale prices of all products. + +Known issues / Roadmap +====================== + +The `list_price` field is not `company-dependent`, meaning that if a product is shared across multiple companies, the same list price will apply to all of them. +To minimize errors, you can set a primary company to take precedence. This can be configured in the settings under the field `Main company for computing sale price`. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub 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 `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Tecnativa + +Contributors +~~~~~~~~~~~~ + +* `Tecnativa `_ + + * Pedro M. Baeza + * Carlos López + +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-carlos-lopez-tecnativa| image:: https://github.com/carlos-lopez-tecnativa.png?size=40px + :target: https://github.com/carlos-lopez-tecnativa + :alt: carlos-lopez-tecnativa + +Current `maintainer `__: + +|maintainer-carlos-lopez-tecnativa| + +This module is part of the `OCA/product-attribute `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/product_list_price_from_pricelist/__init__.py b/product_list_price_from_pricelist/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/product_list_price_from_pricelist/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/product_list_price_from_pricelist/__manifest__.py b/product_list_price_from_pricelist/__manifest__.py new file mode 100644 index 00000000000..7b6fec09a7e --- /dev/null +++ b/product_list_price_from_pricelist/__manifest__.py @@ -0,0 +1,14 @@ +{ + "name": "Compute product sales price from a pricelist", + "version": "16.0.1.0.0", + "author": "Tecnativa, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/product-attribute", + "depends": [ + "sale", + ], + "data": ["data/cron.xml", "views/res_config_settings_views.xml"], + "maintainers": ["carlos-lopez-tecnativa"], + "installable": True, + "auto_install": False, + "license": "AGPL-3", +} diff --git a/product_list_price_from_pricelist/data/cron.xml b/product_list_price_from_pricelist/data/cron.xml new file mode 100644 index 00000000000..40dfe33e5c4 --- /dev/null +++ b/product_list_price_from_pricelist/data/cron.xml @@ -0,0 +1,15 @@ + + + + Product sale price: Update price from pricelist + + code + model._cron_update_product_list_price() + + 1 + days + -1 + + + + diff --git a/product_list_price_from_pricelist/i18n/es.po b/product_list_price_from_pricelist/i18n/es.po new file mode 100644 index 00000000000..7948c502b66 --- /dev/null +++ b/product_list_price_from_pricelist/i18n/es.po @@ -0,0 +1,101 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_list_price_from_pricelist +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-01-23 17:33+0000\n" +"PO-Revision-Date: 2025-01-23 12:33-0500\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.5\n" + +#. module: product_list_price_from_pricelist +#: model_terms:ir.ui.view,arch_db:product_list_price_from_pricelist.view_res_config_settings_form +msgid "Are you sure you want to update the prices for all products?. This operations cannot be undone." +msgstr "" +"¿Estás seguro de actualizar el precio de venta en todos los productos?. Esta operación no puede revertirse." + +#. module: product_list_price_from_pricelist +#: model:ir.model,name:product_list_price_from_pricelist.model_res_company +msgid "Companies" +msgstr "Compañías" + +#. module: product_list_price_from_pricelist +#: model:ir.model,name:product_list_price_from_pricelist.model_res_config_settings +msgid "Config Settings" +msgstr "Ajustes de configuración" + +#. module: product_list_price_from_pricelist +#: model_terms:ir.ui.view,arch_db:product_list_price_from_pricelist.view_res_config_settings_form +msgid "" +"If set, prices will be computed only if the company in the product matches the company specified here or is " +"empty.\n" +" Otherwise, prices will be computed based on the current company." +msgstr "" +"Si está configurada, los precios serán calculados solo si la compañía coincide con la compañía del producto " +"(o si esta vacía).\n" +" De lo contrario, los precios se calcularan basados en la compañía actual." + +#. module: product_list_price_from_pricelist +#: model:ir.model.fields,field_description:product_list_price_from_pricelist.field_res_config_settings__main_company_compute_price_id +msgid "Main company for compute sale price" +msgstr "Compañía principal para calcular precios de venta" + +#. module: product_list_price_from_pricelist +#: model:ir.model,name:product_list_price_from_pricelist.model_product_pricelist +msgid "Pricelist" +msgstr "Lista de precios" + +#. module: product_list_price_from_pricelist +#: model:ir.model,name:product_list_price_from_pricelist.model_product_pricelist_item +msgid "Pricelist Rule" +msgstr "Regla de la lista de precios" + +#. module: product_list_price_from_pricelist +#: model:ir.model.fields,help:product_list_price_from_pricelist.field_res_company__base_pricelist_compute_price_id +#: model:ir.model.fields,help:product_list_price_from_pricelist.field_res_config_settings__base_pricelist_compute_price_id +msgid "Pricelist used to calculate the price of all products" +msgstr "Lista de precio usada para calcular el precio de venta de todos los productos" + +#. module: product_list_price_from_pricelist +#: model:ir.actions.server,name:product_list_price_from_pricelist.ir_cron_update_product_sale_price_ir_actions_server +#: model:ir.cron,cron_name:product_list_price_from_pricelist.ir_cron_update_product_sale_price +msgid "Product sale price: Update price from pricelist" +msgstr "Precio de venta de productos: Actualizar precio desde tarifas de venta" + +#. module: product_list_price_from_pricelist +#: model:ir.model.fields,field_description:product_list_price_from_pricelist.field_res_company__base_pricelist_compute_price_id +#: model:ir.model.fields,field_description:product_list_price_from_pricelist.field_res_config_settings__base_pricelist_compute_price_id +msgid "Recomputing pricelist" +msgstr "Lista de precio para calcular precio de venta" + +#. module: product_list_price_from_pricelist +#: model_terms:ir.ui.view,arch_db:product_list_price_from_pricelist.view_res_config_settings_form +msgid "" +"Set the base pricelist to compute the sales price for all the products.\n" +"
" +msgstr "" +"Configure la tarifa de precios base para calcular el precio de venta de los productos.\n" +"
" + +#. module: product_list_price_from_pricelist +#: model_terms:ir.ui.view,arch_db:product_list_price_from_pricelist.view_res_config_settings_form +msgid "Update product prices" +msgstr "Actualizar precio de venta en productos" + +#. module: product_list_price_from_pricelist +#: model_terms:ir.ui.view,arch_db:product_list_price_from_pricelist.view_res_config_settings_form +msgid "" +"WARNING: Prices are always computed for a quantity of 1, so rules with a minimum quantity higher than that " +"won't be taken into account" +msgstr "" +"ADVERTENCIA: Los precios siempre se calculan para la cantidad de 1, las reglas con cantidad mínima mayor a " +"esto no serán tomadas en cuenta" diff --git a/product_list_price_from_pricelist/i18n/product_list_price_from_pricelist.pot b/product_list_price_from_pricelist/i18n/product_list_price_from_pricelist.pot new file mode 100644 index 00000000000..e0b6763d8d9 --- /dev/null +++ b/product_list_price_from_pricelist/i18n/product_list_price_from_pricelist.pot @@ -0,0 +1,90 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_list_price_from_pricelist +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: product_list_price_from_pricelist +#: model_terms:ir.ui.view,arch_db:product_list_price_from_pricelist.view_res_config_settings_form +msgid "" +"Are you sure you want to update the prices for all products?. This " +"operations cannot be undone." +msgstr "" + +#. module: product_list_price_from_pricelist +#: model:ir.model,name:product_list_price_from_pricelist.model_res_company +msgid "Companies" +msgstr "" + +#. module: product_list_price_from_pricelist +#: model:ir.model,name:product_list_price_from_pricelist.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: product_list_price_from_pricelist +#: model_terms:ir.ui.view,arch_db:product_list_price_from_pricelist.view_res_config_settings_form +msgid "" +"If set, prices will be computed only if the company in the product matches the company specified here or is empty.\n" +" Otherwise, prices will be computed based on the current company." +msgstr "" + +#. module: product_list_price_from_pricelist +#: model:ir.model.fields,field_description:product_list_price_from_pricelist.field_res_config_settings__main_company_compute_price_id +msgid "Main company for compute sale price" +msgstr "" + +#. module: product_list_price_from_pricelist +#: model:ir.model,name:product_list_price_from_pricelist.model_product_pricelist +msgid "Pricelist" +msgstr "" + +#. module: product_list_price_from_pricelist +#: model:ir.model,name:product_list_price_from_pricelist.model_product_pricelist_item +msgid "Pricelist Rule" +msgstr "" + +#. module: product_list_price_from_pricelist +#: model:ir.model.fields,help:product_list_price_from_pricelist.field_res_company__base_pricelist_compute_price_id +#: model:ir.model.fields,help:product_list_price_from_pricelist.field_res_config_settings__base_pricelist_compute_price_id +msgid "Pricelist used to calculate the price of all products" +msgstr "" + +#. module: product_list_price_from_pricelist +#: model:ir.actions.server,name:product_list_price_from_pricelist.ir_cron_update_product_sale_price_ir_actions_server +#: model:ir.cron,cron_name:product_list_price_from_pricelist.ir_cron_update_product_sale_price +msgid "Product sale price: Update price from pricelist" +msgstr "" + +#. module: product_list_price_from_pricelist +#: model:ir.model.fields,field_description:product_list_price_from_pricelist.field_res_company__base_pricelist_compute_price_id +#: model:ir.model.fields,field_description:product_list_price_from_pricelist.field_res_config_settings__base_pricelist_compute_price_id +msgid "Recomputing pricelist" +msgstr "" + +#. module: product_list_price_from_pricelist +#: model_terms:ir.ui.view,arch_db:product_list_price_from_pricelist.view_res_config_settings_form +msgid "" +"Set the base pricelist to compute the sales price for all the products.\n" +"
" +msgstr "" + +#. module: product_list_price_from_pricelist +#: model_terms:ir.ui.view,arch_db:product_list_price_from_pricelist.view_res_config_settings_form +msgid "Update product prices" +msgstr "" + +#. module: product_list_price_from_pricelist +#: model_terms:ir.ui.view,arch_db:product_list_price_from_pricelist.view_res_config_settings_form +msgid "" +"WARNING: Prices are always computed for a quantity of 1, so rules with a " +"minimum quantity higher than that won't be taken into account" +msgstr "" diff --git a/product_list_price_from_pricelist/models/__init__.py b/product_list_price_from_pricelist/models/__init__.py new file mode 100644 index 00000000000..05af701063a --- /dev/null +++ b/product_list_price_from_pricelist/models/__init__.py @@ -0,0 +1,3 @@ +from . import res_company +from . import res_config_settings +from . import product_pricelist diff --git a/product_list_price_from_pricelist/models/product_pricelist.py b/product_list_price_from_pricelist/models/product_pricelist.py new file mode 100644 index 00000000000..c61a5609c38 --- /dev/null +++ b/product_list_price_from_pricelist/models/product_pricelist.py @@ -0,0 +1,142 @@ +from odoo import api, models +from odoo.tools import config + + +class ProductPricelist(models.Model): + _inherit = "product.pricelist" + + def _update_product_price_from_pricelist(self, pricelist_items=None): + self.ensure_one() + all_templates = self.env["product.template"] + if not pricelist_items: + pricelist_items = self.item_ids + for item in pricelist_items: + all_templates |= item._get_all_templates_from_pricelist_item() + if all_templates: + pricelist_data = self._compute_price_rule(all_templates, 1) + for template in all_templates: + new_price, suitable_rule = pricelist_data[template.id] + if suitable_rule and new_price != template.list_price: + template.write({"list_price": new_price}) + return True + + def _get_domain_applicability_for_company(self): + """Return the domain to check if the pricelist is applicable for the company.""" + self.ensure_one() + main_company = self._get_main_company_to_compute_prices() + domain = [ + ("base_pricelist_compute_price_id", "=", self.id), + ("id", "=", main_company.id), + ] + return domain + + def _get_main_company_to_compute_prices(self): + """:return: Recordset of res.company""" + main_company_id = ( + self.env["ir.config_parameter"] + .sudo() + .get_param("main_company_compute_price_id", "") + ) + if main_company_id: + return self.env["res.company"].browse(int(main_company_id)) + return self.company_id or self.env.company + + +class ProductPricelistItem(models.Model): + _inherit = "product.pricelist.item" + + @api.model_create_multi + def create(self, vals_list): + new_pricelist_items = super().create(vals_list) + test_condition = not config["test_enable"] or ( + config["test_enable"] + and self.env.context.get("test_compute_list_price_from_pricelist") + ) + # recompute the product's sales price. + if test_condition: + for pricelist in new_pricelist_items.pricelist_id: + pricelist._update_product_price_from_pricelist(new_pricelist_items) + return new_pricelist_items + + def write(self, vals): + res = super().write(vals) + test_condition = not config["test_enable"] or ( + config["test_enable"] + and self.env.context.get("test_compute_list_price_from_pricelist") + ) + # If any field from the expected ones is changed, + # recompute the product's sales price. + if test_condition and set(vals.keys()).intersection( + self._get_fields_to_recompute_product_list_price() + ): + for pricelist in self.pricelist_id: + pricelist._update_product_price_from_pricelist(self) + return res + + @api.model + def _get_fields_to_recompute_product_list_price(self): + """Return the list of fields that will trigger the + recomputation of the products list price. + :return: list(str) + """ + fields_triggers = [ + "applied_on", + "base", + "base_pricelist_id", + "categ_id", + "compute_price", + "date_start", + "date_end", + "fixed_price", + "percent_price", + "price_discount", + "price_surcharge", + "price_round", + "product_tmpl_id", + "product_id", + ] + return fields_triggers + + def _get_all_templates_from_pricelist_item(self): + """Returns the products template + affected by the pricelist item that require recomputation. + :return: Recordset of product.template""" + self.ensure_one() + templates = self.env["product.template"] + company = self.pricelist_id._get_main_company_to_compute_prices() + is_pricelist_available = bool( + self.env["res.company"].search_count( + self.pricelist_id._get_domain_applicability_for_company() + ) + ) + if not is_pricelist_available or ( + self.pricelist_id.company_id + and self.pricelist_id.company_id.id != company.id + ): + return self.env["product.template"] # empty recordset + domain_company = [("company_id", "in", [False, company.id])] + if self.applied_on == "3_global": + templates = self.env["product.template"].search(domain_company) + elif self.applied_on == "2_product_category" and self.categ_id: + templates = self.env["product.template"].search( + [("categ_id", "=", self.categ_id.id)] + domain_company + ) + elif ( + self.applied_on == "1_product" + and self.product_tmpl_id + and ( + not self.product_tmpl_id.company_id + or self.product_tmpl_id.company_id.id == company.id + ) + ): + templates = self.product_tmpl_id + elif ( + self.applied_on == "0_product_variant" + and self.product_id + and ( + not self.product_id.company_id + or self.product_id.company_id.id == company.id + ) + ): + templates = self.product_id.product_tmpl_id + return templates diff --git a/product_list_price_from_pricelist/models/res_company.py b/product_list_price_from_pricelist/models/res_company.py new file mode 100644 index 00000000000..cf8d716ef9f --- /dev/null +++ b/product_list_price_from_pricelist/models/res_company.py @@ -0,0 +1,20 @@ +from odoo import api, fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + base_pricelist_compute_price_id = fields.Many2one( + "product.pricelist", + string="Recomputing pricelist", + help="Pricelist used to calculate the price of all products", + ) + + @api.model + def _cron_update_product_list_price(self): + companies = self.search([("base_pricelist_compute_price_id", "!=", False)]) + for company in companies: + company.base_pricelist_compute_price_id.with_company( + company + )._update_product_price_from_pricelist() + return True diff --git a/product_list_price_from_pricelist/models/res_config_settings.py b/product_list_price_from_pricelist/models/res_config_settings.py new file mode 100644 index 00000000000..a1555e4c2b7 --- /dev/null +++ b/product_list_price_from_pricelist/models/res_config_settings.py @@ -0,0 +1,22 @@ +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + base_pricelist_compute_price_id = fields.Many2one( + "product.pricelist", + related="company_id.base_pricelist_compute_price_id", + readonly=False, + ) + main_company_compute_price_id = fields.Many2one( + "res.company", + config_parameter="main_company_compute_price_id", + string="Main company for compute sale price", + readonly=False, + ) + + def action_update_product_price_from_pricelist(self): + self.ensure_one() + pricelist = self.company_id.base_pricelist_compute_price_id + pricelist._update_product_price_from_pricelist() diff --git a/product_list_price_from_pricelist/readme/CONFIGURE.rst b/product_list_price_from_pricelist/readme/CONFIGURE.rst new file mode 100644 index 00000000000..474d10fb05c --- /dev/null +++ b/product_list_price_from_pricelist/readme/CONFIGURE.rst @@ -0,0 +1,14 @@ +- Go to `Sales` -> `Products` -> `Pricelists`. +- Create a new pricelist and add at least one rule. +- Specify the product template or category for the rule. +- Set the `computation mode` and save + +**Note**: Ensure the minimum quantity is not great than 1 for the rule to apply effectively. + +- Go to `Sales` -> `Configuration` -> `Settings`. +- In the `Pricing` section, select the `Pricelist to compute sale price` created in the previous step. +- Optionally and only with a multi-company environment enabled, set the `Main company for compute sale price` to restrict the computation to a specific company. +- Save the configuration + +The module creates a cron job to update the product list price every day. The cron job is disabled by default. +To enable it, go to `Settings` -> `Technical` -> `Automation` -> `Scheduled Actions` and search for `Product sale price: Update price from pricelist`. \ No newline at end of file diff --git a/product_list_price_from_pricelist/readme/CONTRIBUTORS.rst b/product_list_price_from_pricelist/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..29e931a17d4 --- /dev/null +++ b/product_list_price_from_pricelist/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* `Tecnativa `_ + + * Pedro M. Baeza + * Carlos López \ No newline at end of file diff --git a/product_list_price_from_pricelist/readme/DESCRIPTION.rst b/product_list_price_from_pricelist/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..7532b365c4b --- /dev/null +++ b/product_list_price_from_pricelist/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module enables the automatic computation of a product's sale price based on the configuration of a pricelist. \ No newline at end of file diff --git a/product_list_price_from_pricelist/readme/ROADMAP.rst b/product_list_price_from_pricelist/readme/ROADMAP.rst new file mode 100644 index 00000000000..ead3062524c --- /dev/null +++ b/product_list_price_from_pricelist/readme/ROADMAP.rst @@ -0,0 +1,2 @@ +The `list_price` field is not `company-dependent`, meaning that if a product is shared across multiple companies, the same list price will apply to all of them. +To minimize errors, you can set a primary company to take precedence. This can be configured in the settings under the field `Main company for computing sale price`. \ No newline at end of file diff --git a/product_list_price_from_pricelist/readme/USAGE.rst b/product_list_price_from_pricelist/readme/USAGE.rst new file mode 100644 index 00000000000..5ce4cf695e4 --- /dev/null +++ b/product_list_price_from_pricelist/readme/USAGE.rst @@ -0,0 +1,4 @@ +**To update product prices according to the pricelist rules** + +- Stay in the settings configuration with the selected Pricelist. +- Click the **Update Product Prices** button to apply the rules and update the sale prices of all products. \ No newline at end of file diff --git a/product_list_price_from_pricelist/static/description/icon.png b/product_list_price_from_pricelist/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1dcc49c24f364e9adf0afbc6fc0bac6dbecdeb11 GIT binary patch literal 10254 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 + + + + +Compute product sales price from a pricelist + + + +
+

Compute product sales price from a pricelist

+ + +

Beta License: AGPL-3 OCA/product-attribute Translate me on Weblate Try me on Runboat

+

This module enables the automatic computation of a product’s sale price based on the configuration of a pricelist.

+

Table of contents

+ +
+

Configuration

+
    +
  • Go to Sales -> Products -> Pricelists.
  • +
  • Create a new pricelist and add at least one rule.
  • +
  • Specify the product template or category for the rule.
  • +
  • Set the computation mode and save
  • +
+

Note: Ensure the minimum quantity is not great than 1 for the rule to apply effectively.

+
    +
  • Go to Sales -> Configuration -> Settings.
  • +
  • In the Pricing section, select the Pricelist to compute sale price created in the previous step.
  • +
  • Optionally and only with a multi-company environment enabled, set the Main company for compute sale price to restrict the computation to a specific company.
  • +
  • Save the configuration
  • +
+

The module creates a cron job to update the product list price every day. The cron job is disabled by default. +To enable it, go to Settings -> Technical -> Automation -> Scheduled Actions and search for Product sale price: Update price from pricelist.

+
+
+

Usage

+

To update product prices according to the pricelist rules

+
    +
  • Stay in the settings configuration with the selected Pricelist.
  • +
  • Click the Update Product Prices button to apply the rules and update the sale prices of all products.
  • +
+
+
+

Known issues / Roadmap

+

The list_price field is not company-dependent, meaning that if a product is shared across multiple companies, the same list price will apply to all of them. +To minimize errors, you can set a primary company to take precedence. This can be configured in the settings under the field Main company for computing sale price.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub 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.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Tecnativa
  • +
+
+
+

Contributors

+
    +
  • Tecnativa
      +
    • Pedro M. Baeza
    • +
    • Carlos López
    • +
    +
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

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.

+

Current maintainer:

+

carlos-lopez-tecnativa

+

This module is part of the OCA/product-attribute project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/product_list_price_from_pricelist/tests/__init__.py b/product_list_price_from_pricelist/tests/__init__.py new file mode 100644 index 00000000000..61e09273836 --- /dev/null +++ b/product_list_price_from_pricelist/tests/__init__.py @@ -0,0 +1 @@ +from . import test_pricelist diff --git a/product_list_price_from_pricelist/tests/test_pricelist.py b/product_list_price_from_pricelist/tests/test_pricelist.py new file mode 100644 index 00000000000..78aa6865e1f --- /dev/null +++ b/product_list_price_from_pricelist/tests/test_pricelist.py @@ -0,0 +1,363 @@ +from odoo.tools import float_compare + +from odoo.addons.base.tests.common import TransactionCase + + +class TestPricelistGlobal(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env( + context=dict(cls.env.context, test_compute_list_price_from_pricelist=True) + ) + cls.Product = cls.env["product.product"] + cls.ProductTemplate = cls.env["product.template"] + cls.ProductCateg = cls.env["product.category"] + cls.Pricelist = cls.env["product.pricelist"] + cls.PricelistItem = cls.env["product.pricelist.item"] + cls.company_2 = cls.env["res.company"].create({"name": "Company 2"}) + cls.categ_1 = cls.ProductCateg.create({"name": "Categ 1"}) + cls.categ_2 = cls.ProductCateg.create({"name": "Categ 2"}) + cls.product_1 = cls.Product.create( + { + "name": "Product 1", + "list_price": 100, + "standard_price": 80, + "categ_id": cls.categ_1.id, + } + ) + cls.product_2 = cls.Product.create( + { + "name": "Product 2", + "list_price": 200, + "standard_price": 180, + "categ_id": cls.categ_1.id, + } + ) + cls.product_3 = cls.Product.create( + { + "name": "Product 3", + "list_price": 300, + "standard_price": 280, + "categ_id": cls.categ_2.id, + } + ) + # this product is not affected by the pricelist + # the price should remain unchanged + cls.product_4 = cls.Product.create( + { + "name": "Product 4", + "list_price": 400, + "categ_id": cls.categ_2.id, + } + ) + # this product just belongs to company 2 + cls.product_5 = cls.Product.create( + { + "name": "Product 4", + "list_price": 500, + "categ_id": cls.categ_2.id, + "company_id": cls.company_2.id, + } + ) + cls.base_pricelist = cls.Pricelist.create({"name": "Base Pricelist"}) + cls.base_pricelist_item_global = cls.PricelistItem.create( + { + "pricelist_id": cls.base_pricelist.id, + "applied_on": "3_global", + "compute_price": "percentage", + "percent_price": -5, + } + ) + cls.base_pricelist_item_product_3 = cls.PricelistItem.create( + { + "pricelist_id": cls.base_pricelist.id, + "applied_on": "0_product_variant", + "product_id": cls.product_3.id, + "compute_price": "percentage", + "percent_price": -10, + } + ) + cls.pricelist = cls.Pricelist.create({"name": "Pricelist"}) + cls.pricelist_item_by_product = cls.PricelistItem.create( + { + "pricelist_id": cls.pricelist.id, + "applied_on": "0_product_variant", + "product_id": cls.product_3.id, + "compute_price": "percentage", + "percent_price": 10, + } + ) + cls.pricelist_item_by_categ = cls.PricelistItem.create( + { + "pricelist_id": cls.pricelist.id, + "applied_on": "2_product_category", + "categ_id": cls.categ_1.id, + "compute_price": "percentage", + "percent_price": 20, + } + ) + # this pricelist is for company 2 + # and just affects the product_4, product_5 and categ_1(products 1 and 2) + cls.pricelist_c2 = cls.Pricelist.create( + {"name": "Pricelist C2", "company_id": cls.company_2.id} + ) + cls.pricelist_item_by_product4_c2 = cls.PricelistItem.create( + { + "pricelist_id": cls.pricelist_c2.id, + "applied_on": "1_product", + "product_tmpl_id": cls.product_4.product_tmpl_id.id, + "compute_price": "percentage", + "percent_price": -10, + } + ) + cls.pricelist_item_by_product5_c2 = cls.PricelistItem.create( + { + "pricelist_id": cls.pricelist_c2.id, + "applied_on": "1_product", + "product_tmpl_id": cls.product_5.product_tmpl_id.id, + "compute_price": "percentage", + "percent_price": -10, + } + ) + cls.pricelist_item_by_categ_c2 = cls.PricelistItem.create( + { + "pricelist_id": cls.pricelist_c2.id, + "applied_on": "2_product_category", + "categ_id": cls.categ_1.id, + "compute_price": "percentage", + "percent_price": -5, + } + ) + cls.env.company.base_pricelist_compute_price_id = cls.pricelist + cls.company_2.base_pricelist_compute_price_id = cls.pricelist_c2 + + def test_02_pricelist_compute_price_percentage_with_discount(self): + self.pricelist._update_product_price_from_pricelist() + self.assertEqual(self.product_1.list_price, 80) + self.assertEqual(self.product_2.list_price, 160) + self.assertEqual(self.product_3.list_price, 270) + self.assertEqual(self.product_4.list_price, 400) + self.assertEqual(self.product_5.list_price, 500) + + def test_03_pricelist_compute_price_percentage_with_recharge(self): + self.pricelist_item_by_product.write({"percent_price": -10}) + self.pricelist_item_by_categ.write({"percent_price": -20}) + self.assertEqual(self.product_1.list_price, 120) + self.assertEqual(self.product_2.list_price, 240) + self.assertEqual(self.product_3.list_price, 330) + self.assertEqual(self.product_4.list_price, 400) + self.assertEqual(self.product_5.list_price, 500) + + def test_04_pricelist_compute_price_fixed(self): + self.pricelist_item_by_product.write( + {"compute_price": "fixed", "fixed_price": 150} + ) + self.pricelist_item_by_categ.write( + {"compute_price": "fixed", "fixed_price": 250} + ) + self.assertEqual(self.product_1.list_price, 250) + self.assertEqual(self.product_2.list_price, 250) + self.assertEqual(self.product_3.list_price, 150) + self.assertEqual(self.product_4.list_price, 400) + self.assertEqual(self.product_5.list_price, 500) + + def test_05_pricelist_compute_price_formula(self): + self.pricelist_item_by_product.write( + {"compute_price": "formula", "price_discount": -10} + ) + self.pricelist_item_by_categ.write( + {"compute_price": "formula", "price_discount": -20} + ) + self.assertEqual(self.product_1.list_price, 120) + self.assertEqual(self.product_2.list_price, 240) + self.assertEqual(self.product_3.list_price, 330) + self.assertEqual(self.product_4.list_price, 400) + self.assertEqual(self.product_5.list_price, 500) + + def test_06_pricelist_compute_price_formula_round(self): + self.pricelist_item_by_product.write( + { + "compute_price": "formula", + "price_discount": -10, + "price_round": 10, + "price_surcharge": -0.01, + } + ) + self.pricelist_item_by_categ.write( + { + "compute_price": "formula", + "price_discount": -20, + "price_round": 10, + "price_surcharge": -0.01, + } + ) + self.assertEqual(float_compare(self.product_1.list_price, 119.99, 2), 0) + self.assertEqual(float_compare(self.product_2.list_price, 239.99, 2), 0) + self.assertEqual(float_compare(self.product_3.list_price, 329.99, 2), 0) + self.assertEqual(self.product_4.list_price, 400) + self.assertEqual(self.product_5.list_price, 500) + + def test_06_pricelist_compute_price_formula_cost(self): + self.pricelist_item_by_product.write( + { + "compute_price": "formula", + "price_discount": -10, + "base": "standard_price", + } + ) + self.pricelist_item_by_categ.write( + { + "compute_price": "formula", + "price_discount": -20, + "base": "standard_price", + } + ) + self.assertEqual(self.product_1.list_price, 96) + self.assertEqual(self.product_2.list_price, 216) + self.assertEqual(self.product_3.list_price, 308) + self.assertEqual(self.product_4.list_price, 400) + self.assertEqual(self.product_5.list_price, 500) + + def test_06_pricelist_compute_price_formula_other_pricelist(self): + self.pricelist_item_by_product.write( + { + "compute_price": "formula", + "price_discount": -10, + "base": "pricelist", + "base_pricelist_id": self.base_pricelist.id, + } + ) + self.pricelist_item_by_categ.write( + { + "compute_price": "formula", + "price_discount": -20, + "base": "pricelist", + "base_pricelist_id": self.base_pricelist.id, + } + ) + self.assertEqual(self.product_1.list_price, 126) + self.assertEqual(self.product_2.list_price, 252) + self.assertEqual(self.product_3.list_price, 363) + self.assertEqual(self.product_4.list_price, 400) + self.assertEqual(self.product_5.list_price, 500) + + def test_06_pricelist_compute_price_automatically(self): + # change fields that trigger recomputation + self.pricelist_item_by_product.write( + { + "compute_price": "formula", + "price_discount": -10, + "base": "pricelist", + "base_pricelist_id": self.base_pricelist.id, + } + ) + self.pricelist_item_by_categ.write( + { + "compute_price": "formula", + "price_discount": -20, + "base": "pricelist", + "base_pricelist_id": self.base_pricelist.id, + } + ) + self.assertEqual(self.product_1.list_price, 126) + self.assertEqual(self.product_2.list_price, 252) + self.assertEqual(self.product_3.list_price, 363) + self.assertEqual(self.product_4.list_price, 400) + self.assertEqual(self.product_5.list_price, 500) + # Change fields that do not trigger recomputation + # the prices should remain unchanged. + self.pricelist_item_by_product.write( + { + "price_max_margin": 100, + "price_min_margin": 1, + } + ) + self.pricelist_item_by_categ.write( + { + "price_max_margin": 200, + "price_min_margin": 2, + } + ) + self.assertEqual(self.product_1.list_price, 126) + self.assertEqual(self.product_2.list_price, 252) + self.assertEqual(self.product_3.list_price, 363) + self.assertEqual(self.product_4.list_price, 400) + self.assertEqual(self.product_5.list_price, 500) + + def test_07_pricelist_global(self): + """ + product_1 and product_2: Apply a 20% surcharge. + product_3: Apply a 10% surcharge. + product_4: Apply a 5% surcharge globally. + """ + self.pricelist_item_by_categ.write({"percent_price": -20}) + self.pricelist_item_by_product.write({"percent_price": -10}) + self.assertEqual(self.product_1.list_price, 120) + self.assertEqual(self.product_2.list_price, 240) + self.assertEqual(self.product_3.list_price, 330) + self.assertEqual(self.product_4.list_price, 400) # no rule applied + self.assertEqual(self.product_5.list_price, 500) # no rule applied + # create a new pricelist item global + # and the all prices should be recomputed automatically for all products + # according to the rules, but this pricelist triggers the recomputation + self.PricelistItem.create( + { + "pricelist_id": self.pricelist.id, + "applied_on": "3_global", + "compute_price": "percentage", + "percent_price": -5, + } + ) + self.assertEqual(self.product_1.list_price, 144) + self.assertEqual(self.product_2.list_price, 288) + self.assertEqual(self.product_3.list_price, 363) + self.assertEqual(self.product_4.list_price, 420) + self.assertEqual(self.product_5.list_price, 500) + + def test_08_pricelist_min_quantity(self): + # min_quantity > 1 should not apply the rule + self.assertEqual(self.product_3.list_price, 300) + self.pricelist_item_by_product.write({"percent_price": -10, "min_quantity": 2}) + self.assertEqual(self.product_3.list_price, 300) + + def test_08_pricelist_multicompany(self): + """ + In C1: + product_1 and product_2: Apply a 20% surcharge. + product_3: Apply a 10% surcharge. + product_4: Not affected by the pricelist in C1. + product_5: Not affected by the pricelist in C1. + In C2: + product_1 and product_2: Apply a 5% surcharge. + product_3: Not affected by the pricelist. + product_4: Apply a 10% surcharge. + product_5: Apply a 10% surcharge. + """ + self.pricelist_item_by_categ.write({"percent_price": -20}) + self.pricelist_item_by_product.write({"percent_price": -10}) + self.assertEqual(self.product_1.list_price, 120) + self.assertEqual(self.product_2.list_price, 240) + self.assertEqual(self.product_3.list_price, 330) + self.assertEqual(self.product_4.list_price, 400) + self.assertEqual(self.product_5.list_price, 500) + self.env["ir.config_parameter"].set_param( + "main_company_compute_price_id", self.company_2.id + ) + self.pricelist_c2._update_product_price_from_pricelist() + self.assertEqual(self.product_1.list_price, 126) + self.assertEqual(self.product_2.list_price, 252) + self.assertEqual(self.product_3.list_price, 330) + self.assertEqual(self.product_4.list_price, 440) + self.assertEqual(self.product_5.list_price, 550) + # Attempt to compute prices for the pricelist of C1 + # (which does not have a company set, so it uses the environment's company (C2)) + # the prices should remain unchanged. + self.pricelist.with_company( + self.company_2 + )._update_product_price_from_pricelist() + self.assertEqual(self.product_1.list_price, 126) + self.assertEqual(self.product_2.list_price, 252) + self.assertEqual(self.product_3.list_price, 330) + self.assertEqual(self.product_4.list_price, 440) + self.assertEqual(self.product_5.list_price, 550) diff --git a/product_list_price_from_pricelist/views/res_config_settings_views.xml b/product_list_price_from_pricelist/views/res_config_settings_views.xml new file mode 100644 index 00000000000..31cabdd3207 --- /dev/null +++ b/product_list_price_from_pricelist/views/res_config_settings_views.xml @@ -0,0 +1,66 @@ + + + + + view.res.config.settings.form + res.config.settings + + + +
+
+
+
+
+ + + + + From d3f43bd5f055a84d01b7ab48bb72124f3bf6e235 Mon Sep 17 00:00:00 2001 From: mymage Date: Tue, 18 Mar 2025 16:39:17 +0000 Subject: [PATCH 2/5] Added translation using Weblate (Italian) --- product_list_price_from_pricelist/i18n/it.po | 104 +++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 product_list_price_from_pricelist/i18n/it.po diff --git a/product_list_price_from_pricelist/i18n/it.po b/product_list_price_from_pricelist/i18n/it.po new file mode 100644 index 00000000000..5f3251b6632 --- /dev/null +++ b/product_list_price_from_pricelist/i18n/it.po @@ -0,0 +1,104 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_list_price_from_pricelist +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-03-18 19:06+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.2\n" + +#. module: product_list_price_from_pricelist +#: model_terms:ir.ui.view,arch_db:product_list_price_from_pricelist.view_res_config_settings_form +msgid "" +"Are you sure you want to update the prices for all products?. This " +"operations cannot be undone." +msgstr "" +"Si è sicuri di voler aggiornare i prezzi di tutti i prodotti? Questa " +"operazione non può essere invertita." + +#. module: product_list_price_from_pricelist +#: model:ir.model,name:product_list_price_from_pricelist.model_res_company +msgid "Companies" +msgstr "Aziende" + +#. module: product_list_price_from_pricelist +#: model:ir.model,name:product_list_price_from_pricelist.model_res_config_settings +msgid "Config Settings" +msgstr "Impostazioni configurazione" + +#. module: product_list_price_from_pricelist +#: model_terms:ir.ui.view,arch_db:product_list_price_from_pricelist.view_res_config_settings_form +msgid "" +"If set, prices will be computed only if the company in the product matches the company specified here or is empty.\n" +" Otherwise, prices will be computed based on the current company." +msgstr "" +"Se impostato, i prezzi saranno calcolati solo se l'azienda nel prodotto " +"corrisponde all'azienda specificata qui o è vuota. \n" +" In caso contrario, i prezzi saranno " +"calcolati in base all'azienda corrente." + +#. module: product_list_price_from_pricelist +#: model:ir.model.fields,field_description:product_list_price_from_pricelist.field_res_config_settings__main_company_compute_price_id +msgid "Main company for compute sale price" +msgstr "Azienda principale per il calcolo del prezzo di vendita" + +#. module: product_list_price_from_pricelist +#: model:ir.model,name:product_list_price_from_pricelist.model_product_pricelist +msgid "Pricelist" +msgstr "Listino" + +#. module: product_list_price_from_pricelist +#: model:ir.model,name:product_list_price_from_pricelist.model_product_pricelist_item +msgid "Pricelist Rule" +msgstr "Regola listino prezzi" + +#. module: product_list_price_from_pricelist +#: model:ir.model.fields,help:product_list_price_from_pricelist.field_res_company__base_pricelist_compute_price_id +#: model:ir.model.fields,help:product_list_price_from_pricelist.field_res_config_settings__base_pricelist_compute_price_id +msgid "Pricelist used to calculate the price of all products" +msgstr "Listino utilizzato per calcolare il prezzo di tutti i prodotti" + +#. module: product_list_price_from_pricelist +#: model:ir.actions.server,name:product_list_price_from_pricelist.ir_cron_update_product_sale_price_ir_actions_server +#: model:ir.cron,cron_name:product_list_price_from_pricelist.ir_cron_update_product_sale_price +msgid "Product sale price: Update price from pricelist" +msgstr "Prezzo di vendita prodotto: aggiorna il prezzo dal listino" + +#. module: product_list_price_from_pricelist +#: model:ir.model.fields,field_description:product_list_price_from_pricelist.field_res_company__base_pricelist_compute_price_id +#: model:ir.model.fields,field_description:product_list_price_from_pricelist.field_res_config_settings__base_pricelist_compute_price_id +msgid "Recomputing pricelist" +msgstr "Ricalcola listino" + +#. module: product_list_price_from_pricelist +#: model_terms:ir.ui.view,arch_db:product_list_price_from_pricelist.view_res_config_settings_form +msgid "" +"Set the base pricelist to compute the sales price for all the products.\n" +"
" +msgstr "" +"Imposta il listino per calcolare il prezzo di vendita per tutti i prodotti.\n" +"
" + +#. module: product_list_price_from_pricelist +#: model_terms:ir.ui.view,arch_db:product_list_price_from_pricelist.view_res_config_settings_form +msgid "Update product prices" +msgstr "Aggiorna prezzi prodotto" + +#. module: product_list_price_from_pricelist +#: model_terms:ir.ui.view,arch_db:product_list_price_from_pricelist.view_res_config_settings_form +msgid "" +"WARNING: Prices are always computed for a quantity of 1, so rules with a " +"minimum quantity higher than that won't be taken into account" +msgstr "" +"Attenzione: i prezzi vengono sempre calcolati per una quantità pari a 1, " +"pertanto le regole con una quantità minima superiore a quella non verranno " +"prese in considerazione" From a20d23280b47d7a9477ebf9beb34b3e7a4acb12a Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Wed, 2 Jul 2025 12:03:25 -0500 Subject: [PATCH 3/5] [IMP] product_list_price_from_pricelist: pre-commit auto fixes --- product_list_price_from_pricelist/README.rst | 57 +++++++++++-------- .../pyproject.toml | 3 + .../readme/CONFIGURE.md | 20 +++++++ .../readme/CONFIGURE.rst | 14 ----- .../readme/CONTRIBUTORS.md | 3 + .../readme/CONTRIBUTORS.rst | 4 -- .../{DESCRIPTION.rst => DESCRIPTION.md} | 3 +- .../readme/ROADMAP.md | 5 ++ .../readme/ROADMAP.rst | 2 - .../readme/{USAGE.rst => USAGE.md} | 3 +- .../static/description/index.html | 41 ++++++++----- .../views/res_config_settings_views.xml | 2 - 12 files changed, 95 insertions(+), 62 deletions(-) create mode 100644 product_list_price_from_pricelist/pyproject.toml create mode 100644 product_list_price_from_pricelist/readme/CONFIGURE.md delete mode 100644 product_list_price_from_pricelist/readme/CONFIGURE.rst create mode 100644 product_list_price_from_pricelist/readme/CONTRIBUTORS.md delete mode 100644 product_list_price_from_pricelist/readme/CONTRIBUTORS.rst rename product_list_price_from_pricelist/readme/{DESCRIPTION.rst => DESCRIPTION.md} (55%) create mode 100644 product_list_price_from_pricelist/readme/ROADMAP.md delete mode 100644 product_list_price_from_pricelist/readme/ROADMAP.rst rename product_list_price_from_pricelist/readme/{USAGE.rst => USAGE.md} (80%) diff --git a/product_list_price_from_pricelist/README.rst b/product_list_price_from_pricelist/README.rst index 85653877407..8d84828d930 100644 --- a/product_list_price_from_pricelist/README.rst +++ b/product_list_price_from_pricelist/README.rst @@ -17,18 +17,19 @@ Compute product sales price from a pricelist :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fproduct--attribute-lightgray.png?logo=github - :target: https://github.com/OCA/product-attribute/tree/16.0/product_list_price_from_pricelist + :target: https://github.com/OCA/product-attribute/tree/18.0/product_list_price_from_pricelist :alt: OCA/product-attribute .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/product-attribute-16-0/product-attribute-16-0-product_list_price_from_pricelist + :target: https://translation.odoo-community.org/projects/product-attribute-18-0/product-attribute-18-0-product_list_price_from_pricelist :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/product-attribute&target_branch=16.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/product-attribute&target_branch=18.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| -This module enables the automatic computation of a product's sale price based on the configuration of a pricelist. +This module enables the automatic computation of a product's sale price +based on the configuration of a pricelist. **Table of contents** @@ -38,20 +39,26 @@ This module enables the automatic computation of a product's sale price based on Configuration ============= -- Go to `Sales` -> `Products` -> `Pricelists`. +- Go to Sales -> Products -> Pricelists. - Create a new pricelist and add at least one rule. - Specify the product template or category for the rule. -- Set the `computation mode` and save +- Set the computation mode and save -**Note**: Ensure the minimum quantity is not great than 1 for the rule to apply effectively. +**Note**: Ensure the minimum quantity is not great than 1 for the rule +to apply effectively. -- Go to `Sales` -> `Configuration` -> `Settings`. -- In the `Pricing` section, select the `Pricelist to compute sale price` created in the previous step. -- Optionally and only with a multi-company environment enabled, set the `Main company for compute sale price` to restrict the computation to a specific company. +- Go to Sales -> Configuration -> Settings. +- In the Pricing section, select the Pricelist to compute sale price + created in the previous step. +- Optionally and only with a multi-company environment enabled, set the + Main company for compute sale price to restrict the computation to a + specific company. - Save the configuration -The module creates a cron job to update the product list price every day. The cron job is disabled by default. -To enable it, go to `Settings` -> `Technical` -> `Automation` -> `Scheduled Actions` and search for `Product sale price: Update price from pricelist`. +The module creates a cron job to update the product list price every +day. The cron job is disabled by default. To enable it, go to Settings +-> Technical -> Automation -> Scheduled Actions and search for Product +sale price: Update price from pricelist. Usage ===== @@ -59,13 +66,17 @@ Usage **To update product prices according to the pricelist rules** - Stay in the settings configuration with the selected Pricelist. -- Click the **Update Product Prices** button to apply the rules and update the sale prices of all products. +- Click the **Update Product Prices** button to apply the rules and + update the sale prices of all products. Known issues / Roadmap ====================== -The `list_price` field is not `company-dependent`, meaning that if a product is shared across multiple companies, the same list price will apply to all of them. -To minimize errors, you can set a primary company to take precedence. This can be configured in the settings under the field `Main company for computing sale price`. +The list_price field is not company-dependent, meaning that if a product +is shared across multiple companies, the same list price will apply to +all of them. To minimize errors, you can set a primary company to take +precedence. This can be configured in the settings under the field Main +company for computing sale price. Bug Tracker =========== @@ -73,7 +84,7 @@ Bug Tracker Bugs are tracked on `GitHub 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 `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -81,20 +92,20 @@ Credits ======= Authors -~~~~~~~ +------- * Tecnativa Contributors -~~~~~~~~~~~~ +------------ -* `Tecnativa `_ +- `Tecnativa `__ - * Pedro M. Baeza - * Carlos López + - Pedro M. Baeza + - Carlos López Maintainers -~~~~~~~~~~~ +----------- This module is maintained by the OCA. @@ -114,6 +125,6 @@ Current `maintainer `__: |maintainer-carlos-lopez-tecnativa| -This module is part of the `OCA/product-attribute `_ project on GitHub. +This module is part of the `OCA/product-attribute `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/product_list_price_from_pricelist/pyproject.toml b/product_list_price_from_pricelist/pyproject.toml new file mode 100644 index 00000000000..4231d0cccb3 --- /dev/null +++ b/product_list_price_from_pricelist/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/product_list_price_from_pricelist/readme/CONFIGURE.md b/product_list_price_from_pricelist/readme/CONFIGURE.md new file mode 100644 index 00000000000..b1e5af7de1a --- /dev/null +++ b/product_list_price_from_pricelist/readme/CONFIGURE.md @@ -0,0 +1,20 @@ +- Go to Sales -\> Products -\> Pricelists. +- Create a new pricelist and add at least one rule. +- Specify the product template or category for the rule. +- Set the computation mode and save + +**Note**: Ensure the minimum quantity is not great than 1 for the rule +to apply effectively. + +- Go to Sales -\> Configuration -\> Settings. +- In the Pricing section, select the Pricelist to compute sale price + created in the previous step. +- Optionally and only with a multi-company environment enabled, set the + Main company for compute sale price to restrict the computation to a + specific company. +- Save the configuration + +The module creates a cron job to update the product list price every +day. The cron job is disabled by default. To enable it, go to Settings +-\> Technical -\> Automation -\> Scheduled Actions and search for +Product sale price: Update price from pricelist. diff --git a/product_list_price_from_pricelist/readme/CONFIGURE.rst b/product_list_price_from_pricelist/readme/CONFIGURE.rst deleted file mode 100644 index 474d10fb05c..00000000000 --- a/product_list_price_from_pricelist/readme/CONFIGURE.rst +++ /dev/null @@ -1,14 +0,0 @@ -- Go to `Sales` -> `Products` -> `Pricelists`. -- Create a new pricelist and add at least one rule. -- Specify the product template or category for the rule. -- Set the `computation mode` and save - -**Note**: Ensure the minimum quantity is not great than 1 for the rule to apply effectively. - -- Go to `Sales` -> `Configuration` -> `Settings`. -- In the `Pricing` section, select the `Pricelist to compute sale price` created in the previous step. -- Optionally and only with a multi-company environment enabled, set the `Main company for compute sale price` to restrict the computation to a specific company. -- Save the configuration - -The module creates a cron job to update the product list price every day. The cron job is disabled by default. -To enable it, go to `Settings` -> `Technical` -> `Automation` -> `Scheduled Actions` and search for `Product sale price: Update price from pricelist`. \ No newline at end of file diff --git a/product_list_price_from_pricelist/readme/CONTRIBUTORS.md b/product_list_price_from_pricelist/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..a628ebcf08e --- /dev/null +++ b/product_list_price_from_pricelist/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +- [Tecnativa](https://www.tecnativa.com) + - Pedro M. Baeza + - Carlos López diff --git a/product_list_price_from_pricelist/readme/CONTRIBUTORS.rst b/product_list_price_from_pricelist/readme/CONTRIBUTORS.rst deleted file mode 100644 index 29e931a17d4..00000000000 --- a/product_list_price_from_pricelist/readme/CONTRIBUTORS.rst +++ /dev/null @@ -1,4 +0,0 @@ -* `Tecnativa `_ - - * Pedro M. Baeza - * Carlos López \ No newline at end of file diff --git a/product_list_price_from_pricelist/readme/DESCRIPTION.rst b/product_list_price_from_pricelist/readme/DESCRIPTION.md similarity index 55% rename from product_list_price_from_pricelist/readme/DESCRIPTION.rst rename to product_list_price_from_pricelist/readme/DESCRIPTION.md index 7532b365c4b..7827ffbeaa5 100644 --- a/product_list_price_from_pricelist/readme/DESCRIPTION.rst +++ b/product_list_price_from_pricelist/readme/DESCRIPTION.md @@ -1 +1,2 @@ -This module enables the automatic computation of a product's sale price based on the configuration of a pricelist. \ No newline at end of file +This module enables the automatic computation of a product's sale price +based on the configuration of a pricelist. diff --git a/product_list_price_from_pricelist/readme/ROADMAP.md b/product_list_price_from_pricelist/readme/ROADMAP.md new file mode 100644 index 00000000000..641f844ed96 --- /dev/null +++ b/product_list_price_from_pricelist/readme/ROADMAP.md @@ -0,0 +1,5 @@ +The list_price field is not company-dependent, meaning that if a product +is shared across multiple companies, the same list price will apply to +all of them. To minimize errors, you can set a primary company to take +precedence. This can be configured in the settings under the field Main +company for computing sale price. diff --git a/product_list_price_from_pricelist/readme/ROADMAP.rst b/product_list_price_from_pricelist/readme/ROADMAP.rst deleted file mode 100644 index ead3062524c..00000000000 --- a/product_list_price_from_pricelist/readme/ROADMAP.rst +++ /dev/null @@ -1,2 +0,0 @@ -The `list_price` field is not `company-dependent`, meaning that if a product is shared across multiple companies, the same list price will apply to all of them. -To minimize errors, you can set a primary company to take precedence. This can be configured in the settings under the field `Main company for computing sale price`. \ No newline at end of file diff --git a/product_list_price_from_pricelist/readme/USAGE.rst b/product_list_price_from_pricelist/readme/USAGE.md similarity index 80% rename from product_list_price_from_pricelist/readme/USAGE.rst rename to product_list_price_from_pricelist/readme/USAGE.md index 5ce4cf695e4..aec067a4124 100644 --- a/product_list_price_from_pricelist/readme/USAGE.rst +++ b/product_list_price_from_pricelist/readme/USAGE.md @@ -1,4 +1,5 @@ **To update product prices according to the pricelist rules** - Stay in the settings configuration with the selected Pricelist. -- Click the **Update Product Prices** button to apply the rules and update the sale prices of all products. \ No newline at end of file +- Click the **Update Product Prices** button to apply the rules and + update the sale prices of all products. diff --git a/product_list_price_from_pricelist/static/description/index.html b/product_list_price_from_pricelist/static/description/index.html index 390d0042fa2..497154dd60e 100644 --- a/product_list_price_from_pricelist/static/description/index.html +++ b/product_list_price_from_pricelist/static/description/index.html @@ -369,8 +369,9 @@

Compute product sales price from a pricelist

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:8a1859f6320d0128db04e5a8467aafca747d73d520ea2f2124e46572b3ce98ef !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/product-attribute Translate me on Weblate Try me on Runboat

-

This module enables the automatic computation of a product’s sale price based on the configuration of a pricelist.

+

Beta License: AGPL-3 OCA/product-attribute Translate me on Weblate Try me on Runboat

+

This module enables the automatic computation of a product’s sale price +based on the configuration of a pricelist.

Table of contents

    @@ -389,40 +390,50 @@

    Compute product sales price from a pricelist

    Configuration

      -
    • Go to Sales -> Products -> Pricelists.
    • +
    • Go to Sales -> Products -> Pricelists.
    • Create a new pricelist and add at least one rule.
    • Specify the product template or category for the rule.
    • -
    • Set the computation mode and save
    • +
    • Set the computation mode and save
    -

    Note: Ensure the minimum quantity is not great than 1 for the rule to apply effectively.

    +

    Note: Ensure the minimum quantity is not great than 1 for the rule +to apply effectively.

      -
    • Go to Sales -> Configuration -> Settings.
    • -
    • In the Pricing section, select the Pricelist to compute sale price created in the previous step.
    • -
    • Optionally and only with a multi-company environment enabled, set the Main company for compute sale price to restrict the computation to a specific company.
    • +
    • Go to Sales -> Configuration -> Settings.
    • +
    • In the Pricing section, select the Pricelist to compute sale price +created in the previous step.
    • +
    • Optionally and only with a multi-company environment enabled, set the +Main company for compute sale price to restrict the computation to a +specific company.
    • Save the configuration
    -

    The module creates a cron job to update the product list price every day. The cron job is disabled by default. -To enable it, go to Settings -> Technical -> Automation -> Scheduled Actions and search for Product sale price: Update price from pricelist.

    +

    The module creates a cron job to update the product list price every +day. The cron job is disabled by default. To enable it, go to Settings +-> Technical -> Automation -> Scheduled Actions and search for Product +sale price: Update price from pricelist.

    Usage

    To update product prices according to the pricelist rules

    • Stay in the settings configuration with the selected Pricelist.
    • -
    • Click the Update Product Prices button to apply the rules and update the sale prices of all products.
    • +
    • Click the Update Product Prices button to apply the rules and +update the sale prices of all products.

    Known issues / Roadmap

    -

    The list_price field is not company-dependent, meaning that if a product is shared across multiple companies, the same list price will apply to all of them. -To minimize errors, you can set a primary company to take precedence. This can be configured in the settings under the field Main company for computing sale price.

    +

    The list_price field is not company-dependent, meaning that if a product +is shared across multiple companies, the same list price will apply to +all of them. To minimize errors, you can set a primary company to take +precedence. This can be configured in the settings under the field Main +company for computing sale price.

    Bug Tracker

    Bugs are tracked on GitHub 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.

    +feedback.

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

    @@ -454,7 +465,7 @@

    Maintainers

    promote its widespread use.

    Current maintainer:

    carlos-lopez-tecnativa

    -

    This module is part of the OCA/product-attribute project on GitHub.

    +

    This module is part of the OCA/product-attribute project on GitHub.

    You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

diff --git a/product_list_price_from_pricelist/views/res_config_settings_views.xml b/product_list_price_from_pricelist/views/res_config_settings_views.xml index 31cabdd3207..d3375851e31 100644 --- a/product_list_price_from_pricelist/views/res_config_settings_views.xml +++ b/product_list_price_from_pricelist/views/res_config_settings_views.xml @@ -1,6 +1,5 @@ - view.res.config.settings.form res.config.settings @@ -62,5 +61,4 @@ - From 6929eaefcd0b7200446b279219434fef6ae1d2ef Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Wed, 2 Jul 2025 12:32:42 -0500 Subject: [PATCH 4/5] [MIG] product_list_price_from_pricelist: Migration to version 18.0 --- product_list_price_from_pricelist/README.rst | 8 +- .../__manifest__.py | 2 +- .../data/cron.xml | 2 - .../product_list_price_from_pricelist.pot | 7 +- .../static/description/index.html | 32 ++++--- .../tests/test_pricelist.py | 4 +- .../views/res_config_settings_views.xml | 85 +++++++++---------- 7 files changed, 71 insertions(+), 69 deletions(-) diff --git a/product_list_price_from_pricelist/README.rst b/product_list_price_from_pricelist/README.rst index 8d84828d930..e09831c9ed4 100644 --- a/product_list_price_from_pricelist/README.rst +++ b/product_list_price_from_pricelist/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + ============================================ Compute product sales price from a pricelist ============================================ @@ -7,13 +11,13 @@ Compute product sales price from a pricelist !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:8a1859f6320d0128db04e5a8467aafca747d73d520ea2f2124e46572b3ce98ef + !! source digest: sha256:4096c6f8dcaff5d0774538747316d929fc1884be26c01d0345519720ec0121b5 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |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 +.. |badge2| image:: https://img.shields.io/badge/license-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%2Fproduct--attribute-lightgray.png?logo=github diff --git a/product_list_price_from_pricelist/__manifest__.py b/product_list_price_from_pricelist/__manifest__.py index 7b6fec09a7e..2c369a56d72 100644 --- a/product_list_price_from_pricelist/__manifest__.py +++ b/product_list_price_from_pricelist/__manifest__.py @@ -1,6 +1,6 @@ { "name": "Compute product sales price from a pricelist", - "version": "16.0.1.0.0", + "version": "18.0.1.0.0", "author": "Tecnativa, Odoo Community Association (OCA)", "website": "https://github.com/OCA/product-attribute", "depends": [ diff --git a/product_list_price_from_pricelist/data/cron.xml b/product_list_price_from_pricelist/data/cron.xml index 40dfe33e5c4..bcfae51cb6b 100644 --- a/product_list_price_from_pricelist/data/cron.xml +++ b/product_list_price_from_pricelist/data/cron.xml @@ -8,8 +8,6 @@ 1 days - -1 - diff --git a/product_list_price_from_pricelist/i18n/product_list_price_from_pricelist.pot b/product_list_price_from_pricelist/i18n/product_list_price_from_pricelist.pot index e0b6763d8d9..7be1827eca1 100644 --- a/product_list_price_from_pricelist/i18n/product_list_price_from_pricelist.pot +++ b/product_list_price_from_pricelist/i18n/product_list_price_from_pricelist.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 16.0\n" +"Project-Id-Version: Odoo Server 18.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" @@ -34,7 +34,7 @@ msgstr "" #: model_terms:ir.ui.view,arch_db:product_list_price_from_pricelist.view_res_config_settings_form msgid "" "If set, prices will be computed only if the company in the product matches the company specified here or is empty.\n" -" Otherwise, prices will be computed based on the current company." +" Otherwise, prices will be computed based on the current company." msgstr "" #. module: product_list_price_from_pricelist @@ -60,7 +60,6 @@ msgstr "" #. module: product_list_price_from_pricelist #: model:ir.actions.server,name:product_list_price_from_pricelist.ir_cron_update_product_sale_price_ir_actions_server -#: model:ir.cron,cron_name:product_list_price_from_pricelist.ir_cron_update_product_sale_price msgid "Product sale price: Update price from pricelist" msgstr "" @@ -74,7 +73,7 @@ msgstr "" #: model_terms:ir.ui.view,arch_db:product_list_price_from_pricelist.view_res_config_settings_form msgid "" "Set the base pricelist to compute the sales price for all the products.\n" -"
" +"
" msgstr "" #. module: product_list_price_from_pricelist diff --git a/product_list_price_from_pricelist/static/description/index.html b/product_list_price_from_pricelist/static/description/index.html index 497154dd60e..f5cad042a59 100644 --- a/product_list_price_from_pricelist/static/description/index.html +++ b/product_list_price_from_pricelist/static/description/index.html @@ -3,7 +3,7 @@ -Compute product sales price from a pricelist +README.rst -
-

Compute product sales price from a pricelist

+
+ + +Odoo Community Association + +
+

Compute product sales price from a pricelist

-

Beta License: AGPL-3 OCA/product-attribute Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/product-attribute Translate me on Weblate Try me on Runboat

This module enables the automatic computation of a product’s sale price based on the configuration of a pricelist.

Table of contents

@@ -388,7 +393,7 @@

Compute product sales price from a pricelist

-

Configuration

+

Configuration

  • Go to Sales -> Products -> Pricelists.
  • Create a new pricelist and add at least one rule.
  • @@ -412,7 +417,7 @@

    Configuration

    sale price: Update price from pricelist.

-

Usage

+

Usage

To update product prices according to the pricelist rules

  • Stay in the settings configuration with the selected Pricelist.
  • @@ -421,7 +426,7 @@

    Usage

-

Known issues / Roadmap

+

Known issues / Roadmap

The list_price field is not company-dependent, meaning that if a product is shared across multiple companies, the same list price will apply to all of them. To minimize errors, you can set a primary company to take @@ -429,7 +434,7 @@

Known issues / Roadmap

company for computing sale price.

-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub 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 @@ -437,15 +442,15 @@

Bug Tracker

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

-

Credits

+

Credits

-

Authors

+

Authors

  • Tecnativa
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -470,5 +475,6 @@

Maintainers

+
diff --git a/product_list_price_from_pricelist/tests/test_pricelist.py b/product_list_price_from_pricelist/tests/test_pricelist.py index 78aa6865e1f..e14ef27c721 100644 --- a/product_list_price_from_pricelist/tests/test_pricelist.py +++ b/product_list_price_from_pricelist/tests/test_pricelist.py @@ -1,9 +1,9 @@ from odoo.tools import float_compare -from odoo.addons.base.tests.common import TransactionCase +from odoo.addons.base.tests.common import BaseCommon -class TestPricelistGlobal(TransactionCase): +class TestPricelistGlobal(BaseCommon): @classmethod def setUpClass(cls): super().setUpClass() diff --git a/product_list_price_from_pricelist/views/res_config_settings_views.xml b/product_list_price_from_pricelist/views/res_config_settings_views.xml index d3375851e31..c5fad712df0 100644 --- a/product_list_price_from_pricelist/views/res_config_settings_views.xml +++ b/product_list_price_from_pricelist/views/res_config_settings_views.xml @@ -5,59 +5,54 @@ res.config.settings - -
+ -
-
-