Commit 5984db7d authored by Jean-Paul Smets's avatar Jean-Paul Smets

Early prototype of implementation.

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@33119 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 0a818390
...@@ -35,7 +35,7 @@ class AmountGeneratorMixin: ...@@ -35,7 +35,7 @@ class AmountGeneratorMixin:
""" """
This class provides a generic implementation of IAmountGenerator. This class provides a generic implementation of IAmountGenerator.
NOTE: very draft placeholder NOTE: this is an early prototype of implementation
""" """
# Declarative security # Declarative security
...@@ -45,76 +45,184 @@ class AmountGeneratorMixin: ...@@ -45,76 +45,184 @@ class AmountGeneratorMixin:
# Declarative interfaces # Declarative interfaces
zope.interface.implements(interfaces.IAmountGenerator,) zope.interface.implements(interfaces.IAmountGenerator,)
security.declareProtected(Permissions.AccessContentsInformation, 'getAggregatedAmountList') def _getGlobalPropertyDict(self, context, amount_list=None, rounding=False)
def getAggregatedAmountList(self, context, movement_list=None, rounding=False):
""" """
NOTE: very draft placeholder This method can be overridden to define global
properties involved in trade model line calculation
""" """
# Generic Transformation Algorithm raise NotImplementedError
# the same algorithm should be applied to payroll, taxes and MRP return {
# transformations
# Set the common arbitrary base amounts in a dictionary
base_amount = {
'delivery': 1, 'delivery': 1,
'employee': 100, 'employee': 100,
} }
# Set ungrouped empty result list def _getAmountPropertyDict(self, amount, amount_list=None, rounding=False)
ungrouped_result = [] """
This method can be overridden to define local
# Initialise loop - properties involved in trade model line calculation
# movement_list can be for example delivery.contentValues(use='product') """
if movement_list is None: raise NotImplementedError
movement_list = context.getMovementList() return dict(
price=amount.getPrice(),
# Build the amounts from delivery lines and trade model lines quantity=amount.getQuantity(),
for line in movement_list: unit=(amount.getQuantityUnit() == 'unit') * amount.getQuantity(),
# Set line level base application amounts ton=(amount.getQuantityUnit() == 'ton') * amount.getQuantity(),
for application in line.getBaseApplicationList(): # Acquired from Resource
base_amount[application] = total_price=line.getTotalPrice()
# Set line level arbitrary base amounts
base_amount.update(dict(
price=line.getPrice(),
quantity=line.getQuantity(),
unit=(line.getQuantityUnit() == 'unit') * line.getQuantity(),
ton=(line.getQuantityUnit() == 'ton') * line.getQuantity(),
# more base applications could be set here # more base applications could be set here
)) )
# Feed the result with trade model (which must be ordered through
# constraint resolution) security.declareProtected(Permissions.AccessContentsInformation, 'getGeneratedAmountList')
for trade_model_line in trade_model.contentValues(portal_type="Trade Model Line"): def getGeneratedAmountList(self, context, amount_list=None, rounding=False):
if trade_model_line.getResource(): """
# This line has a resource, it is an output line Implementation of a generic transformation algorithm which is
amount = Amount(resource=trade_model_line.getResource(), applicable to payroll, tax generation and BOMs. Return the
# Resource can be a VAT service or a Component in MRP list of amounts without any aggregation.
quantity=base_amount[trade_model_line.getBaseApplication()]
*(trade_model_line.getQuantity() or 1.0), TODO:
# Quantity is used a multiplier (like in transformations for MRP) - getTargetLevel support
price=trade_model_line.getPrice(), - is rounding really well supported (ie. before and after aggregation)
# Price could be empty here (like in Transformation) very likely not - proxying before or after must be decided
# or set to the price of a product (ex. a Stamp) """
# or set to a tax ratio (ie. price per value units) # Initialize base_amount global properties (which can be modified
base_contribution_list=trade_model_line.getBaseContributionList(), # during the calculation process)
# We save here the base contribution so that they can base_amount = self._getGlobalPropertyDict(context, amount_list=amount_list, rounding=rounding)
# be later used by getTotalPrice on the delivery itself portal_roundings = self.portal_roundings
)
ungrouped_result.append(amount) # Set empty result by default
else: result = []
# This line has a no resource, it is an intermediate line
value = base_amount[trade_model_line.getBaseApplication()] * \
(trade_model_line.getQuantity() or 1.0) * \
(trade_model_line.getPrice() or 1.0)
# Quantity is used as a multiplier
# Price is used as a ratio (also a kind of multiplier)
for key in trade_model_line.getBaseContribution():
base_amount[key] += value
# Compute the grouped result - It is still unknown if grouping
# should happen here and in which way - XXX
grouped_result = SomeMovementGroup(ungrouped_result)
# Return result
return grouped_result
# If amount_list is None, then try to collect amount_list from
# the current context
if amount_list is None:
if context.providesIMovementCollection():
amount_list = context.getMovementList()
elif context.providesIAmount():
amount_list = [context]
elif context.providesIAmountList():
amount_list = context
else:
raise ValueError('amount_list must implement IMovementCollection, IAmount or IAmountList')
# 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:
# Initialize base_amount with per amount properties
amount_propert_dict = self._getAmountPropertyDict(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
base_amount[application] = amount.getTotalPrice()
# Browse recursively the trade model and accumulate
# applicable values - first define the recursive method
def accumulateAmountList(amount_generator_line):
amount_generator_line_list = amount_generator_line.contentValues(portal_type=self.getPortalAmountGeneratorLineTypeList()):
# Recursively feed base_amount
if len(amount_generator_line_list):
def compareIndex(a, b):
return cmp(a.getIntIndex(), b.getIntIndex())
amount_generator_line_list = amount_generator_line_list.sort(compareIndex)
for amount_generator_line in amount_generator_line_list:
accumulateAmountList(amount_generator_line)
return
# Try to collect cells and aggregate their mapped properties
# using resource + variation as aggregation key or base_application
# for intermediate lines
amount_generator_cell_list = amount_generator_line.contentValues(portal_type=self.getPortalAmountGeneratorCellTypeList()):
if not amount_generator_cell_list:
# Consider the line as the unique cell
amount_generator_cell_list = [amount_generator_line]
resource_amount_aggregate = {}
value_amount_aggregate = {}
for amount_generator_cell in amount_generator_cell_list:
if amount_generator_cell.test(amount): # XXX-JPS getTargetLevel not supported
# Case 1: the cell defines a final amount of resource
if amount_generator_cell.getResource():
# We must aggregate per resource, variation
key = (amount_generator_cell.getResource(), amount_generator_cell.getVariationText())
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():
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())
# Case 2: the cell defines a temporary calculation line
else:
# We must aggregate per base_application
key = amount_generator_cell.getBaseApplication()
value_amount_aggregate.setdefault(key, {})
# Then collect the mapped properties
for property_key in amount_generator_cell.getMappedValuePropertyList():
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())
if resource_amount_aggregate:
for key, property_dict in resource_amount_aggregate.items():
resource, variation_text = key
if property_dict.get('category_list', None) is not None:
category_list = property_dict['category_list']
del property_dict['category_list']
else:
category_list = None
base_application = 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)
# variation params - color, size, employer share, etc.
# price - empty (like in Transformation) price of a product (ex. a Stamp)
# or tax ratio (ie. price per value units)
# base_contribution_list - needed to produce reports with getTotalPrice
#
# Quantity is used as a multiplier (like in transformations for MRP)
# net_converted_quantity is used preferrably to quantity since we need
# 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)
# 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
if category_list: amount._setCategoryList(category_list)
if rounding:
# We hope here that rounding is sufficient at line level
amount = portal_roundings.getRoundingProxy(amount, context=amount_generator_line)
amount._edit(**property_dict)
result.append(amount)
if value_amount_aggregate:
for base_application, property_dict in value_amount_aggregate.items():
# property_dict should include
# base_contribution_list - needed to produce reports with getTotalPrice
# quantity - quantity in component in MRP, (what else XXX)
# price - empty (like in Transformation) price of a product (ex. a Stamp)
# or tax ratio (ie. price per value units)
base_contribution_list = property_dict['base_contribution_list']
value = base_amount[base_application] * \
(property_dict.get('quantity', None) or 1.0) * \
(property_dict.get('price', None) or 1.0)
# Quantity is used as a multiplier
# Price is used as a ratio (also a kind of multiplier)
for base_key in base_contribution_list:
base_amount[base_key] += value
# Browse recursively the trade model and accumulate
# applicable values - now execute the method
accumulateAmountList(self)
# Purge base_amount of amount applications
for application in amount.getBaseApplicationList(): # Acquired from Resource
base_amount[application] = 0
return result
security.declareProtected(Permissions.AccessContentsInformation, 'getAggregatedAmountList')
def getAggregatedAmountList(self, context, movement_list=None, rounding=False):
"""
Implementation of a generic transformation algorith which is
applicable to payroll, tax generation and BOMs. Return the
list of amounts with aggregation.
"""
raise NotImplementedError
result = self.getGeneratedAmountList(context, movement_list=movement_list, rounding=rounding)
return SomeMovementGroup(result)
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