mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
stock_packaging_calculator: make contained mapping computed
Allows to reuse the mapping every time is needed.
This commit is contained in:
committed by
Sébastien BEAU
parent
a9beb26ed9
commit
35d3eb7cc2
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user