diff --git a/sale_packaging_default/README.rst b/sale_packaging_default/README.rst new file mode 100644 index 00000000000..88ba5c280d1 --- /dev/null +++ b/sale_packaging_default/README.rst @@ -0,0 +1,147 @@ +=========================== +Default packaging for sales +=========================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:9db8b2c896fa2d200223b1b75e051f131aa990da064e9e9855588bf9a2a14cb2 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github + :target: https://github.com/OCA/sale-workflow/tree/17.0/sale_packaging_default + :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-17-0/sale-workflow-17-0-sale_packaging_default + :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=17.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module simplifies and emphasizes the usage of packaging in sales +orders. + +- Packaging fields in sale order lines appear before product quantity + fields. +- When selecting a product to sell, its default packaging is added + automatically. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +Some companies, like food distributors, need to emphasize the usage of +product packaging in the sale process. + +Product packaging in Odoo is designed to be computed automatically when +you choose the final amount of products to sell. For example, if you +sell 12 kgs of apples and those can be packaged by bags of 4kg, you'll +get automatically suggested to sell 3 bags. + +Although you can still do that, with this module the natural usage is +inverted: it's easier for you to select the packaging and quantity +first, and then the final amount of products is computed automatically. +Following the same example, you'll choose bags, then 3, and then the +12kg will be computed automatically. + +Configuration +============= + +To configure this module, you need to: + +1. Go to *Sales > Configuration > Settings*. +2. Under *Product Catalog*, enable *Product Packagings*. + +Usage +===== + +To use this module, you need to configure a product with packaging: + +1. Go to *Sales > Products > Products* and select or create a product. +2. In the *Inventory* tab, add some packaging(s). +3. Enable *Sales* and in one packaging. +4. Sort the packaging options at will. The first one enabled for *Sales* + will be considered the default one. + +Then you have to sell it: + +1. Go to *Sales > Orders > Quotations* and create a new quotation. +2. Select any customer. +3. Select that product. + +You will notice that: + +- The product is added with the default sale packaging. +- The packaging quantity is set to 1 packaging unit. +- The product UoM quantity is set to the amount of units contained in 1 + packaging. +- When changing to another product, instead of keeping the amount of + UoM units, we now keep the packaging qty, and the UoM qty is + recomputed accordingly. + +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 +------- + +* Moduon + +Contributors +------------ + +- Jairo Llopis (`Moduon `__) + +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-yajo| image:: https://github.com/yajo.png?size=40px + :target: https://github.com/yajo + :alt: yajo + +Current `maintainer `__: + +|maintainer-yajo| + +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/sale_packaging_default/__init__.py b/sale_packaging_default/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/sale_packaging_default/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/sale_packaging_default/__manifest__.py b/sale_packaging_default/__manifest__.py new file mode 100644 index 00000000000..6511f7d54af --- /dev/null +++ b/sale_packaging_default/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2023 Moduon Team S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) + +{ + "name": "Default packaging for sales", + "version": "17.0.1.0.0", + "summary": "Simplify using products default packaging for sales", + "development_status": "Alpha", + "category": "Sales", + "website": "https://github.com/OCA/sale-workflow", + "author": "Moduon, Odoo Community Association (OCA)", + "maintainers": ["yajo"], + "license": "LGPL-3", + "application": False, + "installable": True, + "depends": ["sale"], + "data": [ + "views/sale_order_view.xml", + ], +} diff --git a/sale_packaging_default/i18n/es.po b/sale_packaging_default/i18n/es.po new file mode 100644 index 00000000000..803afe6e093 --- /dev/null +++ b/sale_packaging_default/i18n/es.po @@ -0,0 +1,37 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_packaging_default +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-03-07 12:35+0000\n" +"Last-Translator: Jairo Llopis \n" +"Language-Team: none\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_packaging_default +#: model:ir.model,name:sale_packaging_default.model_product_packaging +msgid "Product Packaging" +msgstr "Envase del producto" + +#. module: sale_packaging_default +#: model:ir.model,name:sale_packaging_default.model_sale_order_line +msgid "Sales Order Line" +msgstr "Línea de Pedido de Venta" + +#~ msgid "Default for sales" +#~ msgstr "Por defecto para ventas" + +#~ msgid "" +#~ "The first packaging with this option checked will be used by default in " +#~ "sales orders." +#~ msgstr "" +#~ "El primer empaquetado de la lista con esta opción marcada es el que se " +#~ "utilizará por defecto en los pedidos de venta." diff --git a/sale_packaging_default/i18n/it.po b/sale_packaging_default/i18n/it.po new file mode 100644 index 00000000000..60706541f41 --- /dev/null +++ b/sale_packaging_default/i18n/it.po @@ -0,0 +1,37 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_packaging_default +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-12-04 09:34+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 4.17\n" + +#. module: sale_packaging_default +#: model:ir.model,name:sale_packaging_default.model_product_packaging +msgid "Product Packaging" +msgstr "Imballaggio prodotto" + +#. module: sale_packaging_default +#: model:ir.model,name:sale_packaging_default.model_sale_order_line +msgid "Sales Order Line" +msgstr "Riga ordine di vendita" + +#~ msgid "Default for sales" +#~ msgstr "Predefinito per le vendite" + +#~ msgid "" +#~ "The first packaging with this option checked will be used by default in " +#~ "sales orders." +#~ msgstr "" +#~ "Il primo imballaggio con questa opzione selezionata verrà utilizzato in " +#~ "modo predefinito negli ordini di vendita." diff --git a/sale_packaging_default/i18n/sale_packaging_default.pot b/sale_packaging_default/i18n/sale_packaging_default.pot new file mode 100644 index 00000000000..3e32049c572 --- /dev/null +++ b/sale_packaging_default/i18n/sale_packaging_default.pot @@ -0,0 +1,24 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_packaging_default +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: sale_packaging_default +#: model:ir.model,name:sale_packaging_default.model_product_packaging +msgid "Product Packaging" +msgstr "" + +#. module: sale_packaging_default +#: model:ir.model,name:sale_packaging_default.model_sale_order_line +msgid "Sales Order Line" +msgstr "" diff --git a/sale_packaging_default/models/__init__.py b/sale_packaging_default/models/__init__.py new file mode 100644 index 00000000000..6d5182cde79 --- /dev/null +++ b/sale_packaging_default/models/__init__.py @@ -0,0 +1,2 @@ +from . import product_packaging +from . import sale_order_line diff --git a/sale_packaging_default/models/product_packaging.py b/sale_packaging_default/models/product_packaging.py new file mode 100644 index 00000000000..f30bc1d7baf --- /dev/null +++ b/sale_packaging_default/models/product_packaging.py @@ -0,0 +1,13 @@ +# Copyright 2023 Moduon Team S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) +from odoo import models + + +class ProductPackaging(models.Model): + _inherit = "product.packaging" + + def _find_suitable_product_packaging(self, product_qty, uom_id): + """Find nothing if you want to keep what was there.""" + if self.env.context.get("keep_product_packaging"): + return self.browse() + return super()._find_suitable_product_packaging(product_qty, uom_id) diff --git a/sale_packaging_default/models/sale_order_line.py b/sale_packaging_default/models/sale_order_line.py new file mode 100644 index 00000000000..c335e9f49d1 --- /dev/null +++ b/sale_packaging_default/models/sale_order_line.py @@ -0,0 +1,86 @@ +# Copyright 2023 Moduon Team S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) +from contextlib import suppress + +from odoo import api, fields, models + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + def onchange(self, values, field_name, field_onchange): + """Record which field was being changed.""" + if isinstance(field_name, list): + names = set(field_name) + elif field_name: + names = {field_name} + else: + names = set() + _self = self.with_context(changing_fields=names) + return super(SaleOrderLine, _self).onchange(values, field_name, field_onchange) + + @api.depends("product_id", "product_uom_qty", "product_uom") + def _compute_product_packaging_id(self): + """Set a default packaging for sales if possible.""" + for line in self: + if line.product_id != line.product_packaging_id.product_id: + line.product_packaging_id = line._get_default_packaging(line.product_id) + result = super()._compute_product_packaging_id() + # If there's no way to package the desired qty, remove the packaging. + # It is only done when the user is currently manually setting + # `product_uom_qty` to zero. In other cases, we are maybe getting + # default values and this difference will get fixed by other compute + # methods later. + if ( + self.env.context.get("changing_fields") + and "product_uom_qty" not in self.env.context["changing_fields"] + ): + return result + for line in self: + with suppress(ZeroDivisionError): + if ( + line.product_uom_qty + and line.product_uom_qty % line.product_packaging_id.qty + ): + line.product_packaging_id = False + return result + + @api.model + def _get_default_packaging(self, product): + return fields.first( + product.packaging_ids.filtered_domain([("sales", "=", True)]) + ) + + @api.depends("product_packaging_id", "product_uom", "product_uom_qty") + def _compute_product_packaging_qty(self): + """Set a valid packaging quantity.""" + changing_fields = self.env.context.get("changing_fields", set()) + # Keep the packaging qty when changing the product + if "product_id" in changing_fields and all( + line.product_id and line.product_packaging_qty for line in self + ): + return + result = super()._compute_product_packaging_qty() + for line in self: + if not line.product_packaging_id: + continue + # Reset to 1 packaging if it's empty or not a whole number + if not line.product_packaging_qty or line.product_packaging_qty % 1: + line.product_packaging_qty = int( + "product_uom_qty" not in changing_fields + ) + return result + + @api.depends( + "display_type", + "product_id", + "product_packaging_id", + "product_packaging_qty", + ) + def _compute_product_uom_qty(self): + # Avoid a circular dependency. Upstream `product_uom_qty` has an + # undeclared dependency over `product_packaging_qty`, which depends + # again on `product_uom_qty`. + _self = self.with_context(keep_product_packaging=True) + result = super(SaleOrderLine, _self)._compute_product_uom_qty() + return result diff --git a/sale_packaging_default/pyproject.toml b/sale_packaging_default/pyproject.toml new file mode 100644 index 00000000000..4231d0cccb3 --- /dev/null +++ b/sale_packaging_default/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/sale_packaging_default/readme/CONFIGURE.md b/sale_packaging_default/readme/CONFIGURE.md new file mode 100644 index 00000000000..6b68b4d20fb --- /dev/null +++ b/sale_packaging_default/readme/CONFIGURE.md @@ -0,0 +1,4 @@ +To configure this module, you need to: + +1. Go to *Sales \> Configuration \> Settings*. +2. Under *Product Catalog*, enable *Product Packagings*. diff --git a/sale_packaging_default/readme/CONTEXT.md b/sale_packaging_default/readme/CONTEXT.md new file mode 100644 index 00000000000..3b6b53e4fe1 --- /dev/null +++ b/sale_packaging_default/readme/CONTEXT.md @@ -0,0 +1,12 @@ +Some companies, like food distributors, need to emphasize the usage of product +packaging in the sale process. + +Product packaging in Odoo is designed to be computed automatically when you +choose the final amount of products to sell. For example, if you sell 12 kgs of +apples and those can be packaged by bags of 4kg, you'll get automatically +suggested to sell 3 bags. + +Although you can still do that, with this module the natural usage is inverted: +it's easier for you to select the packaging and quantity first, and then the +final amount of products is computed automatically. Following the same example, +you'll choose bags, then 3, and then the 12kg will be computed automatically. diff --git a/sale_packaging_default/readme/CONTRIBUTORS.md b/sale_packaging_default/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..ad3bdb21555 --- /dev/null +++ b/sale_packaging_default/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Jairo Llopis ([Moduon](https://www.moduon.team/)) diff --git a/sale_packaging_default/readme/DESCRIPTION.md b/sale_packaging_default/readme/DESCRIPTION.md new file mode 100644 index 00000000000..cc4d8027df3 --- /dev/null +++ b/sale_packaging_default/readme/DESCRIPTION.md @@ -0,0 +1,7 @@ +This module simplifies and emphasizes the usage of packaging in sales +orders. + +- Packaging fields in sale order lines appear before product quantity + fields. +- When selecting a product to sell, its default packaging is added + automatically. diff --git a/sale_packaging_default/readme/USAGE.md b/sale_packaging_default/readme/USAGE.md new file mode 100644 index 00000000000..54293f02e71 --- /dev/null +++ b/sale_packaging_default/readme/USAGE.md @@ -0,0 +1,21 @@ +To use this module, you need to configure a product with packaging: + +1. Go to *Sales \> Products \> Products* and select or create a + product. +2. In the *Inventory* tab, add some packaging(s). +3. Enable *Sales* and in one packaging. +4. Sort the packaging options at will. The first one enabled for *Sales* will + be considered the default one. + +Then you have to sell it: + +1. Go to *Sales \> Orders \> Quotations* and create a new quotation. +2. Select any customer. +3. Select that product. + +You will notice that: +- The product is added with the default sale packaging. +- The packaging quantity is set to 1 packaging unit. +- The product UoM quantity is set to the amount of units contained in 1 packaging. +- When changing to another product, instead of keeping the amount of UoM units, + we now keep the packaging qty, and the UoM qty is recomputed accordingly. diff --git a/sale_packaging_default/static/description/icon.png b/sale_packaging_default/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/sale_packaging_default/static/description/icon.png differ diff --git a/sale_packaging_default/static/description/index.html b/sale_packaging_default/static/description/index.html new file mode 100644 index 00000000000..a3fc7ab5422 --- /dev/null +++ b/sale_packaging_default/static/description/index.html @@ -0,0 +1,490 @@ + + + + + +Default packaging for sales + + + +
+

Default packaging for sales

+ + +

Alpha License: LGPL-3 OCA/sale-workflow Translate me on Weblate Try me on Runboat

+

This module simplifies and emphasizes the usage of packaging in sales +orders.

+
    +
  • Packaging fields in sale order lines appear before product quantity +fields.
  • +
  • When selecting a product to sell, its default packaging is added +automatically.
  • +
+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

Use Cases / Context

+

Some companies, like food distributors, need to emphasize the usage of +product packaging in the sale process.

+

Product packaging in Odoo is designed to be computed automatically when +you choose the final amount of products to sell. For example, if you +sell 12 kgs of apples and those can be packaged by bags of 4kg, you’ll +get automatically suggested to sell 3 bags.

+

Although you can still do that, with this module the natural usage is +inverted: it’s easier for you to select the packaging and quantity +first, and then the final amount of products is computed automatically. +Following the same example, you’ll choose bags, then 3, and then the +12kg will be computed automatically.

+
+
+

Configuration

+

To configure this module, you need to:

+
    +
  1. Go to Sales > Configuration > Settings.
  2. +
  3. Under Product Catalog, enable Product Packagings.
  4. +
+
+
+

Usage

+

To use this module, you need to configure a product with packaging:

+
    +
  1. Go to Sales > Products > Products and select or create a product.
  2. +
  3. In the Inventory tab, add some packaging(s).
  4. +
  5. Enable Sales and in one packaging.
  6. +
  7. Sort the packaging options at will. The first one enabled for Sales +will be considered the default one.
  8. +
+

Then you have to sell it:

+
    +
  1. Go to Sales > Orders > Quotations and create a new quotation.
  2. +
  3. Select any customer.
  4. +
  5. Select that product.
  6. +
+

You will notice that:

+
    +
  • The product is added with the default sale packaging.
  • +
  • The packaging quantity is set to 1 packaging unit.
  • +
  • The product UoM quantity is set to the amount of units contained in 1 +packaging.
  • +
  • When changing to another product, instead of keeping the amount of +UoM units, we now keep the packaging qty, and the UoM qty is +recomputed accordingly.
  • +
+
+
+

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

+
    +
  • Moduon
  • +
+
+
+

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:

+

yajo

+

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/sale_packaging_default/tests/__init__.py b/sale_packaging_default/tests/__init__.py new file mode 100644 index 00000000000..e565371bf5d --- /dev/null +++ b/sale_packaging_default/tests/__init__.py @@ -0,0 +1 @@ +from . import test_sale_packaging_default diff --git a/sale_packaging_default/tests/test_sale_packaging_default.py b/sale_packaging_default/tests/test_sale_packaging_default.py new file mode 100644 index 00000000000..e586768cc2d --- /dev/null +++ b/sale_packaging_default/tests/test_sale_packaging_default.py @@ -0,0 +1,128 @@ +# Copyright 2023 Moduon Team S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) +from odoo import fields +from odoo.tests.common import Form + +from odoo.addons.product.tests.common import ProductCommon + + +class SalePackagingDefaultCase(ProductCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env.user.groups_id |= cls.env.ref("product.group_stock_packaging") + with Form(cls.product) as product_f: + with product_f.packaging_ids.new() as packaging_f: + packaging_f.name = "Dozen" + packaging_f.qty = 12 + packaging_f.sales = True + packaging_f.sequence = 20 + with product_f.packaging_ids.new() as packaging_f: + packaging_f.name = "Big box" + packaging_f.qty = 100 + packaging_f.sales = True + packaging_f.sequence = 10 # This is the default one + cls.big_box, cls.dozen = cls.product.packaging_ids + assert cls.dozen.name == "Dozen" + assert cls.big_box.name == "Big box" + cls.product2 = cls.env["product.product"].create( + { + "name": "Product 2", + "type": "consu", + "packaging_ids": [ + fields.Command.create( + { + "name": "3-pack", + "qty": 3, + "sales": True, + "sequence": 10, + } + ), + ], + } + ) + cls.p2_three_pack = cls.product2.packaging_ids[0] + assert cls.p2_three_pack.name == "3-pack" + + def test_default_packaging_sale_order(self): + """Check is packaging usage in sale order.""" + # Create a sale order with the product + so_f = Form(self.env["sale.order"]) + so_f.partner_id = self.partner + with so_f.order_line.new() as line_f: + line_f.product_id = self.product + # Automatically set the default packaging and the quantity + self.assertEqual(line_f.product_packaging_id, self.big_box) + self.assertEqual(line_f.product_packaging_qty, 1) + self.assertEqual(line_f.product_uom_qty, 100) + # Change the packaging, and qtys are recalculated + line_f.product_packaging_id = self.dozen + self.assertEqual(line_f.product_packaging_qty, 1) + self.assertEqual(line_f.product_uom_qty, 12) + # Change product qty, and packaging is recalculated + line_f.product_uom_qty = 1200 + self.assertEqual(line_f.product_packaging_qty, 12) + self.assertEqual(line_f.product_packaging_id, self.big_box) + self.assertEqual(line_f.product_uom_qty, 1200) + # I want it in dozens, so I change the packaging + line_f.product_packaging_id = self.dozen + self.assertEqual(line_f.product_packaging_id, self.dozen) + self.assertEqual(line_f.product_uom_qty, 1200) + self.assertEqual(line_f.product_packaging_qty, 100) + # I want less dozens, so I change the packaging qty + line_f.product_packaging_qty = 90 + self.assertEqual(line_f.product_packaging_id, self.dozen) + self.assertEqual(line_f.product_uom_qty, 1080) + # Change the packaging again, and qtys are recalculated + line_f.product_packaging_id = self.big_box + self.assertEqual(line_f.product_packaging_qty, 1) + self.assertEqual(line_f.product_uom_qty, 100) + # I want more units, so I change the uom qty + line_f.product_uom_qty = 120 + self.assertEqual(line_f.product_packaging_qty, 10) + self.assertEqual(line_f.product_packaging_id, self.dozen) + # If I set a uom qty without packaging, it is emptied + line_f.product_uom_qty = 7 + self.assertFalse(line_f.product_packaging_id) + self.assertEqual(line_f.product_packaging_qty, 0) + self.assertEqual(line_f.product_uom_qty, 7) + # Setting zero uom qty resets to the default packaging + line_f.product_uom_qty = 0 + self.assertEqual(line_f.product_packaging_id, self.big_box) + self.assertEqual(line_f.product_packaging_qty, 0) + self.assertEqual(line_f.product_uom_qty, 0) + + def test_sale_order_product_picker_compatibility(self): + """Emulate a call done by the product picker module and see it works. + + This test asserts support for cross-compatibility with + `sale_order_product_picker`. + """ + so_f = Form( + self.env["sale.order"].with_context( + default_product_id=self.product.id, default_price_unit=20 + ) + ) + so_f.partner_id = self.partner + # User clicks on +1 button + with so_f.order_line.new() as line_f: + self.assertEqual(line_f.product_uom_qty, 1) + self.assertFalse(line_f.product_packaging_id) + + def test_product_change(self): + """Set one product, alter qtys, change product, qtys are reset.""" + so_f = Form(self.env["sale.order"]) + so_f.partner_id = self.partner + with so_f.order_line.new() as line_f: + line_f.product_id = self.product + self.assertEqual(line_f.product_packaging_id, self.big_box) + self.assertEqual(line_f.product_packaging_qty, 1) + self.assertEqual(line_f.product_uom_qty, 100) + line_f.product_uom_qty = 120 + self.assertEqual(line_f.product_packaging_id, self.dozen) + self.assertEqual(line_f.product_packaging_qty, 10) + self.assertEqual(line_f.product_uom_qty, 120) + line_f.product_id = self.product2 + self.assertEqual(line_f.product_packaging_id, self.p2_three_pack) + self.assertEqual(line_f.product_packaging_qty, 10) + self.assertEqual(line_f.product_uom_qty, 30) diff --git a/sale_packaging_default/views/sale_order_view.xml b/sale_packaging_default/views/sale_order_view.xml new file mode 100644 index 00000000000..c49657f9891 --- /dev/null +++ b/sale_packaging_default/views/sale_order_view.xml @@ -0,0 +1,41 @@ + + + + + Simplify packaging fields + sale.order + + + + + + + + + + + + + + + +