Commit cd62e742 authored by Jérome Perrin's avatar Jérome Perrin

- Initial implementation of a new way of calulating budget consumptions:

  instead of doing one getInventory by budget cell, we do one getInventoryList
  by budget line.
- Budget variation now uses the variation defined at the proper level, ie if
  this is a line level variation, it uses membership criterion from the line,
  if this is budget level, from the budget. if this is a cell level, from the
  cell. This means that we no longer have to copy all level categories on the
  cell. The UI will have to be updated.
- Test those new features and add some missing tests



git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@37220 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 6eee2db8
......@@ -36,8 +36,7 @@ from Products.ERP5.Variated import Variated
class BudgetLine(Predicate, XMLMatrix, Variated):
"""
BudgetLine a line of budget...
""" A Line of budget, variated in budget cells.
"""
# Default Properties
......@@ -53,7 +52,6 @@ class BudgetLine(Predicate, XMLMatrix, Variated):
, PropertySheet.Budget
, PropertySheet.Amount
, PropertySheet.VariationRange
, PropertySheet.Assignment
)
# CMF Type Definition
......@@ -64,3 +62,49 @@ class BudgetLine(Predicate, XMLMatrix, Variated):
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
security.declareProtected(Permissions.AccessContentsInformation,
'getConsumedBudgetDict')
def getConsumedBudgetDict(self, **kw):
"""Returns all the consumptions in a dict where the keys are the cells, and
the value is the consumed budget.
"""
return self._getBudgetDict(**kw)
security.declareProtected(Permissions.AccessContentsInformation,
'getEngagedBudgetDict')
def getEngagedBudgetDict(self, **kw):
"""Returns all the engagements in a dict where the keys are the cells, and
the value is the engaged budget.
"""
kw.setdefault('explanation_simulation_state',
self.getPortalReservedInventoryStateList() +
self.getPortalCurrentInventoryStateList() +
self.getPortalTransitInventoryStateList())
return self._getBudgetDict(**kw)
def _getBudgetDict(self, **kw):
"""Use getCurrentInventoryList to compute all budget cell consumptions at
once, and returns them in a dict.
"""
budget = self.getParentValue()
budget_model = budget.getSpecialiseValue(portal_type='Budget Model')
if budget_model is None:
return dict()
query_dict = budget_model.getInventoryListQueryDict(self)
query_dict.update(kw)
query_dict.setdefault('ignore_group_by', True)
sign = self.BudgetLine_getConsumptionSign()
budget_dict = dict()
for brain in self.getPortalObject().portal_simulation\
.getCurrentInventoryList(**query_dict):
# XXX total_quantity or total_price ??
previous_value = budget_dict.get(
budget_model._getCellKeyFromInventoryListBrain(brain, self), 0)
budget_dict[budget_model._getCellKeyFromInventoryListBrain(brain, self)] = \
previous_value + brain.total_price * sign
return budget_dict
......@@ -75,7 +75,7 @@ class BudgetModel(Predicate):
return cell_range
def getInventoryQueryDict(self, budget_cell):
"""Returns the query dict to pass to simulation query
"""Returns the query dict to pass to simulation query for a budget cell
"""
query_dict = dict()
for budget_variation in sorted(self.contentValues(
......@@ -83,14 +83,57 @@ class BudgetModel(Predicate):
key=lambda x:x.getIntIndex()):
query_dict.update(
budget_variation.getInventoryQueryDict(budget_cell))
# include dates from the budget
budget = budget_cell.getParentValue().getParentValue()
query_dict.setdefault('from_date', budget.getStartDateRangeMin())
start_date_range_max = budget.getStartDateRangeMax()
if start_date_range_max:
query_dict.setdefault('at_date', start_date_range_max.latestTime())
return query_dict
def getInventoryListQueryDict(self, budget_line):
"""Returns the query dict to pass to simulation query for a budget line
"""
query_dict = dict()
for budget_variation in sorted(self.contentValues(
portal_type=self.getPortalBudgetVariationTypeList()),
key=lambda x:x.getIntIndex()):
variation_query_dict = budget_variation.getInventoryListQueryDict(budget_line)
# Merge group_by argument. All other arguments should not conflict
if 'group_by' in query_dict and 'group_by' in variation_query_dict:
variation_query_dict['group_by'].extend(query_dict['group_by'])
query_dict.update(variation_query_dict)
# include dates from the budget
budget = budget_line.getParentValue()
query_dict.setdefault('from_date', budget.getStartDateRangeMin())
start_date_range_max = budget.getStartDateRangeMax()
if start_date_range_max:
query_dict.setdefault('at_date', start_date_range_max.latestTime())
return query_dict
def _getCellKeyFromInventoryListBrain(self, brain, budget_line):
"""Compute the cell key from an inventory brain, the cell key can be used
to retrieve the budget cell in the corresponding budget line.
"""
cell_key = ()
for budget_variation in sorted(self.contentValues(
portal_type=self.getPortalBudgetVariationTypeList()),
key=lambda x:x.getIntIndex()):
key = budget_variation._getCellKeyFromInventoryListBrain(brain,
budget_line)
if key:
cell_key += (key,)
return cell_key
def asBudgetPredicate(self):
" "
# XXX predicate for line / cell ?
def getBudgetConsumptionMethod(self, budget_cell):
# XXX this API might disapear
# XXX return the method, or compute directly ?
budget_consumption_method = None
for budget_variation in sorted(self.contentValues(
......
......@@ -91,3 +91,74 @@ class BudgetVariation(Predicate):
"""
return {}
def getInventoryListQueryDict(self, budget_line):
"""Returns the query dict to pass to simulation query for a budget line
"""
return {}
def _getCellKeyFromInventoryListBrain(self, brain, budget_line):
"""Compute the cell key from an inventory brain.
The cell key can be used to retrieve the budget cell in the corresponding
budget line using budget_line.getCell
"""
if not self.isMemberOf('budget_variation/budget_cell'):
return None
axis = self.getInventoryAxis()
if not axis:
return None
base_category = self.getProperty('variation_base_category')
if not base_category:
return None
movement = brain.getObject()
# axis 'movement' is simply a category membership on movements
if axis == 'movement':
return movement.getDefaultAcquiredCategoryMembership(base_category,
base=True)
# is it a source brain or destination brain ?
is_source_brain = True
if (brain.node_uid != brain.mirror_node_uid):
is_source_brain = (brain.node_uid == movement.getSourceUid())
elif (brain.section_uid != brain.mirror_section_uid):
is_source_brain = (brain.section_uid == movement.getSourceSectionUid())
elif brain.total_quantity:
is_source_brain = (brain.total_quantity == movement.getQuantity())
else:
raise NotImplementedError('Could not guess brain side')
if axis.endswith('_category') or\
axis.endswith('_category_strict_membership'):
# if the axis is category, we get the node and then returns the category
# from that node
if axis.endswith('_category'):
axis = axis[:-len('_category')]
if axis.endswith('_category_strict_membership'):
axis = axis[:-len('_category_strict_membership')]
if is_source_brain:
if axis == 'node':
node = movement.getSourceValue()
else:
node = movement.getProperty('source_%s_value' % axis)
else:
if axis == 'node':
node = movement.getDestinationValue()
else:
node = movement.getProperty('destination_%s_value' % axis)
if node is not None:
return node.getDefaultAcquiredCategoryMembership(base_category,
base=True)
return None
# otherwise we just return the node
if is_source_brain:
if axis == 'node':
return '%s/%s' % (base_category, movement.getSource())
return '%s/%s' % (base_category,
movement.getProperty('source_%s' % axis))
if axis == 'node':
return '%s/%s' % (base_category, movement.getDestination())
return '%s/%s' % (base_category,
movement.getProperty('destination_%s' % axis))
......@@ -78,7 +78,14 @@ class CategoryBudgetVariation(BudgetVariation):
base_category = self.getProperty('variation_base_category')
if not base_category:
return dict()
for criterion_category in budget_cell.getMembershipCriterionCategoryList():
context = budget_cell
if self.isMemberOf('budget_variation/budget'):
context = budget_cell.getParentValue().getParentValue()
elif self.isMemberOf('budget_variation/budget_line'):
context = budget_cell.getParentValue()
for criterion_category in context.getMembershipCriterionCategoryList():
if '/' not in criterion_category: # safe ...
continue
criterion_base_category, category_url = criterion_category.split('/', 1)
......@@ -94,6 +101,39 @@ class CategoryBudgetVariation(BudgetVariation):
return {axis: criterion_category}
return dict()
def getInventoryListQueryDict(self, budget_line):
"""Returns the query dict to pass to simulation query for a budget line
"""
axis = self.getInventoryAxis()
if not axis:
return dict()
base_category = self.getProperty('variation_base_category')
if not base_category:
return dict()
context = budget_line
if self.isMemberOf('budget_variation/budget'):
context = budget_line.getParentValue()
query_dict = dict()
if axis == 'movement':
axis = 'default_strict_%s_uid' % base_category
query_dict['group_by'] = [axis]
else:
query_dict['group_by_%s' % axis] = True
if axis in ('node', 'section', 'payment', 'function', 'project',
'mirror_section', 'mirror_node' ):
axis = '%s_uid' % axis
for category in context.getVariationCategoryList(
base_category_list=(base_category,)):
if axis.endswith('_uid'):
category = self.getPortalObject().portal_categories\
.getCategoryUid(category)
query_dict.setdefault(axis, []).append(category)
return query_dict
def getBudgetVariationRangeCategoryList(self, context):
"""Returns the Variation Range Category List that can be applied to this
budget.
......
......@@ -132,15 +132,23 @@ class NodeBudgetVariation(BudgetVariation):
if not base_category:
return dict()
budget_line = budget_cell.getParentValue()
portal = self.getPortalObject()
portal_categories = portal.portal_categories
for criterion_category in budget_cell.getMembershipCriterionCategoryList():
context = budget_cell
if self.isMemberOf('budget_variation/budget'):
context = budget_line.getParentValue()
elif self.isMemberOf('budget_variation/budget_line'):
context = budget_line
portal_categories = self.getPortalObject().portal_categories
for criterion_category in context.getMembershipCriterionCategoryList():
if '/' not in criterion_category: # safe ...
continue
criterion_base_category, node_url = criterion_category.split('/', 1)
if criterion_base_category == base_category:
if axis == 'movement':
axis = 'default_%s' % base_category
# TODO: This is not correct if axis is a category (such as
# section_category)
axis = '%s_uid' % axis
if node_url == budget_line.getRelativeUrl():
# This is the "All Other" virtual node
......@@ -155,6 +163,51 @@ class NodeBudgetVariation(BudgetVariation):
return dict()
def getInventoryListQueryDict(self, budget_line):
"""Returns the query dict to pass to simulation query for a budget line
"""
axis = self.getInventoryAxis()
if not axis:
return dict()
base_category = self.getProperty('variation_base_category')
if not base_category:
return dict()
context = budget_line
if self.isMemberOf('budget_variation/budget'):
context = budget_line.getParentValue()
portal_categories = self.getPortalObject().portal_categories
query_dict = dict()
if axis == 'movement':
axis = 'default_%s_uid' % base_category
query_dict['group_by_%s' % axis] = True
# TODO: This is not correct if axis is a category (such as
# section_category)
axis = '%s_uid' % axis
# if we have a virtual "all others" node, we don't set a criterion here.
if self.getProperty('include_virtual_other_node'):
return query_dict
for node_url in context.getVariationCategoryList(
base_category_list=(base_category,)):
query_dict.setdefault(axis, []).append(
portal_categories.getCategoryValue(node_url,
base_category=base_category).getUid())
return query_dict
def _getCellKeyFromInventoryListBrain(self, brain, budget_line):
"""Compute key from inventory brain, with support for "all others" virtual node.
"""
key = BudgetVariation._getCellKeyFromInventoryListBrain(
self, brain, budget_line)
if self.getProperty('include_virtual_other_node'):
if key not in [x[1] for x in
self.getBudgetVariationRangeCategoryList(budget_line)]:
key = '%s/%s' % ( self.getProperty('variation_base_category'),
budget_line.getRelativeUrl() )
return key
def getBudgetLineVariationRangeCategoryList(self, budget_line):
"""Returns the Variation Range Category List that can be applied to this
......
This diff is collapsed.
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