mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
Add stock_packaging_calculator
This commit is contained in:
committed by
Sébastien BEAU
parent
90cd216a32
commit
0655a516e2
1
stock_packaging_calculator/__init__.py
Normal file
1
stock_packaging_calculator/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
15
stock_packaging_calculator/__manifest__.py
Normal file
15
stock_packaging_calculator/__manifest__.py
Normal file
@@ -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"],
|
||||
}
|
||||
1
stock_packaging_calculator/models/__init__.py
Normal file
1
stock_packaging_calculator/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import product
|
||||
51
stock_packaging_calculator/models/product.py
Normal file
51
stock_packaging_calculator/models/product.py
Normal file
@@ -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
|
||||
1
stock_packaging_calculator/readme/CONTRIBUTORS.rst
Normal file
1
stock_packaging_calculator/readme/CONTRIBUTORS.rst
Normal file
@@ -0,0 +1 @@
|
||||
* Simone Orsi <simahawk@gmail.com>
|
||||
18
stock_packaging_calculator/readme/DESCRIPTION.rst
Normal file
18
stock_packaging_calculator/readme/DESCRIPTION.rst
Normal file
@@ -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.
|
||||
4
stock_packaging_calculator/readme/ROADMAP.rst
Normal file
4
stock_packaging_calculator/readme/ROADMAP.rst
Normal file
@@ -0,0 +1,4 @@
|
||||
TODO
|
||||
|
||||
1. Fractional quantities (eg: 0.5 Kg) are lost when counting units
|
||||
2. Maybe rely on `packaging_uom`
|
||||
1
stock_packaging_calculator/tests/__init__.py
Normal file
1
stock_packaging_calculator/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_packaging_calc
|
||||
66
stock_packaging_calculator/tests/test_packaging_calc.py
Normal file
66
stock_packaging_calculator/tests/test_packaging_calc.py
Normal file
@@ -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")])
|
||||
Reference in New Issue
Block a user