[Erp5-report] r25495 - in /erp5/trunk/products/ERP5: Document/ PropertySheet/ tests/

nobody at svn.erp5.org nobody at svn.erp5.org
Mon Feb 9 10:36:06 CET 2009


Author: yusei
Date: Mon Feb  9 10:35:58 2009
New Revision: 25495

URL: http://svn.erp5.org?rev=25495&view=rev
Log:
Added open order functionality.

Added:
    erp5/trunk/products/ERP5/Document/OpenOrderRule.py
    erp5/trunk/products/ERP5/Document/PeriodicityLine.py
    erp5/trunk/products/ERP5/PropertySheet/OpenOrder.py
    erp5/trunk/products/ERP5/PropertySheet/PeriodicityTerm.py
    erp5/trunk/products/ERP5/tests/testOpenOrder.py

Added: erp5/trunk/products/ERP5/Document/OpenOrderRule.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/OpenOrderRule.py?rev=25495&view=auto
==============================================================================
--- erp5/trunk/products/ERP5/Document/OpenOrderRule.py (added)
+++ erp5/trunk/products/ERP5/Document/OpenOrderRule.py [utf8] Mon Feb  9 10:35:58 2009
@@ -1,0 +1,187 @@
+##############################################################################
+#
+# Copyright (c) 2009 Nexedi KK, Nexedi SA and Contributors. All Rights Reserved.
+#
+# 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 import Permissions, PropertySheet, Interface
+from Products.ERP5.Document.Rule import Rule
+from Products.ERP5.Document.DeliveryRule import DeliveryRule
+from zLOG import LOG, WARNING
+from DateTime import DateTime
+
+
+class OpenOrderRule(DeliveryRule):
+  """
+  Order Rule object make sure an Order in the simulation
+  is consistent with the real order
+
+  WARNING: what to do with movement split ?
+  """
+  # CMF Type Definition
+  meta_type = 'ERP5 Open Order Rule'
+  portal_type = 'Open Order Rule'
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+  
+  __implements__ = ( Interface.Predicate,
+                     Interface.Rule )
+
+  # Default Properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore
+                    , PropertySheet.Task
+                    )
+
+  # Simulation workflow
+  security.declareProtected(Permissions.ModifyPortalContent, 'expand')
+  def expand(self, applied_rule, force=0, **kw):
+    """
+      Expands the Order to a new simulation tree.
+      expand is only allowed to modify a simulation movement if it doesn't
+      have a delivery relation yet.
+
+      If the movement is in ordered or planned state, has no delivered
+      child, and is not in order, it can be deleted.
+      Else, if the movement is in ordered or planned state, has no
+      delivered child, and is in order, it can be modified.
+      Else, it cannot be modified.
+    """
+    movement_type = 'Simulation Movement'
+    order = applied_rule.getDefaultCausalityValue()
+    if order is not None:
+      order_movement_list = order.getMovementList(
+        portal_type=order.getPortalOrderMovementTypeList())
+
+      for order_movement in order_movement_list:
+        last_simulation_movement = self._getLastSimulationMovementValue(applied_rule, order_movement)
+        if last_simulation_movement is not None:
+          schedule_start_date = last_simulation_movement.getStartDate()
+          schedule_list = self._getOrderDateScheduleTupleList(order_movement, schedule_start_date, **kw)
+        else:
+          # Because order's start_date might be matched with the periodicity.
+          order_start_date = order.getStartDate()
+          schedule_start_date = order_start_date-1
+          schedule_list = [date_pair
+                           for date_pair in self._getOrderDateScheduleTupleList(order_movement, schedule_start_date, **kw)
+                           if date_pair[0]>=order_start_date]
+
+        for start_date, stop_date in schedule_list:
+          property_dict = {'start_date':start_date, 'stop_date':stop_date}
+          property_dict = self._getExpandablePropertyDict(order_movement,
+                                                          property_dict)
+          simulation_movement = applied_rule.newContent(
+            portal_type=movement_type,
+            order_value=order_movement,
+            order_ratio=1,
+            delivery_ratio=1,
+            deliverable=1,
+            **property_dict
+            )
+
+      # Mark that expand finished.
+      applied_rule.setLastExpandSimulationState(order.getSimulationState())
+    # Pass to base class
+    Rule.expand(self, applied_rule, force=force, **kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation, 'isStable')
+  def isStable(self, applied_rule):
+    """
+    Checks that the applied_rule is stable
+    """
+    LOG('OrderRule.isStable', WARNING, 'Not Implemented')
+    return 1
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'isDivergent')
+  def isDivergent(self, movement):
+    """
+    Checks that the movement is divergent
+    """
+    return Rule.isDivergent(self, movement)
+
+  def _getExpandablePropertyDict(self, order_movement, property_dict=None):
+    property_list = (
+      'title',
+      'reference',
+      'description',
+      'int_index',
+      'source',
+      'source_section',
+      'source_function',
+      'source_trade_list',
+      'destination',
+      'destination_section',
+      'destination_function',
+      'resource',
+      'variation_category_list',
+      'variation_property_dict',
+      'base_contribution_list',
+      'aggregate_list',
+      'price',
+      'price_currency',
+      'quantity',
+      'quantity_unit',
+      )
+    if property_dict is None:
+      property_dict = {}
+    for property_name in property_list:
+      if not property_name in property_dict:
+        property_dict[property_name] = order_movement.getProperty(property_name)
+    return property_dict
+
+  def _getLastSimulationMovementValue(self, applied_rule, order_movement):
+    result = applied_rule.searchFolder(order_uid=order_movement.getUid(),
+                                       sort_on=[('movement.start_date','DESC')])
+    if len(result)>0:
+      return result[0].getObject()
+    else:
+      return None
+
+  def _getOrderDateScheduleTupleList(self, order_movement, schedule_start_date,
+                                     calculation_base_date=None, **kw):
+    if calculation_base_date is None:
+      # This is NOW
+      calculation_base_date = DateTime()
+
+    getPeriodicityLineValueList = order_movement._getTypeBasedMethod('getPeriodicityLineValueList')
+    if getPeriodicityLineValueList is None:
+      raise RuntimeError, "Cannot find getPeriodicityLineValueList script"
+    schedule_stop_date = (calculation_base_date+
+                          order_movement.getForecastingTermDays())
+    if schedule_stop_date > order_movement.getStopDate():
+      schedule_stop_date = order_movement.getStopDate()
+    
+    periodicity_line_list = getPeriodicityLineValueList(schedule_start_date,
+                                                        schedule_stop_date)
+    result = []
+    for periodicity_line in periodicity_line_list:
+      result.extend(periodicity_line.getDatePeriodList(schedule_start_date,
+                                                       schedule_stop_date))
+    return result

Added: erp5/trunk/products/ERP5/Document/PeriodicityLine.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/PeriodicityLine.py?rev=25495&view=auto
==============================================================================
--- erp5/trunk/products/ERP5/Document/PeriodicityLine.py (added)
+++ erp5/trunk/products/ERP5/Document/PeriodicityLine.py [utf8] Mon Feb  9 10:35:58 2009
@@ -1,0 +1,106 @@
+##############################################################################
+#
+# Copyright (c) 2009 Nexedi KK, Nexedi SA and Contributors. All Rights Reserved.
+#
+# 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 import Permissions, PropertySheet
+from Products.ERP5.Document.Path import Path
+from Products.ERP5.Document.Alarm import PeriodicityMixin
+
+
+class PeriodicityLineMixin(PeriodicityMixin):
+  """
+  A class extends PeriodicityMixin to add term.
+  """
+
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  property_sheets = (PropertySheet.PeriodicityTerm,)
+
+  security.declareProtected(Permissions.AccessContentsInformation, 'getPeriodicityTermStopDate')
+  def getPeriodicityTermStopDate(self, start_date, default=None):
+    """Return periodicity term's stop_date by calculating periodicity term
+    length with a start_date argument and other own properties.
+    """
+    length_number = self.getPeriodicityTermLengthNumber()
+    time_scale = self.getPeriodicityTermTimeScale()
+    scope_type = self.getPeriodicityTermScopeType()
+
+    if scope_type:
+      method = self._getTypeBasedMethod('calculateScopeTypeStopDate')
+      if method is None:
+        raise RuntimeError, 'Type based method calculateScopeTypeStopDate does not exist.'
+      else:
+        return method(scope_type, start_date)
+    elif time_scale:
+      if time_scale=='day':
+        day = length_number
+        return start_date+day
+      else:
+        raise RuntimeError, 'Unknown time scale: %s' % time_scale
+    else:
+      return None
+
+class PeriodicityLine(Path, PeriodicityLineMixin):
+  """
+  A class defines how often an order is made.
+  """
+
+  meta_type = 'ERP5 Periodicity Line'
+  portal_type = 'Periodicity Line'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+  isPredicate = 1
+
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  property_sheets = Path.property_sheets+(PropertySheet.Periodicity,
+                                          PropertySheet.PeriodicityTerm,
+                                          )
+
+  security.declareProtected(Permissions.AccessContentsInformation, 'getDatePeriodList')
+  def getDatePeriodList(self, from_date, to_date):
+    """
+    Returns a list of a tuple of start_date and stop_date.
+    """
+    effective_date = self.getEffectiveDate()
+    expiration_date = self.getExpirationDate()
+    result = []
+
+    if effective_date is not None and from_date < effective_date:
+      from_date = effective_date
+    if expiration_date is not None and to_date > expiration_date:
+      to_date = expiration_date
+
+    next_start_date = self.getNextPeriodicalDate(from_date)
+    while next_start_date <= to_date:
+      result.append((next_start_date,
+                     self.getPeriodicityTermStopDate(next_start_date)))
+      next_start_date = self.getNextPeriodicalDate(next_start_date)
+    return result

Added: erp5/trunk/products/ERP5/PropertySheet/OpenOrder.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/PropertySheet/OpenOrder.py?rev=25495&view=auto
==============================================================================
--- erp5/trunk/products/ERP5/PropertySheet/OpenOrder.py (added)
+++ erp5/trunk/products/ERP5/PropertySheet/OpenOrder.py [utf8] Mon Feb  9 10:35:58 2009
@@ -1,0 +1,39 @@
+##############################################################################
+#
+# Copyright (c) 2009 Nexedi KK, Nexedi SA and Contributors. All Rights Reserved.
+#
+# 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.
+#
+##############################################################################
+
+class OpenOrder:
+  """
+  OpenOrder provides a way to order same items repeatedly. This property sheet
+  provides properties which are brought by the nature of repetition.
+  """
+
+  _properties = (
+    {   'id'          : 'forecasting_term_days',
+        'description' : 'A number of days to be forecasted.',
+        'type'        : 'int',
+        'mode'        : 'w' },
+    )

Added: erp5/trunk/products/ERP5/PropertySheet/PeriodicityTerm.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/PropertySheet/PeriodicityTerm.py?rev=25495&view=auto
==============================================================================
--- erp5/trunk/products/ERP5/PropertySheet/PeriodicityTerm.py (added)
+++ erp5/trunk/products/ERP5/PropertySheet/PeriodicityTerm.py [utf8] Mon Feb  9 10:35:58 2009
@@ -1,0 +1,46 @@
+##############################################################################
+#
+# Copyright (c) 2009 Nexedi KK, Nexedi SA and Contributors. All Rights Reserved.
+#
+# 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.
+#
+##############################################################################
+
+class PeriodicityTerm:
+  """
+  A property sheet extends Periodicity to add term.
+  """
+
+  _properties = (
+    {   'id'          : 'periodicity_term_scope_type',
+        'description' : 'A scope type pattern which is often used in the real world. ex. from today to the end of month, the end of year.',
+        'type'        : 'string',
+        'mode'        : 'w' },
+    {   'id'          : 'periodicity_term_time_scale',
+        'description' : 'A time scale of term. ex. day, month, year.',
+        'type'        : 'string',
+        'mode'        : 'w' },
+    {   'id'          : 'periodicity_term_length_number',
+        'description' : 'A length number of term without time scale.',
+        'type'        : 'int',
+        'mode'        : 'w' },
+    )

Added: erp5/trunk/products/ERP5/tests/testOpenOrder.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/tests/testOpenOrder.py?rev=25495&view=auto
==============================================================================
--- erp5/trunk/products/ERP5/tests/testOpenOrder.py (added)
+++ erp5/trunk/products/ERP5/tests/testOpenOrder.py [utf8] Mon Feb  9 10:35:58 2009
@@ -1,0 +1,441 @@
+##############################################################################
+#
+# Copyright (c) 2009 Nexedi KK, Nexedi SA and Contributors. All Rights Reserved.
+#
+# 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.
+#
+##############################################################################
+import unittest
+from transaction import get as get_transaction
+from AccessControl.SecurityManagement import newSecurityManager
+from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
+from DateTime import DateTime
+
+
+class TestOpenOrder(ERP5TypeTestCase):
+  """
+  Test Open Order
+  """
+
+  def getTitle(self):
+    return 'Test Open Order'
+
+  def getBusinessTemplateList(self):
+
+    return ('erp5_base',
+            'erp5_pdm',
+            'erp5_trade',
+            )
+
+  def afterSetUp(self):
+    if getattr(self.portal, '_run_after_setup', None) is not None:
+      return
+
+    self.portal.portal_rules.default_open_order_rule.validate()
+    self.portal.portal_rules.default_order_rule.validate()
+
+    self.portal.portal_categories.base_amount.newContent(
+      id='taxable',
+      portal_type='Category',
+      title='Taxable')
+    tax = self.portal.tax_module.newContent(
+      portal_type='Tax',
+      title='VAT',
+      base_contribution='base_amount/taxable')
+    client = self.portal.organisation_module.newContent(
+      id='client',
+      portal_type='Organisation',
+      title='Client')
+    vendor = self.portal.organisation_module.newContent(
+      id='vendor',
+      portal_type='Organisation',
+      title='Vendor')
+    internet_connection = self.portal.service_module.newContent(
+      id='internet_connection',
+      title='Internet Connection',
+      base_contribution='base_amount/taxable')
+    training = self.portal.service_module.newContent(
+      id='training',
+      title='Training',
+      base_contribution='base_amount/taxable')
+    bread = self.portal.product_module.newContent(
+      id='bread',
+      title='Bread',
+      base_contribution='base_amount/taxable')
+    water = self.portal.product_module.newContent(
+      id='water',
+      title='Water',
+      base_contribution='base_amount/taxable')
+    main_trade_condition = self.portal.sale_trade_condition_module.newContent(
+      id='main_trade_condition',
+      portal_type='Sale Trade Condition',
+      title='Vendor ---> Client',
+      source=vendor.getRelativeUrl(),
+      source_section=vendor.getRelativeUrl(),
+      destination=client.getRelativeUrl(),
+      destination_section=client.getRelativeUrl(),
+      )
+    main_trade_condition.newContent(portal_type='Tax Model Line',
+                               title='VAT',
+                               base_application='base_amount/taxable',
+                               efficiency=0.05)
+    main_trade_condition.newContent(portal_type='Sale Supply Line',
+                               resource=internet_connection.getRelativeUrl(),
+                               priced_quantity=1,
+                               base_price=200)
+    main_trade_condition.newContent(
+      id='internet_connection_periodicity_line',
+      portal_type='Periodicity Line',
+      resource=internet_connection.getRelativeUrl(),
+      periodicity_term_scope_type='until_the_end_of_month',
+      periodicity_minute=0,
+      periodicity_hour=0,
+      periodicity_month_day=1)
+    main_trade_condition.newContent(portal_type='Sale Supply Line',
+                                    resource=training.getRelativeUrl(),
+                                    priced_quantity=4,
+                                    base_price=400)
+    main_trade_condition.newContent(
+      id='training_periodicity_line',
+      portal_type='Periodicity Line',
+      resource=training.getRelativeUrl(),
+      periodicity_term_time_scale='day',
+      periodicity_term_length_number=1,
+      periodicity_hour=10,
+      periodicity_week_day='Monday')
+    main_trade_condition.newContent(portal_type='Sale Supply Line',
+                               resource=bread.getRelativeUrl(),
+                               priced_quantity=1,
+                               base_price=10)
+    main_trade_condition.newContent(
+      id='bread_periodicity_line',
+      portal_type='Periodicity Line',
+      resource=bread.getRelativeUrl(),
+      periodicity_term_time_scale='day',
+      periodicity_term_length_number=0,
+      periodicity_minute=0,
+      periodicity_hour_list=(6, 12),
+      periodicity_week_day_list=('Monday',
+                                 'Tuesday',
+                                 'Wednesday',
+                                 'Thursday',
+                                 'Friday',
+                                 'Saturday'))
+    main_trade_condition.newContent(portal_type='Sale Supply Line',
+                               resource=water.getRelativeUrl(),
+                               priced_quantity=1,
+                               base_price=5)
+    main_trade_condition.newContent(
+      id='water_periodicity_line',
+      portal_type='Periodicity Line',
+      resource=water.getRelativeUrl(),
+      periodicity_term_scope_type='until_the_next_period',
+      periodicity_minute=0,
+      periodicity_hour_list=10,
+      periodicity_week_frequency=2,
+      periodicity_week_day = 'Monday')
+
+    # Inherit trade conditions to make sure that it works.
+    useless_trade_condition = self.portal.sale_trade_condition_module.newContent(
+      portal_type='Sale Trade Condition')
+
+    self.portal.sale_trade_condition_module.newContent(
+      id='trade_condition',
+      portal_type='Sale Trade Condition',
+      specialise_list=(useless_trade_condition.getRelativeUrl(),
+                       main_trade_condition.getRelativeUrl())
+      )
+     
+    self.portal._run_after_setup = True
+    get_transaction().commit()
+    self.tic()
+
+  def testPeriodicityDateList(self):
+    """
+    Make sure that periodicity line can generate correct schedule.
+    """
+    self.assertEqual(self.portal.sale_trade_condition_module.main_trade_condition.internet_connection_periodicity_line.getDatePeriodList(
+      DateTime(2008,1,15), DateTime(2008,12,1)),
+                     [(DateTime(2008,2,1), DateTime(2008,2,29)),
+                      (DateTime(2008,3,1), DateTime(2008,3,31)),
+                      (DateTime(2008,4,1), DateTime(2008,4,30)),
+                      (DateTime(2008,5,1), DateTime(2008,5,31)),
+                      (DateTime(2008,6,1), DateTime(2008,6,30)),
+                      (DateTime(2008,7,1), DateTime(2008,7,31)),
+                      (DateTime(2008,8,1), DateTime(2008,8,31)),
+                      (DateTime(2008,9,1), DateTime(2008,9,30)),
+                      (DateTime(2008,10,1), DateTime(2008,10,31)),
+                      (DateTime(2008,11,1), DateTime(2008,11,30)),
+                      (DateTime(2008,12,1), DateTime(2008,12,31)),
+                      ])
+    
+    self.assertEqual(self.portal.sale_trade_condition_module.main_trade_condition.bread_periodicity_line.getDatePeriodList(
+      DateTime(2008,2,26), DateTime(2008,3,5)),
+                     [(DateTime(2008,2,26,6,0), DateTime(2008,2,26,6,0)),
+                      (DateTime(2008,2,26,12,0), DateTime(2008,2,26,12,0)),
+                      (DateTime(2008,2,27,6,0), DateTime(2008,2,27,6,0)),
+                      (DateTime(2008,2,27,12,0), DateTime(2008,2,27,12,0)),
+                      (DateTime(2008,2,28,6,0), DateTime(2008,2,28,6,0)),
+                      (DateTime(2008,2,28,12,0), DateTime(2008,2,28,12,0)),
+                      (DateTime(2008,2,29,6,0), DateTime(2008,2,29,6,0)),
+                      (DateTime(2008,2,29,12,0), DateTime(2008,2,29,12,0)),
+                      (DateTime(2008,3,1,6,0), DateTime(2008,3,1,6,0)),
+                      (DateTime(2008,3,1,12,0), DateTime(2008,3,1,12,0)),
+                      (DateTime(2008,3,3,6,0), DateTime(2008,3,3,6,0)),
+                      (DateTime(2008,3,3,12,0), DateTime(2008,3,3,12,0)),
+                      (DateTime(2008,3,4,6,0), DateTime(2008,3,4,6,0)),
+                      (DateTime(2008,3,4,12,0), DateTime(2008,3,4,12,0)),
+                      ])
+
+    self.assertEqual(self.portal.sale_trade_condition_module.main_trade_condition.water_periodicity_line.getDatePeriodList(
+      DateTime(2008,2,16), DateTime(2008,4,15)),
+                     [(DateTime(2008,2,18,10,0), DateTime(2008,3,3,10,0)),
+                      (DateTime(2008,3,3,10,0), DateTime(2008,3,17,10,0)),
+                      (DateTime(2008,3,17,10,0), DateTime(2008,3,31,10,0)),
+                      (DateTime(2008,3,31,10,0), DateTime(2008,4,14,10,0)),
+                      (DateTime(2008,4,14,10,0), DateTime(2008,4,28,10,0)),
+                      ])
+    self.assertEqual(self.portal.sale_trade_condition_module.main_trade_condition.training_periodicity_line.getDatePeriodList(
+      DateTime(2008,2,16), DateTime(2008,3,6)),
+                     [(DateTime(2008,2,18,10,0), DateTime(2008,2,19,10,0)),
+                      (DateTime(2008,2,25,10,0), DateTime(2008,2,26,10,0)),
+                      (DateTime(2008,3,3,10,0), DateTime(2008,3,4,10,0)),
+                      ])
+
+  def testOpenOrderRule(self):
+    """
+    Make sure that Open Order Rule can generate simulation movements by
+    following trade conditon's periodicity setting and order's forecasting term.
+    """
+    open_sale_order = self.portal.sale_order_module.newContent(
+      portal_type='Open Sale Order',
+      specialise=self.portal.sale_trade_condition_module.trade_condition.getRelativeUrl(),
+      start_date=DateTime(3000,2,9),
+      stop_date=DateTime(3000,8,1),
+      )
+
+    open_sale_order_line = open_sale_order.newContent(
+      portal_type='Open Sale Order Line',
+      resource=self.portal.service_module.training.getRelativeUrl(),
+      quantity=1)
+
+    open_sale_order.Order_applyTradeCondition(open_sale_order.getSpecialiseValue())
+
+    get_transaction().commit()
+    self.tic()
+
+    self.assertEqual(open_sale_order_line.getPrice(), 100)
+    self.assertEqual(open_sale_order.getTotalPrice(), 100)
+    self.assertEqual(open_sale_order.getTotalNetPrice(), 105)
+
+    open_sale_order.setForecastingTermDays(5)
+    open_sale_order.order()
+    open_sale_order.start()
+
+    get_transaction().commit()
+    self.tic()
+
+    applied_rule = open_sale_order.getCausalityRelatedValue(portal_type='Applied Rule')
+    self.assertEqual(len(applied_rule.objectIds()), 0)
+
+    self.portal.portal_rules.default_open_order_rule.expand(
+      applied_rule,
+      calculation_base_date=DateTime(3000,2,9))
+
+    get_transaction().commit()
+    self.tic()
+
+    self.assertEqual(len(applied_rule.objectIds()), 1)
+    self.assertEqual(applied_rule['1'].getStartDate(), DateTime(3000,2,10,10,0))
+    self.assertEqual(applied_rule['1'].getStopDate(), DateTime(3000,2,11,10,0))
+
+    open_sale_order.setForecastingTermDays(10)
+    self.portal.portal_rules.default_open_order_rule.expand(
+      applied_rule,
+      calculation_base_date=DateTime(3000,2,9))
+
+    get_transaction().commit()
+    self.tic()
+
+    self.assertEqual(len(applied_rule.objectIds()), 2)
+    self.assertEqual(applied_rule['2'].getStartDate(), DateTime(3000,2,17,10,0))
+    self.assertEqual(applied_rule['2'].getStopDate(), DateTime(3000,2,18,10,0))
+
+    self.portal.portal_rules.default_open_order_rule.expand(
+      applied_rule,
+      calculation_base_date=DateTime(3000,3,1))
+
+    get_transaction().commit()
+    self.tic()
+
+    self.assertEqual(len(applied_rule.objectIds()), 5)
+    self.assertEqual([(movement.getStartDate(), movement.getStopDate())
+                      for movement in applied_rule.objectValues(sort_on='start_date')],
+                     [(DateTime(3000,2,10,10,0), DateTime(3000,2,11,10,0)),
+                      (DateTime(3000,2,17,10,0), DateTime(3000,2,18,10,0)),
+                      (DateTime(3000,2,24,10,0), DateTime(3000,2,25,10,0)),
+                      (DateTime(3000,3,3,10,0), DateTime(3000,3,4,10,0)),
+                      (DateTime(3000,3,10,10,0), DateTime(3000,3,11,10,0))
+                      ])
+
+    self.portal.portal_rules.default_open_order_rule.expand(
+      applied_rule,
+      calculation_base_date=DateTime(3000,3,1))
+
+    get_transaction().commit()
+    self.tic()
+
+    self.assertEqual(len(applied_rule.objectIds()), 5)
+
+    self.portal.sale_trade_condition_module.main_trade_condition.setExpirationDate(DateTime(3000,3,22))
+    self.portal.portal_rules.default_open_order_rule.expand(
+      applied_rule,
+      calculation_base_date=DateTime(3000,3,30))
+
+    get_transaction().commit()
+    self.tic()
+
+    self.assertEqual(len(applied_rule.objectIds()), 6)
+    self.assertEqual([(movement.getStartDate(), movement.getStopDate())
+                      for movement in applied_rule.objectValues(sort_on='start_date')],
+                     [(DateTime(3000,2,10,10,0), DateTime(3000,2,11,10,0)),
+                      (DateTime(3000,2,17,10,0), DateTime(3000,2,18,10,0)),
+                      (DateTime(3000,2,24,10,0), DateTime(3000,2,25,10,0)),
+                      (DateTime(3000,3,3,10,0), DateTime(3000,3,4,10,0)),
+                      (DateTime(3000,3,10,10,0), DateTime(3000,3,11,10,0)),
+                      (DateTime(3000,3,17,10,0), DateTime(3000,3,18,10,0)),
+                      ])
+
+  def testBuildingSaleOrder(self):
+    """
+    Make sure that open sale order can create sale orders repeatedly
+    """
+    open_sale_order = self.portal.sale_order_module.newContent(
+      portal_type='Open Sale Order',
+      specialise=self.portal.sale_trade_condition_module.trade_condition.getRelativeUrl(),
+      start_date=DateTime(3000,2,9),
+      stop_date=DateTime(3000,8,1),
+      forecasting_term_days=5
+      )
+
+    # Remove other test's side effect.
+    self.portal.sale_trade_condition_module.main_trade_condition.setExpirationDate(None)
+
+    get_transaction().commit()
+    self.tic()
+
+    open_sale_order.newContent(
+      title='Piano Lesson',
+      portal_type='Open Sale Order Line',
+      resource=self.portal.service_module.training.getRelativeUrl(),
+      quantity=1)
+
+    open_sale_order.newContent(
+      title='Internet Connection',
+      portal_type='Open Sale Order Line',
+      resource=self.portal.service_module.internet_connection.getRelativeUrl(),
+      quantity=1)
+
+    open_sale_order.newContent(
+      title='Bread Delivery Serivce',
+      portal_type='Open Sale Order Line',
+      resource=self.portal.product_module.bread.getRelativeUrl(),
+      quantity=1)
+
+    open_sale_order.newContent(
+      title='Mineral Water Delivery Service',
+      portal_type='Open Sale Order Line',
+      resource=self.portal.product_module.water.getRelativeUrl(),
+      quantity=1)
+
+    open_sale_order.Order_applyTradeCondition(open_sale_order.getSpecialiseValue())
+
+    get_transaction().commit()
+    self.tic()
+
+    open_sale_order.order()
+    open_sale_order.start()
+
+    get_transaction().commit()
+    self.tic()
+
+    applied_rule = open_sale_order.getCausalityRelatedValue(portal_type='Applied Rule')
+    self.assertEqual(len(applied_rule.objectIds()), 0)
+
+    open_sale_order.autoOrderPeriodically(comment='Test', calculation_base_date=DateTime(3000,2,9))
+
+    get_transaction().commit()
+    self.tic()
+
+    self.assertEqual(len(applied_rule.objectIds()), 9)
+    self.assertEqual(len(open_sale_order.getCausalityRelatedValueList(portal_type='Sale Order')), 9)
+
+    # Do the same thing and nothing happens.
+    open_sale_order.autoOrderPeriodically(comment='Test', calculation_base_date=DateTime(3000,2,9))
+
+    get_transaction().commit()
+    self.tic()
+
+    self.assertEqual(len(applied_rule.objectIds()), 9)
+    self.assertEqual(len(open_sale_order.getCausalityRelatedValueList(portal_type='Sale Order')), 9)
+
+    # Next
+    open_sale_order.autoOrderPeriodically(comment='Test', calculation_base_date=DateTime(3000,2,14))
+
+    get_transaction().commit()
+    self.tic()
+
+    self.assertEqual(len(applied_rule.objectIds()), 19)
+    self.assertEqual(len(open_sale_order.getCausalityRelatedValueList(portal_type='Sale Order')), 19)
+
+    # Check sale orders
+    sale_order_list = [
+      brain.getObject()
+      for brain in self.portal.portal_catalog(portal_type='Sale Order',
+                                              causality_uid=open_sale_order.getUid(),
+                                              sort_on='delivery.start_date')]
+
+    # The first order is bread.
+    self.assertEqual(
+      len(sale_order_list[0].objectValues(portal_type='Sale Order Line')),
+      1)
+    self.assertEqual(
+      sale_order_list[0].objectValues(portal_type='Sale Order Line')[0].getTitle(),
+      'Bread Delivery Serivce')
+    self.assertEqual(sale_order_list[0].getTotalPrice(), 10)
+    self.assertEqual(sale_order_list[0].getTotalNetPrice(), 10.5)
+
+    # The second order is piano lesson.
+    self.assertEqual(
+      len(sale_order_list[1].objectValues(portal_type='Sale Order Line')),
+      1)
+    self.assertEqual(
+      sale_order_list[1].objectValues(portal_type='Sale Order Line')[0].getTitle(),
+      'Piano Lesson')
+    self.assertEqual(sale_order_list[1].getTotalPrice(), 100)
+    self.assertEqual(sale_order_list[1].getTotalNetPrice(), 105)
+
+
+def test_suite():
+  suite = unittest.TestSuite()
+  suite.addTest(unittest.makeSuite(TestOpenOrder))
+  return suite




More information about the Erp5-report mailing list