Commit 9bc5eb10 authored by Jérome Perrin's avatar Jérome Perrin

Update Calendar when Calendar Exception is Modifed

When the user add, modify or remove a Calendar Exception, related Group Calendar Assignments should be updated.

This is a continuation of !261  implementing also the suggestions from 1fa90034 (comment 17234)


@rafael @tb @seb @romain @georgios.dagkakis 

/reviewed-on !631
parents 1ce55c56 be060a1e
<workflow_chain> <workflow_chain>
<chain>
<type>Calendar Exception</type>
<workflow>group_calendar_interaction_workflow</workflow>
</chain>
<chain> <chain>
<type>Group Calendar</type> <type>Group Calendar</type>
<workflow>edit_workflow, group_calendar_interaction_workflow, group_calendar_workflow</workflow> <workflow>edit_workflow, group_calendar_interaction_workflow, group_calendar_workflow</workflow>
......
...@@ -27,15 +27,15 @@ ...@@ -27,15 +27,15 @@
<item> <item>
<key> <string>after_script_name</string> </key> <key> <string>after_script_name</string> </key>
<value> <value>
<list> <tuple/>
<string>GroupCalendar_callUpdateRelatedAssignment</string>
</list>
</value> </value>
</item> </item>
<item> <item>
<key> <string>before_commit_script_name</string> </key> <key> <string>before_commit_script_name</string> </key>
<value> <value>
<tuple/> <list>
<string>callUpdateRelatedAssignment</string>
</list>
</value> </value>
</item> </item>
<item> <item>
...@@ -57,6 +57,8 @@ ...@@ -57,6 +57,8 @@
<value> <value>
<list> <list>
<string>_set.*</string> <string>_set.*</string>
<string>manage_delObjects</string>
<string>deleteContent</string>
</list> </list>
</value> </value>
</item> </item>
...@@ -67,9 +69,7 @@ ...@@ -67,9 +69,7 @@
<item> <item>
<key> <string>portal_type_filter</string> </key> <key> <string>portal_type_filter</string> </key>
<value> <value>
<list> <none/>
<string>Group Calendar</string>
</list>
</value> </value>
</item> </item>
<item> <item>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="InteractionDefinition" module="Products.ERP5.Interaction"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>activate_script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>before_commit_script_name</string> </key>
<value>
<list>
<string>GroupPresencePeriod_callUpdateRelatedAssignment</string>
</list>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>group_presence_period_update_related_assignment</string> </value>
</item>
<item>
<key> <string>method_id</string> </key>
<value>
<list>
<string>_set.*</string>
</list>
</value>
</item>
<item>
<key> <string>once_per_transaction</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>portal_type_filter</string> </key>
<value>
<list>
<string>Group Presence Period</string>
</list>
</value>
</item>
<item>
<key> <string>portal_type_group_filter</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>temporary_document_disallowed</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
group_calendar = state_change["object"].getParentValue()
group_calendar.GroupCalendar_updateRelatedAssignment()
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>state_change, **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>GroupPresencePeriod_callUpdateRelatedAssignment</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
'''Re-calculate capacity when a calendar or an exception is modified.
Possible structures are:
Leave Request
Leave Request Period
Calendar Exception *
Presence Request
Presence Request Period
Calendar Exception *
Group Calendar *
Group Presence Period *
Calendar Exception *
This interaction can be triggered at all levels marked with *
For Leave and Presence Request Period, the action is simple, just reindex the period.
For Group Calendar, we reindex all calendar assignments using the group calendar.
'''
calendar = state_change["object"]
def updateLeaveOrPresenceRequestPeriod(calendar):
calendar.reindexObject()
def updateGroupCalendar(calendar):
calendar.GroupCalendar_updateRelatedAssignment()
action_mapping = {
'Leave Request Period': updateLeaveOrPresenceRequestPeriod,
'Presence Request Period': updateLeaveOrPresenceRequestPeriod,
'Group Calendar': updateGroupCalendar,
}
while calendar.getPortalType() not in action_mapping:
calendar = calendar.getParentValue()
action_mapping[calendar.getPortalType()](calendar)
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
<value> <string>GroupCalendar_callUpdateRelatedAssignment</string> </value> <value> <string>callUpdateRelatedAssignment</string> </value>
</item> </item>
</dictionary> </dictionary>
</pickle> </pickle>
......
Calendar Exception | group_calendar_interaction_workflow
Group Calendar Assignment | edit_workflow Group Calendar Assignment | edit_workflow
Group Calendar Assignment | group_calendar_workflow Group Calendar Assignment | group_calendar_workflow
Group Calendar | edit_workflow Group Calendar | edit_workflow
......
...@@ -34,9 +34,9 @@ from AccessControl.SecurityManagement import newSecurityManager ...@@ -34,9 +34,9 @@ from AccessControl.SecurityManagement import newSecurityManager
from Products.ERP5Type.tests.Sequence import SequenceList from Products.ERP5Type.tests.Sequence import SequenceList
from DateTime import DateTime from DateTime import DateTime
class TestCalendar(ERP5ReportTestCase): class TestCalendar(ERP5ReportTestCase):
run_all_test = 1
person_portal_type = "Person" person_portal_type = "Person"
group_calendar_portal_type = "Group Calendar" group_calendar_portal_type = "Group Calendar"
leave_request_portal_type = "Leave Request" leave_request_portal_type = "Leave Request"
...@@ -49,21 +49,11 @@ class TestCalendar(ERP5ReportTestCase): ...@@ -49,21 +49,11 @@ class TestCalendar(ERP5ReportTestCase):
middle_date = start_date + 0.25 middle_date = start_date + 0.25
periodicity_stop_date = start_date + 2 periodicity_stop_date = start_date + 2
def getTitle(self):
return "Calendar"
def getBusinessTemplateList(self): def getBusinessTemplateList(self):
""" """
""" """
return ('erp5_base', 'erp5_pdm', 'erp5_calendar', 'erp5_core_proxy_field_legacy') return ('erp5_base', 'erp5_pdm', 'erp5_calendar', 'erp5_core_proxy_field_legacy')
def login(self, quiet=0, run=run_all_test):
uf = self.getPortal().acl_users
uf._doAddUser('rc', '', ['Manager', 'Author', 'Assignor',
'Assignee', 'Auditor'], [])
user = uf.getUserById('rc').__of__(uf)
newSecurityManager(None, user)
def createCategories(self): def createCategories(self):
""" """
Light install create only base categories, so we create Light install create only base categories, so we create
...@@ -485,12 +475,10 @@ class TestCalendar(ERP5ReportTestCase): ...@@ -485,12 +475,10 @@ class TestCalendar(ERP5ReportTestCase):
presence_request.confirm() presence_request.confirm()
self.assertEqual('confirmed', presence_request.getSimulationState()) self.assertEqual('confirmed', presence_request.getSimulationState())
def test_01_CatalogCalendarPeriod(self, quiet=0, run=run_all_test): def test_01_CatalogCalendarPeriod(self):
""" """
Test indexing Test indexing
""" """
if not run: return
sequence_list = SequenceList() sequence_list = SequenceList()
sequence_string = '\ sequence_string = '\
CreatePerson \ CreatePerson \
...@@ -514,12 +502,10 @@ class TestCalendar(ERP5ReportTestCase): ...@@ -514,12 +502,10 @@ class TestCalendar(ERP5ReportTestCase):
sequence_list.addSequenceString(sequence_string) sequence_list.addSequenceString(sequence_string)
sequence_list.play(self) sequence_list.play(self)
def test_02_CatalogLeaveRequest(self, quiet=0, run=run_all_test): def test_02_CatalogLeaveRequest(self):
""" """
Test indexing Test indexing
""" """
if not run: return
sequence_list = SequenceList() sequence_list = SequenceList()
sequence_string = '\ sequence_string = '\
CreatePerson \ CreatePerson \
...@@ -544,12 +530,10 @@ class TestCalendar(ERP5ReportTestCase): ...@@ -544,12 +530,10 @@ class TestCalendar(ERP5ReportTestCase):
sequence_list.addSequenceString(sequence_string) sequence_list.addSequenceString(sequence_string)
sequence_list.play(self) sequence_list.play(self)
def test_CatalogPresenceRequest(self, quiet=0, run=run_all_test): def test_CatalogPresenceRequest(self):
""" """
Test indexing Test indexing
""" """
if not run: return
sequence_list = SequenceList() sequence_list = SequenceList()
sequence_string = ''' sequence_string = '''
CreatePerson CreatePerson
...@@ -691,12 +675,10 @@ class TestCalendar(ERP5ReportTestCase): ...@@ -691,12 +675,10 @@ class TestCalendar(ERP5ReportTestCase):
# self.assertEqual(len(date_period_list) * second_availability, # self.assertEqual(len(date_period_list) * second_availability,
# person.getAvailableTime()) # person.getAvailableTime())
def test_03_getAvailableTime(self, quiet=0, run=run_all_test): def test_03_getAvailableTime(self):
""" """
Test indexing Test indexing
""" """
if not run: return
# Test that calendar group increase time availability # Test that calendar group increase time availability
sequence_list = SequenceList() sequence_list = SequenceList()
sequence_string = '\ sequence_string = '\
...@@ -808,11 +790,11 @@ class TestCalendar(ERP5ReportTestCase): ...@@ -808,11 +790,11 @@ class TestCalendar(ERP5ReportTestCase):
sequence_list.play(self) sequence_list.play(self)
def test_04_getCapacityAvailability(self, quiet=0, run=0): def test_04_getCapacityAvailability(self):
""" """
Test getCapacityAvailability Test getCapacityAvailability
""" """
if not run: return return # XXX this test is disabled
raise NotImplementedError raise NotImplementedError
# Test that calendar group increase time availability # Test that calendar group increase time availability
...@@ -874,6 +856,167 @@ class TestCalendar(ERP5ReportTestCase): ...@@ -874,6 +856,167 @@ class TestCalendar(ERP5ReportTestCase):
sequence_list.addSequenceString(sequence_string) sequence_list.addSequenceString(sequence_string)
sequence_list.play(self) sequence_list.play(self)
def test_CalendarExceptionInGroupCalendar(self):
group_calendar = self.portal.group_calendar_module.newContent(
portal_type='Group Calendar')
group_calendar_period = group_calendar.newContent(
portal_type='Group Presence Period')
group_calendar_period.setStartDate('2008/01/01 08:00')
group_calendar_period.setStopDate('2008/01/01 18:00')
group_calendar_period.setResourceValue(
self.portal.portal_categories.calendar_period_type.type1)
group_calendar.confirm()
person1 = self.portal.person_module.newContent(
portal_type='Person',
title='Person 1',)
self.tic()
self.assertEqual([0], [x.total_quantity
for x in person1.getAvailableTimeSequence(
from_date=DateTime(2008, 1, 1).earliestTime(),
to_date=DateTime(2008, 1, 1).latestTime(),
day=1)])
assignment = self.portal.group_calendar_assignment_module.newContent(
specialise_value=group_calendar,
resource_value=self.portal.portal_categories.calendar_period_type.type1,
start_date=DateTime(2008, 1, 1).earliestTime(),
stop_date=DateTime(2008, 1, 1).latestTime(),
destination_value=person1)
assignment.confirm()
self.tic()
self.assertEqual([36000], [x.total_quantity
for x in person1.getAvailableTimeSequence(
from_date=DateTime(2008, 1, 1).earliestTime(),
to_date=DateTime(2008, 1, 1).latestTime(),
day=1)])
exception = group_calendar_period.newContent(
portal_type="Calendar Exception",
exception_date=DateTime(2008, 1, 1))
self.tic()
self.assertEqual([0], [x.total_quantity
for x in person1.getAvailableTimeSequence(
from_date=DateTime(2008, 1, 1).earliestTime(),
to_date=DateTime(2008, 1, 1).latestTime(),
day=1)])
# When calendar exception is modified, the assignments on this calendar are
# automatically updated.
exception.setExceptionDate(DateTime(2018, 2, 2))
self.tic()
self.assertEqual([36000], [x.total_quantity
for x in person1.getAvailableTimeSequence(
from_date=DateTime(2008, 1, 1).earliestTime(),
to_date=DateTime(2008, 1, 1).latestTime(),
day=1)])
# "undo" for the rest of the test
exception.setExceptionDate(DateTime(2008, 1, 1))
self.tic()
self.assertEqual([0], [x.total_quantity
for x in person1.getAvailableTimeSequence(
from_date=DateTime(2008, 1, 1).earliestTime(),
to_date=DateTime(2008, 1, 1).latestTime(),
day=1)])
# When calendar exception is deleted, assigments are also automatically updated.
group_calendar_period.manage_delObjects(ids=[exception.getId()])
self.tic()
self.assertEqual([36000], [x.total_quantity
for x in person1.getAvailableTimeSequence(
from_date=DateTime(2008, 1, 1).earliestTime(),
to_date=DateTime(2008, 1, 1).latestTime(),
day=1)])
def test_CalendarExceptionInLeaveRequest(self):
group_calendar = self.portal.group_calendar_module.newContent(
portal_type='Group Calendar')
group_calendar_period = group_calendar.newContent(
portal_type='Group Presence Period')
group_calendar_period.setStartDate('2008/01/01 08:00')
group_calendar_period.setStopDate('2008/01/01 18:00')
group_calendar_period.setResourceValue(
self.portal.portal_categories.calendar_period_type.type1)
group_calendar_period.setPeriodicityWeekDayList(
('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'))
group_calendar_period.setPeriodicityStopDate('2008/02/01')
group_calendar.confirm()
person1 = self.portal.person_module.newContent(
portal_type='Person',
title='Person 1',)
assignment = self.portal.group_calendar_assignment_module.newContent(
specialise_value=group_calendar,
resource_value=self.portal.portal_categories.calendar_period_type.type1,
start_date=DateTime(2008, 1, 1).earliestTime(),
stop_date=DateTime(2008, 1, 31).latestTime(),
destination_value=person1)
assignment.confirm()
self.tic()
# January 2008
# Su Mo Tu We Th Fr Sa
# 1 2 3 4 5
# 6 7 8 9 10 11 12
# 13 14 15 16 17 18 19
# 20 21 22 23 24 25 26
# 27 28 29 30 31
self.assertEqual(
36000 * 31, # 36000 per day for one month
self.portal.portal_simulation.getInventory(
portal_type=self.portal.getPortalCalendarPeriodTypeList(),
from_date=DateTime(2008, 1, 1).earliestTime(),
to_date=DateTime(2008, 2, 1).latestTime(),
node_uid=person1.getUid()))
# Now add a leave request "every friday afternoon"
leave_request = self.portal.leave_request_module.newContent(
portal_type='Leave Request',
destination_value=person1,)
leave_request_period = leave_request.newContent(
portal_type='Leave Request Period',
start_date=DateTime('2008/01/04 13:00'),
stop_date=DateTime('2008/01/04 17:00'),
quantity=4*60,
resource_value=self.portal.portal_categories.calendar_period_type.type1,
periodicity_stop_date=DateTime('2008/02/01'),
periodicity_week_day_list=('Friday',))
leave_request.confirm()
self.tic()
self.assertEqual(
# 36000 per day for one month - 4 Friday afternoons ( one afternoon is 4 hours)
36000 * 31 - 4 * 4 * 60,
self.portal.portal_simulation.getInventory(
portal_type=self.portal.getPortalCalendarPeriodTypeList(),
from_date=DateTime(2008, 1, 1).earliestTime(),
to_date=DateTime(2008, 2, 1).latestTime(),
node_uid=person1.getUid()))
# now add an exception on 2008/01/25
exception = leave_request_period.newContent(
portal_type='Calendar Exception',
exception_date=DateTime('2008/01/25'))
self.tic()
self.assertEqual(
# 36000 per day for one month - 3 Friday afternoons ( one afternoon is 4 hours)
36000 * 31 - 3 * 4 * 60,
self.portal.portal_simulation.getInventory(
portal_type=self.portal.getPortalCalendarPeriodTypeList(),
from_date=DateTime(2008, 1, 1).earliestTime(),
to_date=DateTime(2008, 2, 1).latestTime(),
node_uid=person1.getUid()))
# change exception, capacity is automatically updated
exception.setExceptionDate('2008/02/02') # out or period
self.tic()
self.assertEqual(
# 36000 per day for one month - 4 Friday afternoons ( one afternoon is 4 hours)
36000 * 31 - 4 * 4 * 60,
self.portal.portal_simulation.getInventory(
portal_type=self.portal.getPortalCalendarPeriodTypeList(),
from_date=DateTime(2008, 1, 1).earliestTime(),
to_date=DateTime(2008, 2, 1).latestTime(),
node_uid=person1.getUid()))
def test_GroupCalendarConstraint(self): def test_GroupCalendarConstraint(self):
group_calendar = self.portal.group_calendar_module.newContent( group_calendar = self.portal.group_calendar_module.newContent(
portal_type='Group Calendar') portal_type='Group Calendar')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment