Files
odoo_source/addons/stock/tests/test_move2.py
T
amoyaux b970bf865a [FIX] stock: unable to return SN in chained moves
Use case to reproduce the bug:
- Set multi location in settings
- Set warehouse's deliery method as pick/pack/ship
- Deliver a product to customer
- Process a return from customer to output.
- Create a return from output to pack ->
Unable to reserver product.

It happens because return moves where not linked together but have
the picking that created the return as origin.
Thus action_assign does not detect the return moves and can't
guess the real quantity available.

This commit add a behavior that will create links between
return move. It can be guess from parent move(the picking's move from which
the return is created) and its move dest/orig that have already
created other return. It also correct action assign because
the group by could retourn same line with return moves.
It add a test that check returns and links between them.
2017-08-17 17:49:09 +02:00

1207 lines
55 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.stock.tests.common import TestStockCommon
from odoo.exceptions import UserError
class TestPickShip(TestStockCommon):
def create_pick_ship(self):
picking_client = self.env['stock.picking'].create({
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
dest = self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 10,
'product_uom': self.productA.uom_id.id,
'picking_id': picking_client.id,
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
'state': 'waiting',
})
picking_pick = self.env['stock.picking'].create({
'location_id': self.stock_location,
'location_dest_id': self.pack_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 10,
'product_uom': self.productA.uom_id.id,
'picking_id': picking_pick.id,
'location_id': self.stock_location,
'location_dest_id': self.pack_location,
'move_dest_ids': [(4, dest.id)],
'state': 'confirmed',
})
return picking_pick, picking_client
def create_pick_pack_ship(self):
picking_ship = self.env['stock.picking'].create({
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
ship = self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 1,
'product_uom': self.productA.uom_id.id,
'picking_id': picking_ship.id,
'location_id': self.output_location,
'location_dest_id': self.customer_location,
})
picking_pack = self.env['stock.picking'].create({
'location_id': self.stock_location,
'location_dest_id': self.pack_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
pack = self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 1,
'product_uom': self.productA.uom_id.id,
'picking_id': picking_pack.id,
'location_id': self.pack_location,
'location_dest_id': self.output_location,
'move_dest_ids': [(4, ship.id)],
})
picking_pick = self.env['stock.picking'].create({
'location_id': self.stock_location,
'location_dest_id': self.pack_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 1,
'product_uom': self.productA.uom_id.id,
'picking_id': picking_pick.id,
'location_id': self.stock_location,
'location_dest_id': self.pack_location,
'move_dest_ids': [(4, pack.id)],
'state': 'confirmed',
})
return picking_pick, picking_pack, picking_ship
def test_mto_moves(self):
"""
10 in stock, do pick->ship and check ship is assigned when pick is done, then backorder of ship
"""
picking_pick, picking_client = self.create_pick_ship()
location = self.env['stock.location'].browse(self.stock_location)
# make some stock
self.env['stock.quant']._update_available_quantity(self.productA, location, 10.0)
picking_pick.action_assign()
picking_pick.move_lines[0].move_line_ids[0].qty_done = 10.0
picking_pick.do_transfer()
self.assertEqual(picking_client.state, 'assigned', 'The state of the client should be assigned')
# Now partially transfer the ship
picking_client.move_lines[0].move_line_ids[0].qty_done = 5
picking_client.do_transfer() # no new in order to create backorder
backorder = self.env['stock.picking'].search([('backorder_id', '=', picking_client.id)])
self.assertEqual(backorder.state, 'assigned', 'Backorder should be started')
def test_mto_moves_transfer(self):
"""
10 in stock, 5 in pack. Make sure it does not assign the 5 pieces in pack
"""
picking_pick, picking_client = self.create_pick_ship()
stock_location = self.env['stock.location'].browse(self.stock_location)
self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 10.0)
pack_location = self.env['stock.location'].browse(self.pack_location)
self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 5.0)
self.assertEqual(len(self.env['stock.quant']._gather(self.productA, stock_location)), 1.0)
self.assertEqual(len(self.env['stock.quant']._gather(self.productA, pack_location)), 1.0)
(picking_pick + picking_client).action_assign()
move_pick = picking_pick.move_lines
move_cust = picking_client.move_lines
self.assertEqual(move_pick.state, 'assigned')
self.assertEqual(picking_pick.state, 'assigned')
self.assertEqual(move_cust.state, 'waiting')
self.assertEqual(picking_client.state, 'waiting', 'The picking should not assign what it does not have')
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, stock_location), 0.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 5.0)
move_pick.move_line_ids[0].qty_done = 10.0
picking_pick.do_transfer()
self.assertEqual(move_pick.state, 'done')
self.assertEqual(picking_pick.state, 'done')
self.assertEqual(move_cust.state, 'assigned')
self.assertEqual(picking_client.state, 'assigned', 'The picking should not assign what it does not have')
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, stock_location), 0.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 5.0)
self.assertEqual(len(self.env['stock.quant']._gather(self.productA, stock_location)), 0.0)
self.assertEqual(len(self.env['stock.quant']._gather(self.productA, pack_location)), 1.0)
def test_mto_moves_return(self):
picking_pick, picking_client = self.create_pick_ship()
stock_location = self.env['stock.location'].browse(self.stock_location)
self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 10.0)
picking_pick.action_assign()
picking_pick.move_lines[0].move_line_ids[0].qty_done = 10.0
picking_pick.do_transfer()
self.assertEqual(picking_pick.state, 'done')
self.assertEqual(picking_client.state, 'assigned')
# return a part of what we've done
stock_return_picking = self.env['stock.return.picking']\
.with_context(active_ids=picking_pick.ids, active_id=picking_pick.ids[0])\
.create({})
stock_return_picking.product_return_moves.quantity = 2.0 # Return only 2
stock_return_picking_action = stock_return_picking.create_returns()
return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
return_pick.move_lines[0].move_line_ids[0].qty_done = 2.0
return_pick.do_transfer()
# the client picking should not be assigned anymore, as we returned partially what we took
self.assertEqual(picking_client.state, 'confirmed')
def test_no_backorder_1(self):
""" Check the behavior of doing less than asked in the picking pick and chosing not to
create a backorder. In this behavior, the second picking should obviously only be able to
reserve what was brought, but its initial demand should stay the same and the system will
ask the user will have to consider again if he wants to create a backorder or not.
"""
picking_pick, picking_client = self.create_pick_ship()
location = self.env['stock.location'].browse(self.stock_location)
# make some stock
self.env['stock.quant']._update_available_quantity(self.productA, location, 10.0)
picking_pick.action_assign()
picking_pick.move_lines[0].move_line_ids[0].qty_done = 5.0
# create a backorder
picking_pick.do_transfer()
picking_pick_backorder = self.env['stock.picking'].search([('backorder_id', '=', picking_pick.id)])
self.assertEqual(picking_pick_backorder.state, 'assigned')
self.assertEqual(picking_pick_backorder.move_line_ids.product_qty, 5.0)
self.assertEqual(picking_client.state, 'partially_available')
# cancel the backorder
picking_pick_backorder.action_cancel()
self.assertEqual(picking_client.state, 'partially_available')
def test_edit_done_chained_move(self):
""" Let’s say two moves are chained: the first is done and the second is assigned.
Editing the move line of the first move should impact the reservation of the second one.
"""
picking_pick, picking_client = self.create_pick_ship()
location = self.env['stock.location'].browse(self.stock_location)
# make some stock
self.env['stock.quant']._update_available_quantity(self.productA, location, 10.0)
picking_pick.action_assign()
picking_pick.move_lines[0].move_line_ids[0].qty_done = 10.0
picking_pick.action_done()
self.assertEqual(picking_pick.state, 'done', 'The state of the pick should be done')
self.assertEqual(picking_client.state, 'assigned', 'The state of the client should be assigned')
self.assertEqual(picking_pick.move_lines.quantity_done, 10.0, 'Wrong quantity_done for pick move')
self.assertEqual(picking_client.move_lines.product_qty, 10.0, 'Wrong initial demand for client move')
self.assertEqual(picking_client.move_lines.reserved_availability, 10.0, 'Wrong quantity already reserved for client move')
picking_pick.move_lines[0].move_line_ids[0].qty_done = 5.0
self.assertEqual(picking_pick.state, 'done', 'The state of the pick should be done')
self.assertEqual(picking_client.state, 'partially_available', 'The state of the client should be partially available')
self.assertEqual(picking_pick.move_lines.quantity_done, 5.0, 'Wrong quantity_done for pick move')
self.assertEqual(picking_client.move_lines.product_qty, 10.0, 'Wrong initial demand for client move')
self.assertEqual(picking_client.move_lines.reserved_availability, 5.0, 'Wrong quantity already reserved for client move')
# Check if run action_assign does not crash
picking_client.action_assign()
def test_edit_done_chained_move_with_lot(self):
""" Let’s say two moves are chained: the first is done and the second is assigned.
Editing the lot on the move line of the first move should impact the reservation of the second one.
"""
self.productA.tracking = 'lot'
lot1 = self.env['stock.production.lot'].create({
'name': 'lot1',
'product_id': self.productA.id,
})
lot2 = self.env['stock.production.lot'].create({
'name': 'lot2',
'product_id': self.productA.id,
})
picking_pick, picking_client = self.create_pick_ship()
location = self.env['stock.location'].browse(self.stock_location)
# make some stock
self.env['stock.quant']._update_available_quantity(self.productA, location, 10.0)
picking_pick.action_assign()
picking_pick.move_lines[0].move_line_ids[0].write({
'qty_done': 10.0,
'lot_id': lot1.id,
})
picking_pick.action_done()
self.assertEqual(picking_pick.state, 'done', 'The state of the pick should be done')
self.assertEqual(picking_client.state, 'assigned', 'The state of the client should be assigned')
self.assertEqual(picking_pick.move_lines.quantity_done, 10.0, 'Wrong quantity_done for pick move')
self.assertEqual(picking_client.move_lines.product_qty, 10.0, 'Wrong initial demand for client move')
self.assertEqual(picking_client.move_lines.move_line_ids.lot_id, lot1, 'Wrong lot for client move line')
self.assertEqual(picking_client.move_lines.reserved_availability, 10.0, 'Wrong quantity already reserved for client move')
picking_pick.move_lines[0].move_line_ids[0].lot_id = lot2.id
self.assertEqual(picking_pick.state, 'done', 'The state of the pick should be done')
self.assertEqual(picking_client.state, 'assigned', 'The state of the client should be partially available')
self.assertEqual(picking_pick.move_lines.quantity_done, 10.0, 'Wrong quantity_done for pick move')
self.assertEqual(picking_client.move_lines.product_qty, 10.0, 'Wrong initial demand for client move')
self.assertEqual(picking_client.move_lines.move_line_ids.lot_id, lot2, 'Wrong lot for client move line')
self.assertEqual(picking_client.move_lines.reserved_availability, 10.0, 'Wrong quantity already reserved for client move')
# Check if run action_assign does not crash
picking_client.action_assign()
def test_chained_move_with_uom(self):
""" Create pick ship with a different uom than the once used for quant.
Check that reserved quantity and flow work correctly.
"""
picking_client = self.env['stock.picking'].create({
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
dest = self.MoveObj.create({
'name': self.gB.name,
'product_id': self.gB.id,
'product_uom_qty': 5,
'product_uom': self.uom_kg.id,
'picking_id': picking_client.id,
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
'state': 'waiting',
})
picking_pick = self.env['stock.picking'].create({
'location_id': self.stock_location,
'location_dest_id': self.pack_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
self.MoveObj.create({
'name': self.gB.name,
'product_id': self.gB.id,
'product_uom_qty': 5,
'product_uom': self.uom_kg.id,
'picking_id': picking_pick.id,
'location_id': self.stock_location,
'location_dest_id': self.pack_location,
'move_dest_ids': [(4, dest.id)],
'state': 'confirmed',
})
location = self.env['stock.location'].browse(self.stock_location)
pack_location = self.env['stock.location'].browse(self.pack_location)
# make some stock
self.env['stock.quant']._update_available_quantity(self.gB, location, 10000.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.gB, pack_location), 0.0)
picking_pick.action_assign()
picking_pick.move_lines[0].move_line_ids[0].qty_done = 5.0
picking_pick.action_done()
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.gB, location), 5000.0)
self.assertEqual(self.env['stock.quant']._gather(self.gB, pack_location).quantity, 5000.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.gB, pack_location), 0.0)
self.assertEqual(picking_client.state, 'assigned')
self.assertEqual(picking_client.move_lines.reserved_availability, 5.0)
def test_pick_ship_return(self):
""" Create pick and ship. Bring it ot the customer and then return
it to stock. This test check the state and the quantity after each move in
order to ensure that it is correct.
"""
picking_pick, picking_ship = self.create_pick_ship()
stock_location = self.env['stock.location'].browse(self.stock_location)
pack_location = self.env['stock.location'].browse(self.pack_location)
customer_location = self.env['stock.location'].browse(self.customer_location)
self.productA.tracking = 'lot'
lot = self.env['stock.production.lot'].create({
'product_id': self.productA.id,
'name': '123456789'
})
self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 10.0, lot_id=lot)
picking_pick.action_assign()
picking_pick.move_lines[0].move_line_ids[0].qty_done = 10.0
picking_pick.action_done()
self.assertEqual(picking_pick.state, 'done')
self.assertEqual(picking_ship.state, 'assigned')
picking_ship.action_assign()
picking_ship.move_lines[0].move_line_ids[0].qty_done = 10.0
picking_ship.action_done()
customer_quantity = self.env['stock.quant']._get_available_quantity(self.productA, customer_location, lot_id=lot)
self.assertEqual(customer_quantity, 10, 'It should be one product in customer')
""" First we create the return picking for pick pinking.
Since we do not have created the return between customer and
output. This return should not be available and should only have
picking pick as origin move.
"""
stock_return_picking = self.env['stock.return.picking']\
.with_context(active_ids=picking_pick.ids, active_id=picking_pick.ids[0])\
.create({})
stock_return_picking.product_return_moves.quantity = 10.0
stock_return_picking_action = stock_return_picking.create_returns()
return_pick_picking = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
self.assertEqual(return_pick_picking.state, 'waiting')
stock_return_picking = self.env['stock.return.picking']\
.with_context(active_ids=picking_ship.ids, active_id=picking_ship.ids[0])\
.create({})
stock_return_picking.product_return_moves.quantity = 10.0
stock_return_picking_action = stock_return_picking.create_returns()
return_ship_picking = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
self.assertEqual(return_ship_picking.state, 'assigned', 'Return ship picking should automatically be assigned')
""" We created the return for ship picking. The origin/destination
link between return moves should have been created during return creation.
"""
self.assertTrue(return_ship_picking.move_lines in return_pick_picking.move_lines.mapped('move_orig_ids'),
'The pick return picking\'s moves should have the ship return picking\'s moves as origin')
self.assertTrue(return_pick_picking.move_lines in return_ship_picking.move_lines.mapped('move_dest_ids'),
'The ship return picking\'s moves should have the pick return picking\'s moves as destination')
return_ship_picking.move_lines[0].move_line_ids[0].write({
'qty_done': 10.0,
'lot_id': lot.id,
})
return_ship_picking.action_done()
self.assertEqual(return_ship_picking.state, 'done')
self.assertEqual(return_pick_picking.state, 'assigned')
customer_quantity = self.env['stock.quant']._get_available_quantity(self.productA, customer_location, lot_id=lot)
self.assertEqual(customer_quantity, 0, 'It should be one product in customer')
pack_quantity = self.env['stock.quant']._get_available_quantity(self.productA, pack_location, lot_id=lot)
self.assertEqual(pack_quantity, 0, 'It should be one product in pack location but is reserved')
# Should use previous move lot.
return_pick_picking.move_lines[0].move_line_ids[0].qty_done = 10.0
return_pick_picking.action_done()
self.assertEqual(return_pick_picking.state, 'done')
stock_quantity = self.env['stock.quant']._get_available_quantity(self.productA, stock_location, lot_id=lot)
self.assertEqual(stock_quantity, 10, 'The product is not back in stock')
def test_pick_pack_ship_return(self):
""" This test do a pick pack ship delivery to customer and then
return it to stock. Once everything is done, this test will check
if all the link orgini/destination between moves are correct.
"""
picking_pick, picking_pack, picking_ship = self.create_pick_pack_ship()
stock_location = self.env['stock.location'].browse(self.stock_location)
self.productA.tracking = 'serial'
lot = self.env['stock.production.lot'].create({
'product_id': self.productA.id,
'name': '123456789'
})
self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 1.0, lot_id=lot)
picking_pick.action_assign()
picking_pick.move_lines[0].move_line_ids[0].qty_done = 1.0
picking_pick.action_done()
picking_pack.action_assign()
picking_pack.move_lines[0].move_line_ids[0].qty_done = 1.0
picking_pack.action_done()
picking_ship.action_assign()
picking_ship.move_lines[0].move_line_ids[0].qty_done = 1.0
picking_ship.action_done()
stock_return_picking = self.env['stock.return.picking']\
.with_context(active_ids=picking_ship.ids, active_id=picking_ship.ids[0])\
.create({})
stock_return_picking.product_return_moves.quantity = 1.0
stock_return_picking_action = stock_return_picking.create_returns()
return_ship_picking = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
return_ship_picking.move_lines[0].move_line_ids[0].write({
'qty_done': 1.0,
'lot_id': lot.id,
})
return_ship_picking.action_done()
stock_return_picking = self.env['stock.return.picking']\
.with_context(active_ids=picking_pack.ids, active_id=picking_pack.ids[0])\
.create({})
stock_return_picking.product_return_moves.quantity = 1.0
stock_return_picking_action = stock_return_picking.create_returns()
return_pack_picking = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
return_pack_picking.move_lines[0].move_line_ids[0].qty_done = 1.0
return_pack_picking.action_done()
stock_return_picking = self.env['stock.return.picking']\
.with_context(active_ids=picking_pick.ids, active_id=picking_pick.ids[0])\
.create({})
stock_return_picking.product_return_moves.quantity = 1.0
stock_return_picking_action = stock_return_picking.create_returns()
return_pick_picking = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
return_pick_picking.move_lines[0].move_line_ids[0].qty_done = 1.0
return_pick_picking.action_done()
# Now that everything is returned we will check if the return moves are correctly linked between them.
# +--------------------------------------------------------------------------------------------------------+
# | -- picking_pick(1) --> -- picking_pack(2) --> -- picking_ship(3) -->
# | Stock Pack Output Customer
# | <--- return pick(6) -- <--- return pack(5) -- <--- return ship(4) --
# +--------------------------------------------------------------------------------------------------------+
# Recaps of final link (MO = move_orig_ids, MD = move_dest_ids)
# picking_pick(1) : MO = (), MD = (2,6)
# picking_pack(2) : MO = (1), MD = (3,5)
# picking ship(3) : MO = (2), MD = (4)
# return ship(4) : MO = (3), MD = (5)
# return pack(5) : MO = (2, 4), MD = (6)
# return pick(6) : MO = (1, 5), MD = ()
self.assertEqual(len(picking_pick.move_lines.move_orig_ids), 0, 'Picking pick should not have origin moves')
self.assertEqual(set(picking_pick.move_lines.move_dest_ids.ids), set((picking_pack.move_lines | return_pick_picking.move_lines).ids))
self.assertEqual(set(picking_pack.move_lines.move_orig_ids.ids), set(picking_pick.move_lines.ids))
self.assertEqual(set(picking_pack.move_lines.move_dest_ids.ids), set((picking_ship.move_lines | return_pack_picking.move_lines).ids))
self.assertEqual(set(picking_ship.move_lines.move_orig_ids.ids), set(picking_pack.move_lines.ids))
self.assertEqual(set(picking_ship.move_lines.move_dest_ids.ids), set(return_ship_picking.move_lines.ids))
self.assertEqual(set(return_ship_picking.move_lines.move_orig_ids.ids), set(picking_ship.move_lines.ids))
self.assertEqual(set(return_ship_picking.move_lines.move_dest_ids.ids), set(return_pack_picking.move_lines.ids))
self.assertEqual(set(return_pack_picking.move_lines.move_orig_ids.ids), set((picking_pack.move_lines | return_ship_picking.move_lines).ids))
self.assertEqual(set(return_pack_picking.move_lines.move_dest_ids.ids), set(return_pick_picking.move_lines.ids))
self.assertEqual(set(return_pick_picking.move_lines.move_orig_ids.ids), set((picking_pick.move_lines | return_pack_picking.move_lines).ids))
self.assertEqual(len(return_pick_picking.move_lines.move_dest_ids), 0)
class TestSinglePicking(TestStockCommon):
def test_backorder_1(self):
""" Check the good behavior of creating a backorder for an available stock move.
"""
delivery_order = self.env['stock.picking'].create({
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 2,
'product_uom': self.productA.uom_id.id,
'picking_id': delivery_order.id,
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
})
# make some stock
pack_location = self.env['stock.location'].browse(self.pack_location)
self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 2)
# assign
delivery_order.action_confirm()
delivery_order.action_assign()
self.assertEqual(delivery_order.state, 'assigned')
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0)
# valid with backorder creation
delivery_order.move_lines[0].move_line_ids[0].qty_done = 1
delivery_order.do_transfer()
self.assertNotEqual(delivery_order.date_done, False)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0)
backorder = self.env['stock.picking'].search([('backorder_id', '=', delivery_order.id)])
self.assertEqual(backorder.state, 'assigned')
def test_backorder_2(self):
""" Check the good behavior of creating a backorder for a partially available stock move.
"""
delivery_order = self.env['stock.picking'].create({
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 2,
'product_uom': self.productA.uom_id.id,
'picking_id': delivery_order.id,
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
})
# make some stock
pack_location = self.env['stock.location'].browse(self.pack_location)
self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 1)
# assign to partially available
delivery_order.action_confirm()
delivery_order.action_assign()
self.assertEqual(delivery_order.state, 'partially_available')
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0)
# valid with backorder creation
delivery_order.move_lines[0].move_line_ids[0].qty_done = 1
delivery_order.do_transfer()
self.assertNotEqual(delivery_order.date_done, False)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0)
backorder = self.env['stock.picking'].search([('backorder_id', '=', delivery_order.id)])
self.assertEqual(backorder.state, 'confirmed')
def test_extra_move_1(self):
""" Check the good behavior of creating an extra move in a delivery order. This usecase
simulates the delivery of 2 item while the initial stock move had to move 1 and there's
only 1 in stock.
"""
delivery_order = self.env['stock.picking'].create({
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
move1 = self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 1,
'product_uom': self.productA.uom_id.id,
'picking_id': delivery_order.id,
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
})
# make some stock
pack_location = self.env['stock.location'].browse(self.pack_location)
self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 1)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 1.0)
# assign to available
delivery_order.action_confirm()
delivery_order.action_assign()
self.assertEqual(delivery_order.state, 'assigned')
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0)
# valid with backorder creation
delivery_order.move_lines[0].move_line_ids[0].qty_done = 2
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0)
delivery_order.with_context(debug=True).do_transfer()
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), -1.0)
extra_move = delivery_order.move_lines - move1
extra_move_line = extra_move.move_line_ids[0]
# self.assertEqual(move1.product_qty, 1.0)
# self.assertEqual(move1.quantity_done, 1.0)
self.assertEqual(move1.reserved_availability, 0.0)
self.assertEqual(move1.move_line_ids.product_qty, 0.0) # change reservation to 0 for done move
self.assertEqual(move1.move_line_ids.qty_done, 1.0)
self.assertEqual(move1.state, 'done')
self.assertEqual(extra_move.product_qty, 1.0)
self.assertEqual(extra_move.quantity_done, 1.0)
self.assertEqual(extra_move.reserved_availability, 0.0)
self.assertEqual(extra_move_line.product_qty, 0.0) # should not be able to reserve
self.assertEqual(extra_move_line.qty_done, 1.0)
self.assertEqual(extra_move.state, 'done')
def test_extra_move_2(self):
""" Check the good behavior of creating an extra move in a delivery order. This usecase
simulates the delivery of 3 item while the initial stock move had to move 1 and there's
only 1 in stock.
"""
delivery_order = self.env['stock.picking'].create({
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
move1 = self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 1,
'product_uom': self.productA.uom_id.id,
'picking_id': delivery_order.id,
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
})
# make some stock
pack_location = self.env['stock.location'].browse(self.pack_location)
self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 1)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 1.0)
# assign to available
delivery_order.action_confirm()
delivery_order.action_assign()
self.assertEqual(delivery_order.state, 'assigned')
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0)
# valid with backorder creation
delivery_order.move_lines[0].move_line_ids[0].qty_done = 3
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0)
delivery_order.do_transfer()
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), -2.0)
extra_move = delivery_order.move_lines - move1
extra_move_line = extra_move.move_line_ids[0]
self.assertEqual(move1.product_qty, 1.0)
self.assertEqual(move1.quantity_done, 1.0)
self.assertEqual(move1.reserved_availability, 0.0)
self.assertEqual(move1.move_line_ids.product_qty, 0.0) # change reservation to 0 for done move
self.assertEqual(move1.move_line_ids.qty_done, 1.0)
self.assertEqual(move1.state, 'done')
self.assertEqual(extra_move.product_qty, 2.0)
self.assertEqual(extra_move.quantity_done, 2.0)
self.assertEqual(extra_move.reserved_availability, 0.0)
self.assertEqual(extra_move_line.product_qty, 0.0) # should not be able to reserve
self.assertEqual(extra_move_line.qty_done, 2.0)
self.assertEqual(extra_move.state, 'done')
def test_extra_move_3(self):
""" Check the good behavior of creating an extra move in a receipt. This usecase simulates
the receipt of 2 item while the initial stock move had to move 1.
"""
receipt = self.env['stock.picking'].create({
'location_id': self.supplier_location,
'location_dest_id': self.stock_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_in,
})
move1 = self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 1,
'product_uom': self.productA.uom_id.id,
'picking_id': receipt.id,
'location_id': self.supplier_location,
'location_dest_id': self.stock_location,
})
stock_location = self.env['stock.location'].browse(self.stock_location)
# assign to available
receipt.action_confirm()
receipt.action_assign()
self.assertEqual(receipt.state, 'assigned')
# valid with backorder creation
receipt.move_lines[0].move_line_ids[0].qty_done = 2
receipt.with_context(debug=True).do_transfer()
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, stock_location), 2.0)
extra_move = receipt.move_lines - move1
extra_move_line = extra_move.move_line_ids[0]
self.assertEqual(move1.product_qty, 1.0)
self.assertEqual(move1.quantity_done, 1.0)
self.assertEqual(move1.reserved_availability, 0.0)
self.assertEqual(move1.move_line_ids.product_qty, 0.0) # change reservation to 0 for done move
self.assertEqual(move1.move_line_ids.qty_done, 1.0)
self.assertEqual(move1.state, 'done')
self.assertEqual(extra_move.product_qty, 1.0)
self.assertEqual(extra_move.quantity_done, 1.0)
self.assertEqual(extra_move.reserved_availability, 0.0)
self.assertEqual(extra_move_line.product_qty, 0.0) # should not be able to reserve
self.assertEqual(extra_move_line.qty_done, 1.0)
self.assertEqual(extra_move.state, 'done')
def test_recheck_availability_1(self):
""" Check the good behavior of check availability. I create a DO for 2 unit with
only one in stock. After the first check availability, I should have 1 reserved
product with one move line. After adding a second unit in stock and recheck availability.
The DO should have 2 reserved unit, be in available state and have only one move line.
"""
self.env['stock.quant']._update_available_quantity(self.productA, self.env['stock.location'].browse(self.stock_location), 1.0)
delivery_order = self.env['stock.picking'].create({
'location_id': self.stock_location,
'location_dest_id': self.customer_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
move1 = self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 2,
'product_uom': self.productA.uom_id.id,
'picking_id': delivery_order.id,
'location_id': self.stock_location,
'location_dest_id': self.customer_location,
})
delivery_order.action_confirm()
delivery_order.action_assign()
# Check State
self.assertEqual(delivery_order.state, 'partially_available')
self.assertEqual(move1.state, 'partially_available')
# Check reserved quantity
self.assertEqual(move1.reserved_availability, 1.0)
self.assertEqual(len(move1.move_line_ids), 1)
self.assertEqual(move1.move_line_ids.product_qty, 1)
inventory = self.env['stock.inventory'].create({
'name': 'remove product1',
'filter': 'product',
'location_id': self.stock_location,
'product_id': self.productA.id,
})
inventory.prepare_inventory()
inventory.line_ids.product_qty = 2
inventory.action_done()
delivery_order.action_assign()
self.assertEqual(delivery_order.state, 'assigned')
self.assertEqual(move1.state, 'assigned')
# Check reserved quantity
self.assertEqual(move1.reserved_availability, 2.0)
self.assertEqual(len(move1.move_line_ids), 1)
self.assertEqual(move1.move_line_ids.product_qty, 2)
def test_recheck_availability_2(self):
""" Same check than test_recheck_availability_1 but with lot this time.
If the new product has the same lot that already reserved one, the move lines
reserved quantity should be updated.
Otherwise a new move lines with the new lot should be added.
"""
self.productA.tracking = 'lot'
lot1 = self.env['stock.production.lot'].create({
'name': 'lot1',
'product_id': self.productA.id,
})
stock_location = self.env['stock.location'].browse(self.stock_location)
self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 1.0, lot_id=lot1)
delivery_order = self.env['stock.picking'].create({
'location_id': self.stock_location,
'location_dest_id': self.customer_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
move1 = self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 2,
'product_uom': self.productA.uom_id.id,
'picking_id': delivery_order.id,
'location_id': self.stock_location,
'location_dest_id': self.customer_location,
})
delivery_order.action_confirm()
delivery_order.action_assign()
# Check State
self.assertEqual(delivery_order.state, 'partially_available')
self.assertEqual(move1.state, 'partially_available')
# Check reserved quantity
self.assertEqual(move1.reserved_availability, 1.0)
self.assertEqual(len(move1.move_line_ids), 1)
self.assertEqual(move1.move_line_ids.product_qty, 1)
inventory = self.env['stock.inventory'].create({
'name': 'remove product1',
'filter': 'product',
'location_id': self.stock_location,
'product_id': self.productA.id,
})
inventory.prepare_inventory()
inventory.line_ids.prod_lot_id = lot1
inventory.line_ids.product_qty = 2
inventory.action_done()
delivery_order.action_assign()
self.assertEqual(delivery_order.state, 'assigned')
self.assertEqual(move1.state, 'assigned')
# Check reserved quantity
self.assertEqual(move1.reserved_availability, 2.0)
self.assertEqual(len(move1.move_line_ids), 1)
self.assertEqual(move1.move_line_ids.lot_id.id, lot1.id)
self.assertEqual(move1.move_line_ids.product_qty, 2)
def test_recheck_availability_3(self):
""" Same check than test_recheck_availability_2 but with different lots.
"""
self.productA.tracking = 'lot'
lot1 = self.env['stock.production.lot'].create({
'name': 'lot1',
'product_id': self.productA.id,
})
lot2 = self.env['stock.production.lot'].create({
'name': 'lot2',
'product_id': self.productA.id,
})
stock_location = self.env['stock.location'].browse(self.stock_location)
self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 1.0, lot_id=lot1)
delivery_order = self.env['stock.picking'].create({
'location_id': self.stock_location,
'location_dest_id': self.customer_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
move1 = self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 2,
'product_uom': self.productA.uom_id.id,
'picking_id': delivery_order.id,
'location_id': self.stock_location,
'location_dest_id': self.customer_location,
})
delivery_order.action_confirm()
delivery_order.action_assign()
# Check State
self.assertEqual(delivery_order.state, 'partially_available')
self.assertEqual(move1.state, 'partially_available')
# Check reserved quantity
self.assertEqual(move1.reserved_availability, 1.0)
self.assertEqual(len(move1.move_line_ids), 1)
self.assertEqual(move1.move_line_ids.product_qty, 1)
inventory = self.env['stock.inventory'].create({
'name': 'remove product1',
'filter': 'product',
'location_id': self.stock_location,
'product_id': self.productA.id,
})
inventory.prepare_inventory()
self.env['stock.inventory.line'].create({
'inventory_id': inventory.id,
'location_id': inventory.location_id.id,
'partner_id': inventory.partner_id.id,
'prod_lot_id': lot2.id,
'product_id': self.productA.id,
'product_qty': 1,
})
inventory.action_done()
delivery_order.action_assign()
self.assertEqual(delivery_order.state, 'assigned')
self.assertEqual(move1.state, 'assigned')
# Check reserved quantity
self.assertEqual(move1.reserved_availability, 2.0)
self.assertEqual(len(move1.move_line_ids), 2)
self.assertEqual(move1.move_line_ids[0].lot_id.id, lot1.id)
self.assertEqual(move1.move_line_ids[1].lot_id.id, lot2.id)
def test_recheck_availability_4(self):
""" Same check than test_recheck_availability_2 but with serial number this time.
Serial number reservation should always create a new move line.
"""
self.productA.tracking = 'serial'
serial1 = self.env['stock.production.lot'].create({
'name': 'serial1',
'product_id': self.productA.id,
})
serial2 = self.env['stock.production.lot'].create({
'name': 'serial2',
'product_id': self.productA.id,
})
stock_location = self.env['stock.location'].browse(self.stock_location)
self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 1.0, lot_id=serial1)
delivery_order = self.env['stock.picking'].create({
'location_id': self.stock_location,
'location_dest_id': self.customer_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
move1 = self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 2,
'product_uom': self.productA.uom_id.id,
'picking_id': delivery_order.id,
'location_id': self.stock_location,
'location_dest_id': self.customer_location,
})
delivery_order.action_confirm()
delivery_order.action_assign()
# Check State
self.assertEqual(delivery_order.state, 'partially_available')
self.assertEqual(move1.state, 'partially_available')
# Check reserved quantity
self.assertEqual(move1.reserved_availability, 1.0)
self.assertEqual(len(move1.move_line_ids), 1)
self.assertEqual(move1.move_line_ids.product_qty, 1)
inventory = self.env['stock.inventory'].create({
'name': 'remove product1',
'filter': 'product',
'location_id': self.stock_location,
'product_id': self.productA.id,
})
inventory.prepare_inventory()
self.env['stock.inventory.line'].create({
'inventory_id': inventory.id,
'location_id': inventory.location_id.id,
'partner_id': inventory.partner_id.id,
'prod_lot_id': serial2.id,
'product_id': self.productA.id,
'product_qty': 1,
})
inventory.action_done()
delivery_order.action_assign()
self.assertEqual(delivery_order.state, 'assigned')
self.assertEqual(move1.state, 'assigned')
# Check reserved quantity
self.assertEqual(move1.reserved_availability, 2.0)
self.assertEqual(len(move1.move_line_ids), 2)
self.assertEqual(move1.move_line_ids[0].lot_id.id, serial1.id)
self.assertEqual(move1.move_line_ids[1].lot_id.id, serial2.id)
def test_add_move_when_picking_is_available_1(self):
""" Check that any move added in a picking once it's assigned is directly considered as
assigned and bypass the reservation.
"""
delivery_order = self.env['stock.picking'].create({
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 2,
'product_uom': self.productA.uom_id.id,
'picking_id': delivery_order.id,
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
})
# make some stock
pack_location = self.env['stock.location'].browse(self.pack_location)
self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 2)
# assign
delivery_order.action_confirm()
delivery_order.action_assign()
self.assertEqual(delivery_order.state, 'assigned')
# add a move
move2 = self.MoveObj\
.with_context(default_picking_id=delivery_order.id)\
.create({
'name': self.productA.name,
'product_id': self.productB.id,
'product_uom_qty': 1,
'product_uom': self.productA.uom_id.id,
'picking_id': delivery_order.id,
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
})
self.assertEqual(move2.state, 'assigned')
self.assertEqual(delivery_order.state, 'assigned')
def test_use_create_lot_use_existing_lot_1(self):
""" Check the behavior of a picking when `use_create_lot` and `use_existing_lot` are
set to False and there's a move for a tracked product.
"""
self.env['stock.picking.type']\
.browse(self.picking_type_out)\
.write({
'use_create_lots': False,
'use_existing_lots': False,
})
self.productA.tracking = 'lot'
delivery_order = self.env['stock.picking'].create({
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 2,
'product_uom': self.productA.uom_id.id,
'picking_id': delivery_order.id,
'picking_type_id': self.picking_type_out,
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
})
delivery_order.action_confirm()
delivery_order.force_assign()
delivery_order.move_lines.quantity_done = 2
# do not set a lot_id or lot_name, it should work
delivery_order.action_done()
def test_use_create_lot_use_existing_lot_2(self):
""" Check the behavior of a picking when `use_create_lot` and `use_existing_lot` are
set to True and there's a move for a tracked product.
"""
self.env['stock.picking.type']\
.browse(self.picking_type_out)\
.write({
'use_create_lots': True,
'use_existing_lots': True,
})
self.productA.tracking = 'lot'
delivery_order = self.env['stock.picking'].create({
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 2,
'product_uom': self.productA.uom_id.id,
'picking_id': delivery_order.id,
'picking_type_id': self.picking_type_out,
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
})
delivery_order.action_confirm()
delivery_order.force_assign()
delivery_order.move_lines.quantity_done = 2
move_line = delivery_order.move_lines.move_line_ids
# not lot_name set, should raise
with self.assertRaises(UserError):
delivery_order.action_done()
# enter a new lot name, should work
move_line.lot_name = 'newlot'
delivery_order.action_done()
def test_use_create_lot_use_existing_lot_3(self):
""" Check the behavior of a picking when `use_create_lot` is set to True and
`use_existing_lot` is set to False and there's a move for a tracked product.
"""
self.env['stock.picking.type']\
.browse(self.picking_type_out)\
.write({
'use_create_lots': True,
'use_existing_lots': False,
})
self.productA.tracking = 'lot'
delivery_order = self.env['stock.picking'].create({
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 2,
'product_uom': self.productA.uom_id.id,
'picking_id': delivery_order.id,
'picking_type_id': self.picking_type_out,
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
})
delivery_order.action_confirm()
delivery_order.force_assign()
delivery_order.move_lines.quantity_done = 2
move_line = delivery_order.move_lines.move_line_ids
# not lot_name set, should raise
with self.assertRaises(UserError):
delivery_order.action_done()
# enter a new lot name, should work
move_line.lot_name = 'newlot'
delivery_order.action_done()
def test_use_create_lot_use_existing_lot_4(self):
""" Check the behavior of a picking when `use_create_lot` is set to False and
`use_existing_lot` is set to True and there's a move for a tracked product.
"""
self.env['stock.picking.type']\
.browse(self.picking_type_out)\
.write({
'use_create_lots': False,
'use_existing_lots': True,
})
self.productA.tracking = 'lot'
delivery_order = self.env['stock.picking'].create({
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
'partner_id': self.partner_delta_id,
'picking_type_id': self.picking_type_out,
})
self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 2,
'product_uom': self.productA.uom_id.id,
'picking_id': delivery_order.id,
'picking_type_id': self.picking_type_out,
'location_id': self.pack_location,
'location_dest_id': self.customer_location,
})
delivery_order.action_confirm()
delivery_order.force_assign()
delivery_order.move_lines.quantity_done = 2
move_line = delivery_order.move_lines.move_line_ids
# not lot_name set, should raise
with self.assertRaises(UserError):
delivery_order.action_done()
# creating a lot from the view should raise
with self.assertRaises(UserError):
self.env['stock.production.lot']\
.with_context(active_picking_id=delivery_order.id)\
.create({
'name': 'lot1',
'product_id': self.productA.id,
})
# enter an existing lot_id, should work
lot1 = self.env['stock.production.lot'].create({
'name': 'lot1',
'product_id': self.productA.id,
})
move_line.lot_id = lot1
delivery_order.action_done()