From 09abac7608cc60a56de8e59184575f40629215d5 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Thu, 26 Feb 2015 11:02:12 +0100 Subject: [PATCH] [FIX] Stock: Make sure inventory works with packs and lot = False The inventory should work with packs. If a pack is not indicated in the inventory line, it means we correct the quantity for the product that is not in a pack. Also, when the lot is False, it should not behave as normal stock moves. It should correct for the quantity that has no lot. We try to reconcile the negative quants in the pack also. --- addons/stock/stock.py | 61 ++++++++++----------------- addons/stock/tests/test_stock_flow.py | 52 +++++++++++++++++++++++ 2 files changed, 74 insertions(+), 39 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 85af75c9cd0..4bd984b6558 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -2644,14 +2644,6 @@ class stock_inventory(osv.osv): self.pool.get('stock.inventory.line').write(cr, uid, line_ids, {'product_qty': 0}) return True - def _inventory_line_hook(self, cr, uid, inventory_line, move_vals): - """ Creates a stock move from an inventory line - @param inventory_line: - @param move_vals: - @return: - """ - return self.pool.get('stock.move').create(cr, uid, move_vals) - def action_done(self, cr, uid, ids, context=None): """ Finish the inventory @return: True @@ -2670,35 +2662,7 @@ class stock_inventory(osv.osv): #as they will be moved to inventory loss, and other quants will be created to the encoded quant location. This is a normal behavior #as quants cannot be reuse from inventory location (users can still manually move the products before/after the inventory if they want). move_obj = self.pool.get('stock.move') - move_obj.action_done(cr, uid, [x.id for x in inv.move_ids], context=context) - - def _create_stock_move(self, cr, uid, inventory, todo_line, context=None): - stock_move_obj = self.pool.get('stock.move') - product_obj = self.pool.get('product.product') - inventory_location_id = product_obj.browse(cr, uid, todo_line['product_id'], context=context).property_stock_inventory.id - vals = { - 'name': _('INV:') + (inventory.name or ''), - 'product_id': todo_line['product_id'], - 'product_uom': todo_line['product_uom_id'], - 'date': inventory.date, - 'company_id': inventory.company_id.id, - 'inventory_id': inventory.id, - 'state': 'assigned', - 'restrict_lot_id': todo_line.get('prod_lot_id'), - 'restrict_partner_id': todo_line.get('partner_id'), - } - - if todo_line['product_qty'] < 0: - #found more than expected - vals['location_id'] = inventory_location_id - vals['location_dest_id'] = todo_line['location_id'] - vals['product_uom_qty'] = -todo_line['product_qty'] - else: - #found less than expected - vals['location_id'] = todo_line['location_id'] - vals['location_dest_id'] = inventory_location_id - vals['product_uom_qty'] = todo_line['product_qty'] - return stock_move_obj.create(cr, uid, vals, context=context) + move_obj.action_done(cr, uid, [x.id for x in inv.move_ids if x.state != 'done'], context=context) def action_check(self, cr, uid, ids, context=None): """ Checks the inventory and computes the stock move to do @@ -2712,13 +2676,14 @@ class stock_inventory(osv.osv): stock_move_obj.unlink(cr, uid, move_ids, context=context) for line in inventory.line_ids: #compare the checked quantities on inventory lines to the theorical one - inventory_line_obj._resolve_inventory_line(cr, uid, line, context=context) + stock_move = inventory_line_obj._resolve_inventory_line(cr, uid, line, context=context) def action_cancel_draft(self, cr, uid, ids, context=None): """ Cancels the stock move and change inventory state to draft. @return: True """ for inv in self.browse(cr, uid, ids, context=context): + self.write(cr, uid, [inv.id], {'line_ids': [(5,)]}, context=context) self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in inv.move_ids], context=context) self.write(cr, uid, [inv.id], {'state': 'draft'}, context=context) return True @@ -2869,6 +2834,7 @@ class stock_inventory_line(osv.osv): def _resolve_inventory_line(self, cr, uid, inventory_line, context=None): stock_move_obj = self.pool.get('stock.move') + quant_obj = self.pool.get('stock.quant') diff = inventory_line.theoretical_qty - inventory_line.product_qty if not diff: return @@ -2895,7 +2861,24 @@ class stock_inventory_line(osv.osv): vals['location_id'] = inventory_line.location_id.id vals['location_dest_id'] = inventory_location_id vals['product_uom_qty'] = diff - return stock_move_obj.create(cr, uid, vals, context=context) + move_id = stock_move_obj.create(cr, uid, vals, context=context) + move = stock_move_obj.browse(cr, uid, move_id, context=context) + if diff > 0: + domain = [('qty', '>', 0.0), ('package_id', '=', inventory_line.package_id.id), ('lot_id', '=', inventory_line.prod_lot_id.id)] + preferred_domain_list = [[('reservation_id', '=', False)], [('reservation_id.inventory_id', '!=', inventory_line.inventory_id.id)]] + quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, move.product_qty, domain=domain, prefered_domain_list=preferred_domain_list, restrict_partner_id=move.restrict_partner_id.id, context=context) + quant_obj.quants_reserve(cr, uid, quants, move, context=context) + elif inventory_line.package_id: + stock_move_obj.action_done(cr, uid, move_id, context=context) + quants = [x.id for x in move.quant_ids] + quant_obj.write(cr, uid, quants, {'package_id': inventory_line.package_id.id}, context=context) + res = quant_obj.search(cr, uid, [('qty', '<', 0.0), ('product_id', '=', move.product_id.id), + ('location_id', '=', move.location_dest_id.id), ('package_id', '!=', False)], limit=1, context=context) + if res: + for quant in move.quant_ids: + if quant.location_id.id == move.location_dest_id.id: #To avoid we take a quant that was reconcile already + quant_obj._quant_reconcile_negative(cr, uid, quant, move, context=context) + return move_id # Should be left out in next version def restrict_change(self, cr, uid, ids, theoretical_qty, context=None): diff --git a/addons/stock/tests/test_stock_flow.py b/addons/stock/tests/test_stock_flow.py index 32c9e5e3eb0..bd3302b5be9 100644 --- a/addons/stock/tests/test_stock_flow.py +++ b/addons/stock/tests/test_stock_flow.py @@ -1095,3 +1095,55 @@ class TestStockFlow(TestStockCommon): total_qty = [quant.qty for quant in quants] self.assertEqual(sum(total_qty), 4000, 'Expecting 4000 kg , got %.4f on location stock!' % (sum(total_qty))) self.assertEqual(productKG.qty_available, 4000, 'Expecting 4000 kg , got %.4f of quantity available!' % (productKG.qty_available)) + + + #-------------------------------------------------------- + # TEST PARTIAL INVENTORY WITH PACKS and LOTS + #--------------------------------------------------------- + + packproduct = self.ProductObj.create({'name': 'Pack Product', 'uom_id': self.uom_unit.id, 'uom_po_id': self.uom_unit.id}) + lotproduct = self.ProductObj.create({'name': 'Lot Product', 'uom_id': self.uom_unit.id, 'uom_po_id': self.uom_unit.id}) + inventory = self.InvObj.create({'name': 'Test Partial and Pack', + 'filter': 'partial', + 'location_id': self.stock_location}) + inventory.prepare_inventory() + pack_obj = self.env['stock.quant.package'] + lot_obj = self.env['stock.production.lot'] + pack1 = pack_obj.create({'name': 'PACK00TEST1'}) + pack2 = pack_obj.create({'name': 'PACK00TEST2'}) + lot1 = lot_obj.create({'name': 'Lot001', 'product_id': lotproduct.id}) + move = self.MoveObj.search([('product_id', '=', productKG.id), ('inventory_id', '=', inventory.id)], limit=1) + self.assertEqual(len(move), 0, "Partial filter should not create a lines upon prepare") + + line_vals = [] + line_vals += [{'location_id': self.stock_location, 'product_id': packproduct.id, 'product_qty': 10, 'product_uom_id': packproduct.uom_id.id}] + line_vals += [{'location_id': self.stock_location, 'product_id': packproduct.id, 'product_qty': 20, 'product_uom_id': packproduct.uom_id.id, 'package_id': pack1.id}] + line_vals += [{'location_id': self.stock_location, 'product_id': lotproduct.id, 'product_qty': 30, 'product_uom_id': lotproduct.uom_id.id, 'prod_lot_id': lot1.id}] + line_vals += [{'location_id': self.stock_location, 'product_id': lotproduct.id, 'product_qty': 25, 'product_uom_id': lotproduct.uom_id.id, 'prod_lot_id': False}] + inventory.write({'line_ids': [(0, 0, x) for x in line_vals]}) + inventory.action_done() + self.assertEqual(packproduct.qty_available, 30, "Wrong qty available for packproduct") + self.assertEqual(lotproduct.qty_available, 55, "Wrong qty available for lotproduct") + quants = self.StockQuantObj.search([('product_id', '=', packproduct.id), ('location_id', '=', self.stock_location), ('package_id', '=', pack1.id)]) + total_qty = sum([quant.qty for quant in quants]) + self.assertEqual(total_qty, 20, 'Expecting 20 units on package 1 of packproduct, but we got %.4f on location stock!' % (total_qty)) + + #Create an inventory that will put the lots without lot to 0 and check that taking without pack will not take it from the pack + inventory2 = self.InvObj.create({'name': 'Test Partial Lot and Pack2', + 'filter': 'partial', + 'location_id': self.stock_location}) + inventory2.prepare_inventory() + line_vals = [] + line_vals += [{'location_id': self.stock_location, 'product_id': packproduct.id, 'product_qty': 20, 'product_uom_id': packproduct.uom_id.id}] + line_vals += [{'location_id': self.stock_location, 'product_id': lotproduct.id, 'product_qty': 0, 'product_uom_id': lotproduct.uom_id.id, 'prod_lot_id': False}] + line_vals += [{'location_id': self.stock_location, 'product_id': lotproduct.id, 'product_qty': 10, 'product_uom_id': lotproduct.uom_id.id, 'prod_lot_id': lot1.id}] + inventory2.write({'line_ids': [(0, 0, x) for x in line_vals]}) + inventory2.action_done() + self.assertEqual(packproduct.qty_available, 40, "Wrong qty available for packproduct") + self.assertEqual(lotproduct.qty_available, 10, "Wrong qty available for lotproduct") + quants = self.StockQuantObj.search([('product_id', '=', lotproduct.id), ('location_id', '=', self.stock_location), ('lot_id', '=', lot1.id)]) + total_qty = sum([quant.qty for quant in quants]) + self.assertEqual(total_qty, 10, 'Expecting 0 units lot of lotproduct, but we got %.4f on location stock!' % (total_qty)) + quants = self.StockQuantObj.search([('product_id', '=', lotproduct.id), ('location_id', '=', self.stock_location), ('lot_id', '=', False)]) + total_qty = sum([quant.qty for quant in quants]) + self.assertEqual(total_qty, 0, 'Expecting 0 units lot of lotproduct, but we got %.4f on location stock!' % (total_qty)) \ No newline at end of file