diff --git a/product/ERP5/Tool/SimulationTool.py b/product/ERP5/Tool/SimulationTool.py index b661a927d89b7ef249784f5f764c84e9c7353b2a..91de1ffd31e72e6148eb36f43b1819f474433f03 100755 --- a/product/ERP5/Tool/SimulationTool.py +++ b/product/ERP5/Tool/SimulationTool.py @@ -40,6 +40,7 @@ from zLOG import LOG from Products.ERP5.Capacity.GLPK import solve from Numeric import zeros, resize +import cPickle # Solver Registration is_initialized = 0 @@ -1096,4 +1097,161 @@ class SimulationTool (Folder, UniqueObject): return result + security.declareProtected( Permissions.ModifyPortalContent, 'mergeDeliveryList' ) + def mergeDeliveryList(self, delivery_list): + """ + Merge multiple deliveries into one delivery. + All delivery lines are merged into the first one. + The first one is therefore called main_delivery here. + The others are cancelled. + Return the main delivery. + """ + class MergeDeliveryListError(Exception): pass + + # Sanity checks. + if len(delivery_list) == 0: + raise MergeDeliveryListError, "No delivery is passed" + elif len(delivery_list) == 1: + raise MergeDeliveryListError, "Only one delivery is passed" + + main_delivery = delivery_list[0] + delivery_list = delivery_list[1:] + + # One more sanity check. + for delivery in delivery_list: + for attr in ('portal_type', 'simulation_state', + 'source', 'destination', + 'source_section', 'destination_section', + 'source_decision', 'destination_decision', + 'source_administration', 'destination_administration', + 'source_payment', 'destination_payment'): + main_value = main_delivery.getProperty(attr) + value = delivery.getProperty(attr) + if main_value != value: + raise MergeDeliveryListError, \ + "In %s of %s, %s is different from %s" % (attr, delivery.getId(), value, main_value) + + # Make sure that all activities are flushed, to get simulation movements from delivery cells. + for delivery in delivery_list: + for order in delivery.getCausalityValueList(portal_type = order_type_list): + for applied_rule in order.getCausalityRelatedValueList(portal_type = 'Applied Rule'): + applied_rule.flushActivity(invoke = 1) + + # Get a list of movements. + movement_list = [] + for delivery in delivery_list: + movement_list.extend(delivery.getMovementList()) + + group_list = main_delivery.collectMovement(movement_list) + for group in group_list: + # First, try to find a delivery line which matches this group in the main delivery. + for delivery_line in main_delivery.contentValues(): + if group.resource_id == delivery_line.getResourceId() and \ + group.variation_base_category_list == delivery_line.getVariationBaseCategoryList(): + # Found. Update the list of variation categories. + delivery_line.setVariationCategoryList(group.variation_category_list) + break + else: + # Create a new delivery line. + delivery_line_type = None + for delivery in delivery_list: + for delivery_line in delivery.contentValues(): + delivery_line_type = delivery_line.getPortalType() + break + delivery_line = main_delivery.newContent(portal_type = delivery_line_type, + resource = group.resource) + delivery_line.setVariationBaseCategoryList(group.variation_base_category_list) + delivery_line.setVariationCategoryList(group.variation_category_list) + + # Make variant groups. Since Python cannot use a list as an index of a hash, + # use a picked object as an index instead. + variant_map = {} + for movement in group.movement_list: + variation_category_list = movement.getVariationCategoryList() + variation_category_list.sort() + pickled_variation_category_list = cPickle.dumps(variation_category_list) + if pickled_variation_category_list in variant_map: + variant_map[pickled_variation_category_list].append(movement) + else: + variant_map[pickled_variation_category_list] = [movement] + + for pickled_variation_category_list,movement_list in variant_map.items(): + variation_category_list = cPickle.loads(pickled_variation_category_list) + object_to_update = None + if len(variation_category_list) == 0: + object_to_update = delivery_line + else: + identical = 0 + for delivery_cell in delivery_line.contentValues(): + if len(delivery_cell.getVariationCategoryList()) == len(variation_category_list): + for category in delivery_cell.getVariationCategoryList(): + if category not in variation_category_list: + break + else: + identical = 1 + + if identical: + object_to_update = delivery_cell + + LOG("mergeDeliveryList", 0, "variation_category_list = %s, movement_list = %s, object_to_update = %s" % (repr(variation_category_list), repr(movement_list), repr(object_to_update))) + if object_to_update is not None: + cell_quantity = object_to_update.getQuantity() + cell_target_quantity = object_to_update.getNetConvertedTargetQuantity() + cell_total_price = cell_target_quantity * object_to_update.getPrice() + cell_category_list = list(object_to_update.getCategoryList()) + LOG("mergeDeliveryList", 0, "cell_target_quantity = %s, cell_quantity = %s, cell_total_price = %s, cell_category_list = %s" % (repr(cell_target_quantity), repr(cell_quantity), repr(cell_total_price), repr(cell_category_list))) + + for movement in movement_list : + cell_quantity += movement.getQuantity() + cell_target_quantity += movement.getNetConvertedTargetQuantity() + try: + # XXX WARNING - ADD PRICED QUANTITY + cell_total_price += movement.getNetConvertedTargetQuantity() * movement.getPrice() + except: + cell_total_price = None + for category in movement.getCategoryList(): + if category not in cell_category_list: + cell_category_list.append(category) + LOG("mergeDeliveryList", 0, "movement = %s, cell_target_quantity = %s, cell_quantity = %s, cell_total_price = %s, cell_category_list = %s" % (repr(movement), repr(cell_target_quantity), repr(cell_quantity), repr(cell_total_price), repr(cell_category_list))) + # Make sure that simulation movements point to an appropriate delivery line or + # delivery cell. + for simulation_movement in \ + movement.getDeliveryRelatedValueList(portal_type = 'Simulation Movement'): + simulation_movement.setDeliveryValue(object_to_update) + #simulation_movement.reindexObject() + + if cell_target_quantity != 0 and cell_total_price is not None: + average_price = cell_total_price / cell_target_quantity + else: + average_price = 0 + #LOG('object mis ?jour',0,str(object_to_update.getRelativeUrl())) + object_to_update.setCategoryList(cell_category_list) + object_to_update.edit(target_quantity = cell_target_quantity, + quantity = cell_quantity, + price = average_price, + ) + #object_to_update.reindexObject() + else: + raise MergeDeliveryListError, "No object to update" + + # Unify the list of causality. + causality_list = main_delivery.getCausalityValueList() + for delivery in delivery_list: + for causality in delivery.getCausalityValueList(): + if causality not in causality_list: + causality_list.append(causality) + LOG("mergeDeliveryList", 0, "causality_list = %s" % str(causality_list)) + main_delivery.setCausalityValueList(causality_list) + + # Cancel deliveries. + for delivery in delivery_list: + LOG("mergeDeliveryList", 0, "cancelling %s" % repr(delivery)) + delivery.cancel() + + # Reindex the main delivery. + main_delivery.reindexObject() + + return main_delivery + + InitializeClass(SimulationTool)