From c5c0a634ff06a8f590034c0ac8c106e5e7b9a6be Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Tue, 26 May 2020 10:40:07 +0200 Subject: [PATCH 01/41] Add stock_packaging_calculator --- stock_packaging_calculator/__init__.py | 1 + stock_packaging_calculator/__manifest__.py | 15 +++++ stock_packaging_calculator/models/__init__.py | 1 + stock_packaging_calculator/models/product.py | 51 ++++++++++++++ .../readme/CONTRIBUTORS.rst | 1 + .../readme/DESCRIPTION.rst | 18 +++++ stock_packaging_calculator/readme/ROADMAP.rst | 4 ++ stock_packaging_calculator/tests/__init__.py | 1 + .../tests/test_packaging_calc.py | 66 +++++++++++++++++++ 9 files changed, 158 insertions(+) create mode 100644 stock_packaging_calculator/__init__.py create mode 100644 stock_packaging_calculator/__manifest__.py create mode 100644 stock_packaging_calculator/models/__init__.py create mode 100644 stock_packaging_calculator/models/product.py create mode 100644 stock_packaging_calculator/readme/CONTRIBUTORS.rst create mode 100644 stock_packaging_calculator/readme/DESCRIPTION.rst create mode 100644 stock_packaging_calculator/readme/ROADMAP.rst create mode 100644 stock_packaging_calculator/tests/__init__.py create mode 100644 stock_packaging_calculator/tests/test_packaging_calc.py diff --git a/stock_packaging_calculator/__init__.py b/stock_packaging_calculator/__init__.py new file mode 100644 index 000000000000..0650744f6bc6 --- /dev/null +++ b/stock_packaging_calculator/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/stock_packaging_calculator/__manifest__.py b/stock_packaging_calculator/__manifest__.py new file mode 100644 index 000000000000..4399bba90079 --- /dev/null +++ b/stock_packaging_calculator/__manifest__.py @@ -0,0 +1,15 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +{ + "name": "Stock packaging calculator", + "summary": "Compute product quantity to pick by packaging", + "version": "13.0.1.0.0", + "development_status": "Alpha", + "category": "Warehouse Management", + "website": "https://github.com/OCA/stock-logistics-warehouse", + "author": "Camptocamp, Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": ["product"], +} diff --git a/stock_packaging_calculator/models/__init__.py b/stock_packaging_calculator/models/__init__.py new file mode 100644 index 000000000000..9649db77a159 --- /dev/null +++ b/stock_packaging_calculator/models/__init__.py @@ -0,0 +1 @@ +from . import product diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py new file mode 100644 index 000000000000..5dd73bac390f --- /dev/null +++ b/stock_packaging_calculator/models/product.py @@ -0,0 +1,51 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo import models +from odoo.tools import float_compare + + +class Product(models.Model): + _inherit = "product.product" + + def product_qty_by_packaging(self, prod_qty, min_unit=None): + """Calculate quantity by packaging. + + Limitation: fractional quantities are lost. + + :prod_qty: total qty to satisfy. + :min_unit: minimal unit of measure as a tuple (qty, name). + Default: to UoM unit. + :returns: list of tuple in the form [(qty_per_package, package_name)] + + """ + packagings = [(x.qty, x.name) for x in self.packaging_ids] + if min_unit is None: + # You can pass `False` to skip it. + single_unit = self.uom_id + min_unit = (single_unit.factor, single_unit.name) + if min_unit: + packagings.append(min_unit) + return self._product_qty_by_packaging( + sorted(packagings, reverse=True), prod_qty + ) + + def _product_qty_by_packaging(self, pkg_by_qty, qty): + """Produce a list of tuple of packaging qty and packaging name.""" + # TODO: refactor to handle fractional quantities (eg: 0.5 Kg) + res = [] + for pkg_qty, pkg in pkg_by_qty: + qty_per_pkg, qty = self._qty_by_pkg(pkg_qty, qty) + if qty_per_pkg: + res.append((qty_per_pkg, pkg)) + if not qty: + break + return res + + def _qty_by_pkg(self, pkg_qty, qty): + """Calculate qty needed for given package qty.""" + qty_per_pkg = 0 + while float_compare(qty - pkg_qty, 0.0, precision_digits=3) >= 0.0: + qty -= pkg_qty + qty_per_pkg += 1 + return qty_per_pkg, qty diff --git a/stock_packaging_calculator/readme/CONTRIBUTORS.rst b/stock_packaging_calculator/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..f583948be842 --- /dev/null +++ b/stock_packaging_calculator/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Simone Orsi diff --git a/stock_packaging_calculator/readme/DESCRIPTION.rst b/stock_packaging_calculator/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..b470f8a94da0 --- /dev/null +++ b/stock_packaging_calculator/readme/DESCRIPTION.rst @@ -0,0 +1,18 @@ + +Basic module providing an helper method to calculate the quantity of product by packaging. + +Imagine you have the following packagings: + +* Pallet: 1000 Units +* Big box: 500 Units +* Box: 50 Units + +and you have to pick from your warehouse 2860 Units. + +Then you can do: + + >>> product.product_qty_by_packaging(2860) + + [(2, "Pallet"), (1, "Big Box"), (7, "Box"), (10, "Units")] + +With this you can show a proper message to warehouse operators to quickly pick the quantity they need. diff --git a/stock_packaging_calculator/readme/ROADMAP.rst b/stock_packaging_calculator/readme/ROADMAP.rst new file mode 100644 index 000000000000..e4881d5e69a8 --- /dev/null +++ b/stock_packaging_calculator/readme/ROADMAP.rst @@ -0,0 +1,4 @@ +TODO + +1. Fractional quantities (eg: 0.5 Kg) are lost when counting units +2. Maybe rely on `packaging_uom` diff --git a/stock_packaging_calculator/tests/__init__.py b/stock_packaging_calculator/tests/__init__.py new file mode 100644 index 000000000000..04c47e71156e --- /dev/null +++ b/stock_packaging_calculator/tests/__init__.py @@ -0,0 +1 @@ +from . import test_packaging_calc diff --git a/stock_packaging_calculator/tests/test_packaging_calc.py b/stock_packaging_calculator/tests/test_packaging_calc.py new file mode 100644 index 000000000000..c5836b941c1c --- /dev/null +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -0,0 +1,66 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo.tests import SavepointCase + + +class TestCalc(SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls.uom_unit = cls.env.ref("uom.product_uom_unit") + cls.uom_kg = cls.env.ref("uom.product_uom_kgm") + cls.product_a = cls.env["product.product"].create( + { + "name": "Product A", + "type": "product", + "uom_id": cls.uom_unit.id, + "uom_po_id": cls.uom_unit.id, + } + ) + cls.pkg_box = cls.env["product.packaging"].create( + {"name": "Box", "product_id": cls.product_a.id, "qty": 50} + ) + cls.pkg_big_box = cls.env["product.packaging"].create( + {"name": "Big Box", "product_id": cls.product_a.id, "qty": 200} + ) + cls.pkg_pallet = cls.env["product.packaging"].create( + {"name": "Pallet", "product_id": cls.product_a.id, "qty": 2000} + ) + + def test_calc_1(self): + """Test easy behavior 1.""" + self.assertEqual( + self.product_a.product_qty_by_packaging(2655), + [(1, "Pallet"), (3, "Big Box"), (1, "Box"), (5, self.uom_unit.name)], + ) + + def test_calc_2(self): + """Test easy behavior 2.""" + self.assertEqual( + self.product_a.product_qty_by_packaging(350), [(1, "Big Box"), (3, "Box")] + ) + + def test_calc_3(self): + """Test easy behavior 3.""" + self.assertEqual( + self.product_a.product_qty_by_packaging(80), + [(1, "Box"), (30, self.uom_unit.name)], + ) + + def test_calc_4(self): + """Test minimal unit override.""" + self.assertEqual( + self.product_a.product_qty_by_packaging(80, min_unit=(5, "Pack 5")), + [(1, "Box"), (6, "Pack 5")], + ) + + def test_calc_5(self): + """Test no minimal unit.""" + self.assertEqual( + self.product_a.product_qty_by_packaging(80, min_unit=False), [(1, "Box")] + ) + + def test_calc_6(self): + """Test fractional qty is lost.""" + self.assertEqual(self.product_a.product_qty_by_packaging(50.5), [(1, "Box")]) From d893e454971e787b332509e6ce657f58f4cb04a3 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 8 Jun 2020 09:56:12 +0200 Subject: [PATCH 02/41] stock_packaging_calculator: make product uom the minimal unit Customizing the minimal unit was not needed at all. This way we always assume the precision is the on of the UoM. --- stock_packaging_calculator/models/product.py | 20 +++++++++---------- .../tests/test_packaging_calc.py | 13 ------------ 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index 5dd73bac390f..68907d2a4911 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -8,24 +8,19 @@ class Product(models.Model): _inherit = "product.product" - def product_qty_by_packaging(self, prod_qty, min_unit=None): + def product_qty_by_packaging(self, prod_qty): """Calculate quantity by packaging. + The minimal quantity is always represented by the UoM of the product. + Limitation: fractional quantities are lost. :prod_qty: total qty to satisfy. - :min_unit: minimal unit of measure as a tuple (qty, name). - Default: to UoM unit. :returns: list of tuple in the form [(qty_per_package, package_name)] - """ packagings = [(x.qty, x.name) for x in self.packaging_ids] - if min_unit is None: - # You can pass `False` to skip it. - single_unit = self.uom_id - min_unit = (single_unit.factor, single_unit.name) - if min_unit: - packagings.append(min_unit) + # Add minimal unit + packagings.append((self.uom_id.factor, self.uom_id.name)) return self._product_qty_by_packaging( sorted(packagings, reverse=True), prod_qty ) @@ -45,7 +40,10 @@ def _product_qty_by_packaging(self, pkg_by_qty, qty): def _qty_by_pkg(self, pkg_qty, qty): """Calculate qty needed for given package qty.""" qty_per_pkg = 0 - while float_compare(qty - pkg_qty, 0.0, precision_digits=3) >= 0.0: + while ( + float_compare(qty - pkg_qty, 0.0, precision_digits=self.uom_id.rounding) + >= 0.0 + ): qty -= pkg_qty qty_per_pkg += 1 return qty_per_pkg, qty diff --git a/stock_packaging_calculator/tests/test_packaging_calc.py b/stock_packaging_calculator/tests/test_packaging_calc.py index c5836b941c1c..af1c11541002 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -48,19 +48,6 @@ def test_calc_3(self): [(1, "Box"), (30, self.uom_unit.name)], ) - def test_calc_4(self): - """Test minimal unit override.""" - self.assertEqual( - self.product_a.product_qty_by_packaging(80, min_unit=(5, "Pack 5")), - [(1, "Box"), (6, "Pack 5")], - ) - - def test_calc_5(self): - """Test no minimal unit.""" - self.assertEqual( - self.product_a.product_qty_by_packaging(80, min_unit=False), [(1, "Box")] - ) - def test_calc_6(self): """Test fractional qty is lost.""" self.assertEqual(self.product_a.product_qty_by_packaging(50.5), [(1, "Box")]) From 97ea8068dafa097a77d25abafde433b676dae989 Mon Sep 17 00:00:00 2001 From: oca-travis Date: Mon, 8 Jun 2020 08:00:41 +0000 Subject: [PATCH 03/41] [UPD] Update stock_packaging_calculator.pot --- .../i18n/stock_packaging_calculator.pot | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 stock_packaging_calculator/i18n/stock_packaging_calculator.pot diff --git a/stock_packaging_calculator/i18n/stock_packaging_calculator.pot b/stock_packaging_calculator/i18n/stock_packaging_calculator.pot new file mode 100644 index 000000000000..775124a33c40 --- /dev/null +++ b/stock_packaging_calculator/i18n/stock_packaging_calculator.pot @@ -0,0 +1,19 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_packaging_calculator +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.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: stock_packaging_calculator +#: model:ir.model,name:stock_packaging_calculator.model_product_product +msgid "Product" +msgstr "" From e0249f93c57b7f34301d6a479aaa69530df755be Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 8 Jun 2020 10:18:02 +0200 Subject: [PATCH 04/41] stock_packaging_calculator: return dict instead of tuple Allows to ship more information with each element in the list. --- stock_packaging_calculator/models/product.py | 27 ++++++++++----- .../tests/test_packaging_calc.py | 33 ++++++++++++------- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index 68907d2a4911..d5dca58e14b3 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -1,9 +1,14 @@ # Copyright 2020 Camptocamp SA # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from collections import namedtuple + from odoo import models from odoo.tools import float_compare +# Unify records as we mix up w/ UoM +Packaging = namedtuple("Packaging", "id name qty") + class Product(models.Model): _inherit = "product.product" @@ -16,23 +21,29 @@ def product_qty_by_packaging(self, prod_qty): Limitation: fractional quantities are lost. :prod_qty: total qty to satisfy. - :returns: list of tuple in the form [(qty_per_package, package_name)] + :with_subpackaging: include calculation of contained packagings. + eg: 1 pallet contains 4 big boxes and 6 little boxes. + :returns: list of dict in the form + [{id: 1, qty: qty_per_package, name: package_name}] """ - packagings = [(x.qty, x.name) for x in self.packaging_ids] + packagings = [Packaging(x.id, x.name, x.qty) for x in self.packaging_ids] # Add minimal unit - packagings.append((self.uom_id.factor, self.uom_id.name)) + packagings.append( + Packaging(self.uom_id.id, self.uom_id.name, self.uom_id.factor) + ) return self._product_qty_by_packaging( - sorted(packagings, reverse=True), prod_qty + sorted(packagings, reverse=True, key=lambda x: x.qty), prod_qty, ) def _product_qty_by_packaging(self, pkg_by_qty, qty): - """Produce a list of tuple of packaging qty and packaging name.""" + """Produce a list of dictionaries of packaging info.""" # TODO: refactor to handle fractional quantities (eg: 0.5 Kg) res = [] - for pkg_qty, pkg in pkg_by_qty: - qty_per_pkg, qty = self._qty_by_pkg(pkg_qty, qty) + for pkg in pkg_by_qty: + qty_per_pkg, qty = self._qty_by_pkg(pkg.qty, qty) if qty_per_pkg: - res.append((qty_per_pkg, pkg)) + value = {"id": pkg.id, "qty": qty_per_pkg, "name": pkg.name} + res.append(value) if not qty: break return res diff --git a/stock_packaging_calculator/tests/test_packaging_calc.py b/stock_packaging_calculator/tests/test_packaging_calc.py index af1c11541002..b25d8cb9cb73 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -30,24 +30,33 @@ def setUpClass(cls): def test_calc_1(self): """Test easy behavior 1.""" - self.assertEqual( - self.product_a.product_qty_by_packaging(2655), - [(1, "Pallet"), (3, "Big Box"), (1, "Box"), (5, self.uom_unit.name)], - ) + expected = [ + {"id": self.pkg_pallet.id, "qty": 1, "name": self.pkg_pallet.name}, + {"id": self.pkg_big_box.id, "qty": 3, "name": self.pkg_big_box.name}, + {"id": self.pkg_box.id, "qty": 1, "name": self.pkg_box.name}, + {"id": self.uom_unit.id, "qty": 5, "name": self.uom_unit.name}, + ] + self.assertEqual(self.product_a.product_qty_by_packaging(2655), expected) def test_calc_2(self): """Test easy behavior 2.""" - self.assertEqual( - self.product_a.product_qty_by_packaging(350), [(1, "Big Box"), (3, "Box")] - ) + expected = [ + {"id": self.pkg_big_box.id, "qty": 1, "name": self.pkg_big_box.name}, + {"id": self.pkg_box.id, "qty": 3, "name": self.pkg_box.name}, + ] + self.assertEqual(self.product_a.product_qty_by_packaging(350), expected) def test_calc_3(self): """Test easy behavior 3.""" - self.assertEqual( - self.product_a.product_qty_by_packaging(80), - [(1, "Box"), (30, self.uom_unit.name)], - ) + expected = [ + {"id": self.pkg_box.id, "qty": 1, "name": self.pkg_box.name}, + {"id": self.uom_unit.id, "qty": 30, "name": self.uom_unit.name}, + ] + self.assertEqual(self.product_a.product_qty_by_packaging(80), expected) def test_calc_6(self): """Test fractional qty is lost.""" - self.assertEqual(self.product_a.product_qty_by_packaging(50.5), [(1, "Box")]) + expected = [ + {"id": self.pkg_box.id, "qty": 1, "name": self.pkg_box.name}, + ] + self.assertEqual(self.product_a.product_qty_by_packaging(50.5), expected) From 9b48b273dd0ccbd5fa455caac5e108e4f1049b11 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Mon, 8 Jun 2020 08:25:57 +0000 Subject: [PATCH 05/41] [UPD] README.rst --- stock_packaging_calculator/README.rst | 103 ++++ .../static/description/index.html | 449 ++++++++++++++++++ 2 files changed, 552 insertions(+) create mode 100644 stock_packaging_calculator/README.rst create mode 100644 stock_packaging_calculator/static/description/index.html diff --git a/stock_packaging_calculator/README.rst b/stock_packaging_calculator/README.rst new file mode 100644 index 000000000000..54e81badcbde --- /dev/null +++ b/stock_packaging_calculator/README.rst @@ -0,0 +1,103 @@ +========================== +Stock packaging calculator +========================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-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%2Fstock--logistics--warehouse-lightgray.png?logo=github + :target: https://github.com/OCA/stock-logistics-warehouse/tree/13.0/stock_packaging_calculator + :alt: OCA/stock-logistics-warehouse +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-13-0/stock-logistics-warehouse-13-0-stock_packaging_calculator + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/153/13.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + + +Basic module providing an helper method to calculate the quantity of product by packaging. + +Imagine you have the following packagings: + +* Pallet: 1000 Units +* Big box: 500 Units +* Box: 50 Units + +and you have to pick from your warehouse 2860 Units. + +Then you can do: + + >>> product.product_qty_by_packaging(2860) + + [(2, "Pallet"), (1, "Big Box"), (7, "Box"), (10, "Units")] + +With this you can show a proper message to warehouse operators to quickly pick the quantity they need. + +.. 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: + +Known issues / Roadmap +====================== + +TODO + +1. Fractional quantities (eg: 0.5 Kg) are lost when counting units +2. Maybe rely on `packaging_uom` + +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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Camptocamp + +Contributors +~~~~~~~~~~~~ + +* Simone Orsi + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/stock-logistics-warehouse `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_packaging_calculator/static/description/index.html b/stock_packaging_calculator/static/description/index.html new file mode 100644 index 000000000000..b786d10eabdc --- /dev/null +++ b/stock_packaging_calculator/static/description/index.html @@ -0,0 +1,449 @@ + + + + + + +Stock packaging calculator + + + +
+

Stock packaging calculator

+ + +

Alpha License: AGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runbot

+

Basic module providing an helper method to calculate the quantity of product by packaging.

+

Imagine you have the following packagings:

+
    +
  • Pallet: 1000 Units
  • +
  • Big box: 500 Units
  • +
  • Box: 50 Units
  • +
+

and you have to pick from your warehouse 2860 Units.

+

Then you can do:

+
+
+>>> product.product_qty_by_packaging(2860)
+
+

[(2, “Pallet”), (1, “Big Box”), (7, “Box”), (10, “Units”)]

+
+

With this you can show a proper message to warehouse operators to quickly pick the quantity they need.

+
+

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

+ +
+

Known issues / Roadmap

+

TODO

+
    +
  1. Fractional quantities (eg: 0.5 Kg) are lost when counting units
  2. +
  3. Maybe rely on packaging_uom
  4. +
+
+
+

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 smashing it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Camptocamp
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/stock-logistics-warehouse project on GitHub.

+

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

+
+
+
+ + From 4dd8a45ab32fc868c881518145d8078481f10e11 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Mon, 8 Jun 2020 08:25:58 +0000 Subject: [PATCH 06/41] [ADD] icon.png --- .../static/description/icon.png | Bin 0 -> 9455 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 stock_packaging_calculator/static/description/icon.png diff --git a/stock_packaging_calculator/static/description/icon.png b/stock_packaging_calculator/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 From 93bf5e3bc6b0d8028840d7947955391575f71b41 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 8 Jun 2020 11:47:32 +0200 Subject: [PATCH 07/41] stock_packaging_calculator: add contained packaging compute Optionally include contained packaging qty. --- stock_packaging_calculator/models/product.py | 26 ++++-- .../readme/DESCRIPTION.rst | 17 ---- stock_packaging_calculator/readme/USAGE.rst | 36 ++++++++ .../tests/test_packaging_calc.py | 89 ++++++++++++++++++- 4 files changed, 145 insertions(+), 23 deletions(-) create mode 100644 stock_packaging_calculator/readme/USAGE.rst diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index d5dca58e14b3..2679b0fd6fc8 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -13,7 +13,7 @@ class Product(models.Model): _inherit = "product.product" - def product_qty_by_packaging(self, prod_qty): + def product_qty_by_packaging(self, prod_qty, with_contained=False): """Calculate quantity by packaging. The minimal quantity is always represented by the UoM of the product. @@ -21,28 +21,44 @@ def product_qty_by_packaging(self, prod_qty): Limitation: fractional quantities are lost. :prod_qty: total qty to satisfy. - :with_subpackaging: include calculation of contained packagings. + :with_contained: include calculation of contained packagings. + eg: 1 pallet contains 4 big boxes and 6 little boxes. + :returns: list of dict in the form + [{id: 1, qty: qty_per_package, name: package_name}] + + If `with_contained` is passed, each element will include + the quantity of smaller packaging, like: + + {contained: [{id: 1, qty: 4, name: "Big box"}]} """ packagings = [Packaging(x.id, x.name, x.qty) for x in self.packaging_ids] # Add minimal unit packagings.append( + # NOTE: the ID here could clash w/ one of the packaging's. + # If you create a mapping based on IDs, keep this in mind. Packaging(self.uom_id.id, self.uom_id.name, self.uom_id.factor) ) return self._product_qty_by_packaging( - sorted(packagings, reverse=True, key=lambda x: x.qty), prod_qty, + sorted(packagings, reverse=True, key=lambda x: x.qty), + prod_qty, + with_contained=with_contained, ) - def _product_qty_by_packaging(self, pkg_by_qty, qty): + def _product_qty_by_packaging(self, pkg_by_qty, qty, with_contained=False): """Produce a list of dictionaries of packaging info.""" # TODO: refactor to handle fractional quantities (eg: 0.5 Kg) res = [] - for pkg in pkg_by_qty: + for i, pkg in enumerate(pkg_by_qty): qty_per_pkg, qty = self._qty_by_pkg(pkg.qty, qty) if qty_per_pkg: value = {"id": pkg.id, "qty": qty_per_pkg, "name": pkg.name} + if with_contained: + value["contained"] = self._product_qty_by_packaging( + pkg_by_qty[i + 1 :], pkg.qty + ) res.append(value) if not qty: break diff --git a/stock_packaging_calculator/readme/DESCRIPTION.rst b/stock_packaging_calculator/readme/DESCRIPTION.rst index b470f8a94da0..1d6fceb5c7c4 100644 --- a/stock_packaging_calculator/readme/DESCRIPTION.rst +++ b/stock_packaging_calculator/readme/DESCRIPTION.rst @@ -1,18 +1 @@ - Basic module providing an helper method to calculate the quantity of product by packaging. - -Imagine you have the following packagings: - -* Pallet: 1000 Units -* Big box: 500 Units -* Box: 50 Units - -and you have to pick from your warehouse 2860 Units. - -Then you can do: - - >>> product.product_qty_by_packaging(2860) - - [(2, "Pallet"), (1, "Big Box"), (7, "Box"), (10, "Units")] - -With this you can show a proper message to warehouse operators to quickly pick the quantity they need. diff --git a/stock_packaging_calculator/readme/USAGE.rst b/stock_packaging_calculator/readme/USAGE.rst new file mode 100644 index 000000000000..73f05a708227 --- /dev/null +++ b/stock_packaging_calculator/readme/USAGE.rst @@ -0,0 +1,36 @@ +Imagine you have the following packagings: + +* Pallet: 1000 Units +* Big box: 500 Units +* Box: 50 Units + +and you have to pick from your warehouse 2860 Units. + +Then you can do: + + .. code-block:: + + >>> product.product_qty_by_packaging(2860) + + [ + {"id": 1, "qty": 2, "name": "Pallet"}, + {"id": 2, "qty": 1, "name": "Big box"}, + {"id": 3, "qty": 7, "name": "Box"}, + {"id": 100, "qty": 10, "name": "Units"}, + ] + +With this you can show a proper message to warehouse operators to quickly pick the quantity they need. + +Optionally you can get contained packaging by passing `with_contained` flag: + + + .. code-block:: + + >>> product.product_qty_by_packaging(2860, with_contained=True) + + [ + {"id": 1, "qty": 2, "name": "Pallet", "contained": [{"id": 2, "qty": 2, "name": "Big box"}]}, + {"id": 2, "qty": 1, "name": "Big box", "contained": [{"id": 3, "qty": 10, "name": "Box"}]}, + {"id": 3, "qty": 7, "name": "Box", "contained": [{"id": 100, "qty": 50, "name": "Units"}]}, + {"id": 100, "qty": 10, "name": "Units", "contained": []},}, + ] diff --git a/stock_packaging_calculator/tests/test_packaging_calc.py b/stock_packaging_calculator/tests/test_packaging_calc.py index b25d8cb9cb73..39d03e01bbd8 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -9,7 +9,6 @@ def setUpClass(cls): super().setUpClass() cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) cls.uom_unit = cls.env.ref("uom.product_uom_unit") - cls.uom_kg = cls.env.ref("uom.product_uom_kgm") cls.product_a = cls.env["product.product"].create( { "name": "Product A", @@ -60,3 +59,91 @@ def test_calc_6(self): {"id": self.pkg_box.id, "qty": 1, "name": self.pkg_box.name}, ] self.assertEqual(self.product_a.product_qty_by_packaging(50.5), expected) + + def test_calc_sub1(self): + """Test contained packaging behavior 1.""" + expected = [ + { + "id": self.pkg_pallet.id, + "qty": 1, + "name": self.pkg_pallet.name, + "contained": [ + { + "id": self.pkg_big_box.id, + "qty": 10, + "name": self.pkg_big_box.name, + }, + ], + }, + { + "id": self.pkg_big_box.id, + "qty": 3, + "name": self.pkg_big_box.name, + "contained": [ + {"id": self.pkg_box.id, "qty": 4, "name": self.pkg_box.name}, + ], + }, + { + "id": self.pkg_box.id, + "qty": 1, + "name": self.pkg_box.name, + "contained": [ + {"id": self.uom_unit.id, "qty": 50, "name": self.uom_unit.name}, + ], + }, + { + "id": self.uom_unit.id, + "qty": 5, + "name": self.uom_unit.name, + "contained": [], + }, + ] + self.assertEqual( + self.product_a.product_qty_by_packaging(2655, with_contained=True), + expected, + ) + + def test_calc_sub2(self): + """Test contained packaging behavior 1.""" + self.pkg_box.qty = 30 + expected = [ + { + "id": self.pkg_pallet.id, + "qty": 1, + "name": self.pkg_pallet.name, + "contained": [ + { + "id": self.pkg_big_box.id, + "qty": 10, + "name": self.pkg_big_box.name, + }, + ], + }, + { + "id": self.pkg_big_box.id, + "qty": 3, + "name": self.pkg_big_box.name, + "contained": [ + {"id": self.pkg_box.id, "qty": 6, "name": self.pkg_box.name}, + {"id": self.uom_unit.id, "qty": 20, "name": self.uom_unit.name}, + ], + }, + { + "id": self.pkg_box.id, + "qty": 1, + "name": self.pkg_box.name, + "contained": [ + {"id": self.uom_unit.id, "qty": 30, "name": self.uom_unit.name}, + ], + }, + { + "id": self.uom_unit.id, + "qty": 25, + "name": self.uom_unit.name, + "contained": [], + }, + ] + self.assertEqual( + self.product_a.product_qty_by_packaging(2655, with_contained=True), + expected, + ) From 7539376001d53e4c632333114d02182c1feaac0f Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 9 Jun 2020 08:09:46 +0000 Subject: [PATCH 08/41] [UPD] README.rst --- stock_packaging_calculator/README.rst | 43 ++++++++--- .../static/description/index.html | 75 ++++++++++++------- 2 files changed, 82 insertions(+), 36 deletions(-) diff --git a/stock_packaging_calculator/README.rst b/stock_packaging_calculator/README.rst index 54e81badcbde..5bde39e25d73 100644 --- a/stock_packaging_calculator/README.rst +++ b/stock_packaging_calculator/README.rst @@ -25,9 +25,21 @@ Stock packaging calculator |badge1| |badge2| |badge3| |badge4| |badge5| - Basic module providing an helper method to calculate the quantity of product by packaging. +.. 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: + +Usage +===== + Imagine you have the following packagings: * Pallet: 1000 Units @@ -38,21 +50,32 @@ and you have to pick from your warehouse 2860 Units. Then you can do: - >>> product.product_qty_by_packaging(2860) + .. code-block:: + + >>> product.product_qty_by_packaging(2860) - [(2, "Pallet"), (1, "Big Box"), (7, "Box"), (10, "Units")] + [ + {"id": 1, "qty": 2, "name": "Pallet"}, + {"id": 2, "qty": 1, "name": "Big box"}, + {"id": 3, "qty": 7, "name": "Box"}, + {"id": 100, "qty": 10, "name": "Units"}, + ] With this you can show a proper message to warehouse operators to quickly pick the quantity they need. -.. 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 `_ +Optionally you can get contained packaging by passing `with_contained` flag: -**Table of contents** -.. contents:: - :local: + .. code-block:: + + >>> product.product_qty_by_packaging(2860, with_contained=True) + + [ + {"id": 1, "qty": 2, "name": "Pallet", "contained": [{"id": 2, "qty": 2, "name": "Big box"}]}, + {"id": 2, "qty": 1, "name": "Big box", "contained": [{"id": 3, "qty": 10, "name": "Box"}]}, + {"id": 3, "qty": 7, "name": "Box", "contained": [{"id": 100, "qty": 50, "name": "Units"}]}, + {"id": 100, "qty": 10, "name": "Units", "contained": []},}, + ] Known issues / Roadmap ====================== diff --git a/stock_packaging_calculator/static/description/index.html b/stock_packaging_calculator/static/description/index.html index b786d10eabdc..62793c551a94 100644 --- a/stock_packaging_calculator/static/description/index.html +++ b/stock_packaging_calculator/static/description/index.html @@ -369,6 +369,28 @@

Stock packaging calculator

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Alpha License: AGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runbot

Basic module providing an helper method to calculate the quantity of product by packaging.

+
+

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

+ +
+

Usage

Imagine you have the following packagings:

  • Pallet: 1000 Units
  • @@ -378,33 +400,34 @@

    Stock packaging calculator

    and you have to pick from your warehouse 2860 Units.

    Then you can do:

    -
    +
     >>> product.product_qty_by_packaging(2860)
    +
    +[
    +    {"id": 1, "qty": 2, "name": "Pallet"},
    +    {"id": 2, "qty": 1, "name": "Big box"},
    +    {"id": 3, "qty": 7, "name": "Box"},
    +    {"id": 100, "qty": 10, "name": "Units"},
    +]
     
    -

    [(2, “Pallet”), (1, “Big Box”), (7, “Box”), (10, “Units”)]

    With this you can show a proper message to warehouse operators to quickly pick the quantity they need.

    -
    -

    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

    -
    - +

    Optionally you can get contained packaging by passing with_contained flag:

    +
    +
    +>>> product.product_qty_by_packaging(2860, with_contained=True)
    +
    +[
    +    {"id": 1, "qty": 2, "name": "Pallet", "contained": [{"id": 2, "qty": 2, "name": "Big box"}]},
    +    {"id": 2, "qty": 1, "name": "Big box", "contained": [{"id": 3, "qty": 10, "name": "Box"}]},
    +    {"id": 3, "qty": 7, "name": "Box", "contained": [{"id": 100, "qty": 50, "name": "Units"}]},
    +    {"id": 100, "qty": 10, "name": "Units", "contained": []},},
    +]
    +
    +
    -

    Known issues / Roadmap

    +

    Known issues / Roadmap

    TODO

    1. Fractional quantities (eg: 0.5 Kg) are lost when counting units
    2. @@ -412,7 +435,7 @@

      Known issues / Roadmap

    -

    Bug Tracker

    +

    Bug Tracker

    Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed @@ -420,21 +443,21 @@

    Bug Tracker

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

    -

    Credits

    +

    Credits

    -

    Authors

    +

    Authors

    • Camptocamp
    -

    Maintainers

    +

    Maintainers

    This module is maintained by the OCA.

    Odoo Community Association

    OCA, or the Odoo Community Association, is a nonprofit organization whose From cb0eb5f712fb962d59c4c390ac06bc0ab1026af2 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 9 Jun 2020 08:09:47 +0000 Subject: [PATCH 09/41] stock_packaging_calculator 13.0.1.1.0 --- stock_packaging_calculator/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock_packaging_calculator/__manifest__.py b/stock_packaging_calculator/__manifest__.py index 4399bba90079..731c263b070d 100644 --- a/stock_packaging_calculator/__manifest__.py +++ b/stock_packaging_calculator/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Stock packaging calculator", "summary": "Compute product quantity to pick by packaging", - "version": "13.0.1.0.0", + "version": "13.0.1.1.0", "development_status": "Alpha", "category": "Warehouse Management", "website": "https://github.com/OCA/stock-logistics-warehouse", From e5d336fcd2ce4b58d7f1bfd23c2b8ea3832c5421 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Tue, 9 Jun 2020 10:35:32 +0200 Subject: [PATCH 10/41] stock_packaging_calculator: make contained mapping computed Allows to reuse the mapping every time is needed. --- stock_packaging_calculator/models/product.py | 66 +++++++++++++++---- .../tests/test_packaging_calc.py | 49 +++++++++++++- 2 files changed, 99 insertions(+), 16 deletions(-) diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index 2679b0fd6fc8..4d74f56521f0 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -3,16 +3,45 @@ from collections import namedtuple -from odoo import models +from odoo import api, models from odoo.tools import float_compare +from odoo.addons.base_sparse_field.models.fields import Serialized + # Unify records as we mix up w/ UoM -Packaging = namedtuple("Packaging", "id name qty") +Packaging = namedtuple("Packaging", "id name qty is_unit") class Product(models.Model): _inherit = "product.product" + packaging_contained_mapping = Serialized( + compute="_compute_packaging_contained_mapping", + help="Technical field to store contained packaging. ", + ) + + @api.depends("packaging_ids.qty") + def _compute_packaging_contained_mapping(self): + for rec in self: + rec.packaging_contained_mapping = rec._packaging_contained_mapping() + + def _packaging_contained_mapping(self): + """Produce a mapping of packaging and contained packagings. + + Used mainly for `product_qty_by_packaging` but can be used + to display info as you prefer. + + :returns: a dictionary in the form {pkg.id: [contained packages]} + """ + res = {} + packaging = self._ordered_packaging() + for i, pkg in enumerate(packaging): + if pkg.is_unit: + # skip minimal unit + continue + res[pkg.id] = self._product_qty_by_packaging(packaging[i + 1 :], pkg.qty) + return res + def product_qty_by_packaging(self, prod_qty, with_contained=False): """Calculate quantity by packaging. @@ -34,31 +63,42 @@ def product_qty_by_packaging(self, prod_qty, with_contained=False): {contained: [{id: 1, qty: 4, name: "Big box"}]} """ - packagings = [Packaging(x.id, x.name, x.qty) for x in self.packaging_ids] + return self._product_qty_by_packaging( + self._ordered_packaging(), prod_qty, with_contained=with_contained, + ) + + def _ordered_packaging(self): + """Prepare packaging ordered by qty and exclude empty ones.""" + packagings = [ + Packaging(x.id, x.name, x.qty, False) + for x in self.packaging_ids + # Exclude the ones w/ zero qty as they are useless for the math + if x.qty + ] # Add minimal unit packagings.append( # NOTE: the ID here could clash w/ one of the packaging's. # If you create a mapping based on IDs, keep this in mind. - Packaging(self.uom_id.id, self.uom_id.name, self.uom_id.factor) - ) - return self._product_qty_by_packaging( - sorted(packagings, reverse=True, key=lambda x: x.qty), - prod_qty, - with_contained=with_contained, + # You can use `is_unit` to check this. + Packaging(self.uom_id.id, self.uom_id.name, self.uom_id.factor, True) ) + return sorted(packagings, reverse=True, key=lambda x: x.qty) def _product_qty_by_packaging(self, pkg_by_qty, qty, with_contained=False): """Produce a list of dictionaries of packaging info.""" # TODO: refactor to handle fractional quantities (eg: 0.5 Kg) res = [] - for i, pkg in enumerate(pkg_by_qty): + for pkg in pkg_by_qty: qty_per_pkg, qty = self._qty_by_pkg(pkg.qty, qty) if qty_per_pkg: value = {"id": pkg.id, "qty": qty_per_pkg, "name": pkg.name} if with_contained: - value["contained"] = self._product_qty_by_packaging( - pkg_by_qty[i + 1 :], pkg.qty - ) + contained = None + if not pkg.is_unit: + mapping = self.packaging_contained_mapping + # integer keys are serialized as strings :/ + contained = mapping.get(str(pkg.id)) + value["contained"] = contained res.append(value) if not qty: break diff --git a/stock_packaging_calculator/tests/test_packaging_calc.py b/stock_packaging_calculator/tests/test_packaging_calc.py index 39d03e01bbd8..3482d4329739 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -4,6 +4,10 @@ class TestCalc(SavepointCase): + + at_install = False + post_install = True + @classmethod def setUpClass(cls): super().setUpClass() @@ -12,7 +16,6 @@ def setUpClass(cls): cls.product_a = cls.env["product.product"].create( { "name": "Product A", - "type": "product", "uom_id": cls.uom_unit.id, "uom_po_id": cls.uom_unit.id, } @@ -27,6 +30,46 @@ def setUpClass(cls): {"name": "Pallet", "product_id": cls.product_a.id, "qty": 2000} ) + def test_contained_mapping(self): + self.assertEqual( + self.product_a.packaging_contained_mapping, + { + str(self.pkg_pallet.id): [ + { + "id": self.pkg_big_box.id, + "qty": 10, + "name": self.pkg_big_box.name, + }, + ], + str(self.pkg_big_box.id): [ + {"id": self.pkg_box.id, "qty": 4, "name": self.pkg_box.name}, + ], + str(self.pkg_box.id): [ + {"id": self.uom_unit.id, "qty": 50, "name": self.uom_unit.name}, + ], + }, + ) + # Update pkg qty + self.pkg_pallet.qty = 4000 + self.assertEqual( + self.product_a.packaging_contained_mapping, + { + str(self.pkg_pallet.id): [ + { + "id": self.pkg_big_box.id, + "qty": 20, + "name": self.pkg_big_box.name, + }, + ], + str(self.pkg_big_box.id): [ + {"id": self.pkg_box.id, "qty": 4, "name": self.pkg_box.name}, + ], + str(self.pkg_box.id): [ + {"id": self.uom_unit.id, "qty": 50, "name": self.uom_unit.name}, + ], + }, + ) + def test_calc_1(self): """Test easy behavior 1.""" expected = [ @@ -95,7 +138,7 @@ def test_calc_sub1(self): "id": self.uom_unit.id, "qty": 5, "name": self.uom_unit.name, - "contained": [], + "contained": None, }, ] self.assertEqual( @@ -140,7 +183,7 @@ def test_calc_sub2(self): "id": self.uom_unit.id, "qty": 25, "name": self.uom_unit.name, - "contained": [], + "contained": None, }, ] self.assertEqual( From 1cf5fde2eefb5503fd30a98383601f74aad9ca54 Mon Sep 17 00:00:00 2001 From: oca-travis Date: Mon, 22 Jun 2020 12:42:15 +0000 Subject: [PATCH 11/41] [UPD] Update stock_packaging_calculator.pot --- .../i18n/stock_packaging_calculator.pot | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/stock_packaging_calculator/i18n/stock_packaging_calculator.pot b/stock_packaging_calculator/i18n/stock_packaging_calculator.pot index 775124a33c40..92a274c4a848 100644 --- a/stock_packaging_calculator/i18n/stock_packaging_calculator.pot +++ b/stock_packaging_calculator/i18n/stock_packaging_calculator.pot @@ -13,7 +13,17 @@ msgstr "" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" +#. module: stock_packaging_calculator +#: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_product__packaging_contained_mapping +msgid "Packaging Contained Mapping" +msgstr "" + #. module: stock_packaging_calculator #: model:ir.model,name:stock_packaging_calculator.model_product_product msgid "Product" msgstr "" + +#. module: stock_packaging_calculator +#: model:ir.model.fields,help:stock_packaging_calculator.field_product_product__packaging_contained_mapping +msgid "Technical field to store contained packaging. " +msgstr "" From a1f94a09b383824159cc5a8c92a4d1a569a6e9e9 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Mon, 22 Jun 2020 13:05:22 +0000 Subject: [PATCH 12/41] stock_packaging_calculator 13.0.1.2.0 --- stock_packaging_calculator/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock_packaging_calculator/__manifest__.py b/stock_packaging_calculator/__manifest__.py index 731c263b070d..3285010a50f4 100644 --- a/stock_packaging_calculator/__manifest__.py +++ b/stock_packaging_calculator/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Stock packaging calculator", "summary": "Compute product quantity to pick by packaging", - "version": "13.0.1.1.0", + "version": "13.0.1.2.0", "development_status": "Alpha", "category": "Warehouse Management", "website": "https://github.com/OCA/stock-logistics-warehouse", From a560a7e00ce3c156f83b2f821982034219b4eee3 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Tue, 30 Jun 2020 14:16:42 +0200 Subject: [PATCH 13/41] stock_packaging_calculator: add support for packaging filter --- stock_packaging_calculator/models/product.py | 9 +++++++-- .../tests/test_packaging_calc.py | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index 4d74f56521f0..88332de81942 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -68,10 +68,15 @@ def product_qty_by_packaging(self, prod_qty, with_contained=False): ) def _ordered_packaging(self): - """Prepare packaging ordered by qty and exclude empty ones.""" + """Prepare packaging ordered by qty and exclude empty ones. + + Use ctx key `_packaging_filter` to pass a function to filter packaging + to be considered. + """ + custom_filter = self.env.context.get("_packaging_filter", lambda x: x) packagings = [ Packaging(x.id, x.name, x.qty, False) - for x in self.packaging_ids + for x in self.packaging_ids.filtered(custom_filter) # Exclude the ones w/ zero qty as they are useless for the math if x.qty ] diff --git a/stock_packaging_calculator/tests/test_packaging_calc.py b/stock_packaging_calculator/tests/test_packaging_calc.py index 3482d4329739..fcee8dd5a72f 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -103,6 +103,20 @@ def test_calc_6(self): ] self.assertEqual(self.product_a.product_qty_by_packaging(50.5), expected) + def test_calc_filter(self): + """Test packaging filter.""" + expected = [ + {"id": self.pkg_big_box.id, "qty": 13, "name": self.pkg_big_box.name}, + {"id": self.pkg_box.id, "qty": 1, "name": self.pkg_box.name}, + {"id": self.uom_unit.id, "qty": 5, "name": self.uom_unit.name}, + ] + self.assertEqual( + self.product_a.with_context( + _packaging_filter=lambda x: x != self.pkg_pallet + ).product_qty_by_packaging(2655), + expected, + ) + def test_calc_sub1(self): """Test contained packaging behavior 1.""" expected = [ From 267a39c345fe9de373f3f531c3b8349aa587d8b7 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Tue, 30 Jun 2020 14:29:58 +0200 Subject: [PATCH 14/41] stock_packaging_calculator: add support for custom packaging name --- stock_packaging_calculator/models/product.py | 6 +++++- .../tests/test_packaging_calc.py | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index 88332de81942..b81ac21071ce 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -72,10 +72,14 @@ def _ordered_packaging(self): Use ctx key `_packaging_filter` to pass a function to filter packaging to be considered. + + Use ctx key `_packaging_name_getter` to pass a function to change + the display name of the packaging. """ custom_filter = self.env.context.get("_packaging_filter", lambda x: x) + name_getter = self.env.context.get("_packaging_name_getter", lambda x: x.name) packagings = [ - Packaging(x.id, x.name, x.qty, False) + Packaging(x.id, name_getter(x), x.qty, False) for x in self.packaging_ids.filtered(custom_filter) # Exclude the ones w/ zero qty as they are useless for the math if x.qty diff --git a/stock_packaging_calculator/tests/test_packaging_calc.py b/stock_packaging_calculator/tests/test_packaging_calc.py index fcee8dd5a72f..7a6b643b3c39 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -117,6 +117,25 @@ def test_calc_filter(self): expected, ) + def test_calc_name_get(self): + """Test custom name getter.""" + expected = [ + {"id": self.pkg_pallet.id, "qty": 1, "name": "FOO " + self.pkg_pallet.name}, + { + "id": self.pkg_big_box.id, + "qty": 3, + "name": "FOO " + self.pkg_big_box.name, + }, + {"id": self.pkg_box.id, "qty": 1, "name": "FOO " + self.pkg_box.name}, + {"id": self.uom_unit.id, "qty": 5, "name": self.uom_unit.name}, + ] + self.assertEqual( + self.product_a.with_context( + _packaging_name_getter=lambda x: "FOO " + x.name + ).product_qty_by_packaging(2655), + expected, + ) + def test_calc_sub1(self): """Test contained packaging behavior 1.""" expected = [ From 25e2fa6307f2d395bfa78967cff3a586c1ff6658 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 1 Jul 2020 06:51:32 +0000 Subject: [PATCH 15/41] stock_packaging_calculator 13.0.1.3.0 --- stock_packaging_calculator/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock_packaging_calculator/__manifest__.py b/stock_packaging_calculator/__manifest__.py index 3285010a50f4..1fa33ec03138 100644 --- a/stock_packaging_calculator/__manifest__.py +++ b/stock_packaging_calculator/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Stock packaging calculator", "summary": "Compute product quantity to pick by packaging", - "version": "13.0.1.2.0", + "version": "13.0.1.3.0", "development_status": "Alpha", "category": "Warehouse Management", "website": "https://github.com/OCA/stock-logistics-warehouse", From f67122873fcf7b17a6ddce744db1f13a62320c9f Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Wed, 15 Jul 2020 17:29:44 +0200 Subject: [PATCH 16/41] Re-license stock_packaging_calculator w/ LGPL --- stock_packaging_calculator/README.rst | 6 +++--- stock_packaging_calculator/__manifest__.py | 4 ++-- stock_packaging_calculator/models/product.py | 2 +- stock_packaging_calculator/static/description/index.html | 2 +- stock_packaging_calculator/tests/test_packaging_calc.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/stock_packaging_calculator/README.rst b/stock_packaging_calculator/README.rst index 5bde39e25d73..d6cc846cd089 100644 --- a/stock_packaging_calculator/README.rst +++ b/stock_packaging_calculator/README.rst @@ -10,9 +10,9 @@ Stock packaging calculator .. |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-AGPL--3-blue.png - :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html - :alt: License: AGPL-3 +.. |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%2Fstock--logistics--warehouse-lightgray.png?logo=github :target: https://github.com/OCA/stock-logistics-warehouse/tree/13.0/stock_packaging_calculator :alt: OCA/stock-logistics-warehouse diff --git a/stock_packaging_calculator/__manifest__.py b/stock_packaging_calculator/__manifest__.py index 1fa33ec03138..3ab1b5ebd339 100644 --- a/stock_packaging_calculator/__manifest__.py +++ b/stock_packaging_calculator/__manifest__.py @@ -1,5 +1,5 @@ # Copyright 2020 Camptocamp SA -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl) { "name": "Stock packaging calculator", "summary": "Compute product quantity to pick by packaging", @@ -8,7 +8,7 @@ "category": "Warehouse Management", "website": "https://github.com/OCA/stock-logistics-warehouse", "author": "Camptocamp, Odoo Community Association (OCA)", - "license": "AGPL-3", + "license": "LGPL-3", "application": False, "installable": True, "depends": ["product"], diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index b81ac21071ce..752af5d3f02c 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -1,5 +1,5 @@ # Copyright 2020 Camptocamp SA -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl) from collections import namedtuple diff --git a/stock_packaging_calculator/static/description/index.html b/stock_packaging_calculator/static/description/index.html index 62793c551a94..ebff58a67a4b 100644 --- a/stock_packaging_calculator/static/description/index.html +++ b/stock_packaging_calculator/static/description/index.html @@ -367,7 +367,7 @@

    Stock packaging calculator

    !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

    Alpha License: AGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runbot

    +

    Alpha License: LGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runbot

    Basic module providing an helper method to calculate the quantity of product by packaging.

    Important

    diff --git a/stock_packaging_calculator/tests/test_packaging_calc.py b/stock_packaging_calculator/tests/test_packaging_calc.py index 7a6b643b3c39..b3b4fbbd6c6c 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -1,5 +1,5 @@ # Copyright 2020 Camptocamp SA -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl) from odoo.tests import SavepointCase From edcfbafd06519dc865b8d0a3a924ce0adcbe7019 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Thu, 16 Jul 2020 11:23:27 +0000 Subject: [PATCH 17/41] stock_packaging_calculator 13.0.1.4.0 --- stock_packaging_calculator/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock_packaging_calculator/__manifest__.py b/stock_packaging_calculator/__manifest__.py index 3ab1b5ebd339..cfbf256af966 100644 --- a/stock_packaging_calculator/__manifest__.py +++ b/stock_packaging_calculator/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Stock packaging calculator", "summary": "Compute product quantity to pick by packaging", - "version": "13.0.1.3.0", + "version": "13.0.1.4.0", "development_status": "Alpha", "category": "Warehouse Management", "website": "https://github.com/OCA/stock-logistics-warehouse", From 6a3bb8df9c61c190d2b863183e5c82c9298724a3 Mon Sep 17 00:00:00 2001 From: Tonow-c2c Date: Mon, 6 Jul 2020 16:26:25 +0200 Subject: [PATCH 18/41] [IMP][stock_packaging_calculator] Add key is_unit for in the packaging calculator return --- stock_packaging_calculator/models/product.py | 7 +- .../tests/test_packaging_calc.py | 137 +++++++++++++++--- 2 files changed, 125 insertions(+), 19 deletions(-) diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index 752af5d3f02c..31e8db70b762 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -100,7 +100,12 @@ def _product_qty_by_packaging(self, pkg_by_qty, qty, with_contained=False): for pkg in pkg_by_qty: qty_per_pkg, qty = self._qty_by_pkg(pkg.qty, qty) if qty_per_pkg: - value = {"id": pkg.id, "qty": qty_per_pkg, "name": pkg.name} + value = { + "id": pkg.id, + "qty": qty_per_pkg, + "name": pkg.name, + "is_unit": pkg.is_unit, + } if with_contained: contained = None if not pkg.is_unit: diff --git a/stock_packaging_calculator/tests/test_packaging_calc.py b/stock_packaging_calculator/tests/test_packaging_calc.py index b3b4fbbd6c6c..19207c592b1b 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -39,13 +39,24 @@ def test_contained_mapping(self): "id": self.pkg_big_box.id, "qty": 10, "name": self.pkg_big_box.name, + "is_unit": False, }, ], str(self.pkg_big_box.id): [ - {"id": self.pkg_box.id, "qty": 4, "name": self.pkg_box.name}, + { + "id": self.pkg_box.id, + "qty": 4, + "name": self.pkg_box.name, + "is_unit": False, + }, ], str(self.pkg_box.id): [ - {"id": self.uom_unit.id, "qty": 50, "name": self.uom_unit.name}, + { + "id": self.uom_unit.id, + "qty": 50, + "name": self.uom_unit.name, + "is_unit": False, + }, ], }, ) @@ -59,13 +70,24 @@ def test_contained_mapping(self): "id": self.pkg_big_box.id, "qty": 20, "name": self.pkg_big_box.name, + "is_unit": False, }, ], str(self.pkg_big_box.id): [ - {"id": self.pkg_box.id, "qty": 4, "name": self.pkg_box.name}, + { + "id": self.pkg_box.id, + "qty": 4, + "name": self.pkg_box.name, + "is_unit": False, + }, ], str(self.pkg_box.id): [ - {"id": self.uom_unit.id, "qty": 50, "name": self.uom_unit.name}, + { + "id": self.uom_unit.id, + "qty": 50, + "name": self.uom_unit.name, + "is_unit": False, + }, ], }, ) @@ -73,42 +95,102 @@ def test_contained_mapping(self): def test_calc_1(self): """Test easy behavior 1.""" expected = [ - {"id": self.pkg_pallet.id, "qty": 1, "name": self.pkg_pallet.name}, - {"id": self.pkg_big_box.id, "qty": 3, "name": self.pkg_big_box.name}, - {"id": self.pkg_box.id, "qty": 1, "name": self.pkg_box.name}, - {"id": self.uom_unit.id, "qty": 5, "name": self.uom_unit.name}, + { + "id": self.pkg_pallet.id, + "qty": 1, + "name": self.pkg_pallet.name, + "is_unit": False, + }, + { + "id": self.pkg_big_box.id, + "qty": 3, + "name": self.pkg_big_box.name, + "is_unit": False, + }, + { + "id": self.pkg_box.id, + "qty": 1, + "name": self.pkg_box.name, + "is_unit": False, + }, + { + "id": self.uom_unit.id, + "qty": 5, + "name": self.uom_unit.name, + "is_unit": True, + }, ] self.assertEqual(self.product_a.product_qty_by_packaging(2655), expected) def test_calc_2(self): """Test easy behavior 2.""" expected = [ - {"id": self.pkg_big_box.id, "qty": 1, "name": self.pkg_big_box.name}, - {"id": self.pkg_box.id, "qty": 3, "name": self.pkg_box.name}, + { + "id": self.pkg_big_box.id, + "qty": 1, + "name": self.pkg_big_box.name, + "is_unit": False, + }, + { + "id": self.pkg_box.id, + "qty": 3, + "name": self.pkg_box.name, + "is_unit": False, + }, ] self.assertEqual(self.product_a.product_qty_by_packaging(350), expected) def test_calc_3(self): """Test easy behavior 3.""" expected = [ - {"id": self.pkg_box.id, "qty": 1, "name": self.pkg_box.name}, - {"id": self.uom_unit.id, "qty": 30, "name": self.uom_unit.name}, + { + "id": self.pkg_box.id, + "qty": 1, + "name": self.pkg_box.name, + "is_unit": False, + }, + { + "id": self.uom_unit.id, + "qty": 30, + "name": self.uom_unit.name, + "is_unit": True, + }, ] self.assertEqual(self.product_a.product_qty_by_packaging(80), expected) def test_calc_6(self): """Test fractional qty is lost.""" expected = [ - {"id": self.pkg_box.id, "qty": 1, "name": self.pkg_box.name}, + { + "id": self.pkg_box.id, + "qty": 1, + "name": self.pkg_box.name, + "is_unit": False, + }, ] self.assertEqual(self.product_a.product_qty_by_packaging(50.5), expected) def test_calc_filter(self): """Test packaging filter.""" expected = [ - {"id": self.pkg_big_box.id, "qty": 13, "name": self.pkg_big_box.name}, - {"id": self.pkg_box.id, "qty": 1, "name": self.pkg_box.name}, - {"id": self.uom_unit.id, "qty": 5, "name": self.uom_unit.name}, + { + "id": self.pkg_big_box.id, + "qty": 13, + "name": self.pkg_big_box.name, + "is_unit": False, + }, + { + "id": self.pkg_box.id, + "qty": 1, + "name": self.pkg_box.name, + "is_unit": False, + }, + { + "id": self.uom_unit.id, + "qty": 5, + "name": self.uom_unit.name, + "is_unit": True, + }, ] self.assertEqual( self.product_a.with_context( @@ -125,9 +207,20 @@ def test_calc_name_get(self): "id": self.pkg_big_box.id, "qty": 3, "name": "FOO " + self.pkg_big_box.name, + "is_unit": False, + }, + { + "id": self.pkg_box.id, + "qty": 1, + "name": "FOO " + self.pkg_box.name, + "is_unit": False, + }, + { + "id": self.uom_unit.id, + "qty": 5, + "name": self.uom_unit.name, + "is_unit": True, }, - {"id": self.pkg_box.id, "qty": 1, "name": "FOO " + self.pkg_box.name}, - {"id": self.uom_unit.id, "qty": 5, "name": self.uom_unit.name}, ] self.assertEqual( self.product_a.with_context( @@ -143,6 +236,7 @@ def test_calc_sub1(self): "id": self.pkg_pallet.id, "qty": 1, "name": self.pkg_pallet.name, + "is_unit": False, "contained": [ { "id": self.pkg_big_box.id, @@ -155,6 +249,7 @@ def test_calc_sub1(self): "id": self.pkg_big_box.id, "qty": 3, "name": self.pkg_big_box.name, + "is_unit": False, "contained": [ {"id": self.pkg_box.id, "qty": 4, "name": self.pkg_box.name}, ], @@ -163,6 +258,7 @@ def test_calc_sub1(self): "id": self.pkg_box.id, "qty": 1, "name": self.pkg_box.name, + "is_unit": False, "contained": [ {"id": self.uom_unit.id, "qty": 50, "name": self.uom_unit.name}, ], @@ -171,6 +267,7 @@ def test_calc_sub1(self): "id": self.uom_unit.id, "qty": 5, "name": self.uom_unit.name, + "is_unit": True, "contained": None, }, ] @@ -192,6 +289,7 @@ def test_calc_sub2(self): "id": self.pkg_big_box.id, "qty": 10, "name": self.pkg_big_box.name, + "is_unit": False, }, ], }, @@ -199,6 +297,7 @@ def test_calc_sub2(self): "id": self.pkg_big_box.id, "qty": 3, "name": self.pkg_big_box.name, + "is_unit": False, "contained": [ {"id": self.pkg_box.id, "qty": 6, "name": self.pkg_box.name}, {"id": self.uom_unit.id, "qty": 20, "name": self.uom_unit.name}, @@ -208,6 +307,7 @@ def test_calc_sub2(self): "id": self.pkg_box.id, "qty": 1, "name": self.pkg_box.name, + "is_unit": False, "contained": [ {"id": self.uom_unit.id, "qty": 30, "name": self.uom_unit.name}, ], @@ -216,6 +316,7 @@ def test_calc_sub2(self): "id": self.uom_unit.id, "qty": 25, "name": self.uom_unit.name, + "is_unit": True, "contained": None, }, ] From 0b1eaf90a6759d168235b146f83282b37a2e0b09 Mon Sep 17 00:00:00 2001 From: Akim Juillerat Date: Thu, 16 Jul 2020 10:35:53 +0200 Subject: [PATCH 19/41] stock_packaging_calculator: Add hook on packaging values Fix tests --- stock_packaging_calculator/models/product.py | 16 ++++--- .../tests/test_packaging_calc.py | 48 +++++++++++++++---- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index 31e8db70b762..4d670716fb78 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -63,6 +63,7 @@ def product_qty_by_packaging(self, prod_qty, with_contained=False): {contained: [{id: 1, qty: 4, name: "Big box"}]} """ + self.ensure_one() return self._product_qty_by_packaging( self._ordered_packaging(), prod_qty, with_contained=with_contained, ) @@ -100,12 +101,7 @@ def _product_qty_by_packaging(self, pkg_by_qty, qty, with_contained=False): for pkg in pkg_by_qty: qty_per_pkg, qty = self._qty_by_pkg(pkg.qty, qty) if qty_per_pkg: - value = { - "id": pkg.id, - "qty": qty_per_pkg, - "name": pkg.name, - "is_unit": pkg.is_unit, - } + value = self._prepare_qty_by_packaging_values(pkg, qty_per_pkg) if with_contained: contained = None if not pkg.is_unit: @@ -128,3 +124,11 @@ def _qty_by_pkg(self, pkg_qty, qty): qty -= pkg_qty qty_per_pkg += 1 return qty_per_pkg, qty + + def _prepare_qty_by_packaging_values(self, packaging, qty_per_pkg): + return { + "id": packaging.id, + "qty": qty_per_pkg, + "name": packaging.name, + "is_unit": packaging.is_unit, + } diff --git a/stock_packaging_calculator/tests/test_packaging_calc.py b/stock_packaging_calculator/tests/test_packaging_calc.py index 19207c592b1b..c7ab405bd874 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -55,7 +55,7 @@ def test_contained_mapping(self): "id": self.uom_unit.id, "qty": 50, "name": self.uom_unit.name, - "is_unit": False, + "is_unit": True, }, ], }, @@ -86,7 +86,7 @@ def test_contained_mapping(self): "id": self.uom_unit.id, "qty": 50, "name": self.uom_unit.name, - "is_unit": False, + "is_unit": True, }, ], }, @@ -202,7 +202,12 @@ def test_calc_filter(self): def test_calc_name_get(self): """Test custom name getter.""" expected = [ - {"id": self.pkg_pallet.id, "qty": 1, "name": "FOO " + self.pkg_pallet.name}, + { + "id": self.pkg_pallet.id, + "qty": 1, + "name": "FOO " + self.pkg_pallet.name, + "is_unit": False, + }, { "id": self.pkg_big_box.id, "qty": 3, @@ -242,6 +247,7 @@ def test_calc_sub1(self): "id": self.pkg_big_box.id, "qty": 10, "name": self.pkg_big_box.name, + "is_unit": False, }, ], }, @@ -251,7 +257,12 @@ def test_calc_sub1(self): "name": self.pkg_big_box.name, "is_unit": False, "contained": [ - {"id": self.pkg_box.id, "qty": 4, "name": self.pkg_box.name}, + { + "id": self.pkg_box.id, + "qty": 4, + "name": self.pkg_box.name, + "is_unit": False, + }, ], }, { @@ -260,7 +271,12 @@ def test_calc_sub1(self): "name": self.pkg_box.name, "is_unit": False, "contained": [ - {"id": self.uom_unit.id, "qty": 50, "name": self.uom_unit.name}, + { + "id": self.uom_unit.id, + "qty": 50, + "name": self.uom_unit.name, + "is_unit": True, + }, ], }, { @@ -284,6 +300,7 @@ def test_calc_sub2(self): "id": self.pkg_pallet.id, "qty": 1, "name": self.pkg_pallet.name, + "is_unit": False, "contained": [ { "id": self.pkg_big_box.id, @@ -299,8 +316,18 @@ def test_calc_sub2(self): "name": self.pkg_big_box.name, "is_unit": False, "contained": [ - {"id": self.pkg_box.id, "qty": 6, "name": self.pkg_box.name}, - {"id": self.uom_unit.id, "qty": 20, "name": self.uom_unit.name}, + { + "id": self.pkg_box.id, + "qty": 6, + "name": self.pkg_box.name, + "is_unit": False, + }, + { + "id": self.uom_unit.id, + "qty": 20, + "name": self.uom_unit.name, + "is_unit": True, + }, ], }, { @@ -309,7 +336,12 @@ def test_calc_sub2(self): "name": self.pkg_box.name, "is_unit": False, "contained": [ - {"id": self.uom_unit.id, "qty": 30, "name": self.uom_unit.name}, + { + "id": self.uom_unit.id, + "qty": 30, + "name": self.uom_unit.name, + "is_unit": True, + }, ], }, { From c4b0237727dbe81c471843906632eaf677a3f160 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 17 Jul 2020 17:20:45 +0200 Subject: [PATCH 20/41] stock_packaging_calculator 13.0.1.5.0 --- stock_packaging_calculator/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock_packaging_calculator/__manifest__.py b/stock_packaging_calculator/__manifest__.py index cfbf256af966..d5aa12d877f5 100644 --- a/stock_packaging_calculator/__manifest__.py +++ b/stock_packaging_calculator/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Stock packaging calculator", "summary": "Compute product quantity to pick by packaging", - "version": "13.0.1.4.0", + "version": "13.0.1.5.0", "development_status": "Alpha", "category": "Warehouse Management", "website": "https://github.com/OCA/stock-logistics-warehouse", From 415fff5ac2b9ddfc15f43ec66d327255421dd254 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 3 Aug 2020 10:20:33 +0200 Subject: [PATCH 21/41] packaging_calculator: fix sorting Make sure unit is always the last element in the list. --- stock_packaging_calculator/models/product.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index 4d670716fb78..89eadf5c5944 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -79,12 +79,16 @@ def _ordered_packaging(self): """ custom_filter = self.env.context.get("_packaging_filter", lambda x: x) name_getter = self.env.context.get("_packaging_name_getter", lambda x: x.name) - packagings = [ - Packaging(x.id, name_getter(x), x.qty, False) - for x in self.packaging_ids.filtered(custom_filter) - # Exclude the ones w/ zero qty as they are useless for the math - if x.qty - ] + packagings = sorted( + [ + Packaging(x.id, name_getter(x), x.qty, False) + for x in self.packaging_ids.filtered(custom_filter) + # Exclude the ones w/ zero qty as they are useless for the math + if x.qty + ], + reverse=True, + key=lambda x: x.qty, + ) # Add minimal unit packagings.append( # NOTE: the ID here could clash w/ one of the packaging's. @@ -92,7 +96,7 @@ def _ordered_packaging(self): # You can use `is_unit` to check this. Packaging(self.uom_id.id, self.uom_id.name, self.uom_id.factor, True) ) - return sorted(packagings, reverse=True, key=lambda x: x.qty) + return packagings def _product_qty_by_packaging(self, pkg_by_qty, qty, with_contained=False): """Produce a list of dictionaries of packaging info.""" From 19092f8f9f3bc6cfbe80c0f90c7dc613b924b17d Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 3 Aug 2020 10:30:06 +0200 Subject: [PATCH 22/41] packaging_calculator: allow custom value handler Use _packaging_values_handler ctx key to pass your own handler for specific on demand overrides. --- stock_packaging_calculator/models/product.py | 5 ++++- .../tests/test_packaging_calc.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index 89eadf5c5944..69a85ec0944f 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -102,10 +102,13 @@ def _product_qty_by_packaging(self, pkg_by_qty, qty, with_contained=False): """Produce a list of dictionaries of packaging info.""" # TODO: refactor to handle fractional quantities (eg: 0.5 Kg) res = [] + prepare_values = self.env.context.get( + "_packaging_values_handler", self._prepare_qty_by_packaging_values + ) for pkg in pkg_by_qty: qty_per_pkg, qty = self._qty_by_pkg(pkg.qty, qty) if qty_per_pkg: - value = self._prepare_qty_by_packaging_values(pkg, qty_per_pkg) + value = prepare_values(pkg, qty_per_pkg) if with_contained: contained = None if not pkg.is_unit: diff --git a/stock_packaging_calculator/tests/test_packaging_calc.py b/stock_packaging_calculator/tests/test_packaging_calc.py index c7ab405bd874..1bc721086633 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -234,6 +234,24 @@ def test_calc_name_get(self): expected, ) + def test_calc_custom_values(self): + """Test custom values handler.""" + expected = [ + {"my_qty": 1, "foo": self.pkg_pallet.name}, + {"my_qty": 3, "foo": self.pkg_big_box.name}, + {"my_qty": 1, "foo": self.pkg_box.name}, + {"my_qty": 5, "foo": self.uom_unit.name}, + ] + self.assertEqual( + self.product_a.with_context( + _packaging_values_handler=lambda pkg, qty_per_pkg: { + "my_qty": qty_per_pkg, + "foo": pkg.name, + } + ).product_qty_by_packaging(2655), + expected, + ) + def test_calc_sub1(self): """Test contained packaging behavior 1.""" expected = [ From 0102fec4ef09597051df2d2e05981eed1b405275 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 11 Aug 2020 13:57:32 +0000 Subject: [PATCH 23/41] stock_packaging_calculator 13.0.1.6.0 --- stock_packaging_calculator/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock_packaging_calculator/__manifest__.py b/stock_packaging_calculator/__manifest__.py index d5aa12d877f5..d31c5853c596 100644 --- a/stock_packaging_calculator/__manifest__.py +++ b/stock_packaging_calculator/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Stock packaging calculator", "summary": "Compute product quantity to pick by packaging", - "version": "13.0.1.5.0", + "version": "13.0.1.6.0", "development_status": "Alpha", "category": "Warehouse Management", "website": "https://github.com/OCA/stock-logistics-warehouse", From b086eb7cc3e3d5a397975f561ff3747d575fe5d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BEAU?= Date: Wed, 6 Jan 2021 15:03:10 +0100 Subject: [PATCH 24/41] [IMP] stock_packaging_calculator: black, isort, prettier --- stock_packaging_calculator/models/product.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index 69a85ec0944f..a4327076c861 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -65,7 +65,9 @@ def product_qty_by_packaging(self, prod_qty, with_contained=False): """ self.ensure_one() return self._product_qty_by_packaging( - self._ordered_packaging(), prod_qty, with_contained=with_contained, + self._ordered_packaging(), + prod_qty, + with_contained=with_contained, ) def _ordered_packaging(self): From 77bc73978eaa5114127115a84a8186bf0cb8a8e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20BEAU?= Date: Wed, 6 Jan 2021 15:03:10 +0100 Subject: [PATCH 25/41] [MIG] stock_packaging_calculator: Migration to 14.0 --- stock_packaging_calculator/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock_packaging_calculator/__manifest__.py b/stock_packaging_calculator/__manifest__.py index d31c5853c596..856e147c3d1e 100644 --- a/stock_packaging_calculator/__manifest__.py +++ b/stock_packaging_calculator/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Stock packaging calculator", "summary": "Compute product quantity to pick by packaging", - "version": "13.0.1.6.0", + "version": "14.0.1.0.0", "development_status": "Alpha", "category": "Warehouse Management", "website": "https://github.com/OCA/stock-logistics-warehouse", From 6591a22fc3ab545176aa487cab233a8d49dfa2b7 Mon Sep 17 00:00:00 2001 From: oca-travis Date: Fri, 5 Feb 2021 09:49:42 +0000 Subject: [PATCH 26/41] [UPD] Update stock_packaging_calculator.pot --- .../i18n/stock_packaging_calculator.pot | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/stock_packaging_calculator/i18n/stock_packaging_calculator.pot b/stock_packaging_calculator/i18n/stock_packaging_calculator.pot index 92a274c4a848..664b39319223 100644 --- a/stock_packaging_calculator/i18n/stock_packaging_calculator.pot +++ b/stock_packaging_calculator/i18n/stock_packaging_calculator.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 13.0\n" +"Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" @@ -13,6 +13,21 @@ msgstr "" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" +#. module: stock_packaging_calculator +#: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_product__display_name +msgid "Display Name" +msgstr "" + +#. module: stock_packaging_calculator +#: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_product__id +msgid "ID" +msgstr "" + +#. module: stock_packaging_calculator +#: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_product____last_update +msgid "Last Modified on" +msgstr "" + #. module: stock_packaging_calculator #: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_product__packaging_contained_mapping msgid "Packaging Contained Mapping" From 0ec650a03b9ef39c693fe636b024733c7a705c07 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 5 Feb 2021 10:01:08 +0000 Subject: [PATCH 27/41] [UPD] README.rst --- stock_packaging_calculator/README.rst | 10 +++++----- .../static/description/index.html | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/stock_packaging_calculator/README.rst b/stock_packaging_calculator/README.rst index d6cc846cd089..1a28cd164625 100644 --- a/stock_packaging_calculator/README.rst +++ b/stock_packaging_calculator/README.rst @@ -14,13 +14,13 @@ Stock packaging calculator :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html :alt: License: LGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github - :target: https://github.com/OCA/stock-logistics-warehouse/tree/13.0/stock_packaging_calculator + :target: https://github.com/OCA/stock-logistics-warehouse/tree/14.0/stock_packaging_calculator :alt: OCA/stock-logistics-warehouse .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-13-0/stock-logistics-warehouse-13-0-stock_packaging_calculator + :target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-14-0/stock-logistics-warehouse-14-0-stock_packaging_calculator :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/153/13.0 + :target: https://runbot.odoo-community.org/runbot/153/14.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| @@ -91,7 +91,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 smashing it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -121,6 +121,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/stock-logistics-warehouse `_ project on GitHub. +This module is part of the `OCA/stock-logistics-warehouse `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_packaging_calculator/static/description/index.html b/stock_packaging_calculator/static/description/index.html index ebff58a67a4b..0bfe447f74e8 100644 --- a/stock_packaging_calculator/static/description/index.html +++ b/stock_packaging_calculator/static/description/index.html @@ -367,7 +367,7 @@

    Stock packaging calculator

    !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

    Alpha License: LGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runbot

    +

    Alpha License: LGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runbot

    Basic module providing an helper method to calculate the quantity of product by packaging.

    Important

    @@ -439,7 +439,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 smashing it by providing a detailed and welcomed -feedback.

    +feedback.

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

    @@ -463,7 +463,7 @@

    Maintainers

    OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

    -

    This module is part of the OCA/stock-logistics-warehouse project on GitHub.

    +

    This module is part of the OCA/stock-logistics-warehouse project on GitHub.

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

    From 2eae0e40b56aabb6a06115d181dfcb6ea6278c3a Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 5 Feb 2021 10:01:09 +0000 Subject: [PATCH 28/41] stock_packaging_calculator 14.0.1.0.1 --- stock_packaging_calculator/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock_packaging_calculator/__manifest__.py b/stock_packaging_calculator/__manifest__.py index 856e147c3d1e..f2d7f8685569 100644 --- a/stock_packaging_calculator/__manifest__.py +++ b/stock_packaging_calculator/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Stock packaging calculator", "summary": "Compute product quantity to pick by packaging", - "version": "14.0.1.0.0", + "version": "14.0.1.0.1", "development_status": "Alpha", "category": "Warehouse Management", "website": "https://github.com/OCA/stock-logistics-warehouse", From 11c8f3fccc7892c0409e59de82a0a1f0af6b4d9d Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Tue, 11 May 2021 16:09:28 +0200 Subject: [PATCH 29/41] s_packaging_calculator: include barcode --- stock_packaging_calculator/models/product.py | 7 +- .../tests/test_packaging_calc.py | 322 ++++-------------- stock_packaging_calculator/tests/utils.py | 24 ++ 3 files changed, 99 insertions(+), 254 deletions(-) create mode 100644 stock_packaging_calculator/tests/utils.py diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index a4327076c861..13ae10f3d4bb 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -9,7 +9,7 @@ from odoo.addons.base_sparse_field.models.fields import Serialized # Unify records as we mix up w/ UoM -Packaging = namedtuple("Packaging", "id name qty is_unit") +Packaging = namedtuple("Packaging", "id name qty barcode is_unit") class Product(models.Model): @@ -83,7 +83,7 @@ def _ordered_packaging(self): name_getter = self.env.context.get("_packaging_name_getter", lambda x: x.name) packagings = sorted( [ - Packaging(x.id, name_getter(x), x.qty, False) + Packaging(x.id, name_getter(x), x.qty, x.barcode, False) for x in self.packaging_ids.filtered(custom_filter) # Exclude the ones w/ zero qty as they are useless for the math if x.qty @@ -96,7 +96,7 @@ def _ordered_packaging(self): # NOTE: the ID here could clash w/ one of the packaging's. # If you create a mapping based on IDs, keep this in mind. # You can use `is_unit` to check this. - Packaging(self.uom_id.id, self.uom_id.name, self.uom_id.factor, True) + Packaging(self.uom_id.id, self.uom_id.name, self.uom_id.factor, None, True) ) return packagings @@ -140,4 +140,5 @@ def _prepare_qty_by_packaging_values(self, packaging, qty_per_pkg): "qty": qty_per_pkg, "name": packaging.name, "is_unit": packaging.is_unit, + "barcode": packaging.barcode, } diff --git a/stock_packaging_calculator/tests/test_packaging_calc.py b/stock_packaging_calculator/tests/test_packaging_calc.py index 1bc721086633..99470229b4bc 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -2,11 +2,14 @@ # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl) from odoo.tests import SavepointCase +from .utils import make_pkg_values + class TestCalc(SavepointCase): at_install = False post_install = True + maxDiff = None @classmethod def setUpClass(cls): @@ -21,43 +24,32 @@ def setUpClass(cls): } ) cls.pkg_box = cls.env["product.packaging"].create( - {"name": "Box", "product_id": cls.product_a.id, "qty": 50} + {"name": "Box", "product_id": cls.product_a.id, "qty": 50, "barcode": "BOX"} ) cls.pkg_big_box = cls.env["product.packaging"].create( - {"name": "Big Box", "product_id": cls.product_a.id, "qty": 200} + { + "name": "Big Box", + "product_id": cls.product_a.id, + "qty": 200, + "barcode": "BIGBOX", + } ) cls.pkg_pallet = cls.env["product.packaging"].create( - {"name": "Pallet", "product_id": cls.product_a.id, "qty": 2000} + { + "name": "Pallet", + "product_id": cls.product_a.id, + "qty": 2000, + "barcode": "PALLET", + } ) def test_contained_mapping(self): self.assertEqual( self.product_a.packaging_contained_mapping, { - str(self.pkg_pallet.id): [ - { - "id": self.pkg_big_box.id, - "qty": 10, - "name": self.pkg_big_box.name, - "is_unit": False, - }, - ], - str(self.pkg_big_box.id): [ - { - "id": self.pkg_box.id, - "qty": 4, - "name": self.pkg_box.name, - "is_unit": False, - }, - ], - str(self.pkg_box.id): [ - { - "id": self.uom_unit.id, - "qty": 50, - "name": self.uom_unit.name, - "is_unit": True, - }, - ], + str(self.pkg_pallet.id): [make_pkg_values(self.pkg_big_box, qty=10)], + str(self.pkg_big_box.id): [make_pkg_values(self.pkg_box, qty=4)], + str(self.pkg_box.id): [make_pkg_values(self.uom_unit, qty=50)], }, ) # Update pkg qty @@ -65,132 +57,51 @@ def test_contained_mapping(self): self.assertEqual( self.product_a.packaging_contained_mapping, { - str(self.pkg_pallet.id): [ - { - "id": self.pkg_big_box.id, - "qty": 20, - "name": self.pkg_big_box.name, - "is_unit": False, - }, - ], - str(self.pkg_big_box.id): [ - { - "id": self.pkg_box.id, - "qty": 4, - "name": self.pkg_box.name, - "is_unit": False, - }, - ], - str(self.pkg_box.id): [ - { - "id": self.uom_unit.id, - "qty": 50, - "name": self.uom_unit.name, - "is_unit": True, - }, - ], + str(self.pkg_pallet.id): [make_pkg_values(self.pkg_big_box, qty=20)], + str(self.pkg_big_box.id): [make_pkg_values(self.pkg_box, qty=4)], + str(self.pkg_box.id): [make_pkg_values(self.uom_unit, qty=50)], }, ) def test_calc_1(self): """Test easy behavior 1.""" expected = [ - { - "id": self.pkg_pallet.id, - "qty": 1, - "name": self.pkg_pallet.name, - "is_unit": False, - }, - { - "id": self.pkg_big_box.id, - "qty": 3, - "name": self.pkg_big_box.name, - "is_unit": False, - }, - { - "id": self.pkg_box.id, - "qty": 1, - "name": self.pkg_box.name, - "is_unit": False, - }, - { - "id": self.uom_unit.id, - "qty": 5, - "name": self.uom_unit.name, - "is_unit": True, - }, + make_pkg_values(self.pkg_pallet, qty=1), + make_pkg_values(self.pkg_big_box, qty=3), + make_pkg_values(self.pkg_box, qty=1), + make_pkg_values(self.uom_unit, qty=5), ] self.assertEqual(self.product_a.product_qty_by_packaging(2655), expected) def test_calc_2(self): """Test easy behavior 2.""" expected = [ - { - "id": self.pkg_big_box.id, - "qty": 1, - "name": self.pkg_big_box.name, - "is_unit": False, - }, - { - "id": self.pkg_box.id, - "qty": 3, - "name": self.pkg_box.name, - "is_unit": False, - }, + make_pkg_values(self.pkg_big_box, qty=1), + make_pkg_values(self.pkg_box, qty=3), ] self.assertEqual(self.product_a.product_qty_by_packaging(350), expected) def test_calc_3(self): """Test easy behavior 3.""" expected = [ - { - "id": self.pkg_box.id, - "qty": 1, - "name": self.pkg_box.name, - "is_unit": False, - }, - { - "id": self.uom_unit.id, - "qty": 30, - "name": self.uom_unit.name, - "is_unit": True, - }, + make_pkg_values(self.pkg_box, qty=1), + make_pkg_values(self.uom_unit, qty=30), ] self.assertEqual(self.product_a.product_qty_by_packaging(80), expected) def test_calc_6(self): """Test fractional qty is lost.""" expected = [ - { - "id": self.pkg_box.id, - "qty": 1, - "name": self.pkg_box.name, - "is_unit": False, - }, + make_pkg_values(self.pkg_box, qty=1), ] self.assertEqual(self.product_a.product_qty_by_packaging(50.5), expected) def test_calc_filter(self): """Test packaging filter.""" expected = [ - { - "id": self.pkg_big_box.id, - "qty": 13, - "name": self.pkg_big_box.name, - "is_unit": False, - }, - { - "id": self.pkg_box.id, - "qty": 1, - "name": self.pkg_box.name, - "is_unit": False, - }, - { - "id": self.uom_unit.id, - "qty": 5, - "name": self.uom_unit.name, - "is_unit": True, - }, + make_pkg_values(self.pkg_big_box, qty=13), + make_pkg_values(self.pkg_box, qty=1), + make_pkg_values(self.uom_unit, qty=5), ] self.assertEqual( self.product_a.with_context( @@ -202,30 +113,12 @@ def test_calc_filter(self): def test_calc_name_get(self): """Test custom name getter.""" expected = [ - { - "id": self.pkg_pallet.id, - "qty": 1, - "name": "FOO " + self.pkg_pallet.name, - "is_unit": False, - }, - { - "id": self.pkg_big_box.id, - "qty": 3, - "name": "FOO " + self.pkg_big_box.name, - "is_unit": False, - }, - { - "id": self.pkg_box.id, - "qty": 1, - "name": "FOO " + self.pkg_box.name, - "is_unit": False, - }, - { - "id": self.uom_unit.id, - "qty": 5, - "name": self.uom_unit.name, - "is_unit": True, - }, + make_pkg_values(self.pkg_pallet, qty=1, name="FOO " + self.pkg_pallet.name), + make_pkg_values( + self.pkg_big_box, qty=3, name="FOO " + self.pkg_big_box.name + ), + make_pkg_values(self.pkg_box, qty=1, name="FOO " + self.pkg_box.name), + make_pkg_values(self.uom_unit, qty=5, name=self.uom_unit.name), ] self.assertEqual( self.product_a.with_context( @@ -255,55 +148,20 @@ def test_calc_custom_values(self): def test_calc_sub1(self): """Test contained packaging behavior 1.""" expected = [ - { - "id": self.pkg_pallet.id, - "qty": 1, - "name": self.pkg_pallet.name, - "is_unit": False, - "contained": [ - { - "id": self.pkg_big_box.id, - "qty": 10, - "name": self.pkg_big_box.name, - "is_unit": False, - }, - ], - }, - { - "id": self.pkg_big_box.id, - "qty": 3, - "name": self.pkg_big_box.name, - "is_unit": False, - "contained": [ - { - "id": self.pkg_box.id, - "qty": 4, - "name": self.pkg_box.name, - "is_unit": False, - }, - ], - }, - { - "id": self.pkg_box.id, - "qty": 1, - "name": self.pkg_box.name, - "is_unit": False, - "contained": [ - { - "id": self.uom_unit.id, - "qty": 50, - "name": self.uom_unit.name, - "is_unit": True, - }, - ], - }, - { - "id": self.uom_unit.id, - "qty": 5, - "name": self.uom_unit.name, - "is_unit": True, - "contained": None, - }, + make_pkg_values( + self.pkg_pallet, + qty=1, + contained=[make_pkg_values(self.pkg_big_box, qty=10)], + ), + make_pkg_values( + self.pkg_big_box, + qty=3, + contained=[make_pkg_values(self.pkg_box, qty=4)], + ), + make_pkg_values( + self.pkg_box, qty=1, contained=[make_pkg_values(self.uom_unit, qty=50)], + ), + make_pkg_values(self.uom_unit, qty=5, contained=None), ] self.assertEqual( self.product_a.product_qty_by_packaging(2655, with_contained=True), @@ -311,64 +169,26 @@ def test_calc_sub1(self): ) def test_calc_sub2(self): - """Test contained packaging behavior 1.""" + """Test contained packaging behavior 2.""" self.pkg_box.qty = 30 expected = [ - { - "id": self.pkg_pallet.id, - "qty": 1, - "name": self.pkg_pallet.name, - "is_unit": False, - "contained": [ - { - "id": self.pkg_big_box.id, - "qty": 10, - "name": self.pkg_big_box.name, - "is_unit": False, - }, - ], - }, - { - "id": self.pkg_big_box.id, - "qty": 3, - "name": self.pkg_big_box.name, - "is_unit": False, - "contained": [ - { - "id": self.pkg_box.id, - "qty": 6, - "name": self.pkg_box.name, - "is_unit": False, - }, - { - "id": self.uom_unit.id, - "qty": 20, - "name": self.uom_unit.name, - "is_unit": True, - }, - ], - }, - { - "id": self.pkg_box.id, - "qty": 1, - "name": self.pkg_box.name, - "is_unit": False, - "contained": [ - { - "id": self.uom_unit.id, - "qty": 30, - "name": self.uom_unit.name, - "is_unit": True, - }, + make_pkg_values( + self.pkg_pallet, + qty=1, + contained=[make_pkg_values(self.pkg_big_box, qty=10)], + ), + make_pkg_values( + self.pkg_big_box, + qty=3, + contained=[ + make_pkg_values(self.pkg_box, qty=6), + make_pkg_values(self.uom_unit, qty=20), ], - }, - { - "id": self.uom_unit.id, - "qty": 25, - "name": self.uom_unit.name, - "is_unit": True, - "contained": None, - }, + ), + make_pkg_values( + self.pkg_box, qty=1, contained=[make_pkg_values(self.uom_unit, qty=30)], + ), + make_pkg_values(self.uom_unit, qty=25, contained=None), ] self.assertEqual( self.product_a.product_qty_by_packaging(2655, with_contained=True), diff --git a/stock_packaging_calculator/tests/utils.py b/stock_packaging_calculator/tests/utils.py new file mode 100644 index 000000000000..cd9e12560fac --- /dev/null +++ b/stock_packaging_calculator/tests/utils.py @@ -0,0 +1,24 @@ +# Copyright 2021 Camptocamp SA +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl) + + +def make_pkg_values(record, **kw): + """Helper to generate test values for packaging. + """ + if record._name == "uom.uom": + is_unit = True + barcode = None + qty = record.factor + elif record._name == "product.packaging": + qty = record.qty + is_unit = False + barcode = record.barcode + values = { + "id": record.id, + "name": record.name, + "qty": qty, + "barcode": barcode, + "is_unit": is_unit, + } + values.update(kw) + return values From 2e6fddce9e85020fea366405a7b78e10727c1e1f Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 28 May 2021 09:03:14 +0200 Subject: [PATCH 30/41] s_packaging_calculator: ease override of packaging name --- stock_packaging_calculator/models/product.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index 13ae10f3d4bb..cdf83edf9c31 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -80,7 +80,9 @@ def _ordered_packaging(self): the display name of the packaging. """ custom_filter = self.env.context.get("_packaging_filter", lambda x: x) - name_getter = self.env.context.get("_packaging_name_getter", lambda x: x.name) + name_getter = self.env.context.get( + "_packaging_name_getter", self._packaging_name_getter + ) packagings = sorted( [ Packaging(x.id, name_getter(x), x.qty, x.barcode, False) @@ -100,6 +102,9 @@ def _ordered_packaging(self): ) return packagings + def _packaging_name_getter(self, packaging): + return packaging.name + def _product_qty_by_packaging(self, pkg_by_qty, qty, with_contained=False): """Produce a list of dictionaries of packaging info.""" # TODO: refactor to handle fractional quantities (eg: 0.5 Kg) From 2d52fe87e5203449cb7d49f38476cda08c478a26 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 28 May 2021 09:04:51 +0200 Subject: [PATCH 31/41] s_packaging_calculator: add product_qty_by_packaging_as_str Retrieve quickly packagin bty qty as a string. --- stock_packaging_calculator/models/product.py | 58 +++++++++++++++++++ stock_packaging_calculator/tests/__init__.py | 1 + stock_packaging_calculator/tests/common.py | 42 ++++++++++++++ .../tests/test_packaging_calc.py | 42 +------------- .../tests/test_pkg_qty_str.py | 50 ++++++++++++++++ 5 files changed, 153 insertions(+), 40 deletions(-) create mode 100644 stock_packaging_calculator/tests/common.py create mode 100644 stock_packaging_calculator/tests/test_pkg_qty_str.py diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index cdf83edf9c31..026c7971bb2c 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -1,6 +1,8 @@ # Copyright 2020 Camptocamp SA +# @author: Simone Orsi # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl) +import unicodedata from collections import namedtuple from odoo import api, models @@ -11,6 +13,8 @@ # Unify records as we mix up w/ UoM Packaging = namedtuple("Packaging", "id name qty barcode is_unit") +NO_BREAK_SPACE_CHAR = unicodedata.lookup("NO-BREAK SPACE") + class Product(models.Model): _inherit = "product.product" @@ -147,3 +151,57 @@ def _prepare_qty_by_packaging_values(self, packaging, qty_per_pkg): "is_unit": packaging.is_unit, "barcode": packaging.barcode, } + + def product_qty_by_packaging_as_str(self, prod_qty, include_total_units=False): + """Return a string representing the qty of each packaging. + + :param prod_qty: the qty of current product to translate to pkg qty + :param include_total_units: includes total qty required initially + """ + self.ensure_one() + if not prod_qty: + return "" + + qty_by_packaging = self.product_qty_by_packaging(prod_qty) + if not qty_by_packaging: + return "" + + # Exclude unit qty and reuse it later + unit_qty = None + has_only_units = True + _qty_by_packaging = [] + for pkg_qty in qty_by_packaging: + if pkg_qty["is_unit"]: + unit_qty = pkg_qty["qty"] + continue + has_only_units = False + _qty_by_packaging.append(pkg_qty) + # Browse them all at once + records = self.env["product.packaging"].browse( + [x["id"] for x in _qty_by_packaging] + ) + _qty_by_packaging_as_str = self.env.context.get( + "_qty_by_packaging_as_str", self._qty_by_packaging_as_str + ) + # Collect all strings representations + as_string = [] + for record, info in zip(records, _qty_by_packaging): + bit = _qty_by_packaging_as_str(record, info["qty"]) + if bit: + as_string.append(bit) + # Restore unit information if any. + # Skip it if we get only units in the count. + if unit_qty and not has_only_units: + as_string.append(f"{unit_qty} {self.uom_id.name}") + # We want to avoid line break here as this string + # can be used by reports + res = f",{NO_BREAK_SPACE_CHAR}".join(as_string) + if include_total_units and not has_only_units: + res += " " + self._qty_by_packaging_total_units(prod_qty) + return res + + def _qty_by_packaging_as_str(self, packaging, qty): + return f"{qty} {packaging.name}" + + def _qty_by_packaging_total_units(self, prod_qty): + return f"({prod_qty} {self.uom_id.name})" diff --git a/stock_packaging_calculator/tests/__init__.py b/stock_packaging_calculator/tests/__init__.py index 04c47e71156e..441177cdc372 100644 --- a/stock_packaging_calculator/tests/__init__.py +++ b/stock_packaging_calculator/tests/__init__.py @@ -1 +1,2 @@ from . import test_packaging_calc +from . import test_pkg_qty_str diff --git a/stock_packaging_calculator/tests/common.py b/stock_packaging_calculator/tests/common.py new file mode 100644 index 000000000000..c528958ce17e --- /dev/null +++ b/stock_packaging_calculator/tests/common.py @@ -0,0 +1,42 @@ +# Copyright 2020 Camptocamp SA +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl) +from odoo.tests import SavepointCase + + +class TestCommon(SavepointCase): + + at_install = False + post_install = True + maxDiff = None + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls.uom_unit = cls.env.ref("uom.product_uom_unit") + cls.product_a = cls.env["product.product"].create( + { + "name": "Product A", + "uom_id": cls.uom_unit.id, + "uom_po_id": cls.uom_unit.id, + } + ) + cls.pkg_box = cls.env["product.packaging"].create( + {"name": "Box", "product_id": cls.product_a.id, "qty": 50, "barcode": "BOX"} + ) + cls.pkg_big_box = cls.env["product.packaging"].create( + { + "name": "Big Box", + "product_id": cls.product_a.id, + "qty": 200, + "barcode": "BIGBOX", + } + ) + cls.pkg_pallet = cls.env["product.packaging"].create( + { + "name": "Pallet", + "product_id": cls.product_a.id, + "qty": 2000, + "barcode": "PALLET", + } + ) diff --git a/stock_packaging_calculator/tests/test_packaging_calc.py b/stock_packaging_calculator/tests/test_packaging_calc.py index 99470229b4bc..c51347e40ea3 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -1,48 +1,10 @@ # Copyright 2020 Camptocamp SA # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl) -from odoo.tests import SavepointCase - +from .common import TestCommon from .utils import make_pkg_values -class TestCalc(SavepointCase): - - at_install = False - post_install = True - maxDiff = None - - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) - cls.uom_unit = cls.env.ref("uom.product_uom_unit") - cls.product_a = cls.env["product.product"].create( - { - "name": "Product A", - "uom_id": cls.uom_unit.id, - "uom_po_id": cls.uom_unit.id, - } - ) - cls.pkg_box = cls.env["product.packaging"].create( - {"name": "Box", "product_id": cls.product_a.id, "qty": 50, "barcode": "BOX"} - ) - cls.pkg_big_box = cls.env["product.packaging"].create( - { - "name": "Big Box", - "product_id": cls.product_a.id, - "qty": 200, - "barcode": "BIGBOX", - } - ) - cls.pkg_pallet = cls.env["product.packaging"].create( - { - "name": "Pallet", - "product_id": cls.product_a.id, - "qty": 2000, - "barcode": "PALLET", - } - ) - +class TestCalc(TestCommon): def test_contained_mapping(self): self.assertEqual( self.product_a.packaging_contained_mapping, diff --git a/stock_packaging_calculator/tests/test_pkg_qty_str.py b/stock_packaging_calculator/tests/test_pkg_qty_str.py new file mode 100644 index 000000000000..93998846cf3c --- /dev/null +++ b/stock_packaging_calculator/tests/test_pkg_qty_str.py @@ -0,0 +1,50 @@ +# Copyright 2021 Camptocamp SA +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl) +from .common import TestCommon + + +class TestAsStr(TestCommon): + def test_as_str(self): + self.assertEqual(self.product_a.product_qty_by_packaging_as_str(10), "") + self.assertEqual(self.product_a.product_qty_by_packaging_as_str(100), "2 Box") + self.assertEqual( + self.product_a.product_qty_by_packaging_as_str(250), "1 Big Box,\xa01 Box" + ) + self.assertEqual( + self.product_a.product_qty_by_packaging_as_str(255), + "1 Big Box,\xa01 Box,\xa05 Units", + ) + + def test_as_str_w_units(self): + self.assertEqual( + self.product_a.product_qty_by_packaging_as_str( + 10, include_total_units=True + ), + "", + ) + self.assertEqual( + self.product_a.product_qty_by_packaging_as_str( + 100, include_total_units=True + ), + "2 Box (100 Units)", + ) + self.assertEqual( + self.product_a.product_qty_by_packaging_as_str( + 250, include_total_units=True + ), + "1 Big Box,\xa01 Box (250 Units)", + ) + self.assertEqual( + self.product_a.product_qty_by_packaging_as_str( + 255, include_total_units=True + ), + "1 Big Box,\xa01 Box,\xa05 Units (255 Units)", + ) + + def test_as_str_custom_name(self): + self.assertEqual( + self.product_a.with_context( + _qty_by_packaging_as_str=lambda pkg, qty: f"{pkg.name} {qty} FOO" + ).product_qty_by_packaging_as_str(250), + "Big Box 1 FOO,\xa0Box 1 FOO", + ) From dcfe9b9994594bb3e7bde41746a85c937d26861e Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 28 May 2021 08:59:14 +0200 Subject: [PATCH 32/41] s_packaging_calculator: add handy mixin This mixin can be used to provide qty by packaging features to any model. --- stock_packaging_calculator/models/__init__.py | 1 + .../models/product_qty_by_packaging_mixin.py | 48 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 stock_packaging_calculator/models/product_qty_by_packaging_mixin.py diff --git a/stock_packaging_calculator/models/__init__.py b/stock_packaging_calculator/models/__init__.py index 9649db77a159..fb73bb4d194d 100644 --- a/stock_packaging_calculator/models/__init__.py +++ b/stock_packaging_calculator/models/__init__.py @@ -1 +1,2 @@ from . import product +from . import product_qty_by_packaging_mixin diff --git a/stock_packaging_calculator/models/product_qty_by_packaging_mixin.py b/stock_packaging_calculator/models/product_qty_by_packaging_mixin.py new file mode 100644 index 000000000000..4721fe862fc5 --- /dev/null +++ b/stock_packaging_calculator/models/product_qty_by_packaging_mixin.py @@ -0,0 +1,48 @@ +# Copyright 2021 Camptocamp SA +# @author: Simone Orsi +# @author: Sébastien Alix +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl) +from odoo import api, fields, models + + +class ProductQtyByPackagingMixin(models.AbstractModel): + """Allow displaying product qty by packaging. + """ + + _name = "product.qty_by_packaging.mixin" + _description = "Product Qty By Packaging (Mixin)" + + _qty_by_pkg__product_field_name = "product_id" + _qty_by_pkg__qty_field_name = None + + product_qty_by_packaging_display = fields.Char( + compute="_compute_product_qty_by_packaging_display", string="Qty by packaging" + ) + + def _product_qty_by_packaging_display_depends(self): + depends = [] + if self._qty_by_pkg__product_field_name: + depends.append(self._qty_by_pkg__product_field_name) + if self._qty_by_pkg__qty_field_name: + depends.append(self._qty_by_pkg__qty_field_name) + return depends + + @api.depends_context("lang", "qty_by_pkg_total_units") + @api.depends(lambda self: self._product_qty_by_packaging_display_depends()) + def _compute_product_qty_by_packaging_display(self): + include_total_units = self.env.context.get("qty_by_pkg_total_units", False) + for record in self: + value = "" + product = record._qty_by_packaging_get_product() + if product: + value = product.product_qty_by_packaging_as_str( + record._qty_by_packaging_get_qty(), + include_total_units=include_total_units, + ) + record.product_qty_by_packaging_display = value + + def _qty_by_packaging_get_product(self): + return self[self._qty_by_pkg__product_field_name] + + def _qty_by_packaging_get_qty(self): + return self[self._qty_by_pkg__qty_field_name] From 77c2b38103ad359784d958bb75e65287a99da069 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 3 Jun 2021 13:03:04 +0200 Subject: [PATCH 33/41] s_packaging_calculator: contained mapping depends on lang Otherwise translations won't be taken into account. --- stock_packaging_calculator/models/product.py | 1 + 1 file changed, 1 insertion(+) diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index 026c7971bb2c..57fc4dbdccba 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -24,6 +24,7 @@ class Product(models.Model): help="Technical field to store contained packaging. ", ) + @api.depends_context("lang") @api.depends("packaging_ids.qty") def _compute_packaging_contained_mapping(self): for rec in self: From ee59672e939b1cbaef50608760ada4482b3477c4 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 11 Jun 2021 13:08:24 +0200 Subject: [PATCH 34/41] s_packaging_calculator: include units when units only on demand --- stock_packaging_calculator/models/product.py | 11 ++++++++--- .../models/product_qty_by_packaging_mixin.py | 4 +++- .../tests/test_pkg_qty_str.py | 19 +++++++++++++++++-- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index 57fc4dbdccba..71d7063688d5 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -153,11 +153,16 @@ def _prepare_qty_by_packaging_values(self, packaging, qty_per_pkg): "barcode": packaging.barcode, } - def product_qty_by_packaging_as_str(self, prod_qty, include_total_units=False): + def product_qty_by_packaging_as_str( + self, prod_qty, include_total_units=False, only_packaging=False + ): """Return a string representing the qty of each packaging. :param prod_qty: the qty of current product to translate to pkg qty :param include_total_units: includes total qty required initially + :param only_packaging: exclude units if you have only units. + IOW: if the qty does not match any packaging and this flag is true + you'll get an empty string instead of `N units`. """ self.ensure_one() if not prod_qty: @@ -191,8 +196,8 @@ def product_qty_by_packaging_as_str(self, prod_qty, include_total_units=False): if bit: as_string.append(bit) # Restore unit information if any. - # Skip it if we get only units in the count. - if unit_qty and not has_only_units: + include_units = (has_only_units and not only_packaging) or not has_only_units + if unit_qty and include_units: as_string.append(f"{unit_qty} {self.uom_id.name}") # We want to avoid line break here as this string # can be used by reports diff --git a/stock_packaging_calculator/models/product_qty_by_packaging_mixin.py b/stock_packaging_calculator/models/product_qty_by_packaging_mixin.py index 4721fe862fc5..9065a7b1ff96 100644 --- a/stock_packaging_calculator/models/product_qty_by_packaging_mixin.py +++ b/stock_packaging_calculator/models/product_qty_by_packaging_mixin.py @@ -27,10 +27,11 @@ def _product_qty_by_packaging_display_depends(self): depends.append(self._qty_by_pkg__qty_field_name) return depends - @api.depends_context("lang", "qty_by_pkg_total_units") + @api.depends_context("lang", "qty_by_pkg_total_units", "qty_by_pkg_only_packaging") @api.depends(lambda self: self._product_qty_by_packaging_display_depends()) def _compute_product_qty_by_packaging_display(self): include_total_units = self.env.context.get("qty_by_pkg_total_units", False) + only_packaging = self.env.context.get("qty_by_pkg_only_packaging", False) for record in self: value = "" product = record._qty_by_packaging_get_product() @@ -38,6 +39,7 @@ def _compute_product_qty_by_packaging_display(self): value = product.product_qty_by_packaging_as_str( record._qty_by_packaging_get_qty(), include_total_units=include_total_units, + only_packaging=only_packaging, ) record.product_qty_by_packaging_display = value diff --git a/stock_packaging_calculator/tests/test_pkg_qty_str.py b/stock_packaging_calculator/tests/test_pkg_qty_str.py index 93998846cf3c..7fca487de3b3 100644 --- a/stock_packaging_calculator/tests/test_pkg_qty_str.py +++ b/stock_packaging_calculator/tests/test_pkg_qty_str.py @@ -5,7 +5,10 @@ class TestAsStr(TestCommon): def test_as_str(self): - self.assertEqual(self.product_a.product_qty_by_packaging_as_str(10), "") + self.assertEqual(self.product_a.product_qty_by_packaging_as_str(10), "10 Units") + self.assertEqual( + self.product_a.product_qty_by_packaging_as_str(10, only_packaging=True), "" + ) self.assertEqual(self.product_a.product_qty_by_packaging_as_str(100), "2 Box") self.assertEqual( self.product_a.product_qty_by_packaging_as_str(250), "1 Big Box,\xa01 Box" @@ -14,13 +17,18 @@ def test_as_str(self): self.product_a.product_qty_by_packaging_as_str(255), "1 Big Box,\xa01 Box,\xa05 Units", ) + # only_packaging has no impact if we get not only units + self.assertEqual( + self.product_a.product_qty_by_packaging_as_str(255, only_packaging=True), + "1 Big Box,\xa01 Box,\xa05 Units", + ) def test_as_str_w_units(self): self.assertEqual( self.product_a.product_qty_by_packaging_as_str( 10, include_total_units=True ), - "", + "10 Units", ) self.assertEqual( self.product_a.product_qty_by_packaging_as_str( @@ -40,6 +48,13 @@ def test_as_str_w_units(self): ), "1 Big Box,\xa01 Box,\xa05 Units (255 Units)", ) + # only_packaging has no impact if we get not only units + self.assertEqual( + self.product_a.product_qty_by_packaging_as_str( + 255, include_total_units=True, only_packaging=True + ), + "1 Big Box,\xa01 Box,\xa05 Units (255 Units)", + ) def test_as_str_custom_name(self): self.assertEqual( From a30d36ea90da4b69891abf897d3c1c8addb51eb3 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 15 Jul 2021 16:57:16 +0200 Subject: [PATCH 35/41] s_packaging_calculator: improve test util This way no matter who's changing the behavior of the name getter we'll get the right name. --- stock_packaging_calculator/tests/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/stock_packaging_calculator/tests/utils.py b/stock_packaging_calculator/tests/utils.py index cd9e12560fac..8c885894cd43 100644 --- a/stock_packaging_calculator/tests/utils.py +++ b/stock_packaging_calculator/tests/utils.py @@ -5,6 +5,7 @@ def make_pkg_values(record, **kw): """Helper to generate test values for packaging. """ + name = record.name if record._name == "uom.uom": is_unit = True barcode = None @@ -13,9 +14,11 @@ def make_pkg_values(record, **kw): qty = record.qty is_unit = False barcode = record.barcode + if record.product_id: + name = record.product_id._packaging_name_getter(record) values = { "id": record.id, - "name": record.name, + "name": name, "qty": qty, "barcode": barcode, "is_unit": is_unit, From 6bfe0eedf27a2d45b3729ef31d7c2c0167e9bd51 Mon Sep 17 00:00:00 2001 From: oca-travis Date: Mon, 7 Jun 2021 12:22:57 +0000 Subject: [PATCH 36/41] [UPD] Update stock_packaging_calculator.pot --- .../i18n/stock_packaging_calculator.pot | 44 ------------------- 1 file changed, 44 deletions(-) delete mode 100644 stock_packaging_calculator/i18n/stock_packaging_calculator.pot diff --git a/stock_packaging_calculator/i18n/stock_packaging_calculator.pot b/stock_packaging_calculator/i18n/stock_packaging_calculator.pot deleted file mode 100644 index 664b39319223..000000000000 --- a/stock_packaging_calculator/i18n/stock_packaging_calculator.pot +++ /dev/null @@ -1,44 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * stock_packaging_calculator -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 14.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: stock_packaging_calculator -#: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_product__display_name -msgid "Display Name" -msgstr "" - -#. module: stock_packaging_calculator -#: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_product__id -msgid "ID" -msgstr "" - -#. module: stock_packaging_calculator -#: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_product____last_update -msgid "Last Modified on" -msgstr "" - -#. module: stock_packaging_calculator -#: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_product__packaging_contained_mapping -msgid "Packaging Contained Mapping" -msgstr "" - -#. module: stock_packaging_calculator -#: model:ir.model,name:stock_packaging_calculator.model_product_product -msgid "Product" -msgstr "" - -#. module: stock_packaging_calculator -#: model:ir.model.fields,help:stock_packaging_calculator.field_product_product__packaging_contained_mapping -msgid "Technical field to store contained packaging. " -msgstr "" From b83cb979cc6cb26b9ffdb11ca3357457a4aa3698 Mon Sep 17 00:00:00 2001 From: nguyen hoang hiep Date: Tue, 14 Sep 2021 05:57:42 +0000 Subject: [PATCH 37/41] [IMP] stock_packaging_calculator: black, isort, prettier --- .../i18n/stock_packaging_calculator.pot | 54 +++++++++++++++++++ .../models/product_qty_by_packaging_mixin.py | 3 +- .../tests/test_packaging_calc.py | 8 ++- stock_packaging_calculator/tests/utils.py | 3 +- 4 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 stock_packaging_calculator/i18n/stock_packaging_calculator.pot diff --git a/stock_packaging_calculator/i18n/stock_packaging_calculator.pot b/stock_packaging_calculator/i18n/stock_packaging_calculator.pot new file mode 100644 index 000000000000..1cfae5477919 --- /dev/null +++ b/stock_packaging_calculator/i18n/stock_packaging_calculator.pot @@ -0,0 +1,54 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_packaging_calculator +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.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: stock_packaging_calculator +#: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_qty_by_packaging_mixin__display_name +msgid "Display Name" +msgstr "" + +#. module: stock_packaging_calculator +#: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_qty_by_packaging_mixin__id +msgid "ID" +msgstr "" + +#. module: stock_packaging_calculator +#: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_qty_by_packaging_mixin____last_update +msgid "Last Modified on" +msgstr "" + +#. module: stock_packaging_calculator +#: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_product__packaging_contained_mapping +msgid "Packaging Contained Mapping" +msgstr "" + +#. module: stock_packaging_calculator +#: model:ir.model,name:stock_packaging_calculator.model_product_product +msgid "Product" +msgstr "" + +#. module: stock_packaging_calculator +#: model:ir.model,name:stock_packaging_calculator.model_product_qty_by_packaging_mixin +msgid "Product Qty By Packaging (Mixin)" +msgstr "" + +#. module: stock_packaging_calculator +#: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_qty_by_packaging_mixin__product_qty_by_packaging_display +msgid "Qty by packaging" +msgstr "" + +#. module: stock_packaging_calculator +#: model:ir.model.fields,help:stock_packaging_calculator.field_product_product__packaging_contained_mapping +msgid "Technical field to store contained packaging. " +msgstr "" diff --git a/stock_packaging_calculator/models/product_qty_by_packaging_mixin.py b/stock_packaging_calculator/models/product_qty_by_packaging_mixin.py index 9065a7b1ff96..693ad561a96e 100644 --- a/stock_packaging_calculator/models/product_qty_by_packaging_mixin.py +++ b/stock_packaging_calculator/models/product_qty_by_packaging_mixin.py @@ -6,8 +6,7 @@ class ProductQtyByPackagingMixin(models.AbstractModel): - """Allow displaying product qty by packaging. - """ + """Allow displaying product qty by packaging.""" _name = "product.qty_by_packaging.mixin" _description = "Product Qty By Packaging (Mixin)" diff --git a/stock_packaging_calculator/tests/test_packaging_calc.py b/stock_packaging_calculator/tests/test_packaging_calc.py index c51347e40ea3..3d4bbfa85918 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -121,7 +121,9 @@ def test_calc_sub1(self): contained=[make_pkg_values(self.pkg_box, qty=4)], ), make_pkg_values( - self.pkg_box, qty=1, contained=[make_pkg_values(self.uom_unit, qty=50)], + self.pkg_box, + qty=1, + contained=[make_pkg_values(self.uom_unit, qty=50)], ), make_pkg_values(self.uom_unit, qty=5, contained=None), ] @@ -148,7 +150,9 @@ def test_calc_sub2(self): ], ), make_pkg_values( - self.pkg_box, qty=1, contained=[make_pkg_values(self.uom_unit, qty=30)], + self.pkg_box, + qty=1, + contained=[make_pkg_values(self.uom_unit, qty=30)], ), make_pkg_values(self.uom_unit, qty=25, contained=None), ] diff --git a/stock_packaging_calculator/tests/utils.py b/stock_packaging_calculator/tests/utils.py index 8c885894cd43..26c4b357b1cb 100644 --- a/stock_packaging_calculator/tests/utils.py +++ b/stock_packaging_calculator/tests/utils.py @@ -3,8 +3,7 @@ def make_pkg_values(record, **kw): - """Helper to generate test values for packaging. - """ + """Helper to generate test values for packaging.""" name = record.name if record._name == "uom.uom": is_unit = True From 725307ee8e7e073db1fa59d8f19829135039293a Mon Sep 17 00:00:00 2001 From: oca-travis Date: Tue, 21 Sep 2021 05:51:07 +0000 Subject: [PATCH 38/41] [UPD] Update stock_packaging_calculator.pot --- stock_packaging_calculator/i18n/stock_packaging_calculator.pot | 3 +++ 1 file changed, 3 insertions(+) diff --git a/stock_packaging_calculator/i18n/stock_packaging_calculator.pot b/stock_packaging_calculator/i18n/stock_packaging_calculator.pot index 1cfae5477919..bb3d494b1d82 100644 --- a/stock_packaging_calculator/i18n/stock_packaging_calculator.pot +++ b/stock_packaging_calculator/i18n/stock_packaging_calculator.pot @@ -14,16 +14,19 @@ msgstr "" "Plural-Forms: \n" #. module: stock_packaging_calculator +#: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_product__display_name #: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_qty_by_packaging_mixin__display_name msgid "Display Name" msgstr "" #. module: stock_packaging_calculator +#: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_product__id #: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_qty_by_packaging_mixin__id msgid "ID" msgstr "" #. module: stock_packaging_calculator +#: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_product____last_update #: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_qty_by_packaging_mixin____last_update msgid "Last Modified on" msgstr "" From 57f81a26057ee2a288ee477ebf8bdf74c708fb21 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 21 Sep 2021 06:07:56 +0000 Subject: [PATCH 39/41] stock_packaging_calculator 14.0.1.1.0 --- stock_packaging_calculator/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock_packaging_calculator/__manifest__.py b/stock_packaging_calculator/__manifest__.py index f2d7f8685569..711cf3f14d03 100644 --- a/stock_packaging_calculator/__manifest__.py +++ b/stock_packaging_calculator/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Stock packaging calculator", "summary": "Compute product quantity to pick by packaging", - "version": "14.0.1.0.1", + "version": "14.0.1.1.0", "development_status": "Alpha", "category": "Warehouse Management", "website": "https://github.com/OCA/stock-logistics-warehouse", From 9ded9efb55adbbbf19f6ef128b8aa1602a021dd0 Mon Sep 17 00:00:00 2001 From: Christopher Ormaza Date: Mon, 13 Dec 2021 13:14:34 -0500 Subject: [PATCH 40/41] [IMP] stock_packaging_calculator: black, isort, prettier --- .../odoo/addons/stock_packaging_calculator | 1 + setup/stock_packaging_calculator/setup.py | 6 ++++++ stock_packaging_calculator/models/product.py | 4 ++-- 3 files changed, 9 insertions(+), 2 deletions(-) create mode 120000 setup/stock_packaging_calculator/odoo/addons/stock_packaging_calculator create mode 100644 setup/stock_packaging_calculator/setup.py diff --git a/setup/stock_packaging_calculator/odoo/addons/stock_packaging_calculator b/setup/stock_packaging_calculator/odoo/addons/stock_packaging_calculator new file mode 120000 index 000000000000..d3e128c8deaa --- /dev/null +++ b/setup/stock_packaging_calculator/odoo/addons/stock_packaging_calculator @@ -0,0 +1 @@ +../../../../stock_packaging_calculator \ No newline at end of file diff --git a/setup/stock_packaging_calculator/setup.py b/setup/stock_packaging_calculator/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/stock_packaging_calculator/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index 71d7063688d5..5f93424247df 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -89,12 +89,12 @@ def _ordered_packaging(self): "_packaging_name_getter", self._packaging_name_getter ) packagings = sorted( - [ + ( Packaging(x.id, name_getter(x), x.qty, x.barcode, False) for x in self.packaging_ids.filtered(custom_filter) # Exclude the ones w/ zero qty as they are useless for the math if x.qty - ], + ), reverse=True, key=lambda x: x.qty, ) From 9c005aedbb15b18895dff4a7e5cc61559de2d073 Mon Sep 17 00:00:00 2001 From: Christopher Ormaza Date: Mon, 13 Dec 2021 13:25:18 -0500 Subject: [PATCH 41/41] [15.0][MIG] stock_packaging_calculator --- stock_packaging_calculator/README.rst | 3 ++- stock_packaging_calculator/__manifest__.py | 2 +- stock_packaging_calculator/readme/CONTRIBUTORS.rst | 1 + stock_packaging_calculator/tests/common.py | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/stock_packaging_calculator/README.rst b/stock_packaging_calculator/README.rst index 1a28cd164625..c0561d5eed8f 100644 --- a/stock_packaging_calculator/README.rst +++ b/stock_packaging_calculator/README.rst @@ -23,7 +23,7 @@ Stock packaging calculator :target: https://runbot.odoo-community.org/runbot/153/14.0 :alt: Try me on Runbot -|badge1| |badge2| |badge3| |badge4| |badge5| +|badge1| |badge2| |badge3| |badge4| |badge5| Basic module providing an helper method to calculate the quantity of product by packaging. @@ -107,6 +107,7 @@ Contributors ~~~~~~~~~~~~ * Simone Orsi +* Christopher Ormaza Maintainers ~~~~~~~~~~~ diff --git a/stock_packaging_calculator/__manifest__.py b/stock_packaging_calculator/__manifest__.py index 711cf3f14d03..63bb0de56a90 100644 --- a/stock_packaging_calculator/__manifest__.py +++ b/stock_packaging_calculator/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Stock packaging calculator", "summary": "Compute product quantity to pick by packaging", - "version": "14.0.1.1.0", + "version": "15.0.1.0.0", "development_status": "Alpha", "category": "Warehouse Management", "website": "https://github.com/OCA/stock-logistics-warehouse", diff --git a/stock_packaging_calculator/readme/CONTRIBUTORS.rst b/stock_packaging_calculator/readme/CONTRIBUTORS.rst index f583948be842..5daf85c2e99c 100644 --- a/stock_packaging_calculator/readme/CONTRIBUTORS.rst +++ b/stock_packaging_calculator/readme/CONTRIBUTORS.rst @@ -1 +1,2 @@ * Simone Orsi +* Christopher Ormaza diff --git a/stock_packaging_calculator/tests/common.py b/stock_packaging_calculator/tests/common.py index c528958ce17e..4aa0579a5541 100644 --- a/stock_packaging_calculator/tests/common.py +++ b/stock_packaging_calculator/tests/common.py @@ -1,9 +1,9 @@ # Copyright 2020 Camptocamp SA # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl) -from odoo.tests import SavepointCase +from odoo.tests import TransactionCase -class TestCommon(SavepointCase): +class TestCommon(TransactionCase): at_install = False post_install = True