mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
@@ -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"
|
||||
|
||||
@@ -1 +1 @@
|
||||
from . import stock_location, stock_move
|
||||
from . import stock_location, stock_quant
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user