From 293f18e36071b02ba53666e11e83b8d849b4ea31 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Mon, 28 Aug 2023 13:07:20 +0100 Subject: [PATCH 01/25] [ADD] sale_packaging_default: default packaging for sales 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. Why is this module useful? Packaging usage in Odoo is designed to be computed automatically when you choose the final amount of products to sell. 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. @moduon MT-3695 --- sale_packaging_default/README.rst | 124 +++++ sale_packaging_default/__init__.py | 1 + sale_packaging_default/__manifest__.py | 21 + sale_packaging_default/models/__init__.py | 2 + .../models/product_packaging.py | 31 ++ .../models/sale_order_line.py | 44 ++ sale_packaging_default/readme/CONFIGURE.rst | 4 + .../readme/CONTRIBUTORS.rst | 1 + sale_packaging_default/readme/DESCRIPTION.rst | 10 + sale_packaging_default/readme/USAGE.rst | 14 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 465 ++++++++++++++++++ sale_packaging_default/tests/__init__.py | 1 + .../tests/test_sale_packaging_default.py | 85 ++++ .../views/product_packaging_view.xml | 32 ++ .../views/sale_order_view.xml | 25 + 16 files changed, 860 insertions(+) create mode 100644 sale_packaging_default/README.rst create mode 100644 sale_packaging_default/__init__.py create mode 100644 sale_packaging_default/__manifest__.py create mode 100644 sale_packaging_default/models/__init__.py create mode 100644 sale_packaging_default/models/product_packaging.py create mode 100644 sale_packaging_default/models/sale_order_line.py create mode 100644 sale_packaging_default/readme/CONFIGURE.rst create mode 100644 sale_packaging_default/readme/CONTRIBUTORS.rst create mode 100644 sale_packaging_default/readme/DESCRIPTION.rst create mode 100644 sale_packaging_default/readme/USAGE.rst create mode 100644 sale_packaging_default/static/description/icon.png create mode 100644 sale_packaging_default/static/description/index.html create mode 100644 sale_packaging_default/tests/__init__.py create mode 100644 sale_packaging_default/tests/test_sale_packaging_default.py create mode 100644 sale_packaging_default/views/product_packaging_view.xml create mode 100644 sale_packaging_default/views/sale_order_view.xml diff --git a/sale_packaging_default/README.rst b/sale_packaging_default/README.rst new file mode 100644 index 00000000000..9e00fdbb739 --- /dev/null +++ b/sale_packaging_default/README.rst @@ -0,0 +1,124 @@ +=========================== +Default packaging for sales +=========================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:e9592b6ceb68a0f67cbf51c3cc8f47585c8626ba43a4c0dd76adcd053340bcdd + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/16.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-16-0/sale-workflow-16-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/webui/builds.html?repo=OCA/sale-workflow&target_branch=16.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. + +Why is this module useful? Packaging usage in Odoo is designed to be computed +automatically when you choose the final amount of products to sell. 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. + +.. 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: + +Configuration +============= + +To configure this module, you need to: + +#. Go to *Sales > Configuration > Settings*. +#. Under *Product Catalog*, enable *Product Packagings*. + +Usage +===== + +To use this module, you need to configure a product with packaging: + +#. Go to *Sales > Products > Products* and select or create a product. +#. In the *Inventory* tab, add some packaging(s). +#. Enable *Sales* and *Default for sales* in one packaging. + +Then you have to sell it: + +#. Go to *Sales > Orders > Quotations* and create a new quotation. +#. Select any customer. +#. Add the product with packaging. + +You will notice that the product is added with the default sale packaging, and +the quantity is set to 1 packaging unit. + +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..be57be1150c --- /dev/null +++ b/sale_packaging_default/__manifest__.py @@ -0,0 +1,21 @@ +# 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", + "summary": "Set a default product packaging and simplify its usage in sales orders", + "version": "16.0.1.0.0", + "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/product_packaging_view.xml", + "views/sale_order_view.xml", + ], +} 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..b0da9188f22 --- /dev/null +++ b/sale_packaging_default/models/product_packaging.py @@ -0,0 +1,31 @@ +# Copyright 2023 Moduon Team S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) +from odoo import api, fields, models + + +class ProductPackaging(models.Model): + _inherit = "product.packaging" + + sales_default = fields.Boolean( + "Default for sales", + compute="_compute_sales_default", + readonly=False, + store=True, + help=( + "The first packaging with this option checked will be used by " + "default in sales orders." + ), + ) + + @api.depends("sales") + def _compute_sales_default(self): + """Remove from default if packaging is not available for sales.""" + for packaging in self: + if not packaging.sales: + packaging.sales_default = False + + 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..57c482141eb --- /dev/null +++ b/sale_packaging_default/models/sale_order_line.py @@ -0,0 +1,44 @@ +# Copyright 2023 Moduon Team S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) +from odoo import api, models + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + @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 and not line.product_packaging_id: + line.product_packaging_id = ( + line.product_id.packaging_ids.filtered_domain( + [("sales_default", "=", True), ("sales", "=", True)] + )[:1] + ) + return super()._compute_product_packaging_id() + + @api.depends("product_packaging_id", "product_uom", "product_uom_qty") + def _compute_product_packaging_qty(self): + """Set a valid packaging quantity.""" + 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 = 1 + 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) + return super(SaleOrderLine, _self)._compute_product_uom_qty() diff --git a/sale_packaging_default/readme/CONFIGURE.rst b/sale_packaging_default/readme/CONFIGURE.rst new file mode 100644 index 00000000000..f5df4863900 --- /dev/null +++ b/sale_packaging_default/readme/CONFIGURE.rst @@ -0,0 +1,4 @@ +To configure this module, you need to: + +#. Go to *Sales > Configuration > Settings*. +#. Under *Product Catalog*, enable *Product Packagings*. diff --git a/sale_packaging_default/readme/CONTRIBUTORS.rst b/sale_packaging_default/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..0b01e64914c --- /dev/null +++ b/sale_packaging_default/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Jairo Llopis (`Moduon `__) diff --git a/sale_packaging_default/readme/DESCRIPTION.rst b/sale_packaging_default/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..3a83b6bcee8 --- /dev/null +++ b/sale_packaging_default/readme/DESCRIPTION.rst @@ -0,0 +1,10 @@ +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. + +Why is this module useful? Packaging usage in Odoo is designed to be computed +automatically when you choose the final amount of products to sell. 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. diff --git a/sale_packaging_default/readme/USAGE.rst b/sale_packaging_default/readme/USAGE.rst new file mode 100644 index 00000000000..45a7e220115 --- /dev/null +++ b/sale_packaging_default/readme/USAGE.rst @@ -0,0 +1,14 @@ +To use this module, you need to configure a product with packaging: + +#. Go to *Sales > Products > Products* and select or create a product. +#. In the *Inventory* tab, add some packaging(s). +#. Enable *Sales* and *Default for sales* in one packaging. + +Then you have to sell it: + +#. Go to *Sales > Orders > Quotations* and create a new quotation. +#. Select any customer. +#. Add the product with packaging. + +You will notice that the product is added with the default sale packaging, and +the quantity is set to 1 packaging unit. diff --git a/sale_packaging_default/static/description/icon.png b/sale_packaging_default/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/sale_packaging_default/static/description/index.html b/sale_packaging_default/static/description/index.html new file mode 100644 index 00000000000..1659d680fa1 --- /dev/null +++ b/sale_packaging_default/static/description/index.html @@ -0,0 +1,465 @@ + + + + + + +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.
  • +
+

Why is this module useful? Packaging usage in Odoo is designed to be computed +automatically when you choose the final amount of products to sell. 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.

+
+

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

+ +
+

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 Default for sales in one packaging.
  6. +
+

Then you have to sell it:

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

You will notice that the product is added with the default sale packaging, and +the quantity is set to 1 packaging unit.

+
+
+

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..fabc2cd2cb7 --- /dev/null +++ b/sale_packaging_default/tests/test_sale_packaging_default.py @@ -0,0 +1,85 @@ +# Copyright 2023 Moduon Team S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) +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.sequence = 10 + packaging_f.qty = 12 + packaging_f.sales = True + with product_f.packaging_ids.new() as packaging_f: + packaging_f.name = "Big box" + packaging_f.sequence = 20 + packaging_f.qty = 100 + packaging_f.sales = True + packaging_f.sales_default = True + cls.dozen, cls.big_box = cls.product.packaging_ids + + def test_packaging_sales_default_automation(self): + """Sales default should be only for a packaging available for sale.""" + with Form(self.product) as product_f: + # User creates a new packaging for the product + with product_f.packaging_ids.new() as packaging_f: + packaging_f.name = "Pallet" + packaging_f.qty = 1000 + # It is not available for sale + packaging_f.sales = False + # So, sales_default is invisible + with self.assertRaises( + AssertionError, msg="can't write on invisible field sales_default" + ): + packaging_f.sales_default = True + # But, if we make it available for sale, sales_default is visible + packaging_f.sales = True + packaging_f.sales_default = True + # And, when not saleable, it is removed from default + packaging_f.sales = False + packaging_f.sales = True + self.assertFalse(packaging_f.sales_default) + + 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) diff --git a/sale_packaging_default/views/product_packaging_view.xml b/sale_packaging_default/views/product_packaging_view.xml new file mode 100644 index 00000000000..36a4d09bbb1 --- /dev/null +++ b/sale_packaging_default/views/product_packaging_view.xml @@ -0,0 +1,32 @@ + + + + + Default for sales field + product.packaging + + + + + + + + + + Default for sales field + product.packaging + + + + + + + + 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..bfa1d8b5124 --- /dev/null +++ b/sale_packaging_default/views/sale_order_view.xml @@ -0,0 +1,25 @@ + + + + + Simplify packaging fields + sale.order + + + + + + + + + From 16b26adfa508b551e3d524c740f2c01fe9d986d8 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Thu, 31 Aug 2023 07:50:00 +0000 Subject: [PATCH 02/25] [UPD] Update sale_packaging_default.pot --- .../i18n/sale_packaging_default.pot | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 sale_packaging_default/i18n/sale_packaging_default.pot 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..503b0379a64 --- /dev/null +++ b/sale_packaging_default/i18n/sale_packaging_default.pot @@ -0,0 +1,36 @@ +# 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.fields,field_description:sale_packaging_default.field_product_packaging__sales_default +msgid "Default for sales" +msgstr "" + +#. 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 "" + +#. module: sale_packaging_default +#: model:ir.model.fields,help:sale_packaging_default.field_product_packaging__sales_default +msgid "" +"The first packaging with this option checked will be used by default in " +"sales orders." +msgstr "" From 086766d99f2455fba56580bb7b571fea499f1b56 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Thu, 31 Aug 2023 07:57:04 +0000 Subject: [PATCH 03/25] [UPD] README.rst --- sale_packaging_default/README.rst | 15 ++++++--------- .../static/description/index.html | 8 +++----- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/sale_packaging_default/README.rst b/sale_packaging_default/README.rst index 9e00fdbb739..7d62cba617a 100644 --- a/sale_packaging_default/README.rst +++ b/sale_packaging_default/README.rst @@ -2,13 +2,10 @@ Default packaging for sales =========================== -.. - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:e9592b6ceb68a0f67cbf51c3cc8f47585c8626ba43a4c0dd76adcd053340bcdd - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png :target: https://odoo-community.org/page/development-status @@ -22,11 +19,11 @@ Default packaging for sales .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png :target: https://translation.odoo-community.org/projects/sale-workflow-16-0/sale-workflow-16-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/webui/builds.html?repo=OCA/sale-workflow&target_branch=16.0 - :alt: Try me on Runboat +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/167/16.0 + :alt: Try me on Runbot -|badge1| |badge2| |badge3| |badge4| |badge5| +|badge1| |badge2| |badge3| |badge4| |badge5| This module simplifies and emphasizes the usage of packaging in sales orders. @@ -80,7 +77,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. -If you spotted it first, help us to smash it by providing a detailed and welcomed +If you spotted it first, help us smashing it by providing a detailed and welcomed `feedback `_. Do not contact contributors directly about support or help with technical issues. diff --git a/sale_packaging_default/static/description/index.html b/sale_packaging_default/static/description/index.html index 1659d680fa1..0876b76f107 100644 --- a/sale_packaging_default/static/description/index.html +++ b/sale_packaging_default/static/description/index.html @@ -3,7 +3,7 @@ - + Default packaging for sales