[Erp5-report] r27992 - /erp5/trunk/products/ERP5/Document/
nobody at svn.erp5.org
nobody at svn.erp5.org
Tue Jul 7 14:51:03 CEST 2009
Author: luke
Date: Tue Jul 7 14:51:00 2009
New Revision: 27992
URL: http://svn.erp5.org?rev=27992&view=rev
Log:
- BPM enabled rules, evaluation only
Added:
erp5/trunk/products/ERP5/Document/BPMDeliveryRule.py
erp5/trunk/products/ERP5/Document/BPMInvoiceTransactionRule.py
erp5/trunk/products/ERP5/Document/BPMInvoicingRule.py
erp5/trunk/products/ERP5/Document/BPMOrderRule.py
erp5/trunk/products/ERP5/Document/BPMRule.py
Added: erp5/trunk/products/ERP5/Document/BPMDeliveryRule.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/BPMDeliveryRule.py?rev=27992&view=auto
==============================================================================
--- erp5/trunk/products/ERP5/Document/BPMDeliveryRule.py (added)
+++ erp5/trunk/products/ERP5/Document/BPMDeliveryRule.py [utf8] Tue Jul 7 14:51:00 2009
@@ -1,0 +1,161 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
+# Jean-Paul Smets-Solanes <jp at nexedi.com>
+# Romain Courteaud <romain at nexedi.com>
+# Łukasz Nowak <luke at 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5.Document.BPMRule import BPMRule
+
+class BPMDeliveryRule(BPMRule):
+ """
+ DISCLAIMER: Refer to BPMRule docstring disclaimer.
+
+ This is BPM enabled Delivery Rule.
+ """
+
+ # CMF Type Definition
+ meta_type = 'ERP5 BPM Delivery Rule'
+ portal_type = 'BPM Delivery Rule'
+
+ # Declarative security
+ security = ClassSecurityInfo()
+ security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+ # Default Properties
+ property_sheets = ( PropertySheet.Base
+ , PropertySheet.XMLObject
+ , PropertySheet.CategoryCore
+ , PropertySheet.DublinCore
+ , PropertySheet.Task
+ , PropertySheet.AppliedRule
+ )
+
+ # Simulation workflow
+ security.declareProtected(Permissions.ModifyPortalContent, 'expand')
+ def expand(self, applied_rule, delivery_movement_type_list=None, **kw):
+ """
+ Expands the additional Delivery movements to a new simulation tree.
+ Expand is only allowed to create or modify simulation movements for
+ delivery lines which are not already linked to another simulation
+ movement.
+
+ If the movement is not in current state, has no delivered child, and not
+ in delivery movements, it can be deleted.
+ Else if the movement is not in current state, it can be modified.
+ Else, it cannot be modified.
+ """
+ existing_movement_list = []
+ immutable_movement_list = []
+ delivery = applied_rule.getDefaultCausalityValue()
+ if delivery_movement_type_list is None:
+ delivery_movement_type_list = self.getPortalDeliveryMovementTypeList()
+ if delivery is not None:
+ delivery_movement_list = delivery.getMovementList(
+ portal_type=delivery_movement_type_list)
+ # Check existing movements
+ for movement in applied_rule.contentValues(
+ portal_type=self.movement_type):
+ if movement.getLastExpandSimulationState() not in \
+ self.getPortalCurrentInventoryStateList():
+ # XXX: This condition is quick and dirty hack - knowing if
+ # Simulation Movement is frozen shall not be ever hardcoded,
+ # this is BPM configuration
+ movement_delivery = movement.getDeliveryValue()
+ if not movement._isTreeDelivered(ignore_first=1) and \
+ movement_delivery not in delivery_movement_list:
+ applied_rule._delObject(movement.getId())
+ else:
+ existing_movement_list.append(movement)
+ else:
+ existing_movement_list.append(movement)
+ immutable_movement_list.append(movement)
+
+ # Create or modify movements
+ for deliv_mvt in delivery_movement_list:
+ sim_mvt = deliv_mvt.getDeliveryRelatedValue()
+ if sim_mvt is None:
+ # create a new deliv_mvt
+ if deliv_mvt.getParentUid() == deliv_mvt.getExplanationUid():
+ # We are on a line
+ new_id = deliv_mvt.getId()
+ else:
+ # We are on a cell
+ new_id = "%s_%s" % (deliv_mvt.getParentId(), deliv_mvt.getId())
+ # Generate the simulation deliv_mvt
+ property_dict = self.self._getExpandablePropertyDict(applied_rule,
+ deliv_mvt)
+ new_sim_mvt = applied_rule.newContent(
+ portal_type=self.movement_type,
+ id=new_id,
+ order_value=deliv_mvt,
+ order_ratio=1,
+ delivery_value=deliv_mvt,
+ delivery_ratio=1,
+ deliverable=1,
+
+ **property_dict
+ )
+ elif sim_mvt in existing_movement_list:
+ if sim_mvt not in immutable_movement_list:
+ # modification allowed
+ # XXX Hardcoded value
+ sim_mvt.edit(
+ delivery_value=deliv_mvt,
+ delivery_ratio=1,
+ deliverable=1,
+ force_update=1,
+ **property_dict
+ )
+ else:
+ # modification disallowed, must compensate
+ raise NotImplementedError('BPM *have* to support')
+
+ # Now we can set the last expand simulation state to the current state
+ applied_rule.setLastExpandSimulationState(delivery.getSimulationState())
+ # Pass to base class
+ BPMRule.expand(self, applied_rule, **kw)
+
+ security.declareProtected(Permissions.AccessContentsInformation, 'isStable')
+ def isStable(self, applied_rule):
+ """
+ Checks that the applied_rule is stable
+ """
+ return 0
+
+ # Deliverability / orderability
+ def isOrderable(self, movement):
+ return 1
+
+ def isDeliverable(self, movement):
+ if movement.getSimulationState() in movement \
+ .getPortalDraftOrderStateList():
+ return 0
+ return 1
+
Added: erp5/trunk/products/ERP5/Document/BPMInvoiceTransactionRule.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/BPMInvoiceTransactionRule.py?rev=27992&view=auto
==============================================================================
--- erp5/trunk/products/ERP5/Document/BPMInvoiceTransactionRule.py (added)
+++ erp5/trunk/products/ERP5/Document/BPMInvoiceTransactionRule.py [utf8] Tue Jul 7 14:51:00 2009
@@ -1,0 +1,262 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
+# Jean-Paul Smets-Solanes <jp at nexedi.com>
+# Łukasz Nowak <luke at 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5.Document.BPMRule import BPMRule
+from Products.ERP5.Document.PredicateMatrix import PredicateMatrix
+
+class BPMInvoiceTransactionRule(BPMRule, PredicateMatrix):
+ """
+ DISCLAIMER: Refer to BPMRule docstring disclaimer.
+
+ This is BPM enabled Invoice Transaction Rule.
+ """
+
+ # CMF Type Definition
+ meta_type = 'ERP5 BPM Invoice Transaction Rule'
+ portal_type = 'BPM Invoice Transaction Rule'
+ add_permission = Permissions.AddPortalContent
+ isPortalContent = 1
+ isRADContent = 1
+
+ # Declarative security
+ security = ClassSecurityInfo()
+ security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+ # Default Properties
+ property_sheets = ( PropertySheet.Base
+ , PropertySheet.XMLObject
+ , PropertySheet.CategoryCore
+ , PropertySheet.DublinCore
+ , PropertySheet.Task
+ , PropertySheet.AppliedRule
+ )
+
+#### Helper method for expand
+ def _generatePrevisionList(self, applied_rule, **kw):
+ """
+ Generate a list of movements, that should be children of this rule,
+ based on its context (parent movement, delivery, configuration ...)
+
+ These previsions are actually returned as dictionaries.
+ """
+ prevision_list = []
+ context_movement = applied_rule.getParentValue()
+
+ business_process = applied_rule.getBusinessProcessValue()
+
+ movement_and_path_list = []
+ for business_path in business_process.getPathValueList(
+ self.getProperty('trade_phase_list'),
+ context_movement):
+ movement_and_path_list.append((context_movement, business_path))
+
+ if len(movement_and_path_list) > 1:
+ raise NotImplementedError
+
+ # Find a matching cell
+ cell = self._getMatchingCell(context_movement)
+
+ if cell is not None : # else, we do nothing
+ for accounting_rule_cell_line in cell.objectValues() :
+ # get the resource (in that order):
+ # * resource from the invoice (using deliveryValue)
+ # * price_currency from the invoice
+ # * price_currency from the parents simulation movement's
+ # deliveryValue
+ # * price_currency from the top level simulation movement's
+ # orderValue
+ resource = None
+ invoice_line = context_movement.getDeliveryValue()
+ if invoice_line is not None :
+ invoice = invoice_line.getExplanationValue()
+ resource = invoice.getProperty('resource',
+ invoice.getProperty('price_currency', None))
+ if resource is None :
+ # search the resource on parents simulation movement's deliveries
+ simulation_movement = applied_rule.getParentValue()
+ portal_simulation = self.getPortalObject().portal_simulation
+ while resource is None and \
+ simulation_movement != portal_simulation :
+ delivery = simulation_movement.getDeliveryValue()
+ if delivery is not None:
+ resource = delivery.getProperty('price_currency', None)
+ if (resource is None) and \
+ (simulation_movement.getParentValue().getParentValue() \
+ == portal_simulation) :
+ # we are on the first simulation movement, we'll try
+ # to get the resource from it's order price currency.
+ order = simulation_movement.getOrderValue()
+ if order is not None:
+ resource = order.getProperty('price_currency', None)
+ simulation_movement = simulation_movement\
+ .getParentValue().getParentValue()
+ if resource is None :
+ # last resort : get the resource from the rule
+ resource = accounting_rule_cell_line.getResource() \
+ or cell.getResource()
+ # XXX Harcoded list
+ prevision_line = {
+ 'source': accounting_rule_cell_line.getSource(),
+ 'source_section': context_movement.getSourceSection(),
+ 'source_decision': context_movement.getSourceDecision(),
+ 'source_administration': context_movement \
+ .getSourceAdministration(),
+ 'source_project': context_movement.getSourceProject(),
+ 'source_function': context_movement.getSourceFunction(),
+ 'source_payment': context_movement.getSourcePayment(),
+ 'destination': accounting_rule_cell_line.getDestination(),
+ 'destination_section': context_movement.getDestinationSection(),
+ 'destination_decision': context_movement.getDestinationDecision(),
+ 'destination_administration': context_movement \
+ .getDestinationAdministration(),
+ 'destination_project': context_movement.getDestinationProject(),
+ 'destination_function': context_movement.getDestinationFunction(),
+ 'destination_payment': context_movement.getDestinationPayment(),
+ 'start_date': context_movement.getStartDate(),
+ 'stop_date': context_movement.getStopDate(),
+ 'resource': resource,
+ 'quantity': (context_movement.getCorrectedQuantity() *
+ context_movement.getPrice(0.0)) *
+ accounting_rule_cell_line.getQuantity(),
+ 'price': 1,
+ 'force_update': 1,
+ 'causality_value': business_path,
+ }
+
+ if accounting_rule_cell_line.hasProperty(
+ 'generate_prevision_script_id'):
+ generate_prevision_script_id = \
+ accounting_rule_cell_line.getGeneratePrevisionScriptId()
+ prevision_line.update(getattr(context_movement,
+ generate_prevision_script_id)(prevision_line))
+ prevision_list.append(prevision_line)
+ return prevision_list
+
+ security.declareProtected(Permissions.ModifyPortalContent, 'expand')
+ def expand(self, applied_rule, force=0, **kw):
+ """
+ Expands the rule:
+ - generate a list of previsions
+ - compare the prevision with existing children
+ - get the list of existing movements (immutable, mutable, deletable)
+ - compute the difference between prevision and existing (add,
+ modify, remove)
+ - add/modify/remove child movements to match prevision
+ """
+ add_list, modify_dict, \
+ delete_list = self._getCompensatedMovementList(applied_rule,
+ matching_property_list=['resource', 'source',
+ 'destination','destination_total_asset_price',
+ 'source_total_asset_price'],**kw)
+
+ if len(add_list) or len(modify_dict):
+ pass#import pdb; pdb.set_trace()
+
+ for movement_id in delete_list:
+ applied_rule._delObject(movement_id)
+
+ for movement, prop_dict in modify_dict.items():
+ applied_rule[movement].edit(**prop_dict)
+
+ for movement_dict in add_list:
+ if 'id' in movement_dict.keys():
+ mvmt_id = applied_rule._get_id(movement_dict.pop('id'))
+ new_mvmt = applied_rule.newContent(id=mvmt_id,
+ portal_type=self.movement_type)
+ else:
+ new_mvmt = applied_rule.newContent(portal_type=self.movement_type)
+ new_mvmt.edit(**movement_dict)
+ #set asset_price on movement when resource is different from price
+ #currency of the source/destination section
+ currency = new_mvmt.getResourceValue()
+ if currency is not None:
+ currency_url = currency.getRelativeUrl()
+ dest_section = new_mvmt.getDestinationSectionValue()
+ if dest_section is not None:
+ dest_currency_url = dest_section.getProperty('price_currency', None)
+ else:
+ dest_currency_url = None
+ if dest_currency_url is not None \
+ and currency_url != dest_currency_url:
+ precision = dest_section.getPriceCurrencyValue() \
+ .getQuantityPrecision()
+ dest_exchange_ratio = currency.getPrice(context=new_mvmt.asContext(
+ categories=['price_currency/%s' % dest_currency_url,
+ 'resource/%s' % currency_url],
+ start_date=new_mvmt.getStartDate()))
+ if dest_exchange_ratio is not None:
+ new_mvmt.edit(destination_total_asset_price=round(
+ (dest_exchange_ratio*
+ applied_rule.getParentValue().getTotalPrice()),precision))
+
+ source_section = new_mvmt.getSourceSectionValue()
+ if source_section is not None:
+ source_currency_url = source_section.getProperty(
+ 'price_currency', None)
+ else:
+ source_currency_url = None
+ if source_currency_url is not None \
+ and currency_url != source_currency_url:
+ precision = source_section.getPriceCurrencyValue() \
+ .getQuantityPrecision()
+ source_exchange_ratio = currency.getPrice(context=new_mvmt\
+ .asContext(
+ categories=['price_currency/%s' % source_currency_url,
+ 'resource/%s' % currency_url],
+ start_date=new_mvmt.getStartDate()))
+ if source_exchange_ratio is not None:
+ new_mvmt.setSourceTotalAssetPrice(round(
+ source_exchange_ratio*applied_rule.getParentValue().getTotalPrice(),
+ precision))
+
+ # Pass to base class
+ BPMRule.expand(self, applied_rule, force=force, **kw)
+
+ # Matrix related
+ security.declareProtected( Permissions.ModifyPortalContent,
+ 'newCellContent' )
+ def newCellContent(self, id, portal_type='Accounting Rule Cell', **kw):
+ """
+ Creates a new Cell.
+ """
+ self.invokeFactory(type_name=portal_type, id=id)
+ new_cell = self.get(id)
+ return new_cell
+
+ # Deliverability / orderability
+ def isOrderable(self, m):
+ return 1
+
+ def isDeliverable(self, m):
+ if m.getSimulationState() in self.getPortalDraftOrderStateList():
+ return 0
+ return 1
Added: erp5/trunk/products/ERP5/Document/BPMInvoicingRule.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/BPMInvoicingRule.py?rev=27992&view=auto
==============================================================================
--- erp5/trunk/products/ERP5/Document/BPMInvoicingRule.py (added)
+++ erp5/trunk/products/ERP5/Document/BPMInvoicingRule.py [utf8] Tue Jul 7 14:51:00 2009
@@ -1,0 +1,138 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
+# Sebastien Robin <seb at nexedi.com>
+# Romain Courteaud <romain at nexedi.com>
+# Łukasz Nowak <luke at nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsibility 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
+# guarantees 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., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301,
+# USA.
+#
+##############################################################################
+
+from AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5.Document.BPMRule import BPMRule
+
+class BPMInvoicingRule(BPMRule):
+ """
+ DISCLAIMER: Refer to BPMRule docstring disclaimer.
+
+ This is BPM enabled Invoicing Rule
+ """
+
+ # CMF Type Definition
+ meta_type = 'ERP5 BPM Invoicing Rule'
+ portal_type = 'BPM Invoicing Rule'
+ add_permission = Permissions.AddPortalContent
+ isPortalContent = 1
+ isRADContent = 1
+
+ # Declarative security
+ security = ClassSecurityInfo()
+ security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+ # Default Properties
+ property_sheets = ( PropertySheet.Base
+ , PropertySheet.XMLObject
+ , PropertySheet.CategoryCore
+ , PropertySheet.DublinCore
+ , PropertySheet.Task
+ , PropertySheet.AppliedRule
+ )
+
+ security.declareProtected(Permissions.AccessContentsInformation,
+ 'isAccountable')
+ def isAccountable(self, movement):
+ """
+ Tells whether generated movement needs to be accounted or not.
+
+ Invoice movement are never accountable, so simulation movement for
+ invoice movements should not be accountable either.
+ """
+ return 0
+
+#### Helper method for expand
+ def _generatePrevisionList(self, applied_rule, **kw):
+ """
+ Generate a list of movements, that should be children of this rule,
+ based on its context (parent movement, delivery, configuration ...)
+
+ These previsions are returned as dictionaries.
+ """
+ # XXX Isn't it better to share the code with expand method
+ context_movement = applied_rule.getParentValue()
+ business_process = applied_rule.getBusinessProcessValue()
+
+ movement_and_path_list = []
+ for business_path in business_process.getPathValueList(
+ self.getProperty('trade_phase_list'),
+ context_movement):
+ movement_and_path_list.append((context_movement, business_path))
+
+ if len(movement_and_path_list) > 1:
+ raise NotImplementedError
+
+ for movement, business_path in movement_and_path_list:
+ property_dict = self._getExpandablePropertyDict(
+ applied_rule, movement, business_path)
+ property_dict['deliverable'] = 1
+ return [property_dict]
+
+ security.declareProtected(Permissions.ModifyPortalContent, 'expand')
+ def expand(self, applied_rule, force=0, **kw):
+ """
+ Expands the rule:
+ - generate a list of previsions
+ - compare the prevision with existing children
+ - get the list of existing movements (immutable, mutable, deletable)
+ - compute the difference between prevision and existing (add,
+ modify, remove)
+ - add/modify/remove child movements to match prevision
+ """
+ parent_movement = applied_rule.getParentValue()
+ if parent_movement is not None:
+ if not parent_movement.isFrozen():
+ add_list, modify_dict, \
+ delete_list = self._getCompensatedMovementList(applied_rule, **kw)
+ for movement_id in delete_list:
+ applied_rule._delObject(movement_id)
+
+ for movement, prop_dict in modify_dict.items():
+ applied_rule[movement].edit(**prop_dict)
+
+ for movement_dict in add_list:
+ if 'id' in movement_dict.keys():
+ mvmt_id = applied_rule._get_id(movement_dict.pop('id'))
+ new_mvmt = applied_rule.newContent(id=mvmt_id,
+ portal_type=self.movement_type)
+ else:
+ new_mvmt = applied_rule.newContent(portal_type=self.movement_type)
+ new_mvmt.edit(**movement_dict)
+
+ # Pass to base class
+ BPMRule.expand(self, applied_rule, force=force, **kw)
+
+ def isDeliverable(self, movement):
+ return movement.getResource() is not None
+
Added: erp5/trunk/products/ERP5/Document/BPMOrderRule.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/BPMOrderRule.py?rev=27992&view=auto
==============================================================================
--- erp5/trunk/products/ERP5/Document/BPMOrderRule.py (added)
+++ erp5/trunk/products/ERP5/Document/BPMOrderRule.py [utf8] Tue Jul 7 14:51:00 2009
@@ -1,0 +1,170 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
+# Jean-Paul Smets-Solanes <jp at nexedi.com>
+# Romain Courteaud <romain at nexedi.com>
+# Łukasz Nowak <luke at 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 AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet
+from Products.ERP5.Document.BPMRule import BPMRule
+from Products.ERP5.Document.BPMDeliveryRule import BPMDeliveryRule
+from zLOG import LOG, WARNING
+
+class BPMOrderRule(BPMDeliveryRule):
+ """
+ DISCLAIMER: Refer to BPMRule docstring disclaimer.
+
+ This is BPM enabled Order Rule.
+ """
+ # CMF Type Definition
+ meta_type = 'ERP5 BPM Order Rule'
+ portal_type = 'BPM Order Rule'
+
+ # Declarative security
+ security = ClassSecurityInfo()
+ security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+ # Default Properties
+ property_sheets = ( PropertySheet.Base
+ , PropertySheet.XMLObject
+ , PropertySheet.CategoryCore
+ , PropertySheet.DublinCore
+ , PropertySheet.Task
+ , PropertySheet.AppliedRule
+ )
+
+ # Simulation workflow
+ security.declareProtected(Permissions.ModifyPortalContent, 'expand')
+ def expand(self, applied_rule, force=0, **kw):
+ """
+ Expands the Order to a new simulation tree.
+ expand is only allowed to modify a simulation movement if it doesn't
+ have a delivery relation yet.
+
+ If the movement is in ordered or planned state, has no delivered
+ child, and is not in order, it can be deleted.
+ Else, if the movement is in ordered or planned state, has no
+ delivered child, and is in order, it can be modified.
+ Else, it cannot be modified.
+ """
+
+ existing_movement_list = []
+ immutable_movement_list = []
+ order = applied_rule.getDefaultCausalityValue()
+ business_process = applied_rule.getBusinessProcessValue()
+ if order is not None:
+ order_movement_list = order.getMovementList(
+ portal_type=order.getPortalOrderMovementTypeList())
+ # check existing movements
+ for simulation_movement in applied_rule.contentValues(
+ portal_type=self.movement_type):
+ if (not simulation_movement.getLastExpandSimulationState() in
+ order.getPortalReservedInventoryStateList() and
+ not simulation_movement.getLastExpandSimulationState() in
+ order.getPortalCurrentInventoryStateList()) and \
+ not simulation_movement._isTreeDelivered():
+
+ movement_order = simulation_movement.getOrderValue()
+ if movement_order in order_movement_list:
+ existing_movement_list.append(simulation_movement)
+ else:
+ applied_rule._delObject(simulation_movement.getId())
+ else:
+ existing_movement_list.append(simulation_movement)
+ immutable_movement_list.append(simulation_movement)
+
+ # this dict simulates getOrderRelatedValue, but it will not work if an
+ # order was generated from multiple applied rules
+ order_movement_dict = {}
+ for s_m in applied_rule.objectValues():
+ order_movement = s_m.getOrderValue()
+ if order_movement is not None:
+ order_movement_dict[order_movement.getPath()] = s_m
+
+ # Create or modify movements
+ for order_movement in order_movement_list:
+ related_order = order_movement_dict.get(order_movement.getPath(),
+ None)
+ if related_order is None:
+ related_order = order_movement.getOrderRelatedValue()
+
+ movement_and_path_list = []
+ for business_path in business_process.getPathValueList(
+ self.getProperty('trade_phase_list'),
+ order_movement):
+ movement_and_path_list.append((order_movement, business_path))
+
+ if len(movement_and_path_list) > 1:
+ raise NotImplementedError
+
+ for movement, business_path in movement_and_path_list:
+ property_dict = self._getExpandablePropertyDict(
+ applied_rule, movement,
+ business_path)
+ property_dict.update(order_value=order_movement)
+ if related_order is None:
+ # Generate a simulation movement
+ # Do not try to create meaningfull IDs, as order movement can be
+ # hierarchical
+ applied_rule.newContent(
+ portal_type=self.movement_type,
+ order_ratio=1,
+ delivery_ratio=1,
+ deliverable=1,
+ **property_dict)
+ elif related_order in existing_movement_list:
+ if related_order not in immutable_movement_list:
+ # modification allowed
+ related_order.edit(
+ **property_dict)
+ else:
+ # modification disallowed, must compensate
+ raise NotImplementedError('BPM *have* to support compensation')
+
+ # Now we can set the last expand simulation state to the current
+ # state
+ applied_rule.setLastExpandSimulationState(
+ order.getSimulationState())
+ # Pass to base class
+ BPMRule.expand(self, applied_rule, force=force, **kw)
+
+ security.declareProtected(Permissions.AccessContentsInformation, 'isStable')
+ def isStable(self, applied_rule):
+ """
+ Checks that the applied_rule is stable
+ """
+ LOG('OrderRule.isStable', WARNING, 'Not Implemented')
+ return 1
+
+ security.declareProtected(Permissions.AccessContentsInformation,
+ 'isDivergent')
+ def isDivergent(self, movement):
+ """
+ Checks that the movement is divergent
+ """
+ return BPMRule.isDivergent(self, movement)
Added: erp5/trunk/products/ERP5/Document/BPMRule.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/BPMRule.py?rev=27992&view=auto
==============================================================================
--- erp5/trunk/products/ERP5/Document/BPMRule.py (added)
+++ erp5/trunk/products/ERP5/Document/BPMRule.py [utf8] Tue Jul 7 14:51:00 2009
@@ -1,0 +1,244 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
+# Jean-Paul Smets-Solanes <jp at nexedi.com>
+# Łukasz Nowak <luke at 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.
+#
+##############################################################################
+
+import zope.interface
+from AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet, interfaces
+from Products.ERP5.Document.Rule import Rule
+
+class BPMRule(Rule):
+ """
+ DISCLAIMER: Do not use this in any production system.
+ This is only proof of concept and evaluation of new system
+ design. Implementation and API can change without any
+ further warning.
+
+ *DO NOT USE IN PRODUCTION SYSTEM*
+
+ This is BPM enabled Rule system Base class.
+ """
+
+ # CMF Type Definition
+ meta_type = 'ERP5 BPM Rule'
+ portal_type = 'BPM Rule'
+ add_permission = Permissions.AddPortalContent
+ isPortalContent = 1
+ isRADContent = 1
+ isPredicate = 1
+
+ # Declarative security
+ security = ClassSecurityInfo()
+ security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+ zope.interface.implements( interfaces.IPredicate,
+ interfaces.IRule )
+
+ # Default Properties
+ property_sheets = ( PropertySheet.Base
+ , PropertySheet.XMLObject
+ , PropertySheet.CategoryCore
+ , PropertySheet.DublinCore
+ , PropertySheet.Task
+ , PropertySheet.Predicate
+ , PropertySheet.Reference
+ , PropertySheet.Version
+ )
+
+#### Helpers
+ def _getCurrentMovementList(self, applied_rule, **kw):
+ """
+ Returns the list of current children of the applied rule, sorted in 3
+ groups : immutables/mutables/deletable
+
+ * immutable is frozen
+ * mutable is not frozen, but delivered
+ * deletable is not frozen and not delivered
+
+ Delivered means movement is delivered or any of its children is delivered.
+ """
+ immutable_movement_list = []
+ mutable_movement_list = []
+ deletable_movement_list = []
+
+ for movement in applied_rule.contentValues(
+ portal_type=self.movement_type):
+ if movement.isFrozen():
+ immutable_movement_list.append(movement)
+ else:
+ if movement._isTreeDelivered():
+ mutable_movement_list.append(movement)
+ else:
+ deletable_movement_list.append(movement)
+
+ return (immutable_movement_list, mutable_movement_list,
+ deletable_movement_list)
+
+ def _getCompensatedMovementList(self, applied_rule,
+ matching_property_list=(
+ 'resource',
+ 'variation_category_list',
+ 'variation_property_dict',), **kw):
+ """
+ Compute the difference between prevision and existing movements
+
+ immutable movements need compensation, mutables needs to be modified
+
+ XXX For now, this implementation is too simple. It could be improved by
+ using MovementGroups
+ """
+ add_list = [] # list of movements to be added
+ modify_dict = {} # dict of movements to be modified
+ delete_list = [] # list of movements to be deleted
+
+ prevision_list = self._generatePrevisionList(applied_rule, **kw)
+ immutable_movement_list, mutable_movement_list, \
+ deletable_movement_list = self._getCurrentMovementList(applied_rule,
+ **kw)
+ movement_list = immutable_movement_list + mutable_movement_list \
+ + deletable_movement_list
+ non_matched_list = movement_list[:] # list of remaining movements
+
+ for prevision in prevision_list:
+ p_matched_list = []
+ for movement in non_matched_list:
+ for prop in matching_property_list:
+ if prevision.get(prop) != movement.getProperty(prop):
+ break
+ else:
+ p_matched_list.append(movement)
+
+ # Movements exist, we'll try to make them match the prevision
+ if p_matched_list != []:
+ # Check the quantity
+ m_quantity = 0.0
+ for movement in p_matched_list:
+ m_quantity += movement.getQuantity()#getCorrectedQuantity()
+ if m_quantity != prevision.get('quantity'):
+ q_diff = prevision.get('quantity') - m_quantity
+ # try to find a movement that can be edited
+ for movement in p_matched_list:
+ if movement in (mutable_movement_list \
+ + deletable_movement_list):
+ # mark as requiring modification
+ prop_dict = modify_dict.setdefault(movement.getId(), {})
+ #prop_dict['quantity'] = movement.getCorrectedQuantity() + \
+ prop_dict['quantity'] = movement.getQuantity() + \
+ q_diff
+ break
+ # no modifiable movement was found, need to create one
+ else:
+ prevision['quantity'] = q_diff
+ add_list.append(prevision)
+
+ # Check the date
+ for movement in p_matched_list:
+ if movement in (mutable_movement_list \
+ + deletable_movement_list):
+ prop_dict = modify_dict.setdefault(movement.getId(), {})
+ for prop in ('start_date', 'stop_date'):
+ #XXX should be >= 15
+ if prevision.get(prop) != movement.getProperty(prop):
+ prop_dict[prop] = prevision.get(prop)
+ break
+
+ for k, v in prevision.items():
+ if k not in ('quantity', 'start_date', 'stop_date') and \
+ v != movement.getProperty(k):
+ prop_dict.setdefault(k, v)
+
+ # update movement lists
+ for movement in p_matched_list:
+ non_matched_list.remove(movement)
+
+ # No movement matched, we need to create one
+ else:
+ add_list.append(prevision)
+
+ # delete non matched movements
+ for movement in non_matched_list:
+ if movement in deletable_movement_list:
+ # delete movement
+ delete_list.append(movement.getId())
+ elif movement in mutable_movement_list:
+ # set movement quantity to 0 to make it "void"
+ prop_dict = modify_dict.setdefault(movement.getId(), {})
+ prop_dict['quantity'] = 0.0
+ else:
+ # movement not modifiable, we can decide to create a compensation
+ # with negative quantity
+ raise NotImplementedError(
+ "Can not create a compensation movement for %s" % \
+ movement.getRelativeUrl())
+ return (add_list, modify_dict, delete_list)
+
+ security.declareProtected(Permissions.AccessContentsInformation,
+ '_getExpandablePropertyDict')
+ def _getExpandablePropertyDict(self, applied_rule, movement, business_path,
+ **kw):
+ """
+ Return a Dictionary with the Properties used to edit
+ the simulation movement. A supply path can be passed to assign more
+ information
+ """
+ property_dict = {}
+
+ default_property_list = self.getExpandablePropertyList()
+ for prop in default_property_list:
+ property_dict[prop] = movement.getProperty(prop)
+
+ # Arrow
+ for base_category in \
+ business_path.getSourceBaseCategoryList() +\
+ business_path.getDestinationBaseCategoryList():
+ property_dict[base_category] = business_path\
+ .getDefaultAcquiredCategoryMembership(base_category,
+ context=movement)
+ # Amount
+ if business_path.getQuantity():
+ property_dict['quantity'] = business_path.getQuantity()
+ elif business_path.getEfficiency():
+ property_dict['quantity'] = movement.getQuantity() *\
+ business_path.getEfficiency()
+ else:
+ property_dict['quantity'] = movement.getQuantity()
+
+ if movement.getStartDate() == movement.getStopDate():
+ property_dict['start_date'] = business_path.getExpectedStartDate(
+ movement)
+ property_dict['stop_date'] = business_path.getExpectedStopDate(movement)
+ else: # XXX shall not be used, but business_path.getExpectedStart/StopDate
+ # do not works on second path...
+ property_dict['start_date'] = movement.getStartDate()
+ property_dict['stop_date'] = movement.getStopDate()
+
+ # save a relation to supply path for builders XXX which category
+ property_dict['causality_value'] = business_path
+
+ return property_dict
More information about the Erp5-report
mailing list