Skip to content
Closed
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
61 changes: 47 additions & 14 deletions stock_picking_force_assign/models/stock_picking.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,27 @@ class StockPicking(models.Model):
def action_force_assign_pickings(self):
""" Unreserve other pickings in order to reserve pickings in self """
link_template = '<a href="#" data-oe-model="%s" data-oe-id="%d">%s</a>'
if self.state == 'waiting':
self._unchain_waiting(link_template)
to_unreserve = self._force_assign_find_moves()
to_unreserve._do_unreserve()
self.message_post(body=_(
'Unreserved picking(s) %s in order to assign this one'
) % ', '.join(to_unreserve.mapped('picking_id').mapped(
lambda x: link_template % (x._name, x.id, x.name)
)))
to_unreserve.mapped('picking_id').message_post(body=_(
'Unreserved this picking in order to assign %s'
) % ', '.join(self.mapped(
lambda x: link_template % (x._name, x.id, x.name)
)))
if to_unreserve:
to_unreserve.mapped(lambda m: m._do_unreserve())
self.message_post(body=_(
'Unreserved picking(s) %s in order to assign this one'
) % ', '.join(to_unreserve.mapped('picking_id').mapped(
lambda x: link_template % (x._name, x.id, x.name)
)))
to_unreserve.mapped('picking_id').mapped(lambda p: p.message_post(body=_(
'Unreserved this picking in order to assign %s'
) % ', '.join(self.mapped(
lambda x: link_template % (x._name, x.id, x.name)
))))
# Set procure_method of the affected moves to make_to_stock to not get stuck
to_unreserve.filtered(
lambda x: x.procure_method == 'make_to_order'
).write({
'procure_method': 'make_to_stock'
})
return self.action_assign()

def _force_assign_allow_partial(self):
Expand All @@ -32,15 +41,15 @@ def _force_assign_allow_partial(self):

def _force_assign_find_moves(self):
""" Return moves to unreserve in order to reserve pickings in self """
location = self.mapped('location_id')
result = self.env['stock.move']
assert len(location) == 1, 'Pickings need to be from the same location'
float_compare = functools.partial(
tools.float_compare,
precision_digits=result._fields['product_qty'].digits,
)

for move in self.mapped('move_lines'):
if move.state == 'cancel':
continue
demand = (
move.product_qty - move.reserved_availability -
move.availability
Expand All @@ -50,7 +59,7 @@ def _force_assign_find_moves(self):
candidates = self.env['stock.move'].search([
('id', 'not in', result.ids),
('picking_id', 'not in', self.ids),
('location_id', '=', location.id),
('location_id', '=', move.location_id.id),
Comment thread
pankk marked this conversation as resolved.
('product_id', '=', move.product_id.id),
('state', 'in', ('partially_available', 'assigned')),
], order='write_date asc')
Expand All @@ -70,3 +79,27 @@ def _force_assign_find_moves(self):
)
)
return result

def _unchain_waiting(self, link_template):
"""Set moves to make_to_stock and unlink move_orig_ids from moves.
Moves must be unlinked because _action_assign will otherwise only
consider availability in linked moves, instead of considering the
availability at the location.
Moves that have procure_method set to make_to_order will also
be skipped by _action_assign, so we set it to make_to_stock.
"""
res = False
to_unchain = self.move_lines.mapped('move_orig_ids')
if to_unchain:
self.message_post(body=_(
'Decoupled operation(s) %s in order to assign this one'
) % ', '.join(to_unchain.mapped(
lambda move: link_template % (move._name, move.id, move.name)
)))
res = self.mapped('move_lines').filtered(
lambda move: move.procure_method == 'make_to_order'
or move.more_orig_ids
).write(
{'procure_method': 'make_to_stock', 'move_orig_ids': [(5, 0, 0)]}
)
return res
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,32 @@
class TestStockPickingForceAssign(TransactionCase):
def setUp(self, *args, **kwargs):
super().setUp(*args, **kwargs)
route_buy = self.env['stock.location.route'].create({
'name': 'Buy',
'rule_ids': [(0, 0, {
'name': 'Buy',
'action': 'buy',
'picking_type_id': self.env.ref('stock.picking_type_in').id,
'location_id': self.env.ref('stock.stock_location_stock').id,
})]
})
route_mto = self.env.ref('stock.route_warehouse0_mto')
self.product = self.env['product.product'].create({
'name': 'stockable',
'type': 'product',
})
self.product2 = self.product.copy({
'name': 'stockable-mto',
'purchase_ok': True,
'sale_ok': True,
'route_ids': [(6, 0, [route_mto.id, route_buy.id])],
'seller_ids': [(0, 0, {
'name': self.env.ref('base.main_partner').id,
'min_qty': 1,
'product_name': 'stockable',
'product_code': 'stockable',
})],
})
self.assigned_picking = self.env['stock.picking'].create({
'name': 'Picking to unassign',
'picking_type_id': self.env.ref('stock.picking_type_out').id,
Expand All @@ -35,6 +57,26 @@ def setUp(self, *args, **kwargs):
self.waiting_picking = self.assigned_picking.copy({
'name': 'Picking to assign',
})
self.waitanother_picking = self.env['stock.picking'].create({
'name': 'Picking to unchain',
'picking_type_id': self.env.ref('stock.picking_type_out').id,
'location_id': self.env.ref('stock.stock_location_stock').id,
'location_dest_id':
self.env.ref('stock.stock_location_customers').id,
'move_lines': [(0, 0, {
'name': self.product2.name,
'product_id': self.product2.id,
'product_uom': self.product2.uom_id.id,
'product_uom_qty': 12,
'state': 'waiting',
'procure_method': 'make_to_order',
})],
})
self.env['stock.quant'].create({
'location_id': self.waitanother_picking.location_id.id,
'product_id': self.product2.id,
'quantity': 12,
})

def test_happy_flow(self):
""" Test unreservation works """
Expand Down Expand Up @@ -70,3 +112,14 @@ def test_partial(self):
self.waiting_picking.action_force_assign_pickings()
self.assertEqual(
self.waiting_picking.move_lines.reserved_availability, 42)

def test_waiting_another(self):
""" Test unchaining moves waiting another operation """
self.assertEqual(len(self.waitanother_picking), 1)
self.assertEqual(self.waitanother_picking.state, 'waiting')
self.assertEqual(
self.waitanother_picking.move_lines[0].procure_method, 'make_to_order')
self.waitanother_picking.action_force_assign_pickings()
self.assertNotEqual(self.waitanother_picking.state, 'waiting')
self.assertEqual(
self.waitanother_picking.move_lines[0].procure_method, 'make_to_stock')