flectra/addons/stock/tests/test_warehouse.py

461 lines
22 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
2018-01-16 11:34:37 +01:00
from flectra.addons.stock.tests.common2 import TestStockCommon
class TestWarehouse(TestStockCommon):
def test_inventory_product(self):
self.product_1.type = 'product'
inventory_wizard = self.env['stock.change.product.qty'].create({
'product_id': self.product_1.id,
'new_quantity': 50.0,
'location_id': self.warehouse_1.lot_stock_id.id,
})
inventory_wizard.change_product_qty()
inventory = self.env['stock.inventory'].sudo(self.user_stock_manager).create({
'name': 'Starting for product_1',
'filter': 'product',
'location_id': self.warehouse_1.lot_stock_id.id,
'product_id': self.product_1.id,
})
inventory.action_start()
# As done in common.py, there is already an inventory line existing
self.assertEqual(len(inventory.line_ids), 1)
self.assertEqual(inventory.line_ids.theoretical_qty, 50.0)
self.assertEqual(inventory.line_ids.product_id, self.product_1)
self.assertEqual(inventory.line_ids.product_uom_id, self.product_1.uom_id)
# Update the line, set to 35
inventory.line_ids.write({'product_qty': 35.0})
inventory.action_done()
# Check related move and quants
self.assertIn(inventory.name, inventory.move_ids.name)
self.assertEqual(inventory.move_ids.product_qty, 15.0)
self.assertEqual(inventory.move_ids.location_id, self.warehouse_1.lot_stock_id)
self.assertEqual(inventory.move_ids.location_dest_id, self.env.ref('stock.location_inventory')) # Inventory loss
self.assertEqual(inventory.move_ids.state, 'done')
quants = self.env['stock.quant']._gather(self.product_1, self.env.ref('stock.location_inventory'))
self.assertEqual(len(quants), 1) # One quant created for inventory loss
# Check quantity of product in various locations: current, its parent, brother and other
self.assertEqual(self.env['stock.quant']._gather(self.product_1, self.warehouse_1.lot_stock_id).quantity, 35.0)
self.assertEqual(self.env['stock.quant']._gather(self.product_1, self.warehouse_1.lot_stock_id.location_id).quantity, 35.0)
self.assertEqual(self.env['stock.quant']._gather(self.product_1, self.warehouse_1.view_location_id).quantity, 35.0)
self.assertEqual(self.env['stock.quant']._gather(self.product_1, self.warehouse_1.wh_input_stock_loc_id).quantity, 0.0)
self.assertEqual(self.env['stock.quant']._gather(self.product_1, self.env.ref('stock.stock_location_stock')).quantity, 0.0)
def test_inventory_wizard(self):
self.product_1.type = 'product'
inventory_wizard = self.env['stock.change.product.qty'].create({
'product_id': self.product_1.id,
'new_quantity': 50.0,
'location_id': self.warehouse_1.lot_stock_id.id,
})
inventory_wizard.change_product_qty()
# Check inventory performed in setup was effectivley performed
self.assertEqual(self.product_1.virtual_available, 50.0)
self.assertEqual(self.product_1.qty_available, 50.0)
# Check inventory obj details (1 inventory with 1 line, because 1 product change)
inventory = self.env['stock.inventory'].search([('id', 'not in', self.existing_inventories.ids)])
self.assertEqual(len(inventory), 1)
self.assertIn('INV: %s' % self.product_1.name, inventory.name)
self.assertEqual(len(inventory.line_ids), 1)
self.assertEqual(inventory.line_ids.product_id, self.product_1)
self.assertEqual(inventory.line_ids.product_qty, 50.0)
# Check associated quants: 2 quants for the product and the quantity (1 in stock, 1 in inventory adjustment)
quant = self.env['stock.quant'].search([('id', 'not in', self.existing_quants.ids)])
self.assertEqual(len(quant), 2)
# print quant.name, quant.product_id, quant.location_id
# TDE TODO: expand this test
def test_basic_move(self):
# TDE NOTE: replaces test/move.yml present until saas-10, including onchanges
product = self.product_3.sudo(self.user_stock_manager)
product.type = 'product'
picking_out = self.env['stock.picking'].create({
'partner_id': self.env.ref('base.res_partner_2').id,
'picking_type_id': self.env.ref('stock.picking_type_out').id,
'location_id': self.warehouse_1.lot_stock_id.id,
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
})
customer_move = self.env['stock.move'].create({
'name': product.name,
'product_id': product.id,
'product_uom_qty': 5,
'product_uom': product.uom_id.id,
'picking_id': picking_out.id,
'location_id': self.warehouse_1.lot_stock_id.id,
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
})
# simulate create + onchange
# test move values
self.assertEqual(customer_move.product_uom, product.uom_id)
self.assertEqual(customer_move.location_id, self.warehouse_1.lot_stock_id)
self.assertEqual(customer_move.location_dest_id, self.env.ref('stock.stock_location_customers'))
# confirm move, check quantity on hand and virtually available, without location context
customer_move._action_confirm()
self.assertEqual(product.qty_available, 0.0)
self.assertEqual(product.virtual_available, -5.0)
customer_move.quantity_done = 5
customer_move._action_done()
self.assertEqual(product.qty_available, -5.0)
# compensate negative quants by receiving products from supplier
receive_move = self._create_move(product, self.env.ref('stock.stock_location_suppliers'), self.warehouse_1.lot_stock_id, product_uom_qty=15)
receive_move._action_confirm()
receive_move.quantity_done = 15
receive_move._action_done()
product._compute_quantities()
self.assertEqual(product.qty_available, 10.0)
self.assertEqual(product.virtual_available, 10.0)
# new move towards customer
customer_move_2 = self._create_move(product, self.warehouse_1.lot_stock_id, self.env.ref('stock.stock_location_customers'), product_uom_qty=2)
customer_move_2._action_confirm()
product._compute_quantities()
self.assertEqual(product.qty_available, 10.0)
self.assertEqual(product.virtual_available, 8.0)
customer_move_2.quantity_done = 2.0
customer_move_2._action_done()
product._compute_quantities()
self.assertEqual(product.qty_available, 8.0)
def test_inventory_adjustment_and_negative_quants_1(self):
"""Make sure negative quants from returns get wiped out with an inventory adjustment"""
productA = self.env['product.product'].create({'name': 'Product A', 'type': 'product'})
stock_location = self.env.ref('stock.stock_location_stock')
customer_location = self.env.ref('stock.stock_location_customers')
# Create a picking out and force availability
picking_out = self.env['stock.picking'].create({
'partner_id': self.env.ref('base.res_partner_2').id,
'picking_type_id': self.env.ref('stock.picking_type_out').id,
'location_id': stock_location.id,
'location_dest_id': customer_location.id,
})
self.env['stock.move'].create({
'name': productA.name,
'product_id': productA.id,
'product_uom_qty': 1,
'product_uom': productA.uom_id.id,
'picking_id': picking_out.id,
'location_id': stock_location.id,
'location_dest_id': customer_location.id,
})
picking_out.action_confirm()
picking_out.force_assign()
picking_out.move_lines.quantity_done = 1
picking_out.action_done()
quant = self.env['stock.quant'].search([('product_id', '=', productA.id), ('location_id', '=', stock_location.id)])
self.assertEqual(len(quant), 1)
stock_return_picking = self.env['stock.return.picking']\
.with_context(active_ids=picking_out.ids, active_id=picking_out.ids[0])\
.create({})
stock_return_picking.product_return_moves.quantity = 1.0
stock_return_picking_action = stock_return_picking.create_returns()
return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
return_pick.action_assign()
return_pick.move_lines.quantity_done = 1
return_pick.action_done()
quant = self.env['stock.quant'].search([('product_id', '=', productA.id), ('location_id', '=', stock_location.id)])
self.assertEqual(len(quant), 0)
def test_inventory_adjustment_and_negative_quants_2(self):
"""Make sure negative quants get wiped out with an inventory adjustment"""
productA = self.env['product.product'].create({'name': 'Product A', 'type': 'product'})
stock_location = self.env.ref('stock.stock_location_stock')
customer_location = self.env.ref('stock.stock_location_customers')
location_loss = self.env.ref('stock.location_inventory')
# Create a picking out and force availability
picking_out = self.env['stock.picking'].create({
'partner_id': self.env.ref('base.res_partner_2').id,
'picking_type_id': self.env.ref('stock.picking_type_out').id,
'location_id': stock_location.id,
'location_dest_id': customer_location.id,
})
self.env['stock.move'].create({
'name': productA.name,
'product_id': productA.id,
'product_uom_qty': 1,
'product_uom': productA.uom_id.id,
'picking_id': picking_out.id,
'location_id': stock_location.id,
'location_dest_id': customer_location.id,
})
picking_out.action_confirm()
picking_out.force_assign()
picking_out.move_lines.quantity_done = 1
picking_out.do_transfer()
# Make an inventory adjustment to set the quantity to 0
inventory = self.env['stock.inventory'].create({
'name': 'Starting for product_1',
'filter': 'product',
'location_id': stock_location.id,
'product_id': productA.id,
})
inventory.action_start()
self.assertEqual(len(inventory.line_ids), 1, "Wrong inventory lines generated.")
self.assertEqual(inventory.line_ids.theoretical_qty, -1, "Theoretical quantity should be -1.")
inventory.line_ids.product_qty = 0 # Put the quantity back to 0
inventory.action_done()
# The inventory adjustment should have created one
self.assertEqual(len(inventory.move_ids), 1)
quantity = inventory.move_ids.mapped('product_qty')
self.assertEqual(quantity, [1], "Moves created with wrong quantity.")
location_ids = inventory.move_ids.mapped('location_id').ids
self.assertEqual(set(location_ids), {location_loss.id})
# There should be no quant in the stock location
quants = self.env['stock.quant'].search([('product_id', '=', productA.id), ('location_id', '=', stock_location.id)])
self.assertEqual(len(quants), 0)
# There should be one quant in the inventory loss location
quant = self.env['stock.quant'].search([('product_id', '=', productA.id), ('location_id', '=', location_loss.id)])
self.assertEqual(len(quant), 1)
2018-07-06 14:58:06 +02:00
def test_resupply_route(self):
""" Simulate a resupply chain between warehouses.
Stock -> transit -> Dist. -> transit -> Shop -> Customer
Create the move from Shop to Customer and ensure that all the pull
rules are triggered in order to complete the move chain to Stock.
"""
warehouse_stock = self.env['stock.warehouse'].create({
'name': 'Stock.',
'code': 'STK',
})
warehouse_distribution = self.env['stock.warehouse'].create({
'name': 'Dist.',
'code': 'DIST',
'default_resupply_wh_id': warehouse_stock.id,
'resupply_wh_ids': [(6, 0, [warehouse_stock.id])]
})
warehouse_shop = self.env['stock.warehouse'].create({
'name': 'Shop',
'code': 'SHOP',
'default_resupply_wh_id': warehouse_distribution.id,
'resupply_wh_ids': [(6, 0, [warehouse_distribution.id])]
})
route_stock_to_dist = warehouse_distribution.resupply_route_ids
route_dist_to_shop = warehouse_shop.resupply_route_ids
# Change the procure_method on the pull rules between dist and shop
# warehouses. Since mto and resupply routes are both on product it will
# select one randomly between them and if it select the resupply it is
# 'make to stock' and it will not create the picking between stock and
# dist warehouses.
route_dist_to_shop.pull_ids.write({'procure_method': 'make_to_order'})
product = self.env['product.product'].create({
'name': 'Fakir',
'type': 'product',
'route_ids': [(4, route_id) for route_id in [route_stock_to_dist.id, route_dist_to_shop.id, self.env.ref('stock.route_warehouse0_mto').id]],
})
picking_out = self.env['stock.picking'].create({
'partner_id': self.env.ref('base.res_partner_2').id,
'picking_type_id': self.env.ref('stock.picking_type_out').id,
'location_id': warehouse_shop.lot_stock_id.id,
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
})
self.env['stock.move'].create({
'name': product.name,
'product_id': product.id,
'product_uom_qty': 1,
'product_uom': product.uom_id.id,
'picking_id': picking_out.id,
'location_id': warehouse_shop.lot_stock_id.id,
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
'warehouse_id': warehouse_shop.id,
'procure_method': 'make_to_order',
})
picking_out.action_confirm()
moves = self.env['stock.move'].search([('product_id', '=', product.id)])
# Shop/Stock -> Customer
# Transit -> Shop/Stock
# Dist/Stock -> Transit
# Transit -> Dist/Stock
# Stock/Stock -> Transit
self.assertEqual(len(moves), 5, 'Invalid moves number.')
self.assertTrue(self.env['stock.move'].search([('location_id', '=', warehouse_stock.lot_stock_id.id)]))
self.assertTrue(self.env['stock.move'].search([('location_dest_id', '=', warehouse_distribution.lot_stock_id.id)]))
self.assertTrue(self.env['stock.move'].search([('location_id', '=', warehouse_distribution.lot_stock_id.id)]))
self.assertTrue(self.env['stock.move'].search([('location_dest_id', '=', warehouse_shop.lot_stock_id.id)]))
self.assertTrue(self.env['stock.move'].search([('location_id', '=', warehouse_shop.lot_stock_id.id)]))
def test_mutiple_resupply_warehouse(self):
""" Simulate the following situation:
- 2 shops with stock are resupply by 2 distinct warehouses
- Shop Namur is resupply by the warehouse stock Namur
- Shop Wavre is resupply by the warehouse stock Wavre
- Simulate 2 moves for the same product but in different shop.
This test ensure that the move are supplied by the correct distribution
warehouse.
"""
customer_location = self.env.ref('stock.stock_location_customers')
warehouse_distribution_wavre = self.env['stock.warehouse'].create({
'name': 'Stock Wavre.',
'code': 'WV',
})
warehouse_shop_wavre = self.env['stock.warehouse'].create({
'name': 'Shop Wavre',
'code': 'SHWV',
'default_resupply_wh_id': warehouse_distribution_wavre.id,
'resupply_wh_ids': [(6, 0, [warehouse_distribution_wavre.id])]
})
warehouse_distribution_namur = self.env['stock.warehouse'].create({
'name': 'Stock Namur.',
'code': 'NM',
})
warehouse_shop_namur = self.env['stock.warehouse'].create({
'name': 'Shop Namur',
'code': 'SHNM',
'default_resupply_wh_id': warehouse_distribution_namur.id,
'resupply_wh_ids': [(6, 0, [warehouse_distribution_namur.id])]
})
route_shop_namur = warehouse_shop_namur.resupply_route_ids
route_shop_wavre = warehouse_shop_wavre.resupply_route_ids
# The product contains the 2 resupply routes.
product = self.env['product.product'].create({
'name': 'Fakir',
'type': 'product',
'route_ids': [(4, route_id) for route_id in [route_shop_namur.id, route_shop_wavre.id, self.env.ref('stock.route_warehouse0_mto').id]],
})
# Add 1 quant in each distribution warehouse.
self.env['stock.quant']._update_available_quantity(product, warehouse_distribution_wavre.lot_stock_id, 1.0)
self.env['stock.quant']._update_available_quantity(product, warehouse_distribution_namur.lot_stock_id, 1.0)
# Create the move for the shop Namur. Should create a resupply from
# distribution warehouse Namur.
picking_out_namur = self.env['stock.picking'].create({
'partner_id': self.env.ref('base.res_partner_2').id,
'picking_type_id': self.env.ref('stock.picking_type_out').id,
'location_id': warehouse_shop_namur.lot_stock_id.id,
'location_dest_id': customer_location.id,
})
self.env['stock.move'].create({
'name': product.name,
'product_id': product.id,
'product_uom_qty': 1,
'product_uom': product.uom_id.id,
'picking_id': picking_out_namur.id,
'location_id': warehouse_shop_namur.lot_stock_id.id,
'location_dest_id': customer_location.id,
'warehouse_id': warehouse_shop_namur.id,
'procure_method': 'make_to_order',
})
picking_out_namur.action_confirm()
# Validate the picking
# Dist. warehouse Namur -> transit Location -> Shop Namur
picking_stock_transit = self.env['stock.picking'].search([('location_id', '=', warehouse_distribution_namur.lot_stock_id.id)])
self.assertTrue(picking_stock_transit)
picking_stock_transit.action_assign()
picking_stock_transit.move_lines[0].quantity_done = 1.0
picking_stock_transit.action_done()
picking_transit_shop_namur = self.env['stock.picking'].search([('location_dest_id', '=', warehouse_shop_namur.lot_stock_id.id)])
self.assertTrue(picking_transit_shop_namur)
picking_transit_shop_namur.action_assign()
picking_transit_shop_namur.move_lines[0].quantity_done = 1.0
picking_transit_shop_namur.action_done()
picking_out_namur.action_assign()
picking_out_namur.move_lines[0].quantity_done = 1.0
picking_out_namur.action_done()
# Check that the correct quantity has been provided to customer
self.assertEqual(self.env['stock.quant']._gather(product, customer_location).quantity, 1)
# Ensure there still no quants in distribution warehouse
self.assertEqual(len(self.env['stock.quant']._gather(product, warehouse_distribution_namur.lot_stock_id)), 0)
# Create the move for the shop Wavre. Should create a resupply from
# distribution warehouse Wavre.
picking_out_wavre = self.env['stock.picking'].create({
'partner_id': self.env.ref('base.res_partner_2').id,
'picking_type_id': self.env.ref('stock.picking_type_out').id,
'location_id': warehouse_shop_wavre.lot_stock_id.id,
'location_dest_id': customer_location.id,
})
self.env['stock.move'].create({
'name': product.name,
'product_id': product.id,
'product_uom_qty': 1,
'product_uom': product.uom_id.id,
'picking_id': picking_out_wavre.id,
'location_id': warehouse_shop_wavre.lot_stock_id.id,
'location_dest_id': customer_location.id,
'warehouse_id': warehouse_shop_wavre.id,
'procure_method': 'make_to_order',
})
picking_out_wavre.action_confirm()
# Validate the picking
# Dist. warehouse Wavre -> transit Location -> Shop Wavre
picking_stock_transit = self.env['stock.picking'].search([('location_id', '=', warehouse_distribution_wavre.lot_stock_id.id)])
self.assertTrue(picking_stock_transit)
picking_stock_transit.action_assign()
picking_stock_transit.move_lines[0].quantity_done = 1.0
picking_stock_transit.action_done()
picking_transit_shop_wavre = self.env['stock.picking'].search([('location_dest_id', '=', warehouse_shop_wavre.lot_stock_id.id)])
self.assertTrue(picking_transit_shop_wavre)
picking_transit_shop_wavre.action_assign()
picking_transit_shop_wavre.move_lines[0].quantity_done = 1.0
picking_transit_shop_wavre.action_done()
picking_out_wavre.action_assign()
picking_out_wavre.move_lines[0].quantity_done = 1.0
picking_out_wavre.action_done()
# Check that the correct quantity has been provided to customer
self.assertEqual(self.env['stock.quant']._gather(product, customer_location).quantity, 2)
# Ensure there still no quants in distribution warehouse
self.assertEqual(len(self.env['stock.quant']._gather(product, warehouse_distribution_wavre.lot_stock_id)), 0)
class TestResupply(TestStockCommon):
def setUp(self):
super(TestResupply, self).setUp()
self.warehouse_2 = self.env['stock.warehouse'].create({
'name': 'Small Warehouse',
'code': 'SWH',
'default_resupply_wh_id': self.warehouse_1.id,
'resupply_wh_ids': [(6, 0, [self.warehouse_1.id])]
})
# minimum stock rule for test product on this warehouse
self.env['stock.warehouse.orderpoint'].create({
'warehouse_id': self.warehouse_2.id,
'location_id': self.warehouse_2.lot_stock_id.id,
'product_id': self.product_1.id,
'product_min_qty': 10,
'product_max_qty': 100,
'product_uom': self.uom_unit.id,
})