From f90e5a53000749f23fc9e08cc507a04b42fa8095 Mon Sep 17 00:00:00 2001 From: Kevin Baptiste Date: Wed, 21 Sep 2022 12:48:54 +0000 Subject: [PATCH] [IMP] hr_holidays: migrate calendar views to Owl task-2991495 closes odoo/odoo#101658 X-original-commit: fb2dfd438c240848c4410b040d2177e4ff6a71b3 Signed-off-by: Kevin Baptiste Signed-off-by: William Braeckman (wbr) --- addons/hr_holidays/__manifest__.py | 5 +- addons/hr_holidays/models/hr_leave.py | 13 +- .../report/hr_leave_report_calendar.xml | 11 +- .../src/dashboard/time_off_dashboard.js | 37 ++- .../src/dashboard/time_off_dashboard.xml | 2 +- .../src/js/form/allocation_leave_form.js | 43 ---- .../hr_holidays/static/src/js/radio_image.js | 37 --- .../time_off_calendar_controller.js | 228 ------------------ .../time_off_calendar_employee_controller.js | 33 --- .../time_off_calendar_employee_views.js | 15 -- .../time_off_calendar_model.js | 29 --- .../time_off_calendar_popover.js | 23 -- .../time_off_calendar_renderer.js | 68 ------ .../time_off_calendar_views.js | 40 --- .../time_off_popover_renderer.js | 89 ------- .../static/src/leave_stats/leave_stats.js | 111 ++++----- .../static/src/leave_stats/leave_stats.xml | 4 +- .../hr_holidays/static/src/scss/time_off.scss | 107 -------- .../static/src/tours/hr_holidays_tour.js | 40 +-- .../src/views/calendar/calendar_controller.js | 105 ++++++++ .../views/calendar/calendar_controller.xml | 21 ++ .../src/views/calendar/calendar_model.js | 53 ++++ .../src/views/calendar/calendar_renderer.js | 33 +++ .../src/views/calendar/calendar_renderer.scss | 43 ++++ .../src/views/calendar/calendar_renderer.xml | 11 + .../src/views/calendar/calendar_view.js | 25 ++ .../common/calendar_common_popover.js | 25 ++ .../common/calendar_common_renderer.js | 23 ++ .../filter_panel/calendar_filter_panel.js | 47 ++++ .../filter_panel/calendar_filter_panel.xml | 31 +++ .../calendar/year/calendar_year_renderer.js | 40 +++ .../calendar/year/calendar_year_renderer.scss | 14 ++ addons/hr_holidays/static/src/views/hooks.js | 35 +++ .../src/views/view_dialog/form_view_dialog.js | 63 +++++ .../views/view_dialog/form_view_dialog.xml | 14 ++ .../static/src/xml/time_off_calendar.xml | 72 ------ .../test_leave_stats_widget.js | 150 ++++++------ .../views/hr_leave_allocation_views.xml | 12 +- .../hr_holidays/views/hr_leave_type_views.xml | 2 +- addons/hr_holidays/views/hr_leave_views.xml | 46 ++-- 40 files changed, 818 insertions(+), 982 deletions(-) delete mode 100644 addons/hr_holidays/static/src/js/form/allocation_leave_form.js delete mode 100644 addons/hr_holidays/static/src/js/radio_image.js delete mode 100644 addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_controller.js delete mode 100644 addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_employee_controller.js delete mode 100644 addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_employee_views.js delete mode 100644 addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_model.js delete mode 100644 addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_popover.js delete mode 100644 addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_renderer.js delete mode 100644 addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_views.js delete mode 100644 addons/hr_holidays/static/src/js/time_off_calendar/time_off_popover_renderer.js create mode 100644 addons/hr_holidays/static/src/views/calendar/calendar_controller.js create mode 100644 addons/hr_holidays/static/src/views/calendar/calendar_controller.xml create mode 100644 addons/hr_holidays/static/src/views/calendar/calendar_model.js create mode 100644 addons/hr_holidays/static/src/views/calendar/calendar_renderer.js create mode 100644 addons/hr_holidays/static/src/views/calendar/calendar_renderer.scss create mode 100644 addons/hr_holidays/static/src/views/calendar/calendar_renderer.xml create mode 100644 addons/hr_holidays/static/src/views/calendar/calendar_view.js create mode 100644 addons/hr_holidays/static/src/views/calendar/common/calendar_common_popover.js create mode 100644 addons/hr_holidays/static/src/views/calendar/common/calendar_common_renderer.js create mode 100644 addons/hr_holidays/static/src/views/calendar/filter_panel/calendar_filter_panel.js create mode 100644 addons/hr_holidays/static/src/views/calendar/filter_panel/calendar_filter_panel.xml create mode 100644 addons/hr_holidays/static/src/views/calendar/year/calendar_year_renderer.js create mode 100644 addons/hr_holidays/static/src/views/calendar/year/calendar_year_renderer.scss create mode 100644 addons/hr_holidays/static/src/views/hooks.js create mode 100644 addons/hr_holidays/static/src/views/view_dialog/form_view_dialog.js create mode 100644 addons/hr_holidays/static/src/views/view_dialog/form_view_dialog.xml delete mode 100644 addons/hr_holidays/static/src/xml/time_off_calendar.xml diff --git a/addons/hr_holidays/__manifest__.py b/addons/hr_holidays/__manifest__.py index 8de38cc3759..295fda03633 100644 --- a/addons/hr_holidays/__manifest__.py +++ b/addons/hr_holidays/__manifest__.py @@ -69,7 +69,9 @@ A synchronization with an internal agenda (Meetings of the CRM module) is also p 'hr_holidays/static/src/components/*/*', ], 'web.assets_backend': [ - 'hr_holidays/static/src/js/**/*.js', + 'hr_holidays/static/src/views/**/*.js', + 'hr_holidays/static/src/views/**/*.scss', + 'hr_holidays/static/src/views/**/*.xml', 'hr_holidays/static/src/components/*/*.scss', 'hr_holidays/static/src/components/*/*.xml', 'hr_holidays/static/src/dashboard/**/*.js', @@ -78,7 +80,6 @@ A synchronization with an internal agenda (Meetings of the CRM module) is also p 'hr_holidays/static/src/leave_stats/**/*.js', 'hr_holidays/static/src/leave_stats/**/*.xml', 'hr_holidays/static/src/scss/*.scss', - 'hr_holidays/static/src/xml/**/*.xml', 'hr_holidays/static/src/tours/*.js', 'hr_holidays/static/src/radio_image_field/*.js', 'hr_holidays/static/src/radio_image_field/*.xml', diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py index 5ae5d700c20..cef289cdfeb 100644 --- a/addons/hr_holidays/models/hr_leave.py +++ b/addons/hr_holidays/models/hr_leave.py @@ -80,7 +80,7 @@ class HolidaysRequest(models.Model): defaults = super(HolidaysRequest, self).default_get(fields_list) defaults = self._default_get_request_parameters(defaults) - if 'holiday_status_id' in fields_list and not defaults.get('holiday_status_id'): + if self.env.context.get('holiday_status_name_get', True) and 'holiday_status_id' in fields_list and not defaults.get('holiday_status_id'): lt = self.env['hr.leave.type'].search(['|', ('requires_allocation', '=', 'no'), ('has_valid_allocation', '=', True)], limit=1, order='sequence') if lt: @@ -143,7 +143,7 @@ class HolidaysRequest(models.Model): holiday_status_id = fields.Many2one( "hr.leave.type", compute='_compute_from_employee_id', store=True, string="Time Off Type", required=True, readonly=False, states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]}, - domain=['|', ('requires_allocation', '=', 'no'), ('has_valid_allocation', '=', True)]) + domain="[('company_id', '?=', employee_company_id), '|', ('requires_allocation', '=', 'no'), ('has_valid_allocation', '=', True)]") holiday_allocation_id = fields.Many2one( 'hr.leave.allocation', compute='_compute_from_holiday_status_id', string="Allocation", store=True, readonly=False) color = fields.Integer("Color", related='holiday_status_id.color') @@ -455,7 +455,8 @@ class HolidaysRequest(models.Model): for holiday in self: holiday.manager_id = holiday.employee_id.parent_id.id if holiday.employee_id.user_id != self.env.user and self._origin.employee_id != holiday.employee_id: - holiday.holiday_status_id = False + if holiday.employee_id and not holiday.holiday_status_id.with_context(employee_id=holiday.employee_id.id).has_valid_allocation: + holiday.holiday_status_id = False @api.depends('employee_id', 'holiday_type') def _compute_department_id(self): @@ -753,6 +754,10 @@ class HolidaysRequest(models.Model): # ORM Overrides methods #################################################### + @api.depends('employee_id', 'holiday_status_id') + def _compute_display_name(self): + super()._compute_display_name() + def onchange(self, values, field_name, field_onchange): # Try to force the leave_type name_get when creating new records # This is called right after pressing create and returns the name_get for @@ -809,7 +814,7 @@ class HolidaysRequest(models.Model): display_date = fields.Date.to_string(date_from_utc) or "" if leave.number_of_days > 1 and date_from_utc and date_to_utc: display_date += ' / %s' % fields.Date.to_string(date_to_utc) or "" - if self.env.context.get('hide_employee_name') and 'employee_id' in self.env.context.get('group_by', []): + if not target or self.env.context.get('hide_employee_name') and 'employee_id' in self.env.context.get('group_by', []): res.append(( leave.id, _("%(leave_type)s: %(duration).2f days (%(start)s)", diff --git a/addons/hr_holidays/report/hr_leave_report_calendar.xml b/addons/hr_holidays/report/hr_leave_report_calendar.xml index f7ebb78eb3d..b243cd308a0 100644 --- a/addons/hr_holidays/report/hr_leave_report_calendar.xml +++ b/addons/hr_holidays/report/hr_leave_report_calendar.xml @@ -4,7 +4,16 @@ hr.leave.report.calendar.view hr.leave.report.calendar - + diff --git a/addons/hr_holidays/static/src/dashboard/time_off_dashboard.js b/addons/hr_holidays/static/src/dashboard/time_off_dashboard.js index f448fb2bb1c..65bbd28ace4 100644 --- a/addons/hr_holidays/static/src/dashboard/time_off_dashboard.js +++ b/addons/hr_holidays/static/src/dashboard/time_off_dashboard.js @@ -1,11 +1,42 @@ /* @odoo-module */ import { TimeOffCard } from './time_off_card'; +import { useBus, useService } from "@web/core/utils/hooks"; -const { Component } = owl; +const { Component, useState, onWillStart } = owl; -export class TimeOffDashboard extends Component {} +export class TimeOffDashboard extends Component { + setup() { + this.orm = useService("orm"); + this.state = useState({ + holidays: [], + }); + useBus(this.env.timeOffBus, 'update_dashboard', async () => { + await this.loadDashboardData() + }); + + onWillStart(async () => { + await this.loadDashboardData(); + }); + } + + async loadDashboardData() { + const context = {}; + if (this.props.employeeId !== null) { + context['employee_id'] = this.props.employeeId; + } + + this.state.holidays = await this.orm.call( + 'hr.leave.type', + 'get_days_all_request', + [], + { + context: context + } + ); + } +} TimeOffDashboard.components = { TimeOffCard }; TimeOffDashboard.template = 'hr_holidays.TimeOffDashboard'; -TimeOffDashboard.props = ['holidays']; +TimeOffDashboard.props = ['employeeId']; diff --git a/addons/hr_holidays/static/src/dashboard/time_off_dashboard.xml b/addons/hr_holidays/static/src/dashboard/time_off_dashboard.xml index 365ccd5eae0..078565fcc8a 100644 --- a/addons/hr_holidays/static/src/dashboard/time_off_dashboard.xml +++ b/addons/hr_holidays/static/src/dashboard/time_off_dashboard.xml @@ -1,7 +1,7 @@
- +
diff --git a/addons/hr_holidays/static/src/js/form/allocation_leave_form.js b/addons/hr_holidays/static/src/js/form/allocation_leave_form.js deleted file mode 100644 index c9b588ff637..00000000000 --- a/addons/hr_holidays/static/src/js/form/allocation_leave_form.js +++ /dev/null @@ -1,43 +0,0 @@ -/** @odoo-module **/ - -import viewRegistry from 'web.view_registry'; - -import FormController from "web.FormController"; -import FormView from "web.FormView"; - -export const AllocationLeaveFormController = FormController.extend({ - /** - * @override - */ - async _applyChanges(dataPointID, changes, event) { - const result = await this._super.apply(this, arguments); - const formData = event.target.record.data; - if (formData.holiday_status_id) { - let nameSearchContext = {'holiday_status_name_get': false}; - const employee_id = formData.employee_id; - if (employee_id) { - nameSearchContext = {'holiday_status_name_get': true, 'employee_id': employee_id.data.id}; - } - const nameSearch = await this._rpc({ - model: 'hr.leave.type', - method: 'name_get', - args: [formData.holiday_status_id.data.id], - context: nameSearchContext, - limit: 1 - }); - if (this.$el.find("div[name='holiday_status_id']").find('input')[0]) { - this.$el.find("div[name='holiday_status_id']").find('input')[0].value = nameSearch[0][1]; - } - } - return result; - } - -}); - -export const AllocationLeaveFormView = FormView.extend({ - config: Object.assign({}, FormView.prototype.config, { - Controller: AllocationLeaveFormController, - }), -}); - -viewRegistry.add('allocation_leave_form', AllocationLeaveFormView); diff --git a/addons/hr_holidays/static/src/js/radio_image.js b/addons/hr_holidays/static/src/js/radio_image.js deleted file mode 100644 index de66814d851..00000000000 --- a/addons/hr_holidays/static/src/js/radio_image.js +++ /dev/null @@ -1,37 +0,0 @@ -/** @odoo-module **/ - -import { FieldRadio } from 'web.relational_fields'; -import FieldRegistry from 'web.field_registry'; - -import Core from 'web.core'; -const QWeb = Core.qweb; - -const FieldRadioImage = FieldRadio.extend({ - _render() { - const self = this; - let currentValue; - if (this.field.type === 'many2one') { - currentValue = this.value && this.value.data.id; - } else { - currentValue = this.value; - } - this.$el.empty(); - this.$el.attr('role', 'radiogroup') - .attr('aria-label', this.string); - _.each(this.values, function (value, index) { - if (self.mode === 'edit' || value[0] === currentValue) { - self.$el.append(QWeb.render('FieldRadioIcon.button', { - checked: value[0] === currentValue, - id: self.unique_id + '_' + value[0], - index: index, - name: self.unique_id, - value: value, - disabled: self.hasReadonlyModifier, - mode: self.mode, - })); - } - }); - } -}); - -FieldRegistry.add('hr_holidays_radio_image', FieldRadioImage); diff --git a/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_controller.js b/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_controller.js deleted file mode 100644 index 156e8038d85..00000000000 --- a/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_controller.js +++ /dev/null @@ -1,228 +0,0 @@ -/** @odoo-module **/ - -import { _t, qweb as QWeb } from "web.core"; -import CalendarController from "web.CalendarController"; -import dialogs from "web.view_dialogs"; -import Dialog from "web.Dialog"; - -export const TimeOffCalendarController = CalendarController.extend({ - events: Object.assign({}, CalendarController.prototype.events, { - 'click .btn-time-off': '_onNewTimeOff', - 'click .btn-allocation': '_onNewAllocation', - }), - - /** - * @override - */ - start() { - 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($node) { - this._super(...arguments); - - $(QWeb.render('hr_holidays.dashboard.calendar.button', { - time_off: _t('New Time Off'), - request: this._getAllocationButtonTitle(), - })).appendTo(this.$buttons); - - if ($node) { - this.$buttons.appendTo($node); - } else { - this.$('.o_calendar_buttons').replaceWith(this.$buttons); - } - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @override - */ - _getFormDialogOptions(self, event) { - const options = this._super(...arguments); - const eventId = parseInt(event.data._id) - // Get the time off data from the event - const timeoffData = event.target.state.data.find((timeOff) => timeOff.id === eventId); - // Take the record associated to the timeoffData and read its state to hide or not the Delete button - const timeoffState = timeoffData && timeoffData.record.state; - const canCancel = timeoffData && timeoffData.record.can_cancel; - if(canCancel || timeoffState && !['validate', 'refuse'].includes(timeoffState)) { - options.deletable = true; - options.removeButtonText = _t("Delete"); - options.on_remove = () => this._deleteLeave(timeoffData.record); - } - return options; - }, - - _onDeleteRecord(ev) { - this._deleteLeave(ev.data.event.record); - }, - - _deleteLeave(timeoff) { - const canCancel = timeoff.can_cancel; - if (canCancel) { - const context = Object.assign({}, this.context, { - default_leave_id: timeoff.id, - }); - const cancel_dialog = new dialogs.FormViewDialog(this, { - title: _t('Delete Confirmation'), - res_model: 'hr.holidays.cancel.leave', - context: context, - }); - cancel_dialog.on('execute_action', this, (ev) => { - const action_name = ev.data.action_data.name || ev.data.action_data.special; - const event_data = _.clone(ev.data); - if (action_name === 'action_cancel_leave') { - ev.stopPropagation(); - this.trigger_up('execute_action', event_data); - setTimeout(() => this.reload(), 300); - cancel_dialog.close(); - } - }); - cancel_dialog.open(); - } else { - Dialog.confirm(this, _t("Are you sure you want to delete this record ?"), { - confirm_callback: () => { - this.model.deleteRecords(timeoff.id, this.modelName).then(() => { - this.reload(); - }); - } - }); - } - }, - - _update() { - return this._super(...arguments).then(() => { - const request_args = [ - this.res_id, - this.model.data.start_date, - this.model.data.end_date, - ] - if (this.context.employee_id) - request_args[0] = this.context.employee_id; - this._rpc({ - model: 'hr.employee', - method: 'get_stress_days', - args: request_args, - context: this.context, - }).then((stressDays) => { - this.$el.find('td.fc-day').toArray().forEach((td) => { - if (stressDays[td.dataset.date]) { - td.classList.add('hr_stress_day_' + stressDays[td.dataset.date]); - } - }); - }); - }); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Action: create a new time off request - * - * @private - */ - async _onNewTimeOff() { - this.timeOffDialog = new dialogs.FormViewDialog(this, { - res_model: "hr.leave", - context: this._getTimeOffContext(), - title: _t("New time off"), - disable_multiple_selection: true, - on_saved: () => this.reload() - }); - this.timeOffDialog.open(); - }, - - /** - * Action: create a new allocation request - * - * @private - */ - async _onNewAllocation() { - this.allocationDialog = new dialogs.FormViewDialog(this, { - res_model: "hr.leave.allocation", - context: this._getAllocationContext(), - title: _t("New Allocation"), - disable_multiple_selection: true, - on_saved: () => this.reload() - }); - this.allocationDialog.open(); - }, - - _onOpenCreate() { - this._super(...arguments); - if(this.previousOpen) { - this.previousOpen.on('closed', this, (ev) => { - // we reload as record can be created or modified (sent, unpublished, ...) - this.reload(); - }); - } - }, - - /** - * @override - */ - _setEventTitle() { - return _t('Time Off Request'); - }, - - _getAllocationButtonTitle() { - return _t('Allocation Request'); - }, - - _getFormViewId() { - return 'hr_holidays.hr_leave_allocation_view_form_dashboard'; - }, - - _getTimeOffContext() { - let date_from = moment(); - if (this.mode === "day") { - date_from = this.model.data.start_date; - } - let date_to = moment(date_from); - date_from = date_from.set({ - 'hour': 0, - 'minute': 0, - 'second': 0 - }); - date_from.subtract(this.getSession().getTZOffset(date_from), 'minutes'); - date_from = date_from.locale('en').format('YYYY-MM-DD HH:mm:ss'); - date_to = date_to.set({ - 'hour': 23, - 'minute': 59, - 'second': 59 - }); - date_to.subtract(this.getSession().getTZOffset(date_to), 'minutes'); - date_to = date_to.locale('en').format('YYYY-MM-DD HH:mm:ss'); - return { - 'default_date_from': date_from, - 'default_date_to': date_to, - 'lang': this.context.lang, - 'form_view_ref': 'hr_holidays.hr_leave_view_form_dashboard_new_time_off' - }; - }, - - _getAllocationContext() { - return { - 'default_state': 'confirm', - 'lang': this.context.lang, - 'form_view_ref': this._getFormViewId() - }; - }, - -}); diff --git a/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_employee_controller.js b/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_employee_controller.js deleted file mode 100644 index 1641d9c6b37..00000000000 --- a/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_employee_controller.js +++ /dev/null @@ -1,33 +0,0 @@ -/** @odoo-module **/ - -import { _t } from "web.core"; -import { TimeOffCalendarController } from "./time_off_calendar_controller"; - -export const TimeOffCalendarEmployeeController = TimeOffCalendarController.extend({ - - _onOpenCreate() { - this.context['default_employee_id'] = this.context.employee_id[0]; - this._super(...arguments); - }, - - _getAllocationButtonTitle() { - return _t('New Allocation Request'); - }, - - _getFormViewId() { - return 'hr_holidays.hr_leave_allocation_view_form_manager_dashboard'; - }, - - _getTimeOffContext() { - const context = this._super(...arguments); - context.default_employee_id = this.context.employee_id[0]; - return context; - }, - - _getAllocationContext() { - const context = this._super(...arguments); - context.default_employee_id = this.context.employee_id[0]; - return context; - }, - -}); diff --git a/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_employee_views.js b/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_employee_views.js deleted file mode 100644 index 6695c08c79e..00000000000 --- a/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_employee_views.js +++ /dev/null @@ -1,15 +0,0 @@ -/** @odoo-module **/ - -import { TimeOffCalendarEmployeeController } from "./time_off_calendar_employee_controller"; -import { TimeOffCalendarModel } from "./time_off_calendar_model"; -import { TimeOffCalendarView } from "./time_off_calendar_views"; -import viewRegistry from 'web.view_registry'; - -export const TimeOffCalendarEmployeeView = TimeOffCalendarView.extend({ - config: Object.assign({}, TimeOffCalendarView.prototype.config, { - Controller: TimeOffCalendarEmployeeController, - Model: TimeOffCalendarModel, - }), -}); - -viewRegistry.add('time_off_employee_calendar', TimeOffCalendarEmployeeView); diff --git a/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_model.js b/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_model.js deleted file mode 100644 index de9d9c1fa98..00000000000 --- a/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_model.js +++ /dev/null @@ -1,29 +0,0 @@ -/** @odoo-module */ - -import CalendarModel from "web.CalendarModel"; - -export const TimeOffCalendarModel = CalendarModel.extend({ - _getFilterDomain: function() { - const company_domain = [['user_id.company_id', 'in', this.data.context.allowed_company_ids]]; - return this._super().concat(company_domain); - }, - - calendarEventToRecord(event) { - const data = this._super(...arguments); - if (event.allDay) { - let date_from = data.date_from.subtract(7, 'hours'); - date_from.add(this.getSession().getTZOffset(date_from), 'minutes'); - date_from = date_from.locale('en').format('YYYY-MM-DD HH:mm:ss'); - let date_to = data.date_to.add({ - 'hour': 4, - 'minute': 59, - 'second': 59 - }); - date_to.add(this.getSession().getTZOffset(date_to), 'minutes'); - date_to = date_to.locale('en').format('YYYY-MM-DD HH:mm:ss'); - data.date_from = date_from; - data.date_to = date_to; - } - return data; - }, -}); diff --git a/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_popover.js b/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_popover.js deleted file mode 100644 index 1186065832d..00000000000 --- a/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_popover.js +++ /dev/null @@ -1,23 +0,0 @@ -/** @odoo-module **/ - -import { _t } from 'web.core'; -import CalendarPopover from 'web.CalendarPopover'; - -export const TimeOffCalendarPopover = CalendarPopover.extend({ - template: 'hr_holidays.calendar.popover', - - init(parent, eventInfo) { - this._super(...arguments); - const state = this.event.extendedProps.record.state; - this.canDelete = this.event.extendedProps.record.can_cancel || state && !['validate', 'refuse'].includes(state); - 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; - } - }, -}); diff --git a/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_renderer.js b/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_renderer.js deleted file mode 100644 index 2df26a788f6..00000000000 --- a/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_renderer.js +++ /dev/null @@ -1,68 +0,0 @@ -/** @odoo-module **/ - -import { ComponentWrapper } from 'web.OwlCompatibility'; -import { TimeOffPopoverRenderer } from "./time_off_popover_renderer"; - -import { TimeOffDashboard } from '@hr_holidays/dashboard/time_off_dashboard'; -import { TimeOffCardMobile } from '@hr_holidays/dashboard/time_off_card'; - -import config from 'web.config'; - -export const TimeOffCalendarRenderer = TimeOffPopoverRenderer.extend({ - async willStart() { - await this._super(...arguments); - - this.holidays = await this.loadDashboardData(); - }, - - async start() { - await this._super(...arguments); - - this.TimeOffDashboardWrapper = null; - - if (!config.device.isMobile) { - this.TimeOffDashboardWrapper = new ComponentWrapper(this, TimeOffDashboard, { holidays: this.holidays }); - await this.TimeOffDashboardWrapper.mount(this.el.parentElement, { position: 'first-child' }); - } - }, - - async _render() { - await this._super(...arguments); - - if (this.TimeOffDashboardWrapper) { - this.holidays = await this.loadDashboardData(); - this.TimeOffDashboardWrapper.update({ holidays: this.holidays }); - } - }, - - async loadDashboardData() { - return await this._rpc({ - model: 'hr.leave.type', - method: 'get_days_all_request', - context: this.state.context, - }); - }, - - async _renderFilters() { - await this._super(...arguments); - - if (!config.device.isMobile) { - return; - } - - this.holidays = await this.loadDashboardData(); - for (const holiday of this.holidays) { - const node = this.el.querySelector(`.o_calendar_filter_item[data-value="${holiday[3]}"] .o_cw_filter_title`); - - if (node) { - const mobileCard = new ComponentWrapper(this, TimeOffCardMobile, { - name: holiday[0], - data: holiday[1], - requires_allocation: holiday[2] === 'yes', - id: holiday[3] - }); - await mobileCard.mount(node); - } - } - } -}); diff --git a/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_views.js b/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_views.js deleted file mode 100644 index 2777e808e77..00000000000 --- a/addons/hr_holidays/static/src/js/time_off_calendar/time_off_calendar_views.js +++ /dev/null @@ -1,40 +0,0 @@ -/** @odoo-module **/ - -import CalendarView from "web.CalendarView"; -import config from 'web.config'; -import { TimeOffCalendarController } from "./time_off_calendar_controller"; -import { TimeOffCalendarModel } from "./time_off_calendar_model"; -import { TimeOffCalendarRenderer } from "./time_off_calendar_renderer"; -import { TimeOffPopoverRenderer } from "./time_off_popover_renderer"; -import viewRegistry from 'web.view_registry'; - -export const TimeOffCalendarView = CalendarView.extend({ - config: Object.assign({}, CalendarView.prototype.config, { - Controller: TimeOffCalendarController, - Model: TimeOffCalendarModel, - Renderer: TimeOffCalendarRenderer, - }), - - /** - * @override - */ - init: function (viewInfo, params) { - this._super(viewInfo, params); - if(config.device.isMobile) { - this.loadParams.mode = "month"; - } - } -}); - -/** - * Calendar shown in the "Everyone" menu - */ -export const TimeOffCalendarAllView = CalendarView.extend({ - config: Object.assign({}, CalendarView.prototype.config, { - Controller: TimeOffCalendarController, - Renderer: TimeOffPopoverRenderer, - }), -}); - -viewRegistry.add('time_off_calendar', TimeOffCalendarView); -viewRegistry.add('time_off_calendar_all', TimeOffCalendarAllView); diff --git a/addons/hr_holidays/static/src/js/time_off_calendar/time_off_popover_renderer.js b/addons/hr_holidays/static/src/js/time_off_calendar/time_off_popover_renderer.js deleted file mode 100644 index 41e172ed903..00000000000 --- a/addons/hr_holidays/static/src/js/time_off_calendar/time_off_popover_renderer.js +++ /dev/null @@ -1,89 +0,0 @@ -/** @odoo-module **/ - -import { qweb as QWeb }from 'web.core'; -import CalendarRenderer from "web.CalendarRenderer"; -import { TimeOffCalendarPopover } from "./time_off_calendar_popover"; - -import { WidgetAdapterMixin } from 'web.OwlCompatibility'; - -export const TimeOffPopoverRenderer = CalendarRenderer.extend(WidgetAdapterMixin, { - template: "TimeOff.CalendarView.extend", - /** - * We're overriding this to display the weeknumbers on the year view - * - * @override - * @private - */ - _getFullCalendarOptions() { - const oldOptions = this._super(...arguments); - // Parameters - oldOptions.views.dayGridYear.weekNumbers = true; - oldOptions.views.dayGridYear.weekNumbersWithinDays = false; - return oldOptions; - }, - - config: Object.assign({}, CalendarRenderer.prototype.config, { - CalendarPopover: TimeOffCalendarPopover, - }), - - _getPopoverParams(eventData) { - const params = this._super(...arguments); - let calendarIcon; - const 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() { - return this._super.apply(this, arguments).then(() => { - this.$el.parent().find('.o_calendar_mini').hide(); - }); - }, - /** - * @override - * @private - */ - _renderCalendar() { - this._super(...arguments); - const weekNumbers = this.$el.find('.fc-week-number'); - weekNumbers.each( function() { - const 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 = ''; - } - }); - }, - - /* - * OWL Compatibility layer - */ - on_attach_callback() { - this._super(...arguments); - WidgetAdapterMixin.on_attach_callback.call(this); - }, - - on_detach_callback() { - this._super(...arguments); - WidgetAdapterMixin.on_detach_callback.call(this); - }, - - destroy() { - this._super(...arguments); - WidgetAdapterMixin.destroy.call(this); - }, -}); diff --git a/addons/hr_holidays/static/src/leave_stats/leave_stats.js b/addons/hr_holidays/static/src/leave_stats/leave_stats.js index 861db94cdd3..54a4c684db3 100644 --- a/addons/hr_holidays/static/src/leave_stats/leave_stats.js +++ b/addons/hr_holidays/static/src/leave_stats/leave_stats.js @@ -1,58 +1,52 @@ /* @odoo-module */ import { useService } from '@web/core/utils/hooks'; -import { getLangDateFormat } from 'web.time'; -import widgetRegistry from 'web.widgetRegistry'; +import { registry } from '@web/core/registry' + +import { formatDate, parseDateTime } from "@web/core/l10n/dates"; const { Component, useState, onWillStart, onWillUpdateProps } = owl; +const { DateTime } = luxon; export class LeaveStatsComponent extends Component { setup() { - this.rpc = useService('rpc'); + this.orm = useService('orm'); this.state = useState({ leaves: [], departmentLeaves: [], }); + this.date = this.props.record.data.date_from || DateTime.now(); + this.department = this.props.record.data.department_id; + this.employee = this.props.record.data.employee_id; + onWillStart(async () => { await this.loadLeaves(this.date, this.employee); await this.loadDepartmentLeaves(this.date, this.department, this.employee); }); onWillUpdateProps(async (nextProps) => { - const dateFrom = nextProps.record.data.date_from || moment(); + const dateFrom = nextProps.record.data.date_from || DateTime.now(); const dateChanged = this.date !== dateFrom; - const employee = nextProps.record.data.employee_id.data; - const department = nextProps.record.data.department_id.data; + const employee = nextProps.record.data.employee_id; + const department = nextProps.record.data.department_id; - if (dateChanged || employee && (this.employee && this.employee.id) !== employee.id) { + if (dateChanged || employee && (this.employee && this.employee[0]) !== employee[0]) { await this.loadLeaves(dateFrom, employee); } - if (dateChanged || department && (this.department && this.department.id) !== department.id) { + if (dateChanged || department && (this.department && this.department[0]) !== department[0]) { await this.loadDepartmentLeaves(dateFrom, department, employee); } + + this.date = dateFrom; + this.employee = employee; + this.department = department; }) } - get date() { - return this.props.record.data.date_from || moment(); - } - - get department() { - return this.props.record.data.department_id && this.props.record.data.department_id.data; - } - - get employee() { - return this.props.record.data.employee_id.data; - } - get thisYear() { - return this.date.format('YYYY'); - } - - get thisMonth() { - return this.date.format('MMMM'); + return this.date.toFormat('yyyy'); } async loadDepartmentLeaves(date, department, employee) { @@ -61,30 +55,26 @@ export class LeaveStatsComponent extends Component { return; } - const dateFrom = date.clone().startOf('month'); - const dateTo = date.clone().endOf('month'); - const dateFormat = getLangDateFormat(); + const dateFrom = date.startOf('month'); + const dateTo = date.endOf('month'); - const departmentLeaves = await this.rpc({ - model: 'hr.leave', - method: 'search_read', - args: [ - [ - ['department_id', '=', department.id], - ['state', '=', 'validate'], - ['holiday_type', '=', 'employee'], - ['date_from', '<=', dateTo], - ['date_to', '>=', dateFrom], - ], - ['employee_id', 'date_from', 'date_to', 'number_of_days'], - ] - }); + const departmentLeaves = await this.orm.searchRead( + 'hr.leave', + [ + ['department_id', '=', department[0]], + ['state', '=', 'validate'], + ['holiday_type', '=', 'employee'], + ['date_from', '<=', dateTo], + ['date_to', '>=', dateFrom], + ], + ['employee_id', 'date_from', 'date_to', 'number_of_days'], + ); this.state.departmentLeaves = departmentLeaves.map((leave) => { return Object.assign({}, leave, { - dateFrom: moment(leave.date_from).format(dateFormat), - dateTo: moment(leave.date_to).format(dateFormat), - sameEmployee: leave.employee_id[0] === employee.id, + dateFrom: formatDate(parseDateTime(leave.date_from)), + dateTo: formatDate(parseDateTime(leave.date_to)), + sameEmployee: leave.employee_id[0] === employee[0], }); }); } @@ -95,24 +85,21 @@ export class LeaveStatsComponent extends Component { return; } - const dateFrom = date.clone().startOf('year'); - const dateTo = date.clone().endOf('year'); - this.state.leaves = await this.rpc({ - model: 'hr.leave', - method: 'read_group', - kwargs: { - domain: [ - ['employee_id', '=', employee.id], - ['state', '=', 'validate'], - ['date_from', '<=', dateTo], - ['date_to', '>=', dateFrom] - ], - fields: ['holiday_status_id', 'number_of_days:sum'], - groupby: ['holiday_status_id'], - } - }); + const dateFrom = date.startOf('year'); + const dateTo = date.endOf('year'); + this.state.leaves = await this.orm.readGroup( + 'hr.leave', + [ + ['employee_id', '=', employee[0]], + ['state', '=', 'validate'], + ['date_from', '<=', dateTo], + ['date_to', '>=', dateFrom] + ], + ['holiday_status_id', 'number_of_days:sum'], + ['holiday_status_id'], + ); } } LeaveStatsComponent.template = 'hr_holidays.LeaveStatsComponent'; -widgetRegistry.add('hr_leave_stats', LeaveStatsComponent); +registry.category('view_widgets').add('hr_leave_stats', LeaveStatsComponent); diff --git a/addons/hr_holidays/static/src/leave_stats/leave_stats.xml b/addons/hr_holidays/static/src/leave_stats/leave_stats.xml index c1cef412ac6..726c3cf2a4e 100644 --- a/addons/hr_holidays/static/src/leave_stats/leave_stats.xml +++ b/addons/hr_holidays/static/src/leave_stats/leave_stats.xml @@ -3,7 +3,7 @@
- in + in
None @@ -16,7 +16,7 @@
- +
None diff --git a/addons/hr_holidays/static/src/scss/time_off.scss b/addons/hr_holidays/static/src/scss/time_off.scss index ccad3e23d9d..7b656207d5e 100644 --- a/addons/hr_holidays/static/src/scss/time_off.scss +++ b/addons/hr_holidays/static/src/scss/time_off.scss @@ -1,110 +1,3 @@ -.o_timeoff_calendar .o_content { - .fc-dayGridYear-view { - .fc-bgevent { - border-radius: 25px; - } - > .fc-month-container { - > .fc-month { - .fc-dayGridMonth-view { - .fc-has-event { - background-color: unset; - } - } - .fc-content-skeleton { - padding: 0; - - tbody { - display: none; - } - } - } - } - } - - .o_calendar_filter { - > h5 { - font-weight: bold; - } - } - .fc-week-number { - color: #adb5bd; - } - - .o_legacy_calendar_view .o_calendar_widget .fc-dayGridMonth-view { - .fc-week-number { - padding-top: 0.2rem !important; - padding-bottom: 0.3rem !important; - padding-left: 0.3rem !important; - line-height: unset; - span { - font-size: 0.85rem; - } - } - .fc-week-number.fc-widget-header { - font-size: 0.85rem; - } - } - - @for $size from 1 through length($o-colors) { - .hr_stress_day_#{$size - 1} { - background-color: lighten(nth($o-colors, $size), 25%) !important; - } - } -} - -.o_timeoff_calendar .o_cp_bottom { - @include media-breakpoint-down(md) { - position: relative; - flex-direction: column; - margin-left: -16px; - .o_cp_bottom_right { - position: absolute; - column-gap: 0; - top: 0; - right: 0; - } - .o_cp_bottom_left { - flex: 1; - width: auto; - .o_cp_buttons { - max-width: 100%; - } - .o_calendar_scale_buttons { - display: block; - max-width: 100vw; - } - } - } - @include media-breakpoint-down(sm) { - .o_cp_bottom_left { - button { - padding: 7px 6px; - } - } - } - .o_cp_bottom_left { - flex: 1; - width: auto; - } - .o_cp_bottom_right { - flex: none; - width: auto; - } -} - -.o_timeoff_dashboard_popover { - hr { - margin-top: 0px; - margin-bottom: 0px; - } - - ul { - list-style: none; - padding-left: 0; - margin: 0px; - } -} - .o_hr_leave_form { .o_form_sheet { padding: 0!important; diff --git a/addons/hr_holidays/static/src/tours/hr_holidays_tour.js b/addons/hr_holidays/static/src/tours/hr_holidays_tour.js index 540845250fd..d36180b8f87 100644 --- a/addons/hr_holidays/static/src/tours/hr_holidays_tour.js +++ b/addons/hr_holidays/static/src/tours/hr_holidays_tour.js @@ -1,7 +1,6 @@ /** @odoo-module **/ import tour from 'web_tour.tour'; import { _t } from 'web.core'; -import session from 'web.session'; const leaveType = "NotLimitedHR"; const leaveDateFrom = "01/17/2022"; @@ -25,39 +24,30 @@ tour.register('hr_holidays_tour', { position: 'bottom', }, { - trigger: '.o_field_widget[name="holiday_status_id"]', + trigger: 'div[name="holiday_status_id"] input', content: _t("Let's try to create a Sick Time Off, select it in the list"), - position: 'right', - run: function(actions) { - actions.text(leaveType, this.$anchor.find('input')); - } + run: `text ${leaveType}`, }, { - trigger: ".ui-menu-item > a", + trigger: `.ui-autocomplete .ui-menu-item a:contains("${leaveType}")`, + run: "click", auto: true, - in_modal: false + in_modal: false, }, { - trigger: 'input[name="request_date_from"]', + trigger: '.o_field_widget[name="request_date_from"] input', content: _t("You can select the period you need to take off, from start date to end date"), position: 'right', - run: function(actions) { - this.$anchor.val(leaveDateFrom); - this.$anchor.trigger("change"); - } + run: `text ${leaveDateFrom}`, }, { - trigger: 'input[name="request_date_to"]', + trigger: '.o_field_widget[name="request_date_to"] input', content: _t("You can select the period you need to take off, from start date to end date"), position: 'right', - run: function(actions) { - this.$anchor.val(leaveDateTo); - this.$anchor.trigger("change"); - }, - auto: true + run: `text ${leaveDateTo}`, }, { - trigger: 'textarea[name="name"]', + trigger: 'div[name="name"] textarea', content: _t("Add some description for the people that will validate it"), run: `text ${description}`, position: 'right' @@ -83,15 +73,7 @@ tour.register('hr_holidays_tour', { position: 'bottom', run: function(actions) { const rows = this.$anchor.find('tr.o_data_row'); - const createdRow = rows.filter((i, row) => { - const el = $(row); - return el.find(`div[name="all_employee_ids"]:contains("${session.name}")`).length && - el.find(`td[name="name"]:contains("${description}")`).length && - el.find(`td[name="holiday_status_id"]:contains("${leaveType}")`).length && - el.find(`td[name="date_from"]:contains("${leaveDateFrom}")`).length && - el.find(`td[name="date_to"]:contains("${leaveDateTo}")`).length; - }); - actions.click(createdRow); + actions.click(rows[0]); } }, { diff --git a/addons/hr_holidays/static/src/views/calendar/calendar_controller.js b/addons/hr_holidays/static/src/views/calendar/calendar_controller.js new file mode 100644 index 00000000000..7d765c488d3 --- /dev/null +++ b/addons/hr_holidays/static/src/views/calendar/calendar_controller.js @@ -0,0 +1,105 @@ +/** @odoo-module */ + +import { CalendarController } from '@web/views/calendar/calendar_controller'; +import { FormViewDialog } from '@web/views/view_dialogs/form_view_dialog'; + +import { serializeDate } from "@web/core/l10n/dates"; + +import { TimeOffCalendarFilterPanel } from './filter_panel/calendar_filter_panel'; +import { TimeOffFormViewDialog } from '../view_dialog/form_view_dialog'; +import { useLeaveCancelWizard } from '../hooks'; + +const { EventBus, useSubEnv } = owl; + +export class TimeOffCalendarController extends CalendarController { + setup() { + super.setup(); + useSubEnv({ + timeOffBus: new EventBus(), + }); + this.leaveCancelWizard = useLeaveCancelWizard(); + } + + get employeeId() { + return this.model.employeeId; + } + + newTimeOffRequest() { + const context = {}; + if (this.employeeId) { + context['default_employee_id'] = this.employeeId; + } + if (this.model.meta.scale == 'day') { + context['default_date_from'] = serializeDate( + this.model.data.range.start.set({ hours: 7 }), "datetime" + ); + context['default_date_to'] = serializeDate( + this.model.data.range.end.set({ hours: 19 }), "datetime" + ); + } + + this.displayDialog(FormViewDialog, { + resModel: 'hr.leave', + title: this.env._t('New Time Off'), + viewId: this.model.formViewId, + onRecordSaved: () => { + this.model.load(); + this.env.timeOffBus.trigger('update_dashboard'); + }, + context: context, + }); + } + + newAllocationRequest() { + const context = { + 'default_state': 'confirm', + 'form_view_ref': 'hr_holidays.hr_leave_allocation_view_form_dashboard', + }; + if (this.employeeId) { + context['default_employee_id'] = this.employeeId; + context['form_view_ref'] = 'hr_holidays.hr_leave_allocation_view_form_manager_dashboard'; + } + + this.displayDialog(FormViewDialog, { + resModel: 'hr.leave.allocation', + title: this.env._t('New Allocation'), + context: context, + }); + } + + deleteRecord(record) { + if (!record.rawRecord.can_cancel) { + return super.deleteRecord(record); + } + + this.leaveCancelWizard(record.id, () => { + this.model.load(); + this.env.timeOffBus.trigger('update_dashboard'); + }); + } + + async editRecord(record, context = {}, shouldFetchFormViewId = true) { + return new Promise((resolve) => { + this.displayDialog( + TimeOffFormViewDialog, { + resModel: this.model.resModel, + resId: record.id || false, + context, + title: record.title, + viewId: this.model.formViewId, + onRecordSaved: () => this.model.load(), + onLeaveCancelled: () => { + this.model.load(); + this.env.timeOffBus.trigger('update_dashboard'); + } + }, + { onClose: () => resolve() } + ); + }); + } +} +TimeOffCalendarController.template = "hr_holidays.CalendarController"; +TimeOffCalendarController.components = { + ...TimeOffCalendarController.components, + FilterPanel: TimeOffCalendarFilterPanel, +} diff --git a/addons/hr_holidays/static/src/views/calendar/calendar_controller.xml b/addons/hr_holidays/static/src/views/calendar/calendar_controller.xml new file mode 100644 index 00000000000..18af09af5bf --- /dev/null +++ b/addons/hr_holidays/static/src/views/calendar/calendar_controller.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + diff --git a/addons/hr_holidays/static/src/views/calendar/calendar_model.js b/addons/hr_holidays/static/src/views/calendar/calendar_model.js new file mode 100644 index 00000000000..6ab6150e79a --- /dev/null +++ b/addons/hr_holidays/static/src/views/calendar/calendar_model.js @@ -0,0 +1,53 @@ +/** @odoo-module */ + +import { CalendarModel } from '@web/views/calendar/calendar_model'; +import { serializeDate } from "@web/core/l10n/dates"; + +export class TimeOffCalendarModel extends CalendarModel { + setup(params, services) { + super.setup(params, services); + + this.data.stressDays = {}; + if (this.env.isSmall) { + this.meta.scale = 'month'; + } + } + + makeContextDefaults(record) { + const context = super.makeContextDefaults(record); + if (this.employeeId) { + context['default_employee_id'] = this.employeeId; + } + return context; + } + + async updateData(data) { + await super.updateData(data); + data.stressDays = await this.fetchStressDays(data); + } + + async fetchStressDays(data) { + return this.orm.call("hr.employee", "get_stress_days", [ + this.employeeId, + serializeDate(data.range.start, "datetime"), + serializeDate(data.range.end, "datetime"), + ]); + } + + get stressDays() { + return this.data.stressDays; + } + + get employeeId() { + return this.meta.context.employee_id && this.meta.context.employee_id[0] || null; + } + + fetchRecords(data) { + const { fieldNames, resModel } = this.meta; + const context = {}; + if (!this.employeeId) { + context['short_name'] = 1; + } + return this.orm.searchRead(resModel, this.computeDomain(data), fieldNames, { context }); + } +} diff --git a/addons/hr_holidays/static/src/views/calendar/calendar_renderer.js b/addons/hr_holidays/static/src/views/calendar/calendar_renderer.js new file mode 100644 index 00000000000..7dd41dad394 --- /dev/null +++ b/addons/hr_holidays/static/src/views/calendar/calendar_renderer.js @@ -0,0 +1,33 @@ +/** @odoo-module */ + +import { CalendarRenderer } from '@web/views/calendar/calendar_renderer'; + +import { TimeOffCalendarCommonRenderer } from './common/calendar_common_renderer'; +import { TimeOffCalendarYearRenderer } from './year/calendar_year_renderer'; + +import { TimeOffDashboard } from '../../dashboard/time_off_dashboard'; + +export class TimeOffCalendarRenderer extends CalendarRenderer { + get employeeId() { + return this.props.model.employeeId; + } + + get showDashboard() { + return false; + } +} +TimeOffCalendarRenderer.template = 'hr_holidays.CalendarRenderer'; +TimeOffCalendarRenderer.components = { + ...TimeOffCalendarRenderer.components, + day: TimeOffCalendarCommonRenderer, + week: TimeOffCalendarCommonRenderer, + month: TimeOffCalendarCommonRenderer, + year: TimeOffCalendarYearRenderer, + TimeOffDashboard, +}; + +export class TimeOffDashboardCalendarRenderer extends TimeOffCalendarRenderer { + get showDashboard() { + return !this.env.isSmall; + } +} diff --git a/addons/hr_holidays/static/src/views/calendar/calendar_renderer.scss b/addons/hr_holidays/static/src/views/calendar/calendar_renderer.scss new file mode 100644 index 00000000000..b539bff56ec --- /dev/null +++ b/addons/hr_holidays/static/src/views/calendar/calendar_renderer.scss @@ -0,0 +1,43 @@ +.o_timeoff_calendar { + .o_calendar_renderer { + height: unset; + + .o_calendar_widget { + @for $size from 1 through length($o-colors) { + .hr_stress_day_#{$size - 1}:not(.fc-disabled-day) { + background-color: lighten(nth($o-colors, $size), 25%) !important; + } + } + } + + .fc-bgevent { + border-radius: 25px; + } + + @for $i from 1 through length($o-filter_colors) { + $color: nth($o-filter_colors, $i); + + .o_calendar_color_#{$i - 1} { + &.o_event_hatched { + background: repeating-linear-gradient( + 45deg, + $color, + $color 10px, + rgba($color, 0.4) 10px, + rgba($color, 0.4) 20px + ) !important; + + .fc-bg { + background: repeating-linear-gradient( + 45deg, + $color, + $color 10px, + rgba($color, 0.5) 10px, + rgba($color, 0.5) 20px + ); + } + } + } + } + } +} diff --git a/addons/hr_holidays/static/src/views/calendar/calendar_renderer.xml b/addons/hr_holidays/static/src/views/calendar/calendar_renderer.xml new file mode 100644 index 00000000000..45fc4acdb5c --- /dev/null +++ b/addons/hr_holidays/static/src/views/calendar/calendar_renderer.xml @@ -0,0 +1,11 @@ + + + + +
+ + +
+
+ +
diff --git a/addons/hr_holidays/static/src/views/calendar/calendar_view.js b/addons/hr_holidays/static/src/views/calendar/calendar_view.js new file mode 100644 index 00000000000..6439c10280a --- /dev/null +++ b/addons/hr_holidays/static/src/views/calendar/calendar_view.js @@ -0,0 +1,25 @@ +/** @odoo-module */ + +import { calendarView } from '@web/views/calendar/calendar_view'; + +import { TimeOffCalendarController } from './calendar_controller'; +import { TimeOffCalendarModel } from './calendar_model'; +import { TimeOffCalendarRenderer, TimeOffDashboardCalendarRenderer } from './calendar_renderer'; + +import { registry } from '@web/core/registry'; + +const TimeOffCalendarView = { + ...calendarView, + + Controller: TimeOffCalendarController, + Renderer: TimeOffCalendarRenderer, + Model: TimeOffCalendarModel, + + buttonTemplate: "hr_holidays.CalendarController.controlButtons", +} + +registry.category('views').add('time_off_calendar', TimeOffCalendarView); +registry.category('views').add('time_off_calendar_dashboard', { + ...TimeOffCalendarView, + Renderer: TimeOffDashboardCalendarRenderer, +}); diff --git a/addons/hr_holidays/static/src/views/calendar/common/calendar_common_popover.js b/addons/hr_holidays/static/src/views/calendar/common/calendar_common_popover.js new file mode 100644 index 00000000000..4df3c1954d2 --- /dev/null +++ b/addons/hr_holidays/static/src/views/calendar/common/calendar_common_popover.js @@ -0,0 +1,25 @@ +/** @odoo-module */ + +import { CalendarCommonPopover } from "@web/views/calendar/calendar_common/calendar_common_popover"; + +import { useService } from "@web/core/utils/hooks"; + +export class TimeOffCalendarCommonPopover extends CalendarCommonPopover { + setup() { + super.setup(); + + this.dialog = useService('dialog'); + this.action = useService('action'); + } + + get isEventDeletable() { + const record = this.props.record.rawRecord; + const state = record.state; + return record.can_cancel || state && !['validate', 'refuse'].includes(state); + } + + get isEventEditable() { + const state = this.props.record.rawRecord.state; + return state !== undefined; + } +} diff --git a/addons/hr_holidays/static/src/views/calendar/common/calendar_common_renderer.js b/addons/hr_holidays/static/src/views/calendar/common/calendar_common_renderer.js new file mode 100644 index 00000000000..6a51f4fbc72 --- /dev/null +++ b/addons/hr_holidays/static/src/views/calendar/common/calendar_common_renderer.js @@ -0,0 +1,23 @@ +/** @odoo-module */ + +import { CalendarCommonRenderer } from '@web/views/calendar/calendar_common/calendar_common_renderer'; + +import { useStressDays } from '../../hooks'; +import { TimeOffCalendarCommonPopover } from './calendar_common_popover'; + + +export class TimeOffCalendarCommonRenderer extends CalendarCommonRenderer { + setup() { + super.setup(); + this.stressDays = useStressDays(this.props); + } + + onDayRender(info) { + super.onDayRender(info); + this.stressDays(info); + } +} +TimeOffCalendarCommonRenderer.components = { + ...TimeOffCalendarCommonRenderer, + Popover: TimeOffCalendarCommonPopover, +} diff --git a/addons/hr_holidays/static/src/views/calendar/filter_panel/calendar_filter_panel.js b/addons/hr_holidays/static/src/views/calendar/filter_panel/calendar_filter_panel.js new file mode 100644 index 00000000000..60a9f242b36 --- /dev/null +++ b/addons/hr_holidays/static/src/views/calendar/filter_panel/calendar_filter_panel.js @@ -0,0 +1,47 @@ +/** @odoo-module */ + +import { CalendarFilterPanel } from "@web/views/calendar/filter_panel/calendar_filter_panel"; +import { TimeOffCardMobile } from "../../../dashboard/time_off_card"; + +import { useService } from "@web/core/utils/hooks"; + +const { useState, onWillStart } = owl; + +export class TimeOffCalendarFilterPanel extends CalendarFilterPanel { + setup() { + super.setup(); + + this.orm = useService('orm'); + this.leaveState = useState({ + holidays: [], + }); + + onWillStart(async () => { + await this.loadFilterData(); + }); + } + + async loadFilterData() { + if(!this.env.isSmall) { + return; + } + + const filterData = {}; + const data = await this.orm.call( + 'hr.leave.type', 'get_days_all_request', [], + ); + + data.forEach((leave) => { + filterData[leave[3]] = leave; + }); + this.leaveState.holidays = filterData; + } +} +TimeOffCalendarFilterPanel.template = 'hr_holidays.CalendarFilterPanel'; +TimeOffCalendarFilterPanel.components = { + ...TimeOffCalendarFilterPanel.components, + TimeOffCardMobile, +} +TimeOffCalendarFilterPanel.subTemplates = { + filter: "hr_holidays.CalendarFilterPanel.filter", +} diff --git a/addons/hr_holidays/static/src/views/calendar/filter_panel/calendar_filter_panel.xml b/addons/hr_holidays/static/src/views/calendar/filter_panel/calendar_filter_panel.xml new file mode 100644 index 00000000000..007373e0bb8 --- /dev/null +++ b/addons/hr_holidays/static/src/views/calendar/filter_panel/calendar_filter_panel.xml @@ -0,0 +1,31 @@ + + + + + +
+
Legend
+ +
+ Validated + To Approve + Refused +
+
+
+
+ + + + + + + + + + + + + + +
diff --git a/addons/hr_holidays/static/src/views/calendar/year/calendar_year_renderer.js b/addons/hr_holidays/static/src/views/calendar/year/calendar_year_renderer.js new file mode 100644 index 00000000000..65403f2d7a8 --- /dev/null +++ b/addons/hr_holidays/static/src/views/calendar/year/calendar_year_renderer.js @@ -0,0 +1,40 @@ +/** @odoo-module */ + +import { CalendarYearRenderer } from '@web/views/calendar/calendar_year/calendar_year_renderer'; + +import { useStressDays } from '../../hooks'; + +const { useEffect } = owl; + +export class TimeOffCalendarYearRenderer extends CalendarYearRenderer { + setup() { + super.setup(); + this.stressDays = useStressDays(this.props); + + useEffect((el) => { + for (const week of el) { + const row = week.parentElement; + + // Remove the week number if the week is empty. + // FullCalendar always displays 6 weeks even when empty. + if (!row.children[1].classList.length && + !row.children[row.children.length - 1].classList.length) { + row.remove(); + } + } + }, () => [this.rootRef.el && this.rootRef.el.querySelectorAll('.fc-content-skeleton td.fc-week-number')]); + } + + get options() { + return Object.assign(super.options, { + weekNumbers: true, + weekNumbersWithinDays: false, + weekLabel: this.env._t('Week'), + }); + } + + onDayRender(info) { + super.onDayRender(info); + this.stressDays(info); + } +} diff --git a/addons/hr_holidays/static/src/views/calendar/year/calendar_year_renderer.scss b/addons/hr_holidays/static/src/views/calendar/year/calendar_year_renderer.scss new file mode 100644 index 00000000000..a258bf9c5cf --- /dev/null +++ b/addons/hr_holidays/static/src/views/calendar/year/calendar_year_renderer.scss @@ -0,0 +1,14 @@ +.o_timeoff_calendar { + .o_calendar_widget { + .fc-dayGridYear-view { + .fc-week-number { + color: #adb5bd; + font-size: 0.85rem; + cursor: default; + vertical-align: middle; + + line-height: unset; + } + } + } +} diff --git a/addons/hr_holidays/static/src/views/hooks.js b/addons/hr_holidays/static/src/views/hooks.js new file mode 100644 index 00000000000..563f2312bdb --- /dev/null +++ b/addons/hr_holidays/static/src/views/hooks.js @@ -0,0 +1,35 @@ +/** @odoo-module */ + +import { useService } from "@web/core/utils/hooks"; + +const { useEnv } = owl; + +export function useStressDays(props) { + return (info) => { + const date = luxon.DateTime.fromJSDate(info.date).toISODate(); + const stressDay = props.model.stressDays[date]; + if (stressDay) { + info.el.classList.add('hr_stress_day_' + stressDay); + } + } +} + +export function useLeaveCancelWizard() { + const action = useService('action'); + const env = useEnv(); + + return (leaveId, callback) => { + action.doAction({ + name: env._t('Delete Confirmation'), + type: "ir.actions.act_window", + res_model: "hr.holidays.cancel.leave", + target: "new", + views: [[false, "form"]], + context: { + default_leave_id: leaveId, + } + }, { + onClose: callback, + }); + } +} diff --git a/addons/hr_holidays/static/src/views/view_dialog/form_view_dialog.js b/addons/hr_holidays/static/src/views/view_dialog/form_view_dialog.js new file mode 100644 index 00000000000..f752380fc8a --- /dev/null +++ b/addons/hr_holidays/static/src/views/view_dialog/form_view_dialog.js @@ -0,0 +1,63 @@ +/** @odoo-module */ + +import { FormViewDialog } from "@web/views/view_dialogs/form_view_dialog"; + +import { registry } from '@web/core/registry'; + +import { formView } from '@web/views/form/form_view'; +import { FormController } from '@web/views/form/form_controller'; + +import { useLeaveCancelWizard } from '../hooks'; + +export class TimeOffDialogFormController extends FormController { + setup() { + super.setup(); + this.leaveCancelWizard = useLeaveCancelWizard(); + } + + deleteRecord() { + const leaveId = this.model.root.data.id; + + this.props.onCancelLeave(); + if (this.model.root.data.can_cancel) { + this.leaveCancelWizard(leaveId, () => { + this.props.onLeaveCancelled(); + }); + } + } + + get canDelete() { + return this.model.root.data.can_cancel || false; + } +} + +TimeOffDialogFormController.props = { + ...FormController.props, + onCancelLeave: Function, + onLeaveCancelled: Function, +} + +registry.category('views').add('timeoff_dialog_form', { + ...formView, + Controller: TimeOffDialogFormController, +}); + + +export class TimeOffFormViewDialog extends FormViewDialog { + setup() { + super.setup(); + + this.viewProps = Object.assign(this.viewProps, { + type: "timeoff_dialog_form", + buttonTemplate: 'hr_holidays.FormViewDialog.buttons', + onCancelLeave: () => { + this.props.close(); + }, + onLeaveCancelled: this.props.onLeaveCancelled.bind(this), + }) + } +} +TimeOffFormViewDialog.props = { + ...TimeOffFormViewDialog.props, + onLeaveCancelled: Function, +} diff --git a/addons/hr_holidays/static/src/views/view_dialog/form_view_dialog.xml b/addons/hr_holidays/static/src/views/view_dialog/form_view_dialog.xml new file mode 100644 index 00000000000..73f559533fd --- /dev/null +++ b/addons/hr_holidays/static/src/views/view_dialog/form_view_dialog.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/addons/hr_holidays/static/src/xml/time_off_calendar.xml b/addons/hr_holidays/static/src/xml/time_off_calendar.xml deleted file mode 100644 index 4e7ffebeb9f..00000000000 --- a/addons/hr_holidays/static/src/xml/time_off_calendar.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - -
-
-
-

-
- - -
-

-
-
-
- - - -
-
    -
  • - -
  • -
  • - -
  • -
-
    -
  • - -
  • -
- -
-
- - -
- - -
-
- -
-
- - - -
-
Legend
- Validated
- To Approve
- Refused
-
-
-
- - diff --git a/addons/hr_holidays/static/tests/qunit_suite_tests/test_leave_stats_widget.js b/addons/hr_holidays/static/tests/qunit_suite_tests/test_leave_stats_widget.js index 4636abb7b42..ddc42cb028a 100644 --- a/addons/hr_holidays/static/tests/qunit_suite_tests/test_leave_stats_widget.js +++ b/addons/hr_holidays/static/tests/qunit_suite_tests/test_leave_stats_widget.js @@ -1,14 +1,19 @@ -odoo.define('hr_holidays.leave_stats_widget_tests', function (require) { - "use strict"; +/** @odoo-module */ - var FormView = require("web.FormView"); - var testUtils = require('web.test_utils'); +import { selectDropdownItem, editInput, getFixture } from '@web/../tests/helpers/utils'; +import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; - var createView = testUtils.createView; - QUnit.module('leave_stats_widget', { - beforeEach: function () { - this.data = { +let serverData; +let target; + +QUnit.module('leave_stats_widget', (hooks) => { + hooks.beforeEach(() => { + setupViewRegistries(); + + target = getFixture(); + serverData = { + models: { department: { fields: { name: { string: "Name", type: "char" }, @@ -82,74 +87,73 @@ odoo.define('hr_holidays.leave_stats_widget_tests', function (require) { holiday_type: 'employee', }] } - }; - } - }, function () { - QUnit.test('leave stats renders correctly', async function (assert) { - assert.expect(5); - var self = this; - var form = await createView({ - View: FormView, - model: 'hr.leave', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '', - res_id: 12, - mockRPC: function (route, args) { - if (args.model === 'hr.leave' && args.method === 'search') { - return Promise.resolve(self.data['hr.leave'].records.map(function (record) { return record.id; })); - } - return this._super.apply(this, arguments); - }, - }); - const $leaveTypeBody = form.$('.o_leave_stats #o_leave_stats_employee'); - const $leavesDepartmentBody = form.$('.o_leave_stats #o_leave_stats_department'); + } + }; + }); - assert.strictEqual($leaveTypeBody.find('span:contains(Legal Leave)').length, 1, "it should have leave type"); - assert.strictEqual($leaveTypeBody.find('span:contains(6)').length, 1, "it should have 6 days"); - - assert.strictEqual($leavesDepartmentBody.find('span:contains(Richard)').length, 2, "it should have 2 leaves for Richard"); - assert.strictEqual($leavesDepartmentBody.find('span:contains(Jesus)').length, 1, "it should have 1 leaves for Jesus"); - assert.strictEqual($leavesDepartmentBody.find('div.o_horizontal_separator:contains(R&D)').length, 1, "it should have R&D title"); - form.destroy(); + QUnit.test('leave stats renders correctly', async (assert) => { + assert.expect(5); + await makeView({ + serverData, + type: "form", + resModel: 'hr.leave', + arch: '
' + + '' + + '' + + '' + + '' + + '', + resId: 12, + mockRPC(route, args) { + if (args.model === 'hr.leave' && args.method === 'search') { + return Promise.resolve(this.data['hr.leave'].records.map(function (record) { return record.id; })); + } + }, }); - QUnit.test('leave stats reload when employee/department changes', async function (assert) { - assert.expect(2); - var form = await createView({ - View: FormView, - model: 'hr.leave', - mode: 'edit', - data: this.data, - arch: '
' + - '' + - '' + - '' + - '' + - '', - mockRPC: function (route, args) { - if (args.model === 'hr.leave' && args.method === 'search_read') { - assert.ok(_.some(args.args[0], _.matcher(['department_id', '=', 11])), "It should load department's leaves data"); - } - if (args.model === 'hr.leave' && args.method === 'read_group') { - assert.ok(_.some(args.kwargs.domain, _.matcher(['employee_id', '=', 200])), "It should load employee's leaves data"); - } - return this._super.apply(this, arguments); - }, - }); - // Set date => shouldn't load data yet (no employee nor department defined) - await testUtils.fields.editSelect($('input[name="date_from"]'), '2016-10-12 09:00:00'); - // Set employee => should load employee's date - await testUtils.fields.many2one.clickOpenDropdown("employee_id"); - await testUtils.fields.many2one.clickItem("employee_id", "Jesus"); - // Set department => should load department's data - await testUtils.fields.many2one.clickOpenDropdown("department_id"); - await testUtils.fields.many2one.clickItem("department_id", "R&D"); - form.destroy(); + const $leaveTypeBody = target.querySelector('.o_leave_stats #o_leave_stats_employee'); + const $leavesDepartmentBody = target.querySelector('.o_leave_stats #o_leave_stats_department'); + + assert.containsOnce($leaveTypeBody, 'span:contains(Legal Leave)', "it should have leave type"); + assert.containsOnce($leaveTypeBody, 'span:contains(6)', "it should have 6 days"); + + assert.containsN($leavesDepartmentBody, 'span:contains(Richard)', 2, "it should have 2 leaves for Richard"); + assert.containsOnce($leavesDepartmentBody, 'span:contains(Jesus)', "it should have 1 leaves for Jesus"); + assert.containsOnce($leavesDepartmentBody, 'div.o_horizontal_separator:contains(R&D)', "it should have R&D title"); + }); + QUnit.test('leave stats reload when employee/department changes', async (assert) => { + assert.expect(3); + await makeView({ + serverData, + type: "form", + resModel: 'hr.leave', + mode: 'edit', + arch: '
' + + '' + + '' + + '' + + '' + + '', + mockRPC(route, args) { + if (args.model === 'hr.leave' && args.method === 'search_read') { + assert.ok(_.some(args.kwargs.domain, _.matcher(['department_id', '=', 11])), "It should load department's leaves data"); + } + if (args.model === 'hr.leave' && args.method === 'read_group') { + assert.ok(_.some(args.kwargs.domain, _.matcher(['employee_id', '=', 200])), "It should load employee's leaves data"); + } + }, }); + + // Set date => shouldn't load data yet (no employee nor department defined) + await editInput( + target, + "div[name='date_from'] input", + "2016-10-12 09:00:00" + ); + + // Set employee => should load employee's date + await selectDropdownItem(target, "employee_id", "Jesus"); + // Set department => should load department's data + await selectDropdownItem(target, "department_id", "R&D"); }); }); diff --git a/addons/hr_holidays/views/hr_leave_allocation_views.xml b/addons/hr_holidays/views/hr_leave_allocation_views.xml index ad37a56c2bb..1f96a80498d 100644 --- a/addons/hr_holidays/views/hr_leave_allocation_views.xml +++ b/addons/hr_holidays/views/hr_leave_allocation_views.xml @@ -63,7 +63,7 @@ hr.leave.allocation 32 -
+ @@ -91,7 +91,7 @@
-

+

@@ -102,7 +102,7 @@ attrs="{'readonly': ['|', ('is_officer', '=', False), ('state', 'not in', ('draft', 'confirm'))]}"/> -
+
@@ -116,7 +116,7 @@
-
@@ -224,12 +224,16 @@
1
+
+ 1 +
1
+
diff --git a/addons/hr_holidays/views/hr_leave_type_views.xml b/addons/hr_holidays/views/hr_leave_type_views.xml index e822083deb2..a047afe5d59 100644 --- a/addons/hr_holidays/views/hr_leave_type_views.xml +++ b/addons/hr_holidays/views/hr_leave_type_views.xml @@ -68,7 +68,7 @@ attrs="{ 'invisible': [('leave_validation_type', 'in', ['no_validation', 'manager']), '|', ('requires_allocation', '=', 'no'), ('allocation_validation_type', '!=', 'officer')], 'required': ['|',('leave_validation_type', 'in', ['both', 'hr']), ('requires_allocation', '=', 'yes'), ('allocation_validation_type', '=', 'officer')]}"/> - + diff --git a/addons/hr_holidays/views/hr_leave_views.xml b/addons/hr_holidays/views/hr_leave_views.xml index 80ffef73b82..bb9aaa0a40f 100644 --- a/addons/hr_holidays/views/hr_leave_views.xml +++ b/addons/hr_holidays/views/hr_leave_views.xml @@ -220,7 +220,7 @@ hr.leave 32 - + @@ -256,10 +256,10 @@
-
- +
+
@@ -397,7 +397,7 @@ hr.leave.view.dashboard hr.leave - hr.leave.view.dashboard hr.leave - + @@ -440,6 +450,9 @@ primary 16 + + 0 + @@ -455,14 +468,6 @@ 'required': [('holiday_type', '=', 'department')], 'invisible': [('holiday_type', '!=', 'department')] }"/> - - -
-

- -

-
- + - +
@@ -495,7 +500,16 @@ hr.leave.view.calendar hr.leave - +