From e82106392015b0bb46091f7283d9cf55aca08a05 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Tue, 26 May 2020 10:40:07 +0200 Subject: [PATCH 01/38] 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 000000000..0650744f6 --- /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 000000000..4399bba90 --- /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 000000000..9649db77a --- /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 000000000..5dd73bac3 --- /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 000000000..f583948be --- /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 000000000..b470f8a94 --- /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 000000000..e4881d5e6 --- /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 000000000..04c47e711 --- /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 000000000..c5836b941 --- /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 57761fcd37519a697b74583cf4f7266967b256df Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 8 Jun 2020 09:56:12 +0200 Subject: [PATCH 02/38] 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 5dd73bac3..68907d2a4 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -8,24 +8,19 @@ from odoo.tools import float_compare 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 @@ class Product(models.Model): 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 c5836b941..af1c11541 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -48,19 +48,6 @@ class TestCalc(SavepointCase): [(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 86ba203ea37ad72d23a8c42e2a96c8543fcb73f8 Mon Sep 17 00:00:00 2001 From: oca-travis Date: Mon, 8 Jun 2020 08:00:41 +0000 Subject: [PATCH 03/38] [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 000000000..775124a33 --- /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 7f3e3239e9e6143d0c7fa727c1e7e3b6e192f80b Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 8 Jun 2020 10:18:02 +0200 Subject: [PATCH 04/38] stock_packaging_calculator: return dict instead of tuple Allows to ship more information with each element in the list. --- stock_packaging_calculator/README.rst | 103 ++++ stock_packaging_calculator/models/product.py | 27 +- .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 449 ++++++++++++++++++ .../tests/test_packaging_calc.py | 33 +- 5 files changed, 592 insertions(+), 20 deletions(-) create mode 100644 stock_packaging_calculator/README.rst create mode 100644 stock_packaging_calculator/static/description/icon.png 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 000000000..54e81badc --- /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/models/product.py b/stock_packaging_calculator/models/product.py index 68907d2a4..d5dca58e1 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 @@ class Product(models.Model): 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/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 z?n?eq1XP>p-IM$Z^C;2L3itnbJZAip*Zo0aw2bs8@(s^~*8T9go!%dHcAz2lM;`yp zD=7&xjFV$S&5uDaiScyD?B-i1ze`+CoRtz`Wn+Zl&#s4&}MO{@N!ufrzjG$B79)Y2d3tBk&)TxUTw@QS0TEL_?njX|@vq?Uz(nBFK5Pq7*xj#u*R&i|?7+6# z+|r_n#SW&LXhtheZdah{ZVoqwyT{D>MC3nkFF#N)xLi{p7J1jXlmVeb;cP5?e(=f# zuT7fvjSbjS781v?7{)-X3*?>tq?)Yd)~|1{BDS(pqC zC}~H#WXlkUW*H5CDOo<)#x7%RY)A;ShGhI5s*#cRDA8YgqG(HeKDx+#(ZQ?386dv! zlXCO)w91~Vw4AmOcATuV653fa9R$fyK8ul%rG z-wfS zihugoZyr38Im?Zuh6@RcF~t1anQu7>#lPpb#}4cOA!EM11`%f*07RqOVkmX{p~KJ9 z^zP;K#|)$`^Rb{rnHGH{~>1(fawV0*Z#)}M`m8-?ZJV<+e}s9wE# z)l&az?w^5{)`S(%MRzxdNqrs1n*-=jS^_jqE*5XDrA0+VE`5^*p3CuM<&dZEeCjoz zR;uu_H9ZPZV|fQq`Cyw4nscrVwi!fE6ciMmX$!_hN7uF;jjKG)d2@aC4ropY)8etW=xJvni)8eHi`H$%#zn^WJ5NLc-rqk|u&&4Z6fD_m&JfSI1Bvb?b<*n&sfl0^t z=HnmRl`XrFvMKB%9}>PaA`m-fK6a0(8=qPkWS5bb4=v?XcWi&hRY?O5HdulRi4?fN zlsJ*N-0Qw+Yic@s0(2uy%F@ib;GjXt01Fmx5XbRo6+n|pP(&nodMoap^z{~q ziEeaUT@Mxe3vJSfI6?uLND(CNr=#^W<1b}jzW58bIfyWTDle$mmS(|x-0|2UlX+9k zQ^EX7Nw}?EzVoBfT(-LT|=9N@^hcn-_p&sqG z&*oVs2JSU+N4ZD`FhCAWaS;>|wH2G*Id|?pa#@>tyxX`+4HyIArWDvVrX)2WAOQff z0qyHu&-S@i^MS-+j--!pr4fPBj~_8({~e1bfcl0wI1kaoN>mJL6KUPQm5N7lB(ui1 zE-o%kq)&djzWJ}ob<-GfDlkB;F31j-VHKvQUGQ3sp`CwyGJk_i!y^sD0fqC@$9|jO zOqN!r!8-p==F@ZVP=U$qSpY(gQ0)59P1&t@y?5rvg<}E+GB}26NYPp4f2YFQrQtot5mn3wu_qprZ=>Ig-$ zbW26Ws~IgY>}^5w`vTB(G`PTZaDiGBo5o(tp)qli|NeV( z@H_=R8V39rt5J5YB2Ky?4eJJ#b`_iBe2ot~6%7mLt5t8Vwi^Jy7|jWXqa3amOIoRb zOr}WVFP--DsS`1WpN%~)t3R!arKF^Q$e12KEqU36AWwnCBICpH4XCsfnyrHr>$I$4 z!DpKX$OKLWarN7nv@!uIA+~RNO)l$$w}p(;b>mx8pwYvu;dD_unryX_NhT8*Tj>BTrTTL&!?O+%Rv;b?B??gSzdp?6Uug9{ zd@V08Z$BdI?fpoCS$)t4mg4rT8Q_I}h`0d-vYZ^|dOB*Q^S|xqTV*vIg?@fVFSmMpaw0qtTRbx} z({Pg?#{2`sc9)M5N$*N|4;^t$+QP?#mov zGVC@I*lBVrOU-%2y!7%)fAKjpEFsgQc4{amtiHb95KQEwvf<(3T<9-Zm$xIew#P22 zc2Ix|App^>v6(3L_MCU0d3W##AB0M~3D00EWoKZqsJYT(#@w$Y_H7G22M~ApVFTRHMI_3be)Lkn#0F*V8Pq zc}`Cjy$bE;FJ6H7p=0y#R>`}-m4(0F>%@P|?7fx{=R^uFdISRnZ2W_xQhD{YuR3t< z{6yxu=4~JkeA;|(J6_nv#>Nvs&FuLA&PW^he@t(UwFFE8)|a!R{`E`K`i^ZnyE4$k z;(749Ix|oi$c3QbEJ3b~D_kQsPz~fIUKym($a_7dJ?o+40*OLl^{=&oq$<#Q(yyrp z{J-FAniyAw9tPbe&IhQ|a`DqFTVQGQ&Gq3!C2==4x{6EJwiPZ8zub-iXoUtkJiG{} zPaR&}_fn8_z~(=;5lD-aPWD3z8PZS@AaUiomF!G8I}Mf>e~0g#BelA-5#`cj;O5>N Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/stock_packaging_calculator/static/description/index.html b/stock_packaging_calculator/static/description/index.html new file mode 100644 index 000000000..b786d10ea --- /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.

+
+
+
+ + diff --git a/stock_packaging_calculator/tests/test_packaging_calc.py b/stock_packaging_calculator/tests/test_packaging_calc.py index af1c11541..b25d8cb9c 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -30,24 +30,33 @@ class TestCalc(SavepointCase): 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 dfb9ccc4fc5e4f9ec22749fb86f666bfb4625a49 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 8 Jun 2020 11:47:32 +0200 Subject: [PATCH 05/38] stock_packaging_calculator: add contained packaging compute Optionally include contained packaging qty. --- stock_packaging_calculator/README.rst | 43 ++++++--- stock_packaging_calculator/models/product.py | 26 ++++-- .../readme/DESCRIPTION.rst | 17 ---- stock_packaging_calculator/readme/USAGE.rst | 36 ++++++++ .../static/description/index.html | 77 ++++++++++------ .../tests/test_packaging_calc.py | 89 ++++++++++++++++++- 6 files changed, 228 insertions(+), 60 deletions(-) create mode 100644 stock_packaging_calculator/readme/USAGE.rst 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:

-
    -
  • 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. @@ -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, + ) From b2812f668dd0867dc71a4d3e29b868e3616e5b92 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 9 Jun 2020 08:09:47 +0000 Subject: [PATCH 06/38] 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 4399bba90..731c263b0 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 d4683ba7649ec38c36a90e89c7f24ff78e7b736c Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Tue, 9 Jun 2020 10:35:32 +0200 Subject: [PATCH 07/38] stock_packaging_calculator: make contained mapping computed Allows to reuse the mapping every time is needed. --- .../i18n/stock_packaging_calculator.pot | 10 +++ stock_packaging_calculator/models/product.py | 66 +++++++++++++++---- .../tests/test_packaging_calc.py | 49 +++++++++++++- 3 files changed, 109 insertions(+), 16 deletions(-) diff --git a/stock_packaging_calculator/i18n/stock_packaging_calculator.pot b/stock_packaging_calculator/i18n/stock_packaging_calculator.pot index 775124a33..92a274c4a 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 "" diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index 2679b0fd6..4d74f5652 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 @@ class Product(models.Model): {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 39d03e01b..3482d4329 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -4,6 +4,10 @@ from odoo.tests import SavepointCase class TestCalc(SavepointCase): + + at_install = False + post_install = True + @classmethod def setUpClass(cls): super().setUpClass() @@ -12,7 +16,6 @@ class TestCalc(SavepointCase): 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 @@ class TestCalc(SavepointCase): {"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 @@ class TestCalc(SavepointCase): "id": self.uom_unit.id, "qty": 5, "name": self.uom_unit.name, - "contained": [], + "contained": None, }, ] self.assertEqual( @@ -140,7 +183,7 @@ class TestCalc(SavepointCase): "id": self.uom_unit.id, "qty": 25, "name": self.uom_unit.name, - "contained": [], + "contained": None, }, ] self.assertEqual( From ecbc07b0b68ad544ac9e42f10b3ae7dccac82e71 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Mon, 22 Jun 2020 13:05:22 +0000 Subject: [PATCH 08/38] 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 731c263b0..3285010a5 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 d60d56b300dcdec37863ee7323cd2efaff1d480e Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Tue, 30 Jun 2020 14:16:42 +0200 Subject: [PATCH 09/38] 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 4d74f5652..88332de81 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -68,10 +68,15 @@ class Product(models.Model): ) 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 3482d4329..fcee8dd5a 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -103,6 +103,20 @@ class TestCalc(SavepointCase): ] 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 f989e720de6aa379fe9ced2c5b1a931da49c92b4 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Tue, 30 Jun 2020 14:29:58 +0200 Subject: [PATCH 10/38] 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 88332de81..b81ac2107 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -72,10 +72,14 @@ class Product(models.Model): 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 fcee8dd5a..7a6b643b3 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -117,6 +117,25 @@ class TestCalc(SavepointCase): 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 0a0fa94d2818bf7e2035d510a2290dc72d84d15d Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 1 Jul 2020 06:51:32 +0000 Subject: [PATCH 11/38] 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 3285010a5..1fa33ec03 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 aa4d9b348de8de9a9c8e47a2b2f4545ae11c141f Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Wed, 15 Jul 2020 17:29:44 +0200 Subject: [PATCH 12/38] 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 5bde39e25..d6cc846cd 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 1fa33ec03..3ab1b5ebd 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 b81ac2107..752af5d3f 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 62793c551..ebff58a67 100644 --- a/stock_packaging_calculator/static/description/index.html +++ b/stock_packaging_calculator/static/description/index.html @@ -367,7 +367,7 @@ ul.auto-toc { !! 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 7a6b643b3..b3b4fbbd6 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 ed50ffbcbcdf9fbb95f1e7a81b0ae9f48b08106b Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Thu, 16 Jul 2020 11:23:27 +0000 Subject: [PATCH 13/38] 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 3ab1b5ebd..cfbf256af 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 9b00109e5c8ddb44862e783adee51c154a861899 Mon Sep 17 00:00:00 2001 From: Tonow-c2c Date: Mon, 6 Jul 2020 16:26:25 +0200 Subject: [PATCH 14/38] [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 752af5d3f..31e8db70b 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -100,7 +100,12 @@ class Product(models.Model): 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 b3b4fbbd6..19207c592 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -39,13 +39,24 @@ class TestCalc(SavepointCase): "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 @@ class TestCalc(SavepointCase): "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 @@ class TestCalc(SavepointCase): 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 @@ class TestCalc(SavepointCase): "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 @@ class TestCalc(SavepointCase): "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 @@ class TestCalc(SavepointCase): "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 @@ class TestCalc(SavepointCase): "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 @@ class TestCalc(SavepointCase): "id": self.uom_unit.id, "qty": 5, "name": self.uom_unit.name, + "is_unit": True, "contained": None, }, ] @@ -192,6 +289,7 @@ class TestCalc(SavepointCase): "id": self.pkg_big_box.id, "qty": 10, "name": self.pkg_big_box.name, + "is_unit": False, }, ], }, @@ -199,6 +297,7 @@ class TestCalc(SavepointCase): "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 @@ class TestCalc(SavepointCase): "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 @@ class TestCalc(SavepointCase): "id": self.uom_unit.id, "qty": 25, "name": self.uom_unit.name, + "is_unit": True, "contained": None, }, ] From 06f4cbc06ff8dcb8271481379dfc212a4e7a4839 Mon Sep 17 00:00:00 2001 From: Akim Juillerat Date: Thu, 16 Jul 2020 10:35:53 +0200 Subject: [PATCH 15/38] 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 31e8db70b..4d670716f 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -63,6 +63,7 @@ class Product(models.Model): {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 @@ class Product(models.Model): 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 @@ class Product(models.Model): 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 19207c592..c7ab405bd 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -55,7 +55,7 @@ class TestCalc(SavepointCase): "id": self.uom_unit.id, "qty": 50, "name": self.uom_unit.name, - "is_unit": False, + "is_unit": True, }, ], }, @@ -86,7 +86,7 @@ class TestCalc(SavepointCase): "id": self.uom_unit.id, "qty": 50, "name": self.uom_unit.name, - "is_unit": False, + "is_unit": True, }, ], }, @@ -202,7 +202,12 @@ class TestCalc(SavepointCase): 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 @@ class TestCalc(SavepointCase): "id": self.pkg_big_box.id, "qty": 10, "name": self.pkg_big_box.name, + "is_unit": False, }, ], }, @@ -251,7 +257,12 @@ class TestCalc(SavepointCase): "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 @@ class TestCalc(SavepointCase): "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 @@ class TestCalc(SavepointCase): "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 @@ class TestCalc(SavepointCase): "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 @@ class TestCalc(SavepointCase): "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 fee724691375ae6f5690ce8fcdbd52792c850e21 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 17 Jul 2020 17:20:45 +0200 Subject: [PATCH 16/38] 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 cfbf256af..d5aa12d87 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 062ddecf59ccef6822d27ff810be3ec4ba11cdfd Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 3 Aug 2020 10:20:33 +0200 Subject: [PATCH 17/38] 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 4d670716f..89eadf5c5 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -79,12 +79,16 @@ class Product(models.Model): """ 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 @@ class Product(models.Model): # 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 90bab81864fc5d1d0337ffdac35eb93bdbc5f8ef Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 3 Aug 2020 10:30:06 +0200 Subject: [PATCH 18/38] 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 89eadf5c5..69a85ec09 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -102,10 +102,13 @@ class Product(models.Model): """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 c7ab405bd..1bc721086 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -234,6 +234,24 @@ class TestCalc(SavepointCase): 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 7f2bb551515ac4de0fb355a56011f6a11f22f6bd Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 11 Aug 2020 13:57:32 +0000 Subject: [PATCH 19/38] 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 d5aa12d87..d31c5853c 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 a989a1e429a63873ede5dcaf8c453d14ae9ab434 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 20/38] [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 69a85ec09..a4327076c 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -65,7 +65,9 @@ class Product(models.Model): """ 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 9c3c1bd108af4e74fc78c0dd48526ae08b45abfb 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 21/38] [MIG] stock_packaging_calculator: Migration to 14.0 --- stock_packaging_calculator/README.rst | 10 +++++----- stock_packaging_calculator/__manifest__.py | 2 +- .../i18n/stock_packaging_calculator.pot | 17 ++++++++++++++++- .../static/description/index.html | 6 +++--- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/stock_packaging_calculator/README.rst b/stock_packaging_calculator/README.rst index d6cc846cd..1a28cd164 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/__manifest__.py b/stock_packaging_calculator/__manifest__.py index d31c5853c..856e147c3 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", diff --git a/stock_packaging_calculator/i18n/stock_packaging_calculator.pot b/stock_packaging_calculator/i18n/stock_packaging_calculator.pot index 92a274c4a..664b39319 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" diff --git a/stock_packaging_calculator/static/description/index.html b/stock_packaging_calculator/static/description/index.html index ebff58a67..0bfe447f7 100644 --- a/stock_packaging_calculator/static/description/index.html +++ b/stock_packaging_calculator/static/description/index.html @@ -367,7 +367,7 @@ ul.auto-toc { !! 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 @@ Only for development or testing purpose, do not use in production.

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

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 1580d8e78fb42e4418edaaa388c6753bf836285c Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 5 Feb 2021 10:01:09 +0000 Subject: [PATCH 22/38] 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 856e147c3..f2d7f8685 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 9976cabe5b4e9754116f0ab70ee81b288d705c89 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Tue, 11 May 2021 16:09:28 +0200 Subject: [PATCH 23/38] 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 a4327076c..13ae10f3d 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -9,7 +9,7 @@ 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 is_unit") +Packaging = namedtuple("Packaging", "id name qty barcode is_unit") class Product(models.Model): @@ -83,7 +83,7 @@ class Product(models.Model): 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 @@ class Product(models.Model): # 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 @@ class Product(models.Model): "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 1bc721086..99470229b 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 @@ class TestCalc(SavepointCase): } ) 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 @@ class TestCalc(SavepointCase): 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 @@ class TestCalc(SavepointCase): 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 @@ class TestCalc(SavepointCase): 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 @@ class TestCalc(SavepointCase): ) 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, - }, + 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.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, - }, - ], - }, - { - "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 000000000..cd9e12560 --- /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 20c1fc9099dc694a691796cbf62b1a59cdd04846 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 28 May 2021 09:03:14 +0200 Subject: [PATCH 24/38] 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 13ae10f3d..cdf83edf9 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -80,7 +80,9 @@ class Product(models.Model): 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 @@ class Product(models.Model): ) 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 d883283b28521ef12a538137f271cff8501bbe26 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 28 May 2021 09:04:51 +0200 Subject: [PATCH 25/38] 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 cdf83edf9..026c7971b 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 @@ from odoo.addons.base_sparse_field.models.fields import Serialized # 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 @@ class Product(models.Model): "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 04c47e711..441177cdc 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 000000000..c528958ce --- /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 99470229b..c51347e40 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 000000000..93998846c --- /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 9f0984f3545e89289abba3d739f14570b00ad07f Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 28 May 2021 08:59:14 +0200 Subject: [PATCH 26/38] 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 9649db77a..fb73bb4d1 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 000000000..4721fe862 --- /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 d9dce661ed70769a5f8c343233d334ca4164bb69 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 3 Jun 2021 13:03:04 +0200 Subject: [PATCH 27/38] 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 026c7971b..57fc4dbdc 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 6134dd47fb838500af895919f2515b7090c05723 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 11 Jun 2021 13:08:24 +0200 Subject: [PATCH 28/38] 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 57fc4dbdc..71d706368 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -153,11 +153,16 @@ class Product(models.Model): "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 @@ class Product(models.Model): 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 4721fe862..9065a7b1f 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 @@ class ProductQtyByPackagingMixin(models.AbstractModel): 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 @@ class ProductQtyByPackagingMixin(models.AbstractModel): 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 93998846c..7fca487de 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 @@ 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(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 @@ class TestAsStr(TestCommon): 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 @@ class TestAsStr(TestCommon): ), "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 35cfc4c6efba26b13a7b6578fdfd3551c71a6dc6 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 15 Jul 2021 16:57:16 +0200 Subject: [PATCH 29/38] 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. --- .../i18n/stock_packaging_calculator.pot | 44 ------------------- stock_packaging_calculator/tests/utils.py | 5 ++- 2 files changed, 4 insertions(+), 45 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 664b39319..000000000 --- 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 "" diff --git a/stock_packaging_calculator/tests/utils.py b/stock_packaging_calculator/tests/utils.py index cd9e12560..8c885894c 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 1c99a9b56a2e0765917c1bfdeb32d350aa4a86bb Mon Sep 17 00:00:00 2001 From: nguyen hoang hiep Date: Tue, 14 Sep 2021 05:57:42 +0000 Subject: [PATCH 30/38] [IMP] stock_packaging_calculator: black, isort, prettier --- .../i18n/stock_packaging_calculator.pot | 57 +++++++++++++++++++ .../models/product_qty_by_packaging_mixin.py | 3 +- .../tests/test_packaging_calc.py | 8 ++- stock_packaging_calculator/tests/utils.py | 3 +- 4 files changed, 65 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 000000000..bb3d494b1 --- /dev/null +++ b/stock_packaging_calculator/i18n/stock_packaging_calculator.pot @@ -0,0 +1,57 @@ +# 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 +#: 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 "" + +#. 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 9065a7b1f..693ad561a 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 @@ from odoo import api, fields, models 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 c51347e40..3d4bbfa85 100644 --- a/stock_packaging_calculator/tests/test_packaging_calc.py +++ b/stock_packaging_calculator/tests/test_packaging_calc.py @@ -121,7 +121,9 @@ class TestCalc(TestCommon): 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 @@ class TestCalc(TestCommon): ], ), 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 8c885894c..26c4b357b 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 fd86673585c7500e235ef86ac9a61bcbac2f8cb7 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 21 Sep 2021 06:07:56 +0000 Subject: [PATCH 31/38] 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 f2d7f8685..711cf3f14 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 64ff32b504d22d1766e201143aab422df88ba3ed Mon Sep 17 00:00:00 2001 From: Christopher Ormaza Date: Mon, 13 Dec 2021 13:14:34 -0500 Subject: [PATCH 32/38] [IMP] stock_packaging_calculator: black, isort, prettier --- stock_packaging_calculator/models/product.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stock_packaging_calculator/models/product.py b/stock_packaging_calculator/models/product.py index 71d706368..5f9342424 100644 --- a/stock_packaging_calculator/models/product.py +++ b/stock_packaging_calculator/models/product.py @@ -89,12 +89,12 @@ class Product(models.Model): "_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 7295ebfe392ee2526e64f4385211bdf3dfed8dd1 Mon Sep 17 00:00:00 2001 From: Christopher Ormaza Date: Mon, 13 Dec 2021 13:25:18 -0500 Subject: [PATCH 33/38] [15.0][MIG] stock_packaging_calculator --- stock_packaging_calculator/README.rst | 11 +++++----- stock_packaging_calculator/__manifest__.py | 2 +- .../i18n/stock_packaging_calculator.pot | 20 +------------------ .../readme/CONTRIBUTORS.rst | 1 + .../static/description/index.html | 7 ++++--- stock_packaging_calculator/tests/common.py | 4 ++-- 6 files changed, 15 insertions(+), 30 deletions(-) diff --git a/stock_packaging_calculator/README.rst b/stock_packaging_calculator/README.rst index 1a28cd164..adf3a79d7 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/14.0/stock_packaging_calculator + :target: https://github.com/OCA/stock-logistics-warehouse/tree/15.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-14-0/stock-logistics-warehouse-14-0-stock_packaging_calculator + :target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-15-0/stock-logistics-warehouse-15-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/14.0 + :target: https://runbot.odoo-community.org/runbot/153/15.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. @@ -107,6 +107,7 @@ Contributors ~~~~~~~~~~~~ * Simone Orsi +* Christopher Ormaza Maintainers ~~~~~~~~~~~ @@ -121,6 +122,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/__manifest__.py b/stock_packaging_calculator/__manifest__.py index 711cf3f14..63bb0de56 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/i18n/stock_packaging_calculator.pot b/stock_packaging_calculator/i18n/stock_packaging_calculator.pot index bb3d494b1..3a5552568 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 14.0\n" +"Project-Id-Version: Odoo Server 15.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" @@ -13,24 +13,6 @@ 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 -#: 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 "" - #. module: stock_packaging_calculator #: model:ir.model.fields,field_description:stock_packaging_calculator.field_product_product__packaging_contained_mapping msgid "Packaging Contained Mapping" diff --git a/stock_packaging_calculator/readme/CONTRIBUTORS.rst b/stock_packaging_calculator/readme/CONTRIBUTORS.rst index f583948be..5daf85c2e 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/static/description/index.html b/stock_packaging_calculator/static/description/index.html index 0bfe447f7..0745b2c6d 100644 --- a/stock_packaging_calculator/static/description/index.html +++ b/stock_packaging_calculator/static/description/index.html @@ -367,7 +367,7 @@ ul.auto-toc { !! 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 @@ Only for development or testing purpose, do not use in production.

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.

@@ -454,6 +454,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome

Contributors

@@ -463,7 +464,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome

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/tests/common.py b/stock_packaging_calculator/tests/common.py index c528958ce..4aa0579a5 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 From b7db64a1aba89c91c490b6332f0310b52a771e85 Mon Sep 17 00:00:00 2001 From: Thierry Ducrest Date: Mon, 10 Oct 2022 14:08:14 +0200 Subject: [PATCH 34/38] [MIG] stock_packaging_calculator: Migration to 16.0 --- stock_packaging_calculator/README.rst | 19 +++++++------------ stock_packaging_calculator/__manifest__.py | 4 ++-- .../i18n/stock_packaging_calculator.pot | 10 +++++----- .../static/description/index.html | 12 +++--------- 4 files changed, 17 insertions(+), 28 deletions(-) diff --git a/stock_packaging_calculator/README.rst b/stock_packaging_calculator/README.rst index adf3a79d7..481f22104 100644 --- a/stock_packaging_calculator/README.rst +++ b/stock_packaging_calculator/README.rst @@ -7,31 +7,26 @@ Stock packaging calculator !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status - :alt: Alpha + :alt: Beta .. |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/15.0/stock_packaging_calculator + :target: https://github.com/OCA/stock-logistics-warehouse/tree/16.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-15-0/stock-logistics-warehouse-15-0-stock_packaging_calculator + :target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-16-0/stock-logistics-warehouse-16-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/15.0 + :target: https://runbot.odoo-community.org/runbot/153/16.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. -.. 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:: @@ -91,7 +86,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. @@ -122,6 +117,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/__manifest__.py b/stock_packaging_calculator/__manifest__.py index 63bb0de56..b7e7dfee9 100644 --- a/stock_packaging_calculator/__manifest__.py +++ b/stock_packaging_calculator/__manifest__.py @@ -3,8 +3,8 @@ { "name": "Stock packaging calculator", "summary": "Compute product quantity to pick by packaging", - "version": "15.0.1.0.0", - "development_status": "Alpha", + "version": "16.0.1.0.0", + "development_status": "Beta", "category": "Warehouse Management", "website": "https://github.com/OCA/stock-logistics-warehouse", "author": "Camptocamp, Odoo Community Association (OCA)", diff --git a/stock_packaging_calculator/i18n/stock_packaging_calculator.pot b/stock_packaging_calculator/i18n/stock_packaging_calculator.pot index 3a5552568..a97f6908e 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 15.0\n" +"Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" @@ -19,13 +19,13 @@ msgid "Packaging Contained Mapping" msgstr "" #. module: stock_packaging_calculator -#: model:ir.model,name:stock_packaging_calculator.model_product_product -msgid "Product" +#: 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,name:stock_packaging_calculator.model_product_qty_by_packaging_mixin -msgid "Product Qty By Packaging (Mixin)" +#: model:ir.model,name:stock_packaging_calculator.model_product_product +msgid "Product Variant" msgstr "" #. module: stock_packaging_calculator diff --git a/stock_packaging_calculator/static/description/index.html b/stock_packaging_calculator/static/description/index.html index 0745b2c6d..aa733ea1c 100644 --- a/stock_packaging_calculator/static/description/index.html +++ b/stock_packaging_calculator/static/description/index.html @@ -367,14 +367,8 @@ ul.auto-toc { !! 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

+

Beta 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

-

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

    @@ -439,7 +433,7 @@ Only for development or testing purpose, do not use in production.

    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.

@@ -464,7 +458,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome

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 174132de0b1ce304bc0df2a71134f92c72365d2b Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Mon, 20 Mar 2023 14:34:13 +0000 Subject: [PATCH 35/38] stock_packaging_calculator 16.0.1.0.1 --- stock_packaging_calculator/README.rst | 15 ++++--- stock_packaging_calculator/__manifest__.py | 2 +- .../static/description/index.html | 42 ++++++++++--------- 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/stock_packaging_calculator/README.rst b/stock_packaging_calculator/README.rst index 481f22104..0668c20a3 100644 --- a/stock_packaging_calculator/README.rst +++ b/stock_packaging_calculator/README.rst @@ -2,10 +2,13 @@ Stock packaging calculator ========================== -.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:7f199b0f860e5108a32b20d253fdf4e90e51bd2ba3088e0b48c5465c1274b5a8 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status @@ -19,11 +22,11 @@ Stock packaging calculator .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png :target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-16-0/stock-logistics-warehouse-16-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/16.0 - :alt: Try me on Runbot +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/stock-logistics-warehouse&target_branch=16.0 + :alt: Try me on Runboat -|badge1| |badge2| |badge3| |badge4| |badge5| +|badge1| |badge2| |badge3| |badge4| |badge5| Basic module providing an helper method to calculate the quantity of product by packaging. @@ -85,7 +88,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 +If you spotted it first, help us to smash it by providing a detailed and welcomed `feedback `_. Do not contact contributors directly about support or help with technical issues. diff --git a/stock_packaging_calculator/__manifest__.py b/stock_packaging_calculator/__manifest__.py index b7e7dfee9..929393373 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": "16.0.1.0.0", + "version": "16.0.1.0.1", "development_status": "Beta", "category": "Warehouse Management", "website": "https://github.com/OCA/stock-logistics-warehouse", diff --git a/stock_packaging_calculator/static/description/index.html b/stock_packaging_calculator/static/description/index.html index aa733ea1c..39478209a 100644 --- a/stock_packaging_calculator/static/description/index.html +++ b/stock_packaging_calculator/static/description/index.html @@ -1,20 +1,20 @@ - + - + Stock packaging calculator