# -*- coding: utf-8 -*- 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) 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, })