diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 899d253e70c..708f53d140f 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -648,10 +648,24 @@ class stock_picking(osv.osv): order = {'confirmed': 0, 'waiting': 1, 'assigned': 2} order_inv = dict(zip(order.values(), order.keys())) lst = [order[x.state] for x in pick.move_lines if x.state not in ('cancel', 'done')] - if pick.move_lines == 'one': + if pick.move_type == 'one': res[pick.id] = order_inv[min(lst)] else: + #we are in the case of partial delivery, so if all move are assigned, picking + #should be assign too, else if one of the move is assigned, picking should be + #in partially available state, otherwise, picking is in waiting or confirmed state res[pick.id] = order_inv[max(lst)] + if not all(x == 2 for x in lst): + #there is the case where we only have one product partially available + partial = False + for move in pick.move_lines: + if partial: + break + for quant in move.reserved_quant_ids: + if quant.qty > 0: + res[pick.id] = 'partially_available' + partial = True + break return res def _get_pickings(self, cr, uid, ids, context=None): @@ -661,6 +675,13 @@ class stock_picking(osv.osv): res.add(move.picking_id.id) return list(res) + def _get_pickings_from_quant(self, cr, uid, ids, context=None): + res = set() + for quant in self.browse(cr, uid, ids, context=context): + if quant.reservation_id and quant.reservation_id.picking_id: + res.add(quant.reservation_id.picking_id.id) + return list(res) + def _get_pack_operation_exist(self, cr, uid, ids, field_name, arg, context=None): res = {} for pick in self.browse(cr, uid, ids, context=context): @@ -692,17 +713,20 @@ class stock_picking(osv.osv): 'move_type': fields.selection([('direct', 'Partial'), ('one', 'All at once')], 'Delivery Method', required=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, help="It specifies goods to be deliver partially or all at once"), 'state': fields.function(_state_get, type="selection", store={ 'stock.picking': (lambda self, cr, uid, ids, ctx: ids, ['move_type', 'move_lines'], 20), - 'stock.move': (_get_pickings, ['state', 'picking_id'], 20)}, selection=[ + 'stock.move': (_get_pickings, ['state', 'picking_id'], 20), + 'stock.quant': (_get_pickings_from_quant, ['reservation_id'], 20)}, selection=[ ('draft', 'Draft'), ('cancel', 'Cancelled'), ('waiting', 'Waiting Another Operation'), ('confirmed', 'Waiting Availability'), + ('partially_available', 'Partially Available'), ('assigned', 'Ready to Transfer'), ('done', 'Transferred'), ], string='Status', readonly=True, select=True, track_visibility='onchange', help=""" * Draft: not confirmed yet and will not be scheduled until confirmed\n * Waiting Another Operation: waiting for another move to proceed before it becomes automatically available (e.g. in Make-To-Order flows)\n * Waiting Availability: still waiting for the availability of products\n + * Partially Available: some products are available and reserved\n * Ready to Transfer: products reserved, simply waiting for confirmation.\n * Transferred: has been processed, can't be modified or cancelled anymore\n * Cancelled: has been cancelled, can't be confirmed anymore""" @@ -817,6 +841,8 @@ class stock_picking(osv.osv): for pick in self.browse(cr, uid, ids, context=context): move_ids = [x.id for x in pick.move_lines if x.state in ['confirmed', 'waiting']] self.pool.get('stock.move').force_assign(cr, uid, move_ids, context=context) + if pick.pack_operation_exist: + self.do_prepare_partial(cr, uid, [pick.id], context=None) return True def cancel_assign(self, cr, uid, ids, context=None): @@ -902,9 +928,14 @@ class stock_picking(osv.osv): pack_operation_obj = self.pool.get('stock.pack.operation') pack_obj = self.pool.get("stock.quant.package") quant_obj = self.pool.get("stock.quant") + #get list of existing packages and delete them + existing_package_ids = pack_operation_obj.search(cr, uid, [('picking_id', 'in', picking_ids)], context=context) + if existing_package_ids: + pack_operation_obj.unlink(cr, uid, existing_package_ids, context) + for picking in self.browse(cr, uid, picking_ids, context=context): for move in picking.move_lines: - if move.state != 'assigned': + if move.state not in ('assigned', 'confirmed'): continue #Check which of the reserved quants are entirely in packages (can be in separate method) packages = list(set([x.package_id for x in move.reserved_quant_ids if x.package_id])) @@ -957,22 +988,31 @@ class stock_picking(osv.osv): 'cost': quant.cost, 'package_id': quant.package_id and quant.package_id.id or False, }, context=context) - if remaining_qty > 0: - pack_operation_obj.create(cr, uid, { - 'picking_id': picking.id, - 'product_qty': remaining_qty, - 'product_id': move.product_id.id, - 'product_uom_id': move.product_id.uom_id.id, - 'cost': move.product_id.standard_price, - }, context=context) + if move.state == 'assigned': + #only add a line with remaining qty if we have all qty reserved (move is assigned) + #otherwise we may be in a case where we do a partial delivery with only a few + #available products and we don't want to be able to transfer everything + if remaining_qty > 0: + pack_operation_obj.create(cr, uid, { + 'picking_id': picking.id, + 'product_qty': remaining_qty, + 'product_id': move.product_id.id, + 'product_uom_id': move.product_id.uom_id.id, + 'cost': move.product_id.standard_price, + }, context=context) def do_unreserve(self, cr, uid, picking_ids, context=None): """ Will remove all quants for picking in picking_ids """ moves_to_unreserve = [] + pack_line_to_unreserve = [] for picking in self.browse(cr, uid, picking_ids, context=context): moves_to_unreserve += [m.id for m in picking.move_lines] + if picking.pack_operation_ids: + pack_line_to_unreserve += [p.id for p in picking.pack_operation_ids] + if pack_line_to_unreserve: + self.pool.get('stock.pack.operation').unlink(cr, uid, pack_line_to_unreserve, context=context) if moves_to_unreserve: self.pool.get('stock.move').do_unreserve(cr, uid, moves_to_unreserve, context=context) @@ -1266,6 +1306,33 @@ class stock_move(osv.osv): res[move.id] = min(move.product_qty, availability) return res + def _get_string_qty_information(self, cr, uid, ids, field_name, args, context=None): + res = dict.fromkeys(ids, False) + for move in self.browse(cr, uid, ids, context=context): + info_available = 'Quantity available: '+str(move.availability)+'\n' + info_reserved = 'Quantity reserved: '+str(move.reserved_availability)+'\n' + info_remaining = 'Quantity remaining: '+str(move.remaining_qty) + info = info_reserved + if move.picking_id: + if move.picking_id.state in ('draft', 'confirmed', 'partially_available'): + info = info_available + info + if move.picking_id.pack_operation_exist: + info += info_remaining + res[move.id] = info + return res + + def _get_reserved_availability(self, cr, uid, ids, field_name, args, context=None): + res = dict.fromkeys(ids, False) + for move in self.browse(cr, uid, ids, context=context): + if move.state == 'done': + res[move.id] = move.product_qty + else: + qty = 0 + for quant in move.reserved_quant_ids: + qty += quant.qty + res[move.id] = qty + return res + def _get_move(self, cr, uid, ids, context=None): res = set() for quant in self.browse(cr, uid, ids, context=context): @@ -1357,7 +1424,9 @@ class stock_move(osv.osv): 'lot_ids': fields.function(_get_lot_ids, type='many2many', relation='stock.quant', string='Lots'), 'origin_returned_move_id': fields.many2one('stock.move', 'Origin return move', help='move that created the return move'), 'returned_move_ids': fields.one2many('stock.move', 'origin_returned_move_id', 'All returned moves', help='Optional: all returned moves created from this move'), - 'availability': fields.function(_get_product_availability, type='float', string='Availability'), + 'reserved_availability': fields.function(_get_reserved_availability, type='float', string='Quantity Reserved', readonly=True, help='Quantity that has already been reserved for this move'), + 'availability': fields.function(_get_product_availability, type='float', string='Quantity Available', readonly=True, help='Quantity in stock that can still be reserved'), + 'string_availability_info': fields.function(_get_string_qty_information, type='text', string='Quantities Information', readonly=True, help='Show various information on stock availability'), 'restrict_lot_id': fields.many2one('stock.production.lot', 'Lot', help="Technical field used to depict a restriction on the lot of quants to consider when marking this move as 'done'"), 'restrict_partner_id': fields.many2one('res.partner', 'Owner ', help="Technical field used to depict a restriction on the ownership of quants to consider when marking this move as 'done'"), 'putaway_ids': fields.one2many('stock.move.putaway', 'move_id', 'Put Away Suggestions'), diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index 94e20b1e09f..ecad178dd31 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -747,13 +747,14 @@
@@ -790,8 +791,9 @@ -