[FIX] hr_holidays: consecutive leave date on out of office message

Before this commit, when a user was on leave, the out of office
message displayed the current leave-in-progress date-to.
This information was misleading when the user on leave used different
types of leaves, because other users would expect their colleague to
come back from vacation sooner.

For instance, suppose that user is on vacation on October from 18-22
(included), and from 25-28 (incl.), with a week-end from 23-24:

    +--------------------------------------+
    |          OCTOBER calendar            |
    +--------------------------------------+
    |   Mo.  Tu.  We.  Th.  Fr.  Sa.  Su.  |
    |                                      |
    |                                      |
    |          on-leave          week-end  |
    |   <-------------------->   <------>  |
    |   18   19   20   21   22   23   24   |
    |                                      |
    |        on-leave                      |
    |   <--------------->                  |
    |   25   26   27   28   29   30   31   |
    +--------------------------------------+

On November 20, other users would see "On leave until _November
22_ included", instead of _November 28_.

This commit fixes the issue by taking into account consecutive
holidays to compute the actual end date of vacation.

Task-2002525

closes odoo/odoo#38388

Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Barad Mahendra
2019-11-25 15:59:03 +00:00
committed by Alexandre Kühn
parent fec56ce888
commit 802817c925
5 changed files with 69 additions and 19 deletions
+31
View File
@@ -2,6 +2,8 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import datetime
from collections import defaultdict
from datetime import timedelta
from odoo import api, fields, models
from odoo.tools.float_utils import float_round
@@ -30,6 +32,7 @@ class HrEmployeeBase(models.AbstractModel):
current_leave_id = fields.Many2one('hr.leave.type', compute='_compute_leave_status', string="Current Time Off Type")
leave_date_from = fields.Date('From Date', compute='_compute_leave_status')
leave_date_to = fields.Date('To Date', compute='_compute_leave_status')
leave_consolidated_date_to = fields.Date('Leave Consolidated To Date', compute='_compute_leave_consolidated_date_to')
leaves_count = fields.Float('Number of Time Off', compute='_compute_remaining_leaves')
allocation_count = fields.Float('Total number of days allocated.', compute='_compute_allocation_count')
allocation_used_count = fields.Float('Total number of days off used', compute='_compute_total_allocation_used')
@@ -97,6 +100,34 @@ class HrEmployeeBase(models.AbstractModel):
employees = self.filtered(lambda employee: employee.hr_presence_state != 'present' and employee.is_absent)
employees.update({'hr_presence_state': 'absent'})
def _compute_leave_consolidated_date_to(self):
holidays = self.env['hr.leave'].sudo().search([
('employee_id', 'in', self.ids),
('date_to', '>=', fields.Datetime.now()),
('state', 'not in', ('cancel', 'refuse'))
], order="date_from")
leave_data = defaultdict(list)
for holiday in holidays:
leave_data[holiday.employee_id].append({'date_to': holiday.date_to.date(), 'date_from': holiday.date_from.date()})
for employee, leave_data in leave_data.items():
first_leave = leave_data[0]
date_to = first_leave['date_to']
working_days = employee.mapped('resource_calendar_id.attendance_ids.dayofweek')
days = 1
for next_leave in leave_data:
while str((first_leave['date_to'] + timedelta(days=days)).weekday()) not in working_days:
days += 1
if next_leave['date_from'] == first_leave['date_to'] + timedelta(days=days):
days = 1
date_to = next_leave['date_to']
first_leave = next_leave
employee.leave_consolidated_date_to = date_to
for employee in self - holidays.employee_id:
employee.leave_consolidated_date_to = False
def _compute_leave_status(self):
# Used SUPERUSER_ID to forcefully get status of other user's leave, to bypass record rule
holidays = self.env['hr.leave'].sudo().search([
+4 -13
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo import models
class Channel(models.Model):
@@ -12,16 +12,7 @@ class Channel(models.Model):
# only search for leave out_of_office_message if im_status is on leave
partners_on_leave = [partner_id for partner_id in direct_partners.ids if 'leave' in partner_infos[partner_id]['im_status']]
if partners_on_leave:
now = fields.Datetime.now()
self.env.cr.execute('''SELECT res_users.partner_id as partner_id, hr_leave.date_to as date_to
FROM res_users
JOIN hr_leave ON hr_leave.user_id = res_users.id
AND hr_leave.state not in ('cancel', 'refuse')
AND res_users.active = 't'
AND hr_leave.date_from <= %s
AND hr_leave.date_to >= %s
AND res_users.partner_id in %s''', (now, now, tuple(partners_on_leave)))
out_of_office_infos = dict(((res['partner_id'], res) for res in self.env.cr.dictfetchall()))
for partner_id, out_of_office_info in out_of_office_infos.items():
partner_infos[partner_id]['out_of_office_date_end'] = out_of_office_info['date_to']
for user in self.env['res.users'].sudo().search([('partner_id', 'in', partners_on_leave)]):
partner_infos[user.partner_id.id]['out_of_office_date_end'] = user.leave_consolidated_date_to
return partner_infos
+2 -2
View File
@@ -11,7 +11,7 @@ class User(models.Model):
show_leaves = fields.Boolean(related='employee_id.show_leaves')
allocation_used_count = fields.Float(related='employee_id.allocation_used_count')
allocation_count = fields.Float(related='employee_id.allocation_count')
leave_date_to = fields.Date(related='employee_id.leave_date_to')
leave_consolidated_date_to = fields.Date(related='employee_id.leave_consolidated_date_to')
is_absent = fields.Boolean(related='employee_id.is_absent')
allocation_used_display = fields.Char(related='employee_id.allocation_used_display')
allocation_display = fields.Char(related='employee_id.allocation_display')
@@ -27,7 +27,7 @@ class User(models.Model):
'show_leaves',
'allocation_used_count',
'allocation_count',
'leave_date_to',
'leave_consolidated_date_to',
'is_absent',
'allocation_used_display',
'allocation_display',
+29 -1
View File
@@ -52,7 +52,35 @@ class TestOutOfOffice(TestHrHolidaysCommon):
'name': 'test'
})
infos = channel.with_user(self.user_employee).channel_info()
self.assertEqual(infos[0]['direct_partner'][0]['out_of_office_date_end'], leave_date_end)
self.assertEqual(infos[0]['direct_partner'][0]['out_of_office_date_end'], leave_date_end.date())
def test_consolidated_leave_date_to(self):
# leave from Thursday to Friday
leave_date_end = datetime.today() - relativedelta(days=datetime.today().weekday() - 4)
leave = self.env['hr.leave'].create({
'name': 'Holiday 1',
'employee_id': self.employee_hruser.id,
'holiday_status_id': self.leave_type.id,
'date_from': (leave_date_end - relativedelta(days=1)),
'date_to': leave_date_end,
'number_of_days': 2,
})
leave.action_approve()
self.assertEqual(self.employee_hruser.leave_consolidated_date_to, leave_date_end.date(), "consolidated leave date should be end date of leave1")
# consecutive leave after weekend, start from Monday to Tuesday
leave2_date_end = leave_date_end + relativedelta(days=4)
leave2 = self.env['hr.leave'].create({
'name': 'Holiday 2',
'employee_id': self.employee_hruser.id,
'holiday_status_id': self.leave_type.id,
'date_from': (leave2_date_end - relativedelta(days=1)),
'date_to': leave2_date_end,
'number_of_days': 2,
})
leave2.action_approve()
self.assertEqual(self.employee_hruser.leave_consolidated_date_to, leave2_date_end.date(), "consolidated leave date should be end date of leave2")
@tagged('out_of_office')
class TestOutOfOfficePerformance(TestHrHolidaysCommon, TransactionCaseWithUserDemo):
+3 -3
View File
@@ -154,7 +154,7 @@
<div role="img" class="fa fa-fw fa-plane o_button_icon text-danger" aria-label="Off Till" title="Off Till"/>
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="leave_date_to"/>
<field name="leave_consolidated_date_to"/>
</span>
<span class="o_stat_text">
Off Till
@@ -211,7 +211,7 @@
<div role="img" class="fa fa-fw fa-plane o_button_icon text-warning" aria-label="Off Till" title="Off Till"/>
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="leave_date_to"/>
<field name="leave_consolidated_date_to"/>
</span>
<span class="o_stat_text">
Off Till
@@ -251,7 +251,7 @@
<div role="img" class="fa fa-fw fa-plane o_button_icon text-danger" aria-label="Off Till" title="Off Till"/>
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="leave_date_to"/>
<field name="leave_consolidated_date_to"/>
</span>
<span class="o_stat_text">
Off Till