File: test_leave.py

package info (click to toggle)
odoo 18.0.0%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 878,716 kB
  • sloc: javascript: 927,937; python: 685,670; xml: 388,524; sh: 1,033; sql: 415; makefile: 26
file content (250 lines) | stat: -rw-r--r-- 14,141 bytes parent folder | download
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)