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):
Else if the movement is not in current state, it can 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'
existing_movement_list = []
immutable_movement_list = []
......@@ -220,3 +224,19 @@ class DeliveryRule(Rule):
return 0
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):
security.declareObjectProtected(Permissions.AccessContentsInformation)
#### 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):
"""
Generate a list of movements, that should be children of this rule,
......@@ -60,6 +153,8 @@ class InvoiceTransactionRule(Rule, PredicateMatrix):
These previsions are acrually returned as dictionaries.
"""
if self._isBPM():
return self._generatePrevisionListBPM(applied_rule, *kw)
prevision_list = []
context_movement = applied_rule.getParentValue()
......@@ -159,6 +254,10 @@ class InvoiceTransactionRule(Rule, PredicateMatrix):
modify, remove)
- add/modify/remove child movements to match prevision
"""
if self._isBPM():
Rule.expand(self, applied_rule, force=force, **kw)
return
add_list, modify_dict, \
delete_list = self._getCompensatedMovementList(applied_rule,
matching_property_list=['resource', 'source',
......@@ -283,4 +382,34 @@ class InvoiceTransactionRule(Rule, PredicateMatrix):
if m.getSimulationState() in self.getPortalDraftOrderStateList():
return 0
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):
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
context_movement = applied_rule.getParentValue()
......@@ -121,6 +123,9 @@ class InvoicingRule(Rule):
modify, remove)
- 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()
if parent_movement is not None:
if not parent_movement.isFrozen():
......@@ -147,3 +152,10 @@ class InvoicingRule(Rule):
def isDeliverable(self, movement):
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):
delivered child, and is in order, it can be modified.
Else, it cannot be modified.
"""
if self._isBPM():
DeliveryRule.expand(self, applied_rule, force=force, **kw)
return
movement_type = 'Simulation Movement'
existing_movement_list = []
immutable_movement_list = []
......@@ -149,11 +152,15 @@ class OrderRule(DeliveryRule):
security.declareProtected(Permissions.AccessContentsInformation,
'_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
the simulation movement
"""
if self._isBPM():
return DeliveryRule._getExpandablePropertyDict(self, applied_rule,
movement, business_path, **kw)
property_dict = {}
default_property_list = self.getExpandablePropertyList()
......@@ -191,3 +198,18 @@ class OrderRule(DeliveryRule):
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