diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py index 1b82c77662d..bcd52facd13 100644 --- a/addons/hr_holidays/models/hr_leave.py +++ b/addons/hr_holidays/models/hr_leave.py @@ -256,6 +256,8 @@ class HolidaysRequest(models.Model): request_unit_half = fields.Boolean('Half Day', compute='_compute_request_unit_half', store=True, readonly=False) request_unit_hours = fields.Boolean('Custom Hours', compute='_compute_request_unit_hours', store=True, readonly=False) request_unit_custom = fields.Boolean('Days-long custom hours', compute='_compute_request_unit_custom', store=True, readonly=False) + # view + is_hatched = fields.Boolean('Hatched', compute='_compute_is_hatched') _sql_constraints = [ ('type_value', @@ -282,7 +284,7 @@ class HolidaysRequest(models.Model): is_officer = self.user_has_groups('hr_holidays.group_hr_holidays_user') for leave in self: - if is_officer or leave.user_id == self.env.user or leave.manager_id == self.env.user: + if is_officer or leave.user_id == self.env.user or leave.employee_id.leave_manager_id == self.env.user: leave.name = leave.sudo().private_name else: leave.name = '*****' @@ -291,7 +293,7 @@ class HolidaysRequest(models.Model): is_officer = self.user_has_groups('hr_holidays.group_hr_holidays_user') for leave in self: - if is_officer or leave.user_id == self.env.user or leave.manager_id == self.env.user: + if is_officer or leave.user_id == self.env.user or leave.employee_id.leave_manager_id == self.env.user: leave.sudo().private_name = leave.name def _search_description(self, operator, value): @@ -548,6 +550,11 @@ class HolidaysRequest(models.Model): else: holiday.can_approve = True + @api.depends('state') + def _compute_is_hatched(self): + for holiday in self: + holiday.is_hatched = holiday.state not in ['refuse', 'validate'] + @api.constrains('date_from', 'date_to', 'employee_id') def _check_date(self): for holiday in self.filtered('employee_id'): diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py index f92c72b15af..c4896756a01 100644 --- a/addons/hr_holidays/models/hr_leave_allocation.py +++ b/addons/hr_holidays/models/hr_leave_allocation.py @@ -229,7 +229,7 @@ class HolidaysAllocation(models.Model): is_officer = self.env.user.has_group('hr_holidays.group_hr_holidays_user') for allocation in self: - if is_officer or allocation.employee_id.user_id == self.env.user or allocation.manager_id == self.env.user: + if is_officer or allocation.employee_id.user_id == self.env.user or allocation.employee_id.leave_manager_id == self.env.user: allocation.name = allocation.sudo().private_name else: allocation.name = '*****' @@ -237,7 +237,7 @@ class HolidaysAllocation(models.Model): def _inverse_description(self): is_officer = self.env.user.has_group('hr_holidays.group_hr_holidays_user') for allocation in self: - if is_officer or allocation.employee_id.user_id == self.env.user or allocation.manager_id == self.env.user: + if is_officer or allocation.employee_id.user_id == self.env.user or allocation.employee_id.leave_manager_id == self.env.user: allocation.sudo().private_name = allocation.name def _search_description(self, operator, value): diff --git a/addons/hr_holidays/report/hr_leave_report_calendar.py b/addons/hr_holidays/report/hr_leave_report_calendar.py index 0a5b45db9c6..1dd3cd1288a 100644 --- a/addons/hr_holidays/report/hr_leave_report_calendar.py +++ b/addons/hr_holidays/report/hr_leave_report_calendar.py @@ -28,6 +28,8 @@ class LeaveReportCalendar(models.Model): ('validate', 'Approved') ], readonly=True) + is_hatched = fields.Boolean('Hatched', readonly=True) + def init(self): tools.drop_view_if_exists(self._cr, 'hr_leave_report_calendar') self._cr.execute("""CREATE OR REPLACE VIEW hr_leave_report_calendar AS @@ -42,7 +44,8 @@ class LeaveReportCalendar(models.Model): CASE WHEN hl.holiday_type = 'employee' THEN rr.tz ELSE %s - END AS tz + END AS tz, + state != 'validate' as is_hatched FROM hr_leave hl LEFT JOIN hr_employee em ON em.id = hl.employee_id diff --git a/addons/hr_holidays/report/hr_leave_report_calendar.xml b/addons/hr_holidays/report/hr_leave_report_calendar.xml index c7f8a78e477..a62f10faa1e 100644 --- a/addons/hr_holidays/report/hr_leave_report_calendar.xml +++ b/addons/hr_holidays/report/hr_leave_report_calendar.xml @@ -19,6 +19,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 ef9f262d861..3f863de98bf 100644 --- a/addons/hr_holidays/static/src/js/time_off_calendar.js +++ b/addons/hr_holidays/static/src/js/time_off_calendar.js @@ -138,6 +138,20 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) { var self = this; return this._super.apply(this, arguments).then(function () { self.$el.parent().find('.o_calendar_mini').hide(); + + // Check if there is a filter to display on the sidebar + // If there is no filter, hide the sidebar + const noFilters = !Object.values(self.state.filters).some(f => f.filters.length); + + // Remove the no data sidebar + self.$sidebar.find('#o_calendar_filter_no_data').remove(); + if (noFilters) { + // Show a special sidebar + self.$sidebar.html(QWeb.render('hr_holidays.calendar.sidebar.nofilter', { + title: 'Time Off Type', + description: '(no data)' + })); + } }); }, }); @@ -154,10 +168,14 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) { }).then(function (result) { self.$el.parent().find('.o_calendar_mini').hide(); self.$el.parent().find('.o_timeoff_container').remove(); - var elem = QWeb.render('hr_holidays.dashboard_calendar_header', { - timeoffs: result, - }); - self.$el.before(elem); + + // Do not display header if there is no element to display + if (result.length > 0) { + var elem = QWeb.render('hr_holidays.dashboard_calendar_header', { + timeoffs: result, + }); + self.$el.before(elem); + } }); }, }); diff --git a/addons/hr_holidays/static/src/scss/time_off.scss b/addons/hr_holidays/static/src/scss/time_off.scss index f37b89caf03..ffd80c80722 100644 --- a/addons/hr_holidays/static/src/scss/time_off.scss +++ b/addons/hr_holidays/static/src/scss/time_off.scss @@ -1,10 +1,14 @@ .o_timeoff_calendar .o_content { .o_timeoff_container { height: 6rem; + + + .o_calendar_container { + height: calc(100% - 6rem); + } } .o_calendar_container { - height: calc(100% - 6rem); + height: 100%; } @include media-breakpoint-down(sm) { diff --git a/addons/hr_holidays/static/src/xml/time_off_calendar.xml b/addons/hr_holidays/static/src/xml/time_off_calendar.xml index b041222d7fc..97397fc8d5b 100644 --- a/addons/hr_holidays/static/src/xml/time_off_calendar.xml +++ b/addons/hr_holidays/static/src/xml/time_off_calendar.xml @@ -75,4 +75,18 @@ + + +
+
+
+
+

+ + +

+
+
+
+
diff --git a/addons/hr_holidays/views/hr_leave_views.xml b/addons/hr_holidays/views/hr_leave_views.xml index be46d191e47..1ee485953d5 100644 --- a/addons/hr_holidays/views/hr_leave_views.xml +++ b/addons/hr_holidays/views/hr_leave_views.xml @@ -353,10 +353,11 @@ hr.leave.view.dashboard hr.leave - + + @@ -432,10 +433,11 @@ hr.leave.view.calendar hr.leave - + + @@ -605,7 +607,7 @@ My Time Off Requests ir.actions.act_window hr.leave - tree,form,calendar,kanban,activity + tree,form,kanban,activity {} [('user_id', '=', uid)] diff --git a/addons/web/static/src/js/libs/fullcalendar.js b/addons/web/static/src/js/libs/fullcalendar.js index 7f9714e80e6..16040936ea0 100644 --- a/addons/web/static/src/js/libs/fullcalendar.js +++ b/addons/web/static/src/js/libs/fullcalendar.js @@ -64,6 +64,8 @@ odoo.define('/web/static/src/js/libs/fullcalendar.js', function () { this.el.classList.add('fc-readonly-year-view'); } this.months = []; + const currentMonthNumber = (new Date()).getMonth(); + let currentMonthEl = undefined; for (let monthNumber = 0; monthNumber < 12; monthNumber++) { const monthDate = new Date(this.currentDate.getFullYear(), monthNumber); const monthShortName = moment(monthDate).format('MMM').toLowerCase(); @@ -76,6 +78,13 @@ odoo.define('/web/static/src/js/libs/fullcalendar.js', function () { const calendar = this._createMonthCalendar(el, monthDate); this.months.push({ el, calendar }); calendar.render(); + if (monthNumber === currentMonthNumber) { + currentMonthEl = el; + } + } + + if (currentMonthEl !== undefined) { + currentMonthEl.scrollIntoView(); } } /** diff --git a/addons/web/static/src/scss/web_calendar.scss b/addons/web/static/src/scss/web_calendar.scss index 9c429491cd7..007e66d2bcd 100644 --- a/addons/web/static/src/scss/web_calendar.scss +++ b/addons/web/static/src/scss/web_calendar.scss @@ -40,7 +40,7 @@ $o-cw-filter-avatar-size: 20px; .fc-bg { background-color: mix(theme-color('primary'), white); // Used for placeholder events only (on creation) @include size(101%); // Compensate border - opacity: 0.9; + opacity: 0.7; transition: opacity 0.2s; } @@ -303,9 +303,6 @@ $o-cw-filter-avatar-size: 20px; } &.o_cw_nobg { - .fc-bg { - visibility: hidden; - } &.o_cw_custom_hover, &.o_cw_custom_highlight, &:hover { .fc-bg { @@ -377,8 +374,8 @@ $o-cw-filter-avatar-size: 20px; margin: auto; > .fc-toolbar.fc-header-toolbar { - margin-top: 15px; - margin-bottom: 5px; + margin-top: 10px; + margin-bottom: 4px; cursor: default; h2 { @@ -397,7 +394,7 @@ $o-cw-filter-avatar-size: 20px; } .fc-week.fc-row { - min-height: unset; + min-height: 2rem; } .fc-disabled-day { @@ -406,7 +403,7 @@ $o-cw-filter-avatar-size: 20px; .fc-day-top { text-align: center; - padding: 5px; + padding: 0.4vh; cursor: pointer; > .fc-day-number { @@ -415,7 +412,7 @@ $o-cw-filter-avatar-size: 20px; line-height: unset; margin: auto; padding: 0.1rem 0; - font-size: 1.2rem; + font-size: calc(11px + 0.2vh); } &.fc-today > .fc-day-number { @@ -718,15 +715,28 @@ $o-cw-filter-avatar-size: 20px; $color: nth($o-colors-complete, $i); .o_calendar_view .fc-view { + .o_calendar_hatched_#{$i - 1} { + background: repeating-linear-gradient( + 45deg, + $color, + $color 10px, + rgba($color, 0.4) 10px, + rgba($color, 0.4) 20px + ) !important; + + .fc-bg { + background: rgba(white, 0.5); + } + } .fc-bgevent.o_calendar_color_#{$i - 1} { border-color: $color; background-color: $color; - opacity: 0.2; + opacity: 0.5; } .fc-event.o_calendar_color_#{$i - 1} { border-color: $color; - color: darken($color, 35%); - opacity: 0.8; + color: darken($color, 45%); + opacity: 0.75; &.o_event_hightlight { opacity: 1; @@ -736,8 +746,11 @@ $o-cw-filter-avatar-size: 20px; } } - .fc-bg { - background: mix($color, white); + &:not(.o_calendar_hatched) { + background: $color; + .fc-bg { + background: mix($color, white); + } } &.o_cw_custom_hover, &.o_cw_custom_highlight { @@ -745,7 +758,13 @@ $o-cw-filter-avatar-size: 20px; color: color-yiq($color); .fc-bg { - background: $color; + background: repeating-linear-gradient( + 45deg, + $color, + $color 10px, + rgba($color, 0.5) 10px, + rgba($color, 0.5) 20px + ); } } } diff --git a/addons/web/static/src/xml/web_calendar.xml b/addons/web/static/src/xml/web_calendar.xml index c54329e663b..05c71c7312b 100644 --- a/addons/web/static/src/xml/web_calendar.xml +++ b/addons/web/static/src/xml/web_calendar.xml @@ -12,7 +12,7 @@ -
+
@@ -52,14 +52,15 @@ +
-
+
-
+
diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js index 6ecb0af0fe0..308ac98fde8 100644 --- a/addons/web/static/tests/views/calendar_tests.js +++ b/addons/web/static/tests/views/calendar_tests.js @@ -57,17 +57,18 @@ QUnit.module('Views', { allday: {string: "allday", type: "boolean"}, partner_ids: {string: "attendees", type: "one2many", relation: 'partner', default: [[6, 0, [1]]]}, type: {string: "type", type: "integer"}, - event_type_id: {string: "Event_Type", type: "many2one", relation: 'event_type'}, + event_type_id: {string: "Event Type", type: "many2one", relation: 'event_type'}, color: {string: "Color", type: "integer", related: 'event_type_id.color'}, + is_hatched: {string: "Hatched", type: "boolean"} }, records: [ - {id: 1, user_id: session.uid, partner_id: 1, name: "event 1", start: "2016-12-11 00:00:00", stop: "2016-12-11 00:00:00", allday: false, partner_ids: [1,2,3], type: 1}, - {id: 2, user_id: session.uid, partner_id: 1, name: "event 2", start: "2016-12-12 10:55:05", stop: "2016-12-12 14:55:05", allday: false, partner_ids: [1,2], type: 3}, - {id: 3, user_id: 4, partner_id: 4, name: "event 3", start: "2016-12-12 15:55:05", stop: "2016-12-12 16:55:05", allday: false, partner_ids: [1], type: 2}, - {id: 4, user_id: session.uid, partner_id: 1, name: "event 4", start: "2016-12-14 15:55:05", stop: "2016-12-14 18:55:05", allday: true, partner_ids: [1], type: 2}, - {id: 5, user_id: 4, partner_id: 4, name: "event 5", start: "2016-12-13 15:55:05", stop: "2016-12-20 18:55:05", allday: false, partner_ids: [2,3], type: 2}, - {id: 6, user_id: session.uid, partner_id: 1, name: "event 6", start: "2016-12-18 08:00:00", stop: "2016-12-18 09:00:00", allday: false, partner_ids: [3], type: 3}, - {id: 7, user_id: session.uid, partner_id: 1, name: "event 7", start: "2016-11-14 08:00:00", stop: "2016-11-16 17:00:00", allday: false, partner_ids: [2], type: 1}, + {id: 1, user_id: session.uid, partner_id: 1, name: "event 1", start: "2016-12-11 00:00:00", stop: "2016-12-11 00:00:00", allday: false, partner_ids: [1,2,3], type: 1, is_hatched: false}, + {id: 2, user_id: session.uid, partner_id: 1, name: "event 2", start: "2016-12-12 10:55:05", stop: "2016-12-12 14:55:05", allday: false, partner_ids: [1,2], type: 3, is_hatched: false}, + {id: 3, user_id: 4, partner_id: 4, name: "event 3", start: "2016-12-12 15:55:05", stop: "2016-12-12 16:55:05", allday: false, partner_ids: [1], type: 2, is_hatched: true}, + {id: 4, user_id: session.uid, partner_id: 1, name: "event 4", start: "2016-12-14 15:55:05", stop: "2016-12-14 18:55:05", allday: true, partner_ids: [1], type: 2, is_hatched: false}, + {id: 5, user_id: 4, partner_id: 4, name: "event 5", start: "2016-12-13 15:55:05", stop: "2016-12-20 18:55:05", allday: false, partner_ids: [2,3], type: 2, is_hatched: true}, + {id: 6, user_id: session.uid, partner_id: 1, name: "event 6", start: "2016-12-18 08:00:00", stop: "2016-12-18 09:00:00", allday: false, partner_ids: [3], type: 3, is_hatched: true}, + {id: 7, user_id: session.uid, partner_id: 1, name: "event 7", start: "2016-11-14 08:00:00", stop: "2016-11-16 17:00:00", allday: false, partner_ids: [2], type: 1, is_hatched: false}, ], check_access_rights: function () { return Promise.resolve(true); @@ -2189,7 +2190,7 @@ QUnit.module('Views', { }); QUnit.test('Add filters and specific color', async function (assert) { - assert.expect(5); + assert.expect(6); this.data.event.records.push( {id: 8, user_id: 4, partner_id: 1, name: "event 8", start: "2016-12-11 09:00:00", stop: "2016-12-11 10:00:00", allday: false, partner_ids: [1,2,3], event_type_id: 3, color: 4}, @@ -2218,8 +2219,9 @@ QUnit.module('Views', { assert.containsN(calendar, '.o_calendar_filter', 2, "should display 2 filters"); - var $typeFilter = calendar.$('.o_calendar_filter:has(h5:contains(Event_Type))'); + var $typeFilter = calendar.$('.o_calendar_filter:has(h5:contains(Event Type))'); assert.ok($typeFilter.length, "should display 'Event Type' filter"); + assert.containsOnce($typeFilter, '#o_cw_filter_collapse_EventType', "Id should be equals to o_cw_filter_collapse_EventType for 'Event Type'"); assert.containsN($typeFilter, '.o_calendar_filter_item', 3, "should display 3 filter items for 'Event Type'"); assert.containsOnce($typeFilter, '.o_calendar_filter_item[data-value=3].o_cw_filter_color_4', "Filter for event type 3 must have the color 4"); @@ -3681,7 +3683,7 @@ QUnit.module('Views', { }); QUnit.test('correctly display year view', async function (assert) { - assert.expect(27); + assert.expect(28); const calendar = await createCalendarView({ View: CalendarView, @@ -3700,6 +3702,7 @@ QUnit.module('Views', { > + `, archs: archs, viewOptions: { @@ -3716,6 +3719,9 @@ QUnit.module('Views', { assert.containsN(calendar.el, '.fc-bgevent', 7, 'There should be 6 events displayed but there is 1 split on 2 weeks'); + assert.containsN(calendar.el, '.o_calendar_hatched', 3, + 'There should be 3 events that are hatched') + async function clickDate(date) { const el = calendar.el.querySelector(`.fc-day-top[data-date="${date}"]`); el.scrollIntoView(); // scroll to it as the calendar could be too small