stock_location_package_restriction: add nopackage

Allow to configure that you don't want a package on the location.
Refactor code.
This commit is contained in:
Jacques-Etienne Baudoux
2024-05-31 12:08:39 +02:00
parent aac1969906
commit 8bb53050df
10 changed files with 532 additions and 291 deletions

View File

@@ -1,13 +1,15 @@
# Copyright 2023 Raumschmiede (http://www.raumschmiede.de)
# Copyright 2023 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
"name": "Stock Location Package Restriction",
"summary": """
Control if the location can contain products not in a package""",
Control if the location can contain products in a package""",
"version": "14.0.1.2.1",
"category": "Warehouse Management",
"author": "Raumschmiede.de, BCIM, Odoo Community Association (OCA)",
"maintainters": ["jbaudoux"],
"website": "https://github.com/OCA/stock-logistics-warehouse",
"license": "AGPL-3",
"depends": ["stock"],

View File

@@ -1,8 +1,11 @@
# Copyright 2023 Raumschmiede (http://www.raumschmiede.de)
# Copyright 2024 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
NOPACKAGE = "nopackage"
SINGLEPACKAGE = "singlepackage"
MULTIPACKAGE = "multiplepackage"
@@ -16,21 +19,96 @@ class StockLocation(models.Model):
Control if the location can contain products not in a package.
Options:
* False (not set): Not mandatory, the location can contain products
not part of a package
* False (not set): No restriction, the location can contain products
with and without package
* Forbidden: The location cannot have products part of a package
* Mandatory and unique: The location cannot have products not
part of a package and you cannot have more than 1 package on
the location
* Mandatory and not unique: The location cannot have products
not part of a package and you may have store multiple packages
not part of a package and you may store multiple packages
on the location
""",
store=True,
)
@api.model
def _selection_package_restriction(self):
return [
(NOPACKAGE, "Forbidden"),
(MULTIPACKAGE, "Mandatory"),
(SINGLEPACKAGE, "Mandatory and unique"),
]
def _check_package_restriction(self, move_lines=None):
"""Check if the location respect the package restrictions
:param move_lines: Optional planned move_line to validate its destination location
:raises ValidationError: if the restriction is not respected
"""
error_msgs = []
move_lines = move_lines or self.env["stock.move.line"]
for location in self:
if not location.package_restriction:
continue
invalid_products = False
if location.package_restriction == NOPACKAGE:
if move_lines:
move_lines_with_package = move_lines.filtered("result_package_id")
if move_lines_with_package:
invalid_products = move_lines_with_package.product_id
else:
quants_with_package = location.quant_ids.filtered(
lambda q: q.package_id and q.quantity
)
if quants_with_package:
invalid_products = quants_with_package.product_id
if invalid_products:
error_msgs.append(
_(
"A package is not allowed on the location {location}."
"You cannot move the product(s) {product} with a package."
).format(
location=location.display_name,
product=", ".join(invalid_products.mapped("display_name")),
)
)
continue
# SINGLE or MULTI
if move_lines:
move_lines_without_package = move_lines.filtered(
lambda ml: not ml.result_package_id
)
if move_lines_without_package:
invalid_products = move_lines_without_package.product_id
else:
quants_without_package = location.quant_ids.filtered(
lambda q: not q.package_id and q.quantity
)
if quants_without_package:
invalid_products = quants_without_package.product_id
if invalid_products:
error_msgs.append(
_(
"A package is mandatory on the location {location}.\n"
"You cannot move the product(s) {product} without a package."
).format(
location=location.display_name,
product=", ".join(invalid_products.mapped("display_name")),
)
)
continue
if location.package_restriction == MULTIPACKAGE:
continue
# SINGLE
packages = (
move_lines.result_package_id
| location.quant_ids.filtered("quantity").package_id
)
if len(packages) > 1:
error_msgs.append(
_("Only one package is allowed on the location {location}.").format(
location=location.display_name
)
)
if error_msgs:
raise ValidationError("\n".join(error_msgs))

View File

@@ -1,112 +1,21 @@
# Copyright 2023 Raumschmiede (http://www.raumschmiede.de)
# Copyright 2024 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, models
from odoo.exceptions import ValidationError
from odoo.tools import groupby
from .stock_location import SINGLEPACKAGE
from odoo import models
class StockMove(models.Model):
_inherit = "stock.move"
def _check_location_package_restriction(
self, new_location=None, only_qty_done=True
):
"""Check if the moves can be done regarding potential package restrictions
By default will only check move lines with a quantity done and their set
destination location.
If `new_location` is set it will be use as destination location.
If `only_qty_done` is False the check is executed on all
related move lines.
"""
Package = self.env["stock.quant.package"]
error_msgs = []
dest_location = new_location or self.move_line_ids.location_dest_id
quants_grouped = self.env["stock.quant"].read_group(
[
("location_id", "in", dest_location.ids),
("location_id.package_restriction", "!=", False),
("quantity", ">", 0),
],
["location_id", "package_id:array_agg"],
"location_id",
)
location_packages = {
g["location_id"][0]: set(g["package_id"]) for g in quants_grouped
}
lines_being_processed = (
self.move_line_ids.filtered(lambda line: line.qty_done)
if only_qty_done
else self.move_line_ids
)
for location, move_lines in groupby(
lines_being_processed, lambda m: m.location_dest_id
):
if not location.package_restriction:
continue
if new_location:
location = new_location
existing_package_ids = location_packages.get(location.id, set())
existing_packages = Package.browse(existing_package_ids).exists()
new_packages = Package.browse()
for move_line in move_lines:
package = move_line.result_package_id
# CASE: Package restiction exists but there is no package in move
if not package:
error_msgs.append(
_(
"A package is mandatory on the location {location}. "
"You cannot move the product {product} without a package."
).format(
location=location.display_name,
product=move_line.product_id.display_name,
)
)
continue
if location.package_restriction != SINGLEPACKAGE:
continue
# CASE: Package on location, new package is different
if existing_package_ids and package.id not in existing_package_ids:
error_msgs.append(
_(
"Only one package is allowed on the location {location}."
"You cannot add the {package}, there is already "
"{existing_packages}."
).format(
location=location.display_name,
existing_packages=", ".join(
existing_packages.mapped("name")
),
package=package.display_name,
)
)
continue
# CASE: Multiple packages in Move but single location
# with singlepackage Restriction
if new_packages and package not in new_packages:
error_msgs.append(
_(
"Only one package is allowed on the location {location}."
"You cannot move multiple packages to it:"
"{packages}"
).format(
location=location.display_name,
packages=", ".join(new_packages.mapped("display_name")),
)
)
continue
new_packages |= package
if error_msgs:
raise ValidationError("\n".join(error_msgs))
def _check_location_package_restriction(self):
"""Check if the moves can be done regarding potential package restrictions"""
self.filtered(
lambda m: m.state == "done"
).location_dest_id._check_package_restriction()
def _action_done(self, cancel_backorder=False):
res = super()._action_done(cancel_backorder=cancel_backorder)
self._check_location_package_restriction()
return super()._action_done(cancel_backorder=cancel_backorder)
return res

View File

@@ -1,3 +1,3 @@
* Raumschmiede <Raumschmiede.de>
* Jacques-Etienne Baudoux <je@bcim.be>
* Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
* Juan Miguel Sánchez Arce <juan.sanchez@camptocamp.com>

View File

@@ -1,11 +1,14 @@
Configure on the location if the contained products must be part of a package
and if you allow multiple package. When the module is installed, by default
there is no restriction on the locations.
Configure on the location the package restriction.
The options are:
* False (not set): No restriction, the location can contain products with and
without package
* Forbidden: The location cannot have products part of a package
* Mandatory and unique: The location cannot have products not part of a
package and you cannot have more than 1 package on the location
* Mandatory and not unique: The location cannot have products not part of a
package and you may have store multiple packages on the location
* Not mandatory: The location can contain products not part of a package
* Mandatory and not unique: The location cannot have products not part of a
package and you may store multiple packages on the location
When the module is installed, by default there is no restriction on the locations.

View File

@@ -1 +1,3 @@
from . import test_stock_inventory
from . import test_stock_move
from . import test_stock_move_line

View File

@@ -0,0 +1,142 @@
# Copyright 2023 Raumschmiede (http://www.raumschmiede.de)
# Copyright 2024 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from collections import namedtuple
from odoo.tests.common import SavepointCase
ShortMoveInfo = namedtuple(
"ShortMoveInfo", ["product", "location_dest", "qty", "package_id"]
)
class TestLocationPackageRestrictionCommon(SavepointCase):
@classmethod
def setUpClass(cls):
"""
Data:
2 products: product_1, product_2
2 packages: pack_1, pack_2
1 new warehouse: warehouse1
2 new locations: location1 and location2 are children of
warehouse1's stock location and without
restriction
stock:
* 50 product_1 in location_1
* 0 product_2 in stock
"""
super().setUpClass()
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
cls.uom_unit = cls.env.ref("uom.product_uom_unit")
Product = cls.env["product.product"]
cls.product_1 = Product.create(
{"name": "Wood", "type": "product", "uom_id": cls.uom_unit.id}
)
cls.product_2 = Product.create(
{"name": "Stone", "type": "product", "uom_id": cls.uom_unit.id}
)
# Warehouses
cls.warehouse_1 = cls.env["stock.warehouse"].create(
{
"name": "Base Warehouse",
"reception_steps": "one_step",
"delivery_steps": "ship_only",
"code": "BWH",
}
)
# Locations
cls.location_1 = cls.env["stock.location"].create(
{
"name": "TestLocation1",
"posx": 3,
"location_id": cls.warehouse_1.lot_stock_id.id,
}
)
cls.location_2 = cls.env["stock.location"].create(
{
"name": "TestLocation2",
"posx": 4,
"location_id": cls.warehouse_1.lot_stock_id.id,
}
)
# Packages:
Package = cls.env["stock.quant.package"]
cls.pack_1 = Package.create({"name": "Package 1"})
cls.pack_2 = Package.create({"name": "Package 2"})
cls.supplier_location = cls.env.ref("stock.stock_location_suppliers")
# partner
cls.partner_1 = cls.env["res.partner"].create(
{"name": "Raumschmiede.de", "email": "info@raumschmiede.de"}
)
# picking type
cls.picking_type_in = cls.env.ref("stock.picking_type_in")
cls.StockMove = cls.env["stock.move"]
cls.StockPicking = cls.env["stock.picking"]
@classmethod
def _change_product_qty(cls, product, location, package, qty):
cls.env["stock.quant"].with_context(inventory_mode=True).create(
{
"product_id": product.id,
"package_id": package and package.id,
"inventory_quantity": qty,
"location_id": location.id,
}
)
@classmethod
def _get_package_in_location(cls, location):
return (
cls.env["stock.quant"]
.search([("location_id", "=", location.id)])
.mapped("package_id")
)
@classmethod
def _create_and_assign_picking(cls, short_move_infos, location_dest=None):
location_dest = location_dest or cls.location_1
picking_in = cls.StockPicking.create(
{
"partner_id": cls.partner_1.id,
"picking_type_id": cls.picking_type_in.id,
"location_id": cls.supplier_location.id,
"location_dest_id": location_dest.id,
}
)
for move_info in short_move_infos:
cls.StockMove.create(
{
"name": move_info.product.name,
"product_id": move_info.product.id,
"product_uom_qty": move_info.qty,
"product_uom": move_info.product.uom_id.id,
"picking_id": picking_in.id,
"location_id": cls.supplier_location.id,
"location_dest_id": move_info.location_dest.id,
}
)
picking_in.action_confirm()
for move_info in short_move_infos:
line = picking_in.move_line_ids.filtered(
lambda x: x.product_id.id == move_info.product.id
)
if line:
line.result_package_id = move_info.package_id
return picking_in
@classmethod
def _process_picking(cls, picking):
picking.action_assign()
for line in picking.move_line_ids:
line.qty_done = line.product_qty
picking.button_validate()

View File

@@ -0,0 +1,79 @@
# Copyright 2023 Raumschmiede (http://www.raumschmiede.de)
# Copyright 2024 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo.exceptions import ValidationError
from odoo.addons.stock_location_package_restriction.models.stock_location import (
NOPACKAGE,
SINGLEPACKAGE,
)
from .common import TestLocationPackageRestrictionCommon
class TestInventory(TestLocationPackageRestrictionCommon):
def test_inventory_no_restriction(self):
"""
Data:
location_1 without package_restriction
location_1 with 50 product_1 and pack_1
Test case:
Add qty of product_2 into location_1 with pack_2
Expected result:
The location contains the 2 products in 2 different packages
"""
# Inventory Add product_1 to location_1
self._change_product_qty(self.product_1, self.location_1, self.pack_1, 50)
self.assertEqual(self.pack_1, self._get_package_in_location(self.location_1))
self._change_product_qty(self.product_2, self.location_1, self.pack_2, 10)
self.assertEqual(
self.pack_1 | self.pack_2,
self._get_package_in_location(self.location_1),
)
def test_inventory_no_package_success(self):
"""
Data:
location_1 with no package_restriction
Test case:
Add qty of product_1 into location_1
Expected result:
The location contains the product
"""
self.location_1.package_restriction = NOPACKAGE
# Inventory Add product_1 to location_1
self._change_product_qty(self.product_1, self.location_1, False, 50)
def test_inventory_no_package_error(self):
"""
Data:
location_1 with no package_restriction
Test case:
Add qty of product_1 into location_1 with pack_1
Expected result:
ValidationError
"""
self.location_1.package_restriction = NOPACKAGE
# Inventory Add product_1 to location_1
with self.assertRaises(ValidationError):
self._change_product_qty(self.product_1, self.location_1, self.pack_1, 50)
def test_inventory_single_package(self):
"""
Data:
location_1 with single package restriction
location_1 with 50 product_1 and pack_1
Test case:
Add qty of product_2 into location_1 with pack_2
Expected result:
ValidationError
"""
# Inventory Add product_1 to location_1
self._change_product_qty(self.product_1, self.location_1, self.pack_1, 50)
self.assertEqual(self.pack_1, self._get_package_in_location(self.location_1))
self.location_1.package_restriction = SINGLEPACKAGE
with self.assertRaises(ValidationError):
self._change_product_qty(self.product_2, self.location_1, self.pack_2, 10)

View File

@@ -1,190 +1,24 @@
# Copyright 2023 Raumschmiede (http://www.raumschmiede.de)
# Copyright 2024 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from collections import namedtuple
from odoo.exceptions import ValidationError
from odoo.tests.common import SavepointCase
from odoo.addons.stock_location_package_restriction.models.stock_location import (
MULTIPACKAGE,
NOPACKAGE,
SINGLEPACKAGE,
)
ShortMoveInfo = namedtuple(
"ShortMoveInfo", ["product", "location_dest", "qty", "package_id"]
)
from .common import ShortMoveInfo, TestLocationPackageRestrictionCommon
class TestStockMove(SavepointCase):
@classmethod
def setUpClass(cls):
class TestStockMove(TestLocationPackageRestrictionCommon):
def test_picking_multi_package(self):
"""
Data:
2 products: product_1, product_2
2 packages: pack_1, pack_2
1 new warehouse: warehouse1
2 new locations: location1 and location2 are children of
warehouse1's stock location and without
restriction
stock:
* 50 product_1 in location_1
* 0 product_2 in stock
"""
super().setUpClass()
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
cls.uom_unit = cls.env.ref("uom.product_uom_unit")
Product = cls.env["product.product"]
cls.product_1 = Product.create(
{"name": "Wood", "type": "product", "uom_id": cls.uom_unit.id}
)
cls.product_2 = Product.create(
{"name": "Stone", "type": "product", "uom_id": cls.uom_unit.id}
)
# Warehouses
cls.warehouse_1 = cls.env["stock.warehouse"].create(
{
"name": "Base Warehouse",
"reception_steps": "one_step",
"delivery_steps": "ship_only",
"code": "BWH",
}
)
# Locations
cls.location_1 = cls.env["stock.location"].create(
{
"name": "TestLocation1",
"posx": 3,
"location_id": cls.warehouse_1.lot_stock_id.id,
}
)
cls.location_2 = cls.env["stock.location"].create(
{
"name": "TestLocation2",
"posx": 4,
"location_id": cls.warehouse_1.lot_stock_id.id,
}
)
# Packages:
Package = cls.env["stock.quant.package"]
cls.pack_1 = Package.create({"name": "Package 1"})
cls.pack_2 = Package.create({"name": "Package 2"})
cls.supplier_location = cls.env.ref("stock.stock_location_suppliers")
# partner
cls.partner_1 = cls.env["res.partner"].create(
{"name": "Raumschmiede.de", "email": "info@raumschmiede.de"}
)
# picking type
cls.picking_type_in = cls.env.ref("stock.picking_type_in")
cls.StockMove = cls.env["stock.move"]
cls.StockPicking = cls.env["stock.picking"]
@classmethod
def _change_product_qty(cls, product, location, package, qty):
cls.env["stock.quant"].with_context(inventory_mode=True).create(
{
"product_id": product.id,
"package_id": package.id,
"inventory_quantity": qty,
"location_id": location.id,
}
)
def _get_package_in_location(self, location):
return (
self.env["stock.quant"]
.search([("location_id", "=", location.id)])
.mapped("package_id")
)
def _create_and_assign_picking(self, short_move_infos, location_dest=None):
location_dest = location_dest or self.location_1
picking_in = self.StockPicking.create(
{
"partner_id": self.partner_1.id,
"picking_type_id": self.picking_type_in.id,
"location_id": self.supplier_location.id,
"location_dest_id": location_dest.id,
}
)
for move_info in short_move_infos:
self.StockMove.create(
{
"name": move_info.product.name,
"product_id": move_info.product.id,
"product_uom_qty": move_info.qty,
"product_uom": move_info.product.uom_id.id,
"picking_id": picking_in.id,
"location_id": self.supplier_location.id,
"location_dest_id": move_info.location_dest.id,
}
)
picking_in.action_confirm()
for move_info in short_move_infos:
line = picking_in.move_line_ids.filtered(
lambda x: x.product_id.id == move_info.product.id
)
if line:
line.result_package_id = move_info.package_id
return picking_in
def _process_picking(self, picking):
picking.action_assign()
for line in picking.move_line_ids:
line.qty_done = line.product_qty
picking.button_validate()
def test_00(self):
"""
Data:
location_1 without package_restriction
location_1 with 50 product_1 and pack_1
Test case:
Add qty of product_2 into location_1 with pack_2
Expected result:
The location contains the 2 products in 2 different packages
"""
# Inventory Add product_1 to location_1
self._change_product_qty(self.product_1, self.location_1, self.pack_1, 50)
self.assertEqual(self.pack_1, self._get_package_in_location(self.location_1))
self._change_product_qty(self.product_2, self.location_1, self.pack_2, 10)
self.assertEqual(
self.pack_1 | self.pack_2,
self._get_package_in_location(self.location_1),
)
def test_01(self):
"""
Data:
location_1 with single package restriction
location_1 with 50 product_1 and pack_1
Test case:
Add qty of product_2 into location_1 with pack_2
Expected result:
ValidationError
"""
# Inventory Add product_1 to location_1
self._change_product_qty(self.product_1, self.location_1, self.pack_1, 50)
self.assertEqual(self.pack_1, self._get_package_in_location(self.location_1))
self.location_1.package_restriction = SINGLEPACKAGE
with self.assertRaises(ValidationError):
self._change_product_qty(self.product_2, self.location_1, self.pack_2, 10)
def test_02(self):
"""
Data:
location_2 without product nor product restriction
location_2 without product nor package restriction
a picking with two move with destination location_2
Test case:
Process the picking
@@ -215,7 +49,56 @@ class TestStockMove(SavepointCase):
self._get_package_in_location(self.location_2),
)
def test_03(self):
def test_picking_no_package_success(self):
"""
Data:
location_1 with package restriction = 'no package'
a picking with destination location_1
Test case:
Process the picking without result package
Expected result:
Picking processed
"""
self.location_1.package_restriction = NOPACKAGE
picking = self._create_and_assign_picking(
[
ShortMoveInfo(
product=self.product_1,
location_dest=self.location_1,
qty=2,
package_id=False,
),
],
location_dest=self.location_1,
)
self._process_picking(picking)
def test_picking_no_package_error(self):
"""
Data:
location_1 with package restriction = 'no package'
a picking with destination location_1
Test case:
Process the picking with result package
Expected result:
ValidationError
"""
self.location_1.package_restriction = NOPACKAGE
picking = self._create_and_assign_picking(
[
ShortMoveInfo(
product=self.product_1,
location_dest=self.location_1,
qty=2,
package_id=self.pack_1,
),
],
location_dest=self.location_1,
)
with self.assertRaises(ValidationError):
self._process_picking(picking)
def test_picking_single_package_location_empty(self):
"""
Data:
location_2 without package but with package restriction = 'single package'
@@ -246,7 +129,7 @@ class TestStockMove(SavepointCase):
with self.assertRaises(ValidationError):
self._process_picking(picking)
def test_03_with_backorder(self):
def test_picking_single_package_with_backorder(self):
"""
Data:
location_2 without package but with package restriction = 'single package'
@@ -289,10 +172,10 @@ class TestStockMove(SavepointCase):
wizard.process()
self.assertEqual(self.pack_1, self._get_package_in_location(self.location_2))
def test_04(self):
def test_picking_no_restriction(self):
"""
Data:
location_1 with product_1 and without product restriction = 'single package'
location_1 with product_1 and without package restriction = 'single package'
a picking with two moves:
* product_1 -> location_1,
* product_2 -> location_1
@@ -331,10 +214,10 @@ class TestStockMove(SavepointCase):
self._get_package_in_location(self.location_1),
)
def test_05(self):
def test_picking_single_package_location_not_empty(self):
"""
Data:
location_1 with product_1 but with product restriction = 'single package'
location_1 with product_1 but with package restriction = 'single package'
a picking with one move: product_2 -> location_1
Test case:
Process the picking

View File

@@ -0,0 +1,143 @@
# Copyright 2024 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo.exceptions import ValidationError
from odoo.addons.stock_location_package_restriction.models.stock_location import (
MULTIPACKAGE,
NOPACKAGE,
SINGLEPACKAGE,
)
from .common import ShortMoveInfo, TestLocationPackageRestrictionCommon
class TestMoveLine(TestLocationPackageRestrictionCommon):
def test_move_line_multi_package(self):
"""
Data:
location_1 with pack1
a move line with destination location_1
Test case:
Process the picking
Expected result:
The two packages are on location 2
"""
self.location_2.package_restriction = MULTIPACKAGE
self._change_product_qty(self.product_1, self.location_1, self.pack_1, 50)
picking = self._create_and_assign_picking(
[
ShortMoveInfo(
product=self.product_2,
location_dest=self.location_1,
qty=2,
package_id=self.pack_2,
),
],
location_dest=self.location_1,
)
self.location_1._check_package_restriction(move_lines=picking.move_line_ids)
def test_move_line_no_package_success(self):
"""
Data:
location_1 with package restriction = 'no package'
a move line with destination location_1
Test case:
Process the picking without result package
Expected result:
Picking processed
"""
self.location_1.package_restriction = NOPACKAGE
picking = self._create_and_assign_picking(
[
ShortMoveInfo(
product=self.product_1,
location_dest=self.location_1,
qty=2,
package_id=False,
),
],
location_dest=self.location_1,
)
self.location_1._check_package_restriction(move_lines=picking.move_line_ids)
def test_move_line_no_package_error(self):
"""
Data:
location_1 with package restriction = 'no package'
a move line with destination location_1
Test case:
Process the picking with result package
Expected result:
ValidationError
"""
self.location_1.package_restriction = NOPACKAGE
picking = self._create_and_assign_picking(
[
ShortMoveInfo(
product=self.product_1,
location_dest=self.location_1,
qty=2,
package_id=self.pack_1,
),
],
location_dest=self.location_1,
)
with self.assertRaises(ValidationError):
self.location_1._check_package_restriction(move_lines=picking.move_line_ids)
def test_move_line_single_package_location_empty(self):
"""
Data:
location_1 without package but with package restriction = 'single package'
a move line with two move with destination location_2
Test case:
Process the picking
Expected result:
ValidationError
"""
self.location_1.package_restriction = SINGLEPACKAGE
picking = self._create_and_assign_picking(
[
ShortMoveInfo(
product=self.product_1,
location_dest=self.location_1,
qty=2,
package_id=self.pack_1,
),
],
location_dest=self.location_1,
)
self.location_1._check_package_restriction(move_lines=picking.move_line_ids)
def test_move_line_single_package_location_not_empty(self):
"""
Data:
location_1 with product_1 but with package restriction = 'single package'
a picking with one move: product_2 -> location_1
Test case:
Process the picking
Expected result:
ValidationError
"""
self._change_product_qty(self.product_1, self.location_1, self.pack_1, 50)
self.assertEqual(
self.pack_1,
self._get_package_in_location(self.location_1),
)
self.location_1.package_restriction = SINGLEPACKAGE
picking = self._create_and_assign_picking(
[
ShortMoveInfo(
product=self.product_2,
location_dest=self.location_1,
qty=2,
package_id=self.pack_2,
),
],
location_dest=self.location_1,
)
with self.assertRaises(ValidationError):
self.location_1._check_package_restriction(move_lines=picking.move_line_ids)