diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index b49b99fd6e0..d2ff8553a35 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -800,7 +800,7 @@ class mrp_production(osv.osv): q = min(move.product_qty, qty) quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, scheduled.product_id, q, domain=[('qty', '>', 0.0)], - prefered_domain=[('reservation_id', '=', move.id)], fallback_domain=[('reservation_id', '=', False)], context=context) + prefered_domain_list=[[('reservation_id', '=', move.id)], [('reservation_id', '=', False)]], context=context) for quant, quant_qty in quants: if quant: lot_id = quant.lot_id.id diff --git a/addons/stock/stock.py b/addons/stock/stock.py index fd466f8dfaf..ee5fe7f9287 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -368,7 +368,9 @@ class stock_quant(osv.osv): to_move_quants.append(quant) quants_reconcile.append(quant) if to_move_quants: + to_recompute_move_ids = [x.reservation_id.id for x in to_move_quants if x.reservation_id and x.reservation_id.id != move.id] self.move_quants_write(cr, uid, to_move_quants, move, location_to, dest_package_id, context=context) + self.pool.get('stock.move').recalculate_move_state(cr, uid, to_recompute_move_ids, context=context) if location_to.usage == 'internal': if self.search(cr, uid, [('product_id', '=', move.product_id.id), ('qty','<', 0)], limit=1, context=context): for quant in quants_reconcile: @@ -381,34 +383,31 @@ class stock_quant(osv.osv): 'package_id': dest_package_id} self.write(cr, SUPERUSER_ID, [q.id for q in quants], vals, context=context) - 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): + def quants_get_prefered_domain(self, cr, uid, location, product, qty, domain=None, prefered_domain_list=[], 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 - it tries to find the remaining quantity by using the fallback_domain. + the choice on the quants that match the first item of prefered_domain_list as well. But if the qty requested is not reached + it tries to find the remaining quantity by looping on the prefered_domain_list (tries with the second item and so on). + Make sure the quants aren't found twice => all the domains of prefered_domain_list should be orthogonal ''' + if domain is None: + domain = [] + quants = [(None, qty)] #don't look for quants in location that are of type production, supplier or inventory. if location.usage in ['inventory', 'production', 'supplier']: - return [(None, qty)] - if prefered_domain and fallback_domain: - if domain is None: - domain = [] - quants = self.quants_get(cr, uid, location, product, qty, domain=domain + prefered_domain, restrict_lot_id=restrict_lot_id, restrict_partner_id=restrict_partner_id, context=context) - res_qty = qty - quant_ids = [] - for quant in quants: - if quant[0]: - quant_ids.append(quant[0].id) - res_qty -= quant[1] + return quants + res_qty = qty + if not prefered_domain_list: + return self.quants_get(cr, uid, location, product, qty, domain=domain, restrict_lot_id=restrict_lot_id, restrict_partner_id=restrict_partner_id, context=context) + for prefered_domain in prefered_domain_list: if res_qty > 0: #try to replace the last tuple (None, res_qty) with something that wasn't chosen at first because of the prefered order quants.pop() - #make sure the quants aren't found twice (if the prefered_domain and the fallback_domain aren't orthogonal - domain += [('id', 'not in', quant_ids)] - unprefered_quants = self.quants_get(cr, uid, location, product, res_qty, domain=domain + fallback_domain, restrict_lot_id=restrict_lot_id, restrict_partner_id=restrict_partner_id, context=context) - for quant in unprefered_quants: - quants.append(quant) - return quants - return self.quants_get(cr, uid, location, product, qty, domain=domain, restrict_lot_id=restrict_lot_id, restrict_partner_id=restrict_partner_id, context=context) + tmp_quants = self.quants_get(cr, uid, location, product, res_qty, domain=domain + prefered_domain, restrict_lot_id=restrict_lot_id, restrict_partner_id=restrict_partner_id, context=context) + for quant in tmp_quants: + if quant[0]: + res_qty -= quant[1] + quants += unprefered_quants + return quants def quants_get(self, cr, uid, location, product, qty, domain=None, restrict_lot_id=False, restrict_partner_id=False, context=None): """ @@ -510,8 +509,6 @@ class stock_quant(osv.osv): dom += [('lot_id', '=', quant.lot_id.id)] dom += [('owner_id', '=', quant.owner_id.id)] dom += [('package_id', '=', quant.package_id.id)] - if move.move_dest_id: - dom += [('negative_move_id', '=', move.move_dest_id.id)] quants = self.quants_get(cr, uid, quant.location_id, quant.product_id, quant.qty, dom, context=context) for quant_neg, qty in quants: if not quant_neg: @@ -1725,7 +1722,10 @@ 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) - self.write(cr, uid, [move.id], {'state': 'confirmed'}, context=context) + if self.find_move_ancestors(cr, uid, move, context=context): + self.write(cr, uid, [move.id], {'state': 'waiting'}, context=context) + else: + 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 "/") @@ -2026,8 +2026,7 @@ class stock_move(osv.osv): """ Changes the state to assigned. @return: True """ - #when a MTO move availability is forced, it shouldn't be treated anymore as a MTO so we break the link with ancestors (to avoid further problems in re-reservation) - return self.write(cr, uid, ids, {'state': 'assigned', 'procure_method': 'make_to_stock', 'move_orig_ids': [(6, 0, [])]}, context=context) + return self.write(cr, uid, ids, {'state': 'assigned'}, context=context) def check_tracking(self, cr, uid, move, lot_id, context=None): """ Checks if serial number is assigned to stock move or not and raise an error if it had to. @@ -2092,7 +2091,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 = record.qty if qty: - 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) + quants = quant_obj.quants_get_prefered_domain(cr, uid, ops.location_id, move.product_id, qty, domain=domain, prefered_domain_list=[], 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: move.refresh() @@ -2100,7 +2099,7 @@ class stock_move(osv.osv): if move.state != 'assigned': qty_already_assigned = move.reserved_availability qty = move.product_qty - qty_already_assigned - quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=main_domain[move.id], 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, move.location_id, move.product_id, qty, domain=main_domain[move.id], prefered_domain_list=[], 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, context=context) #force assignation of consumable products and incoming from supplier/inventory/production @@ -2148,18 +2147,22 @@ class stock_move(osv.osv): move2 = not move2.move_orig_ids and move2.split_from or False return ancestors - def check_quants_history(self, cr, uid, move, quants, context=None): - #raise an error in the case the move is chained MTO and the selected quants were not properly chained (manual change in pack operations) because: - #1) users must be wanred they break the MTO flow - #2) if left as it, it will create negative quants complex to reconcile as we allow to take quants only from ancestors, - # and we allow reconciliation only of negative quants created by next move when receiving a chained quant. - ancestors = self.find_move_ancestors(cr, uid, move, context=context) - if move.state == 'waiting' and not ancestors: - raise osv.except_osv(_('Error'), _('You cannot the requested operation. As the move is chained you are supposed to take the same products as in the previous step. Please use the values proposed or force the availability of the move to break the link and process it as you like.')) - if ancestors: - for q, dummy in quants: - if not q or not (set(ancestors) & set([m.id for m in q.history_ids])): - raise osv.except_osv(_('Error'), _('You cannot the requested operation. As the move is chained you are supposed to take the same products as in the previous step. Please use the values proposed or force the availability of the move to break the link and process it as you like.')) + def recalculate_move_state(self, cr, uid, move_ids, context=None): + '''Recompute the state of moves given because their reserved quants were used to fulfill another operation''' + for move in self.browse(cr, uid, move_ids, context=context): + vals = {} + reserved_quant_ids = move.reserved_quant_ids + if len(reserved_quant_ids) > 0 and not move.partially_available: + vals['partially_available'] = True + if len(reserved_quant_ids) == 0 and move.partially_available: + vals['partially_available'] = False + if move.state == 'assigned': + if self.find_move_ancestors(cr, uid, move, context=context): + vals['state'] = 'waiting' + else: + vals['state'] = 'confirmed' + if vals: + self.write(cr, uid, [move.id], vals, context=context) def action_done(self, cr, uid, ids, context=None): """ Process completly the moves given as ids and if all moves are done, it will finish the picking. @@ -2193,9 +2196,11 @@ class stock_move(osv.osv): self.check_tracking(cr, uid, move, ops.package_id.id or ops.lot_id.id, context=context) prefered_domain = [('reservation_id', '=', move.id)] fallback_domain = [('reservation_id', '=', False)] + fallback_domain2 = ['&', ('reservation_id', '!=', move.id), ('reservation_id', '!=', False)] + prefered_domain_list = [prefered_domain] + [fallback_domain] + [fallback_domain2] 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, 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) + quants = quant_obj.quants_get_prefered_domain(cr, uid, ops.location_id, move.product_id, record.qty, domain=dom, prefered_domain_list=prefered_domain_list, + restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context) if ops.result_package_id.id: #if a result package is given, all quants go there quant_dest_package_id = ops.result_package_id.id @@ -2216,9 +2221,11 @@ class stock_move(osv.osv): main_domain = [('qty', '>', 0)] prefered_domain = [('reservation_id', '=', move.id)] fallback_domain = [('reservation_id', '=', False)] + fallback_domain2 = ['&', ('reservation_id', '!=', move.id), ('reservation_id', '!=', False)] + prefered_domain_list = [prefered_domain] + [fallback_domain] + [fallback_domain2] 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) + quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=main_domain, prefered_domain_list=prefered_domain_list, 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, 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) @@ -3517,14 +3524,9 @@ class stock_package(osv.osv): while parent.parent_id: parent = parent.parent_id quant_ids = self.get_content(cr, uid, [parent.id], context=context) - quants = quant_obj.browse(cr, uid, quant_ids, context=context) - normal_quants = [x for x in quants if not x.propagated_from_id if x.qty > 0] - propagated_quants = [x for x in quants if x.propagated_from_id if x.qty > 0] - location_id = normal_quants and normal_quants[0].location_id.id or False - prop_loc_id = propagated_quants and propagated_quants[0].location_id.id or False - all_normal = all([quant.location_id.id == location_id for quant in normal_quants]) - all_propagated = all([quant.location_id.id == prop_loc_id for quant in propagated_quants]) - if not all_normal or not all_propagated: + quants = [x for x in quant_obj.browse(cr, uid, quant_ids, context=context) if x.qty > 0] + location_id = quants and quants[0].location_id.id or False + if not [quant.location_id.id == location_id for quant in quants]: raise osv.except_osv(_('Error'), _('Everything inside a package should be in the same location')) return True diff --git a/addons/stock/test/wiseoperator.yml b/addons/stock/test/wiseoperator.yml index 7b188ba7d67..cfe1180b545 100644 --- a/addons/stock/test/wiseoperator.yml +++ b/addons/stock/test/wiseoperator.yml @@ -95,7 +95,6 @@ pack_ids2 = [x.id for x in picking2.pack_operation_ids] stock_pack.write(cr, uid, pack_ids1, {'picking_id': picking2.id}) stock_pack.write(cr, uid, pack_ids2, {'picking_id': picking1.id}) - picking1.refresh() - Process this picking - @@ -106,7 +105,12 @@ - !python {model: stock.quant}: | reco_id = self.search(cr ,uid , [('product_id','=',ref('product_wise')), ('qty', '<', 0.0)], context=context) - assert len(reco_id) > 0, 'This should have created a negative quant' + assert len(reco_id) == 0, 'This should not have created a negative quant' +- + Check the other delivery order has changed its state back to partially available +- + !python {model: stock.picking}: | + assert self.browse(cr, uid, ref('delivery_order_wise2')).state == 'partially_available', "Delivery order 2 should be back in confirmed state" - Process the second picking - @@ -117,4 +121,4 @@ - !python {model: stock.quant}: | reco_id = self.search(cr ,uid , [('product_id','=',ref('product_wise'))], context=context) - assert all([x.location_id.id==ref('stock_location_customers') and x.qty > 0.0 for x in self.browse(cr, uid, reco_id)]), "Negative quant or wrong location detected" + assert all([x.location_id.id==ref('stock_location_customers') and x.qty > 0.0 for x in self.browse(cr, uid, reco_id)]), "Negative quant or wrong location detected" \ No newline at end of file