diff --git a/product_customerinfo_sale/README.rst b/product_customerinfo_sale/README.rst new file mode 100644 index 00000000000..4b891012afa --- /dev/null +++ b/product_customerinfo_sale/README.rst @@ -0,0 +1,135 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +========================== +Product customer info sale +========================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:d0cbb850a910682f95a0896f1d7f3a9226f093a3d9c242ac0dc8cb0544d9c04a + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/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%2Fsale--workflow-lightgray.png?logo=github + :target: https://github.com/OCA/sale-workflow/tree/19.0/product_customerinfo_sale + :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-19-0/sale-workflow-19-0-product_customerinfo_sale + :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=19.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Based on product_customerinfo, this module loads in every sale order the +customer code defined in the product and allows use the product codes +and product name configured in each products in sale orders. + +If you use Advanced price rules with formulas to define your pricing, +and choose that the price should be calculated from the partner prices +in the product form, the quantity in the sales order will be proposed +from the minimum quantity defined in the customerinfo. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +To use this module, you need: + +- Go to product and configure *Partner product name* and *Partner + product code* for each selected customer. + +|image1| + +- When add order lines in sale quotation for a customer that has an + specific name and code in the product, you can search that product + with that customer name or code. Then, this values will be displayed + in product description. + +|image2| + +|image3| + +- If product does not have a configuration for customer selected, + product will be search by its default code. + +|image4| + +|image5| + +.. |image1| image:: https://raw.githubusercontent.com/OCA/sale-workflow/19.0/product_customerinfo_sale/static/description/configuration_customer.png +.. |image2| image:: https://raw.githubusercontent.com/OCA/sale-workflow/19.0/product_customerinfo_sale/static/description/search_code.png +.. |image3| image:: https://raw.githubusercontent.com/OCA/sale-workflow/19.0/product_customerinfo_sale/static/description/description_code.png +.. |image4| image:: https://raw.githubusercontent.com/OCA/sale-workflow/19.0/product_customerinfo_sale/static/description/search_code_2.png +.. |image5| image:: https://raw.githubusercontent.com/OCA/sale-workflow/19.0/product_customerinfo_sale/static/description/description_code_2.png + +Known issues / Roadmap +====================== + +- Putting a minimum qty in a pricelist rule means the system will use + the option 'list price' instead of any option you chose. + +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 +------- + +* Agile Business Group +* Vauxoo + +Contributors +------------ + +- Xavier Jimenez +- Nicola Malcontenti +- Serpent Consulting Services Pvt. Ltd. +- Moisés López +- Yennifer Santiago +- Julio Serna Hernández +- Sergio Teruel +- Lois Rilo +- Juany Davila +- Carlos Reyes + +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. + +This module is part of the `OCA/sale-workflow `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/product_customerinfo_sale/__init__.py b/product_customerinfo_sale/__init__.py new file mode 100644 index 00000000000..83e553ac462 --- /dev/null +++ b/product_customerinfo_sale/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import models diff --git a/product_customerinfo_sale/__manifest__.py b/product_customerinfo_sale/__manifest__.py new file mode 100644 index 00000000000..52e433bb104 --- /dev/null +++ b/product_customerinfo_sale/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2013-2017 Agile Business Group sagl +# () +# Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +{ + "name": "Product customer info sale", + "version": "19.0.1.0.0", + "summary": "Loads in every sale order line the customer code defined " + "in the product", + "author": "Agile Business Group,Vauxoo,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/sale-workflow", + "category": "Sales Management", + "license": "AGPL-3", + "depends": ["sale", "product_customerinfo"], + "data": [ + "security/ir.model.access.csv", + "views/product_customerinfo_views.xml", + "views/sale_view.xml", + ], + "installable": True, +} diff --git a/product_customerinfo_sale/i18n/ca.po b/product_customerinfo_sale/i18n/ca.po new file mode 100644 index 00000000000..5b1e8db81f9 --- /dev/null +++ b/product_customerinfo_sale/i18n/ca.po @@ -0,0 +1,49 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_supplierinfo_for_customer_sale +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2021-03-10 10:45+0000\n" +"Last-Translator: Daniel Martinez Vila \n" +"Language-Team: none\n" +"Language: ca\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 4.3.2\n" + +#. module: product_customerinfo_sale +#: model:ir.actions.act_window,name:product_customerinfo_sale.action_open_product_customerinfo +#: model:ir.ui.menu,name:product_customerinfo_sale.menu_product_customerinfo +msgid "Prices for customers" +msgstr "" + +#. module: product_customerinfo_sale +#: model:ir.model.fields,field_description:product_customerinfo_sale.field_sale_order_line__product_customer_code +msgid "Product Customer Code" +msgstr "Codi de client del producte" + +#. module: product_customerinfo_sale +#: model:ir.model,name:product_customerinfo_sale.model_sale_order_line +msgid "Sales Order Line" +msgstr "Línia de comanda de vendes" + +#~ msgid "" +#~ "Check this box if this contact is a customer. It can be selected in sales " +#~ "orders." +#~ msgstr "" +#~ "Marqueu aquesta casella si aquest contacte és client. Es pot seleccionar " +#~ "en comandes de venda." + +#~ msgid "Is a Customer" +#~ msgstr "És client" + +#~ msgid "Product Template" +#~ msgstr "Plantilla del Producte" + +#~ msgid "Supplierinfo" +#~ msgstr "Informació del proveïdor" diff --git a/product_customerinfo_sale/i18n/de.po b/product_customerinfo_sale/i18n/de.po new file mode 100644 index 00000000000..3e1b88c8956 --- /dev/null +++ b/product_customerinfo_sale/i18n/de.po @@ -0,0 +1,36 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_supplierinfo_for_customer_sale +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 11.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-10-11 11:38+0000\n" +"Last-Translator: David Brühlmeier \n" +"Language-Team: none\n" +"Language: de\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 4.17\n" + +#. module: product_customerinfo_sale +#: model:ir.actions.act_window,name:product_customerinfo_sale.action_open_product_customerinfo +#: model:ir.ui.menu,name:product_customerinfo_sale.menu_product_customerinfo +msgid "Prices for customers" +msgstr "Kundenpreise" + +#. module: product_customerinfo_sale +#: model:ir.model.fields,field_description:product_customerinfo_sale.field_sale_order_line__product_customer_code +msgid "Product Customer Code" +msgstr "Kundencode des Produkts" + +#. module: product_customerinfo_sale +#: model:ir.model,name:product_customerinfo_sale.model_sale_order_line +msgid "Sales Order Line" +msgstr "Auftragsposition" + +#~ msgid "Product" +#~ msgstr "Produkt" diff --git a/product_customerinfo_sale/i18n/es.po b/product_customerinfo_sale/i18n/es.po new file mode 100644 index 00000000000..40f6da8e263 --- /dev/null +++ b/product_customerinfo_sale/i18n/es.po @@ -0,0 +1,47 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_supplierinfo_for_customer_sale +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-06-16 13:31+0000\n" +"PO-Revision-Date: 2022-06-16 15:32+0200\n" +"Last-Translator: Daniel Martinez Vila \n" +"Language-Team: none\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 2.3\n" + +#. module: product_customerinfo_sale +#: model:ir.actions.act_window,name:product_customerinfo_sale.action_open_product_customerinfo +#: model:ir.ui.menu,name:product_customerinfo_sale.menu_product_customerinfo +msgid "Prices for customers" +msgstr "Precios para clientes" + +#. module: product_customerinfo_sale +#: model:ir.model.fields,field_description:product_customerinfo_sale.field_sale_order_line__product_customer_code +msgid "Product Customer Code" +msgstr "Código de cliente del producto" + +#. module: product_customerinfo_sale +#: model:ir.model,name:product_customerinfo_sale.model_sale_order_line +msgid "Sales Order Line" +msgstr "Línea pedido de venta" + +#~ msgid "" +#~ "Check this box if this contact is a customer. It can be selected in sales " +#~ "orders." +#~ msgstr "" +#~ "Marque esta casilla si este contacto es un cliente. Se puede seleccionar " +#~ "en pedidos de cliente." + +#~ msgid "Product Template" +#~ msgstr "Plantilla de producto" + +#~ msgid "Supplierinfo" +#~ msgstr "Información del proveedor" diff --git a/product_customerinfo_sale/i18n/it.po b/product_customerinfo_sale/i18n/it.po new file mode 100644 index 00000000000..bdf57cad0fd --- /dev/null +++ b/product_customerinfo_sale/i18n/it.po @@ -0,0 +1,51 @@ +# Translation of OpenERP Server. +# This file contains the translation of the following modules: +# * product_customer_code_sale +# +msgid "" +msgstr "" +"Project-Id-Version: OpenERP Server 7.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-01-15 14:31+0000\n" +"PO-Revision-Date: 2025-12-09 12:42+0000\n" +"Last-Translator: mymage \n" +"Language-Team: \n" +"Language: it\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: Weblate 5.10.4\n" +"X-Launchpad-Export-Date: 2014-05-28 05:52+0000\n" + +#. module: product_customerinfo_sale +#: model:ir.actions.act_window,name:product_customerinfo_sale.action_open_product_customerinfo +#: model:ir.ui.menu,name:product_customerinfo_sale.menu_product_customerinfo +msgid "Prices for customers" +msgstr "Prezzi per i clienti" + +#. module: product_customerinfo_sale +#: model:ir.model.fields,field_description:product_customerinfo_sale.field_sale_order_line__product_customer_code +msgid "Product Customer Code" +msgstr "Codice prodotto cliente" + +#. module: product_customerinfo_sale +#: model:ir.model,name:product_customerinfo_sale.model_sale_order_line +msgid "Sales Order Line" +msgstr "Riga ordine di vendita" + +#~ msgid "" +#~ "Check this box if this contact is a customer. It can be selected in sales " +#~ "orders." +#~ msgstr "" +#~ "Attivare se il contatto è un cliente. Può essere selezionato negli ordini " +#~ "di vendita." + +#~ msgid "Is a Customer" +#~ msgstr "È un cliente" + +#~ msgid "Product Template" +#~ msgstr "Modello prodotto" + +#~ msgid "Supplierinfo" +#~ msgstr "v" diff --git a/product_customerinfo_sale/i18n/nl.po b/product_customerinfo_sale/i18n/nl.po new file mode 100644 index 00000000000..bf81417e743 --- /dev/null +++ b/product_customerinfo_sale/i18n/nl.po @@ -0,0 +1,33 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_customerinfo_sale +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-09-11 12:45+0000\n" +"Last-Translator: Bosd \n" +"Language-Team: none\n" +"Language: nl\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.4\n" + +#. module: product_customerinfo_sale +#: model:ir.actions.act_window,name:product_customerinfo_sale.action_open_product_customerinfo +#: model:ir.ui.menu,name:product_customerinfo_sale.menu_product_customerinfo +msgid "Prices for customers" +msgstr "Prijzen voor klanten" + +#. module: product_customerinfo_sale +#: model:ir.model.fields,field_description:product_customerinfo_sale.field_sale_order_line__product_customer_code +msgid "Product Customer Code" +msgstr "Klantartikelcode" + +#. module: product_customerinfo_sale +#: model:ir.model,name:product_customerinfo_sale.model_sale_order_line +msgid "Sales Order Line" +msgstr "Verkooporderregel" diff --git a/product_customerinfo_sale/i18n/product_customerinfo_sale.pot b/product_customerinfo_sale/i18n/product_customerinfo_sale.pot new file mode 100644 index 00000000000..6abc731fb8b --- /dev/null +++ b/product_customerinfo_sale/i18n/product_customerinfo_sale.pot @@ -0,0 +1,30 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_customerinfo_sale +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.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_customerinfo_sale +#: model:ir.actions.act_window,name:product_customerinfo_sale.action_open_product_customerinfo +#: model:ir.ui.menu,name:product_customerinfo_sale.menu_product_customerinfo +msgid "Prices for customers" +msgstr "" + +#. module: product_customerinfo_sale +#: model:ir.model.fields,field_description:product_customerinfo_sale.field_sale_order_line__product_customer_code +msgid "Product Customer Code" +msgstr "" + +#. module: product_customerinfo_sale +#: model:ir.model,name:product_customerinfo_sale.model_sale_order_line +msgid "Sales Order Line" +msgstr "" diff --git a/product_customerinfo_sale/i18n/pt.po b/product_customerinfo_sale/i18n/pt.po new file mode 100644 index 00000000000..55066a3ddc3 --- /dev/null +++ b/product_customerinfo_sale/i18n/pt.po @@ -0,0 +1,49 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_supplierinfo_for_customer_sale +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2020-09-17 17:00+0000\n" +"Last-Translator: Pedro Castro Silva \n" +"Language-Team: none\n" +"Language: pt\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 3.10\n" + +#. module: product_customerinfo_sale +#: model:ir.actions.act_window,name:product_customerinfo_sale.action_open_product_customerinfo +#: model:ir.ui.menu,name:product_customerinfo_sale.menu_product_customerinfo +msgid "Prices for customers" +msgstr "" + +#. module: product_customerinfo_sale +#: model:ir.model.fields,field_description:product_customerinfo_sale.field_sale_order_line__product_customer_code +msgid "Product Customer Code" +msgstr "Código do Produto no Cliente" + +#. module: product_customerinfo_sale +#: model:ir.model,name:product_customerinfo_sale.model_sale_order_line +msgid "Sales Order Line" +msgstr "Linha de Encomenda de Venda" + +#~ msgid "" +#~ "Check this box if this contact is a customer. It can be selected in sales " +#~ "orders." +#~ msgstr "" +#~ "Selecione esta caixa se o contacto for um cliente. Pode ser selecionado " +#~ "na encomendas de venda." + +#~ msgid "Is a Customer" +#~ msgstr "É um Cliente" + +#~ msgid "Product Template" +#~ msgstr "Modelo de Produto" + +#~ msgid "Supplierinfo" +#~ msgstr "Info de Fornecedor" diff --git a/product_customerinfo_sale/models/__init__.py b/product_customerinfo_sale/models/__init__.py new file mode 100644 index 00000000000..8eb9d1d4046 --- /dev/null +++ b/product_customerinfo_sale/models/__init__.py @@ -0,0 +1 @@ +from . import sale_order_line diff --git a/product_customerinfo_sale/models/sale_order_line.py b/product_customerinfo_sale/models/sale_order_line.py new file mode 100644 index 00000000000..a7d8bc1df8a --- /dev/null +++ b/product_customerinfo_sale/models/sale_order_line.py @@ -0,0 +1,69 @@ +# Copyright 2013-2017 Agile Business Group sagl +# () +# Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com) +# Copyright 2024 Tecnativa - Víctor Martínez +# Copyright 2024 Tecnativa - Pedro M. Baeza +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + product_customer_code = fields.Char( + compute="_compute_product_customer_code", + ) + + @api.depends("product_id", "order_partner_id") + def _compute_product_customer_code(self): + for line in self: + if line.product_id: + supplierinfo = line.product_id._select_customerinfo( + partner=line.order_partner_id + ) + code = supplierinfo.product_code + else: + code = "" + line.product_customer_code = code + + def _compute_name(self): + """We need to override the method with product_id is set so that the product + code is not added and add custom code of customerinfo.""" + empty_lines = self.filtered(lambda x: not x.product_id) + res = super(SaleOrderLine, empty_lines)._compute_name() + for item in self - empty_lines: + customerinfo = item.product_id._select_customerinfo( + partner=item.order_partner_id + ) + if customerinfo.product_code: + # Avoid to put the standard internal reference + item = item.with_context(display_default_code=False) + super(SaleOrderLine, item)._compute_name() + if customerinfo.product_code: + item.name = f"[{customerinfo.product_code}] {item.name}" + return res + + @api.onchange("product_id") + def _onchange_product_id(self): + """Assign the mininum quantity if set.""" + res = super()._onchange_product_id() + for line in self: + if line.product_id: + customerinfo = line.product_id._select_customerinfo( + partner=line.order_partner_id + ) + if customerinfo.min_qty: + line.product_uom_qty = customerinfo.min_qty + return res + + def _get_product_price_context(self): + # Include partner in the context to ensure correct computation + # of the price based on product customerinfo. + res = super()._get_product_price_context() + if not self.order_id.partner_id: + return res + return dict( + res, + partner_id=self.order_id.partner_id.id, + ) diff --git a/product_customerinfo_sale/pyproject.toml b/product_customerinfo_sale/pyproject.toml new file mode 100644 index 00000000000..4231d0cccb3 --- /dev/null +++ b/product_customerinfo_sale/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/product_customerinfo_sale/readme/CONTRIBUTORS.md b/product_customerinfo_sale/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..a29ed5e03b4 --- /dev/null +++ b/product_customerinfo_sale/readme/CONTRIBUTORS.md @@ -0,0 +1,10 @@ +- Xavier Jimenez \<\> +- Nicola Malcontenti \<\> +- Serpent Consulting Services Pvt. Ltd. \<\> +- Moisés López \<\> +- Yennifer Santiago \<\> +- Julio Serna Hernández \<\> +- Sergio Teruel \<\> +- Lois Rilo \<\> +- Juany Davila \<\> +- Carlos Reyes \<\> diff --git a/product_customerinfo_sale/readme/DESCRIPTION.md b/product_customerinfo_sale/readme/DESCRIPTION.md new file mode 100644 index 00000000000..99330cbbf42 --- /dev/null +++ b/product_customerinfo_sale/readme/DESCRIPTION.md @@ -0,0 +1,9 @@ +Based on product_customerinfo, this module loads in every +sale order the customer code defined in the product and allows use the +product codes and product name configured in each products in sale +orders. + +If you use Advanced price rules with formulas to define your pricing, +and choose that the price should be calculated from the partner prices +in the product form, the quantity in the sales order will be proposed +from the minimum quantity defined in the customerinfo. diff --git a/product_customerinfo_sale/readme/ROADMAP.md b/product_customerinfo_sale/readme/ROADMAP.md new file mode 100644 index 00000000000..d715f25ccd9 --- /dev/null +++ b/product_customerinfo_sale/readme/ROADMAP.md @@ -0,0 +1,2 @@ +- Putting a minimum qty in a pricelist rule means the system will use + the option 'list price' instead of any option you chose. diff --git a/product_customerinfo_sale/readme/USAGE.md b/product_customerinfo_sale/readme/USAGE.md new file mode 100644 index 00000000000..877c7ffd98d --- /dev/null +++ b/product_customerinfo_sale/readme/USAGE.md @@ -0,0 +1,22 @@ +To use this module, you need: + +- Go to product and configure *Partner product name* and *Partner + product code* for each selected customer. + +![](../static/description/configuration_customer.png) + +- When add order lines in sale quotation for a customer that has an + specific name and code in the product, you can search that product + with that customer name or code. Then, this values will be displayed + in product description. + +![](../static/description/search_code.png) + +![](../static/description/description_code.png) + +- If product does not have a configuration for customer selected, + product will be search by its default code. + +![](../static/description/search_code_2.png) + +![](../static/description/description_code_2.png) diff --git a/product_customerinfo_sale/security/ir.model.access.csv b/product_customerinfo_sale/security/ir.model.access.csv new file mode 100644 index 00000000000..321f0a56ac3 --- /dev/null +++ b/product_customerinfo_sale/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_product_customerinfo_sale_manager,product.customerinfo.sale_manager,product_customerinfo.model_product_customerinfo,sales_team.group_sale_manager,1,1,1,1 +access_product_customerinfo_sale_user,product.customerinfo.sale_user,product_customerinfo.model_product_customerinfo,sales_team.group_sale_salesman,1,0,0,0 diff --git a/product_customerinfo_sale/static/description/configuration_customer.png b/product_customerinfo_sale/static/description/configuration_customer.png new file mode 100644 index 00000000000..e9dd8a65196 Binary files /dev/null and b/product_customerinfo_sale/static/description/configuration_customer.png differ diff --git a/product_customerinfo_sale/static/description/description_code.png b/product_customerinfo_sale/static/description/description_code.png new file mode 100644 index 00000000000..206a6c0e3cd Binary files /dev/null and b/product_customerinfo_sale/static/description/description_code.png differ diff --git a/product_customerinfo_sale/static/description/description_code_2.png b/product_customerinfo_sale/static/description/description_code_2.png new file mode 100644 index 00000000000..b666d3aaedb Binary files /dev/null and b/product_customerinfo_sale/static/description/description_code_2.png differ diff --git a/product_customerinfo_sale/static/description/icon.png b/product_customerinfo_sale/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/product_customerinfo_sale/static/description/icon.png differ diff --git a/product_customerinfo_sale/static/description/index.html b/product_customerinfo_sale/static/description/index.html new file mode 100644 index 00000000000..901721ba245 --- /dev/null +++ b/product_customerinfo_sale/static/description/index.html @@ -0,0 +1,477 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Product customer info sale

+ +

Beta License: AGPL-3 OCA/sale-workflow Translate me on Weblate Try me on Runboat

+

Based on product_customerinfo, this module loads in every sale order the +customer code defined in the product and allows use the product codes +and product name configured in each products in sale orders.

+

If you use Advanced price rules with formulas to define your pricing, +and choose that the price should be calculated from the partner prices +in the product form, the quantity in the sales order will be proposed +from the minimum quantity defined in the customerinfo.

+

Table of contents

+ +
+

Usage

+

To use this module, you need:

+
    +
  • Go to product and configure Partner product name and Partner +product code for each selected customer.
  • +
+

image1

+
    +
  • When add order lines in sale quotation for a customer that has an +specific name and code in the product, you can search that product +with that customer name or code. Then, this values will be displayed +in product description.
  • +
+

image2

+

image3

+
    +
  • If product does not have a configuration for customer selected, +product will be search by its default code.
  • +
+

image4

+

image5

+
+
+

Known issues / Roadmap

+
    +
  • Putting a minimum qty in a pricelist rule means the system will use +the option ‘list price’ instead of any option you chose.
  • +
+
+
+

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

+
    +
  • Agile Business Group
  • +
  • Vauxoo
  • +
+
+
+

Contributors

+ +
+
+

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.

+

This module is part of the OCA/sale-workflow project on GitHub.

+

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

+
+
+
+
+ + diff --git a/product_customerinfo_sale/static/description/search_code.png b/product_customerinfo_sale/static/description/search_code.png new file mode 100644 index 00000000000..af3145f66f7 Binary files /dev/null and b/product_customerinfo_sale/static/description/search_code.png differ diff --git a/product_customerinfo_sale/static/description/search_code_2.png b/product_customerinfo_sale/static/description/search_code_2.png new file mode 100644 index 00000000000..f57a155b779 Binary files /dev/null and b/product_customerinfo_sale/static/description/search_code_2.png differ diff --git a/product_customerinfo_sale/tests/__init__.py b/product_customerinfo_sale/tests/__init__.py new file mode 100644 index 00000000000..72c91a9bb2a --- /dev/null +++ b/product_customerinfo_sale/tests/__init__.py @@ -0,0 +1 @@ +from . import test_product_customerinfo_sale diff --git a/product_customerinfo_sale/tests/test_product_customerinfo_sale.py b/product_customerinfo_sale/tests/test_product_customerinfo_sale.py new file mode 100644 index 00000000000..ee9e4211078 --- /dev/null +++ b/product_customerinfo_sale/tests/test_product_customerinfo_sale.py @@ -0,0 +1,213 @@ +# Copyright 2017 ForgeFlow S.L. +# (http://www.forgeflow.com) +# Copyright 2024 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo.tests import Form + +from odoo.addons.base.tests.common import BaseCommon + + +class TestProductCustomerInfoSale(BaseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.supplierinfo_model = cls.env["product.supplierinfo"] + cls.customerinfo_model = cls.env["product.customerinfo"] + cls.pricelist_item_model = cls.env["product.pricelist.item"] + cls.pricelist_model = cls.env["product.pricelist"] + cls.customer = cls._create_customer("customer1") + attribute = cls.env["product.attribute"].create( + { + "name": "Test Attribute", + } + ) + attribute_values = cls.env["product.attribute.value"].create( + [ + { + "name": "Value 1", + "attribute_id": attribute.id, + "sequence": 1, + }, + { + "name": "Value 2", + "attribute_id": attribute.id, + "sequence": 2, + }, + { + "name": "Value 3", + "attribute_id": attribute.id, + "sequence": 3, + }, + ] + ) + template = cls.env["product.template"].create( + { + "name": "Test Product", + "attribute_line_ids": [ + ( + 0, + 0, + { + "attribute_id": attribute.id, + "value_ids": [(6, 0, attribute_values.ids)], + }, + ) + ], + } + ) + cls.product = template.product_variant_ids[0] + cls.product_variant_1 = template.product_variant_ids[1] + cls.product_variant_2 = template.product_variant_ids[2] + cls.customerinfo = cls._create_partnerinfo( + "customer", cls.customer, cls.product + ) + cls.pricelist = cls._create_pricelist("Test Pricelist", cls.product) + cls.pricelist_item = cls._create_pricelist_item( + "Test Pricelist Item", cls.pricelist, cls.product + ) + cls.company = cls.env.ref("base.main_company") + cls._create_partnerinfo("customer", cls.customer, cls.product_variant_1) + cls._create_partnerinfo( + "customer", cls.customer, cls.product_variant_2, empty_variant=True + ) + cls.product_template = cls.env["product.template"].create( + {"name": "product wo variants"} + ) + cls._create_partnerinfo( + "customer", + cls.customer, + cls.product_template.product_variant_ids[:1], + empty_variant=True, + ) + cls.pricelist_template = cls._create_pricelist( + "Test Pricelist Template", cls.product_template.product_variant_ids[:1] + ) + cls.env.user.group_ids |= cls.env.ref("product.group_product_pricelist") + + @classmethod + def _create_customer(cls, name): + return cls.env["res.partner"].create( + {"name": name, "email": "example@yourcompany.com", "phone": 123456} + ) + + @classmethod + def _create_partnerinfo( + cls, supplierinfo_type, partner, product, empty_variant=False + ): + vals = { + "partner_id": partner.id, + "product_id": product.id, + "product_name": product.name, + "product_code": "00001", + "price": 100.0, + "min_qty": 15.0, + } + if empty_variant: + vals.pop("product_id", None) + vals["product_tmpl_id"] = product.product_tmpl_id.id + return cls.env["product." + supplierinfo_type + "info"].create(vals) + + @classmethod + def _create_pricelist(cls, name, product): + return cls.pricelist_model.create( + {"name": name, "currency_id": cls.env.ref("base.USD").id} + ) + + @classmethod + def _create_pricelist_item(cls, name, pricelist, product): + return cls.pricelist_item_model.create( + { + "name": name, + "pricelist_id": pricelist.id, + "applied_on": "0_product_variant", + "product_id": product.id, + "compute_price": "formula", + "base": "partner", + } + ) + + def test_product_customerinfo(self): + order_form = Form(self.env["sale.order"]) + order_form.partner_id = self.customer + order_form.pricelist_id = self.pricelist + with order_form.order_line.new() as line_form: + line_form.product_id = self.product + order = order_form.save() + line = order.order_line + self.assertIn("00001", order.order_line.name) + self.assertEqual( + line.product_customer_code, + self.customerinfo.product_code, + "Error: Customer product code was not passed to sale order line", + ) + self.assertEqual( + line.product_uom_qty, + self.customerinfo.min_qty, + "Error: Min qty was not passed to the sale order line", + ) + self.assertEqual( + line.price_unit, + 100.0, + "Error: Unit price was not passed to the sale order line", + ) + + def test_product_customerinfo_variant(self): + order_form = Form(self.env["sale.order"]) + order_form.partner_id = self.customer + order_form.pricelist_id = self.pricelist + with order_form.order_line.new() as line_form: + line_form.product_id = self.product_variant_1 + order = order_form.save() + line = order.order_line + self.assertEqual( + line.product_customer_code, + self.customerinfo.product_code, + "Error: Customer product code was not passed to sale order line", + ) + + def test_product_customerinfo_template(self): + customerinfo = self._create_partnerinfo( + "customer", self.customer, self.product_variant_2 + ) + order_form = Form(self.env["sale.order"]) + order_form.partner_id = self.customer + order_form.pricelist_id = self.pricelist + with order_form.order_line.new() as line_form: + line_form.product_id = self.product_variant_2 + order = order_form.save() + line = order.order_line + self.assertEqual( + line.product_customer_code, + customerinfo.product_code, + "Error: Customer product code was not passed to sale order line", + ) + # Test with product without variants + order_form = Form(self.env["sale.order"]) + order_form.partner_id = self.customer + order_form.pricelist_id = self.pricelist_template + with order_form.order_line.new() as line_form: + line_form.product_id = self.product_template.product_variant_ids[0] + order2 = order_form.save() + line2 = order2.order_line + self.assertEqual( + line2.product_customer_code, + customerinfo.product_code, + "Error: Customer product code was not passed to sale order line", + ) + + def test_product_customerinfo_variant_wo_template(self): + customerinfo = self._create_partnerinfo( + "customer", self.customer, self.product_variant_2, empty_variant=True + ) + order_form = Form(self.env["sale.order"]) + order_form.partner_id = self.customer + order_form.pricelist_id = self.pricelist + with order_form.order_line.new() as line_form: + line_form.product_id = self.product_variant_2 + order = order_form.save() + line = order.order_line + self.assertEqual( + line.product_customer_code, + customerinfo.product_code, + "Error: Customer product code was not passed to sale order line", + ) diff --git a/product_customerinfo_sale/views/product_customerinfo_views.xml b/product_customerinfo_sale/views/product_customerinfo_views.xml new file mode 100644 index 00000000000..c76494c282e --- /dev/null +++ b/product_customerinfo_sale/views/product_customerinfo_views.xml @@ -0,0 +1,18 @@ + + + + + Prices for customers + product.customerinfo + {'visible_product_tmpl_id': False} + list,form + + + diff --git a/product_customerinfo_sale/views/sale_view.xml b/product_customerinfo_sale/views/sale_view.xml new file mode 100644 index 00000000000..c9d0466768c --- /dev/null +++ b/product_customerinfo_sale/views/sale_view.xml @@ -0,0 +1,31 @@ + + + + + sale.order.product.code.view.form + sale.order + + + + + + + + + + + + diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 00000000000..b65843318d6 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1 @@ +odoo-addon-product_customerinfo@git+https://github.com/OCA/product-attribute.git@refs/pull/2167/head#subdirectory=product_customerinfo