[FIX] stock: scrap products thanks to internal move
A user should be able to scrap some products thanks to an internal
transfer.
To reproduce the issue:
1. In Settings, enable "Multi-Step Routes"
2. Create a storable product P and update its quantity (> 1)
3. Create a planned and internal transfer T:
- From: WH/Stock
- To: Virtual Locations/YourCompany: Scrap
- With: 1 x P
4. Mark T as done
Error: T is still in draft
The `_compute_state` of a picking should not ignore the scrapped moves.
However, if we include them, we need to think about this use case: a
picking with a cancelled normal move and a done scrapped move -> its
state should be cancelled (see use case and test from [1])
[1] 429b589618e8dc2b0c0ccdec3f6eed88f1c73fc8
OPW-2841190
closes odoo/odoo#90870
X-original-commit: 1c956d49b45e9d3fec2bfa2c2020ea278ea80507
Signed-off-by: Arnold Moyaux <arm@odoo.com>
Signed-off-by: Adrien Widart <awt@odoo.com>
This commit is contained in:
@@ -523,13 +523,15 @@ class Picking(models.Model):
|
||||
'''
|
||||
picking_moves_state_map = defaultdict(dict)
|
||||
picking_move_lines = defaultdict(set)
|
||||
for move in self.env['stock.move'].search([('picking_id', 'in', self.ids), ('scrapped', '=', False)]):
|
||||
for move in self.env['stock.move'].search([('picking_id', 'in', self.ids)]):
|
||||
picking_id = move.picking_id
|
||||
move_state = move.state
|
||||
picking_moves_state_map[picking_id.id].update({
|
||||
'any_draft': picking_moves_state_map[picking_id.id].get('any_draft', False) or move_state == 'draft',
|
||||
'all_cancel': picking_moves_state_map[picking_id.id].get('all_cancel', True) and move_state == 'cancel',
|
||||
'all_cancel_done': picking_moves_state_map[picking_id.id].get('all_cancel_done', True) and move_state in ('cancel', 'done'),
|
||||
'all_done_are_scrapped': picking_moves_state_map[picking_id.id].get('all_done_are_scrapped', True) and (move.scrapped if move_state == 'done' else True),
|
||||
'any_cancel_and_not_scrapped': picking_moves_state_map[picking_id.id].get('any_cancel_and_not_scrapped', False) or (move_state == 'cancel' and not move.scrapped),
|
||||
})
|
||||
picking_move_lines[picking_id.id].add(move.id)
|
||||
for picking in self:
|
||||
@@ -541,7 +543,10 @@ class Picking(models.Model):
|
||||
elif picking_moves_state_map[picking_id]['all_cancel']:
|
||||
picking.state = 'cancel'
|
||||
elif picking_moves_state_map[picking_id]['all_cancel_done']:
|
||||
picking.state = 'done'
|
||||
if picking_moves_state_map[picking_id]['all_done_are_scrapped'] and picking_moves_state_map[picking_id]['any_cancel_and_not_scrapped']:
|
||||
picking.state = 'cancel'
|
||||
else:
|
||||
picking.state = 'done'
|
||||
else:
|
||||
relevant_move_state = self.env['stock.move'].browse(picking_move_lines[picking_id])._get_relevant_state_among_moves()
|
||||
if picking.immediate_transfer and relevant_move_state not in ('draft', 'cancel', 'done'):
|
||||
|
||||
@@ -4655,6 +4655,68 @@ class StockMove(TransactionCase):
|
||||
self.assertEqual(list(warning.keys())[0], 'warning', 'Warning message was not returned')
|
||||
self.assertEqual(scrap.location_id, child_loc1, 'Location was not auto-corrected')
|
||||
|
||||
def test_scrap_8(self):
|
||||
"""
|
||||
Suppose a user wants to scrap some products thanks to internal moves.
|
||||
This test checks the state of the picking based on few cases
|
||||
"""
|
||||
scrap_location = self.env['stock.location'].search([('company_id', '=', self.env.company.id), ('scrap_location', '=', True)], limit=1)
|
||||
internal_operation = self.env['stock.picking.type'].with_context(active_test=False).search([('code', '=', 'internal'), ('company_id', '=', self.env.company.id)], limit=1)
|
||||
internal_operation.active = True
|
||||
|
||||
product01 = self.product
|
||||
product02 = self.env['product.product'].create({
|
||||
'name': 'SuperProduct',
|
||||
'type': 'product',
|
||||
})
|
||||
|
||||
self.env['stock.quant']._update_available_quantity(product01, self.stock_location, 3)
|
||||
self.env['stock.quant']._update_available_quantity(product02, self.stock_location, 1)
|
||||
|
||||
scrap_picking01, scrap_picking02, scrap_picking03 = self.env['stock.picking'].create([{
|
||||
'location_id': self.stock_location.id,
|
||||
'location_dest_id': scrap_location.id,
|
||||
'picking_type_id': internal_operation.id,
|
||||
'move_ids': [(0, 0, {
|
||||
'name': 'Scrap %s' % product.display_name,
|
||||
'location_id': self.stock_location.id,
|
||||
'location_dest_id': scrap_location.id,
|
||||
'product_id': product.id,
|
||||
'product_uom': product.uom_id.id,
|
||||
'product_uom_qty': 1.0,
|
||||
'picking_type_id': internal_operation.id,
|
||||
}) for product in products],
|
||||
} for products in [(product01,), (product01,), (product01, product02)]])
|
||||
|
||||
(scrap_picking01 + scrap_picking02 + scrap_picking03).action_confirm()
|
||||
|
||||
# All SM are processed
|
||||
scrap_picking01.move_ids.quantity_done = 1
|
||||
scrap_picking01.button_validate()
|
||||
|
||||
# All SM are cancelled
|
||||
scrap_picking02.action_cancel()
|
||||
|
||||
# Process one SM and cancel the other one
|
||||
pick03_prod01_move = scrap_picking03.move_ids.filtered(lambda sm: sm.product_id == product01)
|
||||
pick03_prod02_move = scrap_picking03.move_ids - pick03_prod01_move
|
||||
pick03_prod01_move.quantity_done = 1
|
||||
pick03_prod02_move._action_cancel()
|
||||
scrap_picking03.button_validate()
|
||||
|
||||
self.assertEqual(scrap_picking01.move_ids.state, 'done')
|
||||
self.assertEqual(scrap_picking01.state, 'done')
|
||||
|
||||
self.assertEqual(scrap_picking02.move_ids.state, 'cancel')
|
||||
self.assertEqual(scrap_picking02.state, 'cancel')
|
||||
|
||||
self.assertEqual(pick03_prod01_move.state, 'done')
|
||||
self.assertEqual(pick03_prod02_move.state, 'cancel')
|
||||
self.assertEqual(scrap_picking03.state, 'done')
|
||||
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(product01, self.stock_location), 1)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(product02, self.stock_location), 1)
|
||||
|
||||
def test_in_date_1(self):
|
||||
""" Check that moving a tracked quant keeps the incoming date.
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user