283 lines
14 KiB
Python
283 lines
14 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
from collections import defaultdict
|
|
from datetime import datetime, date, time
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
from odoo.tests import new_test_user, tagged
|
|
from .common import TestUiCommon
|
|
|
|
|
|
@tagged('-at_install', 'post_install')
|
|
class TestUi(TestUiCommon):
|
|
def test_01_ui(self):
|
|
self.start_tour("/", 'planning_test_tour', login='admin')
|
|
|
|
def test_shift_switch_ui(self):
|
|
bert_user = new_test_user(self.env,
|
|
login='bert_user',
|
|
groups='planning.group_planning_user',
|
|
name='Bert User',
|
|
email='[email protected]')
|
|
bety_user = new_test_user(self.env,
|
|
login='bety_user',
|
|
groups='planning.group_planning_user',
|
|
name='Bety User',
|
|
email='[email protected]')
|
|
test_role = self.env["planning.role"].create([{'name': 'test_role'}])
|
|
employee_bert, employee_bety = self.env['hr.employee'].create([
|
|
{
|
|
'name': 'bert',
|
|
'work_email': '[email protected]',
|
|
'tz': 'UTC',
|
|
'employee_type': 'freelance',
|
|
'create_date': '2015-01-01 00:00:00',
|
|
'user_id': bert_user.id,
|
|
'planning_role_ids': test_role,
|
|
},
|
|
{
|
|
'name': 'bety',
|
|
'work_email': '[email protected]',
|
|
'tz': 'UTC',
|
|
'employee_type': 'freelance',
|
|
'create_date': '2015-01-01 00:00:00',
|
|
'user_id': bety_user.id,
|
|
'planning_role_ids': test_role,
|
|
}
|
|
])
|
|
test_slot = self.env['planning.slot'].create({
|
|
'start_datetime': datetime.now(),
|
|
'end_datetime': datetime.now() + relativedelta(hours=1),
|
|
'state': 'published',
|
|
'resource_id': employee_bert.resource_id.id,
|
|
})
|
|
self.assertEqual(test_slot.request_to_switch, False, 'Before requesting to switch, the request to switch should be False')
|
|
self.start_tour("/", 'planning_shift_switching_backend', login='bert_user')
|
|
self.assertEqual(test_slot.request_to_switch, True, 'Before requesting to switch, the request to switch should be False')
|
|
self.start_tour("/", 'planning_assigning_unwanted_shift_backend', login='admin')
|
|
self.assertEqual(test_slot.request_to_switch, False, 'After the assign action, the request to switch should be False')
|
|
self.assertEqual(test_slot.resource_id, employee_bety.resource_id, 'The shift should now be assigned to Bety')
|
|
|
|
def test_planning_no_email(self):
|
|
aaron_role = self.env["planning.role"].create([{'name': 'aaron_role'}])
|
|
employee_thibault_user = new_test_user(self.env,
|
|
login='thibault_user',
|
|
groups='planning.group_planning_user',
|
|
name='Thibault User',
|
|
email=None
|
|
)
|
|
self.employee_thibault.write({
|
|
"user_id": employee_thibault_user.id,
|
|
"planning_role_ids": aaron_role,
|
|
})
|
|
self.env['planning.slot'].create({
|
|
'start_datetime': datetime.now(),
|
|
'end_datetime': datetime.now() + relativedelta(hours=1),
|
|
'resource_id': self.employee_thibault.resource_id.id,
|
|
})
|
|
self.start_tour("/", 'planning_test_tour_no_email', login='admin')
|
|
|
|
def test_split_shift_ui(self):
|
|
PlanningSlot = self.env['planning.slot']
|
|
# create a user with planning manager rights and timezone set to UTC
|
|
hugo = new_test_user(
|
|
self.env,
|
|
login='hugo_user',
|
|
groups='planning.group_planning_manager',
|
|
name='Hugo',
|
|
email='[email protected]',
|
|
tz='UTC',
|
|
)
|
|
# 1. Creating work schedule in UTC
|
|
attendance_schedule = { # To round to hours!
|
|
'start_am': time.fromisoformat('08:00:00'),
|
|
'end_am': time.fromisoformat('12:00:00'),
|
|
'start_pm': time.fromisoformat('13:00:00'),
|
|
'end_pm': time.fromisoformat('17:00:00'),
|
|
}
|
|
work_schedule_calendar = self.env['resource.calendar'].create([{
|
|
'name': 'Musketeers schedule (UTC)',
|
|
'tz': 'UTC',
|
|
}])
|
|
self.env['resource.calendar.attendance'].create([
|
|
{
|
|
'name': 'Wednesday AM',
|
|
'dayofweek': '2',
|
|
'hour_from': attendance_schedule['start_am'].hour,
|
|
'hour_to': attendance_schedule['end_am'].hour,
|
|
'calendar_id': work_schedule_calendar.id,
|
|
},
|
|
{
|
|
'name': 'Wednesday PM',
|
|
'dayofweek': '2',
|
|
'hour_from': attendance_schedule['start_am'].hour,
|
|
'hour_to': attendance_schedule['end_am'].hour,
|
|
'calendar_id': work_schedule_calendar.id,
|
|
},
|
|
{
|
|
'name': 'Thursday AM',
|
|
'dayofweek': '3',
|
|
'hour_from': attendance_schedule['start_am'].hour,
|
|
'hour_to': attendance_schedule['end_am'].hour,
|
|
'calendar_id': work_schedule_calendar.id,
|
|
},
|
|
{
|
|
'name': 'Thursday PM',
|
|
'dayofweek': '3',
|
|
'hour_from': attendance_schedule['start_am'].hour,
|
|
'hour_to': attendance_schedule['end_am'].hour,
|
|
'calendar_id': work_schedule_calendar.id,
|
|
},
|
|
{
|
|
'name': 'Friday AM',
|
|
'dayofweek': '4',
|
|
'hour_from': attendance_schedule['start_am'].hour,
|
|
'hour_to': attendance_schedule['end_am'].hour,
|
|
'calendar_id': work_schedule_calendar.id,
|
|
},
|
|
{
|
|
'name': 'Friday PM',
|
|
'dayofweek': '4',
|
|
'hour_from': attendance_schedule['start_am'].hour,
|
|
'hour_to': attendance_schedule['end_am'].hour,
|
|
'calendar_id': work_schedule_calendar.id,
|
|
},
|
|
])
|
|
|
|
# 2. Creating employees using those work schedules
|
|
employees = (
|
|
employee_aramis,
|
|
employee_athos,
|
|
employee_porthos,
|
|
employee_rochefort
|
|
) = self.env['hr.employee'].create([
|
|
{
|
|
'name': 'Aramis',
|
|
'resource_calendar_id': work_schedule_calendar.id,
|
|
},
|
|
{
|
|
'name': 'Athos',
|
|
'resource_calendar_id': work_schedule_calendar.id,
|
|
},
|
|
{
|
|
'name': 'Porthos',
|
|
'resource_calendar_id': work_schedule_calendar.id,
|
|
},
|
|
{
|
|
'name': 'Rochefort',
|
|
'resource_calendar_id': work_schedule_calendar.id,
|
|
},
|
|
])
|
|
|
|
# 3. Creating shifts for those employees (in UTC)
|
|
days_to_wednesday = 2 - date.today().weekday() + 7 * (date.today().weekday() // 6)
|
|
start_date = date.today() + relativedelta(days=days_to_wednesday)
|
|
end_date = start_date + relativedelta(days=2)
|
|
start_date_normal = datetime.combine(start_date, attendance_schedule['start_am'])
|
|
start_date_late = datetime.combine(start_date, attendance_schedule['end_pm']) + relativedelta(hours=3)
|
|
end_date_normal = datetime.combine(end_date, attendance_schedule['end_pm'])
|
|
end_date_early = datetime.combine(end_date, attendance_schedule['start_am']) - relativedelta(hours=2)
|
|
|
|
weekend_shift_start_date = start_date_normal + relativedelta(days=2)
|
|
weekend_shift_end_date = end_date_normal + relativedelta(days=1)
|
|
|
|
full_slot_aramis, full_slot_athos, *_ = PlanningSlot.with_user(hugo).create([
|
|
{
|
|
'start_datetime': start_date_normal,
|
|
'end_datetime': end_date_normal,
|
|
'state': 'published',
|
|
'resource_id': employee_aramis.resource_id.id,
|
|
},
|
|
{
|
|
'start_datetime': start_date_late,
|
|
'end_datetime': end_date_early,
|
|
'state': 'published',
|
|
'resource_id': employee_athos.resource_id.id,
|
|
},
|
|
{
|
|
'start_datetime': start_date_normal,
|
|
'end_datetime': end_date_normal,
|
|
'state': 'published',
|
|
'resource_id': employee_porthos.resource_id.id,
|
|
'repeat': True,
|
|
'repeat_unit': 'week',
|
|
'repeat_interval': 1,
|
|
'repeat_type': 'x_times',
|
|
'repeat_number': 3,
|
|
},
|
|
{
|
|
'start_datetime': weekend_shift_start_date,
|
|
'end_datetime': weekend_shift_end_date,
|
|
'state': 'published',
|
|
'resource_id': employee_rochefort.resource_id.id,
|
|
}
|
|
])
|
|
# Initially 1 slot assigned to Aramis, 1 to Athos and 3 to Porthos
|
|
slot_count_per_resource = dict(PlanningSlot._read_group(
|
|
[('resource_id', 'in', employees.resource_id.ids)],
|
|
['resource_id'], ['__count'],
|
|
))
|
|
self.assertDictEqual(slot_count_per_resource, {
|
|
employee_aramis.resource_id: 1,
|
|
employee_athos.resource_id: 1,
|
|
employee_porthos.resource_id: 3,
|
|
employee_rochefort.resource_id: 1,
|
|
})
|
|
|
|
# 4. Launching tour (Browser in UTC by default)
|
|
self.start_tour("/", 'planning_split_shift_week', login='hugo_user')
|
|
|
|
# 5. Verify the resulting slots after splitting
|
|
slot_data = PlanningSlot.search_read(
|
|
[('resource_id', 'in', employees.resource_id.ids)],
|
|
fields=['resource_id', 'start_datetime', 'end_datetime'],
|
|
order='start_datetime ASC',
|
|
load=False,
|
|
)
|
|
slots_per_resource_id = defaultdict(list)
|
|
for slot in slot_data:
|
|
slots_per_resource_id[slot['resource_id']].append(slot)
|
|
slots_aramis, slots_athos, slots_porthos, slots_rochefort = (
|
|
slots_per_resource_id[employee.resource_id.id]
|
|
for employee in employees
|
|
)
|
|
# After splitting: 2 slot assigned to Aramis, 3 to Athos, 4 to Porthos and 2 to Rochefort
|
|
self.assertEqual(len(slots_aramis), 2)
|
|
self.assertEqual(len(slots_athos), 3)
|
|
self.assertEqual(len(slots_porthos), 4, "Splitting a recurrent shift should only split one occurrence")
|
|
self.assertEqual(len(slots_rochefort), 2)
|
|
self.assertEqual([slots_aramis[0]['start_datetime'], slots_aramis[0]['end_datetime']], [start_date_normal, start_date_normal + relativedelta(hours=9)],
|
|
"When splitting a shift planned during resource's work schedule, resulting shifts should not start or end outside of this schedule.")
|
|
self.assertEqual([slots_aramis[1]['start_datetime'], slots_aramis[1]['end_datetime']], [start_date_normal + relativedelta(days=1), end_date_normal],
|
|
"When splitting a shift planned during resource's work schedule, resulting shifts should not start or end outside of this schedule.")
|
|
self.assertEqual([slots_athos[0]['start_datetime'], slots_athos[0]['end_datetime']], [start_date_late, start_date_late + relativedelta(seconds=1)],
|
|
"When splitting a shift starting after end of resource's work schedule at the end of the first day, first resulting shift should end one second after it starts.")
|
|
self.assertEqual([slots_athos[2]['start_datetime'], slots_athos[2]['end_datetime']], [end_date_early - relativedelta(seconds=1), end_date_early],
|
|
"When splitting a shift ending before the start of resource's work schedule at the end of the penultimate day, last resulting shift should start one second before it ends.")
|
|
self.assertEqual([slots_porthos[2]['start_datetime'], slots_porthos[2]['end_datetime']], [start_date_normal + relativedelta(weeks=1), end_date_normal + relativedelta(weeks=1)],
|
|
"Splitting a recurrent shift should only split one occurrence")
|
|
self.assertEqual([slots_rochefort[1]['start_datetime'], slots_rochefort[1]['end_datetime']], [weekend_shift_end_date - relativedelta(hours=9), weekend_shift_end_date],
|
|
"When splitting a shift planned in a weekend, resulting shifts should have its start/end set to 8AM/5PM.")
|
|
|
|
# 6. Undo the splits
|
|
# One split was done on the slot of Aramis, we undo it and check that the dates of the resulting slot correspond to the ones we had before the split
|
|
slot1_aramis, slot2_aramis = PlanningSlot.browse([slot['id'] for slot in slots_aramis])
|
|
(slot1_aramis + slot2_aramis).undo_split_shift(full_slot_aramis.start_datetime, full_slot_aramis.end_datetime, slot1_aramis.resource_id)
|
|
self.assertFalse(slot2_aramis.exists())
|
|
self.assertEqual([slot1_aramis.start_datetime, slot1_aramis.end_datetime], [full_slot_aramis.start_datetime, full_slot_aramis.end_datetime])
|
|
|
|
# Two splits were done on the slot of Athos, we first undo the second split (between part 1 and part 2 of the slot)
|
|
slot1_athos, slot2_athos, slot3_athos = PlanningSlot.browse([slot['id'] for slot in slots_athos])
|
|
slot_athos12_start_date = slot1_athos.start_datetime
|
|
slot_athos12_end_datetime = slot2_athos.end_datetime
|
|
(slot1_athos + slot2_athos).undo_split_shift(slot_athos12_start_date, slot_athos12_end_datetime, slot1_athos.resource_id)
|
|
self.assertFalse(slot2_athos.exists())
|
|
self.assertEqual([slot1_athos.start_datetime, slot1_athos.end_datetime], [slot_athos12_start_date, slot_athos12_end_datetime])
|
|
# We then undo the first split (between resulting part and part 3 of the slot)
|
|
(slot1_athos + slot3_athos).undo_split_shift(full_slot_athos.start_datetime, full_slot_athos.end_datetime, slot1_athos.resource_id)
|
|
self.assertFalse(slot3_athos.exists())
|
|
self.assertEqual([slot1_athos.start_datetime, slot1_athos.end_datetime], [full_slot_athos.start_datetime, full_slot_athos.end_datetime])
|
|
|
|
def test_onboarding_tour(self):
|
|
self.start_tour("/odoo", 'planning_tour', login='admin')
|