Merge PR #2075 into 16.0

Signed-off-by jbaudoux
This commit is contained in:
OCA-git-bot
2024-10-07 15:35:56 +00:00
6 changed files with 83 additions and 44 deletions

View File

@@ -81,8 +81,9 @@ msgstr "Mouvement de stock"
#, python-format
msgid ""
"The location {location} can only contain items of the same product. You plan "
"to move different products to this location. ({products})"
msgstr ""
"to put different products into this location. ({products})"
msgstr "L'emplacement {location} ne peut contenir que des articles identiques. Vous "
"essayer de mettre d'autres articles dans cet emplacement. ({products}) "
#. module: stock_location_product_restriction
#. odoo-python
@@ -103,10 +104,13 @@ msgstr "Ne respecte pas les règles"
#: code:addons/stock_location_product_restriction/models/stock_move.py:0
#, python-format
msgid ""
"You plan to move the product {product} to the location {location} but the "
"You plan to add the product {product} into the location {location} but the "
"location must only contain items of same product and already contains items "
"of other product(s) ({existing_products})."
msgstr ""
msgstr "Vous essayer d'ajouter le produit {product} dans l'emplacemet {location} mais l'"
"emplacement ne peut contenir que des articles identiques et contient déja "
"d'autes articles ({existing_products})"
#~ msgid "Has restriction violation"
#~ msgstr "Ne respecte pas les règles"

View File

@@ -1 +1 @@
from . import stock_location, stock_move
from . import stock_location, stock_quant

View File

@@ -86,8 +86,15 @@ class StockLocation(models.Model):
stock_quant.location_id
HAVING count(distinct(product_id)) > 1
"""
self.env.cr.execute(SQL, (tuple(records.ids),))
product_ids_by_location_id = dict(self.env.cr.fetchall())
# Browse only real record ids
ids = tuple(
[record.id for record in records if not isinstance(record.id, fields.NewId)]
)
if not ids:
product_ids_by_location_id = dict()
else:
self.env.cr.execute(SQL, (ids,))
product_ids_by_location_id = dict(self.env.cr.fetchall())
for record in self:
record_id = record.id
has_restriction_violation = False

View File

@@ -1,55 +1,49 @@
# Copyright 2020 ACSONE SA/NV
# Copyright 2024 ACSONE SA/NV
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from collections import defaultdict
from odoo import _, models
from odoo import _, api, models
from odoo.exceptions import ValidationError
class StockMove(models.Model):
class StockQuant(models.Model):
_inherit = "stock.move"
_inherit = "stock.quant"
@api.constrains("location_id", "product_id")
def _check_location_product_restriction(self):
"""
Check if the move can be executed according to potential restriction
Check if the quant can be put into the location according to restriction
defined on the stock_location
"""
StockLocation = self.env["stock.location"]
ProductProduct = self.env["product.product"]
# We only check moves with a location_dest that can
# We only check quants with a location_id that can
# only contain the same product
moves_to_ckeck = self.filtered(
lambda m: m.location_dest_id.product_restriction == "same"
quants_to_check = self.filtered(
lambda q: q.location_id.product_restriction == "same"
)
if not moves_to_ckeck:
if not quants_to_check:
return
product_ids_location_dest_id = defaultdict(set)
product_ids_location_id = defaultdict(set)
error_msgs = []
# check dest locations into the stock moves
for move in moves_to_ckeck:
product_ids_location_dest_id[move.location_dest_id.id].add(
move.product_id.id
)
for location_id, product_ids in product_ids_location_dest_id.items():
for quant in quants_to_check:
product_ids_location_id[quant.location_id.id].add(quant.product_id.id)
for location_id, product_ids in product_ids_location_id.items():
if len(product_ids) > 1:
location = StockLocation.browse(location_id)
products = ProductProduct.browse(list(product_ids))
error_msgs.append(
_(
"The location {location} can only contain items of the same "
"product. You plan to move different products to "
"product. You plan to put different products into "
"this location. ({products})"
).format(
location=location.name,
products=", ".join(products.mapped("name")),
)
)
# check dest locations by taking into account product already into the
# locations
# here we use a plain SQL to avoid performance issue
# Get existing product already in the locations
SQL = """
SELECT
location_id,
@@ -61,22 +55,21 @@ class StockMove(models.Model):
GROUP BY
location_id
"""
self.env.cr.execute(
SQL, (tuple(moves_to_ckeck.mapped("location_dest_id").ids),)
)
self.env.cr.execute(SQL, (tuple(quants_to_check.mapped("location_id").ids),))
existing_product_ids_by_location_id = dict(self.env.cr.fetchall())
for (
location_dest_id,
location_id,
existing_product_ids,
) in existing_product_ids_by_location_id.items():
product_ids_to_move = product_ids_location_dest_id[location_dest_id]
if set(existing_product_ids).symmetric_difference(product_ids_to_move):
location = StockLocation.browse(location_dest_id)
product_ids_to_add = product_ids_location_id[location_id]
if set(existing_product_ids).symmetric_difference(product_ids_to_add):
location = StockLocation.browse(location_id)
existing_products = ProductProduct.browse(existing_product_ids)
to_move_products = ProductProduct.browse(list(product_ids_to_move))
to_move_products = ProductProduct.browse(list(product_ids_to_add))
error_msgs.append(
_(
"You plan to move the product {product} to the location {location} "
"You plan to add the product {product} into the location {location} "
"but the location must only contain items of same "
"product and already contains items of other "
"product(s) "
@@ -87,9 +80,6 @@ class StockMove(models.Model):
existing_products=" | ".join(existing_products.mapped("name")),
)
)
if error_msgs:
raise ValidationError("\n".join(error_msgs))
def _action_done(self, cancel_backorder=False):
self._check_location_product_restriction()
return super()._action_done(cancel_backorder=cancel_backorder)

View File

@@ -1,7 +1,6 @@
# Copyright 2020 ACSONE SA/NV
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo.tests.common import TransactionCase
from odoo.tests.common import Form, TransactionCase
class TestStockLocation(TransactionCase):
@@ -258,3 +257,8 @@ class TestStockLocation(TransactionCase):
self.loc_lvl_1_1_1.flush_recordset()
self.assertTrue(self.loc_lvl_1_1_1.has_restriction_violation)
self.assertTrue(self.loc_lvl_1_1_1.restriction_violation_message)
def test_05(self):
# Check location creation
with Form(self.StockLocation) as location_form:
location_form.name = "Test"

View File

@@ -289,3 +289,37 @@ class TestStockMove(TransactionCase):
)
with self.assertRaises(ValidationError):
self._process_picking(picking)
def test_06(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
"""
self.assertEqual(
self.product_1,
self._get_products_in_location(self.location_1),
)
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
with self.assertRaises(ValidationError):
self._process_picking(picking)