diff --git a/stock_packaging_calculator/README.rst b/stock_packaging_calculator/README.rst index 54e81badc..5bde39e25 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:: - [(2, "Pallet"), (1, "Big Box"), (7, "Box"), (10, "Units")] + >>> 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. -.. 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/models/product.py b/stock_packaging_calculator/models/product.py index d5dca58e1..2679b0fd6 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -13,7 +13,7 @@ Packaging = namedtuple("Packaging", "id name qty") 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 @@ class Product(models.Model): 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 b470f8a94..1d6fceb5c 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 000000000..73f05a708 --- /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/static/description/index.html b/stock_packaging_calculator/static/description/index.html index b786d10ea..62793c551 100644 --- a/stock_packaging_calculator/static/description/index.html +++ b/stock_packaging_calculator/static/description/index.html @@ -369,21 +369,6 @@ ul.auto-toc { !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

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:

- -

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. @@ -393,18 +378,56 @@ Only for development or testing purpose, do not use in production.

Table of contents

+
+

Usage

+

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)
+
+[
+    {"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:

+
+
+>>> 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 @@ Only for development or testing purpose, do not use in production.
-

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 @@ If you spotted it first, help us smashing it by providing a detailed and welcome

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 diff --git a/stock_packaging_calculator/tests/test_packaging_calc.py b/stock_packaging_calculator/tests/test_packaging_calc.py index b25d8cb9c..39d03e01b 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -9,7 +9,6 @@ class TestCalc(SavepointCase): 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 @@ class TestCalc(SavepointCase): {"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, + )