From 4a46417832eecda474654808c2b18b63bdcf2e9a Mon Sep 17 00:00:00 2001 From: Yoshinori Okuji <yo@nexedi.com> Date: Mon, 24 May 2004 16:37:46 +0000 Subject: [PATCH] Rewrite collectMovement to make it generic. Fix many bugs in mergeDeliveryList. git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@910 20353a03-c40f-0410-a6d1-a30d3c3de9de --- product/ERP5/Tool/SimulationTool.py | 367 ++++++++++++++++++---------- 1 file changed, 241 insertions(+), 126 deletions(-) diff --git a/product/ERP5/Tool/SimulationTool.py b/product/ERP5/Tool/SimulationTool.py index 42396d4454..2c1dd2aa04 100755 --- a/product/ERP5/Tool/SimulationTool.py +++ b/product/ERP5/Tool/SimulationTool.py @@ -32,7 +32,7 @@ from AccessControl import ClassSecurityInfo from Globals import InitializeClass, DTMLFile from Products.ERP5Type.Document.Folder import Folder from Products.ERP5Type import Permissions -from Products.ERP5.ERP5Globals import default_section_category, order_type_list, delivery_type_list, current_inventory_state_list +from Products.ERP5.ERP5Globals import default_section_category, order_type_list, delivery_type_list, current_inventory_state_list, discount_type_list, simulated_movement_type_list, container_type_list, payment_condition_type_list, invoice_movement_type_list from Products.ERP5 import _dtmldir @@ -40,7 +40,6 @@ from zLOG import LOG from Products.ERP5.Capacity.GLPK import solve from Numeric import zeros, resize -import cPickle # Solver Registration is_initialized = 0 @@ -210,18 +209,50 @@ class SimulationTool (Folder, UniqueObject): ####################################################### # Movement Group Collection / Delivery Creation - def collectMovement(self, movement_list): + def collectMovement(self, movement_list, + check_order = 1, check_path = 1, check_date = 1, check_criterion = 0, + check_resource = 1, check_base_variant = 0, check_variant = 1): class RootGroup: - def __init__(self,movement=None): + def getNestedClass(self, class_list): + for a in class_list: + if a[0]: + return a[1] + return None + + def setNestedClass(self): + """ + This sets an appropriate nested class. + """ + class_list = ((1, RootGroup), + (check_order, OrderGroup), + (check_path, PathGroup), + (check_date, DateGroup), + (check_criterion, CriterionGroup), + (check_resource, ResourceGroup), + (check_base_variant, BaseVariantGroup), + (check_variant, VariantGroup), + ) + for i in range(len(class_list)): + if class_list[i][1] == self.__class__: + break + else: + raise RuntimeError, "no appropriate nested class is found for %s" % str(self) + + self.nested_class = self.getNestedClass(class_list[i+1:]) + + def __init__(self, movement=None): + self.nested_class = None + self.setNestedClass() self.movement_list = [] self.group_list = [] if movement is not None : self.append(movement) def appendGroup(self, movement): - self.group_list.append(OrderGroup(movement)) + if self.nested_class is not None: + self.group_list.append(self.nested_class(movement)) def append(self,movement): self.movement_list.append(movement) @@ -258,9 +289,6 @@ class SimulationTool (Folder, UniqueObject): order_relative_url = order_value.getRelativeUrl() self.order = order_relative_url - def appendGroup(self, movement): - self.group_list.append(PathGroup(movement)) - def test(self,movement): if hasattr(movement, 'getRootAppliedRule'): order_value = movement.getRootAppliedRule().getCausalityValue( @@ -294,9 +322,6 @@ class SimulationTool (Folder, UniqueObject): self.source_section = movement.getSourceSection() self.destination_section = movement.getDestinationSection() - def appendGroup(self, movement): - self.group_list.append(DateGroup(movement)) - def test(self,movement): if movement.getSource() == self.source and \ movement.getDestination() == self.destination and \ @@ -315,9 +340,6 @@ class SimulationTool (Folder, UniqueObject): self.start_date = movement.getStartDate() self.stop_date = movement.getStopDate() - def appendGroup(self, movement): - self.group_list.append(ResourceGroup(movement)) - def test(self,movement): if movement.getStartDate() == self.start_date and \ movement.getStopDate() == self.stop_date : @@ -325,30 +347,58 @@ class SimulationTool (Folder, UniqueObject): else : return 0 + class CriterionGroup(RootGroup): + + def __init__(self,movement): + RootGroup.__init__(self,movement) + if hasattr(movement, 'getGroupCriterion'): + self.criterion = movement.getGroupCriterion() + else: + self.criterion = None + + def test(self,movement): + # we must have the same criterion + if hasattr(movement, 'getGroupCriterion'): + criterion = movement.getGroupCriterion() + else: + criterion = None + return self.criterion == criterion + class ResourceGroup(RootGroup): def __init__(self,movement): RootGroup.__init__(self,movement) self.resource = movement.getResource() - def appendGroup(self, movement): - self.group_list.append(VariantGroup(movement)) - def test(self,movement): if movement.getResource() == self.resource : return 1 else : return 0 + class BaseVariantGroup(RootGroup): + + def __init__(self,movement): + RootGroup.__init__(self,movement) + self.base_category_list = movement.getVariationBaseCategoryList() + + def test(self,movement): + # we must have the same number of categories + categories_identity = 0 + if len(self.base_category_list) == len(movement.getVariationBaseCategoryList()) : + for category in movement.getVariationBaseCategoryList() : + if not category in self.base_category_list : + break + else : + categories_identity = 1 + return categories_identity + class VariantGroup(RootGroup): def __init__(self,movement): RootGroup.__init__(self,movement) self.category_list = movement.getVariationCategoryList() - def appendGroup(self, movement): - pass - def test(self,movement): # we must have the same number of categories categories_identity = 0 @@ -1097,6 +1147,9 @@ class SimulationTool (Folder, UniqueObject): return result + # Used for mergeDeliveryList. + class MergeDeliveryListError(Exception): pass + security.declareProtected( Permissions.ModifyPortalContent, 'mergeDeliveryList' ) def mergeDeliveryList(self, delivery_list): """ @@ -1106,18 +1159,17 @@ class SimulationTool (Folder, UniqueObject): 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" + raise self.MergeDeliveryListError, "No delivery is passed" elif len(delivery_list) == 1: - raise MergeDeliveryListError, "Only one delivery is passed" + raise self.MergeDeliveryListError, "Only one delivery is passed" main_delivery = delivery_list[0] delivery_list = delivery_list[1:] - # One more sanity check. + # Another sanity check. It is necessary for them to be identical in some attributes. for delivery in delivery_list: for attr in ('portal_type', 'simulation_state', 'source', 'destination', @@ -1128,118 +1180,181 @@ class SimulationTool (Folder, UniqueObject): 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) + raise self.MergeDeliveryListError, \ + "%s is not the same between %s and %s (%s and %s)" % (attr, delivery.getId(), main_delivery.getId(), value, main_value) + + # One more sanity check. Check if discounts are the same, if any. + main_discount_list = main_delivery.contentValues(filter = {'portal_type': discount_type_list}) + for delivery in delivery_list: + discount_list = delivery.contentValues(filter = {'portal_type': discount_type_list}) + if len(main_discount_list) != len(discount_list): + raise self.MergeDeliveryListError, "Discount is not the same between %s and %s" % (delivery.getId(), main_delivery.getId()) + for discount in discount_list: + for main_discount in main_discount_list: + if discount.getDiscount() == main_discount.getDiscount() \ + and discount.getDiscountRatio() == main_discount.getDiscountRatio() \ + and discount.getDiscountType() == main_discount.getDiscountType() \ + and discount.getImmediateDiscount() == main_discount.getImmediateDiscount(): + break + else: + raise self.MergeDeliveryListError, "Discount is not the same between %s and %s" % (delivery.getId(), main_delivery.getId()) + + # One more sanity check. Check if payment conditions are the same, if any. + main_payment_condition_list = main_delivery.contentValues(filter = {'portal_type': payment_condition_type_list}) + for delivery in delivery_list: + payment_condition_list = delivery.contentValues(filter = {'portal_type': payment_condition_type_list}) + if len(main_payment_condition_list) != len(payment_condition_list): + raise self.MergeDeliveryListError, "Payment Condition is not the same between %s and %s" % (delivery.getId(), main_delivery.getId()) + for condition in payment_condition_list: + for main_condition in main_payment_condition_list: + if condition.getPaymentMode() == main_condition.getPaymentMode() \ + and condition.getPaymentAdditionalTerm() == main_condition.getPaymentAdditionalTerm() \ + and condition.getPaymentAmount() == main_condition.getPaymentAmount() \ + and condition.getPaymentEndOfMonth() == main_condition.getPaymentEndOfMonth() \ + and condition.getPaymentRatio() == main_condition.getPaymentRatio() \ + and condition.getPaymentTerm() == main_condition.getPaymentTerm(): + break + else: + raise self.MergeDeliveryListError, "Payment Condition is not the same between %s and %s" % (delivery.getId(), main_delivery.getId()) # 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) + for causality_related_delivery in delivery.getCausalityValueList(portal_type = delivery_type_list): + for applied_rule in causality_related_delivery.getCausalityRelatedValueList(portal_type = 'Applied Rule'): + applied_rule.flushActivity(invoke = 1) - # Get a list of movements. - movement_list = [] + # Get a list of simulated movements and invoice movements. + main_simulated_movement_list = main_delivery.getSimulatedMovementList() + main_invoice_movement_list = main_delivery.getInvoiceMovementList() + simulated_movement_list = main_simulated_movement_list[:] + invoice_movement_list = main_invoice_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: - predicate_list = movement.getPredicateValueList() - predicate_list.sort() - pickled_predicate_list = cPickle.dumps(predicate_list) - if pickled_predicate_list in variant_map: - variant_map[pickled_predicate_list].append(movement) - else: - variant_map[pickled_predicate_list] = [movement] + simulated_movement_list.extend(delivery.getSimulatedMovementList()) + invoice_movement_list.extend(delivery.getInvoiceMovementList()) + + LOG('mergeDeliveryList', 0, 'simulated_movement_list = %s, invoice_movement_list = %s' % (str(simulated_movement_list), str(invoice_movement_list))) + for main_movement_list, movement_list in \ + ((main_simulated_movement_list, simulated_movement_list), + (main_invoice_movement_list, invoice_movement_list)): + root_group = self.collectMovement(movement_list, + check_order = 0, + check_path = 0, + check_date = 0, + check_criterion = 1, + check_resource = 1, + check_base_variant = 1, + check_variant = 1) + for criterion_group in root_group.group_list: + for resource_group in criterion_group.group_list: + for base_variant_group in resource_group.group_list: + # Get a list of categories. + category_dict = {} + for variant_group in base_variant_group.group_list: + for category in variant_group.category_list: + category_dict[category] = 1 + category_list = category_dict.keys() + + # Try to find a delivery line. + delivery_line = None + for movement in base_variant_group.movement_list: + if movement in main_movement_list: + if movement.aq_parent.getPortalType() in simulated_movement_type_list \ + or movement.aq_parent.getPortalType() in invoice_movement_type_list: + delivery_line = movement.aq_parent + else: + delivery_line = movement + LOG('mergeDeliveryList', 0, 'delivery_line %s is found: criterion = %s, resource = %s, base_category_list = %s' % (repr(delivery_line), repr(criterion_group.criterion), repr(resource_group.resource), repr(base_variant_group.base_category_list))) + break - for pickled_predicate_list,movement_list in variant_map.items(): - predicate_list = cPickle.loads(pickled_predicate_list) - object_to_update = None - if len(predicate_list) == 0: - object_to_update = delivery_line - else: - identical = 0 - for delivery_cell in delivery_line.contentValues(): - if len(delivery_cell.getPredicateValueList()) == len(predicate_list): - for category in delivery_cell.getPredicateValueList(): - if category not in predicate_list: - break + if delivery_line is None: + # Not found. So create a new delivery line. + movement = base_variant_group.movement_list[0] + if movement.aq_parent.getPortalType() in simulated_movement_type_list \ + or movement.aq_parent.getPortalType() in invoice_movement_type_list: + delivery_line_type = movement.aq_parent.getPortalType() else: - identical = 1 - break + delivery_line_type = movement.getPortalType() + delivery_line = main_delivery.newContent(portal_type = delivery_line_type, + resource = resource_group.resource) + LOG('mergeDeliveryList', 0, 'New delivery_line %s is created: criterion = %s, resource = %s, base_category_list = %s' % (repr(delivery_line), repr(criterion_group.criterion), repr(resource_group.resource), repr(base_variant_group.base_category_list))) + + # Update the base categories and categories. + #LOG('mergeDeliveryList', 0, 'base_category_list = %s, category_list = %s' % (repr(base_category_list), repr(category_list))) + delivery_line.setVariationBaseCategoryList(base_variant_group.base_category_list) + delivery_line.setVariationCategoryList(category_list) + + for variant_group in base_variant_group.group_list: + if len(variant_group.category_list) == 0: + object_to_update = delivery_line + else: + for delivery_cell in delivery_line.contentValues(): + predicate_value_list = delivery_cell.getPredicateValueList() + if len(predicate_value_list) == len(variant_group.category_list): + for category in variant_group.category_list: + if category not in predicate_value_list: + break + else: + object_to_update = delivery_cell + break - if identical: - object_to_update = delivery_cell - - LOG("mergeDeliveryList", 0, "predicate_list = %s, movement_list = %s, object_to_update = %s" % (repr(predicate_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. - if hasattr(movement, 'getDeliveryRelatedValueList'): - for simulation_movement in \ - movement.getDeliveryRelatedValueList(portal_type = 'Simulation Movement'): - simulation_movement.setDeliveryValue(object_to_update) - #simulation_movement.reindexObject() - if hasattr(movement, 'getOrderRelatedValueList'): - for simulation_movement in \ - movement.getOrderRelatedValueList(portal_type = 'Simulation Movement'): - simulation_movement.setOrderValue(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" + LOG('mergeDeliveryList', 0, 'object_to_update = %s' % repr(object_to_update)) + if object_to_update is not None: + cell_quantity = object_to_update.getQuantity() or 0.0 + cell_target_quantity = object_to_update.getNetConvertedTargetQuantity() or 0.0 + cell_total_price = cell_target_quantity * (object_to_update.getPrice() or 0.0) + cell_category_list = list(object_to_update.getCategoryList()) + + for movement in variant_group.movement_list: + if movement in main_movement_list: + continue + LOG('mergeDeliveryList', 0, 'movement = %s' % repr(movement)) + 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) + # Make sure that simulation movements point to an appropriate delivery line or + # delivery cell. + if hasattr(movement, 'getDeliveryRelatedValueList'): + for simulation_movement in \ + movement.getDeliveryRelatedValueList(portal_type = 'Simulation Movement'): + simulation_movement.setDeliveryValue(object_to_update) + #simulation_movement.reindexObject() + if hasattr(movement, 'getOrderRelatedValueList'): + for simulation_movement in \ + movement.getOrderRelatedValueList(portal_type = 'Simulation Movement'): + simulation_movement.setOrderValue(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('mergeDeliveryList', 0, 'cell_category_list = %s' % repr(cell_category_list)) + 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 self.MergeDeliveryListError, "No object to update" + + # Merge containers. Just copy them from other deliveries into the main. + for delivery in delivery_list: + container_id_list = delivery.contentIds(filter = {'portal_type': container_type_list}) + if len(container_id_list) > 0: + copy_data = delivery.manage_copyObjects(ids = container_id_list) + new_id_list = main_delivery.manage_pasteObjects(copy_data) # Unify the list of causality. causality_list = main_delivery.getCausalityValueList() @@ -1261,4 +1376,4 @@ class SimulationTool (Folder, UniqueObject): return main_delivery -InitializeClass(SimulationTool) +InitializeClass(SimulationTool) \ No newline at end of file -- 2.30.9