From 40170ea04c024c015ce2ba2b9454fea3a725a51c Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Mon, 3 Mar 2014 17:42:10 +0100 Subject: [PATCH 01/12] [WIP] Include putaways in pack operations instead (need to change quants move yet) bzr revid: jco@openerp.com-20140303164210-iyhkvae20ayowp4a --- addons/stock/report/picking.py | 37 +++--- addons/stock/stock.py | 198 +++++++++++++++++++-------------- addons/stock/stock_view.xml | 2 + 3 files changed, 136 insertions(+), 101 deletions(-) diff --git a/addons/stock/report/picking.py b/addons/stock/report/picking.py index ce901a8b92e..a82e72766ac 100644 --- a/addons/stock/report/picking.py +++ b/addons/stock/report/picking.py @@ -68,24 +68,25 @@ class picking(report_sxw.rml_parse): def get_internal_picking_dest_lines(self, picking): res = [] - for line in picking.move_lines: - row = {'description': self.get_product_desc(line)} - if not line.putaway_ids: - row['quantity'] = line.product_uom_qty - row['uom'] = line.product_uom.name - row['location_id'] = line.location_dest_id.name - row['barcode'] = line.product_id.ean13 - row['lot_id'] = '' - res.append(row) - else: - for rec in line.putaway_ids: - row2 = row.copy() - row2['quantity'] = rec.quantity - row2['uom'] = line.product_id.uom_id.name - row2['location_id'] = rec.location_id.name - row2['lot_id'] = rec.lot_id and rec.lot_id.name or '' - row2['barcode'] = rec.lot_id and rec.lot_id.name or line.product_id.ean13 - res.append(row2) + #TODO: rewrite according to pack ops +# for line in picking.move_lines: +# row = {'description': self.get_product_desc(line)} +# if not line.putaway_ids: +# row['quantity'] = line.product_uom_qty +# row['uom'] = line.product_uom.name +# row['location_id'] = line.location_dest_id.name +# row['barcode'] = line.product_id.ean13 +# row['lot_id'] = '' +# res.append(row) +# else: +# for rec in line.putaway_ids: +# row2 = row.copy() +# row2['quantity'] = rec.quantity +# row2['uom'] = line.product_id.uom_id.name +# row2['location_id'] = rec.location_id.name +# row2['lot_id'] = rec.lot_id and rec.lot_id.name or '' +# row2['barcode'] = rec.lot_id and rec.lot_id.name or line.product_id.ean13 +# res.append(row2) return res report_sxw.report_sxw('report.stock.picking.list', 'stock.picking', 'addons/stock/report/picking.rml', parser=picking) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index d78753d648c..5e883f2ea86 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -344,13 +344,14 @@ class stock_quant(osv.osv): :param move: browse record (stock.move) :param qty: float - :returns: list of tuple build as [(browe record (stock.move), float)] + :returns: list of tuple build as [(browse record (stock.move), float)] ''' - if move.putaway_ids: - res = [] - for record in move.putaway_ids: - res.append((record.location_id, record.quantity)) - return res + #TODO: rewrite according to pack ops +# if move.putaway_ids: +# res = [] +# for record in move.putaway_ids: +# res.append((record.location_id, record.quantity)) +# return res return [(move.location_dest_id, qty)] def move_single_quant(self, cr, uid, quant, location_to, qty, move, context=None): @@ -933,37 +934,84 @@ class stock_picking(osv.osv): self.action_assign(cr, uid, picking_ids, context=context) self.do_prepare_partial(cr, uid, picking_ids, context=context) + def _quant_putaway_apply(self, cr, uid, picking, quant, putaway, context=None): + if putaway.method == 'fixed' and putaway.location_spec_id: + return putaway.location_spec_id + return False + + + def _putaway_apply(self, cr, uid, picking, quants, qtys_remaining, context=None): + """ + Apply putaway strategy to quants and remaining quantities + """ + product_putaway = {} + quant_dict = {} + for quant in quants: + location = False + # Search putaway strategy + if product_putaway.get(quant.product_id.id): + putaway = product_putaway[quant.product_id.id] + else: + putaway = self.pool.get('stock.location').get_putaway_strategy(cr, uid, picking.location_dest_id, quant.product_id, context=context) + product_putaway[quant.product_id.id] = putaway + if putaway: + location = self._quant_putaway_apply(cr, uid, picking, quant, putaway, context=context) + if location: + quant_dict[quant] = [(quant.qty, location,)] + if not location: + quant_dict[quant] = [(quant.qty, picking.location_dest_id,)] + + remaining_dict = {} + for product in qtys_remaining.keys(): + location = False + if product_putaway.get(product): + putaway = product_putaway[product] + else: + putaway = self.pool.get('stock.location').get_putaway_strategy(cr, uid, picking.location_dest_id, product, context=context) + product_putaway[product] = putaway + if putaway: + location = self._quant_putaway_apply(cr, uid, picking, False, putaway, context=context) + if location: + remaining_dict[product] = [(qtys_remaining[product], location,)] + if not location: + remaining_dict[product] = [(qtys_remaining[product], picking.location_dest_id,)] + return (quant_dict, remaining_dict,) + def do_prepare_partial(self, cr, uid, picking_ids, context=None): context = context or {} pack_operation_obj = self.pool.get('stock.pack.operation') pack_obj = self.pool.get("stock.quant.package") quant_obj = self.pool.get("stock.quant") + + #get list of existing operations and delete them existing_package_ids = pack_operation_obj.search(cr, uid, [('picking_id', 'in', picking_ids)], context=context) if existing_package_ids: pack_operation_obj.unlink(cr, uid, existing_package_ids, context) for picking in self.browse(cr, uid, picking_ids, context=context): - packages = [] reserved_move = {} - qtys_remaining = {} + qtys_remaining = {} #Quantity remaining after calculating reserved quants + quants = [] #Calculate packages, reserved quants, qtys of this picking's moves for move in picking.move_lines: + quants += move.reserved_quant_ids if move.state not in ('assigned', 'confirmed'): continue - packages += [x.package_id for x in move.reserved_quant_ids if x.package_id] reserved_move[move.id] = set([x.id for x in move.reserved_quant_ids]) if move.state == 'assigned': - qty = move.product_qty + qty = move.product_qty - move.reserved_availability else: - qty = move.reserved_availability + qty = 0 #Add qty to qtys remaining - if qtys_remaining.get(move.product_id.id): - qtys_remaining[move.product_id.id] += qty + if qtys_remaining.get(move.product_id): + qtys_remaining[move.product_id] += qty else: - qtys_remaining[move.product_id.id] = qty + qtys_remaining[move.product_id] = qty + (putaway_quants_dict, putaway_remaining_dict) = self._putaway_apply(cr, uid, picking, quants, qtys_remaining, context=context) + packages = list(set([x.package_id for x in putaway_quants_dict.keys() if x])) # Try to find as much as possible top-level packages that can be moved top_lvl_packages = set() @@ -973,8 +1021,28 @@ class stock_picking(osv.osv): test_pack = pack while loop: quants = pack_obj.get_content(cr, uid, [test_pack.id], context=context) - move_list = [m.id for m in picking.move_lines] - if all([(x.reservation_id and x.reservation_id.id in move_list or False) for x in quant_obj.browse(cr, uid, quants, context=context)]): + quants_to_compare = putaway_quants_dict.keys() +# move_list = [m.id for m in picking.move_lines] + common_location = False + all_in = True + for quant in quant_obj.browse(cr, uid, quants, context=context): + # If the quant is not in the quants to compare and not in the common location + if not quant in quants_to_compare: + all_in = False + break + else: + for quants_tup in putaway_quants_dict[quant]: + if not common_location: + common_location = quants_tup[1] + elif common_location != quants_tup[1]: + all_in = False + break +# if not (quant in quants_to_compare and (not common_location or putaway_quants_dict[quant][1] == common_location)): +# all_in = False +# break +# elif not common_location and quant in quants_to_compare: +# common_location = putaway_quants_dict[quant][1] + if all_in: good_pack = test_pack.id if test_pack.parent_id: test_pack = test_pack.parent_id @@ -991,34 +1059,40 @@ class stock_picking(osv.osv): # Create pack operations for the top-level packages found for pack in pack_obj.browse(cr, uid, list(top_lvl_packages), context=context): quants = pack_obj.get_content(cr, uid, [pack.id], context=context) - for quant in quant_obj.browse(cr, uid, quants, context=context): - reserved_move[quant.reservation_id.id] -= set([quant.id]) - qtys_remaining[quant.product_id.id] -= quant.qty + quant = quant_obj.browse(cr, uid, quants[0], context=context) pack_operation_obj.create(cr, uid, { 'picking_id': picking.id, 'package_id': pack.id, 'product_qty': 1.0, + 'location_id': pack.location_id.id, + 'location_dest_id': putaway_quants_dict[quant][0][1].id, }, context=context) + for quant in quant_obj.browse(cr, uid, quants, context=context): + reserved_move[quant.reservation_id.id] -= set([quant.id]) + del putaway_quants_dict[quant] + qtys_remaining[quant.product_id] -= quant.qty + - # Go through all remaining reserved quants and group by product, package, lot, owner qtys_grouped = {} - for move, quant_set in reserved_move.items(): - for quant in quant_obj.browse(cr, uid, list(quant_set), context=context): - qtys_remaining[quant.product_id.id] -= quant.qty - key = (quant.product_id.id, quant.package_id.id, quant.lot_id.id, quant.owner_id.id) - if qtys_grouped.get(key): - qtys_grouped[key] += quant.qty - else: - qtys_grouped[key] = quant.qty - + # Go through all remaining reserved quants and group by product, package, lot, owner, source location and dest location + for quant in putaway_quants_dict.keys(): + for quant_tup in putaway_quants_dict[quant]: + if quant_tup[0] > 0: + key = (quant.product_id.id, quant.package_id.id, quant.lot_id.id, quant.owner_id.id, quant.location_id.id, quant_tup[1].id) + if qtys_grouped.get(key): + qtys_grouped[key] += quant_tup[0] + else: + qtys_grouped[key] = quant_tup[0] + # Add remaining qtys (in cases of force_assign for example) - for product in qtys_remaining.keys(): - if qtys_remaining[product] > 0: - key = (product, False, False, False) - if qtys_grouped.get(key): - qtys_grouped[key] += qtys_remaining[product] - else: - qtys_grouped[key] = qtys_remaining[product] + for product in putaway_remaining_dict.keys(): + for product_tup in putaway_remaining_dict[product]: + if product_tup[0] > 0: + key = (product.id, False, False, False, picking.location_id.id, product_tup[1].id) + if qtys_grouped.get(key): + qtys_grouped[key] += product_tup[0] + else: + qtys_grouped[key] = product_tup[0] # Create the necessary operations for the grouped quants and remaining qtys for key, qty in qtys_grouped.items(): @@ -1029,6 +1103,8 @@ class stock_picking(osv.osv): 'package_id': key[1], 'lot_id': key[2], 'owner_id': key[3], + 'location_id': key[4], + 'location_dest_id': key[5], 'product_uom_id': self.pool.get("product.product").browse(cr, uid, key[0], context=context).uom_id.id, }, context=context) @@ -1446,7 +1522,6 @@ class stock_move(osv.osv): 'string_availability_info': fields.function(_get_string_qty_information, type='text', string='Availability', readonly=True, help='Show various information on stock availability for this move'), 'restrict_lot_id': fields.many2one('stock.production.lot', 'Lot', help="Technical field used to depict a restriction on the lot of quants to consider when marking this move as 'done'"), 'restrict_partner_id': fields.many2one('res.partner', 'Owner ', help="Technical field used to depict a restriction on the ownership of quants to consider when marking this move as 'done'"), - 'putaway_ids': fields.one2many('stock.move.putaway', 'move_id', 'Put Away Suggestions'), 'route_ids': fields.many2many('stock.location.route', 'stock_location_route_move', 'move_id', 'route_id', 'Destination route', help="Preferred route to be followed by the procurement order"), 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', help="Technical field depicting the warehouse to consider for the route selection on the next procurement (if any)."), } @@ -1517,10 +1592,7 @@ class stock_move(osv.osv): if move.state in ('done', 'cancel'): raise osv.except_osv(_('Operation Forbidden!'), _('Cannot unreserve a done move')) quant_obj.quants_unreserve(cr, uid, move, context=context) - putaway_values = [] - for putaway_rec in move.putaway_ids: - putaway_values.append((2, putaway_rec.id)) - self.write(cr, uid, [move.id], {'state': 'confirmed', 'putaway_ids': putaway_values}, context=context) + self.write(cr, uid, [move.id], {'state': 'confirmed'}, context=context) def _prepare_procurement_from_move(self, cr, uid, move, context=None): origin = (move.group_id and (move.group_id.name + ":") or "") + (move.rule_id and move.rule_id.name or "/") @@ -1568,37 +1640,7 @@ class stock_move(osv.osv): push_obj._apply(cr, uid, rule, move, context=context) return True - # Create the stock.move.putaway records - def _putaway_apply(self, cr, uid, move, putaway, context=None): - # Should call different methods here in later versions - moveputaway_obj = self.pool.get('stock.move.putaway') - quant_obj = self.pool.get('stock.quant') - if putaway.method == 'fixed' and putaway.location_spec_id: - qty = move.product_qty - for row in quant_obj.read_group(cr, uid, [('reservation_id', '=', move.id)], ['qty', 'lot_id'], ['lot_id'], context=context): - vals = { - 'move_id': move.id, - 'location_id': putaway.location_spec_id.id, - 'quantity': row['qty'], - 'lot_id': row.get('lot_id') and row['lot_id'][0] or False, - } - moveputaway_obj.create(cr, SUPERUSER_ID, vals, context=context) - qty -= row['qty'] - #if the quants assigned aren't fully explaining where the products have to be moved - if qty > 0: - vals = { - 'move_id': move.id, - 'location_id': putaway.location_spec_id.id, - 'quantity': qty, - } - moveputaway_obj.create(cr, SUPERUSER_ID, vals, context=context) - - def _putaway_check(self, cr, uid, ids, context=None): - for move in self.browse(cr, uid, ids, context=context): - putaway = self.pool.get('stock.location').get_putaway_strategy(cr, uid, move.location_dest_id, move.product_id, context=context) - if putaway: - self._putaway_apply(cr, uid, move, putaway, context=context) def _create_procurement(self, cr, uid, move, context=None): """ This will create a procurement order """ @@ -1840,7 +1882,6 @@ class stock_move(osv.osv): @return: True """ #check putaway method - self._putaway_check(cr, uid, ids, context=context) return self.write(cr, uid, ids, {'state': 'assigned'}) def check_tracking(self, cr, uid, move, lot_id, context=None): @@ -1923,8 +1964,6 @@ class stock_move(osv.osv): #force assignation of consumable products and picking type auto_force_assign if to_assign_moves: self.force_assign(cr, uid, to_assign_moves, context=context) - #check if a putaway rule is likely to be processed and store result on the move - self._putaway_check(cr, uid, ids, context=context) def action_cancel(self, cr, uid, ids, context=None): """ Cancels the moves and if all moves are cancelled it cancels the picking. @@ -3184,15 +3223,6 @@ class stock_location_path(osv.osv): }) move_obj.action_confirm(cr, uid, [move_id], context=None) -class stock_move_putaway(osv.osv): - _name = 'stock.move.putaway' - _description = 'Proposed Destination' - _columns = { - 'move_id': fields.many2one('stock.move', required=True), - 'location_id': fields.many2one('stock.location', 'Location', required=True), - 'lot_id': fields.many2one('stock.production.lot', 'Lot'), - 'quantity': fields.float('Quantity', required=True), - } @@ -3465,6 +3495,8 @@ class stock_pack_operation(osv.osv): 'currency': fields.many2one('res.currency', string="Currency", help="Currency in which Unit cost is expressed", ondelete='CASCADE'), 'linked_move_operation_ids': fields.one2many('stock.move.operation.link', 'operation_id', string='Linked Moves', readonly=True, help='Moves impacted by this operation for the computation of the remaining quantities'), 'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Qty'), + 'location_id': fields.many2one('stock.location', 'Location From'), + 'location_dest_id': fields.many2one('stock.location', 'Location To'), } _defaults = { diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index 596988da556..02f4799a940 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -771,12 +771,14 @@ + + From d305b72bdee8e1cf2e63807d7a3383710fe2b2e6 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Mon, 3 Mar 2014 18:31:47 +0100 Subject: [PATCH 02/12] [IMP] Apply the putaways from the pack ops bzr revid: jco@openerp.com-20140303173147-x26oes0xaed6lqc8 --- addons/stock/security/ir.model.access.csv | 1 - addons/stock/stock.py | 42 ++++++++--------------- 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/addons/stock/security/ir.model.access.csv b/addons/stock/security/ir.model.access.csv index 58a754d283c..9312527c4a4 100644 --- a/addons/stock/security/ir.model.access.csv +++ b/addons/stock/security/ir.model.access.csv @@ -66,7 +66,6 @@ access_procurement_rule_internal,procurement.rule.flow internal,model_procuremen access_stock_pack_operation_manager,stock.pack.operation manager,model_stock_pack_operation,stock.group_stock_manager,1,1,1,1 access_stock_pack_operation_user,stock.pack.operation user,model_stock_pack_operation,stock.group_stock_user,1,1,1,1 access_stock_pack_operation_all,stock.pack.operation all users,model_stock_pack_operation,base.group_user,1,0,0,0 -access_stock_putaway_all,stock.putaway all users,model_stock_move_putaway,base.group_user,1,0,0,0 access_product_putaway_all,product.putaway all users,model_product_putaway,base.group_user,1,0,0,0 access_stock_removal_all,product.removal all users,model_product_removal,base.group_user,1,0,0,0 access_stock_move_operation_link_manager,stock.move.operation.link manager,model_stock_move_operation_link,stock.group_stock_manager,1,1,1,1 diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 5e883f2ea86..7a1a2b2715e 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -322,12 +322,12 @@ class stock_quant(osv.osv): if move.reserved_availability == move.product_qty and move.state in ('confirmed', 'waiting'): self.pool.get('stock.move').write(cr, uid, [move.id], {'state': 'assigned'}, context=context) - def quants_move(self, cr, uid, quants, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, context=None): + def quants_move(self, cr, uid, quants, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, location_dest_id = False, context=None): """Moves all given stock.quant in the destination location of the given move. :param quants: list of tuple(browse record(stock.quant) or None, quantity to move) :param move: browse record (stock.move) - :param lot_id: ID of the lot that mus be set on the quants to move + :param lot_id: ID of the lot that must be set on the quants to move :param owner_id: ID of the partner that must own the quants to move :param src_package_id: ID of the package that contains the quants to move :param dest_package_id: ID of the package that must be set on the moved quant @@ -335,24 +335,11 @@ class stock_quant(osv.osv): for quant, qty in quants: if not quant: #If quant is None, we will create a quant to move (and potentially a negative counterpart too) - quant = self._quant_create(cr, uid, qty, move, lot_id=lot_id, owner_id=owner_id, src_package_id=src_package_id, dest_package_id=dest_package_id, context=context) - self.move_single_quant_tuple(cr, uid, quant, qty, move, context=context) + quant = self._quant_create(cr, uid, qty, move, lot_id=lot_id, owner_id=owner_id, src_package_id=src_package_id, dest_package_id=dest_package_id, force_location = location_dest_id, context=context) + if not location_dest_id: + location_dest_id = move.location_dest_id + self.move_single_quant_tuple(cr, uid, quant, qty, move, location_dest_id, context=context) - def check_preferred_location(self, cr, uid, move, qty, context=None): - '''Checks the preferred location on the move, if any returned by a putaway strategy, and returns a list of - tuple(location, qty) where the quant have to be moved - - :param move: browse record (stock.move) - :param qty: float - :returns: list of tuple build as [(browse record (stock.move), float)] - ''' - #TODO: rewrite according to pack ops -# if move.putaway_ids: -# res = [] -# for record in move.putaway_ids: -# res.append((record.location_id, record.quantity)) -# return res - return [(move.location_dest_id, qty)] def move_single_quant(self, cr, uid, quant, location_to, qty, move, context=None): '''Moves the given 'quant' in 'location_to' for the given 'qty', and logs the stock.move that triggered this move in the quant history. @@ -375,19 +362,18 @@ class stock_quant(osv.osv): quant.refresh() return new_quant - def move_single_quant_tuple(self, cr, uid, quant, qty, move, context=None): + def move_single_quant_tuple(self, cr, uid, quant, qty, move, location_dest_id, context=None): '''Effectively process the move of a tuple (quant record, qty to move). This may result in several quants moved if the preferred locations on the move say so but by default it will only move the quant record given as argument :param quant: browse record (stock.quant) :param qty: float :param move: browse record (stock.move) ''' - for location_to, qty in self.check_preferred_location(cr, uid, move, qty, context=context): - if not quant: - break - new_quant = self.move_single_quant(cr, uid, quant, location_to, qty, move, context=context) - self._quant_reconcile_negative(cr, uid, quant, move, context=context) - quant = new_quant + if not quant: + return True + new_quant = self.move_single_quant(cr, uid, quant, location_dest_id, qty, move, context=context) + self._quant_reconcile_negative(cr, uid, quant, move, context=context) + quant = new_quant def quants_get_prefered_domain(self, cr, uid, location, product, qty, domain=None, prefered_domain=False, fallback_domain=False, restrict_lot_id=False, restrict_partner_id=False, context=None): ''' This function tries to find quants in the given location for the given domain, by trying to first limit @@ -1011,7 +997,7 @@ class stock_picking(osv.osv): else: qtys_remaining[move.product_id] = qty (putaway_quants_dict, putaway_remaining_dict) = self._putaway_apply(cr, uid, picking, quants, qtys_remaining, context=context) - packages = list(set([x.package_id for x in putaway_quants_dict.keys() if x])) + packages = list(set([x.package_id for x in putaway_quants_dict.keys() if x and x.package_id])) # Try to find as much as possible top-level packages that can be moved top_lvl_packages = set() @@ -2037,7 +2023,7 @@ class stock_move(osv.osv): #if a package and a result_package is given, we don't enter here because it will be processed by process_packaging() later #but for operations having only result_package_id, we will create new quants in the final package directly package_id = record.operation_id.result_package_id.id or False - quant_obj.quants_move(cr, uid, quants, move, lot_id=ops.lot_id.id, owner_id=ops.owner_id.id, src_package_id=ops.package_id.id, dest_package_id=package_id, context=context) + quant_obj.quants_move(cr, uid, quants, move, lot_id=ops.lot_id.id, owner_id=ops.owner_id.id, src_package_id=ops.package_id.id, dest_package_id=package_id, location_dest_id = ops.location_dest_id, context=context) #packaging process pack_op_obj.process_packaging(cr, uid, ops, [x[0].id for x in quants if x[0]], context=context) move_qty[move.id] -= record.qty From d5673e4c33474056de9109cb9be7b7ddf3a143c5 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Fri, 7 Mar 2014 14:51:17 +0100 Subject: [PATCH 03/12] [IMP] Take location from pack operations when possible, otherwise use move location_id in get preferred bzr revid: jco@openerp.com-20140307135117-hqeja9stlakkx3cw --- addons/stock/stock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 7a1a2b2715e..1434a546485 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1936,7 +1936,7 @@ class stock_move(osv.osv): domain = main_domain[move.id] + self.pool.get('stock.move.operation.link').get_specific_domain(cr, uid, record, context=context) qty_already_assigned = sum([q.qty for q in record.reserved_quant_ids]) qty = record.qty - qty_already_assigned - quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=domain, prefered_domain=[], fallback_domain=[], restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context) + quants = quant_obj.quants_get_prefered_domain(cr, uid, ops.location_id or move.location_id, move.product_id, qty, domain=domain, prefered_domain=[], fallback_domain=[], restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context) quant_obj.quants_reserve(cr, uid, quants, move, record, context=context) for move in todo_moves: @@ -2016,7 +2016,7 @@ class stock_move(osv.osv): fallback_domain = [('reservation_id', '=', False)] self.check_tracking(cr, uid, move, ops.package_id.id or ops.lot_id.id, context=context) dom = main_domain + self.pool.get('stock.move.operation.link').get_specific_domain(cr, uid, record, context=context) - quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, record.qty, domain=dom, prefered_domain=prefered_domain, + quants = quant_obj.quants_get_prefered_domain(cr, uid, ops.location_id or move.location_id, move.product_id, record.qty, domain=dom, prefered_domain=prefered_domain, fallback_domain=fallback_domain, restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context) package_id = False if not record.operation_id.package_id: From 38afcf4d61ca18920eeec33d1cdc9db227ed42e9 Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Tue, 11 Mar 2014 14:31:09 +0100 Subject: [PATCH 04/12] [REF] stock: refactoring of do_prepare_partial bzr revid: qdp-launchpad@openerp.com-20140311133109-e07mnhy4iaqm63in --- addons/stock/stock.py | 321 ++++++++++++++++-------------------- addons/stock/stock_demo.yml | 6 + 2 files changed, 152 insertions(+), 175 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 1434a546485..003c6cdc910 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -322,11 +322,12 @@ class stock_quant(osv.osv): if move.reserved_availability == move.product_qty and move.state in ('confirmed', 'waiting'): self.pool.get('stock.move').write(cr, uid, [move.id], {'state': 'assigned'}, context=context) - def quants_move(self, cr, uid, quants, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, location_dest_id = False, context=None): - """Moves all given stock.quant in the destination location of the given move. + def quants_move(self, cr, uid, quants, move, location_to, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, context=None): + """Moves all given stock.quant in the given destination location. :param quants: list of tuple(browse record(stock.quant) or None, quantity to move) :param move: browse record (stock.move) + :param location_to: browse record (stock.location) depicting where the quants have to be moved :param lot_id: ID of the lot that must be set on the quants to move :param owner_id: ID of the partner that must own the quants to move :param src_package_id: ID of the package that contains the quants to move @@ -335,11 +336,9 @@ class stock_quant(osv.osv): for quant, qty in quants: if not quant: #If quant is None, we will create a quant to move (and potentially a negative counterpart too) - quant = self._quant_create(cr, uid, qty, move, lot_id=lot_id, owner_id=owner_id, src_package_id=src_package_id, dest_package_id=dest_package_id, force_location = location_dest_id, context=context) - if not location_dest_id: - location_dest_id = move.location_dest_id - self.move_single_quant_tuple(cr, uid, quant, qty, move, location_dest_id, context=context) - + quant = self._quant_create(cr, uid, qty, move, lot_id=lot_id, owner_id=owner_id, src_package_id=src_package_id, dest_package_id=dest_package_id, force_location=location_to, context=context) + self.move_single_quant(cr, uid, quant, location_to, qty, move, context=context) + self._quant_reconcile_negative(cr, uid, quant, move, context=context) def move_single_quant(self, cr, uid, quant, location_to, qty, move, context=None): '''Moves the given 'quant' in 'location_to' for the given 'qty', and logs the stock.move that triggered this move in the quant history. @@ -362,19 +361,6 @@ class stock_quant(osv.osv): quant.refresh() return new_quant - def move_single_quant_tuple(self, cr, uid, quant, qty, move, location_dest_id, context=None): - '''Effectively process the move of a tuple (quant record, qty to move). This may result in several quants moved - if the preferred locations on the move say so but by default it will only move the quant record given as argument - :param quant: browse record (stock.quant) - :param qty: float - :param move: browse record (stock.move) - ''' - if not quant: - return True - new_quant = self.move_single_quant(cr, uid, quant, location_dest_id, qty, move, context=context) - self._quant_reconcile_negative(cr, uid, quant, move, context=context) - quant = new_quant - def quants_get_prefered_domain(self, cr, uid, location, product, qty, domain=None, prefered_domain=False, fallback_domain=False, restrict_lot_id=False, restrict_partner_id=False, context=None): ''' This function tries to find quants in the given location for the given domain, by trying to first limit the choice on the quants that match the prefered_domain as well. But if the qty requested is not reached @@ -920,179 +906,167 @@ class stock_picking(osv.osv): self.action_assign(cr, uid, picking_ids, context=context) self.do_prepare_partial(cr, uid, picking_ids, context=context) - def _quant_putaway_apply(self, cr, uid, picking, quant, putaway, context=None): + def _picking_putaway_resolution(self, cr, uid, picking, product, putaway, context=None): if putaway.method == 'fixed' and putaway.location_spec_id: - return putaway.location_spec_id + return putaway.location_spec_id.id return False + def _get_top_level_packages(self, cr, uid, quants_suggested_locations, context=None): + """This method searches for the higher level packages that can be moved as a single operation, given a list of quants + to move and their suggested destination, and returns the list of matching packages. + """ + # Try to find as much as possible top-level packages that can be moved + pack_obj = self.pool.get("stock.quant.package") + quant_obj = self.pool.get("stock.quant") + top_lvl_packages = set() + quants_to_compare = [x.id for x in quants_suggested_locations.keys()] + for pack in list(set([x.package_id for x in quants_suggested_locations.keys() if x and x.package_id])): + loop = True + good_pack = False + test_pack = pack + while loop: + pack_quants = pack_obj.get_content(cr, uid, [test_pack.id], context=context) + pack_destination = False + all_in = True + for quant in quant_obj.browse(cr, uid, pack_quants, context=context): + # If the quant is not in the quants to compare and not in the common location + if not quant in quants_to_compare: + all_in = False + break + else: + #if putaway strat apply, the destination location of each quant may be different (and thus the package should not be taken as a single operation) + if not pack_destination: + pack_destination = quants_suggested_locations[quant] + elif pack_destination != quants_suggested_locations[quant]: + all_in = False + break + if all_in: + good_pack = test_pack.id + if test_pack.parent_id: + test_pack = test_pack.parent_id + else: + #stop the loop when there's no parent package anymore + loop = False + else: + #stop the loop when the package test_pack is not totally reserved for moves of this picking + #(some quants may be reserved for other picking or not reserved at all) + loop = False + if good_pack: + top_lvl_packages.add(good_pack) + return list(top_lvl_packages) - def _putaway_apply(self, cr, uid, picking, quants, qtys_remaining, context=None): + def _prepare_pack_ops(self, cr, uid, picking, quants, forced_qties, context=None): + """ returns a list of dict, ready to be used in create() of stock.pack.operation. + + :param picking: browse record (stock.picking) + :param quants: browse record list (stock.quant). List of quants associated to the picking + :param forced_qties: dictionary showing for each product (keys) its corresponding quantity (value) that is not covered by the quants associated to the picking """ - Apply putaway strategy to quants and remaining quantities - """ - product_putaway = {} - quant_dict = {} - for quant in quants: + def _picking_putaway_apply(product): location = False # Search putaway strategy - if product_putaway.get(quant.product_id.id): - putaway = product_putaway[quant.product_id.id] + if product_putaway_strats.get(product.id): + putaway_strat = product_putaway_strats[product.id] else: - putaway = self.pool.get('stock.location').get_putaway_strategy(cr, uid, picking.location_dest_id, quant.product_id, context=context) - product_putaway[quant.product_id.id] = putaway - if putaway: - location = self._quant_putaway_apply(cr, uid, picking, quant, putaway, context=context) - if location: - quant_dict[quant] = [(quant.qty, location,)] - if not location: - quant_dict[quant] = [(quant.qty, picking.location_dest_id,)] + putaway_strat = self.pool.get('stock.location').get_putaway_strategy(cr, uid, picking.location_dest_id, product, context=context) + product_putaway_strats[product.id] = putaway_strat + if putaway_strat: + location = self._picking_putaway_resolution(cr, uid, picking, product, putaway_strat, context=context) + return location or picking.picking_type_id.default_location_dest_id.id or picking.location_dest_id.id - remaining_dict = {} - for product in qtys_remaining.keys(): - location = False - if product_putaway.get(product): - putaway = product_putaway[product] + pack_obj = self.pool.get("stock.quant.package") + quant_obj = self.pool.get("stock.quant") + vals = [] + qtys_grouped = {} + #for each quant of the picking, find the suggested location + quants_suggested_locations = {} + product_putaway_strats = {} + for quant in quants: + if quant.qty <= 0: + continue + suggested_location_id = _picking_putaway_apply(quant.product_id) + quants_suggested_locations[quant] = suggested_location_id + + #find the packages we can movei as a whole + top_lvl_packages = self._get_top_level_packages(cr, uid, quants_suggested_locations, context=context) + # and then create pack operations for the top-level packages found + for pack in pack_obj.browse(cr, uid, top_lvl_packages, context=context): + pack_quants = pack_obj.get_content(cr, uid, [pack.id], context=context) + vals.append({ + 'picking_id': picking.id, + 'package_id': pack.id, + 'product_qty': 1.0, + 'location_id': pack.location_id.id, + 'location_dest_id': quants_suggested_locations[pack_quants[0]], + }) + #remove the quants inside the package so that they are excluded from the rest of the computation + for quant in quant_obj.browse(cr, uid, pack_quants, context=context): + del quants_suggested_locations[quant] + + # Go through all remaining reserved quants and group by product, package, lot, owner, source location and dest location + for quant, dest_location_id in quants_suggested_locations.items(): + key = (quant.product_id.id, quant.package_id.id, quant.lot_id.id, quant.owner_id.id, quant.location_id.id, dest_location_id) + if qtys_grouped.get(key): + qtys_grouped[key] += quant.qty else: - putaway = self.pool.get('stock.location').get_putaway_strategy(cr, uid, picking.location_dest_id, product, context=context) - product_putaway[product] = putaway - if putaway: - location = self._quant_putaway_apply(cr, uid, picking, False, putaway, context=context) - if location: - remaining_dict[product] = [(qtys_remaining[product], location,)] - if not location: - remaining_dict[product] = [(qtys_remaining[product], picking.location_dest_id,)] - return (quant_dict, remaining_dict,) + qtys_grouped[key] = quant.qty + + # Do the same for the forced quantities (in cases of force_assign or incomming shipment for example) + for product, qty in forced_qties.items(): + if qty <= 0: + continue + suggested_location_id = _picking_putaway_apply(product) + key = (product.id, False, False, False, picking.picking_type_id.default_location_src_id.id or picking.location_id.id, suggested_location_id) + if qtys_grouped.get(key): + qtys_grouped[key] += qty + else: + qtys_grouped[key] = qty + + # Create the necessary operations for the grouped quants and remaining qtys + for key, qty in qtys_grouped.items(): + vals.append({ + 'picking_id': picking.id, + 'product_qty': qty, + 'product_id': key[0], + 'package_id': key[1], + 'lot_id': key[2], + 'owner_id': key[3], + 'location_id': key[4], + 'location_dest_id': key[5], + 'product_uom_id': self.pool.get("product.product").browse(cr, uid, key[0], context=context).uom_id.id, + }) + return vals def do_prepare_partial(self, cr, uid, picking_ids, context=None): context = context or {} pack_operation_obj = self.pool.get('stock.pack.operation') - pack_obj = self.pool.get("stock.quant.package") - quant_obj = self.pool.get("stock.quant") + #pack_obj = self.pool.get("stock.quant.package") + #quant_obj = self.pool.get("stock.quant") - #get list of existing operations and delete them existing_package_ids = pack_operation_obj.search(cr, uid, [('picking_id', 'in', picking_ids)], context=context) if existing_package_ids: pack_operation_obj.unlink(cr, uid, existing_package_ids, context) for picking in self.browse(cr, uid, picking_ids, context=context): - reserved_move = {} - qtys_remaining = {} #Quantity remaining after calculating reserved quants - quants = [] + forced_qties = {} # Quantity remaining after calculating reserved quants + picking_quants = [] #Calculate packages, reserved quants, qtys of this picking's moves for move in picking.move_lines: - quants += move.reserved_quant_ids if move.state not in ('assigned', 'confirmed'): continue - reserved_move[move.id] = set([x.id for x in move.reserved_quant_ids]) - if move.state == 'assigned': - qty = move.product_qty - move.reserved_availability - else: - qty = 0 - - #Add qty to qtys remaining - if qtys_remaining.get(move.product_id): - qtys_remaining[move.product_id] += qty - else: - qtys_remaining[move.product_id] = qty - (putaway_quants_dict, putaway_remaining_dict) = self._putaway_apply(cr, uid, picking, quants, qtys_remaining, context=context) - packages = list(set([x.package_id for x in putaway_quants_dict.keys() if x and x.package_id])) - - # Try to find as much as possible top-level packages that can be moved - top_lvl_packages = set() - for pack in packages: - loop = True - good_pack = False - test_pack = pack - while loop: - quants = pack_obj.get_content(cr, uid, [test_pack.id], context=context) - quants_to_compare = putaway_quants_dict.keys() -# move_list = [m.id for m in picking.move_lines] - common_location = False - all_in = True - for quant in quant_obj.browse(cr, uid, quants, context=context): - # If the quant is not in the quants to compare and not in the common location - if not quant in quants_to_compare: - all_in = False - break - else: - for quants_tup in putaway_quants_dict[quant]: - if not common_location: - common_location = quants_tup[1] - elif common_location != quants_tup[1]: - all_in = False - break -# if not (quant in quants_to_compare and (not common_location or putaway_quants_dict[quant][1] == common_location)): -# all_in = False -# break -# elif not common_location and quant in quants_to_compare: -# common_location = putaway_quants_dict[quant][1] - if all_in: - good_pack = test_pack.id - if test_pack.parent_id: - test_pack = test_pack.parent_id - else: - #stop the loop when there's no parent package anymore - loop = False + move_quants = move.reserved_quant_ids + picking_quants += move_quants + forced_qty = (move.state == 'assigned') and move.product_qty - move.reserved_availability or 0 + #if we used force_assign() on the move, or if the move is incomming, forced_qty > 0 + if forced_qty: + if forced_qties.get(move.product_id): + forced_qties[move.product_id] += forced_qty else: - #stop the loop when the package test_pack is not totally reserved for moves of this picking - #(some quants may be reserved for other picking or not reserved at all) - loop = False - if good_pack: - top_lvl_packages.add(good_pack) - - # Create pack operations for the top-level packages found - for pack in pack_obj.browse(cr, uid, list(top_lvl_packages), context=context): - quants = pack_obj.get_content(cr, uid, [pack.id], context=context) - quant = quant_obj.browse(cr, uid, quants[0], context=context) - pack_operation_obj.create(cr, uid, { - 'picking_id': picking.id, - 'package_id': pack.id, - 'product_qty': 1.0, - 'location_id': pack.location_id.id, - 'location_dest_id': putaway_quants_dict[quant][0][1].id, - }, context=context) - for quant in quant_obj.browse(cr, uid, quants, context=context): - reserved_move[quant.reservation_id.id] -= set([quant.id]) - del putaway_quants_dict[quant] - qtys_remaining[quant.product_id] -= quant.qty - - - qtys_grouped = {} - # Go through all remaining reserved quants and group by product, package, lot, owner, source location and dest location - for quant in putaway_quants_dict.keys(): - for quant_tup in putaway_quants_dict[quant]: - if quant_tup[0] > 0: - key = (quant.product_id.id, quant.package_id.id, quant.lot_id.id, quant.owner_id.id, quant.location_id.id, quant_tup[1].id) - if qtys_grouped.get(key): - qtys_grouped[key] += quant_tup[0] - else: - qtys_grouped[key] = quant_tup[0] - - # Add remaining qtys (in cases of force_assign for example) - for product in putaway_remaining_dict.keys(): - for product_tup in putaway_remaining_dict[product]: - if product_tup[0] > 0: - key = (product.id, False, False, False, picking.location_id.id, product_tup[1].id) - if qtys_grouped.get(key): - qtys_grouped[key] += product_tup[0] - else: - qtys_grouped[key] = product_tup[0] - - # Create the necessary operations for the grouped quants and remaining qtys - for key, qty in qtys_grouped.items(): - pack_operation_obj.create(cr, uid, { - 'picking_id': picking.id, - 'product_qty': qty, - 'product_id': key[0], - 'package_id': key[1], - 'lot_id': key[2], - 'owner_id': key[3], - 'location_id': key[4], - 'location_dest_id': key[5], - 'product_uom_id': self.pool.get("product.product").browse(cr, uid, key[0], context=context).uom_id.id, - }, context=context) + forced_qties[move.product_id] = forced_qty + for vals in self._prepare_pack_ops(cr, uid, picking, picking_quants, forced_qties, context=context): + pack_operation_obj.create(cr, uid, vals, context=context) def do_unreserve(self, cr, uid, picking_ids, context=None): """ @@ -1626,8 +1600,6 @@ class stock_move(osv.osv): push_obj._apply(cr, uid, rule, move, context=context) return True - - def _create_procurement(self, cr, uid, move, context=None): """ This will create a procurement order """ return self.pool.get("procurement.order").create(cr, uid, self._prepare_procurement_from_move(cr, uid, move, context=context)) @@ -1936,7 +1908,7 @@ class stock_move(osv.osv): domain = main_domain[move.id] + self.pool.get('stock.move.operation.link').get_specific_domain(cr, uid, record, context=context) qty_already_assigned = sum([q.qty for q in record.reserved_quant_ids]) qty = record.qty - qty_already_assigned - quants = quant_obj.quants_get_prefered_domain(cr, uid, ops.location_id or move.location_id, move.product_id, qty, domain=domain, prefered_domain=[], fallback_domain=[], restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context) + quants = quant_obj.quants_get_prefered_domain(cr, uid, ops.location_id, move.product_id, qty, domain=domain, prefered_domain=[], fallback_domain=[], restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context) quant_obj.quants_reserve(cr, uid, quants, move, record, context=context) for move in todo_moves: @@ -2016,14 +1988,14 @@ class stock_move(osv.osv): fallback_domain = [('reservation_id', '=', False)] self.check_tracking(cr, uid, move, ops.package_id.id or ops.lot_id.id, context=context) dom = main_domain + self.pool.get('stock.move.operation.link').get_specific_domain(cr, uid, record, context=context) - quants = quant_obj.quants_get_prefered_domain(cr, uid, ops.location_id or move.location_id, move.product_id, record.qty, domain=dom, prefered_domain=prefered_domain, + quants = quant_obj.quants_get_prefered_domain(cr, uid, ops.location_id, move.product_id, record.qty, domain=dom, prefered_domain=prefered_domain, fallback_domain=fallback_domain, restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context) package_id = False if not record.operation_id.package_id: #if a package and a result_package is given, we don't enter here because it will be processed by process_packaging() later #but for operations having only result_package_id, we will create new quants in the final package directly package_id = record.operation_id.result_package_id.id or False - quant_obj.quants_move(cr, uid, quants, move, lot_id=ops.lot_id.id, owner_id=ops.owner_id.id, src_package_id=ops.package_id.id, dest_package_id=package_id, location_dest_id = ops.location_dest_id, context=context) + quant_obj.quants_move(cr, uid, quants, move, ops.location_dest_id, lot_id=ops.lot_id.id, owner_id=ops.owner_id.id, src_package_id=ops.package_id.id, dest_package_id=package_id, context=context) #packaging process pack_op_obj.process_packaging(cr, uid, ops, [x[0].id for x in quants if x[0]], context=context) move_qty[move.id] -= record.qty @@ -2036,7 +2008,7 @@ class stock_move(osv.osv): self.check_tracking(cr, uid, move, move.restrict_lot_id.id, context=context) qty = move_qty[move.id] quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=main_domain, prefered_domain=prefered_domain, fallback_domain=fallback_domain, restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context) - quant_obj.quants_move(cr, uid, quants, move, lot_id=move.restrict_lot_id.id, owner_id=move.restrict_partner_id.id, context=context) + quant_obj.quants_move(cr, uid, quants, move, move.location_dest_id, lot_id=move.restrict_lot_id.id, owner_id=move.restrict_partner_id.id, context=context) #unreserve the quants and make them available for other operations/moves quant_obj.quants_unreserve(cr, uid, move, context=context) @@ -3211,7 +3183,6 @@ class stock_location_path(osv.osv): - # ------------------------- # Packaging related stuff # ------------------------- @@ -3481,8 +3452,8 @@ class stock_pack_operation(osv.osv): 'currency': fields.many2one('res.currency', string="Currency", help="Currency in which Unit cost is expressed", ondelete='CASCADE'), 'linked_move_operation_ids': fields.one2many('stock.move.operation.link', 'operation_id', string='Linked Moves', readonly=True, help='Moves impacted by this operation for the computation of the remaining quantities'), 'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Qty'), - 'location_id': fields.many2one('stock.location', 'Location From'), - 'location_dest_id': fields.many2one('stock.location', 'Location To'), + 'location_id': fields.many2one('stock.location', 'Location From', required=True), + 'location_dest_id': fields.many2one('stock.location', 'Location To', required=True), } _defaults = { diff --git a/addons/stock/stock_demo.yml b/addons/stock/stock_demo.yml index a3a49ba1225..46427cb5e09 100644 --- a/addons/stock/stock_demo.yml +++ b/addons/stock/stock_demo.yml @@ -90,6 +90,8 @@ - product_id: product.product_product_6 product_qty: 100 product_uom_id: product.product_uom_unit + location_id: stock.stock_location_stock + location_dest_id: stock.stock_location_customers - !record {model: stock.picking, id: outgoing_shipment_main_warehouse2}: picking_type_id: stock.picking_type_out @@ -209,6 +211,8 @@ - product_id: product.product_product_48 product_qty: 100 product_uom_id: product.product_uom_unit + location_id: stock.stock_location_suppliers + location_dest_id: stock.stock_location_shop0 - !record {model: stock.picking, id: incomming_chicago_warehouse2}: picking_type_id: chi_picking_type_in @@ -264,6 +268,8 @@ - product_id: product.product_product_6 product_qty: 100 product_uom_id: product.product_uom_unit + location_id: stock.stock_location_shop0 + location_dest_id: stock.stock_location_customers - !record {model: stock.picking, id: outgoing_chicago_warehouse2}: picking_type_id: chi_picking_type_out From aed228331fe0af4004755a42e4d8cfe16d26737a Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Tue, 11 Mar 2014 16:44:09 +0100 Subject: [PATCH 05/12] [FIX] stock: do prepare partial bzr revid: qdp-launchpad@openerp.com-20140311154409-v5aoj3tx2frfi9jz --- addons/stock/stock.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 003c6cdc910..794958bf895 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -919,14 +919,14 @@ class stock_picking(osv.osv): pack_obj = self.pool.get("stock.quant.package") quant_obj = self.pool.get("stock.quant") top_lvl_packages = set() - quants_to_compare = [x.id for x in quants_suggested_locations.keys()] + quants_to_compare = quants_suggested_locations.keys() for pack in list(set([x.package_id for x in quants_suggested_locations.keys() if x and x.package_id])): loop = True - good_pack = False test_pack = pack + good_pack = False + pack_destination = False while loop: pack_quants = pack_obj.get_content(cr, uid, [test_pack.id], context=context) - pack_destination = False all_in = True for quant in quant_obj.browse(cr, uid, pack_quants, context=context): # If the quant is not in the quants to compare and not in the common location @@ -1156,6 +1156,8 @@ class stock_picking(osv.osv): todo_move_ids.append(move.id) #Assign move as it was assigned before toassign_move_ids.append(new_move) + else: + todo_move_ids.append(move.id) self.rereserve_quants(cr, uid, picking, move_ids=todo_move_ids, context=context) if todo_move_ids and not context.get('do_only_split'): self.pool.get('stock.move').action_done(cr, uid, todo_move_ids, context=context) @@ -1861,8 +1863,6 @@ class stock_move(osv.osv): context = context or {} quant_obj = self.pool.get("stock.quant") to_assign_moves = [] - prefered_domain = {} - fallback_domain = {} main_domain = {} todo_moves = [] operations = set() From 7d0760b59a21dddfcc8b2d49f7977190a8272e6b Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Tue, 11 Mar 2014 17:01:11 +0100 Subject: [PATCH 06/12] [FIX] stock: prepare partial when top_lvl_package is used bzr revid: qdp-launchpad@openerp.com-20140311160111-ed895thk06fg6da6 --- addons/stock/stock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 794958bf895..3db4d003b4c 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -997,7 +997,7 @@ class stock_picking(osv.osv): 'package_id': pack.id, 'product_qty': 1.0, 'location_id': pack.location_id.id, - 'location_dest_id': quants_suggested_locations[pack_quants[0]], + 'location_dest_id': quants_suggested_locations[quant_obj.browse(cr, uid, pack_quants[0], context=context)], }) #remove the quants inside the package so that they are excluded from the rest of the computation for quant in quant_obj.browse(cr, uid, pack_quants, context=context): From ff09748ec9eed3b2430f52ad723d947d6695475e Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Tue, 11 Mar 2014 18:14:43 +0100 Subject: [PATCH 07/12] [FIX] stock: shipment.yml fixed. The fields location_id and location_dest_id are now required on pack.operations bzr revid: qdp-launchpad@openerp.com-20140311171443-sbxsa2p8biyzo7t9 --- addons/stock/test/shipment.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/stock/test/shipment.yml b/addons/stock/test/shipment.yml index 5379cd8d4e0..585c2469108 100644 --- a/addons/stock/test/shipment.yml +++ b/addons/stock/test/shipment.yml @@ -19,6 +19,8 @@ 'product_id': ref('product_icecream'), 'product_uom_id': ref('product.product_uom_kgm'), 'product_qty': 40 + 'location_id': ref('stock.stock_location_suppliers'), + 'location_dest_id': ref('stock.stock_location_14') }) context.update({'active_model': 'stock.picking', 'active_id': ref('incomming_shipment'), 'active_ids': [ref('incomming_shipment')]}) pick.do_transfer(context=context) @@ -47,6 +49,8 @@ 'product_id': ref('product_icecream'), 'product_uom_id': ref('product.product_uom_kgm'), 'product_qty': 10 + 'location_id': ref('stock.stock_location_suppliers'), + 'location_dest_id': ref('stock.stock_location_14') }) backorder.do_transfer(context=context) - From fcf8d138063b37c193f5292c5ebbe5657b1907a5 Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Wed, 12 Mar 2014 09:10:10 +0100 Subject: [PATCH 08/12] [FIX] stock: ... bzr revid: qdp-launchpad@openerp.com-20140312081010-x5oxh4te1xh0v3vv --- addons/stock/test/shipment.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/stock/test/shipment.yml b/addons/stock/test/shipment.yml index 585c2469108..4e640f3aa64 100644 --- a/addons/stock/test/shipment.yml +++ b/addons/stock/test/shipment.yml @@ -18,7 +18,7 @@ 'picking_id': pick.id, 'product_id': ref('product_icecream'), 'product_uom_id': ref('product.product_uom_kgm'), - 'product_qty': 40 + 'product_qty': 40, 'location_id': ref('stock.stock_location_suppliers'), 'location_dest_id': ref('stock.stock_location_14') }) @@ -48,7 +48,7 @@ 'picking_id': backorder.id, 'product_id': ref('product_icecream'), 'product_uom_id': ref('product.product_uom_kgm'), - 'product_qty': 10 + 'product_qty': 10, 'location_id': ref('stock.stock_location_suppliers'), 'location_dest_id': ref('stock.stock_location_14') }) From 46c6aba808f57b67cc1490b9dea258f950b475ea Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Wed, 12 Mar 2014 10:57:11 +0100 Subject: [PATCH 09/12] [FIX] stock, packing.yml: location_id and location_dest_id are required bzr revid: qdp-launchpad@openerp.com-20140312095711-lyxfyda4nydd6twd --- addons/stock/test/packing.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/stock/test/packing.yml b/addons/stock/test/packing.yml index 3be5b9038c6..70e844ca4eb 100644 --- a/addons/stock/test/packing.yml +++ b/addons/stock/test/packing.yml @@ -49,8 +49,8 @@ #Create package for each line and assign it as result_package_id #create pack operation stock_pack.write(cr, uid, record.pack_operation_ids[0].id, {'result_package_id': package1, 'product_qty': 120}) - new_pack1 = stock_pack.create(cr, uid, {'product_id': ref('product1'), 'product_uom_id': ref('product.product_uom_unit'), 'picking_id': ref('pick1'), 'lot_id': lot_a, 'result_package_id': package2, 'product_qty': 120}, context=context) - new_pack2 = stock_pack.create(cr, uid, {'product_id': ref('product1'), 'product_uom_id': ref('product.product_uom_unit'), 'picking_id': ref('pick1'), 'result_package_id': package3, 'product_qty': 60}, context=context) + new_pack1 = stock_pack.create(cr, uid, {'product_id': ref('product1'), 'product_uom_id': ref('product.product_uom_unit'), 'picking_id': ref('pick1'), 'lot_id': lot_a, 'result_package_id': package2, 'product_qty': 120, 'location_id': ref('stock_location_suppliers'), 'location_dest_id': ref('stock_location_stock')}, context=context) + new_pack2 = stock_pack.create(cr, uid, {'product_id': ref('product1'), 'product_uom_id': ref('product.product_uom_unit'), 'picking_id': ref('pick1'), 'result_package_id': package3, 'product_qty': 60, 'location_id': ref('stock_location_suppliers'), 'location_dest_id': ref('stock_location_stock')}, context=context) - Transfer the reception - From da1431f544a0f1b42337154387846b118bd5f0e6 Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Wed, 12 Mar 2014 11:00:09 +0100 Subject: [PATCH 10/12] [FIX] stock, packingneg.yml: location_id and location_dest_id are required bzr revid: qdp-launchpad@openerp.com-20140312100009-u9b83h8bqzik2yx0 --- addons/stock/test/packingneg.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/stock/test/packingneg.yml b/addons/stock/test/packingneg.yml index d42cbfa52e2..5bb93d44aca 100644 --- a/addons/stock/test/packingneg.yml +++ b/addons/stock/test/packingneg.yml @@ -49,8 +49,8 @@ #Create package for each line and assign it as result_package_id #create pack operation stock_pack.write(cr, uid, record.pack_operation_ids[0].id, {'result_package_id': package1, 'product_qty': 120}) - new_pack1 = stock_pack.create(cr, uid, {'product_id': ref('product_neg'), 'product_uom_id': ref('product.product_uom_unit'), 'picking_id': ref('pick_neg'), 'lot_id': lot_a, 'result_package_id': package2, 'product_qty': 120}, context=context) - new_pack2 = stock_pack.create(cr, uid, {'product_id': ref('product_neg'), 'product_uom_id': ref('product.product_uom_unit'), 'picking_id': ref('pick_neg'), 'result_package_id': package3, 'product_qty': 60}, context=context) + new_pack1 = stock_pack.create(cr, uid, {'product_id': ref('product_neg'), 'product_uom_id': ref('product.product_uom_unit'), 'picking_id': ref('pick_neg'), 'lot_id': lot_a, 'result_package_id': package2, 'product_qty': 120, 'location_id': ref('stock_location_suppliers'), 'location_dest_id': ref('stock_location_stock')}, context=context) + new_pack2 = stock_pack.create(cr, uid, {'product_id': ref('product_neg'), 'product_uom_id': ref('product.product_uom_unit'), 'picking_id': ref('pick_neg'), 'result_package_id': package3, 'product_qty': 60, 'location_id': ref('stock_location_suppliers'), 'location_dest_id': ref('stock_location_stock')}, context=context) - Transfer the reception - From 5298db0f062f66f189fa2ba0128d05bd6c8993b7 Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Wed, 12 Mar 2014 12:06:53 +0100 Subject: [PATCH 11/12] [FIX] sale_stock: location_id and location_dest_id are now required on stock.pack.operation bzr revid: qdp-launchpad@openerp.com-20140312110653-5dfjcmt9ahgu5ich --- addons/sale_stock/test/cancel_order_sale_stock.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/sale_stock/test/cancel_order_sale_stock.yml b/addons/sale_stock/test/cancel_order_sale_stock.yml index d9116d843f5..77508e28a2d 100644 --- a/addons/sale_stock/test/cancel_order_sale_stock.yml +++ b/addons/sale_stock/test/cancel_order_sale_stock.yml @@ -20,7 +20,9 @@ 'picking_id': pick.id, 'product_id': ref('product.product_product_27'), 'product_uom_id': ref('product.product_uom_unit'), - 'product_qty': 1 + 'product_qty': 1, + 'location_id': pick.location_id.id, + 'location_dest_id': pick.location_dest_id.id, }) pick.do_transfer(context=context) - From 6dae580a6b422d8d4955978fdb8505bbb5ba0b0e Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Wed, 12 Mar 2014 12:24:34 +0100 Subject: [PATCH 12/12] [IMP] stock: default location_id and location_dest_id for pack operations bzr revid: qdp-launchpad@openerp.com-20140312112434-nt2bc9wsb29dawom --- addons/stock/stock_view.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index 02f4799a940..ede38e6058a 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -769,7 +769,7 @@