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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
|
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, date
from dateutil.relativedelta import relativedelta
from freezegun import freeze_time
from odoo import SUPERUSER_ID
from odoo.addons.hr_work_entry_holidays.tests.common import TestWorkEntryHolidaysBase
from odoo.tests import tagged
@tagged('test_leave')
class TestWorkEntryLeave(TestWorkEntryHolidaysBase):
def test_resource_leave_has_work_entry_type(self):
leave = self.create_leave()
resource_leave = leave._create_resource_leave()
self.assertEqual(resource_leave.work_entry_type_id, self.leave_type.work_entry_type_id, "it should have the corresponding work_entry type")
def test_resource_leave_in_contract_calendar(self):
other_calendar = self.env['resource.calendar'].create({'name': 'New calendar'})
contract = self.richard_emp.contract_ids[0]
contract.resource_calendar_id = other_calendar
contract.state = 'open' # this set richard's calendar to New calendar
leave = self.create_leave()
resource_leave = leave._create_resource_leave()
self.assertEqual(len(resource_leave), 1, "it should have created only one resource leave")
self.assertEqual(resource_leave.work_entry_type_id, self.leave_type.work_entry_type_id, "it should have the corresponding work_entry type")
def test_create_mark_conflicting_work_entries(self):
work_entry = self.create_work_entry(datetime(2019, 10, 10, 9, 0), datetime(2019, 10, 10, 12, 0))
self.assertNotEqual(work_entry.state, 'conflict', "It should not be conflicting")
leave = self.create_leave(date(2019, 10, 10), date(2019, 10, 10))
self.assertEqual(work_entry.state, 'conflict', "It should be conflicting")
self.assertEqual(work_entry.leave_id, leave, "It should be linked to conflicting leave")
def test_write_mark_conflicting_work_entries(self):
leave = self.create_leave(date(2019, 10, 10), datetime(2019, 10, 10))
work_entry = self.create_work_entry(leave.date_from - relativedelta(days=1), leave.date_from) # the day before
self.assertNotEqual(work_entry.state, 'conflict', "It should not be conflicting")
leave.request_date_from = date(2019, 10, 9) # now it conflicts
self.assertEqual(work_entry.state, 'conflict', "It should be conflicting")
self.assertEqual(work_entry.leave_id, leave, "It should be linked to conflicting leave")
def test_validate_leave_with_overlap(self):
contract = self.richard_emp.contract_ids[:1]
contract.state = 'open'
contract.date_generated_from = datetime(2019, 10, 10, 9, 0)
contract.date_generated_to = datetime(2019, 10, 10, 9, 0)
leave = self.create_leave(datetime(2019, 10, 10, 9, 0), datetime(2019, 10, 12, 18, 0))
work_entry_1 = self.create_work_entry(datetime(2019, 10, 8, 9, 0), datetime(2019, 10, 11, 9, 0)) # overlaps
work_entry_2 = self.create_work_entry(datetime(2019, 10, 11, 9, 0), datetime(2019, 10, 11, 10, 0)) # included
adjacent_work_entry = self.create_work_entry(datetime(2019, 10, 12, 18, 0), datetime(2019, 10, 13, 18, 0)) # after and don't overlap
leave.action_validate()
self.assertNotEqual(adjacent_work_entry.state, 'conflict', "It should not conflict")
self.assertFalse(work_entry_2.active, "It should have been archived")
self.assertEqual(work_entry_1.state, 'conflict', "It should conflict")
self.assertFalse(work_entry_1.leave_id, "It should not be linked to the leave")
leave_work_entry = self.env['hr.work.entry'].search([('leave_id', '=', leave.id)]) - work_entry_1
self.assertTrue(leave_work_entry.work_entry_type_id.is_leave, "It should have created a leave work entry")
self.assertEqual(leave_work_entry[:1].state, 'conflict', "The leave work entry should conflict")
def test_conflict_move_work_entry(self):
leave = self.create_leave(datetime(2019, 10, 10, 9, 0), datetime(2019, 10, 12, 18, 0))
work_entry = self.create_work_entry(datetime(2019, 10, 8, 9, 0), datetime(2019, 10, 11, 9, 0)) # overlaps
self.assertEqual(work_entry.state, 'conflict', "It should be conflicting")
self.assertEqual(work_entry.leave_id, leave, "It should be linked to conflicting leave")
work_entry.date_stop = datetime(2019, 10, 9, 9, 0) # no longer overlaps
self.assertNotEqual(work_entry.state, 'conflict', "It should not be conflicting")
self.assertFalse(work_entry.leave_id, "It should not be linked to any leave")
def test_validate_leave_without_overlap(self):
contract = self.richard_emp.contract_ids[:1]
contract.state = 'open'
contract.date_generated_from = datetime(2019, 10, 10, 9, 0)
contract.date_generated_to = datetime(2019, 10, 10, 9, 0)
leave = self.create_leave(datetime(2019, 10, 10, 9, 0), datetime(2019, 10, 12, 18, 0))
work_entry = self.create_work_entry(datetime(2019, 10, 11, 9, 0), datetime(2019, 10, 11, 10, 0)) # included
leave.action_validate()
self.assertFalse(work_entry[:1].active, "It should have been archived")
leave_work_entry = self.env['hr.work.entry'].search([('leave_id', '=', leave.id)])
self.assertTrue(leave_work_entry.work_entry_type_id.is_leave, "It should have created a leave work entry")
self.assertNotEqual(leave_work_entry[:1].state, 'conflict', "The leave work entry should not conflict")
def test_refuse_leave(self):
leave = self.create_leave(date(2019, 10, 10), date(2019, 10, 10))
work_entries = self.richard_emp.contract_id._generate_work_entries(datetime(2019, 10, 10, 0, 0, 0), datetime(2019, 10, 10, 23, 59, 59))
adjacent_work_entry = self.create_work_entry(leave.date_from - relativedelta(days=3), leave.date_from)
self.assertTrue(all(work_entries.mapped(lambda w: w.state == 'conflict')), "Attendance work entries should all conflict with the leave")
self.assertNotEqual(adjacent_work_entry.state, 'conflict', "Non overlapping work entry should not conflict")
leave.action_refuse()
self.assertTrue(all(work_entries.mapped(lambda w: w.state != 'conflict')), "Attendance work entries should no longer conflict")
self.assertNotEqual(adjacent_work_entry.state, 'conflict', "Non overlapping work entry should not conflict")
def test_refuse_approved_leave(self):
start = datetime(2019, 10, 10, 6, 0)
end = datetime(2019, 10, 10, 18, 0)
# Setup contract generation state
contract = self.richard_emp.contract_ids[:1]
contract.state = 'open'
contract.date_generated_from = start - relativedelta(hours=1)
contract.date_generated_to = start - relativedelta(hours=1)
leave = self.create_leave(start, end)
leave.action_validate()
work_entries = self.env['hr.work.entry'].search([('employee_id', '=', self.richard_emp.id), ('date_start', '<=', end), ('date_stop', '>=', start)])
leave_work_entry = self.richard_emp.contract_ids.generate_work_entries(start.date(), end.date())
self.assertEqual(leave_work_entry[:1].leave_id, leave)
leave.action_refuse()
work_entries = self.env['hr.work.entry'].search([('employee_id', '=', self.richard_emp.id), ('date_start', '>=', start), ('date_stop', '<=', end)])
self.assertFalse(leave_work_entry[:1].filtered('leave_id').active)
self.assertEqual(len(work_entries), 2, "Attendance work entries should have been re-created (morning and afternoon)")
self.assertTrue(all(work_entries.mapped(lambda w: w.state != 'conflict')), "Attendance work entries should not conflict")
def test_archived_work_entry_conflict(self):
self.create_leave(datetime(2019, 10, 10, 9, 0), datetime(2019, 10, 10, 18, 0))
work_entry = self.create_work_entry(datetime(2019, 10, 10, 9, 0), datetime(2019, 10, 10, 18, 0))
self.assertTrue(work_entry.active)
self.assertEqual(work_entry.state, 'conflict', "Attendance work entries should conflict with the leave")
work_entry.toggle_active()
self.assertEqual(work_entry.state, 'cancelled', "Attendance work entries should be cancelled and not conflict")
self.assertFalse(work_entry.active)
def test_work_entry_cancel_leave(self):
user = self.env['res.users'].create({
'name': 'User Employee',
'login': 'jul',
'password': 'julpassword',
})
self.richard_emp.user_id = user
self.richard_emp.contract_ids.state = 'open'
with freeze_time(datetime(2022, 3, 21)):
# Tests that cancelling a leave archives the work entries.
leave = self.env['hr.leave'].with_user(user).create({
'name': 'Sick 1 week during christmas snif',
'employee_id': self.richard_emp.id,
'holiday_status_id': self.leave_type.id,
'request_date_from': date(2022, 3, 22),
'request_date_to': date(2022, 3, 25),
})
leave.with_user(SUPERUSER_ID).action_validate()
# No work entries exist yet
self.assertTrue(leave.can_cancel, "The leave should still be cancellable")
# can not create in the future
self.richard_emp.contract_ids.generate_work_entries(date(2022, 3, 21), date(2022, 3, 25))
work_entries = self.env['hr.work.entry'].search([('employee_id', '=', self.richard_emp.id)])
leave.invalidate_recordset(['can_cancel'])
# Work entries exist but are not locked yet
self.assertTrue(leave.can_cancel, "The leave should still be cancellable")
work_entries.action_validate()
leave.invalidate_recordset(['can_cancel'])
# Work entries locked
self.assertFalse(leave.can_cancel, "The leave should not be cancellable")
def test_work_entry_generation_company_time_off(self):
existing_leaves = self.env['hr.leave'].search([])
existing_leaves.action_refuse()
existing_leaves.action_reset_confirm()
existing_leaves.unlink()
start = date(2022, 8, 1)
end = date(2022, 8, 31)
self.contract_cdi.generate_work_entries(start, end)
work_entries = self.env['hr.work.entry'].search([
('employee_id', '=', self.jules_emp.id),
('date_start', '>=', start),
('date_stop', '<=', end),
])
self.assertEqual(len(work_entries.work_entry_type_id), 1)
leave = self.env['hr.leave.generate.multi.wizard'].create({
'name': 'Holiday!!!',
'allocation_mode': 'company',
'company_id': self.env.company.id,
'holiday_status_id': self.leave_type.id,
'date_from': datetime(2022, 8, 8),
'date_to': datetime(2022, 8, 8),
})
leave.action_generate_time_off()
work_entries = self.env['hr.work.entry'].search([
('employee_id', '=', self.jules_emp.id),
('date_start', '>=', start),
('date_stop', '<=', end),
])
self.assertEqual(len(work_entries.work_entry_type_id), 2)
def test_time_off_duration_contract_state_change(self):
# check that setting a contract without end state from
# expired to running won't erase the time off duration
leave = self.create_leave(datetime(2019, 10, 10, 9, 0), datetime(2019, 10, 10, 18, 0))
self.assertTrue(leave.number_of_days, 1)
contract = self.richard_emp.contract_ids
contract.state = "close"
contract.date_end = False
self.assertTrue(leave.number_of_days, 1)
contract.state = "open"
self.assertTrue(leave.number_of_days, 1)
def test_split_leaves_by_entry_type(self):
entry_type_paid, entry_type_unpaid = self.env['hr.work.entry.type'].create([
{'name': 'Paid leave', 'code': 'PAID', 'is_leave': True},
{'name': 'Unpaid leave', 'code': 'UNPAID', 'is_leave': True},
])
leave_type_paid, leave_type_unpaid = self.env['hr.leave.type'].create([{
'name': 'Paid leave type',
'requires_allocation': 'no',
'request_unit': 'hour',
'work_entry_type_id': entry_type_paid.id,
},
{
'name': 'Unpaid leave type',
'requires_allocation': 'no',
'request_unit': 'hour',
'work_entry_type_id': entry_type_unpaid.id,
}])
leave_paid, leave_unpaid = self.env['hr.leave'].create([{
'name': 'Paid leave',
'employee_id': self.jules_emp.id,
'holiday_status_id': leave_type_paid.id,
'request_date_from': datetime(2024, 9, 10),
'request_date_to': datetime(2024, 9, 10),
'request_unit_hours': True,
'request_hour_from': '8',
'request_hour_to': '9',
},
{
'name': 'Unpaid leave',
'employee_id': self.jules_emp.id,
'holiday_status_id': leave_type_unpaid.id,
'request_date_from': datetime(2024, 9, 10),
'request_date_to': datetime(2024, 9, 10),
'request_unit_hours': True,
'request_hour_from': '9',
'request_hour_to': '10',
}])
(leave_paid | leave_unpaid).with_user(SUPERUSER_ID).action_validate()
entries = self.contract_cdi._generate_work_entries(datetime(2024, 9, 10, 0, 0, 0), datetime(2024, 9, 10, 23, 59, 59))
paid_leave_entry = entries.filtered_domain([('work_entry_type_id', '=', entry_type_paid.id)])
unpaid_leave_entry = entries.filtered_domain([('work_entry_type_id', '=', entry_type_unpaid.id)])
self.assertEqual(len(entries), 4, 'Leaves should have 1 entry per type')
self.assertEqual((paid_leave_entry.date_stop - paid_leave_entry.date_start).seconds, 3600)
self.assertEqual((unpaid_leave_entry.date_stop - unpaid_leave_entry.date_start).seconds, 3600)
|