[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)
# 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:
#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
self._quant_reconcile_negative(cr, uid, quant_record, context=context)
def check_preferred_location(self, cr, uid, move, context=None):
if move.putaway_ids and move.putaway_ids[0]:
#Take only first suggestion for the moment
return move.putaway_ids[0].location_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:
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:
self._quant_split(cr, uid, quant, qty, context=context)
# 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):
"""
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:
backorder_moves = picking.move_lines
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']:
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:
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)
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
"""
ids_to_free = []
quant_obj = self.pool.get("stock.quant")
moves_to_unreserve = []
for picking in self.browse(cr, uid, picking_ids, context=context):
for move in picking.move_lines:
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)
moves_to_unreserve += [m.id for m in picking.move_lines]
if moves_to_unreserve:
self.pool.get('stock.move').do_unreserve(cr, uid, moves_to_unreserve, context=context)
def _reserve_quants_ops_move(self, cr, uid, ops, move, qty, create=False, context=None):
"""
Will return the quantity that could not be reserved
"""
quant_obj = self.pool.get("stock.quant")
op_obj = self.pool.get("stock.pack.operation")
if create and move.location_id.usage != 'internal':
# 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.write({'reservation_op_id': ops.id, 'location_id': move.location_id.id})
quant_obj.quants_reserve(cr, uid, [(quant, qty)], move, context=context)
return 0
#def _reserve_quants_ops_move(self, cr, uid, ops, move, qty, create=False, context=None):
# """
# Will return the quantity that could not be reserved
# """
# quant_obj = self.pool.get("stock.quant")
# op_obj = self.pool.get("stock.pack.operation")
# if create and move.location_id.usage != 'internal':
# # 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.write({'reservation_op_id': ops.id, 'location_id': move.location_id.id})
# quant_obj.quants_reserve(cr, uid, [(quant, qty)], move, context=context)
# 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:
#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
tocheck_product_ids = product_ids
#loop on the operations to compute the total of each product
for op in picking.pack_operation_ids:
quant_ids = []
if op.product_id and op.product_id.id in tocheck_product_ids:
_update_quantity(op.product_id, op.product_qty, op.product_uom_id.id)
elif op.quant_id:
quant_ids = [op.quant_id.id]
elif op.package_id:
quant_ids = self.pool.get('stock.quant.package').get_content_package(cr, uid, ids, context=context)
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')
res = {} # Qty still to do from ops
res2 = {} #what is left from moves
resneg= {} #Number of negative quants to create for move/op
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 picking in self.browse(cr, uid, ids, context=context):
product_dict = self._get_total_from_pack_operations(cr, uid, picking, product_ids=product_ids, context=context)
self._set_remaining_on_lines(cr, uid, picking, product_dict, context=context)
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 rereserve(self, cr, uid, picking_ids, create=False, context=None):
# """
# 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')
# res = {} # Qty still to do from ops
# res2 = {} #what is left from moves
# resneg= {} #Number of negative quants to create for move/op
# 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))
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
Otherwise, do the pack operations
"""
if not context:
context={}
context = {}
stock_move_obj = self.pool.get('stock.move')
for picking in self.browse(cr, uid, picking_ids, context=context):
if not picking.pack_operation_ids:
self.action_done(cr, uid, [picking.id], context=context)
continue
else:
# 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]]
for move in orig_moves + stock_move_obj.browse(cr, uid, extra_moves, context=context):
#recompute remaining quantities on stock move
remaining_dict = self.recompute_remaining_quantities(cr, uid, [picking.id], product_ids=[], context=context)
#create extra moves in the picking (unexpected product moves coming from pack operations)
self._create_extra_moves(cr, uid, picking, remaining_dict, context=context)
picking.refresh()
#split move lines eventually
todo_move_ids = []
toassign_move_ids = []
for move in picking.move_lines:
if move.state == 'draft':
self.pool.get('stock.move').action_confirm(cr, uid, [move.id], context=context)
todo.append(move.id)
elif move.state in ('assigned','confirmed'):
todo.append(move.id)
if len(todo) and not ('do_only_split' in context and context['do_only_split']):
self.pool.get('stock.move').action_done(cr, uid, todo, negatives = resneg, context=context)
elif 'do_only_split' in context and context['do_only_split']:
context.update({'split': [x.id for x in orig_moves] + extra_moves})
toassign_move_ids.append(move.id)
if move.remaining_qty == 0:
if move.state in ('draft', 'assigned', 'confirmed'):
todo_move_ids.append(move.id)
elif move.remaining_qty > 0:
new_move = stock_move_obj.split(cr, uid, move, move.remaining_qty, context=context)
#Assign move as it was assigned before
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()
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
## 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):
"""
@ -1112,7 +1227,7 @@ class stock_picking(osv.osv):
context = {}
ctx = context.copy()
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
# 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)
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 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:
'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'),
'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Quantity',
digits_compute=dp.get_precision('Product Unit of Measure'), states={'done': [('readonly', True)]},
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)}),
'remaining_qty': fields.float('Remaining Quantity'),
#'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Quantity',
# digits_compute=dp.get_precision('Product Unit of Measure'), states={'done': [('readonly', True)]},
# 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'),
'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'),
@ -1414,6 +1530,14 @@ class stock_move(osv.osv):
default['state'] = 'draft'
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):
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
@ -1693,23 +1817,25 @@ class stock_move(osv.osv):
done.append(move.id)
continue
else:
qty = move.product_qty
dp = []
qty_already_assigned = ...
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 q in m2.quant_ids:
dp.append(str(q.id))
qty -= q.qty
domain = ['|', ('reservation_id', '=', False), ('reservation_id', '=', move.id), ('qty', '>', 0)]
prefered_domain = dp and [('id', 'not in', dp)] or []
fallback_domain = dp and [('id', 'in', dp)] or []
prev_quant_ids.append(q.id)
prefered_domain = prev_quant_ids and [(('id', 'in', prev_quant_ids)] or []
fallback_domain = prev_quant_ids and [('id', 'not in', prev_quant_ids)] 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)
#Will only reserve physical quants, no negative
quant_obj.quants_reserve(cr, uid, quants, move, context=context)
# 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)
self.write(cr, uid, done, {'state': 'assigned'})
self._putaway_apply(cr, uid, ids, context=context)
self.write(cr, uid, done, {'state': 'assigned'}, context=context)
self._putaway_apply(cr, uid, ids, context=context)
return done
@ -1750,9 +1876,9 @@ class stock_move(osv.osv):
# res[move.id] = [x.id for x in move.reserved_quant_ids]
# 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.
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
@return:
"""
@ -1784,26 +1910,25 @@ class stock_move(osv.osv):
prefered_domain = [('reservation_id', '=', move.id)]
fallback_domain = [('reservation_id', '=', False)]
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)
quant_obj.quants_move(cr, uid, quants, move, context=context)
if negatives and move.id in negatives:
for negative_op in negatives[move.id].keys():
ops = ops_obj.browse(cr, uid, negative_op, context=context)
negatives[move.id][negative_op] = quant_obj.quants_move(cr, uid, [(None, negatives[move.id][negative_op])], 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,
package_id = ops.package_id and ops.package_id.id or False, context=context)
#if negatives and move.id in negatives:
# for negative_op in negatives[move.id].keys():
# ops = ops_obj.browse(cr, uid, negative_op, context=context)
# negatives[move.id][negative_op] = quant_obj.quants_move(cr, uid, [(None, negatives[move.id][negative_op])], 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,
# package_id = ops.package_id and ops.package_id.id or False, context=context)
#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]))
for ops in ops_obj.browse(cr, uid, reserved_ops, context=context):
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)
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)
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)
#Will move all quants_get and as such create negative quants
quant_obj.quants_move(cr, uid, quants, move, 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
quant_obj.quants_move(cr, uid, quants, move, context=context)
quant_obj.quants_unreserve(cr, uid, move, context=context)
#Check moves that were pushed
@ -3140,14 +3265,14 @@ class stock_pack_operation(osv.osv):
_name = "stock.pack.operation"
_description = "Packing Operation"
def _get_remaining_qty(self, cr, uid, ids, name, args, context=None):
res = {}
for ops in self.browse(cr, uid, ids, context=context):
qty = ops.product_qty
for quant in ops.reserved_quant_ids:
qty -= quant.qty
res[ops.id] = qty
return res
#def _get_remaining_qty(self, cr, uid, ids, name, args, context=None):
# res = {}
# for ops in self.browse(cr, uid, ids, context=context):
# qty = ops.product_qty
# for quant in ops.reserved_quant_ids:
# qty -= quant.qty
# res[ops.id] = qty
# return res
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)
@ -3192,7 +3317,8 @@ class stock_pack_operation(osv.osv):
'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'),
'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 = {

View File

@ -747,9 +747,9 @@
<form string="Transfer" version="7.0">
<header>
<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="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="%(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"/>
@ -779,7 +779,14 @@
</group>
<notebook>
<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}"/>
<group>
<group col="3">
@ -1039,6 +1046,7 @@
groups="base.group_user"/>
<field name="scrapped" invisible="1"/>
<field name="location_dest_id" groups="stock.group_locations"/>
<field name="remaining_qty"/>
<field name="state"/>
</tree>
</field>