Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 73 additions & 1 deletion stock_restrict_lot/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from odoo import _, api, exceptions, fields, models
from odoo.exceptions import ValidationError


class StockMove(models.Model):
_inherit = "stock.move"

# seems better to not copy this field except when a move is splitted, because a move
# seems better to not copy this field except when a move is split, because a move
# can be copied in multiple different occasions and could even be copied with a
# different product...
restrict_lot_id = fields.Many2one("stock.lot", string="Restrict Lot", copy=False)
Expand Down Expand Up @@ -88,3 +89,74 @@ def _split(self, qty, restrict_partner_id=False):
if vals_list and self.restrict_lot_id:
vals_list[0]["restrict_lot_id"] = self.restrict_lot_id.id
return vals_list

def get_all_dest_moves(self):
res = self.move_dest_ids
moves_to_search = self.move_dest_ids
while moves_to_search:
new_dest_ids = moves_to_search.move_dest_ids
moves_to_search = new_dest_ids - res
res |= new_dest_ids
return res

def get_all_orig_moves(self):
res = self.move_orig_ids
moves_to_search = self.move_orig_ids
while moves_to_search:
new_orig_ids = moves_to_search.move_orig_ids
moves_to_search = new_orig_ids - res
res |= new_orig_ids
return res

def write(self, vals):
if "restrict_lot_id" not in vals:
res = super().write(vals)
if "move_dest_ids" in vals or "move_orig_ids" in vals:
for move in self:
chained_moves = (
move | move.get_all_dest_moves() | move.get_all_orig_moves()
)
if not move.restrict_lot_id and move.state not in ["done", "cancel"]:
# update restrict_lot_id on current move from chained_moves
if chained_moves.restrict_lot_id:
move.restrict_lot_id = chained_moves.restrict_lot_id[0]
else:
# update chained_moves
to_update_move = chained_moves.filtered(
lambda sm: sm.state not in ['done', 'cancel'] and
sm.restrict_lot_id != move.restrict_lot_id
)
to_update_move.restrict_lot_id = move.restrict_lot_id
else:
return res
else:
restrict_lot_id = vals.pop("restrict_lot_id")
restrict_lot = self.env["stock.lot"].browse(restrict_lot_id)
chained_moves = self | self.get_all_dest_moves() | self.get_all_orig_moves()
if any(
[
sm.state == "done" and sm.lot_ids != restrict_lot
for sm in chained_moves
]
):
raise ValidationError(
_(
"You can't modify the Lot/Serial number "
"because at least one move in the chain has "
"already been done with another Lot/Serial number."
)
)
super(StockMove, chained_moves).write({"restrict_lot_id": restrict_lot_id})
return super().write(vals)

@api.model_create_multi
def create(self, vals_list):
res = super().create(vals_list)
for move in res:
if not move.restrict_lot_id:
chained_moves = (
move | move.get_all_dest_moves() | move.get_all_orig_moves()
)
if chained_moves.restrict_lot_id:
move.restrict_lot_id = chained_moves.restrict_lot_id[0]
return res
100 changes: 100 additions & 0 deletions stock_restrict_lot/tests/test_restrict_lot.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from odoo.exceptions import ValidationError
from odoo.tests.common import TransactionCase


Expand All @@ -19,6 +20,50 @@ def setUpClass(cls):
}
)

def _create_move_with_lot(self):
move = self.env["stock.move"].create(
{
"product_id": self.product.id,
"location_id": self.output_loc.id,
"location_dest_id": self.customer_loc.id,
"product_uom_qty": 1,
"product_uom": self.product.uom_id.id,
"name": "test",
"procure_method": "make_to_order",
"warehouse_id": self.warehouse.id,
"route_ids": [(6, 0, self.warehouse.delivery_route_id.ids)],
"restrict_lot_id": self.lot.id,
}
)
move._action_confirm()
move._action_assign()
new_lot = self.env["stock.lot"].create(
{
"name": "lot2",
"product_id": self.product.id,
"company_id": self.warehouse.company_id.id,
}
)
return move, new_lot

def _create_move_dest(self, with_lot = True):
return self.env["stock.move"].create(
{
"product_id": self.product.id,
"location_id": self.customer_loc.id,
"product_uom_qty": 1,
"product_uom": self.product.uom_id.id,
"picking_type_id": self.warehouse.out_type_id.id,
"location_dest_id": self.output_loc.id,
"name": "test",
"procure_method": "make_to_order",
"warehouse_id": self.warehouse.id,
"route_ids": [(6, 0, self.warehouse.delivery_route_id.ids)],
"state": "waiting",
"restrict_lot_id": self.lot.id if with_lot else False,
}
)

def test_00_move_restrict_lot_propagation(self):
move = self.env["stock.move"].create(
{
Expand Down Expand Up @@ -224,3 +269,58 @@ def assert_move_line_per_lot_and_location(
assert_move_line_per_lot_and_location(
pick.move_line_ids_without_package, lot2, location_2, 25
)

def test_restrict_lot_propagation_dest_moves(self):
move, new_lot = self._create_move_with_lot()
move_dest = self._create_move_dest()
move.move_dest_ids = [(4, move_dest.id)]
self.assertEqual(move_dest.restrict_lot_id, self.lot)
move.restrict_lot_id = new_lot.id
self.assertEqual(move_dest.restrict_lot_id, new_lot)

def test_restrict_lot_propagation_dest_moves_without_lot(self):
move, new_lot = self._create_move_with_lot()
move_dest = self._create_move_dest(False)
move.move_dest_ids = [(4, move_dest.id)]
chained_moves = (
move | move.get_all_dest_moves() | move.get_all_orig_moves()
)
self.assertEqual(chained_moves.restrict_lot_id, self.lot)
self.assertEqual(move_dest.restrict_lot_id, self.lot)
move.restrict_lot_id = new_lot.id
self.assertEqual(move_dest.restrict_lot_id, new_lot)

def test_restrict_lot_propagation_origin_moves(self):
move, new_lot = self._create_move_with_lot()
orig_move = move.move_orig_ids
self.assertEqual(orig_move.restrict_lot_id, self.lot)
move.restrict_lot_id = new_lot.id
self.assertEqual(orig_move.restrict_lot_id, new_lot)

def test_restrict_lot_propagation_orig_moves_without_lot(self):
move, new_lot = self._create_move_with_lot()
move_dest = self._create_move_dest(False)
move_dest.move_orig_ids = [(4, move.id)]
self.assertEqual(move_dest.restrict_lot_id, self.lot)
move.restrict_lot_id = new_lot.id
self.assertEqual(move_dest.restrict_lot_id, new_lot)

def test_restrict_lot_propagation_origin_and_dest_moves(self):
move, new_lot = self._create_move_with_lot()
move_dest = self._create_move_dest()
move.move_dest_ids = [(4, move_dest.id)]
orig_move = move.move_orig_ids
self.assertEqual(move_dest.restrict_lot_id, self.lot)
self.assertEqual(orig_move.restrict_lot_id, self.lot)
move.restrict_lot_id = new_lot.id
self.assertEqual(orig_move.restrict_lot_id, new_lot)
self.assertEqual(move_dest.restrict_lot_id, new_lot)

def test_restrict_lot_no_propagation_error(self):
move, new_lot = self._create_move_with_lot()
orig_move = move.move_orig_ids
orig_move.state = "done"
self.assertEqual(orig_move.state, "done")
with self.assertRaises(ValidationError) as m:
move.restrict_lot_id = new_lot.id
self.assertIn("You can't modify the Lot/Serial number", m.exception.args[0])