[Erp5-report] r13865 - /erp5/trunk/products/ERP5/Document/

nobody at svn.erp5.org nobody at svn.erp5.org
Mon Apr 2 18:47:59 CEST 2007


Author: romain
Date: Mon Apr  2 18:47:57 2007
New Revision: 13865

URL: http://svn.erp5.org?rev=13865&view=rev
Log:
Remove Periodicity document.
Add PeriodicityMixin class.
Rename getNextAlarmDate method to getNextPeriodicalDate.
Add some comments.

Removed:
    erp5/trunk/products/ERP5/Document/Periodicity.py
Modified:
    erp5/trunk/products/ERP5/Document/Alarm.py
    erp5/trunk/products/ERP5/Document/CalendarPeriod.py

Modified: erp5/trunk/products/ERP5/Document/Alarm.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/Alarm.py?rev=13865&r1=13864&r2=13865&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/Document/Alarm.py (original)
+++ erp5/trunk/products/ERP5/Document/Alarm.py Mon Apr  2 18:47:57 2007
@@ -1,6 +1,6 @@
 ##############################################################################
 #
-# Copyright (c) 2004 Nexedi SARL and Contributors. All Rights Reserved.
+# Copyright (c) 2004, 2007 Nexedi SARL and Contributors. All Rights Reserved.
 #                    Sebastien Robin <seb at nexedi.com>
 #
 # WARNING: This program as such is intended to be used by professional
@@ -30,164 +30,388 @@
 from Products.CMFCore.utils import getToolByName
 from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
 from Products.ERP5Type.XMLObject import XMLObject
-from Products.ERP5.Document.Periodicity import Periodicity
 from Products.CMFCore.WorkflowCore import WorkflowMethod
 from Acquisition import aq_base, aq_parent, aq_inner, aq_acquire
 from Products.CMFCore.utils import getToolByName
 from DateTime import DateTime
+from Products.ERP5Type.Message import Message
+from Products.ERP5Type.DateUtils import addToDate
 
 from zLOG import LOG
 
-
-
-class Alarm(Periodicity, XMLObject):
-    """
-    An Alarm is in charge of checking anything (quantity of a certain
-    resource on the stock, consistency of some order,....) periodically.
-
-    It should also provide a solution if something wrong happens.
-
-    Some information should be displayed to the user, and also notifications.
-    """
-
-    # CMF Type Definition
-    meta_type = 'ERP5 Alarm'
-    portal_type = 'Alarm'
-    add_permission = Permissions.AddPortalContent
-    isPortalContent = 1
-    isRADContent = 1
-
-    # Declarative security
-    security = ClassSecurityInfo()
-    security.declareObjectProtected(Permissions.AccessContentsInformation)
-
-    # Default Properties
-    property_sheets = ( PropertySheet.Base
-                      , PropertySheet.XMLObject
-                      , PropertySheet.CategoryCore
-                      , PropertySheet.DublinCore
-                      , PropertySheet.Periodicity
-                      , PropertySheet.Document
-                      , PropertySheet.Task
-                      , PropertySheet.Alarm
-                      )
-
-    security.declareProtected(Permissions.View, 'isActive')
-    def isActive(self):
-      """
-      This method returns only True or False. 
-      It simply tells if this alarm is currently
-      active or not. It is activated when it is doing some calculation with
-      activeSense or solve.
-      """
-      return self.hasActivity()
-
-    security.declareProtected(Permissions.ModifyPortalContent, 'activeSense')
-    def activeSense(self):
-      """
-      This method checks if there is a problem. This method can launch a very long
-      activity. We don't care about the response, we just want to start
-      some calculations. Results should be read with the method 'sense'
-      later.
-
-      """
-      self.setNextAlarmDate()
-      method_id = self.getActiveSenseMethodId()
-      if method_id is not None:
-        method = getattr(self.activate(),method_id)
-        return method()
-
-    security.declareProtected(Permissions.ModifyPortalContent, 'sense')
-    def sense(self):
-      """
-      This method returns True or False. False for no problem, True for problem.
-
-      This method should respond quickly.  Basically the response depends on some
-      previous calculation made by activeSense.
-      """
-      method_id = self.getSenseMethodId()
-      process = self.getLastActiveProcess()
-      if process is None:
-        return value
-      if method_id is not None:
-        method = getattr(self,method_id)
-        value = method()
+class PeriodicityMixin:
+  """
+  Periodicity is a Mixin Class used to calculate date periodicity.
+  """
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  def _validateMinute(self, date, previous_date):
+    """
+    Validate if date's minute matches the periodicity definition
+    """
+    periodicity_minute_frequency = self.getPeriodicityMinuteFrequency()
+    periodicity_minute_list = self.getPeriodicityMinuteList()
+    if (periodicity_minute_frequency is None) and \
+       (periodicity_minute_list in ([], None, ())):
+      # in this case, we may want to have an periodicity every hour 
+      # based on the start date
+      # without defining anything about minutes periodicity, 
+      # so we compare with minutes with the one defined 
+      # in the previous alarm date
+      return (date.minute() == previous_date.minute())
+    if periodicity_minute_frequency not in ('', None):
+      return (date.minute() % periodicity_minute_frequency) == 0
+    elif len(periodicity_minute_list) > 0:
+      return date.minute() in periodicity_minute_list
+
+  def _validateHour(self, date):
+    """
+    Validate if date's hour matches the periodicity definition
+    """
+    periodicity_hour_frequency = self.getPeriodicityHourFrequency()
+    periodicity_hour_list = self.getPeriodicityHourList()
+    if (periodicity_hour_frequency is None) and \
+       (periodicity_hour_list in ([], None, ())):
+      return 1
+    if periodicity_hour_frequency not in ('', None):
+      return (date.hour() % periodicity_hour_frequency) == 0
+    elif len(periodicity_hour_list) > 0:
+      return date.hour() in periodicity_hour_list
+
+  def _validateDay(self, date):
+    """
+    Validate if date's day matches the periodicity definition
+    """
+    periodicity_day_frequency = self.getPeriodicityDayFrequency()
+    periodicity_month_day_list = self.getPeriodicityMonthDayList()
+    if (periodicity_day_frequency is None) and \
+       (periodicity_month_day_list in ([], None, ())):
+      return 1
+    if periodicity_day_frequency not in ('', None):
+      return (date.day() % periodicity_day_frequency) == 0
+    elif len(periodicity_month_day_list) > 0:
+      return date.day() in periodicity_month_day_list
+
+  def _validateWeek(self, date):
+    """
+    Validate if date's week matches the periodicity definition
+    """
+    periodicity_week_frequency = self.getPeriodicityWeekFrequency()
+    periodicity_week_day_list = self.getPeriodicityWeekDayList()
+    periodicity_week_list = self.getPeriodicityWeekList()
+    if (periodicity_week_frequency is None) and \
+       (periodicity_week_day_list in ([], None, ())) and \
+       (periodicity_week_list is None):
+      return 1
+    if periodicity_week_frequency not in ('', None):
+      if not((date.week() % periodicity_week_frequency) == 0):
+        return 0
+    if periodicity_week_day_list not in (None, (), []):
+      if not (date.Day() in periodicity_week_day_list):
+        return 0
+    if periodicity_week_list not in (None, (), []):
+      if not (date.week() in periodicity_week_list):
+        return 0
+    return 1
+
+  def _validateMonth(self, date):
+    """
+    Validate if date's month matches the periodicity definition
+    """
+    periodicity_month_frequency = self.getPeriodicityMonthFrequency()
+    periodicity_month_list = self.getPeriodicityMonthList()
+    if (periodicity_month_frequency is None) and \
+       (periodicity_month_list in ([], None, ())):
+      return 1
+    if periodicity_month_frequency not in ('', None):
+      return (date.month() % periodicity_month_frequency) == 0
+    elif len(periodicity_month_list) > 0:
+      return date.month() in periodicity_month_list
+
+  def getNextPeriodicalDate(self, current_date, next_start_date=None):
+    """
+    Get the next date where this periodic event should start.
+
+    We have to take into account the start date, because
+    sometimes an event may be started by hand. We must be
+    sure to never forget to start an event, even with some
+    delays.
+
+    Here are some rules :
+    - if the periodicity start date is in the past and we never starts
+      this periodic event, then return the periodicity start date.
+    - if the periodicity start date is in the past but we already
+      have started the periodic event, then see
+
+    XXX Better API is needed. It may defined which minimal time duration has to 
+    be added in order to calculate next date.
+    Ex: here, we use minute as smaller duration.
+    On CalendarPeriod, day is the smaller duration.
+    """
+    if next_start_date is None:
+      next_start_date = current_date
+    if next_start_date > current_date:
+      return
+    else:
+      # Make sure the old date is not too far away
+      day_count = int(current_date-next_start_date)
+      next_start_date = next_start_date + day_count
+
+    previous_date = next_start_date
+    next_start_date = addToDate(next_start_date, minute=1)
+    while 1:
+      validate_minute = self._validateMinute(next_start_date, previous_date)
+      validate_hour = self._validateHour(next_start_date)
+      validate_day = self._validateDay(next_start_date)
+      validate_week = self._validateWeek(next_start_date)
+      validate_month = self._validateMonth(next_start_date)
+      if (next_start_date >= current_date \
+          and validate_minute and validate_hour and validate_day \
+          and validate_week and validate_month):
+        break
       else:
-        for result in process.getResultList():
-          if result.severity > result.INFO:
-            value = True
-            break
-      process.setSenseValue(value)
+        if not(validate_minute):
+          next_start_date = addToDate(next_start_date, minute=1)
+        else:
+          if not(validate_hour):
+            next_start_date = addToDate(next_start_date, hour=1)
+          else:
+            if not(validate_day and validate_week and validate_month):
+              next_start_date = addToDate(next_start_date, day=1)
+            else:
+              # Everything is right, but the date is still not bigger
+              # than the current date, so we must continue
+              next_start_date = addToDate(next_start_date, minute=1)
+    return next_start_date
+
+  # XXX May be we should create a Date class for following methods ???
+  security.declareProtected(Permissions.View, 'getWeekDayList')
+  def getWeekDayList(self):
+    """
+    returns something like ['Sunday','Monday',...]
+    """
+    return DateTime._days
+
+  security.declareProtected(Permissions.View, 'getWeekDayItemList')
+  def getWeekDayItemList(self):
+    """
+    returns something like [('Sunday', 'Sunday'), ('Monday', 'Monday'),...]
+    """
+    return [(Message(domain='erp5_ui', message=x), x) \
+            for x in self.getWeekDayList()]
+
+  security.declareProtected(Permissions.View, 'getWeekDayItemList')
+  def getMonthItemList(self):
+    """
+    returns something like [('January', 1), ('February', 2),...]
+    """
+    # DateTime._months return '' as first item
+    return [(Message(domain='erp5_ui', message=DateTime._months[i]), i) \
+            for i in range(1, len(DateTime._months))]
+
+  security.declareProtected(Permissions.View,'getPeriodicityWeekDayList')
+  def getPeriodicityWeekDayList(self):
+    """
+    Make sure that the list of days is ordered
+    """
+    #LOG('getPeriodicityWeekDayList',0,'we should order')
+    day_list = self._baseGetPeriodicityWeekDayList()
+    new_list = []
+    for day in self.getWeekDayList():
+      if day_list is not None:
+        if day in day_list:
+          new_list += [day]
+    return new_list
+
+class Alarm(XMLObject, PeriodicityMixin):
+  """
+  An Alarm is in charge of checking anything (quantity of a certain
+  resource on the stock, consistency of some order,....) periodically.
+
+  It should also provide a solution if something wrong happens.
+
+  Some information should be displayed to the user, and also notifications.
+  """
+
+  # CMF Type Definition
+  meta_type = 'ERP5 Alarm'
+  portal_type = 'Alarm'
+  add_permission = Permissions.AddPortalContent
+  isPortalContent = 1
+  isRADContent = 1
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+  # Default Properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore
+                    , PropertySheet.Periodicity
+                    , PropertySheet.Document
+                    , PropertySheet.Task
+                    , PropertySheet.Alarm
+                    )
+
+  security.declareProtected(Permissions.View, 'isActive')
+  def isActive(self):
+    """
+    This method returns only True or False. 
+    It simply tells if this alarm is currently
+    active or not. It is activated when it is doing some calculation with
+    activeSense or solve.
+    """
+    return self.hasActivity()
+
+  security.declareProtected(Permissions.ModifyPortalContent, 'activeSense')
+  def activeSense(self):
+    """
+    This method checks if there is a problem. This method can launch a very long
+    activity. We don't care about the response, we just want to start
+    some calculations. Results should be read with the method 'sense'
+    later.
+
+    """
+    # Set the new date
+    LOG('activeSense, self.getPath()',0,self.getPath())
+
+    self.setNextAlarmDate()
+    method_id = self.getActiveSenseMethodId()
+    if method_id is not None:
+      method = getattr(self.activate(),method_id)
+      return method()
+
+  security.declareProtected(Permissions.ModifyPortalContent, 'sense')
+  def sense(self):
+    """
+    This method returns True or False. False for no problem, True for problem.
+
+    This method should respond quickly.  Basically the response depends on some
+    previous calculation made by activeSense.
+    """
+    method_id = self.getSenseMethodId()
+    process = self.getLastActiveProcess()
+    if process is None:
       return value
-
-    security.declareProtected(Permissions.View, 'report')
-    def report(self,process=None):
-      """
-      This methods produces a report (HTML)
-      This generate the output of the results. It can be used to nicely
-      explain the problem. We don't do calculation at this time, it should
-      be made by activeSense.
-      """
-      method_id = self.getReportMethodId(None)
-      #LOG('Alarm.report, method_id',0,method_id)
-      if method_id is None:
-          return ''
+    if method_id is not None:
       method = getattr(self,method_id)
-      process = self.getLastActiveProcess()
-      result = None
-      if process is not None:
-        result = method(process=process)
-      return result
-
-    security.declareProtected(Permissions.ModifyPortalContent, 'solve')
-    def solve(self):
-      """
-      This method tries solves the problem detected by sense.
-
-      This solve the problem if there is a problem detected by sense. If
-      no problems, then nothing to do here.
-      """
-      pass
-
-    security.declareProtected(Permissions.ModifyPortalContent, 'notify')
-    def _notify(self):
-      """
-      This method is called to notify people that some alarm has
-      been sensed.
-
-      for example we can send email.
-
-      We define nothing here, because we will use an interaction workflow.
-      """
-      pass
-
-    notify = WorkflowMethod(_notify, id='notify')
-
-    security.declareProtected(Permissions.View, 'getLastActiveProcess')
-    def getLastActiveProcess(self):
-      """
-      This returns the last active process finished. So it will
-      not returns the current one
-      """
-      active_process_list = self.getCausalityRelatedValueList(
-                                    portal_type='Active Process')
-      def sort_date(a, b):
-        return cmp(a.getStartDate(), b.getStartDate())
-      active_process_list.sort(sort_date)
-      active_process = None
-      if len(active_process_list)>0:
-        active_process = active_process_list[-1]
-      return active_process
-
-    security.declareProtected(Permissions.ModifyPortalContent, 'newActiveProcess')
-    def newActiveProcess(self):
-      """
-      We will create a new active process in order to store
-      new results, then this process will be added to the list
-      of processes
-      """
-      portal_activities = getToolByName(self,'portal_activities')
-      active_process = portal_activities.newActiveProcess()
-      active_process.setStartDate(DateTime())
-      active_process.setCausalityValue(self)
-      return active_process
+      value = method()
+    else:
+      for result in process.getResultList():
+        if result.severity > result.INFO:
+          value = True
+          break
+    process.setSenseValue(value)
+    return value
+
+  security.declareProtected(Permissions.View, 'report')
+  def report(self, process=None):
+    """
+    This methods produces a report (HTML)
+    This generate the output of the results. It can be used to nicely
+    explain the problem. We don't do calculation at this time, it should
+    be made by activeSense.
+    """
+    method_id = self.getReportMethodId(None)
+    #LOG('Alarm.report, method_id',0,method_id)
+    if method_id is None:
+        return ''
+    method = getattr(self,method_id)
+    process = self.getLastActiveProcess()
+    result = None
+    if process is not None:
+      result = method(process=process)
+    return result
+
+  security.declareProtected(Permissions.ModifyPortalContent, 'solve')
+  def solve(self):
+    """
+    This method tries solves the problem detected by sense.
+
+    This solve the problem if there is a problem detected by sense. If
+    no problems, then nothing to do here.
+    """
+    pass
+
+  security.declareProtected(Permissions.ModifyPortalContent, 'notify')
+  def _notify(self):
+    """
+    This method is called to notify people that some alarm has
+    been sensed.
+
+    for example we can send email.
+
+    We define nothing here, because we will use an interaction workflow.
+    """
+    pass
+
+  notify = WorkflowMethod(_notify, id='notify')
+
+  security.declareProtected(Permissions.View, 'getLastActiveProcess')
+  def getLastActiveProcess(self):
+    """
+    This returns the last active process finished. So it will
+    not returns the current one
+    """
+    active_process_list = self.getCausalityRelatedValueList(
+                                  portal_type='Active Process')
+    def sort_date(a, b):
+      return cmp(a.getStartDate(), b.getStartDate())
+    active_process_list.sort(sort_date)
+    active_process = None
+    if len(active_process_list)>0:
+      active_process = active_process_list[-1]
+    return active_process
+
+  security.declareProtected(Permissions.ModifyPortalContent, 
+                            'newActiveProcess')
+  def newActiveProcess(self):
+    """
+    We will create a new active process in order to store
+    new results, then this process will be added to the list
+    of processes
+    """
+    portal_activities = getToolByName(self,'portal_activities')
+    active_process = portal_activities.newActiveProcess()
+    active_process.setStartDate(DateTime())
+    active_process.setCausalityValue(self)
+    return active_process
+
+  security.declareProtected(Permissions.View, 'setNextAlarmDate')
+  def setNextAlarmDate(self, current_date=None):
+    """
+    Save the next alarm date
+    """
+    if self.getPeriodicityStartDate() is None:
+      return
+    next_start_date = self.getAlarmDate()
+    if current_date is None:
+      # This is usefull to set the current date as parameter for
+      # unit testing, by default it should be now
+      current_date = DateTime()
+
+    next_start_date = self.getNextPeriodicalDate(current_date, 
+                                            next_start_date=next_start_date)
+    if next_start_date is not None:
+      self.Alarm_zUpdateAlarmDate(uid=self.getUid(), 
+                                  alarm_date=next_start_date)
+
+  security.declareProtected(Permissions.View, 'getAlarmDate')
+  def getAlarmDate(self):
+    """
+    returns something like ['Sunday','Monday',...]
+    """
+    #alarm_date = self._baseGetAlarmDate()
+    #if alarm_date is None:
+    #  alarm_date = self.getPeriodicityStartDate()
+    alarm_date=None
+    result_list = self.Alarm_zGetAlarmDate(uid=self.getUid())
+    if len(result_list)==1:
+      alarm_date = result_list[0].alarm_date
+      periodicity_start_date = self.getPeriodicityStartDate()
+      if alarm_date < periodicity_start_date:
+        alarm_date = periodicity_start_date
+    return alarm_date

Modified: erp5/trunk/products/ERP5/Document/CalendarPeriod.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/Document/CalendarPeriod.py?rev=13865&r1=13864&r2=13865&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/Document/CalendarPeriod.py (original)
+++ erp5/trunk/products/ERP5/Document/CalendarPeriod.py Mon Apr  2 18:47:57 2007
@@ -33,10 +33,11 @@
 
 from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
 from Products.ERP5.Document.DeliveryLine import DeliveryLine
-from Products.ERP5.Document.Periodicity import Periodicity
+from Products.ERP5.Document.Alarm import PeriodicityMixin
 from Products.ERP5.Document.Movement import Movement
-
-class CalendarPeriod(Movement, Periodicity):
+from Products.ERP5Type.DateUtils import addToDate
+
+class CalendarPeriod(Movement, PeriodicityMixin):
   """
   Calendar Period is used to add available time of the user in a 
   period of Time
@@ -89,7 +90,8 @@
                             'getInventoriatedQuantity')
   def getInventoriatedQuantity(self, default=None, *args, **kw):
     """
-    Surcharged accessor to calculate the Quantity in second.
+    Surcharged accessor to calculate the Quantity in second
+    from stop date and start date values.
     """
     quantity = self.getQuantity(*args, **kw)
     if quantity in [None, 0]:
@@ -143,7 +145,7 @@
                                           start_date)
       duration = stop_date - start_date
       # First date has to respect the periodicity config
-      next_start_date = self.getNextAlarmDate(start_date-1)
+      next_start_date = self.getNextPeriodicalDate(start_date-1)
       while (next_start_date is not None) and \
         (next_start_date <= periodicity_stop_date):
 
@@ -171,30 +173,36 @@
           # SQL method don't like iterator
 #             yield (next_start_date, next_start_date+duration)
           result.append([next_start_date, next_start_date+duration])
-        next_start_date = self.getNextAlarmDate(next_start_date)
+        next_start_date = self.getNextPeriodicalDate(next_start_date)
 
     return result
 
-  def getNextAlarmDate(self, current_date, next_start_date=None):
+  def getNextPeriodicalDate(self, current_date, next_start_date=None):
     """
     Get the next date where this periodic event should start.
-    """
+
+    XXX It completely reimplements the PeriodictyMixin method because
+    the minimal duration between dates is day, and not minute
+    Better way would be to improve the API of getNextPeriodicalDate,
+    and optimize addToDate method.
+    """
+    # XXX Copy/Paste from PeriodicityMixin
     if next_start_date is None:
       next_start_date = current_date
     if next_start_date > current_date:
       return
     else:
       # Make sure the old date is not too far away
-      nb_days = int(current_date-next_start_date)
-      next_start_date = next_start_date + nb_days
+      day_count = int(current_date-next_start_date)
+      next_start_date = next_start_date + day_count
 
     previous_date = next_start_date
-    next_start_date += 1
+    next_start_date = addToDate(next_start_date, day=1)
     while 1:
-      if (self.validateDay(next_start_date)) and \
-         (self.validateWeek(next_start_date)) and \
-         (self.validateMonth(next_start_date)):
+      if (self._validateDay(next_start_date)) and \
+         (self._validateWeek(next_start_date)) and \
+         (self._validateMonth(next_start_date)):
         break
       else:
-        next_start_date += 1
+        next_start_date = addToDate(next_start_date, day=1)
     return next_start_date




More information about the Erp5-report mailing list