diff --git a/addons/hr_holidays/__manifest__.py b/addons/hr_holidays/__manifest__.py
index b2911b88e2b..76baca06f2d 100644
--- a/addons/hr_holidays/__manifest__.py
+++ b/addons/hr_holidays/__manifest__.py
@@ -66,6 +66,7 @@ A synchronization with an internal agenda (Meetings of the CRM module) is also p
],
'web.assets_backend': [
'hr_holidays/static/src/js/time_off_calendar.js',
+ 'hr_holidays/static/src/js/time_off_calendar_employee.js',
'hr_holidays/static/src/js/radio_image.js',
'hr_holidays/static/src/js/leave_stats_widget.js',
'hr_holidays/static/src/models/*/*.js',
diff --git a/addons/hr_holidays/models/hr_employee.py b/addons/hr_holidays/models/hr_employee.py
index 1b344387c39..f14706120ba 100644
--- a/addons/hr_holidays/models/hr_employee.py
+++ b/addons/hr_holidays/models/hr_employee.py
@@ -4,7 +4,7 @@
import datetime
from dateutil.relativedelta import relativedelta
-from odoo import api, fields, models
+from odoo import _, api, fields, models
from odoo.tools.float_utils import float_round
@@ -246,3 +246,19 @@ class HrEmployee(models.Model):
def _get_user_m2o_to_empty_on_archived_employees(self):
return super()._get_user_m2o_to_empty_on_archived_employees() + ['leave_manager_id']
+
+ def action_time_off_dashboard(self):
+ domain = []
+ if self.env.context.get('active_ids'):
+ domain = [('employee_id', 'in', self.env.context.get('active_ids', []))]
+
+ return {
+ 'name': _('Time Off Dashboard'),
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'hr.leave',
+ 'views': [[self.env.ref('hr_holidays.hr_leave_employee_view_dashboard').id, 'calendar']],
+ 'domain': domain,
+ 'context': {
+ 'employee_id': self.ids,
+ },
+ }
diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py
index c98cb9205f9..71019014c5d 100644
--- a/addons/hr_holidays/models/hr_leave_allocation.py
+++ b/addons/hr_holidays/models/hr_leave_allocation.py
@@ -41,6 +41,7 @@ class HolidaysAllocation(models.Model):
return [('employee_requests', '=', 'yes')]
name = fields.Char('Description', compute='_compute_description', inverse='_inverse_description', search='_search_description', compute_sudo=False)
+ name_validity = fields.Char('Description with validity', compute='_compute_description_validity')
active = fields.Boolean(default=True)
private_name = fields.Char('Allocation Description', groups='hr_holidays.group_hr_holidays_user')
state = fields.Selection([
@@ -181,6 +182,15 @@ class HolidaysAllocation(models.Model):
allocations = self.sudo().search(domain)
return [('id', 'in', allocations.ids)]
+ @api.depends('name', 'date_from', 'date_to')
+ def _compute_description_validity(self):
+ for allocation in self:
+ if allocation.date_to:
+ name_validity = _("%s (from %s to %s)", allocation.name, allocation.date_from.strftime("%b %d %Y"), allocation.date_to.strftime("%b %d %Y"))
+ else:
+ name_validity = _("%s (from %s to No Limit)", allocation.name, allocation.date_from.strftime("%b %d %Y"))
+ allocation.name_validity = name_validity
+
@api.depends('employee_id', 'holiday_status_id', 'taken_leave_ids.number_of_days', 'taken_leave_ids.state')
def _compute_leaves(self):
for allocation in self:
diff --git a/addons/hr_holidays/models/hr_leave_type.py b/addons/hr_holidays/models/hr_leave_type.py
index d93f54ebb30..241c29316ff 100644
--- a/addons/hr_holidays/models/hr_leave_type.py
+++ b/addons/hr_holidays/models/hr_leave_type.py
@@ -327,7 +327,8 @@ class HolidaysType(models.Model):
employee_id = self._get_contextual_employee_id()
if employee_id:
- data_days = self.get_employees_days([employee_id])[employee_id]
+ data_days = (self.get_employees_days(employee_id)[employee_id[0]] if isinstance(employee_id, list) else
+ self.get_employees_days([employee_id])[employee_id])
for holiday_status in self:
result = data_days.get(holiday_status.id, {})
diff --git a/addons/hr_holidays/report/hr_leave_report_calendar.xml b/addons/hr_holidays/report/hr_leave_report_calendar.xml
index 341761d99ce..4af565e6a7f 100644
--- a/addons/hr_holidays/report/hr_leave_report_calendar.xml
+++ b/addons/hr_holidays/report/hr_leave_report_calendar.xml
@@ -37,10 +37,9 @@
-
-
+
-
+
diff --git a/addons/hr_holidays/report/hr_leave_reports.xml b/addons/hr_holidays/report/hr_leave_reports.xml
index f6b9b822bc2..b3ef2d4296d 100644
--- a/addons/hr_holidays/report/hr_leave_reports.xml
+++ b/addons/hr_holidays/report/hr_leave_reports.xml
@@ -13,8 +13,7 @@
-
-
+
diff --git a/addons/hr_holidays/static/src/js/time_off_calendar.js b/addons/hr_holidays/static/src/js/time_off_calendar.js
index 120a8fa3167..03db8ffc202 100644
--- a/addons/hr_holidays/static/src/js/time_off_calendar.js
+++ b/addons/hr_holidays/static/src/js/time_off_calendar.js
@@ -8,6 +8,7 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) {
var CalendarRenderer = require("web.CalendarRenderer");
var CalendarModel = require("web.CalendarModel");
var CalendarView = require("web.CalendarView");
+ var dialogs = require('web.view_dialogs');
var viewRegistry = require('web.view_registry');
var _t = core._t;
@@ -82,12 +83,32 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) {
* @private
*/
_onNewTimeOff: function () {
- var self = this;
- //use formViewId for this view?
- this.do_action('hr_holidays.hr_leave_action_my_request', {
- on_close: function () {
- self.reload();
- }
+ let self = this;
+
+ let domain = [
+ ['name', '=', 'hr.leave.view.form.dashboard.new.time.off'],
+ ['model', '=', 'hr.leave']
+ ];
+
+ self._rpc({
+ model: 'ir.ui.view',
+ method: 'search',
+ args: [domain],
+ }).then(function(ids) {
+ self.timeOffDialog = new dialogs.FormViewDialog(self, {
+ res_model: "hr.leave",
+ view_id: ids,
+ context: {
+ 'default_date_from': moment().format('YYYY-MM-DD'),
+ 'default_date_to': moment().add(1, 'days').format('YYYY-MM-DD'),
+ },
+ title: _t("New time off"),
+ disable_multiple_selection: true,
+ on_saved: function() {
+ self.reload();
+ },
+ });
+ self.timeOffDialog.open();
});
},
@@ -97,18 +118,32 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) {
* @private
*/
_onNewAllocation: function () {
- var self = this;
- this.do_action({
- type: 'ir.actions.act_window',
- res_model: 'hr.leave.allocation',
- name: 'New Allocation Request',
- views: [[false,'form']],
- context: {'form_view_ref': 'hr_holidays.hr_leave_allocation_view_form_dashboard'},
- target: 'new',
- }, {
- on_close: function () {
- self.reload();
- }
+ let self = this;
+
+ let domain = [
+ ['name', '=', 'hr.leave.view.form.dashboard'],
+ ['model', '=', 'hr.leave.allocation']
+ ];
+
+ self._rpc({
+ model: 'ir.ui.view',
+ method: 'search',
+ args: [domain],
+ }).then(function(ids) {
+ self.allocationDialog = new dialogs.FormViewDialog(self, {
+ res_model: "hr.leave.allocation",
+ view_id: ids,
+ context: {
+ 'default_employee_ids': self.context.employee_id,
+ 'default_state': 'confirm',
+ },
+ title: _t("New Allocation"),
+ disable_multiple_selection: true,
+ on_saved: function() {
+ self.reload();
+ },
+ });
+ self.allocationDialog.open();
});
},
diff --git a/addons/hr_holidays/static/src/js/time_off_calendar_employee.js b/addons/hr_holidays/static/src/js/time_off_calendar_employee.js
new file mode 100644
index 00000000000..3aa71c6c367
--- /dev/null
+++ b/addons/hr_holidays/static/src/js/time_off_calendar_employee.js
@@ -0,0 +1,266 @@
+odoo.define('hr_holidays.employee.dashboard.views', function(require) {
+ 'use strict';
+
+ var core = require('web.core');
+ const config = require('web.config');
+ var CalendarController = require("web.CalendarController");
+ var CalendarRenderer = require("web.CalendarRenderer");
+ var CalendarPopover = require('web.CalendarPopover');
+ var CalendarView = require("web.CalendarView");
+ var dialogs = require('web.view_dialogs');
+ var viewRegistry = require('web.view_registry');
+
+ var _t = core._t;
+ var QWeb = core.qweb;
+
+ // TODO in master: Split the timeoff calendar components into several files to reuse or extend them here
+
+ var TimeOffCalendarPopover = CalendarPopover.extend({
+ template: 'hr_holidays.calendar.popover',
+
+ init: function (parent, eventInfo) {
+ this._super.apply(this, arguments);
+ const state = this.event.extendedProps.record.state;
+ this.canDelete = state && ['validate', 'refuse'].indexOf(state) === -1;
+ this.canEdit = state !== undefined;
+ this.displayFields = [];
+
+ if (this.modelName === "hr.leave.report.calendar") {
+ const duration = this.event.extendedProps.record.display_name.split(':').slice(-1);
+ this.display_name = _.str.sprintf(_t("Time Off : %s"), duration);
+ } else {
+ this.display_name = this.event.extendedProps.record.display_name;
+ }
+ },
+ });
+
+ var TimeOffPopoverRenderer = CalendarRenderer.extend({
+ template: "TimeOff.CalendarView.extend",
+ /**
+ * We're overriding this to display the weeknumbers on the year view
+ *
+ * @override
+ * @private
+ */
+ _getFullCalendarOptions: function () {
+ const oldOptions = this._super(...arguments);
+ // Parameters
+ oldOptions.views.dayGridYear.weekNumbers = true;
+ oldOptions.views.dayGridYear.weekNumbersWithinDays = false;
+ return oldOptions;
+ },
+
+ config: _.extend({}, CalendarRenderer.prototype.config, {
+ CalendarPopover: TimeOffCalendarPopover,
+ }),
+
+ _getPopoverParams: function (eventData) {
+ let params = this._super.apply(this, arguments);
+ let calendarIcon;
+ let state = eventData.extendedProps.record.state;
+
+ if (state === 'validate') {
+ calendarIcon = 'fa-calendar-check-o';
+ } else if (state === 'refuse') {
+ calendarIcon = 'fa-calendar-times-o';
+ } else if(state) {
+ calendarIcon = 'fa-calendar-o';
+ }
+
+ params['title'] = eventData.extendedProps.record.display_name.split(':').slice(0, -1).join(':');
+ params['template'] = QWeb.render('hr_holidays.calendar.popover.placeholder', {color: this.getColor(eventData.color_index), calendarIcon: calendarIcon});
+ return params;
+ },
+
+ _render: function () {
+ var self = this;
+ return this._super.apply(this, arguments).then(function () {
+ self.$el.parent().find('.o_calendar_mini').hide();
+ });
+ },
+ /**
+ * @override
+ * @private
+ */
+ _renderCalendar: function() {
+ this._super.apply(this, arguments);
+ let weekNumbers = this.$el.find('.fc-week-number');
+ weekNumbers.each( function() {
+ let weekRow = this.parentNode;
+ // By default, each month has 6 weeks displayed, hide the week number if there is no days for the week
+ if(!weekRow.children[1].classList.length && !weekRow.children[weekRow.children.length-1].classList.length) {
+ this.innerHTML = '';
+ }
+ });
+ },
+ });
+
+ var TimeOffCalendarRenderer = TimeOffPopoverRenderer.extend({
+ _render: function () {
+ var self = this;
+ return this._super.apply(this, arguments).then(function () {
+ return self._rpc({
+ model: 'hr.leave.type',
+ method: 'get_days_all_request',
+ context: self.state.context,
+ });
+ }).then(function (result) {
+ self.$el.parent().find('.o_calendar_mini').hide();
+ self.$el.parent().find('.o_timeoff_container').remove();
+
+ // Do not display header if there is no element to display
+ if (result.length > 0) {
+ if (config.device.isMobile) {
+ result.forEach((data) => {
+ const elem = QWeb.render('hr_holidays.dashboard_calendar_header_mobile', {
+ timeoff: data,
+ });
+ self.$el.find('.o_calendar_filter_item[data-value=' + data[4] + '] .o_cw_filter_title').append(elem);
+ });
+ } else {
+ const elem = QWeb.render('hr_holidays.dashboard_calendar_header', {
+ timeoffs: result,
+ });
+ self.$el.before(elem);
+ }
+ }
+ });
+ },
+ });
+
+ var TimeOffCalendarEmployeeController = CalendarController.extend({
+
+ events: _.extend({}, CalendarController.prototype.events, {
+ 'click .btn-time-off': '_onNewTimeOff',
+ 'click .btn-allocation': '_onNewAllocation',
+ }),
+
+ /**
+ * @override
+ */
+ start: function () {
+ this.$el.addClass('o_timeoff_calendar');
+ return this._super(...arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * Render the buttons and add new button about
+ * time off and allocations request
+ *
+ * @override
+ */
+
+ renderButtons: function ($node) {
+ this._super.apply(this, arguments);
+
+ $(QWeb.render('hr_holidays.dashboard.calendar.button', {
+ time_off: _t('New Time Off'),
+ request: _t('New Allocation Request'),
+ })).appendTo(this.$buttons);
+
+ if ($node) {
+ this.$buttons.appendTo($node);
+ } else {
+ this.$('.o_calendar_buttons').replaceWith(this.$buttons);
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Action: create a new time off request
+ *
+ * @private
+ */
+ _onNewTimeOff: function () {
+ let self = this;
+
+ let domain = [
+ ['name', '=', 'hr.leave.view.form.dashboard.new.time.off']
+ ];
+
+ self._rpc({
+ model: 'ir.ui.view',
+ method: 'search',
+ args: [domain],
+ }).then(function(ids) {
+ self.timeOffDialog = new dialogs.FormViewDialog(self, {
+ res_model: "hr.leave",
+ view_id: ids,
+ context: {
+ 'default_employee_id': self.context.employee_id[0],
+ 'default_date_from': moment().format('YYYY-MM-DD'),
+ 'default_date_to': moment().add(1, 'days').format('YYYY-MM-DD'),
+ },
+ title: _t("New time off"),
+ disable_multiple_selection: true,
+ on_saved: function() {
+ self.reload();
+ },
+ });
+ self.timeOffDialog.open();
+ });
+ },
+
+ /**
+ * Action: create a new allocation request
+ *
+ * @private
+ */
+ _onNewAllocation: function () {
+ let self = this;
+
+ let domain = [
+ ['name', '=', 'hr.leave.allocation.view.form.manager.dashboard']
+ ];
+
+ self._rpc({
+ model: 'ir.ui.view',
+ method: 'search',
+ args: [domain],
+ }).then(function(ids) {
+ self.allocationDialog = new dialogs.FormViewDialog(self, {
+ res_model: "hr.leave.allocation",
+ view_id: ids,
+ context: {
+ 'default_employee_ids': self.context.employee_id,
+ 'default_state': 'confirm',
+ },
+ title: _t("New Allocation"),
+ disable_multiple_selection: true,
+ on_saved: function() {
+ self.reload();
+ },
+ });
+ self.allocationDialog.open();
+ });
+ },
+
+ _onOpenCreate: function () {
+ this.context['default_employee_id'] = this.context.employee_id[0];
+ this._super(...arguments);
+ },
+
+ /**
+ * @override
+ */
+ _setEventTitle: function () {
+ return _t('Time Off Request');
+ },
+ });
+
+ var TimeOffCalendarEmployeeView = CalendarView.extend({
+ config: _.extend({}, CalendarView.prototype.config, {
+ Controller: TimeOffCalendarEmployeeController,
+ Renderer: TimeOffCalendarRenderer,
+ }),
+ });
+
+ viewRegistry.add('time_off_employee_calendar', TimeOffCalendarEmployeeView);
+});
diff --git a/addons/hr_holidays/views/hr_leave_allocation_views.xml b/addons/hr_holidays/views/hr_leave_allocation_views.xml
index 058d62fe4bd..22d36f03f4b 100644
--- a/addons/hr_holidays/views/hr_leave_allocation_views.xml
+++ b/addons/hr_holidays/views/hr_leave_allocation_views.xml
@@ -17,8 +17,8 @@
-
-
+
+
@@ -90,8 +90,9 @@
-
-
+
+
+
@@ -103,14 +104,15 @@
-
+
@@ -157,6 +159,11 @@
+
+
+
+
+
@@ -185,13 +192,20 @@
0
-
- 0
- {'readonly': [('allocation_type', '=', 'accrual'), ('state', 'not in', ('draft', 'confirm'))]}
+
+
-
- 0
- {'readonly': [('allocation_type', '=', 'accrual'), ('state', 'not in', ('draft', 'confirm'))]}
+
+
+
+
+
+
+
+
+
+
+ No limit
@@ -215,12 +229,22 @@
-
-
+
+
+
+
+ hr.leave.allocation.view.form.manager.dashboard
+ hr.leave.allocation
+
+ primary
+ 16
+
+
+ 1
+
@@ -287,7 +311,7 @@
1
-
+
1
@@ -448,7 +472,7 @@
Allocations
hr.leave.allocation
tree,form,kanban,activity
- {'search_default_managed_people': 1,'search_default_approve': 2, 'search_default_active_employee': 3}
+ {'search_default_my_team': 1,'search_default_approve': 2, 'search_default_active_employee': 3}
[]
diff --git a/addons/hr_holidays/views/hr_leave_type_views.xml b/addons/hr_holidays/views/hr_leave_type_views.xml
index 4ae2b7b6767..4b5671fdfe9 100644
--- a/addons/hr_holidays/views/hr_leave_type_views.xml
+++ b/addons/hr_holidays/views/hr_leave_type_views.xml
@@ -53,7 +53,7 @@
attrs="{
'invisible': [('leave_validation_type', 'in', ['no_validation', 'manager']), '|', ('requires_allocation', '=', 'no'), ('allocation_validation_type', '=', 'officer')]}"/>
-
+
@@ -65,11 +65,14 @@
-
-
- Visual
-
+
+
+
+ Display Option
+
+
+
diff --git a/addons/hr_holidays/views/hr_leave_views.xml b/addons/hr_holidays/views/hr_leave_views.xml
index f676b463412..81416efb6d1 100644
--- a/addons/hr_holidays/views/hr_leave_views.xml
+++ b/addons/hr_holidays/views/hr_leave_views.xml
@@ -48,9 +48,9 @@
-
-
+
+
@@ -384,6 +384,20 @@
+
+ hr.leave.view.dashboard
+ hr.leave
+
+
+
+
+
+
+
+
+
+
+
hr.leave.view.form.manager
hr.leave
@@ -524,7 +538,7 @@
1
-
+
1
@@ -643,7 +657,7 @@
{
'search_default_approve': 1,
- 'search_default_managed_people': 2,
+ 'search_default_my_team': 2,
'search_default_active_employee': 3,
'search_default_active_time_off': 4,
'hide_employee_name': 1}
diff --git a/addons/hr_holidays/views/hr_views.xml b/addons/hr_holidays/views/hr_views.xml
index 7f2c29d7faa..f6c34438c79 100644
--- a/addons/hr_holidays/views/hr_views.xml
+++ b/addons/hr_holidays/views/hr_views.xml
@@ -168,10 +168,10 @@
-
-
-
-
diff --git a/addons/project_timesheet_holidays/views/hr_holidays_views.xml b/addons/project_timesheet_holidays/views/hr_holidays_views.xml
index 1ccfcfad294..2c25ad5c8b7 100644
--- a/addons/project_timesheet_holidays/views/hr_holidays_views.xml
+++ b/addons/project_timesheet_holidays/views/hr_holidays_views.xml
@@ -6,8 +6,9 @@
hr.leave.type
-
-
+
+
+ Timesheets