Skip to content

Commit

Permalink
Merge PR #2211 into 16.0
Browse files Browse the repository at this point in the history
Signed-off-by jbaudoux
  • Loading branch information
OCA-git-bot committed Dec 31, 2024
2 parents 9a4a273 + e28db5c commit 66442b8
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 15 deletions.
64 changes: 60 additions & 4 deletions stock_location_product_restriction/models/stock_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,25 @@ def _compute_product_restriction(self):
@api.depends("product_restriction")
def _compute_restriction_violation(self):
records = self
self.env["stock.quant"].flush_model(
[
"product_id",
"location_id",
"quantity",
"reserved_quantity",
"available_quantity",
"inventory_quantity",
]
)
self.flush_model(
[
"product_restriction",
]
)
ProductProduct = self.env["product.product"]
precision_digits = max(
6, self.sudo().env.ref("product.decimal_product_uom").digits * 2
)
SQL = """
SELECT
stock_quant.location_id,
Expand All @@ -82,6 +100,11 @@ def _compute_restriction_violation(self):
stock_quant.location_id in %s
and stock_location.id = stock_quant.location_id
and stock_location.product_restriction = 'same'
/* Mimic the _unlink_zero_quant() query in Odoo */
AND (NOT (round(quantity::numeric, %s) = 0 OR quantity IS NULL)
OR NOT round(reserved_quantity::numeric, %s) = 0
OR NOT (round(inventory_quantity::numeric, %s) = 0
OR inventory_quantity IS NULL))
GROUP BY
stock_quant.location_id
HAVING count(distinct(product_id)) > 1
Expand All @@ -93,7 +116,9 @@ def _compute_restriction_violation(self):
if not ids:
product_ids_by_location_id = dict()
else:
self.env.cr.execute(SQL, (ids,))
self.env.cr.execute(
SQL, (ids, precision_digits, precision_digits, precision_digits)
)
product_ids_by_location_id = dict(self.env.cr.fetchall())
for record in self:
record_id = record.id
Expand All @@ -105,18 +130,37 @@ def _compute_restriction_violation(self):
has_restriction_violation = True
restriction_violation_message = _(
"This location should only contain items of the same "
"product but it contains items of products {products}"
).format(products=" | ".join(products.mapped("name")))
"product but it contains items of products %(products)s",
products=" | ".join(products.mapped("name")),
)
record.has_restriction_violation = has_restriction_violation
record.restriction_violation_message = restriction_violation_message

def _search_has_restriction_violation(self, operator, value):
precision_digits = max(
6, self.sudo().env.ref("product.decimal_product_uom").digits * 2
)
search_has_violation = (
# has_restriction_violation != False
(operator in NEGATIVE_TERM_OPERATORS and not value)
# has_restriction_violation = True
or (operator not in NEGATIVE_TERM_OPERATORS and value)
)
self.env["stock.quant"].flush_model(
[
"product_id",
"location_id",
"quantity",
"reserved_quantity",
"available_quantity",
"inventory_quantity",
]
)
self.flush_model(
[
"product_restriction",
]
)
SQL = """
SELECT
stock_quant.location_id
Expand All @@ -126,11 +170,23 @@ def _search_has_restriction_violation(self, operator, value):
WHERE
stock_location.id = stock_quant.location_id
and stock_location.product_restriction = 'same'
/* Mimic the _unlink_zero_quant() query in Odoo */
AND (NOT (round(quantity::numeric, %s) = 0 OR quantity IS NULL)
OR NOT round(reserved_quantity::numeric, %s) = 0
OR NOT (round(inventory_quantity::numeric, %s) = 0
OR inventory_quantity IS NULL))
GROUP BY
stock_quant.location_id
HAVING count(distinct(product_id)) > 1
"""
self.env.cr.execute(SQL)
self.env.cr.execute(
SQL,
(
precision_digits,
precision_digits,
precision_digits,
),
)
violation_ids = [r[0] for r in self.env.cr.fetchall()]
if search_has_violation:
op = "in"
Expand Down
39 changes: 32 additions & 7 deletions stock_location_product_restriction/models/stock_quant.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,27 @@ def _check_location_product_restriction(self):
products = ProductProduct.browse(list(product_ids))
error_msgs.append(
_(
"The location {location} can only contain items of the same "
"The location %(location)s can only contain items of the same "
"product. You plan to put different products into "
"this location. ({products})"
).format(
"this location. (%(products)s)",
location=location.name,
products=", ".join(products.mapped("name")),
)
)
# Get existing product already in the locations
precision_digits = max(
6, self.sudo().env.ref("product.decimal_product_uom").digits * 2
)
self.flush_model(
[
"product_id",
"location_id",
"quantity",
"reserved_quantity",
"available_quantity",
"inventory_quantity",
]
)
SQL = """
SELECT
location_id,
Expand All @@ -52,10 +64,23 @@ def _check_location_product_restriction(self):
stock_quant
WHERE
location_id in %s
/* Mimic the _unlink_zero_quant() query in Odoo */
AND (NOT (round(quantity::numeric, %s) = 0 OR quantity IS NULL)
OR NOT round(reserved_quantity::numeric, %s) = 0
OR NOT (round(inventory_quantity::numeric, %s) = 0
OR inventory_quantity IS NULL))
GROUP BY
location_id
"""
self.env.cr.execute(SQL, (tuple(quants_to_check.mapped("location_id").ids),))
self.env.cr.execute(
SQL,
(
tuple(quants_to_check.mapped("location_id").ids),
precision_digits,
precision_digits,
precision_digits,
),
)
existing_product_ids_by_location_id = dict(self.env.cr.fetchall())

for (
Expand All @@ -69,12 +94,12 @@ def _check_location_product_restriction(self):
to_move_products = ProductProduct.browse(list(product_ids_to_add))
error_msgs.append(
_(
"You plan to add the product {product} into the location {location} "
"You plan to add the product %(product)s into the location"
" %(location)s "
"but the location must only contain items of same "
"product and already contains items of other "
"product(s) "
"({existing_products})."
).format(
"(%(existing_products)s).",
product=" | ".join(to_move_products.mapped("name")),
location=location.name,
existing_products=" | ".join(existing_products.mapped("name")),
Expand Down
29 changes: 25 additions & 4 deletions stock_location_product_restriction/tests/test_stock_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,31 +38,31 @@ def setUpClass(cls):

# quants
StockQuant = cls.env["stock.quant"]
StockQuant.create(
cls.quant_1_lvl_1_1_1 = StockQuant.create(
{
"product_id": cls.product_1.id,
"location_id": cls.loc_lvl_1_1_1.id,
"quantity": 10.0,
"owner_id": cls.env.user.id,
}
)
StockQuant.create(
cls.quant_2_lvl_1_1_1 = StockQuant.create(
{
"product_id": cls.product_2.id,
"location_id": cls.loc_lvl_1_1_1.id,
"quantity": 10.0,
"owner_id": cls.env.user.id,
}
)
StockQuant.create(
cls.quant_1_lvl_1_1_2 = StockQuant.create(
{
"product_id": cls.product_1.id,
"location_id": cls.loc_lvl_1_1_2.id,
"quantity": 10.0,
"owner_id": cls.env.user.id,
}
)
StockQuant.create(
cls.quant_2_lvl_1_1_2 = StockQuant.create(
{
"product_id": cls.product_2.id,
"location_id": cls.loc_lvl_1_1_2.id,
Expand Down Expand Up @@ -262,3 +262,24 @@ def test_05(self):
# Check location creation
with Form(self.StockLocation) as location_form:
location_form.name = "Test"

def test_zero_quant(self):
"""
Data:
* Location level_1_1_1 with 2 different products no restriction
Test Case:
1. Check restriction message
2. Change product 1 quant to 0.0
3. Set restriction 'same' on location level_1_1_1
4. Check restriction message
Expected result:
1. No restriction message
"""
self.loc_lvl_1_1_1.product_restriction = "any"
self.assertFalse(self.loc_lvl_1_1_1.has_restriction_violation)
self.assertFalse(self.loc_lvl_1_1_1.restriction_violation_message)
self.quant_1_lvl_1_1_1.inventory_quantity = 0.0
self.quant_1_lvl_1_1_1._apply_inventory()
self.loc_lvl_1_1_1.product_restriction = "same"
self.assertFalse(self.loc_lvl_1_1_1.has_restriction_violation)
self.assertFalse(self.loc_lvl_1_1_1.restriction_violation_message)
36 changes: 36 additions & 0 deletions stock_location_product_restriction/tests/test_stock_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ def _get_products_in_location(self, location):
.mapped("product_id")
)

def _get_quants_in_location(self, location):
return self.env["stock.quant"].search([("location_id", "=", location.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(
Expand Down Expand Up @@ -323,3 +326,36 @@ def test_06(self):
picking.move_line_ids.location_dest_id = self.location_1
with self.assertRaises(ValidationError):
self._process_picking(picking)

def test_location_with_zero_quant(self):
"""
Data:
location_1 with product_1 but with product restriction = 'same'
a picking with one move: product_2 -> location_1
Test case:
# set the location dest only on the move line and the parent on the
# move
Process the picking
Expected result:
ValidationError
"""

quants = self._get_quants_in_location(self.location_1)
quants.with_context(inventory_mode=True).write({"inventory_quantity": 0.0})
quants._apply_inventory()

self.location_1.specific_product_restriction = "same"
self.location_1.invalidate_recordset()
parent_location = self.location_1.location_id
picking = self._create_and_assign_picking(
[
ShortMoveInfo(
product=self.product_2,
location_dest=parent_location,
qty=2,
),
],
location_dest=parent_location,
)
picking.move_line_ids.location_dest_id = self.location_1
self._process_picking(picking)

0 comments on commit 66442b8

Please sign in to comment.