[Erp5-report] r37220 jerome - in /erp5/trunk/products/ERP5: Document/ tests/

nobody at svn.erp5.org nobody at svn.erp5.org
Wed Jul 21 16:44:16 CEST 2010


Author: jerome
Date: Wed Jul 21 16:44:15 2010
New Revision: 37220

URL: http://svn.erp5.org?rev=37220&view=rev
Log:
- 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


Modified:
    erp5/trunk/products/ERP5/Document/BudgetLine.py
    erp5/trunk/products/ERP5/Document/BudgetModel.py
    erp5/trunk/products/ERP5/Document/BudgetVariation.py
    erp5/trunk/products/ERP5/Document/CategoryBudgetVariation.py
    erp5/trunk/products/ERP5/Document/NodeBudgetVariation.py
    erp5/trunk/products/ERP5/tests/testBudget.py

Modified: erp5/trunk/products/ERP5/Document/BudgetLine.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/BudgetLine.py?rev=37220&r1=37219&r2=37220&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/Document/BudgetLine.py [utf8] (original)
+++ erp5/trunk/products/ERP5/Document/BudgetLine.py [utf8] Wed Jul 21 16:44:15 2010
@@ -36,31 +36,75 @@ from Products.ERP5.Variated import Varia
 
 
 class BudgetLine(Predicate, XMLMatrix, Variated):
+  """ A Line of budget, variated in budget cells.
+  """
+
+  # Default Properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.SimpleItem
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.Folder
+                    , PropertySheet.Predicate
+                    , PropertySheet.SortIndex
+                    , PropertySheet.Task
+                    , PropertySheet.Arrow
+                    , PropertySheet.Budget
+                    , PropertySheet.Amount
+                    , PropertySheet.VariationRange
+  )
+
+  # CMF Type Definition
+  meta_type='ERP5 Budget Line'
+  portal_type='Budget Line'    
+  add_permission = Permissions.AddPortalContent
+
+  # 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.
     """
-    BudgetLine  a line of 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
 
-    # Default Properties
-    property_sheets = ( PropertySheet.Base
-                      , PropertySheet.XMLObject
-                      , PropertySheet.SimpleItem
-                      , PropertySheet.CategoryCore
-                      , PropertySheet.Folder
-                      , PropertySheet.Predicate
-                      , PropertySheet.SortIndex
-                      , PropertySheet.Task
-                      , PropertySheet.Arrow
-                      , PropertySheet.Budget
-                      , PropertySheet.Amount
-                      , PropertySheet.VariationRange
-                      , PropertySheet.Assignment
-    )
-
-    # CMF Type Definition
-    meta_type='ERP5 Budget Line'
-    portal_type='Budget Line'    
-    add_permission = Permissions.AddPortalContent
-
-    # Declarative security
-    security = ClassSecurityInfo()
-    security.declareObjectProtected(Permissions.AccessContentsInformation)

Modified: erp5/trunk/products/ERP5/Document/BudgetModel.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/BudgetModel.py?rev=37220&r1=37219&r2=37220&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/Document/BudgetModel.py [utf8] (original)
+++ erp5/trunk/products/ERP5/Document/BudgetModel.py [utf8] Wed Jul 21 16:44:15 2010
@@ -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(

Modified: erp5/trunk/products/ERP5/Document/BudgetVariation.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/BudgetVariation.py?rev=37220&r1=37219&r2=37220&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/Document/BudgetVariation.py [utf8] (original)
+++ erp5/trunk/products/ERP5/Document/BudgetVariation.py [utf8] Wed Jul 21 16:44:15 2010
@@ -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))
+

Modified: erp5/trunk/products/ERP5/Document/CategoryBudgetVariation.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/CategoryBudgetVariation.py?rev=37220&r1=37219&r2=37220&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/Document/CategoryBudgetVariation.py [utf8] (original)
+++ erp5/trunk/products/ERP5/Document/CategoryBudgetVariation.py [utf8] Wed Jul 21 16:44:15 2010
@@ -78,7 +78,14 @@ class CategoryBudgetVariation(BudgetVari
     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(BudgetVari
         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.

Modified: erp5/trunk/products/ERP5/Document/NodeBudgetVariation.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/NodeBudgetVariation.py?rev=37220&r1=37219&r2=37220&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/Document/NodeBudgetVariation.py [utf8] (original)
+++ erp5/trunk/products/ERP5/Document/NodeBudgetVariation.py [utf8] Wed Jul 21 16:44:15 2010
@@ -132,15 +132,23 @@ class NodeBudgetVariation(BudgetVariatio
     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(BudgetVariatio
 
     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

Modified: erp5/trunk/products/ERP5/tests/testBudget.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/tests/testBudget.py?rev=37220&r1=37219&r2=37220&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/tests/testBudget.py [utf8] (original)
+++ erp5/trunk/products/ERP5/tests/testBudget.py [utf8] Wed Jul 21 16:44:15 2010
@@ -28,9 +28,31 @@
 import unittest
 
 import transaction
+from DateTime import DateTime
+
 from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
+from AccessControl import getSecurityManager
 
 class TestBudget(ERP5TypeTestCase):
+  
+  def afterSetUp(self):
+    self.validateRules()
+    product_line = self.portal.portal_categories.product_line
+    if '1' not in product_line.objectIds():
+      category = product_line.newContent(portal_type='Category', id='1')
+      category.newContent(portal_type='Category', id='1.1')
+      category.newContent(portal_type='Category', id='1.2')
+    if '2' not in product_line.objectIds():
+      category = product_line.newContent(portal_type='Category', id='2')
+      category.newContent(portal_type='Category', id='2.1')
+      category.newContent(portal_type='Category', id='2.2')
+
+  def beforeTearDown(self):
+    transaction.abort()
+    self.portal.accounting_module.manage_delObjects(
+       list(self.portal.accounting_module.objectIds()))
+    transaction.commit()
+    self.tic()
 
   def getBusinessTemplateList(self):
     """Return the list of required business templates.
@@ -93,21 +115,513 @@ class TestBudget(ERP5TypeTestCase):
     self.assertEquals(budget_line.getMembershipCriterionCategoryList(), [])
     self.assertEquals(
         budget_line.getMembershipCriterionBaseCategoryList(), [])
+    
 
-    # TODO: create cells and test variation on cell
-  
-  # Other TODOs
-  # test simple category variation in getInventory
-   
-  # test that using a category variation on budget level sets membership
-  # criterion on budget
+    # simuate a request and call Base_edit, which does all the work of creating
+    # cell and setting cell properties.
+    form = budget_line.BudgetLine_view
+    self.portal.REQUEST.other.update(
+        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),
+
+             field_membership_criterion_base_category_list=
+        form.membership_criterion_base_category_list.get_value('default'),
+             field_mapped_value_property_list=
+        form.mapped_value_property_list.get_value('default'),
+
+             field_matrixbox_quantity_cell_0_0_0="5",
+             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[
+               'source/account_module/goods_purchase'],
+        ))
+    budget_line.Base_edit(form_id=form.getId())
+
+    self.assertEquals(1, len(budget_line.contentValues()))
+    budget_cell = budget_line.getCell('source/account_module/goods_purchase')
+    self.assertNotEquals(None, budget_cell)
+
+    self.assertEquals(['source/account_module/goods_purchase'],
+        budget_cell.getMembershipCriterionCategoryList())
+    self.assertEquals(5, budget_cell.getQuantity())
+
+    # there is no budget consumption
+    self.assertEquals(0, budget_cell.getConsumedBudget())
+    self.assertEquals(0, budget_cell.getEngagedBudget())
+    self.assertEquals(5, budget_cell.getAvailableBudget())
+    # there is no budget transfer
+    self.assertEquals(5, budget_cell.getCurrentBalance())
+
+
+  def test_category_budget_cell_variation(self):
+    budget_model = self.portal.budget_model_module.newContent(
+                            portal_type='Budget Model')
+    budget_model.newContent(
+                    portal_type='Category Budget Variation',
+                    int_index=1,
+                    budget_variation='budget_cell',
+                    inventory_axis='node_category',
+                    variation_base_category='account_type',)
+    budget = self.portal.budget_module.newContent(
+                    portal_type='Budget',
+                    specialise_value=budget_model)
+    budget_line = budget.newContent(portal_type='Budget Line')
+    self.assertEquals(['account_type'],
+                      budget_line.getVariationBaseCategoryList())
+
+    variation_range_category_list = \
+       budget_line.BudgetLine_getVariationRangeCategoryList()
+    self.assertTrue(['', ''] in variation_range_category_list)
+    self.assertTrue(['Expense', 'account_type/expense'] in variation_range_category_list)
+
+  def test_category_budget_line_variation(self):
+    # test that using a variation on budget line level sets membership
+    # criterion on budget line
+    budget_model = self.portal.budget_model_module.newContent(
+                            portal_type='Budget Model')
+    budget_model.newContent(
+                    portal_type='Category Budget Variation',
+                    int_index=1,
+                    budget_variation='budget_line',
+                    inventory_axis='section_category',
+                    variation_base_category='group',)
+    budget = self.portal.budget_module.newContent(
+                    portal_type='Budget',
+                    specialise_value=budget_model)
+    budget_line = budget.newContent(portal_type='Budget Line')
+
+    self.assertEquals(['group'],
+                      budget_line.getVariationBaseCategoryList())
+
+    variation_range_category_list = \
+       budget_line.BudgetLine_getVariationRangeCategoryList()
+
+    self.assertTrue(['', ''] in variation_range_category_list)
+    self.assertTrue(['Demo Group', 'group/demo_group'] in variation_range_category_list)
+    
+    budget_line.edit(variation_category_list=['group/demo_group'])
+    self.assertEquals(['group'],
+        budget_line.getMembershipCriterionBaseCategoryList())
+    self.assertEquals(['group/demo_group'],
+        budget_line.getMembershipCriterionCategoryList())
+
+
+  def test_category_budget_variation(self):
+    budget_model = self.portal.budget_model_module.newContent(
+                            portal_type='Budget Model')
+    budget_model.newContent(
+                    portal_type='Category Budget Variation',
+                    int_index=1,
+                    budget_variation='budget',
+                    inventory_axis='section_category',
+                    variation_base_category='group',)
+    budget = self.portal.budget_module.newContent(
+                    portal_type='Budget',
+                    specialise_value=budget_model)
+
+    self.assertEquals(['group'],
+                      budget.getVariationBaseCategoryList())
+
+    variation_range_category_list = \
+       budget.Budget_getVariationRangeCategoryList()
+
+    self.assertTrue(['', ''] in variation_range_category_list)
+    self.assertTrue(['Demo Group', 'group/demo_group'] in variation_range_category_list)
+
+    # setting this variation on the budget also sets membership
+    budget.edit(variation_category_list=['group/demo_group'])
+    self.assertEquals('demo_group', budget.getGroup())
+    self.assertEquals('Demo Group', budget.getGroupTitle())
+
+  def test_simple_consumption(self):
+    budget_model = self.portal.budget_model_module.newContent(
+                            portal_type='Budget Model')
+    budget_model.newContent(
+                    portal_type='Category Budget Variation',
+                    int_index=1,
+                    budget_variation='budget',
+                    inventory_axis='section_category',
+                    variation_base_category='group',)
+    budget_model.newContent(
+                    portal_type='Node Budget Variation',
+                    int_index=2,
+                    budget_variation='budget_cell',
+                    inventory_axis='node',
+                    variation_base_category='source',
+                    aggregate_value_list=(
+                      self.portal.account_module.goods_purchase,
+                      self.portal.account_module.fixed_assets,
+                    ))
+    budget_model.newContent(
+                    portal_type='Category Budget Variation',
+                    int_index=3,
+                    budget_variation='budget_cell',
+                    inventory_axis='node_category',
+                    variation_base_category='account_type',)
+
+    budget = self.portal.budget_module.newContent(
+                    portal_type='Budget',
+                    start_date_range_min=DateTime(2000, 1, 1),
+                    start_date_range_max=DateTime(2000, 12, 31),
+                    specialise_value=budget_model)
+
+    budget.edit(variation_category_list=['group/demo_group'])
+    budget_line = budget.newContent(portal_type='Budget Line')
+
+    # set the range, this will adjust the matrix
+    budget_line.edit(
+        variation_category_list=(
+          'source/account_module/goods_purchase',
+          'source/account_module/fixed_assets',
+          'account_type/expense',
+          'account_type/asset', ))
+
+    # simuate a request and call Base_edit, which does all the work of creating
+    # cell and setting cell properties.
+    form = budget_line.BudgetLine_view
+    self.portal.REQUEST.other.update(
+        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),
+
+             field_membership_criterion_base_category_list=
+        form.membership_criterion_base_category_list.get_value('default'),
+             field_mapped_value_property_list=
+        form.mapped_value_property_list.get_value('default'),
+
+             field_matrixbox_quantity_cell_0_0_0="",
+             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[],
+             field_matrixbox_quantity_cell_1_0_0="2",
+             field_matrixbox_membership_criterion_category_list_cell_1_0_0=[
+               'source/account_module/fixed_assets',
+               'account_type/asset'],
+             field_matrixbox_quantity_cell_0_1_0="1",
+             field_matrixbox_membership_criterion_category_list_cell_0_1_0=[
+               'source/account_module/goods_purchase',
+               'account_type/expense'],
+             field_matrixbox_quantity_cell_1_1_0="",
+             field_matrixbox_membership_criterion_category_list_cell_1_1_0=[],
+        ))
+    budget_line.Base_edit(form_id=form.getId())
+
+    self.assertEquals(2, len(budget_line.contentValues()))
+    budget_cell = budget_line.getCell('source/account_module/goods_purchase',
+                                      'account_type/expense')
+    self.assertNotEquals(None, budget_cell)
+    self.assertEquals(
+        dict(from_date=DateTime(2000, 1, 1),
+             at_date=DateTime(2000, 12, 31).latestTime(),
+             node_category='account_type/expense',
+             node_uid=self.portal.account_module.goods_purchase.getUid(),
+             section_category='group/demo_group',),
+        budget_model.getInventoryQueryDict(budget_cell))
+
+    budget_cell = budget_line.getCell('source/account_module/fixed_assets',
+                                      'account_type/asset')
+    self.assertNotEquals(None, budget_cell)
+    self.assertEquals(
+        dict(from_date=DateTime(2000, 1, 1),
+             at_date=DateTime(2000, 12, 31).latestTime(),
+             node_category='account_type/asset',
+             node_uid=self.portal.account_module.fixed_assets.getUid(),
+             section_category='group/demo_group',),
+        budget_model.getInventoryQueryDict(budget_cell))
+
+    self.assertEquals(
+        dict(from_date=DateTime(2000, 1, 1),
+             at_date=DateTime(2000, 12, 31).latestTime(),
+             node_category=['account_type/expense', 'account_type/asset'],
+             node_uid=[self.portal.account_module.goods_purchase.getUid(),
+                       self.portal.account_module.fixed_assets.getUid()],
+             section_category=['group/demo_group'],
+             group_by_node_category=True,
+             group_by_node=True,
+             group_by_section_category=True,
+             ),
+        budget_model.getInventoryListQueryDict(budget_line))
+
+
+    atransaction = self.portal.accounting_module.newContent(
+                  portal_type='Accounting Transaction',
+                  resource_value=self.portal.currency_module.euro,
+                  source_section_value=self.portal.organisation_module.my_organisation,
+                  start_date=DateTime(2000, 1, 2))
+    atransaction.newContent(
+                  portal_type='Accounting Transaction Line',
+                  source_value=self.portal.account_module.goods_purchase,
+                  source_debit=100)
+    atransaction.newContent(
+                  portal_type='Accounting Transaction Line',
+                  source_value=self.portal.account_module.fixed_assets,
+                  source_credit=100)
+    atransaction.stop()
+
+    transaction.commit()
+    self.tic()
+
+    self.assertEquals(
+      {('source/account_module/fixed_assets', 'account_type/asset'): -100.0,
+       ('source/account_module/goods_purchase', 'account_type/expense'): 100.0},
+        budget_line.getConsumedBudgetDict())
+
+    self.assertEquals(
+      {('source/account_module/fixed_assets', 'account_type/asset'): -100.0,
+       ('source/account_module/goods_purchase', 'account_type/expense'): 100.0},
+        budget_line.getEngagedBudgetDict())
+      
+  def test_all_other_and_strict_consumption(self):
+    # tests consumptions, by using "all other" virtual node on a node budget
+    # variation, and strict membership on category budget variation
+    budget_model = self.portal.budget_model_module.newContent(
+                            portal_type='Budget Model')
+    budget_model.newContent(
+                    portal_type='Category Budget Variation',
+                    int_index=1,
+                    budget_variation='budget',
+                    inventory_axis='section_category_strict_membership',
+                    variation_base_category='group',)
+    budget_model.newContent(
+                    portal_type='Node Budget Variation',
+                    int_index=2,
+                    budget_variation='budget_cell',
+                    inventory_axis='node',
+                    variation_base_category='source',
+                    aggregate_value_list=(
+                      self.portal.account_module.goods_purchase,),
+                    include_virtual_other_node=True)
+    budget_model.newContent(
+                    portal_type='Category Budget Variation',
+                    int_index=3,
+                    budget_variation='budget_cell',
+                    inventory_axis='node_category_strict_membership',
+                    variation_base_category='account_type',)
+
+    budget = self.portal.budget_module.newContent(
+                    portal_type='Budget',
+                    start_date_range_min=DateTime(2000, 1, 1),
+                    start_date_range_max=DateTime(2000, 12, 31),
+                    specialise_value=budget_model)
+
+    budget.edit(variation_category_list=['group/demo_group/sub1'])
+    budget_line = budget.newContent(portal_type='Budget Line')
+
+    # set the range, this will adjust the matrix
+    budget_line.edit(
+        variation_category_list=(
+          'source/account_module/goods_purchase',
+          'source/%s' % budget_line.getRelativeUrl(), # this is 'all others'
+          'account_type/expense',
+          'account_type/asset', ))
+
+    # simuate a request and call Base_edit, which does all the work of creating
+    # cell and setting cell properties.
+    form = budget_line.BudgetLine_view
+    self.portal.REQUEST.other.update(
+        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),
+
+             field_membership_criterion_base_category_list=
+        form.membership_criterion_base_category_list.get_value('default'),
+             field_mapped_value_property_list=
+        form.mapped_value_property_list.get_value('default'),
+
+             field_matrixbox_quantity_cell_0_0_0="",
+             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[],
+             field_matrixbox_quantity_cell_1_0_0="2",
+             field_matrixbox_membership_criterion_category_list_cell_1_0_0=[
+               'source/%s' % budget_line.getRelativeUrl(),
+               'account_type/asset'],
+             field_matrixbox_quantity_cell_0_1_0="1",
+             field_matrixbox_membership_criterion_category_list_cell_0_1_0=[
+               'source/account_module/goods_purchase',
+               'account_type/expense'],
+             field_matrixbox_quantity_cell_1_1_0="",
+             field_matrixbox_membership_criterion_category_list_cell_1_1_0=[],
+        ))
+    budget_line.Base_edit(form_id=form.getId())
+
+    self.assertEquals(2, len(budget_line.contentValues()))
+
+    self.assertEquals(
+        dict(from_date=DateTime(2000, 1, 1),
+             at_date=DateTime(2000, 12, 31).latestTime(),
+             node_category_strict_membership=['account_type/expense',
+                                              'account_type/asset'],
+             section_category_strict_membership=['group/demo_group/sub1'],
+             group_by_node_category_strict_membership=True,
+             group_by_node=True,
+             group_by_section_category_strict_membership=True,
+             ),
+        budget_model.getInventoryListQueryDict(budget_line))
+
+
+    atransaction = self.portal.accounting_module.newContent(
+                  portal_type='Accounting Transaction',
+                  resource_value=self.portal.currency_module.euro,
+                  source_section_value=self.portal.organisation_module.my_organisation,
+                  start_date=DateTime(2000, 1, 2))
+    atransaction.newContent(
+                  portal_type='Accounting Transaction Line',
+                  source_value=self.portal.account_module.goods_purchase,
+                  source_debit=100)
+    atransaction.newContent(
+                  portal_type='Accounting Transaction Line',
+                  source_value=self.portal.account_module.fixed_assets,
+                  source_credit=100)
+    atransaction.stop()
+
+    transaction.commit()
+    self.tic()
+
+    self.assertEquals(
+      {('source/%s' % budget_line.getRelativeUrl(), 'account_type/asset'): -100.0,
+       ('source/account_module/goods_purchase', 'account_type/expense'): 100.0},
+        budget_line.getConsumedBudgetDict())
+
+    self.assertEquals(
+      {('source/%s' % budget_line.getRelativeUrl(), 'account_type/asset'): -100.0,
+       ('source/account_module/goods_purchase', 'account_type/expense'): 100.0},
+        budget_line.getEngagedBudgetDict())
+      
+
+  def test_consumption_movement_category(self):
+    # test for budget consumption using movement category
+    budget_model = self.portal.budget_model_module.newContent(
+                            portal_type='Budget Model')
+    budget_model.newContent(
+                    portal_type='Category Budget Variation',
+                    int_index=1,
+                    budget_variation='budget',
+                    inventory_axis='section_category',
+                    variation_base_category='group',)
+    budget_model.newContent(
+                    portal_type='Node Budget Variation',
+                    int_index=2,
+                    budget_variation='budget_cell',
+                    inventory_axis='node',
+                    variation_base_category='source',
+                    aggregate_value_list=(
+                      self.portal.account_module.goods_purchase,
+                      self.portal.account_module.fixed_assets,
+                    ))
+    budget_model.newContent(
+                    portal_type='Category Budget Variation',
+                    int_index=3,
+                    budget_variation='budget_cell',
+                    inventory_axis='movement',
+                    variation_base_category='product_line',)
+
+    budget = self.portal.budget_module.newContent(
+                    portal_type='Budget',
+                    start_date_range_min=DateTime(2000, 1, 1),
+                    start_date_range_max=DateTime(2000, 12, 31),
+                    specialise_value=budget_model)
+
+    budget.edit(variation_category_list=['group/demo_group'])
+    budget_line = budget.newContent(portal_type='Budget Line')
+
+    # set the range, this will adjust the matrix
+    budget_line.edit(
+        variation_category_list=(
+          'source/account_module/goods_purchase',
+          'source/account_module/fixed_assets',
+          'product_line/1',
+          'product_line/1/1.1',
+          'product_line/1/1.2', ))
+
+    # simuate a request and call Base_edit, which does all the work of creating
+    # cell and setting cell properties.
+    form = budget_line.BudgetLine_view
+    self.portal.REQUEST.other.update(
+        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),
+
+             field_membership_criterion_base_category_list=
+        form.membership_criterion_base_category_list.get_value('default'),
+             field_mapped_value_property_list=
+        form.mapped_value_property_list.get_value('default'),
+
+            # this cell will be a summary cell
+             field_matrixbox_quantity_cell_0_0_0="2",
+             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[
+               'source/account_module/goods_purchase',
+               'product_line/1'],
+             field_matrixbox_quantity_cell_1_0_0="",
+             field_matrixbox_membership_criterion_category_list_cell_1_0_0=[],
+             field_matrixbox_quantity_cell_0_1_0="2",
+             field_matrixbox_membership_criterion_category_list_cell_0_1_0=[
+               'source/account_module/goods_purchase',
+               'product_line/1/1.1'],
+             field_matrixbox_quantity_cell_1_1_0="",
+             field_matrixbox_membership_criterion_category_list_cell_1_1_0=[],
+             field_matrixbox_quantity_cell_0_2_0="",
+             field_matrixbox_membership_criterion_category_list_cell_0_2_0=[],
+             field_matrixbox_quantity_cell_1_2_0="",
+             field_matrixbox_membership_criterion_category_list_cell_1_2_0=[],
+        ))
+    budget_line.Base_edit(form_id=form.getId())
+
+    self.assertEquals(2, len(budget_line.contentValues()))
+
+    product_line_1 = self.portal.portal_categories.product_line['1']
+    product_line_1_11 = product_line_1['1.1']
+    product_line_1_12 = product_line_1['1.2']
+
+    self.assertEquals(
+        dict(from_date=DateTime(2000, 1, 1),
+             at_date=DateTime(2000, 12, 31).latestTime(),
+             node_uid=[self.portal.account_module.goods_purchase.getUid(),
+                       self.portal.account_module.fixed_assets.getUid(),],
+             default_strict_product_line_uid=[product_line_1.getUid(),
+                                       product_line_1_11.getUid(),
+                                       product_line_1_12.getUid(),],
+             section_category=['group/demo_group'],
+             group_by=['default_strict_product_line_uid'],
+             group_by_node=True,
+             group_by_section_category=True,
+             ),
+        budget_model.getInventoryListQueryDict(budget_line))
+
+
+    atransaction = self.portal.accounting_module.newContent(
+                  portal_type='Accounting Transaction',
+                  resource_value=self.portal.currency_module.euro,
+                  source_section_value=self.portal.organisation_module.my_organisation,
+                  start_date=DateTime(2000, 1, 2))
+    atransaction.newContent(
+                  portal_type='Accounting Transaction Line',
+                  source_value=self.portal.account_module.goods_purchase,
+                  product_line_value=product_line_1_11,
+                  source_debit=100)
+    atransaction.newContent(
+                  portal_type='Accounting Transaction Line',
+                  source_value=self.portal.account_module.fixed_assets,
+                  product_line_value=product_line_1_12,
+                  source_credit=100)
+    atransaction.stop()
+
+    transaction.commit()
+    self.tic()
+
+    self.assertEquals(
+      {('source/account_module/fixed_assets', 'product_line/1/1.2'): -100.0,
+       ('source/account_module/goods_purchase', 'product_line/1/1.1'): 100.0,
+       # summary line is automatically added (TODO)
+##       ('source/account_module/goods_purchase', 'product_line/1'): 100.0
+       },
+        budget_line.getConsumedBudgetDict())
+
+    self.assertEquals(
+      {('source/account_module/fixed_assets', 'product_line/1/1.2'): -100.0,
+       ('source/account_module/goods_purchase', 'product_line/1/1.1'): 100.0,
+       # summary line is automatically added (TODO)
+##       ('source/account_module/goods_purchase', 'product_line/1'): 100.0 
+       },
+        budget_line.getEngagedBudgetDict())
+
+
+    # Other TODOs:
+
+  # section_category & summary
 
-  # test that using a variation on budget line level sets membership
-  # criterion on budget line (and budget cell or not ?)
+  # resource/price currency on budget ?
 
+  # test virtual all others when cloning an existing budget
 
-  # test that using a category variation on budget level is used in inventory
-  # calculation 
+  # predicates 
 
 
 def test_suite():




More information about the Erp5-report mailing list