mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
stock_packaging_calculator: add contained packaging compute
Optionally include contained packaging qty.
This commit is contained in:
@@ -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 <https://odoo-community.org/page/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 <https://odoo-community.org/page/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
|
||||
======================
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
36
stock_packaging_calculator/readme/USAGE.rst
Normal file
36
stock_packaging_calculator/readme/USAGE.rst
Normal file
@@ -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": []},},
|
||||
]
|
||||
@@ -369,21 +369,6 @@ ul.auto-toc {
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
||||
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Alpha" src="https://img.shields.io/badge/maturity-Alpha-red.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/tree/13.0/stock_packaging_calculator"><img alt="OCA/stock-logistics-warehouse" src="https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/stock-logistics-warehouse-13-0/stock-logistics-warehouse-13-0-stock_packaging_calculator"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/153/13.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
|
||||
<p>Basic module providing an helper method to calculate the quantity of product by packaging.</p>
|
||||
<p>Imagine you have the following packagings:</p>
|
||||
<ul class="simple">
|
||||
<li>Pallet: 1000 Units</li>
|
||||
<li>Big box: 500 Units</li>
|
||||
<li>Box: 50 Units</li>
|
||||
</ul>
|
||||
<p>and you have to pick from your warehouse 2860 Units.</p>
|
||||
<p>Then you can do:</p>
|
||||
<blockquote>
|
||||
<pre class="doctest-block">
|
||||
>>> product.product_qty_by_packaging(2860)
|
||||
</pre>
|
||||
<p>[(2, “Pallet”), (1, “Big Box”), (7, “Box”), (10, “Units”)]</p>
|
||||
</blockquote>
|
||||
<p>With this you can show a proper message to warehouse operators to quickly pick the quantity they need.</p>
|
||||
<div class="admonition important">
|
||||
<p class="first admonition-title">Important</p>
|
||||
<p class="last">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.
|
||||
<p><strong>Table of contents</strong></p>
|
||||
<div class="contents local topic" id="contents">
|
||||
<ul class="simple">
|
||||
<li><a class="reference internal" href="#known-issues-roadmap" id="id1">Known issues / Roadmap</a></li>
|
||||
<li><a class="reference internal" href="#bug-tracker" id="id2">Bug Tracker</a></li>
|
||||
<li><a class="reference internal" href="#credits" id="id3">Credits</a><ul>
|
||||
<li><a class="reference internal" href="#authors" id="id4">Authors</a></li>
|
||||
<li><a class="reference internal" href="#contributors" id="id5">Contributors</a></li>
|
||||
<li><a class="reference internal" href="#maintainers" id="id6">Maintainers</a></li>
|
||||
<li><a class="reference internal" href="#usage" id="id1">Usage</a></li>
|
||||
<li><a class="reference internal" href="#known-issues-roadmap" id="id2">Known issues / Roadmap</a></li>
|
||||
<li><a class="reference internal" href="#bug-tracker" id="id3">Bug Tracker</a></li>
|
||||
<li><a class="reference internal" href="#credits" id="id4">Credits</a><ul>
|
||||
<li><a class="reference internal" href="#authors" id="id5">Authors</a></li>
|
||||
<li><a class="reference internal" href="#contributors" id="id6">Contributors</a></li>
|
||||
<li><a class="reference internal" href="#maintainers" id="id7">Maintainers</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="usage">
|
||||
<h1><a class="toc-backref" href="#id1">Usage</a></h1>
|
||||
<p>Imagine you have the following packagings:</p>
|
||||
<ul class="simple">
|
||||
<li>Pallet: 1000 Units</li>
|
||||
<li>Big box: 500 Units</li>
|
||||
<li>Box: 50 Units</li>
|
||||
</ul>
|
||||
<p>and you have to pick from your warehouse 2860 Units.</p>
|
||||
<p>Then you can do:</p>
|
||||
<blockquote>
|
||||
<pre class="code literal-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"},
|
||||
]
|
||||
</pre>
|
||||
</blockquote>
|
||||
<p>With this you can show a proper message to warehouse operators to quickly pick the quantity they need.</p>
|
||||
<p>Optionally you can get contained packaging by passing <cite>with_contained</cite> flag:</p>
|
||||
<blockquote>
|
||||
<pre class="code literal-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": []},},
|
||||
]
|
||||
</pre>
|
||||
</blockquote>
|
||||
</div>
|
||||
<div class="section" id="known-issues-roadmap">
|
||||
<h1><a class="toc-backref" href="#id1">Known issues / Roadmap</a></h1>
|
||||
<h1><a class="toc-backref" href="#id2">Known issues / Roadmap</a></h1>
|
||||
<p>TODO</p>
|
||||
<ol class="arabic simple">
|
||||
<li>Fractional quantities (eg: 0.5 Kg) are lost when counting units</li>
|
||||
@@ -412,7 +435,7 @@ Only for development or testing purpose, do not use in production.
|
||||
</ol>
|
||||
</div>
|
||||
<div class="section" id="bug-tracker">
|
||||
<h1><a class="toc-backref" href="#id2">Bug Tracker</a></h1>
|
||||
<h1><a class="toc-backref" href="#id3">Bug Tracker</a></h1>
|
||||
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/issues">GitHub Issues</a>.
|
||||
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
|
||||
<p>Do not contact contributors directly about support or help with technical issues.</p>
|
||||
</div>
|
||||
<div class="section" id="credits">
|
||||
<h1><a class="toc-backref" href="#id3">Credits</a></h1>
|
||||
<h1><a class="toc-backref" href="#id4">Credits</a></h1>
|
||||
<div class="section" id="authors">
|
||||
<h2><a class="toc-backref" href="#id4">Authors</a></h2>
|
||||
<h2><a class="toc-backref" href="#id5">Authors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Camptocamp</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="contributors">
|
||||
<h2><a class="toc-backref" href="#id5">Contributors</a></h2>
|
||||
<h2><a class="toc-backref" href="#id6">Contributors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Simone Orsi <<a class="reference external" href="mailto:simahawk@gmail.com">simahawk@gmail.com</a>></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="maintainers">
|
||||
<h2><a class="toc-backref" href="#id6">Maintainers</a></h2>
|
||||
<h2><a class="toc-backref" href="#id7">Maintainers</a></h2>
|
||||
<p>This module is maintained by the OCA.</p>
|
||||
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
|
||||
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user