diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 307d25e4db6..c2e3771af05 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -334,7 +334,7 @@ class stock_quant(osv.osv): if quantity >= abs(quant.qty): res += [(quant, abs(quant.qty))] quantity -= abs(quant.qty) - else: + elif quantity != 0: res += [(quant, quantity)] quantity = 0 break @@ -557,6 +557,21 @@ class stock_picking(osv.osv): self.pool.get('stock.move').cancel_assign(cr, uid, move_ids) return True + def _create_backorder(self, cr, uid, picking, context=None): + sequence_obj = self.pool.get('ir.sequence') + new_picking_name = picking.name + #TODO back_order_name is False currently => find why + back_order_name = sequence_obj.get(cr, uid, 'stock.picking') #TODO: Need to have sequence for every picking type + self.write(cr, uid, [picking.id], {'name': back_order_name}) + backorder_id = self.copy(cr, uid, picking.id, { + 'name': new_picking_name, + 'move_lines': [], # the move_lines are linked after + 'state': 'done' # the backorder is already created as 'done' because it is created only after the action_done of stock moves + }) + self.message_post(cr, uid, picking.id, body=_("Back order %s has been created.") % (back_order_name), context=context) + unlink_operation_order = [(2, op.id) for op in picking.pack_operation_ids] + self.write(cr, uid, [picking.id], {'backorder_id': backorder_id, 'pack_operation_ids': unlink_operation_order}, context=context) + return backorder_id def action_cancel(self, cr, uid, ids, context=None): """ Changes picking state to cancel. @@ -654,12 +669,18 @@ class stock_picking(osv.osv): quant_ids.append(quant.id) quant_obj.write(cr, uid, quant_ids, {'package_id': op.result_package_id.id}, context=context) - def _find_move_from_product(self, cr, uid, picking_id, product_id, context=None): + def _find_moves_from_product(self, cr, uid, picking_id, product_id, qty_to_dispatch, context=None): stock_move_obj = self.pool.get('stock.move') - move_id = stock_move_obj.search(cr, uid, [('picking_id', '=', picking_id), ('product_id', '=', product_id), ('state', 'not in', ['cancel', 'done'])], limit=1, context=context) - return move_id and move_id[0] or False + move_ids = stock_move_obj.search(cr, uid, [('picking_id', '=', picking_id), ('product_id', '=', product_id), ('state', 'not in', ['cancel', 'done'])], limit=1, context=context) + res = [] + for move in stock_move_obj.browse(cr, uid, move_ids, context=context): + res += [(move.id, min(move.product_qty, qty_to_dispatch))] + qty_to_dispatch -= move.product_qty + if qty_to_dispatch > 0: + res += [(None, qty_to_dispatch)] + return res - def _create_unexpected_move(self, cr, uid, picking, product, qty, cost, context=None): + def _create_unexpected_move(self, cr, uid, picking, product, qty, cost, quant=False, context=None): stock_move_obj = self.pool.get('stock.move') #TODO fix me move_name = 'stock.picking.in' @@ -670,7 +691,8 @@ class stock_picking(osv.osv): #'lot_id': wizard_line.lot_id.id, 'location_id': picking.location_id.id, 'location_dest_id': picking.location_dest_id.id, - 'picking_id': False, + 'picking_id': picking.id, + 'reserved_quant_ids': quant and [(4,quant.id)] or [], #TODO fix me 'picking_type_id': self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'picking_type_internal')[1], 'price_unit': cost}, context=context) @@ -684,59 +706,48 @@ class stock_picking(osv.osv): stock_move_obj = self.pool.get('stock.move') quant_obj = self.pool.get('stock.quant') quant_package_obj = self.pool.get('stock.quant.package') - proc_group = self.pool.get("procurement.group") - group_id = proc_group.create(cr, uid, {}, context=context) todo_move_ids = [] assert len(picking_ids) == 1, 'Partial picking is only supported for one record at a time' if context is None: context = {} picking = self.browse(cr, uid, picking_ids[0], context=context) - need_backorder = False - #first check that a backorder needs to be created or not - for move in picking.move_lines: - if move.remaining_qty != 0: - need_backorder = True - break - if need_backorder: - for op in picking.pack_operation_ids: - if op.package_id: - #moving a package that includes several quants - included_package_ids = quant_package_obj.search(cr, uid, [('parent_id', 'child_of', [op.package_id.id])], context=context) - included_quant_ids = quant_obj.search(cr, uid, [('package_id', 'in', included_package_ids)], context=context) - for quant in quant_obj.browse(cr, uid, included_quant_ids, context=context): - move_id = self._find_move_from_product(cr, uid, picking.id, quant.product_id.id, context=context) - partial_datas = { - 'product_qty': quant.qty * op.product_qty, - 'product_uom_id': quant.product_id.uom_id.id, - 'cost': quant.cost, - } + for op in picking.pack_operation_ids: + if op.package_id: + #moving a package that includes several quants + included_package_ids = quant_package_obj.search(cr, uid, [('parent_id', 'child_of', [op.package_id.id])], context=context) + included_quant_ids = quant_obj.search(cr, uid, [('package_id', 'in', included_package_ids)], context=context) + for quant in quant_obj.browse(cr, uid, included_quant_ids, context=context): + qty_to_dispatch = quant.qty * op.product_qty + for move_id, qty in self._find_moves_from_product(cr, uid, picking.id, quant.product_id.id, qty_to_dispatch, context=context): if not move_id: - move_id = self._create_unexpected_move(cr, uid, picking, quant.product_id, op.product_qty * quant.qty, quant.cost, context=context) - todo_move_ids += [stock_move_obj.do_partial(cr, uid, move_id, partial_datas, group_id, context=context)] - else: - move_id = op.move_id.id - partial_datas = { - 'product_qty': op.product_qty, - 'product_uom_id': op.product_uom_id.id, - 'cost': op.cost, - } + move_id = self._create_unexpected_move(cr, uid, picking, quant.product_id, qty, quant.cost, quant, context=context) + else: + partial_datas = { + 'product_qty': qty, + 'product_uom_id': quant.product_id.uom_id.id, + 'cost': quant.cost, + 'reserved_quant_ids': [(4, quant.id)], + } + todo_move_ids += [stock_move_obj.do_partial(cr, uid, move_id, partial_datas, context=context)] + else: + product = op.quant_id and op.quant_id.product_id or op.product_id + cost = op.quant_id and op.quant_id.cost or op.product_id.standard_price + reserved_quant_id = op.quant_id and op.quant_id.id or False + for move_id, qty in self._find_moves_from_product(cr, uid, picking.id, product.id, op.product_qty, context=context): if not move_id: - if op.product_id: - #product not expected - move_id = self._create_unexpected_move(cr, uid, picking, op.product_id, op.product_qty, op.product_id.standard_price, context=context) - elif op.quant_id: - #quant not expected - move_id = self._create_unexpected_move(cr, uid, picking, op.quant_id.product_id, op.product_qty, op.quant_id.cost, context=context) - - todo_move_ids += [stock_move_obj.do_partial(cr, uid, move_id, partial_datas, group_id, context=context)] - else: - todo_move_ids = [m.id for m in picking.move_lines] - if need_backorder: - ctx = context.copy() - ctx.update({'backorder_of': picking.id}) - stock_move_obj.action_confirm(cr, uid, todo_move_ids, context=ctx) + #product not expected + move_id = self._create_unexpected_move(cr, uid, picking, product, qty, cost, quant=reserved_quant_id, context=context) + else: + partial_datas = { + 'product_qty': qty, + 'product_uom_id': op.product_uom_id.id, + 'cost': op.cost, + 'reserved_quant_id': [(4, reserved_quant_id)] + } + todo_move_ids += [stock_move_obj.do_partial(cr, uid, move_id, partial_datas, context=context)] if not only_split_lines and todo_move_ids: stock_move_obj.action_done(cr, uid, todo_move_ids, context=context) + #if there was a backorder, the picking to package may be different picking_to_package = stock_move_obj.browse(cr, uid, todo_move_ids[0], context=context).picking_id self.make_packaging(cr, uid, picking_to_package.id, todo_move_ids, context=context) @@ -1264,40 +1275,23 @@ class stock_move(osv.osv): context = {} # TODO: Put the move in the right picking according to group_id -> should be more elaborated (draft is nok) and picking should be confirmed pick_obj = self.pool.get("stock.picking") - sequence_obj = self.pool.get('ir.sequence') picks = pick_obj.search(cr, uid, [('group_id', '=', move.group_id.id), ('location_id', '=', move.location_id.id), ('location_dest_id', '=', move.location_dest_id.id), ('state', 'in', ['confirmed', 'waiting', 'draft'])], context=context) if picks: pick = picks[0] else: - if context.get('backorder_of'): - #copy the original picking - original_picking = pick_obj.browse(cr, uid, context.get('backorder_of'), context=context) - new_picking_name = original_picking.name - #TODO back_order_name is False currently => find why - back_order_name = sequence_obj.get_by_id(cr, uid, original_picking.picking_type_id.id, 'id') #TODO: Need to have sequence for every picking type - pick_obj.write(cr, uid, [original_picking.id], {'name': back_order_name}) - pick = pick_obj.copy(cr, uid, original_picking.id, {'name': new_picking_name, - 'move_lines': [], - 'state': 'draft' - }) - - pick_obj.message_post(cr, uid, original_picking.id, body=_("Back order %s has been created.") % (back_order_name), context=context) - unlink_operation_order = [(2, op.id) for op in original_picking.pack_operation_ids] - pick_obj.write(cr, uid, [original_picking.id], {'backorder_id': pick, 'pack_operation_ids': unlink_operation_order}, context=context) - else: - #a backorder picking doesn't exist yet, create a new one - values = {'origin': move.origin, - 'company_id': move.company_id and move.company_id.id or False, - 'move_type': 'one', - 'partner_id': move.partner_id and move.partner_id.id or False, - 'date_done': move.date_expected, - #'invoice_state': move.invoice_state - 'state': 'confirmed', - 'group_id': move.group_id and move.group_id.id or False, - 'picking_type_id': move.rule_id and move.rule_id.picking_type_id.id or False, - } - pick = pick_obj.create(cr, uid, values, context=context) + #a picking doesn't exist yet, create a new one + values = {'origin': move.origin, + 'company_id': move.company_id and move.company_id.id or False, + 'move_type': 'one', + 'partner_id': move.partner_id and move.partner_id.id or False, + 'date_done': move.date_expected, + #'invoice_state': move.invoice_state + 'state': 'confirmed', + 'group_id': move.group_id and move.group_id.id or False, + 'picking_type_id': move.rule_id and move.rule_id.picking_type_id.id or False, + } + pick = pick_obj.create(cr, uid, values, context=context) return pick def onchange_date(self, cr, uid, ids, date, date_expected, context=None): @@ -1402,15 +1396,15 @@ class stock_move(osv.osv): self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'}) return self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False}) - def _get_quants_from_pack(self, cr, uid, ids, context=None): - """ - Suppose for the moment we don't have any packaging - """ - res = {} - for move in self.browse(cr, uid, ids, context=context): - #Split according to pack wizard if necessary - res[move.id] = [x.id for x in move.reserved_quant_ids] - return res + #def _get_quants_from_pack(self, cr, uid, ids, context=None): + # """ + # Suppose for the moment we don't have any packaging + # """ + # res = {} + # for move in self.browse(cr, uid, ids, context=context): + # #Split according to pack wizard if necessary + # res[move.id] = [x.id for x in move.reserved_quant_ids] + # return res def action_done(self, cr, uid, ids, context=None): """ Makes the move done and if all moves are done, it will finish the picking. @@ -1426,7 +1420,10 @@ class stock_move(osv.osv): if todo: self.action_confirm(cr, uid, todo, context=context) + pickings = set() for move in self.browse(cr, uid, ids, context=context): + if move.picking_id: + pickings.add(move.picking_id.id) qty = move.product_uom_qty # for qty, location_id in move_id.prefered_location_ids: @@ -1449,6 +1446,12 @@ class stock_move(osv.osv): self.action_assign(cr, uid, [move.move_dest_id.id], context=context) self.write(cr, uid, ids, {'state': 'done', 'date': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context) + #if all moves of a picking aren't done, we create a backorder + for picking in picking_obj.browse(cr, uid, list(pickings), context=context): + if picking.state != 'done': + backorder_id = picking_obj._create_backorder(cr, uid, picking, context=context) + done_move_ids = [x.id for x in picking.move_lines if x.state == 'done'] + self.write(cr, uid, done_move_ids, {'picking_id': backorder_id}, context=context) return True def unlink(self, cr, uid, ids, context=None): @@ -1662,7 +1665,7 @@ class stock_move(osv.osv): # product_avail[product.id] += product_uom_qty # return res - def do_partial(self, cr, uid, move_id, partial_datas, move_group_id, context=None): + def do_partial(self, cr, uid, move_id, partial_datas, context=None): """ Partially (or not) moves a stock.move. @param partial_datas: Dictionary containing details of partial picking like partner_id, delivery_date, delivery @@ -1706,20 +1709,19 @@ class stock_move(osv.osv): defaults = { 'product_uom_qty': product_uom_qty, 'product_uos_qty': product_uom_qty, - 'picking_id': False, - 'group_id': move_group_id, 'state': 'assigned', 'move_dest_id': False, 'price_unit': partial_datas.get('cost', 0.0), + 'reserved_quant_ids': partial_datas.get('reserved_quant_ids', []) } new_move = self.copy(cr, uid, move.id, defaults) todo_move_id = new_move self.write(cr, uid, [move.id], {'product_uom_qty': move.product_qty - product_uom_qty, 'product_uos_qty': move.product_qty - product_uom_qty, 'lot_id': False, - 'tracking_id': False}) + 'tracking_id': False, + 'reserved_quant_ids': []}) else: - self.write(cr, uid, [move.id], {'product_uom_qty': move.product_uom_qty, 'product_uos_qty': move.product_uom_qty, 'group_id': move_group_id, 'picking_id': False}) todo_move_id = move.id #TODO LOT management @@ -2007,18 +2009,6 @@ class stock_package(osv.osv): 'datas': datas } - ## FP Note: why not just over ridding the copy method? - #def action_copy(self, cr, uid, ids, context=None): - # stock_operation_obj = self.pool.get('stock.pack.operation') - # #search all the operations of given package - # operation_ids = stock_operation_obj.search(cr, uid, [('result_package_id', 'in', ids)], context=context) - # #create a new empty stock.quant.package - # package_id = self.create(cr, uid, {}, context=context) - # new_ops = [] - # #copy all operation and set the newly created package as result_package_id - # for op in operation_ids: - # new_ops += [stock_operation_obj.copy(cr, uid, op, {'result_package_id': package_id, 'quant_ids': []}, context=context)] - def quants_get(self, cr, uid, package_record, context=None): ''' find all the quants in the given package (browse record) recursively''' res = [] @@ -2052,7 +2042,6 @@ class stock_pack_operation(osv.osv): 'result_package_id': fields.many2one('stock.quant.package', 'Container Package', help="If set, the operations are packed into this package", required=False, ondelete='cascade'), 'date': fields.datetime('Date', required=True), #'lot_id': fields.many2one('stock.production.lot', 'Serial Number', ondelete='CASCADE'), - 'move_id': fields.many2one('stock.move', "Move"), #'update_cost': fields.boolean('Need cost update'), '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'), @@ -2080,12 +2069,13 @@ class stock_pack_operation(osv.osv): :param key: tuple directly reusable in a domain context can receive a key 'current_package_id' with the package to consider for this operation - returns the update to do in stock.move one2many field of picking (adapt remaining quantities) and to the list of package in the classic one2many syntax + returns True + + previously: returns the update to do in stock.move one2many field of picking (adapt remaining quantities) and to the list of package in the classic one2many syntax (0, 0, { values }) link to a new record that needs to be created with the given values dictionary (1, ID, { values }) update the linked record with id = ID (write *values* on it) (2, ID) remove and delete the linked record with id = ID (calls unlink on ID, that will delete the object completely, and the link to it as well) ''' - stock_move_obj = self.pool.get('stock.move') quant_obj = self.pool.get('stock.quant') if context is None: context = {} @@ -2098,41 +2088,24 @@ class stock_pack_operation(osv.osv): operation_id = existing_operation_ids[0] qty = self.browse(cr, uid, operation_id, context=context).product_qty + 1 self.write(cr, uid, operation_id, {'product_qty': qty}, context=context) - todo_on_operations = [(1, operation_id, {'product_qty': qty})] else: #no existing operation found for the given key and picking => create a new one var_name, dummy, value = key uom_id = False - move_id = False if var_name == 'product_id': uom_id = self.pool.get('product.product').browse(cr, uid, value, context=context).uom_id.id - move_id = picking_obj._find_move_from_product(cr, uid, picking_id, value, context=context) elif var_name == 'quant_id': quant = quant_obj.browse(cr, uid, value, context=context) uom_id = quant.product_id.uom_id.id - move_id = picking_obj._find_move_from_product(cr, uid, picking_id, quant.product_id.id, context=context) - move_id = move_id and move_id[0] or False values = { 'picking_id': picking_id, var_name: value, 'product_qty': 1, 'product_uom_id': uom_id, - 'move_id': move_id } operation_id = self.create(cr, uid, values, context=context) values.update({'id': operation_id}) - todo_on_operations = [(0, 0, values)] - todo_on_moves = [] - product_ids = self._find_product_ids(cr, uid, operation_id, context=context) - for product_id in product_ids: - corresponding_move_ids = stock_move_obj.search(cr, uid, [('picking_id', '=', picking_id), ('product_id', '=', product_id)], context=context) - if corresponding_move_ids: - corresponding_move = stock_move_obj.browse(cr, uid, corresponding_move_ids[0], context=context) - todo_on_moves += [(1, corresponding_move.id, {'remaining_qty': corresponding_move.remaining_qty - 1})] - else: - #TODO: no move found for the given operation => decide what to do.. raise an error? - pass - return todo_on_moves, todo_on_operations + return True class stock_warehouse_orderpoint(osv.osv): """ diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index cba470c73df..e44e7b4edb2 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -587,7 +587,7 @@