[Erp5-report] r43576 jm - in /erp5/trunk/products/ERP5: Document/ mixin/ tests/

nobody at svn.erp5.org nobody at svn.erp5.org
Tue Feb 22 19:41:53 CET 2011


Author: jm
Date: Tue Feb 22 19:41:53 2011
New Revision: 43576

URL: http://svn.erp5.org?rev=43576&view=rev
Log:
amount_generator: add support for variation

Modified:
    erp5/trunk/products/ERP5/Document/AmountGeneratorLine.py
    erp5/trunk/products/ERP5/mixin/amount_generator.py
    erp5/trunk/products/ERP5/tests/testBPMCore.py
    erp5/trunk/products/ERP5/tests/testTradeModelLine.py

Modified: erp5/trunk/products/ERP5/Document/AmountGeneratorLine.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/AmountGeneratorLine.py?rev=43576&r1=43575&r2=43576&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/Document/AmountGeneratorLine.py [utf8] (original)
+++ erp5/trunk/products/ERP5/Document/AmountGeneratorLine.py [utf8] Tue Feb 22 19:41:53 2011
@@ -56,17 +56,25 @@ class AmountGeneratorLine(MappedValue, X
                             'getCellAggregateKey')
   def getCellAggregateKey(self):
     """Define a key in order to aggregate amounts at cell level"""
-    return (self.getResource(),
-            self.getVariationText()) # Variation UID, Hash ?
+    resource = self.getResource()
+    if resource:
+      return (resource, self.getVariationText()) # Variation UID, Hash ?
+    # For a pure intermediate line, we need another way to prevent merging:
+    # do not merge if base_application or base_contribution is variated.
+    return frozenset(self.getBaseApplicationList() +
+                     self.getBaseContributionList())
 
   security.declareProtected(Permissions.AccessContentsInformation,
                             'getBaseAmountQuantity')
   @classmethod
-  def getBaseAmountQuantity(cls, delivery_amount, base_application, rounding):
+  def getBaseAmountQuantity(cls, delivery_amount, base_application,
+                            variation_category_list=(), **kw):
     """Default method to compute quantity for the given base_application"""
-    value = delivery_amount.getGeneratedAmountQuantity(base_application)
+    value = delivery_amount.getGeneratedAmountQuantity(
+      base_application, variation_category_list)
     delivery_amount = delivery_amount.getObject()
     if base_application in delivery_amount.getBaseContributionList():
+      assert not variation_category_list
       value += cls._getBaseAmountQuantity(delivery_amount)
     return value
 

Modified: erp5/trunk/products/ERP5/mixin/amount_generator.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/mixin/amount_generator.py?rev=43576&r1=43575&r2=43576&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/mixin/amount_generator.py [utf8] (original)
+++ erp5/trunk/products/ERP5/mixin/amount_generator.py [utf8] Tue Feb 22 19:41:53 2011
@@ -70,24 +70,29 @@ class BaseAmountDict(Implicit):
         yield amount
     yield self
 
-  def contribute(self, base_amount, value):
-    if base_amount in self._frozen:
+  def contribute(self, base_amount, variation_category_list, value):
+    variated_base_amount = base_amount, variation_category_list
+    if variated_base_amount in self._frozen:
+      if variation_category_list:
+        base_amount = (base_amount,) + variation_category_list
       raise ValueError("Can not contribute to %r because this base_amount is"
                        " already applied. Order of Amount Generator Lines is"
-                       " wrong." % base_amount)
-    self._dict[base_amount] = self._getQuantity(base_amount) + value
+                       " wrong." % (base_amount,))
+    self._dict[variated_base_amount] = \
+      self._getQuantity(variated_base_amount) + value
 
-  def _getQuantity(self, base_amount):
+  def _getQuantity(self, variated_base_amount):
     """Get intermediate computed quantity for given base_application"""
     try:
-      return self._dict[base_amount]
+      return self._dict[variated_base_amount]
     except KeyError:
       value = 0
       amount_generator_line = self._amount_generator_line
       for base_amount_dict in self._amount_list:
         base_amount_dict._amount_generator_line = amount_generator_line
-        value += base_amount_dict.getGeneratedAmountQuantity(base_amount)
-      self._dict[base_amount] = value
+        value += base_amount_dict.getGeneratedAmountQuantity(
+          *variated_base_amount)
+      self._dict[variated_base_amount] = value
       return value
 
   getBaseAmountList__roles__ = None # public
@@ -100,7 +105,7 @@ class BaseAmountDict(Implicit):
     return list(self._amount_list)
 
   getGeneratedAmountQuantity__roles__ = None # public
-  def getGeneratedAmountQuantity(self, base_amount):
+  def getGeneratedAmountQuantity(self, base_amount, variation_category_list=()):
     """Get final computed quantity for given base_amount
 
     Note: During a call to getQuantity, this method may be called again by
@@ -108,9 +113,10 @@ class BaseAmountDict(Implicit):
           In this case, the returned value is the last intermediate value just
           before finalization.
     """
-    if base_amount in self._frozen:
-      return self._getQuantity(base_amount)
-    self._frozen.add(base_amount)
+    variated_base_amount = base_amount, variation_category_list
+    if variated_base_amount in self._frozen:
+      return self._getQuantity(variated_base_amount)
+    self._frozen.add(variated_base_amount)
     try:
       method = self._cache[base_amount]
     except KeyError:
@@ -121,8 +127,13 @@ class BaseAmountDict(Implicit):
       if method is None:
         method = self._amount_generator_line.getBaseAmountQuantity
       self._cache[base_amount] = method
-    value = method(self, base_amount, **self._method_kw)
-    self._dict[base_amount] = value
+    if variation_category_list:
+      kw = dict(self._method_kw,
+                variation_category_list=variation_category_list)
+    else:
+      kw = self._method_kw
+    value = method(self, base_amount, **kw)
+    self._dict[variated_base_amount] = value
     return value
 
 
@@ -218,6 +229,8 @@ class AmountGeneratorMixin:
         portal_type=amount_generator_cell_type_list)
       cell_aggregate = {} # aggregates final line information
 
+      base_application_list = self.getBaseApplicationList()
+      base_contribution_list = self.getBaseContributionList()
       for cell in amount_generator_cell_list:
         if not cell.test(delivery_amount):
           if cell is self:
@@ -228,8 +241,8 @@ class AmountGeneratorMixin:
           property_dict = cell_aggregate[key]
         except KeyError:
           cell_aggregate[key] = property_dict = {
-            'base_application_set': set(),
-            'base_contribution_set': set(),
+            'base_application_set': set(base_application_list),
+            'base_contribution_set': set(base_contribution_list),
             'category_list': [],
             'causality_value_list': [],
             'efficiency': self.getEfficiency(),
@@ -250,11 +263,11 @@ class AmountGeneratorMixin:
           cell.getMappedValueBaseCategoryList(), base=1)
         property_dict['category_list'] += category_list
         property_dict['resource'] = cell.getResource()
-        # For final amounts, base_application and id MUST be defined
-        property_dict['base_application_set'].update(
+        if cell is not self:
+          # cells inherit base_application and base_contribution from line
+          property_dict['base_application_set'].update(
             cell.getBaseApplicationList())
-        # For intermediate calculations, base_contribution_list MUST be defined
-        property_dict['base_contribution_set'].update(
+          property_dict['base_contribution_set'].update(
             cell.getBaseContributionList())
         property_dict['causality_value_list'].append(cell)
 
@@ -271,6 +284,12 @@ class AmountGeneratorMixin:
         if causality_value is self and len(cell_aggregate) > 1:
           continue
         base_application_set = property_dict['base_application_set']
+        # allow a single base_application to be variated
+        variation_category_list = tuple(sorted([x for x in base_application_set
+                                                  if x[:12] != 'base_amount/']))
+        if variation_category_list:
+          base_application_set.difference_update(variation_category_list)
+          assert len(base_application_set) == 1
         # property_dict may include
         #   resource - VAT service or a Component in MRP
         #              (if unset, the amount will only be used for reporting)
@@ -286,8 +305,9 @@ class AmountGeneratorMixin:
         # for future simulation of efficiencies.
         # If no quantity is provided, we consider that the value is 1.0
         # (XXX is it OK ?) XXX-JPS Need careful review with taxes
-        quantity = float(sum(map(base_amount.getGeneratedAmountQuantity,
-                                 base_application_set)))
+        quantity = float(sum(base_amount.getGeneratedAmountQuantity(
+                               base_application, variation_category_list)
+                             for base_application in base_application_set))
         for key in 'quantity', 'price', 'efficiency':
           if property_dict.get(key, 0) in (None, ''):
             del property_dict[key]
@@ -324,8 +344,16 @@ class AmountGeneratorMixin:
           quantity /= property_dict.get('efficiency', 1)
         except ZeroDivisionError:
           quantity *= float('inf')
-        for base_contribution in property_dict['base_contribution_set']:
-          base_amount.contribute(base_contribution, quantity)
+        base_contribution_set = property_dict['base_contribution_set']
+        # allow a single base_contribution to be variated
+        variation_category_list = tuple(sorted([x for x in base_contribution_set
+                                                  if x[:12] != 'base_amount/']))
+        if variation_category_list:
+          base_contribution_set.difference_update(variation_category_list)
+          assert len(base_contribution_set) == 1
+        for base_contribution in base_contribution_set:
+          base_amount.contribute(base_contribution, variation_category_list,
+                                 quantity)
 
     is_mapped_value = isinstance(self, MappedValue)
 

Modified: erp5/trunk/products/ERP5/tests/testBPMCore.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/tests/testBPMCore.py?rev=43576&r1=43575&r2=43576&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/tests/testBPMCore.py [utf8] (original)
+++ erp5/trunk/products/ERP5/tests/testBPMCore.py [utf8] Tue Feb 22 19:41:53 2011
@@ -56,8 +56,8 @@ class TestBPMMixin(ERP5TypeTestCase):
   def createCategoriesInCategory(self, category, category_id_list):
     for category_id in category_id_list:
       if not category.hasObject(category_id):
-        category.newContent(portal_type='Category', id = category_id,
-            title = category_id)
+        category.newContent(category_id,
+          title=category_id.replace('_', ' ').title())
 
   @reindex
   def createCategories(self):
@@ -73,6 +73,10 @@ class TestBPMMixin(ERP5TypeTestCase):
     self.createCategoriesInCategory(category_tool.trade_state,
         ['ordered', 'invoiced', 'delivered', 'taxed',
          'state_a', 'state_b', 'state_c', 'state_d', 'state_e'])
+    self.createCategoriesInCategory(category_tool, ('tax_range', 'tax_share'))
+    self.createCategoriesInCategory(category_tool.tax_range,
+                                    ('0_200', '200_inf'))
+    self.createCategoriesInCategory(category_tool.tax_share, 'AB')
 
   @reindex
   def createBusinessProcess(self, **kw):

Modified: erp5/trunk/products/ERP5/tests/testTradeModelLine.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/tests/testTradeModelLine.py?rev=43576&r1=43575&r2=43576&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/tests/testTradeModelLine.py [utf8] (original)
+++ erp5/trunk/products/ERP5/tests/testTradeModelLine.py [utf8] Tue Feb 22 19:41:53 2011
@@ -37,7 +37,7 @@ from Products.ERP5.tests.testBPMCore imp
 from Products.ERP5Type.Base import Base
 from Products.ERP5Type.Utils import simple_decorator
 from DateTime import DateTime
-from Products.ERP5Type.tests.utils import createZODBPythonScript
+from Products.ERP5Type.tests.utils import createZODBPythonScript, updateCellList
 
 
 def save_result_as(name):
@@ -774,6 +774,66 @@ class TestTradeModelLine(TestTradeModelL
 
     self.checkAggregatedAmountList(order)
 
+  def test_03_VariatedModelLine(self):
+    base_amount = self.setBaseAmountQuantityMethod('tax', """\
+def getBaseAmountQuantity(delivery_amount, base_application,
+                          variation_category_list=(), **kw):
+  if variation_category_list:
+    quantity = delivery_amount.getGeneratedAmountQuantity(base_application)
+    tax_range, = variation_category_list
+    if tax_range == 'tax_range/0_200':
+      return min(quantity, 200)
+    else:
+      assert tax_range == 'tax_range/200_inf'
+      return max(0, quantity - 200)
+  return context.getBaseAmountQuantity(delivery_amount, base_application, **kw)
+return getBaseAmountQuantity""")
+    business_process = self.createBusinessProcess()
+    trade_condition = self.createTradeCondition(business_process, (
+      dict(price=0.3,
+           base_application=base_amount,
+           reference='tax1',
+           int_index=10),
+      dict(base_application=base_amount,
+           base_contribution='base_amount/total_tax',
+           reference='tax2',
+           int_index=20),
+      dict(base_application='base_amount/total_tax',
+           base_contribution='base_amount/total',
+           reference='tax3',
+           int_index=30),
+      ))
+    def createCells(line, matrix, base_application=(), base_contribution=()):
+      range_list = [set() for x in iter(matrix).next()]
+      for index in matrix:
+        for x, y in zip(range_list, index):
+          x.add(y)
+      line.setCellRange(*range_list)
+      for index, price in matrix.iteritems():
+        line.newCell(mapped_value_property='price', price=price,
+          base_application_list=[index[i] for i in base_application],
+          base_contribution_list=[index[i] for i in base_contribution],
+          *index)
+    createCells(self['trade_model_line/tax2'], {
+      ('tax_range/0_200', 'tax_share/A'): .1,
+      ('tax_range/0_200', 'tax_share/B'): .2,
+      ('tax_range/200_inf', 'tax_share/A'): .3,
+      ('tax_range/200_inf', 'tax_share/B'): .4,
+      }, base_application=(0,), base_contribution=(1,))
+    createCells(self['trade_model_line/tax3'], {
+      ('tax_share/A',): .5,
+      ('tax_share/B',): .6,
+      }, base_application=(0,))
+    from Products.ERP5Type.Document import newTempAmount
+    for x in ((100, 30, 10, 20, 5, 12),
+              (500, 150, 20, 90, 40, 120, 55, 96)):
+      amount = newTempAmount(self.portal, '_',
+                            quantity=x[0], price=1,
+                            base_contribution=base_amount)
+      amount_list = trade_condition.getGeneratedAmountList((amount,))
+      self.assertEqual(sorted(x[1:]),
+                       sorted(y.getTotalPrice() for y in amount_list))
+
   def test_tradeModelLineWithFixedPrice(self):
     """
       Check it's possible to have fixed quantity on lines. Sometimes we want



More information about the Erp5-report mailing list