Commit 4407076f authored by Jean-Paul Smets's avatar Jean-Paul Smets

First working version for transformations (using new transformation code which...

First working version for transformations (using new transformation code which is not yet committed).

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@33187 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent c0c6a0f8
......@@ -27,6 +27,7 @@
##############################################################################
import zope.interface
from zLOG import LOG
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, interfaces
from Products.ERP5.Document.Amount import Amount
......@@ -34,8 +35,15 @@ from Products.ERP5.Document.Amount import Amount
class AmountGeneratorMixin:
"""
This class provides a generic implementation of IAmountGenerator.
It is used by Transformation, Trade Model, Paysheet, etc. It is
designed to support about any transformation process based
on IMappedValue interface. The key idea is that the Amount Generator
Lines and Cell provide either directly or through acquisition the
methods 'getMappedValuePropertyList' and 'getMappedValueBaseCategoryList'
to gather the properties and categories to copy from the model
to the generated amounts.
NOTE: this is an early prototype of implementation
NOTE: this is an first prototype of implementation
"""
# Declarative security
......@@ -47,27 +55,48 @@ class AmountGeneratorMixin:
def _getGlobalPropertyDict(self, context, amount_list=None, rounding=False):
"""
This method can be overridden to define global
properties involved in trade model line calculation
This method must be overridden to define global
properties involved in trade model line or transformation calculation
TODO:
default implementation could use type based method
"""
raise NotImplementedError
# Example of return value
return {
'delivery': 1,
'employee': 100,
'delivery': 1, # Sets the base_amount 'delivery' to 1
# so that it is possible to create models based
# on the number of deliveries (instead of quantity)
'employee': 100, # Sets the base_amount 'employee' to 100
# so that it is possible to create models based
# on the number of employee (instead of quantity)
}
def _getAmountPropertyDict(self, amount, amount_list=None, rounding=False):
"""
This method can be overridden to define local
properties involved in trade model line calculation
This method must be overridden to define per amount local
properties involved in trade model line or transformation calculation
TODO:
default implementation could use type based method
"""
raise NotImplementedError
# Example of return value
return dict(
price=amount.getPrice(),
# Sets the base_amount 'price' to the price
# This base_amount often uses another name though
quantity=amount.getQuantity(),
# Sets the base_amount 'quantity' to the quantity
# This base_amount often uses another name though
unit=(amount.getQuantityUnit() == 'unit') * amount.getQuantity(),
# Sets the base_amount 'unit' to the number of units
# so that it is possible to create models based
# on the number of units
ton=(amount.getQuantityUnit() == 'ton') * amount.getQuantity(),
# more base applications could be set here
# Sets the base_amount 'ton' to the weight in tons
# so that it is possible to create models based
# on the weight in tons
)
security.declareProtected(Permissions.AccessContentsInformation, 'getGeneratedAmountList')
......@@ -82,6 +111,10 @@ class AmountGeneratorMixin:
- is rounding really well supported (ie. before and after aggregation)
very likely not - proxying before or after must be decided
"""
# It is the only place we can import this
from Products.ERP5Type.Document import newTempAmount
portal = self.getPortalObject()
# Initialize base_amount global properties (which can be modified
# during the calculation process)
base_amount = self._getGlobalPropertyDict(context, amount_list=amount_list, rounding=rounding)
......@@ -104,13 +137,13 @@ class AmountGeneratorMixin:
# Each amount in amount_list creates a new amount to take into account
# We thus need to start with a loop on amount_list
for amount in amount_list:
for delivery_amount in amount_list:
# Initialize base_amount with per amount properties
amount_propert_dict = self._getAmountPropertyDict(amount, amount_list=amount_list, rounding=rounding)
amount_propert_dict = self._getAmountPropertyDict(delivery_amount, amount_list=amount_list, rounding=rounding)
base_amount.update(amount_propert_dict)
# Initialize base_amount with total_price for each amount applications
for application in amount.getBaseApplicationList(): # Acquired from Resource
for application in delivery_amount.getBaseApplicationList(): # Acquired from Resource
base_amount[application] = amount.getTotalPrice()
# Browse recursively the trade model and accumulate
......@@ -119,7 +152,7 @@ class AmountGeneratorMixin:
amount_generator_line_list = amount_generator_line.contentValues(portal_type=self.getPortalAmountGeneratorLineTypeList())
# Recursively feed base_amount
if len(amount_generator_line_list):
amount_generator_line_list = amount_generator_line_list.sort(key=lambda x: x.getIntIndex())
amount_generator_line_list.sort(key=lambda x: x.getIntIndex())
for amount_generator_line in amount_generator_line_list:
accumulateAmountList(amount_generator_line)
return
......@@ -133,21 +166,27 @@ class AmountGeneratorMixin:
resource_amount_aggregate = {} # aggregates final line information
value_amount_aggregate = {} # aggregates intermediate line information
for amount_generator_cell in amount_generator_cell_list:
if amount_generator_cell.test(amount): # XXX-JPS getTargetLevel not supported
if amount_generator_cell.test(delivery_amount): # XXX-JPS getTargetLevel not supported
# Case 1: the cell defines a final amount of resource
if amount_generator_cell.getResource():
if amount_generator_cell.getResource() and\
getattr(amount_generator_cell, 'getBaseApplication', None) is not None:
# We must aggregate per resource, variation
key = (amount_generator_cell.getResource(), amount_generator_cell.getVariationText()) # Variation UID, Hash ?
resource_amount_aggregate.setdefault(key, {})
# Then collect the mapped properties (resource, quantity, net_converted_quantity, base_contribution_list, base_application, etc.)
for property_key in amount_generator_cell.getMappedValuePropertyList():
# Handling of property lists ? XXX?
# XXX-JPS Make sure handling of list properties can be handled
resource_amount_aggregate[key][property_key] = amount_generator_cell.getProperty(property_key)
resource_amount_aggregate[key]['category_list'] = amount_generator_cell.getCategoryMembershipList(
amount_generator_cell.getMappedValueBaseCategoryList())
resource_amount_aggregate[key]['resource'] = amount_generator_cell.getResource()
# For final amounts, base_application and id MUST be defined
resource_amount_aggregate[key]['base_application'] = amount_generator_cell.getBaseApplication() # Required
resource_amount_aggregate[key]['id'] = amount_generator_cell.getRelativeUrl().replace('/', '_')
# Case 2: the cell defines a temporary calculation line
else:
elif getattr(amount_generator_cell, 'getBaseContributionList', None) is not None:
# We must aggregate per base_application
# Therefore, base_application MUST be defined
key = amount_generator_cell.getBaseApplication()
value_amount_aggregate.setdefault(key, {})
# Then collect the mapped properties
......@@ -155,6 +194,8 @@ class AmountGeneratorMixin:
value_amount_aggregate[key][property_key] = amount_generator_cell.getProperty(property_key)
value_amount_aggregate[key]['category_list'] = amount_generator_cell.getCategoryMembershipList(
amount_generator_cell.getMappedValueBaseCategoryList())
# For intermediate calculations, base_contribution_list MUST be defined
value_amount_aggregate[key]['base_contribution_list'] = amount_generator_cell.getBaseContributionList() # Required
if resource_amount_aggregate:
for key, property_dict in resource_amount_aggregate.items():
resource, variation_text = key
......@@ -164,6 +205,7 @@ class AmountGeneratorMixin:
else:
category_list = None
base_application = property_dict['base_application']
del property_dict['base_application']
# property_dict should include
# resource - VAT service or a Component in MRP
# quantity - quantity in component in MRP, (what else XXX)
......@@ -177,12 +219,16 @@ class AmountGeneratorMixin:
# values converted to the default management unit
# If not quantity is provided, we consider that the value is 1.0 (XXX is it OK ?)
property_dict['quantity'] = base_amount[amount_generator_line.getBaseApplication()] * \
(property_dict.get('net_converted_quantity', property_dict.get('quantity')), 1.0)
(property_dict.get('net_converted_quantity', property_dict.get('quantity', 1.0)))
# This sounds wrong if cell has getBaseApplication()
# We should not keep net_converted_quantity
if property_dict.get('net_converted_quantity', None) is not None:
del property_dict['net_converted_quantity']
# Create an Amount object
amount = Amount() # XXX-JPS we could use a movement for safety
id = property_dict['id']
del property_dict['id']
amount = newTempAmount(portal, id) # XXX-JPS Could we use a movement for safety ?
if category_list: amount._setCategoryList(category_list)
amount._edit(**property_dict)
if rounding:
......@@ -221,7 +267,11 @@ class AmountGeneratorMixin:
Implementation of a generic transformation algorith which is
applicable to payroll, tax generation and BOMs. Return the
list of amounts with aggregation.
TODO:
- make working sample code
"""
raise NotImplementedError
# Example of return code
result = self.getGeneratedAmountList(context, movement_list=movement_list, rounding=rounding)
return SomeMovementGroup(result)
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002-2010 Nexedi SA 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 AccessControl import ClassSecurityInfo
from Products.ERP5Type.Globals import InitializeClass
from Products.CMFCore.utils import getToolByName
from Products.ERP5Type import Context, interfaces, Permissions
from Products.ERP5Type.Base import Base
from Products.CMFCategory.Renderer import Renderer
from warnings import warn
from zope.interface import implements
class VariatedMixin(Base):
"""
Variated is a mix-in class for all classes which implement
the Variated Interface.
A Variable object is an object which can variate
according to multiple dimensions. Variable objects include:
- a Resource instance
- an Amount instance (a Movement, a DeliveryLine, etc.)
- an Item
- a TransformedResource instance
"""
# Declarative security
security = ClassSecurityInfo()
# Declarative interfaces
implements(interfaces.IVariated)
security.declareProtected(Permissions.AccessContentsInformation,
'getVariationBaseCategoryList')
def getVariationBaseCategoryList(self, omit_optional_variation=0,
omit_option_base_category=None, omit_individual_variation=0):
"""
Return the list of variation base category.
If omit_optional_variation==1, do not include base category
considered as option (ex: industrial_phase).
"""
#XXX backwards compatibility
if omit_option_base_category is not None:
warn("Please use omit_optional_variation instead of"\
" omit_option_base_category.", DeprecationWarning)
omit_optional_variation = omit_option_base_category
vbcl = self._baseGetVariationBaseCategoryList()
if omit_optional_variation == 1:
# XXX First implementation
# option base category list is a portal method, until the creation
# of a good API.
option_base_category_list = self.getPortalOptionBaseCategoryList()
vbcl = [x for x in vbcl if x not in option_base_category_list]
else:
vbcl.extend(self.getOptionalVariationBaseCategoryList())
if omit_individual_variation == 0:
vbcl.extend(self.getIndividualVariationBaseCategoryList())
return vbcl
security.declareProtected(Permissions.AccessContentsInformation,
'_getVariationCategoryList')
def _getVariationCategoryList(self, base_category_list = ()):
if base_category_list is ():
base_category_list = self.getVariationBaseCategoryList()
# base_category_list = self.getVariationRangeBaseCategoryList()
return self.getAcquiredCategoryMembershipList(base_category_list, base=1)
security.declareProtected(Permissions.AccessContentsInformation,
'getVariationCategoryList')
def getVariationCategoryList(self, base_category_list=(),
omit_optional_variation=0, omit_option_base_category=None):
"""
Returns the list of possible variations
"""
#XXX backwards compatibility
if omit_option_base_category is not None:
warn("Please use omit_optional_variation instead of"\
" omit_option_base_category.", DeprecationWarning)
omit_optional_variation = omit_option_base_category
return self._getVariationCategoryList(
base_category_list=base_category_list)
security.declareProtected(Permissions.AccessContentsInformation,
'getVariationCategoryItemList')
def getVariationCategoryItemList(self, base_category_list=(), base=1,
display_id='logical_path', display_base_category=1,
current_category=None, omit_optional_variation=0,
omit_option_base_category=None, **kw):
"""
Returns the list of possible variations
"""
#XXX backwards compatibility
if omit_option_base_category is not None:
warn("Please use omit_optional_variation instead of"\
" omit_option_base_category.", DeprecationWarning)
omit_optional_variation = omit_option_base_category
variation_category_item_list = []
if current_category is not None:
variation_category_item_list.append((current_category,current_category))
if base_category_list is ():
base_category_list = self.getVariationBaseCategoryList()
if omit_optional_variation == 1:
base_category_list = [x for x in base_category_list if x not in
self.getPortalOptionBaseCategoryList()]
# Prepare 2 rendering
portal_categories = self.portal_categories
for base_category in base_category_list:
variation_category_list = self._getVariationCategoryList(
base_category_list=[base_category])
category_list = []
object_list = []
for variation_category_path in variation_category_list:
try:
variation_category = portal_categories.resolveCategory(
variation_category_path)
var_cat_portal_type = variation_category.getPortalType()
except AttributeError:
variation_category_item_list.append((variation_category_path,
variation_category_path))
else:
if var_cat_portal_type != 'Category':
object_list.append(variation_category)
else:
category_list.append(variation_category)
# Render categories
variation_category_item_list.extend(Renderer(
display_base_category=display_base_category,
display_none_category=0, base=base,
current_category=current_category,
display_id=display_id, **kw).\
render(category_list))
# Render the others
variation_category_item_list.extend(Renderer(
base_category=base_category,
display_base_category=display_base_category,
display_none_category=0, base=base,
current_category=current_category,
display_id='title', **kw).\
render(object_list))
return variation_category_item_list
# XXX Is it used ?
# def getVariationCategoryTitleOrIdItemList(self, base_category_list=(),
# base=1, **kw):
# """
# Returns a list of tuples by parsing recursively all categories in a
# given list of base categories. Uses getTitleOrId as method
# """
# return self.getVariationCategoryItemList(
# display_id='title_or_id',
# base_category_list=base_category_list, base=base, **kw)
security.declareProtected(Permissions.ModifyPortalContent,
'_setVariationCategoryList')
def _setVariationCategoryList(self, node_list, base_category_list=()):
if base_category_list is ():
base_category_list = self.getVariationBaseCategoryList()
self._setCategoryMembership(base_category_list,node_list,base=1)
security.declareProtected(Permissions.ModifyPortalContent,
'setVariationCategoryList')
def setVariationCategoryList(self, node_list, base_category_list=()):
self._setVariationCategoryList(node_list,
base_category_list=base_category_list)
self.reindexObject()
# Range
security.declareProtected(Permissions.AccessContentsInformation,
'getVariationRangeBaseCategoryList')
def getVariationRangeBaseCategoryList(self):
"""
Returns possible variation base_category ids.
"""
# Get a portal method which defines a list of
# variation base category
return self.getPortalVariationBaseCategoryList()
security.declareProtected(Permissions.AccessContentsInformation,
'getVariationRangeBaseCategoryItemList')
def getVariationRangeBaseCategoryItemList(self, base=1,
display_id='getTitle',
current_category=None):
"""
Returns possible variations of the resource
as a list of tuples (id, title). This is mostly
useful in ERP5Form instances to generate selection
menus.
"""
return self.portal_categories.getItemList(
self.getVariationBaseCategoryList())
security.declareProtected(Permissions.AccessContentsInformation,
'getVariationBaseCategoryItemList')
def getVariationBaseCategoryItemList(self, display_id='title_or_id',
omit_optional_variation=0, omit_option_base_category=None,
omit_individual_variation=0):
"""
Returns base category of the resource
as a list of tuples (title, id). This is mostly
useful in ERP5Form instances to generate selection
menus.
"""
#XXX backwards compatibility
if omit_option_base_category is not None:
warn("Please use omit_optional_variation instead of"\
" omit_option_base_category.", DeprecationWarning)
omit_optional_variation = omit_option_base_category
variation_base_category_list = self.getVariationBaseCategoryList(
omit_optional_variation=omit_optional_variation,
omit_individual_variation=omit_individual_variation)
result = []
for base_category in variation_base_category_list:
bc = self.portal_categories.resolveCategory(base_category)
result.extend(Renderer(display_base_category=0,
display_none_category=0, base=1,
display_id=display_id).render([bc]))
return result
# Methods for matrix UI widgets
# XXX FIXME Those method are depreciated.
# We now use _asCellRange scripts.
security.declareProtected(Permissions.AccessContentsInformation,
'getLineVariationRangeCategoryItemList')
def getLineVariationRangeCategoryItemList(self):
"""
Returns possible variations in line
"""
try:
resource = self.getDefaultResourceValue()
except AttributeError:
resource = None
if resource is not None:
clist = resource.getVariationRangeCategoryItemList(
base_category_list=self.getVariationBaseCategoryLine(),
root=0)
else:
clist = [(None,None)]
return clist
security.declareProtected(Permissions.AccessContentsInformation,
'getColumnVariationRangeCategoryItemList')
def getColumnVariationRangeCategoryItemList(self):
"""
Returns possible variations in column
"""
try:
resource = self.getDefaultResourceValue()
except AttributeError:
resource = None
if resource is not None:
clist = resource.getVariationRangeCategoryItemList(base_category_list =
self.getVariationBaseCategoryColumn(), root=0)
else:
clist = [(None,None)]
return clist
security.declareProtected(Permissions.AccessContentsInformation,
'getTabVariationRangeCategoryItemList')
def getTabVariationRangeCategoryItemList(self):
"""
Returns possible variations in tab
"""
try:
resource = self.getDefaultResourceValue()
except AttributeError:
resource = None
if resource is not None:
clist = resource.getVariationRangeCategoryItemList(base_category_list =
self.getVariationBaseCategoryTabList(), root=0)
else:
clist = [(None,None)]
return clist
# Help
security.declareProtected(Permissions.AccessContentsInformation,
'getMatrixVariationRangeBaseCategoryList')
def getMatrixVariationRangeBaseCategoryList(self):
"""
Return base categories used in the matrix
"""
line_bc= self.getVariationBaseCategoryLine()
column_bc = self.getVariationBaseCategoryColumn()
# We need to copy values first
tab_bc = list(self.getVariationBaseCategoryTabList())
result = tab_bc
if line_bc is not None and line_bc is not '':
result += [line_bc]
if column_bc is not None and column_bc is not '':
result += [column_bc]
return result
security.declareProtected(Permissions.AccessContentsInformation,
'getVariationRangeCategoryItemList')
def getVariationRangeCategoryItemList(self, base_category_list=(), base=1,
root=1,
display_method_id='getCategoryChildLogicalPathItemList',
display_base_category=1,
current_category=None, **kw):
"""
Returns possible variations
=> [(display, value)]
"""
result = []
if base_category_list is ():
base_category_list = self.getVariationBaseCategoryList()
elif type(base_category_list) is type('a'):
base_category_list = (base_category_list, )
traverse = getToolByName(self, 'portal_categories').unrestrictedTraverse
# Render categories
for base_category in base_category_list:
result += getattr(traverse(base_category), display_method_id)(
base=base,
display_base_category=display_base_category,
display_none_category=0, **kw)
# Return result
return result
security.declareProtected(Permissions.AccessContentsInformation,
'getVariationRangeCategoryList')
def getVariationRangeCategoryList(self, base_category_list=(), base=1,
root=1, current_category=None,
omit_individual_variation=0):
"""
Returns the range of acceptable categories
"""
vrcil = self.getVariationRangeCategoryItemList(
base_category_list=base_category_list,
base=base, root=root,
current_category=current_category,
omit_individual_variation=omit_individual_variation)
# display is on left
return [x[1] for x in vrcil]
# Context related methods
security.declarePublic('newVariationValue')
def newVariationValue(self, context=None, REQUEST=None, **kw):
# PERFORMANCE ISSUE
from Products.ERP5.VariationValue import newVariationValue
if context is None:
return newVariationValue(REQUEST=REQUEST, **kw)
else:
return newVariationValue(context=context, REQUEST=REQUEST, **kw)
# Provide a string representation of variations
security.declarePublic('getVariationText')
def getVariationText(self):
"""
Provide a string representation of variation
"""
category_list = list(self.getVariationCategoryList())
category_list.sort()
return '\n'.join(category_list)
InitializeClass(VariatedMixin)
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved.
#
# 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.
#
##############################################################################
import zope.interface
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, interfaces
from Products.ERP5.Document.Amount import Amount
class VariationMixin:
"""
Although MappedValue are supposed to be independent of any
design choice, we have to implement them as subclasses of
Amount in order to make sure they provide a complete
variation interface. In particular, we want to be able
to call getVariationValue / setVariationValue on a
MappedValue.
(XXX - NO MORE)
XXX - Amount should be remove from here
Interesting Idea: properties and categories of the mapped value
(not of the predicate) could be handled through additional matrix
dimensions rather than through ad-hoc definition.
"""
\ No newline at end of file
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