diff --git a/addons/hr_holidays/models/__init__.py b/addons/hr_holidays/models/__init__.py
index d391276412b..2da28fe24fa 100644
--- a/addons/hr_holidays/models/__init__.py
+++ b/addons/hr_holidays/models/__init__.py
@@ -6,4 +6,7 @@ from . import hr
from . import hr_leave
from . import hr_leave_allocation
from . import hr_leave_type
-from . import mail_message_subtype
\ No newline at end of file
+from . import mail_channel
+from . import mail_message_subtype
+from . import res_partner
+from . import res_users
diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py
index c475dec01c7..5536a80f1b8 100644
--- a/addons/hr_holidays/models/hr_leave.py
+++ b/addons/hr_holidays/models/hr_leave.py
@@ -9,7 +9,7 @@ import math
from datetime import datetime
from pytz import timezone, UTC
-from odoo import api, fields, models
+from odoo import api, fields, models, tools
from odoo.addons.resource.models.resource import float_to_time, HOURS_PER_DAY
from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.tools import float_compare
@@ -128,6 +128,7 @@ class HolidaysRequest(models.Model):
'hr.department', string='Department', readonly=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
notes = fields.Text('Reasons', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
+ out_of_office_message = fields.Char(string='Out of Office Message')
# duration
date_from = fields.Datetime(
'Start Date', readonly=True, index=True, copy=False, required=True,
@@ -250,6 +251,13 @@ class HolidaysRequest(models.Model):
('duration_check', "CHECK ( number_of_days >= 0 )", "If you want to change the number of days you should use the 'period' mode"),
]
+ @api.model_cr_context
+ def _auto_init(self):
+ res = super(HolidaysRequest, self)._auto_init()
+ tools.create_index(self._cr, 'hr_leave_date_to_date_from_index',
+ self._table, ['date_to', 'date_from'])
+ return res
+
@api.onchange('holiday_status_id')
def _onchange_holiday_status_id(self):
self.request_unit_half = False
diff --git a/addons/hr_holidays/models/mail_channel.py b/addons/hr_holidays/models/mail_channel.py
new file mode 100644
index 00000000000..b6d22625411
--- /dev/null
+++ b/addons/hr_holidays/models/mail_channel.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models
+
+
+class Channel(models.Model):
+ _inherit = 'mail.channel'
+
+ @api.multi
+ def channel_info(self, extra_info=False):
+ channel_infos = super(Channel, self).channel_info(extra_info)
+ partners_on_leave = []
+ for channel_info in channel_infos:
+ if 'direct_partner' in channel_info:
+ for direct_partner in channel_info['direct_partner']:
+ if 'leave' in direct_partner['im_status']:
+ partners_on_leave.append(direct_partner['id'])
+ if partners_on_leave:
+ now = fields.Datetime.now()
+ self.env.cr.execute('''SELECT res_users.partner_id as partner_id, hr_leave.out_of_office_message as out_of_office_message, 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_info = dict(((res['partner_id'], res) for res in self.env.cr.dictfetchall()))
+ for channel_info in channel_infos:
+ if 'direct_partner' in channel_info:
+ for direct_partner in channel_info['direct_partner']:
+ if 'leave' in direct_partner['im_status']:
+ direct_partner['out_of_office_date_end'] = out_of_office_info.get(direct_partner['id'], {}).get('date_to')
+ direct_partner['out_of_office_message'] = out_of_office_info.get(direct_partner['id'], {}).get('out_of_office_message')
+ return channel_infos
diff --git a/addons/hr_holidays/models/res_partner.py b/addons/hr_holidays/models/res_partner.py
new file mode 100644
index 00000000000..dde82f5cc9b
--- /dev/null
+++ b/addons/hr_holidays/models/res_partner.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, models
+
+
+class ResPartner(models.Model):
+ _inherit = 'res.partner'
+
+ def _compute_im_status(self):
+ super(ResPartner, self)._compute_im_status()
+ absent_now = self._get_on_leave_ids()
+ for partner in self:
+ if partner.id in absent_now:
+ if partner.im_status == 'online':
+ partner.im_status = 'leave_online'
+ else:
+ partner.im_status = 'leave_offline'
+
+ @api.model
+ def _get_on_leave_ids(self):
+ return self.env['res.users']._get_on_leave_ids(partner=True)
diff --git a/addons/hr_holidays/models/res_users.py b/addons/hr_holidays/models/res_users.py
new file mode 100644
index 00000000000..3df8f5175f5
--- /dev/null
+++ b/addons/hr_holidays/models/res_users.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models
+
+
+class ResUsers(models.Model):
+ _inherit = "res.users"
+
+ def _compute_im_status(self):
+ super(ResUsers, self)._compute_im_status()
+ on_leave_user_ids = self._get_on_leave_ids()
+ for user in self:
+ if user.id in on_leave_user_ids:
+ if user.im_status == 'online':
+ user.im_status = 'leave_online'
+ else:
+ user.im_status = 'leave_offline'
+
+ @api.model
+ def _get_on_leave_ids(self, partner=False):
+ now = fields.Datetime.now()
+ field = 'partner_id' if partner else 'id'
+ self.env.cr.execute('''SELECT res_users.%s FROM res_users
+ JOIN hr_leave ON hr_leave.user_id = res_users.id
+ AND state not in ('cancel', 'refuse')
+ AND res_users.active = 't'
+ AND date_from <= %%s AND date_to >= %%s''' % field, (now, now))
+ return [r[0] for r in self.env.cr.fetchall()]
diff --git a/addons/hr_holidays/static/src/xml/discuss.xml b/addons/hr_holidays/static/src/xml/discuss.xml
new file mode 100644
index 00000000000..33128c54c3f
--- /dev/null
+++ b/addons/hr_holidays/static/src/xml/discuss.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/addons/hr_holidays/tests/__init__.py b/addons/hr_holidays/tests/__init__.py
index 9510f75b34b..67c8327e999 100644
--- a/addons/hr_holidays/tests/__init__.py
+++ b/addons/hr_holidays/tests/__init__.py
@@ -7,3 +7,4 @@ from . import test_hr_leave_type
from . import test_accrual_allocations
from . import test_change_department
from . import test_leave_requests
+from . import test_out_of_office
diff --git a/addons/hr_holidays/tests/test_out_of_office.py b/addons/hr_holidays/tests/test_out_of_office.py
new file mode 100644
index 00000000000..05e3e480cc0
--- /dev/null
+++ b/addons/hr_holidays/tests/test_out_of_office.py
@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from datetime import datetime
+from dateutil.relativedelta import relativedelta
+
+from odoo.tests.common import tagged, users, warmup
+from odoo.addons.hr_holidays.tests.common import TestHrHolidaysBase
+
+
+@tagged('out_of_office')
+class TestOutOfOffice(TestHrHolidaysBase):
+
+ def test_leave_ooo(self):
+ leave_type = self.env['hr.leave.type'].create({
+ 'name': 'Legal Leaves',
+ 'time_type': 'leave',
+ 'allocation_type': 'no',
+ })
+ self.assertNotEqual(self.employee_hruser.user_id.im_status, 'leave_offline', 'user should not be on leave')
+ self.assertNotEqual(self.employee_hruser.user_id.partner_id.im_status, 'leave_offline', 'user should not be on leave')
+ leave_date_end = (datetime.today() + relativedelta(days=3))
+ leave = self.env['hr.leave'].create({
+ 'name': 'Christmas',
+ 'employee_id': self.employee_hruser.id,
+ 'holiday_status_id': leave_type.id,
+ 'date_from': (datetime.today() - relativedelta(days=1)),
+ 'date_to': leave_date_end,
+ 'out_of_office_message': 'contact tde in case of problems',
+ 'number_of_days': 4,
+ })
+ leave.action_approve()
+ self.assertEqual(self.employee_hruser.user_id.im_status, 'leave_offline', 'user should be out (leave_offline)')
+ self.assertEqual(self.employee_hruser.user_id.partner_id.im_status, 'leave_offline', 'user should be out (leave_offline)')
+
+ partner = self.employee_hruser.user_id.partner_id
+ partner2 = self.user_employee.partner_id
+
+ channel = self.env['mail.channel'].with_context({
+ 'mail_create_nolog': True,
+ 'mail_create_nosubscribe': True,
+ 'mail_channel_noautofollow': True,
+ }).create({
+ 'channel_partner_ids': [(4, partner.id), (4, partner2.id)],
+ 'public': 'private',
+ 'channel_type': 'chat',
+ 'email_send': False,
+ 'name': 'test'
+ })
+ infos = channel.sudo(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_message'], 'contact tde in case of problems')
+
+
+@tagged('out_of_office')
+class TestOutOfOfficePerformance(TestHrHolidaysBase):
+
+ def setUp(self):
+ super(TestOutOfOfficePerformance, self).setUp()
+ self.leave_type = self.env['hr.leave.type'].create({
+ 'name': 'Legal Leaves',
+ 'time_type': 'leave',
+ 'allocation_type': 'no',
+ })
+ self.leave_date_end = (datetime.today() + relativedelta(days=3))
+ self.leave = self.env['hr.leave'].create({
+ 'name': 'Christmas',
+ 'employee_id': self.employee_hruser_id,
+ 'holiday_status_id': self.leave_type.id,
+ 'date_from': (datetime.today() - relativedelta(days=1)),
+ 'date_to': (datetime.today() + relativedelta(days=3)),
+ 'out_of_office_message': 'contact tde in case of problems',
+ 'number_of_days': 4,
+ })
+
+ self.hr_user = self.employee_hruser.user_id
+ self.hr_partner = self.employee_hruser.user_id.partner_id
+ self.employer_partner = self.user_employee.partner_id
+
+ @users('__system__', 'demo')
+ @warmup
+ def test_leave_im_status_performance_partner_offline(self):
+ with self.assertQueryCount(__system__=2, demo=2):
+ self.assertEqual(self.employer_partner.im_status, 'offline')
+
+ @users('__system__', 'demo')
+ @warmup
+ def test_leave_im_status_performance_user_leave_offline(self):
+ with self.assertQueryCount(__system__=2, demo=2):
+ self.assertEqual(self.hr_user.im_status, 'leave_offline')
+
+ @users('__system__', 'demo')
+ @warmup
+ def test_leave_im_status_performance_partner_leave_offline(self):
+ with self.assertQueryCount(__system__=2, demo=2):
+ self.assertEqual(self.hr_partner.im_status, 'leave_offline')
diff --git a/addons/hr_holidays/views/hr_leave_views.xml b/addons/hr_holidays/views/hr_leave_views.xml
index f655906d0af..c38c2d9f7dd 100644
--- a/addons/hr_holidays/views/hr_leave_views.xml
+++ b/addons/hr_holidays/views/hr_leave_views.xml
@@ -246,7 +246,7 @@
-
+
diff --git a/addons/mail/static/src/js/models/threads/abstract_thread.js b/addons/mail/static/src/js/models/threads/abstract_thread.js
index d6a37967c68..a515c4d2f8a 100644
--- a/addons/mail/static/src/js/models/threads/abstract_thread.js
+++ b/addons/mail/static/src/js/models/threads/abstract_thread.js
@@ -74,6 +74,22 @@ var AbstractThread = Class.extend(Mixins.EventDispatcherMixin, {
getName: function () {
return this._name;
},
+ /**
+ * By default, threads do not have out-of-office feature
+ *
+ * @return {string|undefined}
+ */
+ getOutOfOfficeInfo: function () {
+ return undefined;
+ },
+ /**
+ * By default, threads do not have out-of-office feature
+ *
+ * @return {string|undefined}
+ */
+ getOutOfOfficeMessage: function () {
+ return undefined;
+ },
/**
* Get the status of the thread (e.g. 'online', 'offline', etc.)
*
diff --git a/addons/mail/static/src/js/models/threads/dm_chat.js b/addons/mail/static/src/js/models/threads/dm_chat.js
index 21fb72e91ae..fb524515193 100644
--- a/addons/mail/static/src/js/models/threads/dm_chat.js
+++ b/addons/mail/static/src/js/models/threads/dm_chat.js
@@ -1,8 +1,10 @@
odoo.define('mail.model.DMChat', function (require) {
"use strict";
-var core = require('web.core');
var TwoUserChannel = require('mail.model.TwoUserChannel');
+
+var core = require('web.core');
+
var _t = core._t;
/**
@@ -20,7 +22,8 @@ var DMChat = TwoUserChannel.extend({
* @param {integer} params.data.direct_partner[0].id
* @param {string} params.data.direct_partner[0].im_status
* @param {string} params.data.direct_partner[0].name
- * @param {string} [params.data.direct_partner[0].out_of_office_message='']
+ * @param {string} [params.data.direct_partner[0].out_of_office_message]
+ * @param {string} [params.data.direct_partner[0].out_of_office_date_end]
*/
init: function (params) {
this._super.apply(this, arguments);
@@ -54,14 +57,31 @@ var DMChat = TwoUserChannel.extend({
return this._directPartnerID;
},
/**
+ * Get the out of office info
+ *
+ * @override {mail.model.AbstractThread}
+ * @returns {string|undefined}
+ */
+ getOutOfOfficeInfo: function () {
+ if (!this._outOfOfficeDateEnd) {
+ return undefined;
+ }
+ var currentDate = new Date();
+ var date = new Date(this._outOfOfficeDateEnd);
+ var options = { day: 'numeric', month: 'short' };
+ if (currentDate.getFullYear() !== date.getFullYear()) {
+ options.year = 'numeric';
+ }
+ var formattedDate = date.toLocaleDateString(window.navigator.language, options);
+ return _.str.sprintf(_t("Out of office until %s"), formattedDate);
+ },
+ /**
* Get the out of office message of the thread
*
+ * @override {mail.model.AbstractThread}
* @returns {string}
*/
getOutOfOfficeMessage: function () {
- if (this._outOfOfficeMessage === '') {
- return undefined;
- }
return this._outOfOfficeMessage;
},
/**
diff --git a/addons/mail/static/src/js/thread_windows/abstract_thread_window.js b/addons/mail/static/src/js/thread_windows/abstract_thread_window.js
index da958c1c5f1..300bfb11a3e 100644
--- a/addons/mail/static/src/js/thread_windows/abstract_thread_window.js
+++ b/addons/mail/static/src/js/thread_windows/abstract_thread_window.js
@@ -148,17 +148,23 @@ var AbstractThreadWindow = Widget.extend({
return this._thread;
},
/**
- * Get out of office user text
+ *Get out of office info
*
* @returns {string|undefined}
*/
- getOutOfOfficeMessage: function () {
- if (
- !(
- this.hasThread() &&
- this._thread.getType() === 'dm_chat'
- )
- ) {
+ getOutOfOfficeInfo: function () {
+ if (!this.hasThread()) {
+ return undefined;
+ }
+ return this._thread.getOutOfOfficeInfo();
+ },
+ /**
+ * Get out of office user text
+ *
+ * @returns {string|undefined}
+ */
+ getOutOfOfficeMessage: function () {
+ if (!this.hasThread()) {
return undefined;
}
return this._thread.getOutOfOfficeMessage();
diff --git a/addons/mail/static/src/scss/abstract_thread_window.scss b/addons/mail/static/src/scss/abstract_thread_window.scss
index 62b0c42d033..50db05d952c 100644
--- a/addons/mail/static/src/scss/abstract_thread_window.scss
+++ b/addons/mail/static/src/scss/abstract_thread_window.scss
@@ -82,6 +82,10 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
.o_out_of_office {
background-color: #9dd0e8;
padding:8px;
+
+ .o_out_of_office_info {
+ font-weight: bold;
+ }
}
.o_mail_thread {
diff --git a/addons/mail/static/src/xml/thread_window.xml b/addons/mail/static/src/xml/thread_window.xml
index 4d175fcf2b8..d519d0ebbec 100644
--- a/addons/mail/static/src/xml/thread_window.xml
+++ b/addons/mail/static/src/xml/thread_window.xml
@@ -6,8 +6,9 @@
-->
-