Commit 6f8ca320 authored by Łukasz Nowak's avatar Łukasz Nowak

- merge BPM rules functionality into normal rules


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@28539 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 53aebe66
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Romain Courteaud <romain@nexedi.com>
# Łukasz Nowak <luke@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.Rule import Rule
class BPMDeliveryRule(Rule):
"""
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)
def _getInputMovementList(self, applied_rule):
"""Return list of movements from delivery"""
delivery = applied_rule.getDefaultCausalityValue()
if delivery is not None:
return delivery.getMovementList(
portal_type=delivery.getPortalDeliveryMovementTypeList())
return []
def _getExpandablePropertyUpdateDict(self, applied_rule, movement,
business_path, current_property_dict):
"""Delivery specific update dict"""
return {
'order_list': [movement.getRelativeUrl()],
'delivery_list': [movement.getRelativeUrl()],
'deliverable': 1,
}
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Łukasz Nowak <luke@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.Rule import Rule
from Products.ERP5.Document.PredicateMatrix import PredicateMatrix
class BPMInvoiceTransactionRule(Rule, PredicateMatrix):
"""
This is BPM enabled Invoice Transaction Rule.
"""
# CMF Type Definition
meta_type = 'ERP5 BPM Invoice Transaction Rule'
portal_type = 'BPM Invoice Transaction Rule'
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
def _getCurrencyRatioByArrow(self, arrow, prevision_line):
from Products.ERP5Type.Document import newTempSimulationMovement
try:
prevision_currency = prevision_line['resource_list'][0]
except IndexError:
prevision_currency = None
temporary_movement = newTempSimulationMovement(self.getPortalObject(),
'1', **prevision_line)
exchange_ratio = None
try:
section = prevision_line['%s_list' % arrow][0]
except IndexError:
section = None
if section is not None:
currency_url = self.restrictedTraverse(section).getProperty(
'price_currency', None)
else:
currency_url = None
if currency_url is not None and prevision_currency != currency_url:
precision = section.getPriceCurrencyValue() \
.getQuantityPrecision()
exchange_ratio = currency.getPrice(
context=temporary_movement.asContext(
categories=['price_currency/%s' % currency_url,
'resource/%s' % prevision_currency],
start_date=temporary_movement.getStartDate()))
return exchange_ratio
#### 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.
"""
input_movement, business_path = self._getInputMovementAndPathTupleList(
applied_rule)[0]
prevision_list = []
# Find a matching cell
cell = self._getMatchingCell(input_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 = input_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()
prevision_line = {}
prevision_line.update(**self._getExpandablePropertyDict(applied_rule,
input_movement, business_path))
prevision_line.update(
source_list = [accounting_rule_cell_line.getSource()],
destination_list = [accounting_rule_cell_line.getDestination()],
quantity = (input_movement.getCorrectedQuantity() *
input_movement.getPrice(0.0)) *
accounting_rule_cell_line.getQuantity(),
resource_list = [resource],
price = 1,
)
if resource is not None:
#set asset_price on movement when resource is different from price
#currency of the source/destination section
destination_exchange_ratio = self._getCurrencyRatioByArrow(
'destination_section', prevision_line)
if destination_exchange_ratio is not None:
prevision_line.update(destination_total_asset_price=round(
(destination_exchange_ratio*
applied_rule.getParentValue().getTotalPrice()),precision))
source_exchange_ratio = self._getCurrencyRatioByArrow(
'source_section', prevision_line)
if source_exchange_ratio is not None:
prevision_line.update(source_total_asset_price=round(
(source_exchange_ratio*
applied_rule.getParentValue().getTotalPrice()),precision))
if accounting_rule_cell_line.hasProperty(
'generate_prevision_script_id'):
generate_prevision_script_id = \
accounting_rule_cell_line.getGeneratePrevisionScriptId()
prevision_line.update(getattr(input_movement,
generate_prevision_script_id)(prevision_line))
prevision_list.append(prevision_line)
return prevision_list
# 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
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
# Sebastien Robin <seb@nexedi.com>
# Romain Courteaud <romain@nexedi.com>
# Łukasz Nowak <luke@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.Rule import Rule
class BPMInvoicingRule(Rule):
"""
This is BPM enabled Invoicing Rule
"""
# CMF Type Definition
meta_type = 'ERP5 BPM Invoicing Rule'
portal_type = 'BPM Invoicing Rule'
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
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 methods for expand
def _getExpandablePropertyUpdateDict(self, applied_rule, movement,
business_path, current_property_dict):
return {
'deliverable': 1
}
def isDeliverable(self, movement):
return movement.getResource() is not None
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002-2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
# Romain Courteaud <romain@nexedi.com>
# Łukasz Nowak <luke@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
from Products.ERP5.Document.BPMDeliveryRule import BPMDeliveryRule
class BPMOrderRule(BPMDeliveryRule):
"""
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)
def _getInputMovementList(self, applied_rule):
"""Input movement list comes from order"""
order = applied_rule.getDefaultCausalityValue()
if order is not None:
return order.getMovementList(
portal_type=order.getPortalOrderMovementTypeList())
return []
def _getExpandablePropertyUpdateDict(self, applied_rule, movement,
business_path, current_property_dict):
"""Order rule specific update dictionary"""
return {
'order_list': [movement.getRelativeUrl()],
'deliverable': 1,
}
...@@ -62,6 +62,10 @@ class DeliveryRule(Rule): ...@@ -62,6 +62,10 @@ class DeliveryRule(Rule):
Else if the movement is not in current state, it can be modified. Else if the movement is not in current state, it can be modified.
Else, it cannot be modified. Else, it cannot be modified.
""" """
if self._isBPM():
Rule.expand(self, applied_rule,
delivery_movement_type_list=delivery_movement_type_list, **kw)
return
movement_type = 'Simulation Movement' movement_type = 'Simulation Movement'
existing_movement_list = [] existing_movement_list = []
immutable_movement_list = [] immutable_movement_list = []
...@@ -220,3 +224,19 @@ class DeliveryRule(Rule): ...@@ -220,3 +224,19 @@ class DeliveryRule(Rule):
return 0 return 0
return 1 return 1
def _getInputMovementList(self, applied_rule):
"""Return list of movements from delivery"""
delivery = applied_rule.getDefaultCausalityValue()
if delivery is not None:
return delivery.getMovementList(
portal_type=delivery.getPortalDeliveryMovementTypeList())
return []
def _getExpandablePropertyUpdateDict(self, applied_rule, movement,
business_path, current_property_dict):
"""Delivery specific update dict"""
return {
'order_list': [movement.getRelativeUrl()],
'delivery_list': [movement.getRelativeUrl()],
'deliverable': 1,
}
...@@ -53,6 +53,99 @@ class InvoiceTransactionRule(Rule, PredicateMatrix): ...@@ -53,6 +53,99 @@ class InvoiceTransactionRule(Rule, PredicateMatrix):
security.declareObjectProtected(Permissions.AccessContentsInformation) security.declareObjectProtected(Permissions.AccessContentsInformation)
#### Helper method for expand #### Helper method for expand
def _generatePrevisionListBPM(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.
"""
input_movement, business_path = self._getInputMovementAndPathTupleList(
applied_rule)[0]
prevision_list = []
# Find a matching cell
cell = self._getMatchingCell(input_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 = input_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()
prevision_line = {}
prevision_line.update(**self._getExpandablePropertyDict(applied_rule,
input_movement, business_path))
prevision_line.update(
source_list = [accounting_rule_cell_line.getSource()],
destination_list = [accounting_rule_cell_line.getDestination()],
quantity = (input_movement.getCorrectedQuantity() *
input_movement.getPrice(0.0)) *
accounting_rule_cell_line.getQuantity(),
resource_list = [resource],
price = 1,
)
if resource is not None:
#set asset_price on movement when resource is different from price
#currency of the source/destination section
destination_exchange_ratio, precision = self \
._getCurrencyRatioAndPrecisionByArrow(
'destination_section', prevision_line)
if destination_exchange_ratio is not None:
prevision_line.update(destination_total_asset_price=round(
(destination_exchange_ratio*
applied_rule.getParentValue().getTotalPrice()),precision))
source_exchange_ratio, precision = self \
._getCurrencyRatioAndPrecisionByArrow(
'source_section', prevision_line)
if source_exchange_ratio is not None:
prevision_line.update(source_total_asset_price=round(
(source_exchange_ratio*
applied_rule.getParentValue().getTotalPrice()),precision))
if accounting_rule_cell_line.hasProperty(
'generate_prevision_script_id'):
generate_prevision_script_id = \
accounting_rule_cell_line.getGeneratePrevisionScriptId()
prevision_line.update(getattr(input_movement,
generate_prevision_script_id)(prevision_line))
prevision_list.append(prevision_line)
return prevision_list
def _generatePrevisionList(self, applied_rule, **kw): def _generatePrevisionList(self, applied_rule, **kw):
""" """
Generate a list of movements, that should be children of this rule, Generate a list of movements, that should be children of this rule,
...@@ -60,6 +153,8 @@ class InvoiceTransactionRule(Rule, PredicateMatrix): ...@@ -60,6 +153,8 @@ class InvoiceTransactionRule(Rule, PredicateMatrix):
These previsions are acrually returned as dictionaries. These previsions are acrually returned as dictionaries.
""" """
if self._isBPM():
return self._generatePrevisionListBPM(applied_rule, *kw)
prevision_list = [] prevision_list = []
context_movement = applied_rule.getParentValue() context_movement = applied_rule.getParentValue()
...@@ -159,6 +254,10 @@ class InvoiceTransactionRule(Rule, PredicateMatrix): ...@@ -159,6 +254,10 @@ class InvoiceTransactionRule(Rule, PredicateMatrix):
modify, remove) modify, remove)
- add/modify/remove child movements to match prevision - add/modify/remove child movements to match prevision
""" """
if self._isBPM():
Rule.expand(self, applied_rule, force=force, **kw)
return
add_list, modify_dict, \ add_list, modify_dict, \
delete_list = self._getCompensatedMovementList(applied_rule, delete_list = self._getCompensatedMovementList(applied_rule,
matching_property_list=['resource', 'source', matching_property_list=['resource', 'source',
...@@ -283,4 +382,34 @@ class InvoiceTransactionRule(Rule, PredicateMatrix): ...@@ -283,4 +382,34 @@ class InvoiceTransactionRule(Rule, PredicateMatrix):
if m.getSimulationState() in self.getPortalDraftOrderStateList(): if m.getSimulationState() in self.getPortalDraftOrderStateList():
return 0 return 0
return 1 return 1
def _getCurrencyRatioAndPrecisionByArrow(self, arrow, prevision_line):
from Products.ERP5Type.Document import newTempSimulationMovement
try:
prevision_currency = prevision_line['resource_list'][0]
except IndexError:
prevision_currency = None
temporary_movement = newTempSimulationMovement(self.getPortalObject(),
'1', **prevision_line)
exchange_ratio = None
precision = None
try:
section = prevision_line['%s_list' % arrow][0]
except IndexError:
section = None
if section is not None:
currency_url = self.restrictedTraverse(section).getProperty(
'price_currency', None)
else:
currency_url = None
if currency_url is not None and prevision_currency != currency_url:
precision = section.getPriceCurrencyValue() \
.getQuantityPrecision()
exchange_ratio = self.restrictedTraverse(currency_url).getPrice(
context=temporary_movement.asContext(
categories=['price_currency/%s' % currency_url,
'resource/%s' % prevision_currency],
start_date=temporary_movement.getStartDate()))
return exchange_ratio, precision
...@@ -68,6 +68,8 @@ class InvoicingRule(Rule): ...@@ -68,6 +68,8 @@ class InvoicingRule(Rule):
These previsions are returned as dictionaries. These previsions are returned as dictionaries.
""" """
if self._isBPM():
return Rule._generatePrevisionList(self, applied_rule, **kw)
# XXX Isn't it better to share the code with expand method # XXX Isn't it better to share the code with expand method
context_movement = applied_rule.getParentValue() context_movement = applied_rule.getParentValue()
...@@ -121,6 +123,9 @@ class InvoicingRule(Rule): ...@@ -121,6 +123,9 @@ class InvoicingRule(Rule):
modify, remove) modify, remove)
- add/modify/remove child movements to match prevision - add/modify/remove child movements to match prevision
""" """
if self._isBPM():
Rule.expand(self, applied_rule, force=force, **kw)
return
parent_movement = applied_rule.getParentValue() parent_movement = applied_rule.getParentValue()
if parent_movement is not None: if parent_movement is not None:
if not parent_movement.isFrozen(): if not parent_movement.isFrozen():
...@@ -147,3 +152,10 @@ class InvoicingRule(Rule): ...@@ -147,3 +152,10 @@ class InvoicingRule(Rule):
def isDeliverable(self, movement): def isDeliverable(self, movement):
return movement.getResource() is not None return movement.getResource() is not None
def _getExpandablePropertyUpdateDict(self, applied_rule, movement,
business_path, current_property_dict):
return {
'deliverable': 1
}
...@@ -62,7 +62,10 @@ class OrderRule(DeliveryRule): ...@@ -62,7 +62,10 @@ class OrderRule(DeliveryRule):
delivered child, and is in order, it can be modified. delivered child, and is in order, it can be modified.
Else, it cannot be modified. Else, it cannot be modified.
""" """
if self._isBPM():
DeliveryRule.expand(self, applied_rule, force=force, **kw)
return
movement_type = 'Simulation Movement' movement_type = 'Simulation Movement'
existing_movement_list = [] existing_movement_list = []
immutable_movement_list = [] immutable_movement_list = []
...@@ -149,11 +152,15 @@ class OrderRule(DeliveryRule): ...@@ -149,11 +152,15 @@ class OrderRule(DeliveryRule):
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'_getExpandablePropertyDict') '_getExpandablePropertyDict')
def _getExpandablePropertyDict(self, applied_rule, movement, **kw): def _getExpandablePropertyDict(self, applied_rule, movement,
business_path=None, **kw):
""" """
Return a Dictionary with the Properties used to edit Return a Dictionary with the Properties used to edit
the simulation movement the simulation movement
""" """
if self._isBPM():
return DeliveryRule._getExpandablePropertyDict(self, applied_rule,
movement, business_path, **kw)
property_dict = {} property_dict = {}
default_property_list = self.getExpandablePropertyList() default_property_list = self.getExpandablePropertyList()
...@@ -191,3 +198,18 @@ class OrderRule(DeliveryRule): ...@@ -191,3 +198,18 @@ class OrderRule(DeliveryRule):
return property_dict return property_dict
def _getInputMovementList(self, applied_rule):
"""Input movement list comes from order"""
order = applied_rule.getDefaultCausalityValue()
if order is not None:
return order.getMovementList(
portal_type=order.getPortalOrderMovementTypeList())
return []
def _getExpandablePropertyUpdateDict(self, applied_rule, movement,
business_path, current_property_dict):
"""Order rule specific update dictionary"""
return {
'order_list': [movement.getRelativeUrl()],
'deliverable': 1,
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment