diff --git a/product/ERP5/Document/OrderBuilder.py b/product/ERP5/Document/OrderBuilder.py index 0415d04a79e8277abfad7eafa2e1876418ee5e81..30453b079d5446f4e38064f080b26660ccb36427 100755 --- a/product/ERP5/Document/OrderBuilder.py +++ b/product/ERP5/Document/OrderBuilder.py @@ -27,15 +27,13 @@ ############################################################################## from AccessControl import ClassSecurityInfo -from Products.CMFCore.utils import getToolByName -from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface +from Products.ERP5Type import Permissions, PropertySheet from Products.ERP5Type.XMLObject import XMLObject from Products.ERP5.Document.Predicate import Predicate from Products.ERP5.Document.Amount import Amount -from Acquisition import aq_base, aq_parent, aq_inner, aq_acquire from Products.ERP5 import MovementGroup from Products.ERP5Type.Utils import convertToUpperCase - +from DateTime import DateTime from zLOG import LOG class OrderBuilder(XMLObject, Amount, Predicate): @@ -89,10 +87,10 @@ class OrderBuilder(XMLObject, Amount, Predicate): , PropertySheet.Comment , PropertySheet.DeliveryBuilder ) - + security.declareProtected(Permissions.ModifyPortalContent, 'build') - def build(self, applied_rule_uid=None, movement_relative_url_list=[], - delivery_relative_url_list=[]): + def build(self, applied_rule_uid=None, movement_relative_url_list=None, + delivery_relative_url_list=None): """ Build deliveries from a list of movements @@ -100,12 +98,19 @@ class OrderBuilder(XMLObject, Amount, Predicate): restrict selection to a given root Applied Rule caused by a single Order or to Simulation Movements related to a limited set of existing """ + # Parameter initialization + if movement_relative_url_list is None: + movement_relative_url_list = [] + if delivery_relative_url_list is None: + delivery_relative_url_list = [] + # Call a script before building + self.callBeforeBuildingScript() # Select if movement_relative_url_list == []: movement_list = self.searchMovementList( applied_rule_uid=applied_rule_uid) else: - movement_list = [self.restrictedTraverse(relative_url) for relative_url\ + movement_list = [self.restrictedTraverse(relative_url) for relative_url \ in movement_relative_url_list] # Collect root_group = self.collectMovement(movement_list) @@ -114,43 +119,96 @@ class OrderBuilder(XMLObject, Amount, Predicate): root_group, delivery_relative_url_list=delivery_relative_url_list, movement_list=movement_list) - # Call script on each delivery built - delivery_after_generation_script_id =\ - self.getDeliveryAfterGenerationScriptId() - if delivery_after_generation_script_id not in ["", None]: - for delivery in delivery_list: - getattr(delivery, delivery_after_generation_script_id)() + # Call a script after building + self.callAfterBuildingScript(delivery_list) + # XXX Returning the delivery list is probably not necessary return delivery_list - def searchMovementList(self, applied_rule_uid=None): + def callBeforeBuildingScript(self): """ - defines how to query all Simulation Movements which meet certain criteria - (including the above path path definition). + Call a script on the module, for example, to remove some + auto_planned Order. + This part can only be done with a script, because user may want + to keep existing auto_planned Order, and only update lines in + them. + No activities are used when deleting a object, so, current + implementation should be OK. + """ + delivery_module_before_building_script_id = \ + self.getDeliveryModuleBeforeBuildingScriptId() + if delivery_module_before_building_script_id not in ["", None]: + delivery_module = getattr(self, self.getDeliveryModule()) + getattr(delivery_module, delivery_module_before_building_script_id)() - First, select movement matching to criteria define on DeliveryBuilder - Then, call script simulation_select_method to restrict movement_list + def searchMovementList(self, applied_rule_uid=None): """ + Defines how to query all Simulation Movements which meet certain + criteria (including the above path path definition). + First, select movement matching to criteria define on + DeliveryBuilder. + Then, call script simulation_select_method to restrict + movement_list. + """ + from Products.ERP5Type.Document import newTempMovement movement_list = [] kw = {} - # We only search Simulation Movement - kw['portal_type'] = 'Simulation Movement' - # Search only child movement from this applied rule - if applied_rule_uid is not None: - kw['parent_uid'] = applied_rule_uid - # XXX Add profile query - # Add resource query - if self.resource_portal_type not in ('', None): - kw['resourceType'] = self.resource_portal_type - if self.simulation_select_method_id in ['', None]: - kw.update(self.portal_catalog.buildSQLQuery(**kw)) - movement_list = [x.getObject() for x in self.portal_catalog(**kw)] - else: - select_method = getattr(self, self.simulation_select_method_id) - movement_list = select_method(**kw) - # XXX Use buildSQLQuery will be better - movement_list = filter(lambda x: x.getDeliveryRelatedValueList()==[], - movement_list) - # XXX Add predicate test + for attribute, method in [('node_uid', 'getDestinationUid'), + ('section_uid', 'getDestinationSectionUid')]: + if getattr(self, method)() not in ("", None): + kw[attribute] = getattr(self, method)() + # We have to check the inventory for each stock movement date. + # Inventory can be negative in some date, and positive in futur !! + # This must be done by subclassing OrderBuilder with a new inventory + # algorithm. + sql_list = self.portal_simulation.getFutureInventoryList( + group_by_variation=1, + group_by_resource=1, + group_by_node=1, + group_by_section=0, + **kw) + id_count = 0 + for inventory_item in sql_list: + # XXX FIXME SQL return None inventory... + # It may be better to return always good values + if (inventory_item.inventory is not None): + dumb_movement = inventory_item.getObject() + # Create temporary movement + movement = newTempMovement(self.getPortalObject(), + str(id_count)) + id_count += 1 + movement.edit( + resource=inventory_item.resource_relative_url, + variation_category_list=dumb_movement.getVariationCategoryList(), + destination_value=self.getDestinationValue(), + destination_section_value=self.getDestinationSectionValue()) + # We can do other test on inventory here + # XXX It is better if it can be sql parameters + resource_portal_type = self.getResourcePortalType() + resource = movement.getResourceValue() + # FIXME: XXX Those properties are defined on a supply line !! + # min_flow, max_delay + min_flow = resource.getMinFlow(0) + if (resource.getPortalType() == resource_portal_type) and\ + (round(inventory_item.inventory, 5) < min_flow): + # FIXME XXX getNextNegativeInventoryDate must work + stop_date = DateTime()+10 +# stop_date = resource.getNextNegativeInventoryDate( +# variation_text=movement.getVariationText(), +# from_date=DateTime(), +# # node_category=node_category, +# # section_category=section_category) +# node_uid=self.getDestinationUid(), +# section_uid=self.getDestinationSectionUid()) + max_delay = resource.getMaxDelay(0) + movement.edit( + start_date=stop_date-max_delay, + stop_date=stop_date, + quantity=min_flow-inventory_item.inventory, + quantity_unit=resource.getQuantityUnit() + # XXX FIXME define on a supply line + # quantity_unit + ) + movement_list.append(movement) return movement_list def getCollectOrderList(self): @@ -186,15 +244,15 @@ class OrderBuilder(XMLObject, Amount, Predicate): my_root_group.append(movement) return my_root_group - def testObjectProperties(self, object, property_dict): + def testObjectProperties(self, instance, property_dict): """ - Test object properties. + Test instance properties. """ result = 1 for key in property_dict: getter_name = 'get%s' % convertToUpperCase(key) - if hasattr(object, getter_name): - value = getattr(object, getter_name)() + if hasattr(instance, getter_name): + value = getattr(instance, getter_name)() if value != property_dict[key]: result = 0 break @@ -203,21 +261,25 @@ class OrderBuilder(XMLObject, Amount, Predicate): break return result - def buildDeliveryList(self, movement_group, delivery_relative_url_list=[], + def buildDeliveryList(self, movement_group, delivery_relative_url_list=None, movement_list=None): """ Build deliveries from a list of movements """ + # Parameter initialization + if delivery_relative_url_list is None: + delivery_relative_url_list = [] # Module where we can create new deliveries delivery_module = getattr(self, self.getDeliveryModule()) - delivery_to_update_list = [self.restrictedTraverse(relative_url) for\ + delivery_to_update_list = [self.restrictedTraverse(relative_url) for \ relative_url in delivery_relative_url_list] # Deliveries we are trying to update delivery_select_method_id = self.getDeliverySelectMethodId() if delivery_select_method_id not in ["", None]: - to_update_delivery_sql_list = getattr(self, delivery_select_method_id)\ + to_update_delivery_sql_list = getattr(self, delivery_select_method_id) \ (movement_list=movement_list) - delivery_to_update_list.extend([x.getObject() for x\ + delivery_to_update_list.extend([sql_delivery.getObject() \ + for sql_delivery \ in to_update_delivery_sql_list]) delivery_list = self._deliveryGroupProcessing( delivery_module, @@ -229,10 +291,13 @@ class OrderBuilder(XMLObject, Amount, Predicate): def _deliveryGroupProcessing(self, delivery_module, movement_group, collect_order_list, property_dict, - delivery_to_update_list=[]): + delivery_to_update_list=None): """ Build empty delivery from a list of movement """ + # Parameter initialization + if delivery_to_update_list is None: + delivery_to_update_list = [] delivery_list = [] # Get current properties from current movement group # And fill property_dict @@ -294,12 +359,12 @@ class OrderBuilder(XMLObject, Amount, Predicate): # Test if we can update an existing line, or if we need to create a new # one delivery_line = None - update_existing_line=0 + update_existing_line = 0 for delivery_line_to_update in delivery.contentValues( filter={'portal_type':self.getDeliveryLinePortalType()}): if self.testObjectProperties(delivery_line_to_update, property_dict): delivery_line = delivery_line_to_update - update_existing_line=1 + update_existing_line = 1 break if delivery_line == None: # Create delivery line @@ -316,7 +381,8 @@ class OrderBuilder(XMLObject, Amount, Predicate): line_variation_category_list.extend( movement.getVariationCategoryList()) # erase double - line_variation_category_list = dict([(x, 1) for x in\ + line_variation_category_list = dict([(variation_category, 1) \ + for variation_category in \ line_variation_category_list]).keys() delivery_line.setVariationCategoryList(line_variation_category_list) # Then, create delivery movement (delivery cell or complete delivery @@ -358,7 +424,7 @@ class OrderBuilder(XMLObject, Amount, Predicate): base_id = 'movement' object_to_update = None # We need to initialize the cell - update_existing_movement=0 + update_existing_movement = 0 movement = movement_list[0] # decide if we create a cell or if we update the line # Decision can only be made with line matrix range: @@ -370,7 +436,7 @@ class OrderBuilder(XMLObject, Amount, Predicate): if self.testObjectProperties(delivery_line, property_dict): if update_existing_line == 1: # We update a initialized line - update_existing_movement=1 + update_existing_movement = 1 else: for cell_key in delivery_line.getCellKeyList(base_id=base_id): if delivery_line.hasCell(base_id=base_id, *cell_key): @@ -379,7 +445,7 @@ class OrderBuilder(XMLObject, Amount, Predicate): # We update a existing cell # delivery_ratio of new related movement to this cell # must be updated to 0. - update_existing_movement=1 + update_existing_movement = 1 object_to_update = cell break if object_to_update is None: @@ -387,7 +453,7 @@ class OrderBuilder(XMLObject, Amount, Predicate): cell_key = movement.getVariationCategoryList( omit_option_base_category=1) if not delivery_line.hasCell(base_id=base_id, *cell_key): - cell = delivery_line.newCell(base_id=base_id,\ + cell = delivery_line.newCell(base_id=base_id, \ portal_type=self.getDeliveryCellPortalType(), *cell_key) vcl = movement.getVariationCategoryList() cell._edit(category_list=vcl, @@ -398,7 +464,7 @@ class OrderBuilder(XMLObject, Amount, Predicate): getVariationBaseCategoryList()) object_to_update = cell else: - raise 'MatrixError', 'Cell: %s already exists on %s' %\ + raise 'MatrixError', 'Cell: %s already exists on %s' % \ (str(cell_key), str(delivery_line)) self._setDeliveryMovementProperties( object_to_update, movement, property_dict, @@ -429,3 +495,13 @@ class OrderBuilder(XMLObject, Amount, Predicate): delivery_movement._edit(**property_dict) #simulation_movement.setDeliveryRatio(1) simulation_movement.edit(delivery_ratio=1) + + def callAfterBuildingScript(self, delivery_list): + """ + Call script on each delivery built + """ + delivery_after_generation_script_id = \ + self.getDeliveryAfterGenerationScriptId() + if delivery_after_generation_script_id not in ["", None]: + for delivery in delivery_list: + getattr(delivery, delivery_after_generation_script_id)()