From 2b267bdcdd6fe30b152dbc87f8c86c2fd0a13254 Mon Sep 17 00:00:00 2001 From: Sebastien Robin <seb@nexedi.com> Date: Thu, 30 Sep 2004 08:35:31 +0000 Subject: [PATCH] it has been integrated git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@1806 20353a03-c40f-0410-a6d1-a30d3c3de9de --- .../ERP5/Tool/SimulationTool.py.gm_proposal | 1620 ----------------- 1 file changed, 1620 deletions(-) delete mode 100755 product/ERP5/Tool/SimulationTool.py.gm_proposal diff --git a/product/ERP5/Tool/SimulationTool.py.gm_proposal b/product/ERP5/Tool/SimulationTool.py.gm_proposal deleted file mode 100755 index 66bdcb2fcb..0000000000 --- a/product/ERP5/Tool/SimulationTool.py.gm_proposal +++ /dev/null @@ -1,1620 +0,0 @@ -############################################################################## -# -# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved. -# Jean-Paul Smets-Solanes <jp@nexedi.com> -# -# WARNING: This program as such is intended to be used by professional -# programmers who take the whole responsability of assessing all potential -# consequences resulting from its eventual inadequacies and bugs -# End users who are looking for a ready-to-use solution with commercial -# garantees and support are strongly adviced to contract a Free Software -# Service Company -# -# This program is Free Software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# -############################################################################## - -from Products.CMFCore.utils import UniqueObject - -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, discount_type_list, simulated_movement_type_list, container_type_list, payment_condition_type_list, invoice_movement_type_list - -from Products.ERP5 import _dtmldir - -from zLOG import LOG - -from Products.ERP5.Capacity.GLPK import solve -from Numeric import zeros, resize - -# Solver Registration -is_initialized = 0 -delivery_solver_dict = {} -delivery_solver_list = [] - -def registerDeliverySolver(solver): - global delivery_solver_list, delivery_solver_dict - #LOG('Register Solver', 0, str(solver.__name__)) - delivery_solver_list.append(solver) - delivery_solver_dict[solver.__name__] = solver - -target_solver_dict = {} -target_solver_list = [] - -def registerTargetSolver(solver): - global target_solver_list, target_solver_dict - #LOG('Register Solver', 0, str(solver.__name__)) - target_solver_list.append(solver) - target_solver_dict[solver.__name__] = solver - -class Target: - - def __init__(self, **kw): - """ - Defines a target (target_quantity, start_date, stop_date) - """ - self.__dict__.update(kw) - -class SimulationTool (Folder, UniqueObject): - """ - The SimulationTool implements the ERP5 - simulation algorithmics. - - - Examples of applications: - - - - - - - ERP5 main purpose: - - - - - - - - """ - id = 'portal_simulation' - meta_type = 'ERP5 Simulation Tool' - portal_type = 'Simulation Tool' - allowed_types = ( 'ERP5 Applied Rule', ) - - # Declarative Security - security = ClassSecurityInfo() - - # - # ZMI methods - # - manage_options = ( ( { 'label' : 'Overview' - , 'action' : 'manage_overview' - } - , - ) - + Folder.manage_options - ) - - security.declareProtected( Permissions.ManagePortal, 'manage_overview' ) - manage_overview = DTMLFile( 'explainSimulationTool', _dtmldir ) - - # Filter content (ZMI)) - def __init__(self): - return Folder.__init__(self, SimulationTool.id) - - # Filter content (ZMI)) - def filtered_meta_types(self, user=None): - # Filters the list of available meta types. - all = SimulationTool.inheritedAttribute('filtered_meta_types')(self) - meta_types = [] - for meta_type in self.all_meta_types(): - if meta_type['name'] in self.allowed_types: - meta_types.append(meta_type) - return meta_types - - def initialize(self): - """ - Update values of simulation movements based on delivery - target values and solver - """ - from Products.ERP5.TargetSolver import Reduce, Defer, SplitAndDefer, CopyToTarget - from Products.ERP5.DeliverySolver import Distribute, Copy - - def isInitialized(self): - global is_initialized - return is_initialized - - def newDeliverySolver(self, solver_id, *args, **kw): - """ - Returns a solver instance - """ - if not self.isInitialized(): self.initialize() - solver = delivery_solver_dict[solver_id](self, *args, **kw) - return solver - - def applyDeliverySolver(self, movement, solver): - """ - Update values of simulation movements based on delivery - target values and solver. - - movement -- a delivery line or cell - - solver -- a delivery solver - """ - if not self.isInitialized(): self.initialize() - solver.solve(movement) - - def newTargetSolver(self, solver_id, *args, **kw): - """ - Returns a solver instance - """ - if not self.isInitialized(): self.initialize() - solver = target_solver_dict[solver_id](self, *args, **kw) - return solver - - def applyTargetSolver(self, movement, solver, new_target=None): - """ - Update upper targets based on new targets - - movement -- a simulation movement - - solver -- a target solver - - new_target-- new target values for that movement - """ - if new_target is None: - # Default behaviour is to solve target based on - # target defined by Delivery - # it must be overriden in recursive upward update - # to make sure - new_target = Target(target_quantity = movement.getQuantity(), - target_start_date = movement.getStartDate(), - target_stop_date = movement.getStopDate()) - if not self.isInitialized(): self.initialize() - solver.solve(movement, new_target) - - def closeTargetSolver(self, solver): - return solver.close() - - def showTargetSolver(self, solver): - #LOG("SimulationTool",0,"in showTargetSolver") - return str(solver.__dict__) - - - ####################################################### - # Stock Management - security.declareProtected(Permissions.AccessContentsInformation, 'getInventory') - def getInventory(self, resource_uid=None, at_date = None, section = None, node = None, - node_category=None, section_category=default_section_category, simulation_state=None, - ignore_variation=0, **kw): - result = self.Resource_zGetInventory(resource_uid = resource_uid, - to_date=at_date, - section=section, node=node, - node_category=node_category, - section_category=section_category) - if len(result) > 0: - return result[0].inventory - return 0.0 - - ####################################################### - # Movement Group Collection / Delivery Creation - 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): - current_order = 2 - check_list = [check_order, check_path, check_date, check_criterion, check_resource, check_base_variant, check_variant] - for i in range(len(check_list)): - if check_list[i]: - check_list[i] = current_order - current_order += 1 - check_order = check_list[0] - check_path = check_list[1] - check_date = check_list[2] - check_criterion = check_list[3] - check_resource = check_list[4] - check_base_variant = check_list[5] - check_variant = check_list[6] - - return orderedCollectMovement(movement_list=movement_list, - check_order=check_order, - check_path=check_path, - check_date=check_date, - check_criterion=check_criterion, - check_resource=check_resource, - check_base_variant=check_base_variant, - check_variant=check_variant) - - - def orderedCollectMovement(self, movement_list, - check_order = 2, check_path = 3, check_date = 4, check_criterion = 0, - check_resource = 5, check_base_variant = 0, check_variant = 6): - LOG('orderedCollectMovement :', 0, 'movement_list = %s' % repr(movement_list)) - - class RootGroup: - - 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. - """ - def cmpfunc(a,b): - return cmp(a[0],b[0]) - - 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), - ] - class_list.sort(cmpfunc) - 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): - if self.nested_class is not None: - self.group_list.append(self.nested_class(movement)) - - def append(self,movement): - self.movement_list.append(movement) - movement_in_group = 0 - for group in self.group_list : - if group.test(movement) : - group.append(movement) - movement_in_group = 1 - break - if movement_in_group == 0 : - self.appendGroup(movement) - - class OrderGroup(RootGroup): - - def __init__(self,movement): - RootGroup.__init__(self,movement) - if hasattr(movement, 'getRootAppliedRule'): - # This is a simulation movement - order_value = movement.getRootAppliedRule().getCausalityValue( - portal_type=order_type_list) - if order_value is None: - # In some cases (ex. DeliveryRule), there is no order - # we may consider a PackingList as the order in the OrderGroup - order_value = movement.getRootAppliedRule().getCausalityValue( - portal_type=delivery_type_list) - else: - # This is a temp movement - order_value = None - if order_value is None: - order_relative_url = None - else: - # get the id of the enclosing delivery - # for this cell or line - order_relative_url = order_value.getRelativeUrl() - self.order = order_relative_url - - def test(self,movement): - if hasattr(movement, 'getRootAppliedRule'): - order_value = movement.getRootAppliedRule().getCausalityValue( - portal_type=order_type_list) - - if order_value is None: - # In some cases (ex. DeliveryRule), there is no order - # we may consider a PackingList as the order in the OrderGroup - order_value = movement.getRootAppliedRule().getCausalityValue( - portal_type=delivery_type_list) - else: - # This is a temp movement - order_value = None - if order_value is None: - order_relative_url = None - else: - # get the id of the enclosing delivery - # for this cell or line - order_relative_url = order_value.getRelativeUrl() - if order_relative_url == self.order: - return 1 - else : - return 0 - - class PathGroup(RootGroup): - - def __init__(self,movement): - RootGroup.__init__(self,movement) - self.source = movement.getSource() - self.destination = movement.getDestination() - self.source_section = movement.getSourceSection() - self.destination_section = movement.getDestinationSection() - - def test(self,movement): - if movement.getSource() == self.source and \ - movement.getDestination() == self.destination and \ - movement.getSourceSection() == self.source_section and \ - movement.getDestinationSection() == self.destination_section : - return 1 - else : - return 0 - - class DateGroup(RootGroup): - - def __init__(self,movement): - RootGroup.__init__(self,movement) - self.target_start_date = movement.getTargetStartDate() - self.target_stop_date = movement.getTargetStopDate() - self.start_date = movement.getStartDate() - self.stop_date = movement.getStopDate() - - def test(self,movement): - if movement.getStartDate() == self.start_date and \ - movement.getStopDate() == self.stop_date : - return 1 - 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 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() - if self.base_category_list is None: - LOG('BaseVariantGroup __init__', 0, 'movement = %s, movement.showDict() = %s' % (repr(movement), repr(movement.showDict()))) - self.base_category_list = [] - - def test(self,movement): - # we must have the same number of categories - categories_identity = 0 - #LOG('BaseVariantGroup', 0, 'self.base_category_list = %s, movement = %s, movement.getVariationBaseCategoryList() = %s' % (repr(self.base_category_list), repr(movement), repr(movement.getVariationBaseCategoryList()))) - movement_base_category_list = movement.getVariationBaseCategoryList() - if movement_base_category_list is None: - LOG('BaseVariantGroup test', 0, 'movement = %s, movement.showDict() = %s' % (repr(movement), repr(movement.showDict()))) - movement_base_category_list = [] - if len(self.base_category_list) == len(movement_base_category_list): - for category in movement_base_category_list: - 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() - if self.category_list is None: - LOG('VariantGroup __init__', 0, 'movement = %s, movement.showDict() = %s' % (repr(movement), repr(movement.showDict()))) - self.category_list = [] - - def test(self,movement): - # we must have the same number of categories - categories_identity = 0 - movement_category_list = movement.getVariationCategoryList() - if movement_category_list is None: - LOG('VariantGroup test', 0, 'movement = %s, movement.showDict() = %s' % (repr(movement), repr(movement.showDict()))) - movement_category_list = [] - if len(self.category_list) == len(movement_category_list): - for category in movement_category_list: - if not category in self.category_list : - break - else : - categories_identity = 1 - return categories_identity - - my_root_group = RootGroup() - for movement in movement_list : - if not movement in my_root_group.movement_list : - my_root_group.append(movement) - - return my_root_group - - def buildOrderList(self, movement_group): - # Build orders from a list of movements (attached to orders) - order_list = [] - - if movement_group is not None: - for order_group in movement_group.group_list: - if order_group.order is None: - # Only build if there is not order yet - for path_group in order_group.group_list : - if path_group.destination.find('site/Stock_PF') >=0 : - # Build a Production Order - delivery_module = self.ordre_fabrication - delivery_type = 'Production Order' - delivery_line_type = delivery_type + ' Line' - delivery_cell_type = 'Delivery Cell' - else: - # Build a Purchase Order - delivery_module = self.commande_achat - delivery_type = 'Purchase Order' - delivery_line_type = delivery_type + ' Line' - delivery_cell_type = 'Delivery Cell' - # we create a new delivery for each DateGroup - for date_group in path_group.group_list : - - for resource_group in date_group.group_list : - - # Create a new production Order for each resource (Modele) - modele_url_items = resource_group.resource.split('/') - modele_id = modele_url_items[len(modele_url_items)-1] - try : - modele_object = self.getPortalObject().modele[modele_id] - except : - modele_object = None - if modele_object is not None : - of_description = modele_id + ' ' + modele_object.getDefaultDestinationTitle('') - else : - of_description = modele_id - - new_delivery_id = str(delivery_module.generateNewId()) - self.portal_types.constructContent(type_name = delivery_type, - container = delivery_module, - id = new_delivery_id, - target_start_date = date_group.start_date, - target_stop_date = date_group.stop_date, - start_date = date_group.start_date, - stop_date = date_group.stop_date, - source = path_group.source, - destination = path_group.destination, - source_section = path_group.source_section, - destination_section = path_group.destination_section, - description = of_description, - title = new_delivery_id - ) - delivery = delivery_module[new_delivery_id] - # the new delivery is added to the order_list - order_list.append(delivery) - - # Create each delivery_line in the new delivery - - new_delivery_line_id = str(delivery.generateNewId()) - self.portal_types.constructContent(type_name = delivery_line_type, - container = delivery, - id = new_delivery_line_id, - resource = resource_group.resource, - ) - delivery_line = delivery[new_delivery_line_id] - - line_variation_category_list = [] - line_variation_base_category_dict = {} - - # compute line_variation_base_category_list and - # line_variation_category_list for new delivery_line - for variant_group in resource_group.group_list : - for variation_item in variant_group.category_list : - if not variation_item in line_variation_category_list : - line_variation_category_list.append(variation_item) - variation_base_category_items = variation_item.split('/') - if len(variation_base_category_items) > 0 : - line_variation_base_category_dict[variation_base_category_items[0]] = 1 - - # update variation_base_category_list and line_variation_category_list for delivery_line - line_variation_base_category_list = line_variation_base_category_dict.keys() - delivery_line.setVariationBaseCategoryList(line_variation_base_category_list) - delivery_line.setVariationCategoryList(line_variation_category_list) - - # IMPORTANT : delivery cells are automatically created during setVariationCategoryList - - # update target_quantity for each delivery_cell - for variant_group in resource_group.group_list : - #LOG('Variant_group examin',0,str(variant_group.category_list)) - object_to_update = None - # if there is no variation of the resource, update delivery_line with quantities and price - if len(variant_group.category_list) == 0 : - object_to_update = delivery_line - # else find which delivery_cell is represented by variant_group - else : - categories_identity = 0 - #LOG('Before Check cell',0,str(delivery_cell_type)) - #LOG('Before Check cell',0,str(delivery_line.contentValues())) - for delivery_cell in delivery_line.contentValues(filter={'portal_type':'Delivery Cell'}) : - #LOG('Check cell',0,str(delivery_cell)) - #LOG('Check cell',0,str(variant_group.category_list)) - #LOG('Check cell',0,str(delivery_cell.getVariationCategoryList())) - if len(variant_group.category_list) == len(delivery_cell.getVariationCategoryList()) : - #LOG('Parse category',0,str(delivery_cell.getVariationCategoryList())) - for category in delivery_cell.getVariationCategoryList() : - if not category in variant_group.category_list : - #LOG('Not found category',0,str(category)) - break - else : - categories_identity = 1 - - if categories_identity : - object_to_update = delivery_cell - break - - # compute target_quantity, quantity and price for delivery_cell or delivery_line and - # build relation between simulation_movement and delivery_cell or delivery_line - if object_to_update is not None : - cell_target_quantity = 0 - for movement in variant_group.movement_list : - cell_target_quantity += movement.getConvertedTargetQuantity() - # We do not create a relation or modifu anything - # since planification of this movement will create new applied rule - object_to_update.edit(target_quantity = cell_target_quantity, - quantity = cell_target_quantity, - force_update = 1) - - return order_list - - - - - - def buildDeliveryList(self, movement_group): - # Build deliveries from a list of movements - - - def orderGroupProcessing(order_group, delivery_list, reindexable_movement_list, **kw): - - # Order should never be None - LOG("buildDeliveryList", 0, str(order_group.__dict__)) - if order_group.order is not None: - order = self.portal_categories.resolveCategory(order_group.order) - if order is not None: - # define some variables - LOG("order", 0, str(order.__dict__)) - if order.getPortalType() == 'Purchase Order' : - delivery_module = order.getPortalObject().livraison_achat - delivery_type = 'Purchase Packing List' - delivery_line_type = delivery_type + ' Line' - delivery_cell_type = 'Delivery Cell' - else : - delivery_module = order.getPortalObject().livraison_vente - delivery_type = 'Sales Packing List' - delivery_line_type = delivery_type + ' Line' - delivery_cell_type = 'Delivery Cell' - else : # should never be none - LOG("order is None", 0, str(order.__dict__)) - return -1 - else: # order is None - order = None - # possible when we build deliveries for tranfer of property - delivery_module = self.getPortalObject().livraison_vente - delivery_type = 'Sales Packing List' - delivery_line_type = delivery_type + ' Line' - delivery_cell_type = 'Delivery Cell' - - for path_group in order_group.group_list: - pathGroupProcessing(path_group=path_group, - delivery_module=delivery_module, - delivery_type=delivery_type, - delivery_line_type=delivery_line_type, - delivery_cell_type=delivery_cell_type, - order=order, - delivery_list=delivery_list, - reindexable_movement_list=reindexable_movement_list, **kw) - - return 0 - - - def pathGroupProcessing(path_group, delivery_module, delivery_type, delivery_line_type, delivery_cell_type, order, delivery_list, reindexable_movement_list, default_rule_id=None, **kw): - # we create a new delivery for each DateGroup - - - if default_rule_id is 'default_amortisation_rule': - pass - else: - # if path is internal ??? - # JPS NEW - if path_group.source is None or path_group.destination is None: - # Production Path - LOG("Builder",0, "Strange Path %s " % path_group.source) - LOG("Builder",0, "Strange Path %s " % path_group.destination) - - if path_group.source is None or path_group.destination is None: - delivery_module = self.rapport_fabrication - delivery_type = 'Production Report' - delivery_line_type = 'Production Report Line' - delivery_cell_type = 'Production Report Cell' - elif path_group.destination.find('site/Stock_PF') >= 0 and \ - path_group.source.find('site/Piquage') >= 0: - delivery_module = self.livraison_fabrication - delivery_type = 'Production Packing List' - delivery_line_type = delivery_type + ' Line' - delivery_cell_type = 'Delivery Cell' - elif path_group.source.find('site/Stock_MP') >= 0 and \ - path_group.destination.find('site/Piquage') >= 0: - delivery_module = self.livraison_fabrication - delivery_type = 'Production Packing List' - delivery_line_type = delivery_type + ' Line' - delivery_cell_type = 'Delivery Cell' - - for date_group in path_group.group_list : - dateGroupProcessing(date_group=date_group, - path_group=path_group, - delivery_module=delivery_module, - delivery_type=delivery_type, - delivery_line_type=delivery_line_type, - delivery_cell_type=delivery_cell_type, - order=order, - delivery_list=delivery_list, - reindexable_movement_list=reindexable_movement_list, - default_rule_id=default_rule_id, **kw) - - - def dateGroupProcessing(date_group, path_group, delivery_module, delivery_type, delivery_line_type, delivery_cell_type, order, delivery_list, reindexable_movement_list, default_rule_id=None, resource=None, **kw): - - if default_rule_id == 'default_amortisation_rule': - accounting_transaction_data_list = {} - - for path_group in date_group.group_list: - source_section = path_group.source_section - destination_section = path_group.destination_section - source = path_group.source - destination = path_group.destination - - accounting_transaction_data = accounting_transaction_data_list.get( (source_section, destination_section), None) - if accounting_transaction_data is None: - accounting_transaction_data_list[ (source_section, destination_section) ] = {} - accounting_transaction_data = accounting_transaction_data_list.get( (source_section, destination_section), None) - quantity = 0 - source_movement_list = [] - - for movement in path_group.movement_list: - if movement.getDeliveryValue() is None: - quantity += movement.getQuantity() - source_movement_list.append(movement) - LOG('buildDeliveryList :', 0, 'adding movement %s : quantity = %s' % (repr(movement), repr(movement.getQuantity()))) - else: - LOG('buildDeliveryList :', 0, 'movement %s... delivery value = %s' % (repr(movement), repr(movement.getDeliveryValue()))) - - accounting_transaction_data[ (source, destination) ] = (quantity, source_movement_list) - if len(source_movement_list) == 0: - LOG('buildDeliveryList :', 0, 'deleting transaction line because no source movement list... path_group.movement_list = %s' % repr(path_group.movement_list)) - del accounting_transaction_data[ (source, destination) ] - - for (source_section, destination_section), accounting_transaction_data in accounting_transaction_data_list.items(): - if len(accounting_transaction_data.items()) > 0: - new_delivery_id = str(delivery_module.generateNewId()) - self.portal_types.constructContent(type_name = delivery_type, - container = delivery_module, - id = new_delivery_id, - target_start_date = date_group.start_date, - target_stop_date = date_group.stop_date, - start_date = date_group.start_date, - stop_date = date_group.stop_date, - source_section = source_section, - destination_section = destination_section - ) - accounting_transaction = delivery_module[new_delivery_id] - accounting_transaction.setResource(resource) - for (source, destination), (quantity, source_movement_list) in accounting_transaction_data.items(): - new_transaction_line_id = str(accounting_transaction.generateNewId()) - self.portal_types.constructContent(type_name = delivery_line_type, - container = accounting_transaction, - id = new_transaction_line_id, - source = source, - destination = destination) - accounting_transaction_line = accounting_transaction[new_transaction_line_id] - accounting_transaction_line.setQuantity(quantity) - LOG('buildDeliveryList :', 0, 'setting resource for line %s... resource = %s' % (repr(accounting_transaction_line), repr(resource))) - accounting_transaction_line.setResource(resource) - for movement in source_movement_list: - LOG('buildDeliveryList :', 0, 'setting delivery value... movement = %s, accounting_transaction_line = %s' % (repr(movement), repr(accounting_transaction_line))) - movement.setDeliveryValue(accounting_transaction_line) - LOG('buildDeliveryList :', 0, 'after setting it, movement.delivery_value = %s' % repr(movement.getDeliveryValue())) - movement.recursiveImmediateReindexObject() - - else: - # Create a new packing list - new_delivery_id = str(delivery_module.generateNewId()) - self.portal_types.constructContent(type_name = delivery_type, - container = delivery_module, - id = new_delivery_id, - target_start_date = date_group.start_date, - target_stop_date = date_group.stop_date, - start_date = date_group.start_date, - stop_date = date_group.stop_date, - source = path_group.source, - destination = path_group.destination, - source_section = path_group.source_section, - destination_section = path_group.destination_section - ) - delivery = delivery_module[new_delivery_id] - if order is not None : - delivery.edit(title = order.getTitle(), - causality_value = order, - incoterm = order.getIncoterm(), - delivery_mode = order.getDeliveryMode() - ) - # the new delivery is added to the delivery_list - delivery_list.append(delivery) - # LOG('Livraison cr้้e',0,str(delivery.getId())) - - # Create each delivery_line in the new delivery - - for resource_group in date_group.group_list : - resourceGroupProcessing(resource_group=resource_group, - delivery=delivery, - delivery_type=delivery_type, - delivery_line_type=delivery_line_type, - delivery_cell_type=delivery_cell_type, - delivery_list=delivery_list, - reindexable_movement_list=reindexable_movement_list, **kw) - - - def resourceGroupProcessing(resource_group, delivery, delivery_type, delivery_line_type, delivery_cell_type, delivery_list, reindexable_movement_list, delivery_module=None, default_rule_id=None, **kw): - - if default_rule_id == 'default_amortisation_rule': - resource = resource_group.resource - for date_group in resource_group.group_list: - dateGroupProcessing(date_group=date_group, - path_group=None, - delivery_module=delivery_module, - delivery_type=delivery_type, - delivery_line_type=delivery_line_type, - delivery_cell_type=delivery_cell_type, - order=None, - delivery_list=delivery_list, - reindexable_movement_list=reindexable_movement_list, - default_rule_id=default_rule_id, - resource=resource) - else: - - if delivery_type == 'Production Report': - if resource_group.resource.find('operation') == 0: - delivery_line_type = 'Production Report Operation' - else: - delivery_line_type = 'Production Report Component' - - new_delivery_line_id = str(delivery.generateNewId()) - self.portal_types.constructContent(type_name = delivery_line_type, - container = delivery, - id = new_delivery_line_id, - resource = resource_group.resource, - ) - delivery_line = delivery[new_delivery_line_id] - - line_variation_category_list = [] - line_variation_base_category_dict = {} - - # compute line_variation_base_category_list and - # line_variation_category_list for new delivery_line - for variant_group in resource_group.group_list : - for variation_item in variant_group.category_list : - if not variation_item in line_variation_category_list : - line_variation_category_list.append(variation_item) - variation_base_category_items = variation_item.split('/') - if len(variation_base_category_items) > 0 : - line_variation_base_category_dict[variation_base_category_items[0]] = 1 - - # update variation_base_category_list and line_variation_category_list for delivery_line - line_variation_base_category_list = line_variation_base_category_dict.keys() - delivery_line._setVariationBaseCategoryList(line_variation_base_category_list) - delivery_line.setVariationCategoryList(line_variation_category_list) - - # IMPORTANT : delivery cells are automatically created during setVariationCategoryList - - # update target_quantity for each delivery_cell - for variant_group in resource_group.group_list : - #LOG('Variant_group examin?,0,str(variant_group.category_list)) - object_to_update = None - # if there is no variation of the resource, update delivery_line with quantities and price - if len(variant_group.category_list) == 0 : - object_to_update = delivery_line - # else find which delivery_cell is represented by variant_group - else : - categories_identity = 0 - #LOG('Before Check cell',0,str(delivery_cell_type)) - #LOG('Before Check cell',0,str(delivery_line.contentValues())) - for delivery_cell in delivery_line.contentValues( - filter={'portal_type':delivery_cell_type}) : - #LOG('Check cell',0,str(delivery_cell)) - if len(variant_group.category_list) == len(delivery_cell.getVariationCategoryList()) : - #LOG('Parse category',0,str(delivery_cell.getVariationCategoryList())) - for category in delivery_cell.getVariationCategoryList() : - if not category in variant_group.category_list : - #LOG('Not found category',0,str(category)) - break - else : - categories_identity = 1 - - if categories_identity : - object_to_update = delivery_cell - break - - # compute target_quantity, quantity and price for delivery_cell or delivery_line and - # build relation between simulation_movement and delivery_cell or delivery_line - if object_to_update is not None : - cell_target_quantity = 0 - cell_total_price = 0 - for movement in variant_group.movement_list : - LOG('SimulationTool, movement.getPhysicalPath',0,movement.getPhysicalPath()) - LOG('SimulationTool, movement.showDict',0,movement.showDict()) - cell_target_quantity += movement.getNetConvertedTargetQuantity() - try: - cell_total_price += movement.getNetConvertedTargetQuantity()*movement.getPrice() # XXX WARNING - ADD PRICED QUANTITY - except: - cell_total_price = None - - if movement.getPortalType() == 'Simulation Movement' : - # update every simulation_movement - # we set delivery_value and target dates and quantity - movement._setDeliveryValue(object_to_update) - movement._setTargetQuantity(movement.getTargetQuantity()) - movement._setQuantity(movement.getTargetQuantity()) - movement._setEfficiency(movement.getTargetEfficiency()) - movement._setTargetStartDate(movement.getTargetStartDate()) - movement._setTargetStopDate(movement.getTargetStopDate()) - movement._setStartDate(movement.getTargetStartDate()) - movement._setStopDate(movement.getTargetStopDate()) - # We will reindex later - reindexable_movement_list.append(movement) - - 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._edit(target_quantity = cell_target_quantity, - quantity = cell_target_quantity, - price = average_price, - force_update = 1, - ) - - - - delivery_list = [] - reindexable_movement_list = [] - - - if movement_group is not None: - # Verify the rule used to build the movements - default_rule_id = None - if len(movement_group.movement_list) > 0: - f = getattr(movement_group.movement_list[0], 'getRootAppliedRule', None) - if f is not None: - applied_rule = f() - default_rule_id = applied_rule.getSpecialiseId() - - - LOG('buildDeliveryList :', 0, 'default_rule = %s' % repr(default_rule_id)) - if default_rule_id == 'default_amortisation_rule': - LOG('buildDeliveryList :', 0, 'default_rule is default_amortisation_rule') - delivery_module = self.getPortalObject().accounting - delivery_type = 'Amortisation Transaction' - delivery_line_type = delivery_type + ' Line' - delivery_cell_type = None - - for resource_group in movement_group.group_list: - resourceGroupProcessing(resource_group=resource_group, - delivery=None, - delivery_module=delivery_module, - delivery_type=delivery_type, - delivery_line_type=delivery_line_type, - delivery_cell_type=delivery_cell_type, - delivery_list=delivery_list, - reindexable_movement_list=reindexable_movement_list, - default_rule_id=default_rule_id) - for movement in movement_group.movement_list: - movement.immediateReindexObject() - - - else: - for order_group in movement_group.group_list: - if orderGroupProcessing(order_group=order_group, - delivery_list=delivery_list, - reindexable_movement_list=reindexable_movement_list) == -1: - return delivery_list - - - # If we reach this point, it means we could - # create deliveries - # get_transaction().commit() - # DO NOT USE COMMIT BECAUSE OF WORKFLOW - - # Now, let us index what must be indexed - # Since we comitted changes, there should be no risk of conflict - for movement in reindexable_movement_list: - movement.reindexObject() # we do it now because we need to - # update category relation - - # Now return deliveries which were created - return delivery_list - - ####################################################### - # Capacity Management - security.declareProtected( Permissions.ModifyPortalContent, 'updateCapacity' ) - def updateCapacity(self, node): - capacity_item_list = [] - for o in node.contentValues(): - if o.isCapacity(): - # Do whatever is needed - capacity_item_list += o.asCapacityItemList() - pass - # Do whatever with capacity_item_list - # and store the resulting new capacity in node - node._capacity_item_list = capacity_item_list - - security.declareProtected( Permissions.ModifyPortalContent, 'isMovementInsideCapacity' ) - def isMovementInsideCapacity(self, movement): - """ - Purpose: provide answer to customer for the question "can you do it ?" - - movement: - date - source destination (2 nodes) - source_section ... - """ - # Get nodes and dat - source_node = movement.getSourceValue() - destination_node = movement.getDestinationValue() - start_date = movement.getTargetStartDate() - stop_date = movement.getTargetStopDate() - # Return result - return self.isNodeInsideCapacity(source_node, start_date, additional_movement=movement, sign=1) and self.isNodeInsideCapacity(destination_node, stop_date, additional_movement=movement, sign=-1) - - security.declareProtected( Permissions.ModifyPortalContent, 'isNodeInsideCapacity' ) - def isNodeInsideCapacity(self, node, date, simulation_state=None, additional_movement=None, sign=1): - """ - Purpose: decide if a node is consistent with its capacity definitions - at a certain date (ie. considreing the stock / production history - """ - # First get the current inventory situation for this node - inventory_list = node.getInventoryList(XXXXX) - # Add additional movement - if additional_movement: - inventory_list = inventory_list + sign * additional_movement # needs to be implemented - # Return answer - return self.isAmountListInsideCapacity(node, inventory_list) - - security.declareProtected( Permissions.ModifyPortalContent, 'isAmountListInsideCapacity' ) - def isAmountListInsideCapacity(self, node, amount_list, - resource_aggregation_base_category=None, resource_aggregation_depth=None): - """ - Purpose: decide if a list of amounts is consistent with the capacity of a node - - If any resource in amount_list is missing in the capacity of the node, resource - aggregation is performed, based on resource_aggregation_base_category. If the - base category is not specified, it is an error (should guess instead?). The resource - aggregation is done at the level of resource_aggregation_depth in the tree - of categories. If resource_aggregation_depth is not specified, it's an error. - - Assumptions: amount_list is an association list, like ((R1 V1) (R2 V2)). - node has an attribute '_capacity_item_list' which is a list of association lists. - resource_aggregation_base_category is a Base Category object or a list of Base - Category objects or None. - resource_aggregation_depth is a strictly positive integer or None. - """ - # Make a copy of the attribute _capacity_item_list, because it may be necessary - # to modify it for resource aggregation. - capacity_item_list = node._capacity_item_list[:] - - # Make a mapping between resources and its indices. - resource_map = {} - index = 0 - for alist in capacity_item_list: - for pair in alist: - resource = pair[0] -# LOG('isAmountListInsideCapacity', 0, -# "resource is %s" % repr(resource)) - if resource not in resource_map: - resource_map[resource] = index - index += 1 - - # Build a point from the amount list. - point = zeros(index, 'd') # Fill up zeros for safety. - mask_map = {} # This is used to skip items in amount_list. - for amount in amount_list: - if amount[0] in mask_map: - continue - # This will fail, if amount_list has any different resource from the capacity. - # If it has any different point, then we should ...... - # - # There would be two possible different solutions: - # 1) If a missing resource is a meta-resource of resources supported by the capacity, - # it is possible to add the resource into the capacity by aggregation. - # 2) If a missing resource has a meta-resource as a parent and the capacity supports - # the meta-resource directly or indirectly (`indirectly' means `by aggregation'), - # it is possible to convert the missing resource into the meta-resource. - # - # However, another way has been implemented here. This does the following, if the resource - # is not present in the capacity: - # 1) If the value is zero, just ignore the resource, because zero is always acceptable. - # 2) Attempt to aggregate resources both of the capacity and of the amount list. This aggregation - # is performed at the depth of 'resource_aggregation_depth' under the base category - # 'resource_aggregation_base_category'. - # - resource = amount[0] - if resource in resource_map: - point[resource_map[amount[0]]] = amount[1] - else: - if amount[1] == 0: - # If the value is zero, no need to consider. - pass - elif resource_aggregation_base_category is None or resource_aggregation_depth is None: - # XXX use an appropriate error class - # XXX should guess a base category instead of emitting an exception - raise RuntimeError, "The resource '%s' is not found in the capacity, and the argument 'resource_aggregation_base_category' or the argument 'resource_aggregation_depth' is not specified" % resource - else: - # It is necessary to aggregate resources, to guess the capacity of this resource. - - def getAggregationResourceUrl(url, depth): - # Return a partial url of the argument 'url'. - # If 'url' is '/foo/bar/baz' and 'depth' is 2, return '/foo/bar'. - pos = 0 - for i in range(resource_aggregation_depth): - pos = url.find('/', pos+1) - if pos < 0: - break - if pos < 0: - return None - pos = url.find('/', pos+1) - if pos < 0: - pos = len(url) - return url[:pos] - - def getAggregatedResourceList(aggregation_url, category, resource_list): - # Return a list of resources which should be aggregated. 'aggregation_url' is used - # for a top url of those resources. 'category' is a base category for the aggregation. - aggregated_resource_list = [] - for resource in resource_list: - for url in resource.getCategoryMembershipList(category, base=1): - if url.startswith(aggregation_url): - aggregated_resource_list.append(resource) - return aggregated_resource_list - - def getAggregatedItemList(item_list, resource_list, aggregation_resource): - # Return a list of association lists, which is a result of an aggregation. - # 'resource_list' is a list of resources which should be aggregated. - # 'aggregation_resource' is a category object which is a new resource created by - # this aggregation. - # 'item_list' is a list of association lists. - new_item_list = [] - for alist in item_list: - new_val = 0 - new_alist = [] - # If a resource is not a aggregated, then add it to the new alist as it is. - # Otherwise, aggregate it to a single value. - for pair in alist: - if pair[0] in resource_list: - new_val += pair[1] - else: - new_alist.append(pair) - # If it is zero, ignore this alist, as it is nonsense. - if new_val != 0: - new_alist.append([aggregation_resource, new_val]) - new_item_list.append(new_alist) - return new_item_list - - # Convert this to a string if necessary, for convenience. - if type(resource_aggregation_base_category) not in (type([]), type(())): - resource_aggregation_base_category = (resource_aggregation_base_category,) - - done = 0 -# LOG('isAmountListInsideCapacity', 0, -# "resource_aggregation_base_category is %s" % repr(resource_aggregation_base_category)) - for category in resource_aggregation_base_category: - for resource_url in resource.getCategoryMembershipList(category, base=1): - aggregation_url = getAggregationResourceUrl(resource_url, - resource_aggregation_depth) - if aggregation_url is None: - continue - aggregated_resource_list = getAggregatedResourceList (aggregation_url, - category, - resource_map.keys()) - # If any, do the aggregation. - if len(aggregated_resource_list) > 0: - aggregation_resource = self.portal_categories.resolveCategory(aggregation_url) - # Add the resource to the mapping. - # LOG('aggregation_resource', 0, str(aggregation_resource)) - resource_map[aggregation_resource] = index - index += 1 - # Add the resource to the point. - point = resize(point, (index,)) - val = 0 - for aggregated_amount in amount_list: - for url in aggregated_amount[0].getCategoryMembershipList(category, base=1): - if url.startswith(aggregation_url): - val += aggregated_amount[1] - mask_map[aggregated_amount[0]] = None - break - point[index-1] = val - # Add capacity definitions of the resource into the capacity. - capacity_item_list += getAggregatedItemList(capacity_item_list, - aggregated_resource_list, - aggregation_resource) - done = 1 - break - if done: - break - if not done: - raise RuntimeError, "Aggregation failed" - - # Build a matrix from the capacity item list. -# LOG('resource_map', 0, str(resource_map)) - matrix = zeros((len(capacity_item_list)+1, index), 'd') - for index in range(len(capacity_item_list)): - for pair in capacity_item_list[index]: - matrix[index,resource_map[pair[0]]] = pair[1] - -# LOG('isAmountListInsideCapacity', 0, -# "matrix = %s, point = %s, capacity_item_list = %s" % (str(matrix), str(point), str(capacity_item_list))) - return solve(matrix, point) - - - # Asset Price Calculation - def updateAssetPrice(self, resource, variation_text, section_category, node_category, - strict_membership=0, simulation_state=current_inventory_state_list): - section_value = self.portal_categories.resolveCategory(section_category) - node_value = self.portal_categories.resolveCategory(node_category) - # Initialize price - current_asset_price = 0.0 # Missing: initial inventory price !!! - current_inventory = 0.0 - # Parse each movement - brain_list = self.Resource_zGetMovementHistoryList(resource=[resource], - variation_text=variation_text, - section_category=section_category, - node_category=node_category, - strict_membership=strict_membership, - simulation_state=simulation_state) # strict_membership not taken into account - # We select movements related to certain nodes (ex. Stock) and sections (ex.Coramy Group) - result = [] - for b in brain_list: - m = b.getObject() - if m is not None: - previous_inventory = current_inventory - inventory_quantity = b.quantity # We should use the aggregated quantity provided by Resource_zGetMovementHistoryList - quantity = m.getQuantity() # The movement quantity is important to determine the meaning of source and destination - # Maybe we should take care of target qty in delired deliveries - if quantity is None: - quantity = 0.0 - if m.getSourceValue() is None: - # This is a production movement or an inventory movement - # Use Industrial Price - current_inventory += inventory_quantity # Update inventory - if m.getPortalType() in ('Inventory Line', 'Inventory Cell'): # XX should be replaced by isInventory ??? - asset_price = m.getPrice() - if asset_price in (0.0, None): - asset_price = current_asset_price # Use current price if no price defined - else: # this is a production - asset_price = m.getIndustrialPrice() - if asset_price is None: asset_price = current_asset_price # Use current price if no price defined - result.append((m.getRelativeUrl(), m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(), - m.getTargetQuantity(), 'Production or Inventory', 'Price: %s' % asset_price - )) - elif m.getDestinationValue() is None: - # This is a consumption movement or an inventory movement - current_inventory += inventory_quantity # Update inventory - asset_price = current_asset_price - result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(), - m.getTargetQuantity(), 'Consumption or Inventory', 'Price: %s' % asset_price - )) - elif m.getSourceValue().isAcquiredMemberOf(node_category) and m.getDestinationValue().isAcquiredMemberOf(node_category): - # This is an internal movement - current_inventory += inventory_quantity # Update inventory - asset_price = current_asset_price - result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(), - m.getTargetQuantity(), 'Internal', 'Price: %s' % asset_price - )) - elif m.getSourceValue().isAcquiredMemberOf(node_category) and quantity < 0: - # This is a physically inbound movement - try to use commercial price - if m.getSourceSectionValue() is None: - # No meaning - current_inventory += inventory_quantity # Update inventory - asset_price = current_asset_price - result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(), - m.getTargetQuantity(), 'Error', 'Price: %s' % asset_price - )) - elif m.getDestinationSectionValue() is None: - # No meaning - current_inventory += inventory_quantity # Update inventory - asset_price = current_asset_price - result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(), - m.getTargetQuantity(), 'Error', 'Price: %s' % asset_price - )) - elif m.getDestinationSectionValue().isAcquiredMemberOf(section_category): - current_inventory += inventory_quantity # Update inventory - if m.getDestinationValue().isAcquiredMemberOf('site/Piquage'): - # Production - asset_price = m.getIndustrialPrice() - if asset_price is None: asset_price = current_asset_price # Use current price if no price defined - result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(), - m.getTargetQuantity(), 'Production', 'Price: %s' % asset_price - )) - else: - # Inbound from same section - asset_price = current_asset_price - result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(), - m.getTargetQuantity(), 'Inbound same section', 'Price: %s' % asset_price - )) - else: - current_inventory += inventory_quantity # Update inventory - asset_price = m.getPrice() - result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(), - m.getTargetQuantity(), 'Inbound different section', 'Price: %s' % asset_price - )) - elif m.getDestinationValue().isAcquiredMemberOf(node_category) and quantity > 0: - # This is a physically inbound movement - try to use commercial price - if m.getSourceSectionValue() is None: - # No meaning - current_inventory += inventory_quantity # Update inventory - asset_price = current_asset_price - result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(), - m.getTargetQuantity(), 'Error', 'Price: %s' % asset_price - )) - elif m.getDestinationSectionValue() is None: - # No meaning - current_inventory += inventory_quantity # Update inventory - asset_price = current_asset_price - result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(), - m.getTargetQuantity(), 'Error', 'Price: %s' % asset_price - )) - elif m.getSourceSectionValue().isAcquiredMemberOf(section_category): - current_inventory += inventory_quantity # Update inventory - if m.getSourceValue().isAcquiredMemberOf('site/Piquage'): - # Production - asset_price = m.getIndustrialPrice() - if asset_price is None: asset_price = current_asset_price # Use current price if no price defined - result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(), - m.getTargetQuantity(), 'Production', 'Price: %s' % asset_price - )) - else: - # Inbound from same section - asset_price = current_asset_price - result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(), - m.getTargetQuantity(), 'Inbound same section', 'Price: %s' % asset_price - )) - else: - current_inventory += inventory_quantity # Update inventory - asset_price = m.getPrice() - result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(), - m.getTargetQuantity(), 'Inbound different section', 'Price: %s' % asset_price - )) - else: - # Outbound movement - current_inventory += inventory_quantity # Update inventory - asset_price = current_asset_price - result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(), - m.getTargetQuantity(), 'Outbound', 'Price: %s' % asset_price - )) - - # Update asset_price - if current_inventory > 0: - if inventory_quantity is not None: - # Update price with an average of incoming goods and current goods - current_asset_price = ( current_asset_price * previous_inventory + asset_price * inventory_quantity ) / float(current_inventory) - else: - # New price is the price of incoming goods - negative stock has no meaning for asset calculation - current_asset_price = asset_price - - result.append(('###New Asset Price', current_asset_price, 'New Inventory', current_inventory)) - - # Update Asset Price on the right side - if m.getSourceSectionValue() is not None and m.getSourceSectionValue().isAcquiredMemberOf(section_category): - # for each movement, source section is member of one and one only accounting category - # therefore there is only one and one only source asset price - m._setSourceAssetPrice(current_asset_price) - #quantity = m.getInventoriatedQuantity() - #if quantity: - # #total_asset_price = - current_asset_price * quantity - # #m.Movement_zSetSourceTotalAssetPrice(uid=m.getUid(), total_asset_price = total_asset_price) - # m._setSourceAssetPrice(current_asset_price) - if m.getDestinationSectionValue() is not None and m.getDestinationSectionValue().isMemberOf(section_category): - # for each movement, destination section is member of one and one only accounting category - # therefore there is only one and one only destination asset price - m._setDestinationAssetPrice(current_asset_price) - #quantity = m.getInventoriatedQuantity() - #if quantity: - # total_asset_price = current_asset_price * quantity - # m.Movement_zSetDestinationTotalAssetPrice(uid=m.getUid(), total_asset_price = total_asset_price) - # Global reindexing required afterwards in any case: so let us do it now - # Until we get faster methods (->reindexObject()) - #m.immediateReindexObject() - m.reindexObject() - #m.activate(priority=7).immediateReindexObject() # Too slow - - return result - - # Used for mergeDeliveryList. - class MergeDeliveryListError(Exception): pass - - 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. - """ - # Sanity checks. - if len(delivery_list) == 0: - raise self.MergeDeliveryListError, "No delivery is passed" - elif len(delivery_list) == 1: - raise self.MergeDeliveryListError, "Only one delivery is passed" - - main_delivery = delivery_list[0] - delivery_list = delivery_list[1:] - - # 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', - '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 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 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: - simulated_movement_list.extend(delivery.getSimulatedMovementList()) - invoice_movement_list.extend(delivery.getInvoiceMovementList()) - - #for movement in simulated_movement_list + invoice_movement_list: - # parent = movement.aq_parent - # LOG('mergeDeliveryList', 0, 'movement = %s, parent = %s, movement.getPortalType() = %s, parent.getPortalType() = %s' % (repr(movement), repr(parent), repr(movement.getPortalType()), repr(parent.getPortalType()))) - - 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: - LOG('mergeDeliveryList dump tree', 0, 'criterion = %s, movement_list = %s, group_list = %s' % (repr(criterion_group.criterion), repr(criterion_group.movement_list), repr(criterion_group.group_list))) - for resource_group in criterion_group.group_list: - LOG('mergeDeliveryList dump tree', 0, 'resource = %s, movement_list = %s, group_list = %s' % (repr(resource_group.resource), repr(resource_group.movement_list), repr(resource_group.group_list))) - for base_variant_group in resource_group.group_list: - LOG('mergeDeliveryList dump tree', 0, 'base_category_list = %s, movement_list = %s, group_list = %s' % (repr(base_variant_group.base_category_list), repr(base_variant_group.movement_list), repr(base_variant_group.group_list))) - for variant_group in base_variant_group.group_list: - LOG('mergeDeliveryList dump tree', 0, 'category_list = %s, movement_list = %s, group_list = %s' % (repr(variant_group.category_list), repr(variant_group.movement_list), repr(variant_group.group_list))) - - 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 - - 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: - 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) - - object_to_update = None - 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() - LOG('mergeDeliveryList', 0, 'delivery_cell = %s, predicate_value_list = %s, variant_group.category_list = %s' % (repr(delivery_cell), repr(predicate_value_list), repr(variant_group.category_list))) - 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 - - #LOG('mergeDeliveryList', 0, 'object_to_update = %s' % repr(object_to_update)) - if object_to_update is not None: - cell_price = object_to_update.getPrice() or 0.0 - 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 * cell_price - 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_price = movement.getPrice() - cell_total_price += movement.getNetConvertedTargetQuantity() * cell_price - 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, 'object_to_update = %s, cell_category_list = %s, cell_target_quantity = %s, cell_quantity = %s, average_price = %s' % (repr(object_to_update), repr(cell_category_list), repr(cell_target_quantity), repr(cell_quantity), repr(average_price))) - object_to_update.setCategoryList(cell_category_list) - if object_to_update.getPortalType() in simulated_movement_type_list: - object_to_update.edit(target_quantity = cell_target_quantity, - quantity = cell_quantity, - price = average_price, - ) - elif object_to_update.getPortalType() in invoice_movement_type_list: - # Invoices do not have target quantities, and the price never change. - object_to_update.edit(quantity = cell_quantity, - price = cell_price, - ) - else: - raise self.MergeDeliveryListError, "Unknown portal type %s" % str(object_to_update.getPortalType()) - #object_to_update.immediateReindexObject() - 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() - 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) -- 2.30.9