diff --git a/move.py b/move.py index af3b78557c..ec32479395 100644 --- a/move.py +++ b/move.py @@ -38,8 +38,8 @@ class Move(OSV): planned_date = fields.Date("Planned Date", states=STATES,) effective_date = fields.Date("Effective Date", readonly=True) state = fields.Selection( - [('draft', 'Draft'),('done', 'Done'),('cancel', 'Cancel'), - ('waiting', 'Waiting')], 'State', select=True, readonly=True) + [('draft', 'Draft'), ('done', 'Done'), ('cancel', 'Cancel'), + ('waiting', 'Waiting'), ('assigned', 'Assigned')], 'State', select=True, readonly=True) def __init__(self): @@ -154,8 +154,8 @@ class Move(OSV): def set_state_done(self, cursor, user, ids, context=None): self.set_by_state( - cursor, user, ids, ['draft', 'waiting'], - {'state':'done', + cursor, user, ids, ['draft', 'waiting', 'assigned'], + {'state': 'done', 'effective_date': time.strftime('%Y-%m-%d %H:%M:%S')}) def set_state_draft(self, cursor, user, ids, context=None): @@ -165,22 +165,27 @@ class Move(OSV): def set_state_cancel(self, cursor, user, ids, context=None): self.set_by_state( - cursor, user, ids, ['done', 'waiting'], - {'state':'cancel',}) + cursor, user, ids, ['done', 'waiting', 'assigned'], + {'state': 'cancel',}) + def set_state_waiting(self, cursor, user, ids, context=None): self.set_by_state( cursor, user, ids, ['draft',], {'state':'waiting',}) + def set_state_assigned(self, cursor, user, ids, context=None): + self.set_by_state( + cursor, user, ids, ['draft', 'waiting'], + {'state': 'waiting',}) + def unlink(self, cursor, user, ids, context=None): move_ids = self.search( cursor, user, [('id', 'in', ids), ('state', 'in', ['done', 'waiting', 'cancel'])], context) - if move_ids: raise ExceptORM('UserError', 'You can only delete draft moves !') - return super(Move, self).unlink(cursor, user, ids, context=context) + Move() diff --git a/packing.py b/packing.py index ee8872ba63..a0aad12a14 100644 --- a/packing.py +++ b/packing.py @@ -102,27 +102,24 @@ class PackingIn(OSV): move_obj = self.pool.get('stock.move') for packing in self.browse(cursor, user, ids, context=context): product_balance = {} - for move in packing.incoming_moves: - key = (move.product.id, move.uom.id, move.to_location.id) + for move in packing.inventory_moves: + key = (move.product.id, move.uom.id, move.from_location.id) if key in product_balance: product_balance[key] += move.quantity else: product_balance[key] = move.quantity - for move in packing.inventory_moves: - key = (move.product.id, move.uom.id, move.from_location.id) - if key in product_balance: + for move in packing.incoming_moves: + key = (move.product.id, move.uom.id, move.to_location.id) + if key in product_balance and \ + product_balance[key] >= move.quantity: product_balance[key] -= move.quantity else: - product_balance[key] = -move.quantity - - for (product,uom,location),quantity in product_balance.iteritems(): - if quantity>0: values = { - 'product': product, - 'uom': uom, - 'quantity': quantity, - 'from_location':location, + 'product': move.product.id, + 'uom': move.uom.id, + 'quantity': move.quantity, + 'from_location': move.to_location.id, 'to_location': packing.warehouse.storage_location.id, 'inventory_packing_in': packing.id, 'state': 'waiting', @@ -141,20 +138,25 @@ class PackingOut(OSV): effective_date =fields.DateTime('Effective Date', readonly=True) planned_date = fields.DateTime('Planned Date', readonly=True) warehouse = fields.Many2One( - 'stock.location',"Warehouse", required=True, states=STATES, + 'stock.location', "Warehouse", required=True, + states={'readonly': "state != 'draft'",}, domain="[('type', '=', 'warehouse')]",) + customer_location = fields.Many2One( + 'stock.location', "Customer Location", required=True, + states={'readonly': "state != 'draft'",}, + domain="[('type', '=', 'customer')]",) outgoing_moves = fields.One2Many( 'stock.move', 'outgoing_packing_out', 'Outgoing Moves', - states=STATES, + states={'readonly':"state != 'ready'",}, context="{'warehouse': warehouse, 'packing_state': state, 'type':'outgoing',}") inventory_moves = fields.One2Many( 'stock.move', 'inventory_packing_out', 'Inventory Moves', - states={'readonly':"state not in ('assigned')",}, + states={'readonly':"state in ('ready', 'done')",}, context="{'warehouse': warehouse, 'packing_state': state, 'type':'inventory_out',}") code = fields.Char("Code", size=None, select=1, readonly=True,) state = fields.Selection( - [('draft','Draft'),('done','Done'),('cancel','Cancel'), - ('ready','Ready'), ('assigned','Assigned'),('waiting','Waiting')], + [('draft', 'Draft'), ('done', 'Done'), ('cancel', 'Cancel'), + ('assigned', 'Assigned'),('ready', 'Ready'), ('waiting', 'Waiting')], 'State', readonly=True) def __init__(self): @@ -174,13 +176,20 @@ class PackingOut(OSV): def default_state(self, cursor, user, context=None): return 'draft' + def set_state_assigned(self, cursor, user, packing_id, context=None): + self.write(cursor, user, packing_id, {'state':'assigned'}, + context=context) + def set_state_done(self, cursor, user, packing_id, context=None): move_obj = self.pool.get('stock.move') packing = self.browse(cursor, user, packing_id, context=context) move_obj.set_state_done( cursor, user, [m.id for m in packing.outgoing_moves], context=context) - self.write(cursor, user, packing_id, {'state':'done'}, context=context) + self.write( + cursor, user, packing_id, + {'state':'done', 'effective_date': time.strftime('%Y-%m-%d %H:%M:%S')}, + context=context) def set_state_ready(self, cursor, user, packing_id, context=None): move_obj = self.pool.get('stock.move') @@ -190,6 +199,16 @@ class PackingOut(OSV): context=context) self.write(cursor, user, packing_id, {'state':'ready'}, context=context) + for move in packing.inventory_moves: + move_obj.create(cursor, user, { + 'from_location': move.to_location.id, + 'to_location': packing.customer_location.id, + 'product': move.product.id, + 'uom': move.uom.id, + 'quantity': move.quantity, + 'outgoing_packing_out': packing.id, + 'state': 'waiting', + }, context=context) def set_state_cancel(self, cursor, user, packing_id, context=None): move_obj = self.pool.get('stock.move') @@ -205,7 +224,7 @@ class PackingOut(OSV): move_obj = self.pool.get('stock.move') packing = self.browse(cursor, user, packing_id, context=context) move_obj.set_state_waiting( - cursor, user, [m.id for m in packing.outgoing_moves], + cursor, user, [m.id for m in packing.inventory_moves], context=context) self.write(cursor, user, packing_id, {'state':'waiting'}, context=context) @@ -221,8 +240,6 @@ class PackingOut(OSV): context=context) self.write(cursor, user, packing_id, {'state':'draft'}, context=context) - def set_state_assigned(self, cursor, user, packing_id, context=None): - self.write(cursor, user, packing_id, {'state':'assigned'}, context=context) def create(self, cursor, user, values, context=None): values['code'] = self.pool.get('ir.sequence').get( @@ -237,129 +254,112 @@ class PackingOut(OSV): implementation. Product is a browse record and location_index is the index of the browse record of all the locations. """ - to_pick = {} - for location,qty in location_quantities.iteritems(): + to_pick = [] + for location,qty in location_quantities: if total_qty <= qty: - to_pick[location]= total_qty + to_pick.append((location, total_qty)) return to_pick else: - to_pick[location]= qty + to_pick.append((location, qty)) total_qty -= qty return False - def _location_quantities(self, cursor, user, target_product, target_uom, - raw_data, uom_index, context=None): + def _location_amount(self, cursor, user, target_uom, + qty_uom, uom_index, context=None): """ - Take a raw list of product by location by uom and convert it - to the target uom. + Take a raw list of quantities and uom and convert it to + the target uom. """ uom_obj = self.pool.get('product.uom') - res = {} - for line in raw_data: - location, product, uom, qty = line - if product != target_product: - return [] - if location not in res: - res[location] = 0 - res[location] += uom_obj.compute_qty( + res = 0 + for uom,qty in qty_uom: + res += uom_obj.compute_qty( cursor, user, uom_index[uom], qty, uom_index[target_uom]) return res + def assign_try(self, cursor, user, id, context=None): - """ - Try to assign products for a given customer packing. - """ location_obj = self.pool.get('stock.location') move_obj = self.pool.get('stock.move') - uom_obj = self.pool.get('product.uom') + product_obj = self.pool.get('product.product') packing = self.browse(cursor, user, id, context=context) - if not packing.outgoing_moves: - return False + parent_to_locations = {} + inventory_moves = [] + uom_index = {} + location_index = {} + # Fetch child_of for each location + for move in packing.inventory_moves: + if move.state != 'waiting': continue + inventory_moves.append(move) + uom_index[move.uom.id] = move.uom + location_index[move.from_location.id] = move.from_location + if move.from_location.id in parent_to_locations: + continue + childs = location_obj.search( + cursor, user, [('parent', 'child_of', [move.from_location.id])]) + parent_to_locations[move.from_location.id] = childs - # Remove potentialy existing inventory moves: - move_obj.unlink( - cursor, user, [m.id for m in packing.inventory_moves], + # Collect all raw quantities + context = context or {} + context.update({'in_states': ['done', 'assigned'], + 'out_states': ['done', 'assigned']}) + raw_data = product_obj.raw_products_by_location( + cursor, user, + location_ids=reduce( + lambda x,y:x+y, parent_to_locations.values(), []), + product_ids=[move.product.id for move in inventory_moves], context=context) - - uom_ids = uom_obj.search(cursor, user, [], context=context) - uom_index = dict((x.id, x) for x in uom_obj.browse( - cursor, user, uom_ids, context=context)) - product_ids = [m.product.id for m in packing.outgoing_moves] - location_index = dict([(l.id,l) for l in packing.warehouse.locations]) - - # Fetch location contents: - raw_results = location_obj.raw_products_by_location( - cursor, user, location_index.keys(), product_ids, context=context) - # XXX: Maybe index results by products to speed things - - moves_to_create = [] + # convert raw data to something like: + # {(location,product):[(qty,uom), ...],} + processed_data = {} + for line in raw_data: + if line[:2] in processed_data: + processed_data[line[:2]].append(line[2:]) + else: + processed_data[line[:2]] = [line[2:]] success = True - for move in packing.outgoing_moves: - # Process data for the given product and uom: - location_quantities = self._location_quantities( - cursor, user, move.product.id, move.uom.id, raw_results, - uom_index, context=context) - # Chose how to pick products: + for move in inventory_moves: + location_qties = [] + for location in parent_to_locations[move.from_location.id]: + qty = self._location_amount( + cursor, user, move.uom.id, + processed_data[(move.from_location.id, move.product.id)], + uom_index, context=context,) + location_qties.append((location, qty)) + to_pick = self.pick_product( - cursor, user, move.quantity, location_quantities, - product=move.product, location_index= location_index, context=context) + cursor, user, move.quantity, location_qties, + product=move.product, location_index=location_index, + context=context) + if to_pick == False: success = False - else: - for location, qty in to_pick.iteritems(): - # Update raw data: - raw_results.append((location, move.product.id, move.uom.id, -qty)) - # Remember what to create: - moves_to_create.append((location, move.product.id, move.uom.id, qty)) + continue - # Create moves: - for line in moves_to_create: - location, product, uom, qty = line - move_obj.create(cursor, user, { + first = True + for location, qty in to_pick: + values = { 'from_location': location, 'to_location': packing.warehouse.output_location.id, - 'product': product, - 'uom': uom, + 'product': move.product.id, + 'uom': move.uom.id, 'quantity': qty, 'inventory_packing_out': packing.id, - 'state': success and 'waiting' or 'draft', - }, context=context) + 'state': 'assigned', + } + if first: + move_obj.write(cursor, user, move.id, values, + context=context) + else: + move_obj.create(cursor, user, values, context=context) + processed_data[(location, move.product.id)].append((move.uom.id, -qty)) + return success def assign_force(self, cursor, user, id, context=None): - # Try to assign normally: - res = self.assign_try(cursor, user, id, context=context) - if res: return True packing = self.browse(cursor, user, id, context=context) move_obj = self.pool.get('stock.move') - # Find missing items: - missing_moves = {} - for move in packing.outgoing_moves: - product, uom = move.product, move.uom - if (product, uom) in missing_moves: - missing_moves[(product, uom)] += move.quantity - else: - missing_moves[(product, uom)] = move.quantity - - for move in packing.inventory_moves: - product, uom = move.product, move.uom - if (product, uom) in missing_moves: - missing_moves[(product, uom)] -= move.quantity - else: - raise "Unexpected Error" - # Create them - for (product,uom),quantity in missing_moves.iteritems(): - values = { - 'product': product, - 'uom': uom, - 'quantity': quantity, - 'from_location': packing.warehouse.output_location.id, - 'to_location': packing.warehouse.storage_location.id, - 'inventory_packing_out': packing.id, - 'state': 'waiting', - } - move_obj.create(cursor, user, values, context=context) - + move_obj.write(cursor, user, [m.id for m in packing.inventory_moves], {'state':'assigned'}) return True PackingOut() diff --git a/packing.xml b/packing.xml index fa60238d9d..8739341bd7 100644 --- a/packing.xml +++ b/packing.xml @@ -89,24 +89,26 @@ form +