[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