diff --git a/product_pack/models/product_template.py b/product_pack/models/product_template.py index dc1770abc..8c5384946 100644 --- a/product_pack/models/product_template.py +++ b/product_pack/models/product_template.py @@ -41,7 +41,9 @@ class ProductTemplate(models.Model): ) pack_modifiable = fields.Boolean( help="If you check this field yo will be able to edit " - "sale/purchase order line relate to its component", + "sale/purchase order line relate to its component.\n" + "For 'Non Detailed' packs, this will automatically expand the pack " + "into editable detailed lines.", ) pack_modifiable_invisible = fields.Boolean( compute="_compute_pack_modifiable_invisible", @@ -55,22 +57,23 @@ def _get_pack_modifiable_invisible_depends(self): @api.depends(lambda self: self._get_pack_modifiable_invisible_depends()) def _compute_pack_modifiable_invisible(self): """ - The pack modifiable field should be invisible when: - - pack details are not displayed or - - pack component prices are not detailed - + The pack modifiable field is visible when: + - Pack Display Type is 'Detailed' and Pack Component Price is + 'Detailed per component' + - Pack Display Type is 'Non Detailed' """ for product in self: product.pack_modifiable_invisible = ( - product.pack_type != "detailed" - or product.pack_component_price != "detailed" + product.pack_type == "detailed" + and product.pack_component_price != "detailed" ) @api.onchange("pack_type", "pack_component_price") def onchange_pack_type(self): products = self.filtered( lambda x: x.pack_modifiable - and (x.pack_type != "detailed" or x.pack_component_price != "detailed") + and x.pack_type == "detailed" + and x.pack_component_price != "detailed" ) for rec in products: rec.pack_modifiable = False diff --git a/product_pack/tests/test_product_pack.py b/product_pack/tests/test_product_pack.py index 6db14fd7d..90e5ec048 100644 --- a/product_pack/tests/test_product_pack.py +++ b/product_pack/tests/test_product_pack.py @@ -53,18 +53,18 @@ def test_pack_type(self): self.pack.pack_modifiable = True with Form(self.pack.product_tmpl_id) as pack_form: pack_form.pack_type = "non_detailed" - self.assertFalse(pack_form.pack_modifiable) + self.assertTrue(pack_form.pack_modifiable) def test_pack_modifiable(self): # Pack is detailed with component price as detailed # Pack modifiable invisible should be False + self.assertFalse(self.pack.pack_modifiable_invisible) # Set the Pack as non detailed - # Pack modifiable invisible should be True + # Pack modifiable invisible should be False + self.pack.pack_type = "non_detailed" + self.assertFalse(self.pack.pack_modifiable_invisible) # Set the Pack as detailed with component price as totalized # Pack modifiable invisible should be True - self.assertFalse(self.pack.pack_modifiable_invisible) - self.pack.pack_type = "non_detailed" - self.assertTrue(self.pack.pack_modifiable_invisible) self.pack.pack_type = "detailed" self.pack.pack_component_price = "totalized" self.assertTrue(self.pack.pack_modifiable_invisible) diff --git a/sale_product_pack/README.rst b/sale_product_pack/README.rst new file mode 100644 index 000000000..3f3424d24 --- /dev/null +++ b/sale_product_pack/README.rst @@ -0,0 +1,145 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +================= +Sale Product Pack +================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:d0034cc2b56fe98dabcd701ddded4a3adc81775fbf8857041c8b9ebd13eeb6ad + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fproduct--pack-lightgray.png?logo=github + :target: https://github.com/OCA/product-pack/tree/19.0/sale_product_pack + :alt: OCA/product-pack +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/product-pack-19-0/product-pack-19-0-sale_product_pack + :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-pack&target_branch=19.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds *Product Pack* functionality to sales orders. You can +choose a *Pack* in *sales order lines* and see different behaviors +depending on "Pack type" and "Pack component price" fields options +selected on this *Pack*. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +To use this module, you need to: + +1. Go to *Sales > Products > Products*, create or select a product and + check *Is Pack?* +2. Set "Product type" and "Pack component price" fields in the *Pack* + page. +3. Add the products to be included in it. +4. Go to *Sales > Orders > Quotations* and create a Quotation. +5. Add a product that has checked "Is Pack?" +6. Save data and you will see an specific behavior depending on "Pack + type" and "Pack component price" fields options selected on this + *Pack*. For example, for products that has *Detailed* option selected + in "Pack type" field you will see one *sale order line* per component + that belong to this Pack. (See *Product pack* module README.rst file) + +Known issues / Roadmap +====================== + +- If this module is installed and stock module is installed too, when + you create a Sale order for a *Non detailed* Pack and you confirm it, + a *Stock picking* is not created with the storable components of that + Pack. So, add a new module called *sale_stock_product_pack* that adds + that feature. + +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 +------- + +* NaN·tic +* ADHOC SA +* Tecnativa + +Contributors +------------ + +- `Tecnativa `__: + + - Ernesto Tejeda + - Pedro M. Baeza + +- `Akretion `__: + + - Raphaël Reverdy + +- `Open Source Integrators `__: + + - Daniel Reis + +- `Acsone `__: + + - Maxime Franco + +- `ADHOC SA `__: + + - Bruno Zanotti + - Augusto Weiss + - Nicolas Col + +- `Apik `__: + + - Michel Guiheneuf + +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-victoralmau| image:: https://github.com/victoralmau.png?size=40px + :target: https://github.com/victoralmau + :alt: victoralmau + +Current `maintainer `__: + +|maintainer-victoralmau| + +This module is part of the `OCA/product-pack `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/sale_product_pack/__init__.py b/sale_product_pack/__init__.py new file mode 100644 index 000000000..cb45f2710 --- /dev/null +++ b/sale_product_pack/__init__.py @@ -0,0 +1,2 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import models diff --git a/sale_product_pack/__manifest__.py b/sale_product_pack/__manifest__.py new file mode 100644 index 000000000..ee3fce997 --- /dev/null +++ b/sale_product_pack/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2019 NaN (http://www.nan-tic.com) - Àngel Àlvarez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Sale Product Pack", + "version": "19.0.1.0.0", + "category": "Sales", + "summary": "This module allows you to sell product packs", + "website": "https://github.com/OCA/product-pack", + "author": "NaN·tic, ADHOC SA, Tecnativa, Odoo Community Association (OCA)", + "maintainers": ["victoralmau"], + "license": "AGPL-3", + "depends": ["product_pack", "sale"], + "data": ["security/ir.model.access.csv", "views/product_pack_line_views.xml"], + "demo": [ + "demo/product_pack_line_demo.xml", + "demo/sale_pack_demo.xml", + ], + "installable": True, +} diff --git a/sale_product_pack/demo/product_pack_line_demo.xml b/sale_product_pack/demo/product_pack_line_demo.xml new file mode 100644 index 000000000..b40178bdf --- /dev/null +++ b/sale_product_pack/demo/product_pack_line_demo.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/sale_product_pack/demo/sale_pack_demo.xml b/sale_product_pack/demo/sale_pack_demo.xml new file mode 100644 index 000000000..320e132f8 --- /dev/null +++ b/sale_product_pack/demo/sale_pack_demo.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + Detailed Displayed Components Price + line_section + 1 + + + + + 2 + + + + + Detailed Totalized Components Price + line_section + 3 + + + + + 4 + + + + + Detailed Ignored Components Price + line_section + 5 + + + + + 6 + + + + + Not Detailed - Totalized Components Price + line_section + 7 + + + + + 8 + + + + + COMPONENTS + line_section + 9 + + + + + 10 + + + + + + 11 + + + + + + 12 + + + + diff --git a/sale_product_pack/i18n/ca.po b/sale_product_pack/i18n/ca.po new file mode 100644 index 000000000..8d9af5915 --- /dev/null +++ b/sale_product_pack/i18n/ca.po @@ -0,0 +1,180 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_product_pack +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2022-02-28 13:17+0000\n" +"Last-Translator: Noel estudillo \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: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_depth +msgid "Depth" +msgstr "Profunditat" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_depth +msgid "Depth of the product if it is part of a pack." +msgstr "Profunditat del producte si forma part d’un pack." + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__do_no_expand_pack_lines +msgid "Do No Expand Pack Lines" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_child_line_ids +msgid "Lines in pack" +msgstr "Línies en pack" + +#. module: sale_product_pack +#: model_terms:ir.ui.view,arch_db:sale_product_pack.view_order_form +msgid "Locked" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_component_price +msgid "" +"On sale orders or purchase orders:\n" +"* Detailed per component: Detail lines with prices.\n" +"* Totalized in main product: Detail lines merging lines prices on pack " +"(don't show component prices).\n" +"* Ignored: Use product pack price (ignore detail line prices)." +msgstr "" +"Comandes de venda o comandes de compra:\n" +"* Detall per component: Línies de detall amb preus.\n" +"* Totalitzat en producte principal: línies de detalls fusionen els preus de " +"les línies en el pack (no mostren els preus dels components).\n" +"* Ignorat: utilitza el preu del pack de producte (ignora els preus de la " +"línia de detalls)." + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_type +msgid "" +"On sale orders or purchase orders:\n" +"* Detailed: Display components individually in the sale order.\n" +"* Non Detailed: Do not display components individually in the sale order." +msgstr "" +"Comandes de venda o comandes de compra:\n" +"* Detallat: mostra els components de forma individual en la comanda de " +"venda.\n" +"* No detallat: no mostren els components de forma individual en la comanda " +"de venda." + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_parent_line_id +msgid "Pack" +msgstr "Pack" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_component_price +msgid "Pack Component Price" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_type +msgid "Pack Display Type" +msgstr "Tipus de pantalla de paquet" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_modifiable +msgid "Pack Modifiable" +msgstr "Paquet modificable" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order_line.py:0 +msgid "Parent Product" +msgstr "Producte principal" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_product_pack_line +msgid "Product pack line" +msgstr "Línia de pack" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_product_pack_line__sale_discount +msgid "Sale discount (%)" +msgstr "Descompte per vendes" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_sale_order +msgid "Sales Order" +msgstr "Comanda de venda" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_sale_order_line +msgid "Sales Order Line" +msgstr "Línia de comandes de vendes" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_parent_line_id +msgid "The pack that contains this product." +msgstr "El pack que conté aquest producte." + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_modifiable +msgid "The parent pack is modifiable" +msgstr "El paquet principal és modificable" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__do_no_expand_pack_lines +msgid "" +"This is a technical field in order to check if pack lines has to be expanded" +msgstr "" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order_line.py:0 +msgid "" +"You can not change this line because is part of a pack included in this order" +msgstr "" +"No podeu canviar aquesta línia perquè forma part d’un pack inclòs en aquest " +"ordre" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order.py:0 +msgid "" +"You cannot delete this line because is part of a pack in this sale order. In " +"order to delete this line you need to delete the pack itself" +msgstr "" +"No podeu suprimir aquesta línia perquè forma part d'un paquet d'aquesta " +"comanda de venda. Per eliminar aquesta línia, heu d'esborrar el paquet en si" + +#~ msgid "Parent Pack is not modifiable" +#~ msgstr "El paquet de pares no es pot modificar" + +#~ msgid "Display Name" +#~ msgstr "Nom de visualització" + +#~ msgid "ID" +#~ msgstr "ID" + +#~ msgid "Last Modified on" +#~ msgstr "Última modificació el" + +#~ msgid "Pack component price" +#~ msgstr "Preu dels components" + +#~ msgid "Pack Type" +#~ msgstr "Tipus de pack" + +#~ msgid "Sale Order" +#~ msgstr "Comanda de venda" + +#~ msgid "" +#~ "You can not delete this line because is part of a pack in this sale " +#~ "order. In order to delete this line you need to delete the pack itself" +#~ msgstr "" +#~ "No podeu suprimir aquesta línia perquè forma part d’un pack d’aquesta " +#~ "comanda de venda. Per eliminar aquesta línia, heu de suprimir el pack" diff --git a/sale_product_pack/i18n/ca_ES.po b/sale_product_pack/i18n/ca_ES.po new file mode 100644 index 000000000..87aeb9f09 --- /dev/null +++ b/sale_product_pack/i18n/ca_ES.po @@ -0,0 +1,135 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_product_pack +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ca_ES\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" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_depth +msgid "Depth" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_depth +msgid "Depth of the product if it is part of a pack." +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__do_no_expand_pack_lines +msgid "Do No Expand Pack Lines" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_child_line_ids +msgid "Lines in pack" +msgstr "" + +#. module: sale_product_pack +#: model_terms:ir.ui.view,arch_db:sale_product_pack.view_order_form +msgid "Locked" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_component_price +msgid "" +"On sale orders or purchase orders:\n" +"* Detailed per component: Detail lines with prices.\n" +"* Totalized in main product: Detail lines merging lines prices on pack " +"(don't show component prices).\n" +"* Ignored: Use product pack price (ignore detail line prices)." +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_type +msgid "" +"On sale orders or purchase orders:\n" +"* Detailed: Display components individually in the sale order.\n" +"* Non Detailed: Do not display components individually in the sale order." +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_parent_line_id +msgid "Pack" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_component_price +msgid "Pack Component Price" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_type +msgid "Pack Display Type" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_modifiable +msgid "Pack Modifiable" +msgstr "" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order_line.py:0 +msgid "Parent Product" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_product_pack_line +msgid "Product pack line" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_product_pack_line__sale_discount +msgid "Sale discount (%)" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_sale_order_line +msgid "Sales Order Line" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_parent_line_id +msgid "The pack that contains this product." +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_modifiable +msgid "The parent pack is modifiable" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__do_no_expand_pack_lines +msgid "" +"This is a technical field in order to check if pack lines has to be expanded" +msgstr "" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order_line.py:0 +msgid "" +"You can not change this line because is part of a pack included in this order" +msgstr "" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order.py:0 +msgid "" +"You cannot delete this line because is part of a pack in this sale order. In " +"order to delete this line you need to delete the pack itself" +msgstr "" diff --git a/sale_product_pack/i18n/es.po b/sale_product_pack/i18n/es.po new file mode 100644 index 000000000..dfb017877 --- /dev/null +++ b/sale_product_pack/i18n/es.po @@ -0,0 +1,174 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_product_pack +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-09-20 22:46+0000\n" +"PO-Revision-Date: 2023-09-03 13:43+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: \n" +"Language: es\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: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_depth +msgid "Depth" +msgstr "Profundidad" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_depth +msgid "Depth of the product if it is part of a pack." +msgstr "Profundidad del producto si forma parte de un pack." + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__do_no_expand_pack_lines +msgid "Do No Expand Pack Lines" +msgstr "No ampliar las líneas de embalaje" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_child_line_ids +msgid "Lines in pack" +msgstr "Líneas en el pack" + +#. module: sale_product_pack +#: model_terms:ir.ui.view,arch_db:sale_product_pack.view_order_form +msgid "Locked" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_component_price +msgid "" +"On sale orders or purchase orders:\n" +"* Detailed per component: Detail lines with prices.\n" +"* Totalized in main product: Detail lines merging lines prices on pack " +"(don't show component prices).\n" +"* Ignored: Use product pack price (ignore detail line prices)." +msgstr "" +"En órdenes de venta u órdenes de compra:\n" +"* Detallado por componente: Detalla las líneas con precios.\n" +"* Totalizado en el producto principal: Detalla las líneas mezclando sus " +"precios en el Pack (No muestra los precios de los componentes).\n" +"* Ignorado: Se usa el precio el Pack (los precios de las líneas son " +"ignorados)." + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_type +msgid "" +"On sale orders or purchase orders:\n" +"* Detailed: Display components individually in the sale order.\n" +"* Non Detailed: Do not display components individually in the sale order." +msgstr "" +"En órdenes de venta u órdenes de compra:\n" +"* Detallado: Muestra los componentes individualmente en líneas de la Orden " +"de Venta/Orden de Compra\n" +"* No Detallado: No muestra los componentes individualmente en líneas de la " +"Orden de Venta/Orden de Compra." + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_parent_line_id +msgid "Pack" +msgstr "Paquete" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_component_price +msgid "Pack Component Price" +msgstr "Precio de los componentes del paquete" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_type +msgid "Pack Display Type" +msgstr "Tipo de visualización del paquete" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_modifiable +msgid "Pack Modifiable" +msgstr "Pack modificable" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order_line.py:0 +msgid "Parent Product" +msgstr "Producto Parental" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_product_pack_line +msgid "Product pack line" +msgstr "Linea de pack" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_product_pack_line__sale_discount +msgid "Sale discount (%)" +msgstr "Descuento para ventas" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_sale_order +msgid "Sales Order" +msgstr "Órdenes de venta" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_sale_order_line +msgid "Sales Order Line" +msgstr "Línea de pedido de venta" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_parent_line_id +msgid "The pack that contains this product." +msgstr "El pack que contiene este producto." + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_modifiable +msgid "The parent pack is modifiable" +msgstr "El paquete parental es modificable" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__do_no_expand_pack_lines +msgid "" +"This is a technical field in order to check if pack lines has to be expanded" +msgstr "" +"Se trata de un campo técnico para comprobar si es necesario ampliar las " +"líneas de envasado" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order_line.py:0 +msgid "" +"You can not change this line because is part of a pack included in this order" +msgstr "" +"No puedes cambiar esta línea porque es parte de un pack incluido en este " +"orden" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order.py:0 +msgid "" +"You cannot delete this line because is part of a pack in this sale order. In " +"order to delete this line you need to delete the pack itself" +msgstr "" +"No puede eliminar esta línea porque forma parte de un paquete en este pedido " +"de venta. Para eliminar esta línea debe eliminar el propio paquete" + +#~ msgid "Parent Pack is not modifiable" +#~ msgstr "El paquete parental no es modificable" + +#~ msgid "Pack component price" +#~ msgstr "Precio de los componentes" + +#~ msgid "Pack Type" +#~ msgstr "Tipo de Pack" + +#~ msgid "Sale Order" +#~ msgstr "Pedido de venta" + +#~ msgid "" +#~ "You can not delete this line because is part of a pack in this sale " +#~ "order. In order to delete this line you need to delete the pack itself" +#~ msgstr "" +#~ "No puede eliminar esta línea porque forma parte de un pack en esta orden " +#~ "de venta. Para eliminar esta línea necesitas eliminar el pack" diff --git a/sale_product_pack/i18n/fr.po b/sale_product_pack/i18n/fr.po new file mode 100644 index 000000000..29e19db71 --- /dev/null +++ b/sale_product_pack/i18n/fr.po @@ -0,0 +1,168 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_product_pack +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2020-05-28 12:19+0000\n" +"Last-Translator: Yann Papouin \n" +"Language-Team: none\n" +"Language: fr\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: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_depth +msgid "Depth" +msgstr "Niveau" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_depth +msgid "Depth of the product if it is part of a pack." +msgstr "Niveau de l'article s'il fait partie d'un pack." + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__do_no_expand_pack_lines +msgid "Do No Expand Pack Lines" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_child_line_ids +msgid "Lines in pack" +msgstr "Lignes rattachées à un pack" + +#. module: sale_product_pack +#: model_terms:ir.ui.view,arch_db:sale_product_pack.view_order_form +msgid "Locked" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_component_price +msgid "" +"On sale orders or purchase orders:\n" +"* Detailed per component: Detail lines with prices.\n" +"* Totalized in main product: Detail lines merging lines prices on pack " +"(don't show component prices).\n" +"* Ignored: Use product pack price (ignore detail line prices)." +msgstr "" +"Sur les commandes clients ou fournisseurs :\n" +"* Détaillé par composant : lignes détaillées avec prix.\n" +"* Totalisé dans l'article principal : lignes détaillées mais le prix est " +"additionné sur celui du pack (n'affiche pas les prix par composant).\n" +"* Ignoré : utilise le prix du pack d'articles (ignore les prix des " +"composants)." + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_type +msgid "" +"On sale orders or purchase orders:\n" +"* Detailed: Display components individually in the sale order.\n" +"* Non Detailed: Do not display components individually in the sale order." +msgstr "" +"Sur les commandes clients ou fournisseurs :\n" +"* Détaillé : affiche les composants individuellement dans la commande.\n" +"* Non détaillé : n'affiche pas les composants dans la commande." + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_parent_line_id +msgid "Pack" +msgstr "Pack" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_component_price +msgid "Pack Component Price" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_type +msgid "Pack Display Type" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_modifiable +msgid "Pack Modifiable" +msgstr "Pack modifiable" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order_line.py:0 +msgid "Parent Product" +msgstr "Article parent" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_product_pack_line +msgid "Product pack line" +msgstr "Ligne d'article de pack" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_product_pack_line__sale_discount +msgid "Sale discount (%)" +msgstr "Remise de vente (%)" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_sale_order_line +msgid "Sales Order Line" +msgstr "Ligne de bons de commande" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_parent_line_id +msgid "The pack that contains this product." +msgstr "Le pack qui contient cet article." + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_modifiable +msgid "The parent pack is modifiable" +msgstr "Le pack parent est modifiable" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__do_no_expand_pack_lines +msgid "" +"This is a technical field in order to check if pack lines has to be expanded" +msgstr "" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order_line.py:0 +msgid "" +"You can not change this line because is part of a pack included in this order" +msgstr "" +"Vous ne pouvez pas modifier cette ligne car elle fait partie d'un pack " +"inclus dans cette commande" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order.py:0 +msgid "" +"You cannot delete this line because is part of a pack in this sale order. In " +"order to delete this line you need to delete the pack itself" +msgstr "" + +#~ msgid "Parent Pack is not modifiable" +#~ msgstr "Le pack parent n'est pas modifiable" + +#~ msgid "Pack component price" +#~ msgstr "Prix d'un composant du pack" + +#~ msgid "Pack Type" +#~ msgstr "Type de pack" + +#~ msgid "Sale Order" +#~ msgstr "Bon de commande" + +#~ msgid "" +#~ "You can not delete this line because is part of a pack in this sale " +#~ "order. In order to delete this line you need to delete the pack itself" +#~ msgstr "" +#~ "Vous ne pouvez pas supprimer cette ligne car elle fait partie d'un pack " +#~ "dans ce bon de commande. Pour supprimer cette ligne, vous devez supprimer " +#~ "le pack lui-même" diff --git a/sale_product_pack/i18n/it.po b/sale_product_pack/i18n/it.po new file mode 100644 index 000000000..745abbeb7 --- /dev/null +++ b/sale_product_pack/i18n/it.po @@ -0,0 +1,156 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_product_pack +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-02-12 16: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.6.2\n" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_depth +msgid "Depth" +msgstr "Profondità" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_depth +msgid "Depth of the product if it is part of a pack." +msgstr "Profondità di un prodotto se fa parte di un collo." + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__do_no_expand_pack_lines +msgid "Do No Expand Pack Lines" +msgstr "Non espandere le righe del collo" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_child_line_ids +msgid "Lines in pack" +msgstr "Righe nel collo" + +#. module: sale_product_pack +#: model_terms:ir.ui.view,arch_db:sale_product_pack.view_order_form +msgid "Locked" +msgstr "Bloccato" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_component_price +msgid "" +"On sale orders or purchase orders:\n" +"* Detailed per component: Detail lines with prices.\n" +"* Totalized in main product: Detail lines merging lines prices on pack " +"(don't show component prices).\n" +"* Ignored: Use product pack price (ignore detail line prices)." +msgstr "" +"Negli ordini di vendita o di acquisto:\n" +"* Dettagliato per componente: dettaglio righe con prezzi.\n" +"* Totalizzate nel prodotto principale: dettaglio righe unendo i prezzi delle " +"righe nel collo (non mostra i prezzi dei componenti).\n" +"* Ignorate: usa il prezzo prodotto del collo (ignora il dettaglio prezzi " +"riga)." + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_type +msgid "" +"On sale orders or purchase orders:\n" +"* Detailed: Display components individually in the sale order.\n" +"* Non Detailed: Do not display components individually in the sale order." +msgstr "" +"Negli ordini di ventita o di acquisto:\n" +"* Dettagliati: visualizza i singoli componenti nell'ordine di vendita.\n" +"* Non detagliati: non visualizza i singoli componenti nell'ordine di vendita." + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_parent_line_id +msgid "Pack" +msgstr "Pacco" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_component_price +msgid "Pack Component Price" +msgstr "Prezzo componente collo" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_type +msgid "Pack Display Type" +msgstr "Visualizza tipo collo" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_modifiable +msgid "Pack Modifiable" +msgstr "Collo modificabile" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order_line.py:0 +msgid "Parent Product" +msgstr "Prodotto padre" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_product_pack_line +msgid "Product pack line" +msgstr "Riga collo prodotto" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_product_pack_line__sale_discount +msgid "Sale discount (%)" +msgstr "Sconto vendita (%)" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_sale_order +msgid "Sales Order" +msgstr "Ordine di vendita" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_sale_order_line +msgid "Sales Order Line" +msgstr "Riga ordine di vendita" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_parent_line_id +msgid "The pack that contains this product." +msgstr "Il collo che contiene questo prodotto." + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_modifiable +msgid "The parent pack is modifiable" +msgstr "Il collo padre è modificabile" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__do_no_expand_pack_lines +msgid "" +"This is a technical field in order to check if pack lines has to be expanded" +msgstr "" +"Questo è un campo tecnico per controllare se le righe del collo devono " +"essere espanse" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order_line.py:0 +msgid "" +"You can not change this line because is part of a pack included in this order" +msgstr "" +"Non si può modificare questa riga perché è parte di un collo incluso in " +"questo ordine" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order.py:0 +msgid "" +"You cannot delete this line because is part of a pack in this sale order. In " +"order to delete this line you need to delete the pack itself" +msgstr "" +"Non si può cancellare questa riga perché è parte di un collo dello stesso " +"ordine di vendita. Per poter cancellare questa riga bisogna cancellare il " +"collo stesso" + +#~ msgid "Parent Pack is not modifiable" +#~ msgstr "Il collo padre non è modificabIle" diff --git a/sale_product_pack/i18n/nl.po b/sale_product_pack/i18n/nl.po new file mode 100644 index 000000000..7c4e3a03f --- /dev/null +++ b/sale_product_pack/i18n/nl.po @@ -0,0 +1,142 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_product_pack +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2020-12-14 00:28+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 4.3.2\n" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_depth +msgid "Depth" +msgstr "Diepte" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_depth +msgid "Depth of the product if it is part of a pack." +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__do_no_expand_pack_lines +msgid "Do No Expand Pack Lines" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_child_line_ids +msgid "Lines in pack" +msgstr "regels in verpakking" + +#. module: sale_product_pack +#: model_terms:ir.ui.view,arch_db:sale_product_pack.view_order_form +msgid "Locked" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_component_price +msgid "" +"On sale orders or purchase orders:\n" +"* Detailed per component: Detail lines with prices.\n" +"* Totalized in main product: Detail lines merging lines prices on pack " +"(don't show component prices).\n" +"* Ignored: Use product pack price (ignore detail line prices)." +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_type +msgid "" +"On sale orders or purchase orders:\n" +"* Detailed: Display components individually in the sale order.\n" +"* Non Detailed: Do not display components individually in the sale order." +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_parent_line_id +msgid "Pack" +msgstr "Verpakking" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_component_price +msgid "Pack Component Price" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_type +msgid "Pack Display Type" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_modifiable +msgid "Pack Modifiable" +msgstr "Aanpasbare verpakking" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order_line.py:0 +msgid "Parent Product" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_product_pack_line +msgid "Product pack line" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_product_pack_line__sale_discount +msgid "Sale discount (%)" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_sale_order +msgid "Sales Order" +msgstr "Verkooporder" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_sale_order_line +msgid "Sales Order Line" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_parent_line_id +msgid "The pack that contains this product." +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_modifiable +msgid "The parent pack is modifiable" +msgstr "De hoofdverpakking is aanpasbaar" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__do_no_expand_pack_lines +msgid "" +"This is a technical field in order to check if pack lines has to be expanded" +msgstr "" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order_line.py:0 +msgid "" +"You can not change this line because is part of a pack included in this order" +msgstr "" +"Deze regel kan niet worden aangepast omdat een verpakking is gebruikt in " +"deze verkoop order" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order.py:0 +msgid "" +"You cannot delete this line because is part of a pack in this sale order. In " +"order to delete this line you need to delete the pack itself" +msgstr "" + +#~ msgid "Pack Type" +#~ msgstr "Verpakkingstype" diff --git a/sale_product_pack/i18n/pt.po b/sale_product_pack/i18n/pt.po new file mode 100644 index 000000000..00579b4ef --- /dev/null +++ b/sale_product_pack/i18n/pt.po @@ -0,0 +1,164 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_product_pack +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2019-12-12 22:05+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.9.1\n" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_depth +msgid "Depth" +msgstr "Profundidade" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_depth +msgid "Depth of the product if it is part of a pack." +msgstr "Profundidade do produto se é parte de um pack." + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__do_no_expand_pack_lines +msgid "Do No Expand Pack Lines" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_child_line_ids +msgid "Lines in pack" +msgstr "Linhas no pack" + +#. module: sale_product_pack +#: model_terms:ir.ui.view,arch_db:sale_product_pack.view_order_form +msgid "Locked" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_component_price +msgid "" +"On sale orders or purchase orders:\n" +"* Detailed per component: Detail lines with prices.\n" +"* Totalized in main product: Detail lines merging lines prices on pack " +"(don't show component prices).\n" +"* Ignored: Use product pack price (ignore detail line prices)." +msgstr "" +"Em encomendas de compra ou venda:\n" +"* Detalhado por componente: Linha de detalhes com preços.\n" +"* Totalizado no produto principal: Linhas de detalhes fundindo os preços das " +"linhas no pack (não mostrar preços dos componentes).\n" +"* Ignorado: Usar o preço do pack de produtos (ignorar preços das linhas de " +"detalhes)." + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_type +msgid "" +"On sale orders or purchase orders:\n" +"* Detailed: Display components individually in the sale order.\n" +"* Non Detailed: Do not display components individually in the sale order." +msgstr "" +"Em encomendas de compra ou venda:\n" +"* Detalhado: Exibir os componentes individualmente na encomenda.\n" +"*Não Detalhado: Não exibir os componentes individualmente na encomenda." + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_parent_line_id +msgid "Pack" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_component_price +msgid "Pack Component Price" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_type +msgid "Pack Display Type" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_modifiable +msgid "Pack Modifiable" +msgstr "" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order_line.py:0 +msgid "Parent Product" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_product_pack_line +msgid "Product pack line" +msgstr "Linha de pack de produto" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_product_pack_line__sale_discount +msgid "Sale discount (%)" +msgstr "Desconto de venda (%)" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_sale_order_line +msgid "Sales Order Line" +msgstr "Linha de Encomenda de Venda" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_parent_line_id +msgid "The pack that contains this product." +msgstr "O pack que contém este produto." + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_modifiable +msgid "The parent pack is modifiable" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__do_no_expand_pack_lines +msgid "" +"This is a technical field in order to check if pack lines has to be expanded" +msgstr "" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order_line.py:0 +msgid "" +"You can not change this line because is part of a pack included in this order" +msgstr "" +"Não pode alterar esta linha porque é parte de um pack incluído nesta " +"encomenda" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order.py:0 +msgid "" +"You cannot delete this line because is part of a pack in this sale order. In " +"order to delete this line you need to delete the pack itself" +msgstr "" + +#~ msgid "Pack component price" +#~ msgstr "Preço do componente do pack" + +#~ msgid "Pack Type" +#~ msgstr "Tipo de Pack" + +#~ msgid "Sale Order" +#~ msgstr "Encomenda de Venda" + +#~ msgid "" +#~ "You can not delete this line because is part of a pack in this sale " +#~ "order. In order to delete this line you need to delete the pack itself" +#~ msgstr "" +#~ "Não pode eliminar esta linha porque é parte de um pack incluído nesta " +#~ "encomenda. Para a eliminar, precisa de eliminar o próprio pack" diff --git a/sale_product_pack/i18n/sale_product_pack.pot b/sale_product_pack/i18n/sale_product_pack.pot new file mode 100644 index 000000000..e977b70f3 --- /dev/null +++ b/sale_product_pack/i18n/sale_product_pack.pot @@ -0,0 +1,134 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_product_pack +# +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: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_depth +msgid "Depth" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_depth +msgid "Depth of the product if it is part of a pack." +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__do_no_expand_pack_lines +msgid "Do No Expand Pack Lines" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_child_line_ids +msgid "Lines in pack" +msgstr "" + +#. module: sale_product_pack +#: model_terms:ir.ui.view,arch_db:sale_product_pack.view_order_form +msgid "Locked" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_component_price +msgid "" +"On sale orders or purchase orders:\n" +"* Detailed per component: Detail lines with prices.\n" +"* Totalized in main product: Detail lines merging lines prices on pack (don't show component prices).\n" +"* Ignored: Use product pack price (ignore detail line prices)." +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_type +msgid "" +"On sale orders or purchase orders:\n" +"* Detailed: Display components individually in the sale order.\n" +"* Non Detailed: Do not display components individually in the sale order." +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_parent_line_id +msgid "Pack" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_component_price +msgid "Pack Component Price" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_type +msgid "Pack Display Type" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_sale_order_line__pack_modifiable +msgid "Pack Modifiable" +msgstr "" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order_line.py:0 +msgid "Parent Product" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_product_pack_line +msgid "Product pack line" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,field_description:sale_product_pack.field_product_pack_line__sale_discount +msgid "Sale discount (%)" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model,name:sale_product_pack.model_sale_order_line +msgid "Sales Order Line" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_parent_line_id +msgid "The pack that contains this product." +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__pack_modifiable +msgid "The parent pack is modifiable" +msgstr "" + +#. module: sale_product_pack +#: model:ir.model.fields,help:sale_product_pack.field_sale_order_line__do_no_expand_pack_lines +msgid "" +"This is a technical field in order to check if pack lines has to be expanded" +msgstr "" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order_line.py:0 +msgid "" +"You can not change this line because is part of a pack included in this " +"order" +msgstr "" + +#. module: sale_product_pack +#. odoo-python +#: code:addons/sale_product_pack/models/sale_order.py:0 +msgid "" +"You cannot delete this line because is part of a pack in this sale order. In" +" order to delete this line you need to delete the pack itself" +msgstr "" diff --git a/sale_product_pack/models/__init__.py b/sale_product_pack/models/__init__.py new file mode 100644 index 000000000..cd9997169 --- /dev/null +++ b/sale_product_pack/models/__init__.py @@ -0,0 +1,5 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import product_pack_line +from . import sale_order_line +from . import sale_order diff --git a/sale_product_pack/models/product_pack_line.py b/sale_product_pack/models/product_pack_line.py new file mode 100644 index 000000000..cb4011087 --- /dev/null +++ b/sale_product_pack/models/product_pack_line.py @@ -0,0 +1,53 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class ProductPack(models.Model): + _inherit = "product.pack.line" + + sale_discount = fields.Float( + "Sale discount (%)", + digits="Discount", + ) + + def get_sale_order_line_vals(self, line, order): + self.ensure_one() + quantity = self.quantity * line.product_uom_qty + line_vals = { + "order_id": order.id, + "sequence": line.sequence, + "product_id": self.product_id.id or False, + "pack_parent_line_id": line.id, + "pack_depth": line.pack_depth + 1, + "company_id": order.company_id.id, + "pack_modifiable": line.product_id.pack_modifiable, + "product_uom_qty": quantity, + } + sol = line.new(line_vals) + vals = sol._convert_to_write(sol._cache) + pack_price_types = {"totalized", "ignored"} + if ( + line.product_id.pack_type == "detailed" + and line.product_id.pack_component_price in pack_price_types + ): + vals["price_unit"] = 0.0 + + vals["name"] = f"{'> ' * (line.pack_depth + 1)}{sol.name}" + + return vals + + def _get_pack_line_price(self, pricelist, quantity, uom=None, date=False, **kwargs): + return super()._get_pack_line_price( + pricelist, quantity, uom=uom, date=date, **kwargs + ) * (1 - self.sale_discount / 100.0) + + def _pack_line_price_compute( + self, price_type, uom=False, currency=False, company=False, date=False + ): + pack_line_prices = super()._pack_line_price_compute( + price_type, uom, currency, company, date + ) + for line in self: + pack_line_prices[line.product_id.id] *= 1 - line.sale_discount / 100.0 + return pack_line_prices diff --git a/sale_product_pack/models/sale_order.py b/sale_product_pack/models/sale_order.py new file mode 100644 index 000000000..9273b9898 --- /dev/null +++ b/sale_product_pack/models/sale_order.py @@ -0,0 +1,66 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import api, models +from odoo.exceptions import UserError + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + def copy(self, default=None): + sale_copy = super().copy(default) + # we unlink pack lines that should not be copied + pack_copied_lines = sale_copy.order_line.filtered( + lambda line: line.pack_parent_line_id.order_id == self + ) + pack_copied_lines.unlink() + return sale_copy + + @api.onchange("order_line") + def check_pack_line_unlink(self): + """At least on embeded tree editable view odoo returns a recordset on + _origin.order_line only when lines are unlinked and this is exactly + what we need + """ + origin_line_ids = self._origin.order_line.ids + line_ids = self.order_line.ids + removed_line_ids = list(set(origin_line_ids) - set(line_ids)) + removed_line = self.env["sale.order.line"].browse(removed_line_ids) + if removed_line.filtered( + lambda x: x.pack_parent_line_id + and not x.pack_parent_line_id.product_id.pack_modifiable + ): + raise UserError( + self.env._( + "You cannot delete this line because is part of a pack in" + " this sale order. In order to delete this line you need to" + " delete the pack itself" + ) + ) + + def write(self, vals): + if "order_line" in vals: + to_delete_ids = [e[1] for e in vals["order_line"] if e[0] == 2] + subpacks_to_delete_ids = ( + self.env["sale.order.line"] + .search( + [("id", "child_of", to_delete_ids), ("id", "not in", to_delete_ids)] + ) + .ids + ) + if subpacks_to_delete_ids: + for cmd in vals["order_line"]: + if cmd[1] in subpacks_to_delete_ids: + if cmd[0] != 2: + cmd[0] = 2 + subpacks_to_delete_ids.remove(cmd[1]) + for to_delete_id in subpacks_to_delete_ids: + vals["order_line"].append([2, to_delete_id, False]) + return super().write(vals) + + def _get_update_prices_lines(self): + res = super()._get_update_prices_lines() + return res.filtered( + lambda line: not line.pack_parent_line_id + or line.pack_parent_line_id.pack_component_price == "detailed" + ) diff --git a/sale_product_pack/models/sale_order_line.py b/sale_product_pack/models/sale_order_line.py new file mode 100644 index 000000000..f941fc30a --- /dev/null +++ b/sale_product_pack/models/sale_order_line.py @@ -0,0 +1,235 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import api, fields, models +from odoo.exceptions import UserError + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + _parent_name = "pack_parent_line_id" + + pack_type = fields.Selection( + related="product_id.pack_type", + ) + pack_component_price = fields.Selection( + related="product_id.pack_component_price", + ) + + # Fields for common packs + pack_depth = fields.Integer( + "Depth", help="Depth of the product if it is part of a pack." + ) + pack_parent_line_id = fields.Many2one( + "sale.order.line", + "Pack", + help="The pack that contains this product.", + ) + pack_child_line_ids = fields.One2many( + "sale.order.line", "pack_parent_line_id", "Lines in pack" + ) + pack_modifiable = fields.Boolean(help="The parent pack is modifiable") + + do_no_expand_pack_lines = fields.Boolean( + compute="_compute_do_no_expand_pack_lines", + help="This is a technical field in order to check if pack lines has to be " + "expanded", + ) + + @api.depends_context("update_prices", "update_pricelist") + def _compute_do_no_expand_pack_lines(self): + do_not_expand = self.env.context.get("update_prices") or self.env.context.get( + "update_pricelist", False + ) + self.update( + { + "do_no_expand_pack_lines": do_not_expand, + } + ) + + def expand_pack_line(self, write=False): + self.ensure_one() + # if we are using update_pricelist or checking out on ecommerce we + # only want to update prices + vals_list = [] + if self.product_id.pack_ok and self.pack_type == "detailed": + for subline in self.product_id.get_pack_lines(): + vals = subline.get_sale_order_line_vals(self, self.order_id) + if write: + existing_subline = ( + self.pack_child_line_ids.filtered( + lambda child, s=subline: child.product_id == s.product_id + ) + )[:1] + # if subline already exists we update, if not we create + if existing_subline: + if self.do_no_expand_pack_lines: + vals.pop("product_uom_qty", None) + vals.pop("discount", None) + existing_subline.write(vals) + elif not self.do_no_expand_pack_lines: + vals_list.append(vals) + else: + vals_list.append(vals) + if vals_list: + self.create(vals_list) + + def action_transform_pack_to_lines(self): + """ + Transform non_detailed pack with pack_modifiable into detailed lines: + 1. Create a section line with the pack product name + 2. Create individual editable lines for each component with their + qty and discount + 3. Delete the original pack line + """ + self.ensure_one() + + if ( + not self.product_id.pack_ok + or self.pack_type != "non_detailed" + or not self.product_id.pack_modifiable + ): + return self.env["sale.order.line"] + + pack_name = self.product_id.display_name + pack_sequence = self.sequence + order = self.order_id + + # Create section line for the pack + section_vals = { + "order_id": order.id, + "display_type": "line_section", + "name": pack_name, + "sequence": pack_sequence, + "collapse_composition": True, + } + section_line = self.env["sale.order.line"].create(section_vals) + created_lines = section_line + + # Create editable component lines + for idx, pack_line in enumerate(self.product_id.get_pack_lines(), start=1): + component_vals = pack_line.get_sale_order_line_vals(self, order) + component_vals.update( + { + "sequence": pack_sequence + idx, + } + ) + created_lines += self.env["sale.order.line"].create(component_vals) + + # Delete the original pack line + self.unlink() + return created_lines + + @api.model_create_multi + def create(self, vals_list): + """Only when strictly necessary (a product is a pack) will be created line + by line, this is necessary to maintain the correct order. + """ + product_ids = [elem.get("product_id") for elem in vals_list] + products = self.env["product.product"].browse(product_ids) + if any( + p.pack_ok + and ( + p.pack_type == "detailed" + or (p.pack_type == "non_detailed" and p.pack_modifiable) + ) + for p in products + ): + res = self.browse() + for elem in vals_list: + line = super().create([elem]) + product = line.product_id + if product and product.pack_ok: + if product.pack_type == "detailed": + res += line + line.expand_pack_line() + elif ( + product.pack_type == "non_detailed" and product.pack_modifiable + ): + res += line.action_transform_pack_to_lines() + else: + res += line + return res + else: + return super().create(vals_list) + + def write(self, vals): + res = super().write(vals) + if "product_id" in vals or "product_uom_qty" in vals: + for record in self: + record.expand_pack_line(write=True) + if ( + "product_id" in vals + and record.product_id.pack_ok + and record.pack_type == "non_detailed" + and record.product_id.pack_modifiable + ): + record.action_transform_pack_to_lines() + return res + + @api.onchange( + "product_id", + "product_uom_qty", + "product_uom_id", + "price_unit", + "discount", + "name", + "tax_id", + ) + def check_pack_line_modify(self): + """Do not let to edit a sale order line if this one belongs to pack""" + if self._origin.pack_parent_line_id and not self._origin.pack_modifiable: + raise UserError( + self.env._( + "You can not change this line because is part of a pack" + " included in this order" + ) + ) + + def action_open_parent_pack_product_view(self): + domain = [ + ("id", "in", self.mapped("pack_parent_line_id").mapped("product_id").ids) + ] + return { + "name": self.env._("Parent Product"), + "type": "ir.actions.act_window", + "res_model": "product.product", + "view_type": "form", + "view_mode": "list,form", + "domain": domain, + } + + def _get_pricelist_price(self): + """Compute the price given by the pricelist for the given line information. + + :return: the product sales price in the order currency (without taxes) + :rtype: float + """ + price = super()._get_pricelist_price() + + if self.product_id.product_tmpl_id._is_pack_to_be_handled(): + price = self.order_id.pricelist_id._get_product_price( + product=self.product_id.product_tmpl_id, quantity=1.0 + ) + return price + + def _get_pack_line_discount(self): + """returns the discount settled in the parent pack lines""" + self.ensure_one() + discount = 0.0 + if self.pack_parent_line_id.pack_component_price == "detailed": + for pack_line in self.pack_parent_line_id.product_id.pack_line_ids: + if pack_line.product_id == self.product_id: + discount = 100.0 - ( + (100.0 - self.discount) + * (100.0 - pack_line.sale_discount) + / 100.0 + ) + break + return discount + + @api.depends("product_id", "product_uom_id", "product_uom_qty") + def _compute_discount(self): + res = super()._compute_discount() + for pack_line in self.filtered("pack_parent_line_id"): + pack_line.discount = pack_line._get_pack_line_discount() + return res diff --git a/sale_product_pack/pyproject.toml b/sale_product_pack/pyproject.toml new file mode 100644 index 000000000..f74e31420 --- /dev/null +++ b/sale_product_pack/pyproject.toml @@ -0,0 +1,9 @@ +[build-system] +requires = ["whool"] + +[project] +name = "odoo-addons-oca-product-pak" +version = "19.0.20251027.0" +dependencies = [ + "odoo-addon-product_pack @ git+https://github.com/OCA/product-pack.git@refs/pull/223/head#subdirectory=product_pack", +] diff --git a/sale_product_pack/readme/CONTRIBUTORS.md b/sale_product_pack/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..caf04aee0 --- /dev/null +++ b/sale_product_pack/readme/CONTRIBUTORS.md @@ -0,0 +1,15 @@ +- [Tecnativa](https://www.tecnativa.com): + - Ernesto Tejeda + - Pedro M. Baeza +- [Akretion](https://akretion.com): + - Raphaël Reverdy +- [Open Source Integrators](https://opensourceintegrators.eu): + - Daniel Reis \<\> +- [Acsone](https://www.acsone.eu/): + - Maxime Franco +- [ADHOC SA](https://www.adhoc.com.ar): + - Bruno Zanotti + - Augusto Weiss + - Nicolas Col +- [Apik](https://apik.cloud/): + - Michel Guiheneuf \ No newline at end of file diff --git a/sale_product_pack/readme/DESCRIPTION.md b/sale_product_pack/readme/DESCRIPTION.md new file mode 100644 index 000000000..802631cf0 --- /dev/null +++ b/sale_product_pack/readme/DESCRIPTION.md @@ -0,0 +1,4 @@ +This module adds *Product Pack* functionality to sales orders. You can +choose a *Pack* in *sales order lines* and see different behaviors +depending on "Pack type" and "Pack component price" fields options +selected on this *Pack*. diff --git a/sale_product_pack/readme/ROADMAP.md b/sale_product_pack/readme/ROADMAP.md new file mode 100644 index 000000000..02047557f --- /dev/null +++ b/sale_product_pack/readme/ROADMAP.md @@ -0,0 +1,5 @@ +- If this module is installed and stock module is installed too, when + you create a Sale order for a *Non detailed* Pack and you confirm it, + a *Stock picking* is not created with the storable components of that + Pack. So, add a new module called *sale_stock_product_pack* that adds + that feature. diff --git a/sale_product_pack/readme/USAGE.md b/sale_product_pack/readme/USAGE.md new file mode 100644 index 000000000..62b6ac783 --- /dev/null +++ b/sale_product_pack/readme/USAGE.md @@ -0,0 +1,15 @@ +To use this module, you need to: + +1. Go to *Sales \> Products \> Products*, create or select a product + and check *Is Pack?* +2. Set "Product type" and "Pack component price" fields in the *Pack* + page. +3. Add the products to be included in it. +4. Go to *Sales \> Orders \> Quotations* and create a Quotation. +5. Add a product that has checked "Is Pack?" +6. Save data and you will see an specific behavior depending on "Pack + type" and "Pack component price" fields options selected on this + *Pack*. For example, for products that has *Detailed* option + selected in "Pack type" field you will see one *sale order line* per + component that belong to this Pack. (See *Product pack* module + README.rst file) diff --git a/sale_product_pack/security/ir.model.access.csv b/sale_product_pack/security/ir.model.access.csv new file mode 100644 index 000000000..6b968d84d --- /dev/null +++ b/sale_product_pack/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_product_pack_line_sale_manager,product.pack.line,model_product_pack_line,sales_team.group_sale_manager,1,1,1,1 diff --git a/sale_product_pack/static/description/icon.png b/sale_product_pack/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/sale_product_pack/static/description/icon.png differ diff --git a/sale_product_pack/static/description/index.html b/sale_product_pack/static/description/index.html new file mode 100644 index 000000000..8f2a614c8 --- /dev/null +++ b/sale_product_pack/static/description/index.html @@ -0,0 +1,492 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Sale Product Pack

+ +

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

+

This module adds Product Pack functionality to sales orders. You can +choose a Pack in sales order lines and see different behaviors +depending on “Pack type” and “Pack component price” fields options +selected on this Pack.

+

Table of contents

+ +
+

Usage

+

To use this module, you need to:

+
    +
  1. Go to Sales > Products > Products, create or select a product and +check Is Pack?
  2. +
  3. Set “Product type” and “Pack component price” fields in the Pack +page.
  4. +
  5. Add the products to be included in it.
  6. +
  7. Go to Sales > Orders > Quotations and create a Quotation.
  8. +
  9. Add a product that has checked “Is Pack?”
  10. +
  11. Save data and you will see an specific behavior depending on “Pack +type” and “Pack component price” fields options selected on this +Pack. For example, for products that has Detailed option selected +in “Pack type” field you will see one sale order line per component +that belong to this Pack. (See Product pack module README.rst file)
  12. +
+
+
+

Known issues / Roadmap

+
    +
  • If this module is installed and stock module is installed too, when +you create a Sale order for a Non detailed Pack and you confirm it, +a Stock picking is not created with the storable components of that +Pack. So, add a new module called sale_stock_product_pack that adds +that feature.
  • +
+
+
+

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

+
    +
  • NaN·tic
  • +
  • ADHOC SA
  • +
  • Tecnativa
  • +
+
+
+

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.

+

Current maintainer:

+

victoralmau

+

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

+

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

+
+
+
+
+ + diff --git a/sale_product_pack/tests/__init__.py b/sale_product_pack/tests/__init__.py new file mode 100644 index 000000000..365edcb3d --- /dev/null +++ b/sale_product_pack/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import test_sale_product_pack diff --git a/sale_product_pack/tests/common.py b/sale_product_pack/tests/common.py new file mode 100644 index 000000000..5e8bdeecc --- /dev/null +++ b/sale_product_pack/tests/common.py @@ -0,0 +1,72 @@ +# Copyright 2025 Tecnativa - Pedro M. Baeza +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import Command + +from odoo.addons.product_pack.tests.common import ProductPackCommon + + +class TestSaleProductPackBase(ProductPackCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + pricelist = cls.env["product.pricelist"].create( + { + "name": "Test", + "company_id": cls.env.company.id, + "item_ids": [ + Command.create( + { + "applied_on": "3_global", + "compute_price": "formula", + "base": "list_price", + }, + ) + ], + } + ) + cls.discount_pricelist = cls.env["product.pricelist"].create( + { + "name": "Discount", + "company_id": cls.env.company.id, + "item_ids": [ + Command.create( + { + "applied_on": "3_global", + "compute_price": "percentage", + "percent_price": 10, + }, + ) + ], + } + ) + partner = cls.env["res.partner"].create( + { + "name": "Customer test", + "email": "test@test.example.com", + "phone": "+33 601 020 304", + "street": "Rue de la mairie", + "city": "New York", + "zip": "97648", + "website": "https://test.exemple.com", + } + ) + cls.sale_order = cls.env["sale.order"].create( + { + "company_id": cls.env.company.id, + "partner_id": partner.id, + "pricelist_id": pricelist.id, + } + ) + + def _add_so_line(self, product=None, sequence=10): + product = product or self.pack + return self.env["sale.order.line"].create( + { + "order_id": self.sale_order.id, + "name": product.name, + "product_id": product.id, + "product_uom_qty": 1, + "sequence": sequence, + } + ) diff --git a/sale_product_pack/tests/test_sale_product_pack.py b/sale_product_pack/tests/test_sale_product_pack.py new file mode 100644 index 000000000..8634f2a71 --- /dev/null +++ b/sale_product_pack/tests/test_sale_product_pack.py @@ -0,0 +1,165 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# Copyright 2025 Tecnativa - Pedro M. Baeza +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from .common import TestSaleProductPackBase + + +class TestSaleProductPack(TestSaleProductPackBase): + def test_create_components_price_order_line(self): + group_discount = self.env.ref("sale.group_discount_per_so_line") + self.env.user.write({"group_ids": [(4, group_discount.id)]}) + self._add_so_line() + # After create, there will be four lines + self.assertEqual(len(self.sale_order.order_line), 3) + # Check if sequence is the same as pack product one + for so_line in self.sale_order.order_line: + self.assertEqual(so_line.sequence, 10) + # The products of those four lines are the main product pack and its + # product components + self.assertEqual(self.sale_order.order_line[0].product_id, self.pack) + self.assertEqual(self.sale_order.order_line[1].product_id, self.component1) + self.assertEqual(self.sale_order.order_line[2].product_id, self.component2) + # Price before update pricelist + self.assertEqual(self.sale_order.order_line[0].price_subtotal, 10) + self.assertEqual(self.sale_order.order_line[1].price_subtotal, 40) + self.assertEqual(self.sale_order.order_line[2].price_subtotal, 30) + # Update pricelist with a discount + self.sale_order.pricelist_id = self.discount_pricelist + self.sale_order.action_update_prices() + self.assertEqual(self.sale_order.order_line[0].discount, 10) + self.assertEqual(self.sale_order.order_line[0].price_subtotal, 9) + self.assertEqual(self.sale_order.order_line[1].discount, 10) + self.assertEqual(self.sale_order.order_line[1].price_subtotal, 36) + self.assertEqual(self.sale_order.order_line[2].discount, 10) + self.assertEqual(self.sale_order.order_line[2].price_subtotal, 27) + + def test_create_ignored_price_order_line(self): + self.pack.pack_component_price = "ignored" + self._add_so_line() + # After create, there will be four lines + self.assertEqual(len(self.sale_order.order_line), 3) + # The products of those four lines are the main product pack and its + # product components + self.assertEqual(self.sale_order.order_line[0].product_id, self.pack) + self.assertEqual(self.sale_order.order_line[1].product_id, self.component1) + self.assertEqual(self.sale_order.order_line[2].product_id, self.component2) + # All component lines have zero as subtotal + self.assertEqual(self.sale_order.order_line[1].price_subtotal, 0) + self.assertEqual(self.sale_order.order_line[2].price_subtotal, 0) + # Pack price is different from the sum of component prices + self.assertEqual(self.sale_order.order_line[0].price_subtotal, 10) + # Update pricelist with a discount + self.sale_order.pricelist_id = self.discount_pricelist + self.sale_order.action_update_prices() + self.assertEqual(self.sale_order.order_line[0].price_subtotal, 9) + self.assertEqual(self.sale_order.order_line[1].price_subtotal, 0) + self.assertEqual(self.sale_order.order_line[2].price_subtotal, 0) + + def test_create_totalized_price_order_line(self): + self.pack.pack_component_price = "totalized" + self._add_so_line() + # After create, there will be four lines + self.assertEqual(len(self.sale_order.order_line), 3) + # The products of those four lines are the main product pack and its + # product components + self.assertEqual(self.sale_order.order_line[0].product_id, self.pack) + self.assertEqual(self.sale_order.order_line[1].product_id, self.component1) + self.assertEqual(self.sale_order.order_line[2].product_id, self.component2) + # All component lines have zero as subtotal + self.assertEqual(self.sale_order.order_line[1].price_subtotal, 0) + self.assertEqual(self.sale_order.order_line[2].price_subtotal, 0) + # Pack price is equal to the sum of component prices + self.assertEqual(self.sale_order.order_line[0].price_subtotal, 70) + # Update pricelist with a discount + self.sale_order.pricelist_id = self.discount_pricelist + self.sale_order.action_update_prices() + self.assertEqual(self.sale_order.order_line[0].price_subtotal, 63) + self.assertEqual(self.sale_order.order_line[1].price_subtotal, 0) + self.assertEqual(self.sale_order.order_line[2].price_subtotal, 0) + + def test_create_non_detailed_price_order_line(self): + self.pack.pack_type = "non_detailed" + self._add_so_line() + # After create, there will be only one line, because product_type is + # not a detailed one + self.assertEqual(len(self.sale_order.order_line), 1) + # Pack price is equal to the sum of component prices + self.assertEqual(self.sale_order.order_line.price_subtotal, 70) + # Update pricelist with a discount + self.sale_order.pricelist_id = self.discount_pricelist + self.sale_order.action_update_prices() + self.assertEqual(self.sale_order.order_line.price_subtotal, 63) + + def test_update_qty(self): + pack_line = self._add_so_line() + # change qty of main sol and ensure all the quantities have doubled + pack_line.product_uom_qty = 2 + self.assertAlmostEqual(self.sale_order.order_line[1].product_uom_qty, 4) + self.assertAlmostEqual(self.sale_order.order_line[2].product_uom_qty, 2) + # Confirm the sale + self.sale_order.action_confirm() + # Ensure we can still update the quantity + pack_line.product_uom_qty = 4 + self.assertAlmostEqual(self.sale_order.order_line[1].product_uom_qty, 8) + self.assertAlmostEqual(self.sale_order.order_line[2].product_uom_qty, 4) + + def test_do_not_expand(self): + pack_line = self._add_so_line() + pack_line_update = pack_line.with_context(update_prices=True) + self.assertTrue(pack_line_update.do_no_expand_pack_lines) + pack_line_update = pack_line.with_context(update_pricelist=True) + self.assertTrue(pack_line_update.do_no_expand_pack_lines) + + def test_create_several_lines_01(self): + # Create two sale order lines with two pack products + self._add_so_line() + self._add_so_line(sequence=20) + # Check 6 lines are created + self.assertEqual(len(self.sale_order.order_line), 6) + # Check lines sequences and order are respected + for so_line in self.sale_order.order_line[:3]: + self.assertEqual(so_line.sequence, 10) + for so_line in self.sale_order.order_line[3:]: + self.assertEqual(so_line.sequence, 20) + + def test_create_several_lines_02(self): + # Create several sale order lines + product = self.env["product.product"].create({"name": "Test product"}) + self._add_so_line(product=product) + self._add_so_line(sequence=20) + self._add_so_line(product=product, sequence=30) + # After create, there will be 4 lines (1 + 3 + 1) + self.assertEqual(len(self.sale_order.order_line), 5) + # Check if lines are well ordered + self.assertEqual(self.sale_order.order_line[0].product_id, product) + self.assertEqual(self.sale_order.order_line[1].product_id, self.pack) + self.assertEqual(self.sale_order.order_line[2].product_id, self.component1) + self.assertEqual(self.sale_order.order_line[3].product_id, self.component2) + self.assertEqual(self.sale_order.order_line[4].product_id, product) + + def test_non_detailed_modifiable_pack(self): + """Test non_detailed pack with pack_modifiable auto-expands.""" + # Configure pack as non_detailed with pack_modifiable + self.pack.pack_type = "non_detailed" + self.pack.pack_modifiable = True + self._add_so_line() + # After create, pack should be expanded into: + # 1 section line + 2 component lines + self.assertEqual(len(self.sale_order.order_line), 3) + # First line should be a section with pack name + section_line = self.sale_order.order_line[0] + self.assertEqual(section_line.display_type, "line_section") + self.assertEqual(section_line.name, self.pack.display_name) + self.assertTrue(section_line.collapse_composition) + # Next lines should be the components + self.assertEqual(self.sale_order.order_line[1].product_id, self.component1) + self.assertEqual(self.sale_order.order_line[2].product_id, self.component2) + # Component lines should have proper quantities and prices + self.assertEqual(self.sale_order.order_line[1].product_uom_qty, 2) + self.assertEqual(self.sale_order.order_line[2].product_uom_qty, 1) + self.assertAlmostEqual(self.sale_order.order_line[1].price_unit, 20) + self.assertAlmostEqual(self.sale_order.order_line[2].price_unit, 30) + # Lines should be editable (no pack_parent_line_id) + self.assertFalse(self.sale_order.order_line[1].pack_parent_line_id) + self.assertFalse(self.sale_order.order_line[2].pack_parent_line_id) diff --git a/sale_product_pack/views/product_pack_line_views.xml b/sale_product_pack/views/product_pack_line_views.xml new file mode 100644 index 000000000..ba7e94b23 --- /dev/null +++ b/sale_product_pack/views/product_pack_line_views.xml @@ -0,0 +1,45 @@ + + + + + product.pack.line.sale.form + product.pack.line + + + + + + + + + product.pack.line.sale.tree + product.pack.line + + + + + + + + + sale.order + + + + + +