[REF] stock: MEMO on the solution to adopt for quant assignation, do_transer and so and so...

bzr revid: qdp-launchpad@openerp.com-20131114211428-kgbzxdqbnxcfv17b
This commit is contained in:
Quentin (OpenERP) 2013-11-14 22:14:28 +01:00
parent f8a32c79ba
commit 3e21ab83e8
2 changed files with 341 additions and 207 deletions

View File

@ -267,23 +267,22 @@ class stock_quant(osv.osv):
return self.write(cr, SUPERUSER_ID, toreserve, {'reservation_id': move.id}, context=context) return self.write(cr, SUPERUSER_ID, toreserve, {'reservation_id': move.id}, context=context)
# add location_dest_id in parameters (False=use the destination of the move) # add location_dest_id in parameters (False=use the destination of the move)
def quants_move(self, cr, uid, quants, move, lot_id = False, owner_id = False, package_id = False, context=None): def quants_move(self, cr, uid, quants, move, lot_id=False, owner_id=False, package_id=False, context=None):
for quant, qty in quants: for quant, qty in quants:
#quant may be a browse record or None #quant may be a browse record or None
quant_record = self.move_single_quant(cr, uid, quant, qty, move, lot_id = lot_id, package_id = package_id, context=context) quant_record = self.move_single_quant(cr, uid, quant, qty, move, lot_id=lot_id, package_id=package_id, context=context)
#quant_record is the quant newly created or already split #quant_record is the quant newly created or already split
self._quant_reconcile_negative(cr, uid, quant_record, context=context) self._quant_reconcile_negative(cr, uid, quant_record, context=context)
def check_preferred_location(self, cr, uid, move, context=None): def check_preferred_location(self, cr, uid, move, context=None):
if move.putaway_ids and move.putaway_ids[0]: if move.putaway_ids and move.putaway_ids[0]:
#Take only first suggestion for the moment #Take only first suggestion for the moment
return move.putaway_ids[0].location_id return move.putaway_ids[0].location_id
return move.location_dest_id return move.location_dest_id
def move_single_quant(self, cr, uid, quant, qty, move, lot_id = False, owner_id = False, package_id = False, context=None): def move_single_quant(self, cr, uid, quant, qty, move, lot_id=False, owner_id=False, package_id=False, context=None):
if not quant: if not quant:
quant = self._quant_create(cr, uid, qty, move, lot_id = lot_id, owner_id = owner_id, package_id = package_id, context = context) quant = self._quant_create(cr, uid, qty, move, lot_id=lot_id, owner_id=owner_id, package_id=package_id, context=context)
else: else:
self._quant_split(cr, uid, quant, qty, context=context) self._quant_split(cr, uid, quant, qty, context=context)
# FP Note: improve this using preferred locations # FP Note: improve this using preferred locations
@ -820,13 +819,13 @@ class stock_picking(osv.osv):
def _create_backorder(self, cr, uid, picking, backorder_moves=[], context=None): def _create_backorder(self, cr, uid, picking, backorder_moves=[], context=None):
""" """
Move all non-done lines into a new backorder picking Move all non-done lines into a new backorder picking. If the key 'do_only_split' is given in the context, then move all lines not in context.get('split', []) instead of all non-done lines.
""" """
if not backorder_moves: if not backorder_moves:
backorder_moves = picking.move_lines backorder_moves = picking.move_lines
backorder_move_ids = [x.id for x in backorder_moves if x.state not in ('done','cancel')] backorder_move_ids = [x.id for x in backorder_moves if x.state not in ('done','cancel')]
if 'do_only_split' in context and context['do_only_split']: if 'do_only_split' in context and context['do_only_split']:
backorder_move_ids = [x.id for x in backorder_moves if x.id not in context['split']] backorder_move_ids = [x.id for x in backorder_moves if x.id not in context.get('split',[])]
if backorder_move_ids: if backorder_move_ids:
backorder_id = self.copy(cr, uid, picking.id, { backorder_id = self.copy(cr, uid, picking.id, {
@ -921,188 +920,304 @@ class stock_picking(osv.osv):
''' '''
self.rereserve(cr, uid, picking_ids, context=context) self.rereserve(cr, uid, picking_ids, context=context)
def do_unreserve(self,cr,uid,picking_ids, context=None): def do_unreserve(self, cr, uid, picking_ids, context=None):
""" """
Will remove all quants for picking in picking_ids Will remove all quants for picking in picking_ids
""" """
ids_to_free = [] moves_to_unreserve = []
quant_obj = self.pool.get("stock.quant")
for picking in self.browse(cr, uid, picking_ids, context=context): for picking in self.browse(cr, uid, picking_ids, context=context):
for move in picking.move_lines: moves_to_unreserve += [m.id for m in picking.move_lines]
ids_to_free += [quant.id for quant in move.reserved_quant_ids] if moves_to_unreserve:
if ids_to_free: self.pool.get('stock.move').do_unreserve(cr, uid, moves_to_unreserve, context=context)
quant_obj.write(cr, SUPERUSER_ID, ids_to_free, {'reservation_id' : False, 'reservation_op_id': False }, context = context)
def _reserve_quants_ops_move(self, cr, uid, ops, move, qty, create=False, context=None): #def _reserve_quants_ops_move(self, cr, uid, ops, move, qty, create=False, context=None):
""" # """
Will return the quantity that could not be reserved # Will return the quantity that could not be reserved
""" # """
quant_obj = self.pool.get("stock.quant") # quant_obj = self.pool.get("stock.quant")
op_obj = self.pool.get("stock.pack.operation") # op_obj = self.pool.get("stock.pack.operation")
if create and move.location_id.usage != 'internal': # if create and move.location_id.usage != 'internal':
# Create quants # # Create quants
quant = quant_obj._quant_create(cr, uid, qty, move, lot_id=ops.lot_id and ops.lot_id.id or False, owner_id=ops.owner_id and ops.owner_id.id or False, context=context) # quant = quant_obj._quant_create(cr, uid, qty, move, lot_id=ops.lot_id and ops.lot_id.id or False, owner_id=ops.owner_id and ops.owner_id.id or False, context=context)
quant.write({'reservation_op_id': ops.id, 'location_id': move.location_id.id}) # quant.write({'reservation_op_id': ops.id, 'location_id': move.location_id.id})
quant_obj.quants_reserve(cr, uid, [(quant, qty)], move, context=context) # quant_obj.quants_reserve(cr, uid, [(quant, qty)], move, context=context)
return 0 # return 0
# else:
# #Quants get
# dom = op_obj._get_domain(cr, uid, ops, context=context)
# dom = dom + [('reservation_id', 'not in', [x.id for x in move.picking_id.move_lines])]
# quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=dom, prefered_domain=[('reservation_id', '=', False)], fallback_domain=[('reservation_id', '!=', False)], context=context)
# res_qty = qty
# for quant in quants:
# if quant[0]: # If quant can be reserved
# res_qty -= quant[1]
# quant_obj.quants_reserve(cr, uid, quants, move, context=context)
# quant_obj.write(cr, SUPERUSER_ID, [x[0].id for x in quants if x[0]], {'reservation_op_id': ops.id}, context=context)
# return res_qty
def do_recompute_remaining_quantities(self, cr, uid, ids, context=None):
'''This function simply calls recompute_remaining_quantities but is needed in order to pass the context in the righ argument'''
return self.recompute_remaining_quantities(cr, uid, ids, product_ids=[], context=context)
def _get_total_from_pack_operations(self, cr, picking, ids, product_ids=[], context=None):
def _update_quantity(product, quantity, uom_id):
quantity_in_move_uom = uom_obj._compute_qty(cr, uid, uom_id, quantity, product.uom_id.id, round=False)
self._create_link_move_op(cr, uid, op
res = {}
#if product_ids is not given, we'll recompute the remaining quantity for all move lines
if not product_ids:
tocheck_product_ids = [m.product_id.id for m in picking.move_lines]
else: else:
#Quants get tocheck_product_ids = product_ids
dom = op_obj._get_domain(cr, uid, ops, context=context) #loop on the operations to compute the total of each product
dom = dom + [('reservation_id', 'not in', [x.id for x in move.picking_id.move_lines])] for op in picking.pack_operation_ids:
quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=dom, prefered_domain=[('reservation_id', '=', False)], fallback_domain=[('reservation_id', '!=', False)], context=context) quant_ids = []
res_qty = qty if op.product_id and op.product_id.id in tocheck_product_ids:
for quant in quants: _update_quantity(op.product_id, op.product_qty, op.product_uom_id.id)
if quant[0]: # If quant can be reserved elif op.quant_id:
res_qty -= quant[1] quant_ids = [op.quant_id.id]
quant_obj.quants_reserve(cr, uid, quants, move, context=context) elif op.package_id:
quant_obj.write(cr, SUPERUSER_ID, [x[0].id for x in quants if x[0]], {'reservation_op_id': ops.id}, context=context) quant_ids = self.pool.get('stock.quant.package').get_content_package(cr, uid, ids, context=context)
return res_qty if quant_ids:
for quant in self.pool.get('stock.quant').browse(cr, uid, quant_ids, context=context):
if quant.product_id.id in tocheck_product_ids:
_update_quantity(quant.product_id, quant.qty, quant.product_id.uom_id.id)
return res
def rereserve(self, cr, uid, picking_ids, create=False, context=None): def _set_remaining_on_lines(self, cr, uid, picking, product_dict, context=None):
#loop on the move lines to write the remaining quantity
for move in picking.move_lines:
if product_dict.get(move.product_id.id):
qty_to_write = min(move.product_qty, product_dict[move.product_id.id])
product_dict[move.product_id.id] -= qty_to_write
qty_to_write = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, qty_to_write, move.product_uom.id)
self.pool.get('stock.move').write(cr, uid, [move.id], {'remaining_qty': move.product_uom_qty - qty_to_write}, context=context)
#loop on the product_dict items to set the remaining_qty on the pack operations if needed
for product_id, remaining_qty in product_dict.items:
if remaining_qty > 0:
#there was more in pack operations than in move lines
### MEMO
###remaining_qty sur move et pack sont des champ calculés grace à des one2many vers une nouvelle table(move_id, pack_id, qty_in_move_uom)
###check_availability() (pas sur du nom)
###1) unreserve
###2) action_assign
###
###build_domain()
###if op.package_id: return [('product_id', '=', move.produc_id), ('id', 'in', _get_all_quants)]
###elif op.quant_id: return [('product_id', '=', move.produc_id), ('id', '=', op.quant_id)]
###elif op.lot_id: return [('product_id', '=', move.producT_id), ('lot_id', '=', op.lot_id)]
###else: return [] #fallback on the default behavior
###
###
###do_transfer:
###1) create extra moves based on remaining_qty of pack_operations
###2) check_availability
###2) a) unreserve
###2) b) assign quants: loop on move_lines,
### for record in move.newtablelink:
### domain = buil_domain(record)
### if domain:
### quants = quant_get(domain, record.qty)
### for quant, qtty in quants:
### if quant:
### reserve quant for move (split)
### else: rien
### if move pas totallement available:
### action_assign(move) #mieux de faire action_assign car ca va checker le move précedent pyus faure un fallback (/!\ qty?)
###3) action_done du move: base sur les quants assigned (semble ok pour l'instant)
###4) packaging (donc hors du action_done du move)
def recompute_remaining_quantities(self, cr, uid, ids, product_ids=[], context=None):
""" This will recompute the remaining qty on all move lines on the given picking 'ids'
based on the pack operations of the picking. An optionnal product_ids may be given in
order to restrict the recomputation on stock moves having a product_id in 'product_ids' only.
""" """
This will unreserve all products and reserve the quants from the operations again
:return: Tuple (res, res2, resneg)
res: dictionary of ops with quantity that could not be processed matching ops and moves
res2: dictionary of moves with quantity that could not be processed matching ops and moves
resneg: the negative quants to be created: resneg[move][ops] gives negative quant to be created
tuple of dictionary with quantities of quant operation and product that can not be matched between ops and moves
and dictionary with remaining values on moves
"""
quant_obj = self.pool.get("stock.quant")
pack_obj = self.pool.get("stock.quant.package")
uom_obj = self.pool.get('product.uom') uom_obj = self.pool.get('product.uom')
res = {} # Qty still to do from ops for picking in self.browse(cr, uid, ids, context=context):
res2 = {} #what is left from moves product_dict = self._get_total_from_pack_operations(cr, uid, picking, product_ids=product_ids, context=context)
resneg= {} #Number of negative quants to create for move/op self._set_remaining_on_lines(cr, uid, picking, product_dict, context=context)
for picking in self.browse(cr, uid, picking_ids, context=context):
products_moves = {}
# unreserve everything and initialize res2
for move in picking.move_lines:
quant_obj.quants_unreserve(cr, uid, move, context=context)
res2[move.id] = move.product_qty
resneg[move.id] = {}
if move.state in ('confirmed', 'assigned'):
products_moves.setdefault(move.product_id.id, []).append(move)
# Resort pack_operation_ids such that package transfers happen first and then the most specific operations from the product
orderedpackops = picking.pack_operation_ids
orderedpackops.sort(key = lambda x: ((x.package_id and not x.product_id) and -3 or 0) + (x.package_id and -1 or 0) + (x.lot_id and -1 or 0))
for ops in orderedpackops: #def rereserve(self, cr, uid, picking_ids, create=False, context=None):
#If a product is specified in the ops, search for appropriate quants # """
if ops.product_id: # This will unreserve all products and reserve the quants from the operations again
# Match with moves # :return: Tuple (res, res2, resneg)
move_ids = ops.product_id.id in products_moves and filter(lambda x: res2[x.id] > 0, products_moves[ops.product_id.id]) or [] # res: dictionary of ops with quantity that could not be processed matching ops and moves
qty_to_do = uom_obj._compute_qty(cr, uid, ops.product_uom_id.id, ops.product_qty, to_uom_id=ops.product_id.uom_id.id) # res2: dictionary of moves with quantity that could not be processed matching ops and moves
while qty_to_do > 0 and move_ids: # resneg: the negative quants to be created: resneg[move][ops] gives negative quant to be created
move = move_ids.pop() # tuple of dictionary with quantities of quant operation and product that can not be matched between ops and moves
if res2[move.id] > qty_to_do: # and dictionary with remaining values on moves
qty = qty_to_do # """
qty_to_do = 0 # quant_obj = self.pool.get("stock.quant")
else: # pack_obj = self.pool.get("stock.quant.package")
qty = res2[move.id] # uom_obj = self.pool.get('product.uom')
qty_to_do -= res2[move.id] # res = {} # Qty still to do from ops
neg_qty = self._reserve_quants_ops_move(cr, uid, ops, move, qty, create=create, context=context) # res2 = {} #what is left from moves
if neg_qty > 0: # resneg= {} #Number of negative quants to create for move/op
resneg[move.id].setdefault(ops.id, 0) # for picking in self.browse(cr, uid, picking_ids, context=context):
resneg [move.id][ops.id] += neg_qty # products_moves = {}
res2[move.id] -= qty # # unreserve everything and initialize res2
res[ops.id] = {} # for move in picking.move_lines:
res[ops.id][ops.product_id.id] = qty_to_do # quant_obj.quants_unreserve(cr, uid, move, context=context)
# In case only a package is specified, take all the quants from the package # res2[move.id] = move.product_qty
elif ops.package_id: # resneg[move.id] = {}
quants = quant_obj.browse(cr, uid, pack_obj.get_content(cr, uid, [ops.package_id.id], context=context)) # if move.state in ('confirmed', 'assigned'):
quants = [x for x in quants if x.qty > 0] #Negative quants should not be moved # products_moves.setdefault(move.product_id.id, []).append(move)
for quant in quants: #
# Match with moves #
move_ids = quant.product_id.id in products_moves and filter(lambda x: res2[x.id] > 0, products_moves[quant.product_id.id]) or [] # # Resort pack_operation_ids such that package transfers happen first and then the most specific operations from the product
qty_to_do = quant.qty #
while qty_to_do > 0 and move_ids: # orderedpackops = picking.pack_operation_ids
move = move_ids.pop() # orderedpackops.sort(key = lambda x: ((x.package_id and not x.product_id) and -3 or 0) + (x.package_id and -1 or 0) + (x.lot_id and -1 or 0))
if res2[move.id] > qty_to_do:
qty = qty_to_do
qty_to_do = 0.0
else:
qty = res2[move.id]
qty_to_do -= res2[move.id]
quant_obj.quants_reserve(cr, uid, [(quant, qty)], move, context=context)
quant_obj.write(cr, uid, [quant.id], {'reservation_op_id': ops.id}, context=context)
res2[move.id] -= qty
res.setdefault(ops.id, {}).setdefault(quant.product_id.id, 0.0)
res[ops.id][quant.product_id.id] += qty_to_do
return (res, res2, resneg)
def do_partial(self, cr, uid, picking_ids, context=None): # for ops in orderedpackops:
# #If a product is specified in the ops, search for appropriate quants
# if ops.product_id:
# # Match with moves
# move_ids = ops.product_id.id in products_moves and filter(lambda x: res2[x.id] > 0, products_moves[ops.product_id.id]) or []
# qty_to_do = uom_obj._compute_qty(cr, uid, ops.product_uom_id.id, ops.product_qty, to_uom_id=ops.product_id.uom_id.id)
# while qty_to_do > 0 and move_ids:
# move = move_ids.pop()
# if res2[move.id] > qty_to_do:
# qty = qty_to_do
# qty_to_do = 0
# else:
# qty = res2[move.id]
# qty_to_do -= res2[move.id]
# neg_qty = self._reserve_quants_ops_move(cr, uid, ops, move, qty, create=create, context=context)
# if neg_qty > 0:
# resneg[move.id].setdefault(ops.id, 0)
# resneg [move.id][ops.id] += neg_qty
# res2[move.id] -= qty
# res[ops.id] = {}
# res[ops.id][ops.product_id.id] = qty_to_do
# # In case only a package is specified, take all the quants from the package
# elif ops.package_id:
# quants = quant_obj.browse(cr, uid, pack_obj.get_content(cr, uid, [ops.package_id.id], context=context))
# quants = [x for x in quants if x.qty > 0] #Negative quants should not be moved
# for quant in quants:
# # Match with moves
# move_ids = quant.product_id.id in products_moves and filter(lambda x: res2[x.id] > 0, products_moves[quant.product_id.id]) or []
# qty_to_do = quant.qty
# while qty_to_do > 0 and move_ids:
# move = move_ids.pop()
# if res2[move.id] > qty_to_do:
# qty = qty_to_do
# qty_to_do = 0.0
# else:
# qty = res2[move.id]
# qty_to_do -= res2[move.id]
# quant_obj.quants_reserve(cr, uid, [(quant, qty)], move, context=context)
# quant_obj.write(cr, uid, [quant.id], {'reservation_op_id': ops.id}, context=context)
# res2[move.id] -= qty
# res.setdefault(ops.id, {}).setdefault(quant.product_id.id, 0.0)
# res[ops.id][quant.product_id.id] += qty_to_do
# return (res, res2, resneg)
def _create_extra_moves(self, cr, uid, picking, remaining_dict, context=None):
'''This function creates move lines on a picking, at the time of do_transfer, based on unexpected product transfers (or exceeding quantities)
'''
for
pass
def rereserve_quants(self, cr, uid, picking, move_ids=[], context=None):
stock_move_obj = self.pool.get('stock.move')
if not move_ids:
self.do_unreserve(cr, uid, [picking.id], context=context)
self.action_assign(cr, uid, [picking.id], context=context)
else:
stock_move_obj.do_unreserve(cr, uid, move_ids, context=context)
stock_move_obj.action_assign(cr, uid, move_ids, context=context)
def do_transfer(self, cr, uid, picking_ids, context=None):
""" """
If no pack operation, we do simple action_done of the picking If no pack operation, we do simple action_done of the picking
Otherwise, do the pack operations Otherwise, do the pack operations
""" """
if not context: if not context:
context={} context = {}
stock_move_obj = self.pool.get('stock.move') stock_move_obj = self.pool.get('stock.move')
for picking in self.browse(cr, uid, picking_ids, context=context): for picking in self.browse(cr, uid, picking_ids, context=context):
if not picking.pack_operation_ids: if not picking.pack_operation_ids:
self.action_done(cr, uid, [picking.id], context=context) self.action_done(cr, uid, [picking.id], context=context)
continue continue
else: else:
# Rereserve quants #recompute remaining quantities on stock move
# TODO: quants could have been created already in Supplier, so create parameter could disappear remaining_dict = self.recompute_remaining_quantities(cr, uid, [picking.id], product_ids=[], context=context)
res = self.rereserve(cr, uid, [picking.id], create = True, context = context) #This time, quants need to be created #create extra moves in the picking (unexpected product moves coming from pack operations)
resneg = res[2] self._create_extra_moves(cr, uid, picking, remaining_dict, context=context)
orig_moves = picking.move_lines picking.refresh()
orig_qtys = {} #split move lines eventually
for orig in orig_moves: todo_move_ids = []
orig_qtys[orig.id] = orig.product_qty toassign_move_ids = []
#Add moves that operations need extra for move in picking.move_lines:
extra_moves = []
for ops in res[0].keys():
for prod in res[0][ops].keys():
product = self.pool.get('product.product').browse(cr, uid, prod, context=context)
qty = res[0][ops][prod]
if qty > 0:
#Create moves for products too many on operation
move_id = stock_move_obj.create(cr, uid, {
'name': product.name,
'product_id': product.id,
'product_uom_qty': qty,
'product_uom': product.uom_id.id,
'location_id': picking.location_id.id,
'location_dest_id': picking.location_dest_id.id,
'picking_id': picking.id,
'picking_type_id': picking.picking_type_id.id,
'group_id': picking.group_id.id,
}, context=context)
stock_move_obj.action_confirm(cr, uid, [move_id], context=context)
move = stock_move_obj.browse(cr, uid, move_id, context=context)
ops_rec = self.pool.get("stock.pack.operation").browse(cr, uid, ops, context=context)
resneg[move_id] = {}
resneg[move_id][ops] = self._reserve_quants_ops_move(cr, uid, ops_rec, move, qty, create=True, context=context)
extra_moves.append(move_id)
res2 = res[1]
#Backorder
for move in res2.keys():
if res2[move] > 0:
mov = stock_move_obj.browse(cr, uid, move, context=context)
new_move = stock_move_obj.split(cr, uid, mov, res2[move], context=context)
#Assign move as it was assigned before
stock_move_obj.action_assign(cr, uid, [new_move], context=context)
todo = []
orig_moves = [x for x in orig_moves if res[1][x.id] < orig_qtys[x.id]]
for move in orig_moves + stock_move_obj.browse(cr, uid, extra_moves, context=context):
if move.state == 'draft': if move.state == 'draft':
self.pool.get('stock.move').action_confirm(cr, uid, [move.id], context=context) toassign_move_ids.append(move.id)
todo.append(move.id) if move.remaining_qty == 0:
elif move.state in ('assigned','confirmed'): if move.state in ('draft', 'assigned', 'confirmed'):
todo.append(move.id) todo_move_ids.append(move.id)
if len(todo) and not ('do_only_split' in context and context['do_only_split']): elif move.remaining_qty > 0:
self.pool.get('stock.move').action_done(cr, uid, todo, negatives = resneg, context=context) new_move = stock_move_obj.split(cr, uid, move, move.remaining_qty, context=context)
elif 'do_only_split' in context and context['do_only_split']: #Assign move as it was assigned before
context.update({'split': [x.id for x in orig_moves] + extra_moves}) toassign_move_ids.append(new_move)
else:
#this should never happens
raise
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)
elif context.get('do_only_split'):
context.update({'split': todo_move_ids})
picking.refresh() picking.refresh()
self._create_backorder(cr, uid, picking, context=context) self._create_backorder(cr, uid, picking, context=context)
if toassign_move_ids:
stock_move_obj.action_assign(cr, uid, toassign_move_ids, context=context)
return True return True
## Rereserve quants
## TODO: quants could have been created already in Supplier, so create parameter could disappear
#res = self.rereserve(cr, uid, [picking.id], create = True, context = context) #This time, quants need to be created
#resneg = res[2]
#orig_moves = picking.move_lines
#orig_qtys = {}
#for orig in orig_moves:
# orig_qtys[orig.id] = orig.product_qty
##Add moves that operations need extra
#extra_moves = []
#for ops in res[0].keys():
# for prod in res[0][ops].keys():
# product = self.pool.get('product.product').browse(cr, uid, prod, context=context)
# qty = res[0][ops][prod]
# if qty > 0:
# #Create moves for products too many on operation
# move_id = stock_move_obj.create(cr, uid, {
# 'name': product.name,
# 'product_id': product.id,
# 'product_uom_qty': qty,
# 'product_uom': product.uom_id.id,
# 'location_id': picking.location_id.id,
# 'location_dest_id': picking.location_dest_id.id,
# 'picking_id': picking.id,
# 'picking_type_id': picking.picking_type_id.id,
# 'group_id': picking.group_id.id,
# }, context=context)
# stock_move_obj.action_confirm(cr, uid, [move_id], context=context)
# move = stock_move_obj.browse(cr, uid, move_id, context=context)
# ops_rec = self.pool.get("stock.pack.operation").browse(cr, uid, ops, context=context)
# resneg[move_id] = {}
# resneg[move_id][ops] = self._reserve_quants_ops_move(cr, uid, ops_rec, move, qty, create=True, context=context)
# extra_moves.append(move_id)
#res2 = res[1]
##Backorder
#for move in res2.keys():
# if res2[move] > 0:
# mov = stock_move_obj.browse(cr, uid, move, context=context)
# new_move = stock_move_obj.split(cr, uid, mov, res2[move], context=context)
# #Assign move as it was assigned before
# stock_move_obj.action_assign(cr, uid, [new_move], context=context)
#todo = []
#orig_moves = [x for x in orig_moves if res[1][x.id] < orig_qtys[x.id]]
def do_split(self, cr, uid, picking_ids, context=None): def do_split(self, cr, uid, picking_ids, context=None):
""" """
@ -1112,7 +1227,7 @@ class stock_picking(osv.osv):
context = {} context = {}
ctx = context.copy() ctx = context.copy()
ctx['do_only_split'] = True ctx['do_only_split'] = True
self.do_partial(cr, uid, picking_ids, context=ctx) self.do_transfer(cr, uid, picking_ids, context=ctx)
return True return True
# Methods for the barcode UI # Methods for the barcode UI
@ -1121,7 +1236,7 @@ class stock_picking(osv.osv):
return self.search(cr, uid, [('state', 'in', ('confirmed', 'assigned')), ('picking_type_id', '=', context.get('default_picking_type_id'))], context=context) return self.search(cr, uid, [('state', 'in', ('confirmed', 'assigned')), ('picking_type_id', '=', context.get('default_picking_type_id'))], context=context)
def action_done_from_packing_ui(self, cr, uid, picking_id, only_split_lines=False, context=None): def action_done_from_packing_ui(self, cr, uid, picking_id, only_split_lines=False, context=None):
self.do_partial(cr, uid, picking_id, only_split_lines, context=context) self.do_transfer(cr, uid, picking_id, only_split_lines, context=context)
#return id of next picking to work on #return id of next picking to work on
return self.get_picking_for_packing_ui(cr, uid, context=context) return self.get_picking_for_packing_ui(cr, uid, context=context)
@ -1334,12 +1449,13 @@ class stock_move(osv.osv):
# used for colors in tree views: # used for colors in tree views:
'scrapped': fields.related('location_dest_id','scrap_location',type='boolean',relation='stock.location',string='Scrapped', readonly=True), 'scrapped': fields.related('location_dest_id','scrap_location',type='boolean',relation='stock.location',string='Scrapped', readonly=True),
'quant_ids': fields.many2many('stock.quant', 'stock_quant_move_rel', 'move_id', 'quant_id', 'Quants'), 'quant_ids': fields.many2many('stock.quant', 'stock_quant_move_rel', 'move_id', 'quant_id', 'Moved Quants'),
'reserved_quant_ids': fields.one2many('stock.quant', 'reservation_id', 'Reserved quants'), 'reserved_quant_ids': fields.one2many('stock.quant', 'reservation_id', 'Reserved quants'),
'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Quantity', 'remaining_qty': fields.float('Remaining Quantity'),
digits_compute=dp.get_precision('Product Unit of Measure'), states={'done': [('readonly', True)]}, #'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Quantity',
store = {'stock.move': (lambda self, cr, uid, ids, c={}: ids , ['product_uom_qty', 'product_uom', 'reserved_quant_ids'], 20), # digits_compute=dp.get_precision('Product Unit of Measure'), states={'done': [('readonly', True)]},
'stock.quant': (_get_move, ['reservation_id'], 10)}), # store = {'stock.move': (lambda self, cr, uid, ids, c={}: ids , ['product_uom_qty', 'product_uom', 'reserved_quant_ids'], 20),
# 'stock.quant': (_get_move, ['reservation_id'], 10)}),
'procurement_id': fields.many2one('procurement.order', 'Procurement'), 'procurement_id': fields.many2one('procurement.order', 'Procurement'),
'group_id': fields.many2one('procurement.group', 'Procurement Group'), 'group_id': fields.many2one('procurement.group', 'Procurement Group'),
'rule_id': fields.many2one('procurement.rule', 'Procurement Rule', help='The pull rule that created this stock move'), 'rule_id': fields.many2one('procurement.rule', 'Procurement Rule', help='The pull rule that created this stock move'),
@ -1414,6 +1530,14 @@ class stock_move(osv.osv):
default['state'] = 'draft' default['state'] = 'draft'
return super(stock_move, self).copy(cr, uid, id, default, context) return super(stock_move, self).copy(cr, uid, id, default, context)
def do_unreserve(self, cr, uid, move_ids, context=None):
ids_to_free = []
quant_obj = self.pool.get("stock.quant")
for move in self.browse(cr, uid, move_ids, context=context):
ids_to_free += [quant.id for quant in move.reserved_quant_ids]
if ids_to_free:
quant_obj.write(cr, SUPERUSER_ID, ids_to_free, {'reservation_id': False, 'reservation_op_id': False}, context=context)
def _prepare_procurement_from_move(self, cr, uid, move, context=None): 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 "/") origin = (move.group_id and (move.group_id.name + ":") or "") + (move.rule_id and move.rule_id.name or "/")
group_id = move.group_id and move.group_id.id or False group_id = move.group_id and move.group_id.id or False
@ -1693,23 +1817,25 @@ class stock_move(osv.osv):
done.append(move.id) done.append(move.id)
continue continue
else: else:
qty = move.product_qty qty_already_assigned = ...
dp = [] qty = move.product_qty - qty_already_assigned
#we keep the quants already assigned and try to find the remaining qauntity on quants not assigned only
domain = [('reservation_id', '=', False), ('qty', '>', 0)]
#build the prefered domain based on quants that moved in previous linked done move
prev_quant_ids = []
for m2 in move.move_orig_ids: for m2 in move.move_orig_ids:
for q in m2.quant_ids: for q in m2.quant_ids:
dp.append(str(q.id)) prev_quant_ids.append(q.id)
qty -= q.qty prefered_domain = prev_quant_ids and [(('id', 'in', prev_quant_ids)] or []
domain = ['|', ('reservation_id', '=', False), ('reservation_id', '=', move.id), ('qty', '>', 0)] fallback_domain = prev_quant_ids and [('id', 'not in', prev_quant_ids)] or []
prefered_domain = dp and [('id', 'not in', dp)] or []
fallback_domain = dp and [('id', 'in', dp)] or []
quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=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=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)
#Will only reserve physical quants, no negative #Will only reserve physical quants, no negative
quant_obj.quants_reserve(cr, uid, quants, move, context=context) quant_obj.quants_reserve(cr, uid, quants, move, context=context)
# the total quantity is provided by existing quants # the total quantity is provided by existing quants
if all(map(lambda x:x[0], quants)): if all(map(lambda x: x[0], quants)):
done.append(move.id) done.append(move.id)
self.write(cr, uid, done, {'state': 'assigned'}) self.write(cr, uid, done, {'state': 'assigned'}, context=context)
self._putaway_apply(cr, uid, ids, context=context) self._putaway_apply(cr, uid, ids, context=context)
return done return done
@ -1750,9 +1876,9 @@ class stock_move(osv.osv):
# res[move.id] = [x.id for x in move.reserved_quant_ids] # res[move.id] = [x.id for x in move.reserved_quant_ids]
# return res # return res
def action_done(self, cr, uid, ids, negatives = False, context=None): def action_done(self, cr, uid, ids, context=None):
""" Makes the move done and if all moves are done, it will finish the picking. """ Makes the move done and if all moves are done, it will finish the picking.
If quants are not assigned yet, it should assign them It assumes that quants are already assigned to stock moves.
Putaway strategies should be applied Putaway strategies should be applied
@return: @return:
""" """
@ -1784,26 +1910,25 @@ class stock_move(osv.osv):
prefered_domain = [('reservation_id', '=', move.id)] prefered_domain = [('reservation_id', '=', move.id)]
fallback_domain = [('reservation_id', '=', False)] fallback_domain = [('reservation_id', '=', False)]
if move.picking_id and move.picking_id.pack_operation_ids: if move.picking_id and move.picking_id.pack_operation_ids:
quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty - move.remaining_qty, domain=dom, prefered_domain=prefered_domain, fallback_domain=fallback_domain, context=context) #if negatives and move.id in negatives:
quant_obj.quants_move(cr, uid, quants, move, context=context) # for negative_op in negatives[move.id].keys():
if negatives and move.id in negatives: # ops = ops_obj.browse(cr, uid, negative_op, context=context)
for negative_op in negatives[move.id].keys(): # negatives[move.id][negative_op] = quant_obj.quants_move(cr, uid, [(None, negatives[move.id][negative_op])], move,
ops = ops_obj.browse(cr, uid, negative_op, context=context) # lot_id = ops.lot_id and ops.lot_id.id or False,
negatives[move.id][negative_op] = quant_obj.quants_move(cr, uid, [(None, negatives[move.id][negative_op])], move, # owner_id = ops.owner_id and ops.owner_id.id or False,
lot_id = ops.lot_id and ops.lot_id.id or False, # package_id = ops.package_id and ops.package_id.id or False, context=context)
owner_id = ops.owner_id and ops.owner_id.id or False,
package_id = ops.package_id and ops.package_id.id or False, context=context)
#Packing: #Packing:
#TODO: decide if this has to be moved in do_transfer or not
#WARNING: in any ways, this code isn't good and must be revised
reserved_ops = list(set([x.reservation_op_id.id for x in move.reserved_quant_ids])) reserved_ops = list(set([x.reservation_op_id.id for x in move.reserved_quant_ids]))
for ops in ops_obj.browse(cr, uid, reserved_ops, context=context): for ops in ops_obj.browse(cr, uid, reserved_ops, context=context):
if ops.product_id: if ops.product_id:
quant_obj.write(cr, uid, [x.id for x in ops.reserved_quant_ids], {'package_id': ops.result_package_id and ops.result_package_id.id or False}, context=context) quant_obj.write(cr, uid, [x.id for x in ops.reserved_quant_ids], {'package_id': ops.result_package_id and ops.result_package_id.id or False}, context=context)
else: else:
pack_obj.write(cr, uid, [ops.package_id.id], {'parent_id': ops.result_package_id and ops.result_package_id.id or False}, context=context) pack_obj.write(cr, uid, [ops.package_id.id], {'parent_id': ops.result_package_id and ops.result_package_id.id or False}, context=context)
else: quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=dom, prefered_domain=prefered_domain, fallback_domain=fallback_domain, context=context)
quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=dom, prefered_domain=prefered_domain, fallback_domain=fallback_domain, context=context) #Will move all quants_get and as such create negative quants
#Will move all quants_get and as such create negative quants quant_obj.quants_move(cr, uid, quants, move, context=context)
quant_obj.quants_move(cr, uid, quants, move, context=context)
quant_obj.quants_unreserve(cr, uid, move, context=context) quant_obj.quants_unreserve(cr, uid, move, context=context)
#Check moves that were pushed #Check moves that were pushed
@ -3140,14 +3265,14 @@ class stock_pack_operation(osv.osv):
_name = "stock.pack.operation" _name = "stock.pack.operation"
_description = "Packing Operation" _description = "Packing Operation"
def _get_remaining_qty(self, cr, uid, ids, name, args, context=None): #def _get_remaining_qty(self, cr, uid, ids, name, args, context=None):
res = {} # res = {}
for ops in self.browse(cr, uid, ids, context=context): # for ops in self.browse(cr, uid, ids, context=context):
qty = ops.product_qty # qty = ops.product_qty
for quant in ops.reserved_quant_ids: # for quant in ops.reserved_quant_ids:
qty -= quant.qty # qty -= quant.qty
res[ops.id] = qty # res[ops.id] = qty
return res # return res
def product_id_change(self, cr, uid, ids, product_id, product_uom_id, product_qty, context=None): def product_id_change(self, cr, uid, ids, product_id, product_uom_id, product_qty, context=None):
res = self.on_change_tests(cr, uid, ids, product_id, product_uom_id, product_qty, context=context) res = self.on_change_tests(cr, uid, ids, product_id, product_uom_id, product_qty, context=context)
@ -3192,7 +3317,8 @@ class stock_pack_operation(osv.osv):
'cost': fields.float("Cost", help="Unit Cost for this product line"), 'cost': fields.float("Cost", help="Unit Cost for this product line"),
'currency': fields.many2one('res.currency', string="Currency", help="Currency in which Unit cost is expressed", ondelete='CASCADE'), 'currency': fields.many2one('res.currency', string="Currency", help="Currency in which Unit cost is expressed", ondelete='CASCADE'),
'reserved_quant_ids': fields.one2many('stock.quant', 'reservation_op_id', string='Reserved Quants', readonly=True, help='Quants reserved for this operation'), 'reserved_quant_ids': fields.one2many('stock.quant', 'reservation_op_id', string='Reserved Quants', readonly=True, help='Quants reserved for this operation'),
'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Qty'), #'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Qty'),
'remaining_qty': fields.float('Remaining Qty'),
} }
_defaults = { _defaults = {

View File

@ -747,9 +747,9 @@
<form string="Transfer" version="7.0"> <form string="Transfer" version="7.0">
<header> <header>
<button name="action_confirm" states="draft" string="Mark as Todo" type="object" class="oe_highlight" groups="base.group_user"/> <button name="action_confirm" states="draft" string="Mark as Todo" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="action_assign" states="confirmed" string="Check Availability" type="object" class="oe_highlight" groups="base.group_user"/> <button name="do_rereserve" states="confirmed" string="Check Availability" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="force_assign" states="confirmed" string="Force Availability" type="object" class="oe_highlight" groups="base.group_user"/> <button name="force_assign" states="confirmed" string="Force Availability" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="do_partial" states="assigned" string="Transfer Done" groups="stock.group_stock_user" type="object" class="oe_highlight"/> <button name="do_transfer" states="assigned" string="Transfer Done" groups="stock.group_stock_user" type="object" class="oe_highlight"/>
<button name="do_prepare_partial" string="Partial Transfer" groups="stock.group_stock_user" type="object" class="oe_highlight" attrs="{'invisible': ['|',('pack_operation_exist', '=', True),('state','!=','assigned')]}"/> <button name="do_prepare_partial" string="Partial Transfer" groups="stock.group_stock_user" type="object" class="oe_highlight" attrs="{'invisible': ['|',('pack_operation_exist', '=', True),('state','!=','assigned')]}"/>
<button name="%(act_stock_return_picking)d" string="Reverse Transfer" states="done" type="action" groups="base.group_user"/> <button name="%(act_stock_return_picking)d" string="Reverse Transfer" states="done" type="action" groups="base.group_user"/>
<button name="action_cancel" states="assigned,confirmed,draft" string="Cancel Transfer" groups="base.group_user" type="object"/> <button name="action_cancel" states="assigned,confirmed,draft" string="Cancel Transfer" groups="base.group_user" type="object"/>
@ -779,7 +779,14 @@
</group> </group>
<notebook> <notebook>
<page string="Products"> <page string="Products">
<separator string="Expected Quantities" attrs="{'invisible': [('pack_operation_exist', '=', False)]}"/> <group>
<group col="3">
<separator string="Expected Quantities" attrs="{'invisible': [('pack_operation_exist', '=', False)]}"/>
</group>
<group col="1">
<button name="do_recompute_remaining_quantities" string="Recompute Remaining Quantities" type="object" attrs="{'invisible': ['|',('pack_operation_exist', '=', False),('state','!=','assigned')]}" class="oe_link oe_right oe_inline"/>
</group>
</group>
<field name="move_lines" context="{'address_in_id': partner_id, 'form_view_ref':'stock.view_move_picking_form', 'tree_view_ref':'view_move_picking_tree', 'default_picking_type_id': picking_type_id,'default_picking_id': active_id}"/> <field name="move_lines" context="{'address_in_id': partner_id, 'form_view_ref':'stock.view_move_picking_form', 'tree_view_ref':'view_move_picking_tree', 'default_picking_type_id': picking_type_id,'default_picking_id': active_id}"/>
<group> <group>
<group col="3"> <group col="3">
@ -1039,6 +1046,7 @@
groups="base.group_user"/> groups="base.group_user"/>
<field name="scrapped" invisible="1"/> <field name="scrapped" invisible="1"/>
<field name="location_dest_id" groups="stock.group_locations"/> <field name="location_dest_id" groups="stock.group_locations"/>
<field name="remaining_qty"/>
<field name="state"/> <field name="state"/>
</tree> </tree>
</field> </field>