[IMP] hr_holidays: migrate calendar views to Owl
task-2991495 closes odoo/odoo#101658 X-original-commit: fb2dfd438c240848c4410b040d2177e4ff6a71b3 Signed-off-by: Kevin Baptiste <kba@odoo.com> Signed-off-by: William Braeckman (wbr) <wbr@odoo.com>
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -4,7 +4,16 @@
|
||||
<field name="name">hr.leave.report.calendar.view</field>
|
||||
<field name="model">hr.leave.report.calendar</field>
|
||||
<field name="arch" type="xml">
|
||||
<calendar string="Time Off" date_start="start_datetime" date_stop="stop_datetime" mode="month" quick_add="False" color="employee_id" event_open_popup="True" js_class="time_off_calendar_all" show_unusual_days="True">
|
||||
<calendar
|
||||
string="Time Off"
|
||||
date_start="start_datetime"
|
||||
date_stop="stop_datetime"
|
||||
mode="month"
|
||||
quick_add="False"
|
||||
color="employee_id"
|
||||
event_open_popup="True"
|
||||
js_class="time_off_calendar"
|
||||
show_unusual_days="True">
|
||||
<field name="name"/>
|
||||
<field name="employee_id" filters="1" invisible="1"/>
|
||||
<field name="is_hatched" invisible="1"/>
|
||||
|
||||
@@ -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'];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<div t-name="hr_holidays.TimeOffDashboard" owl="1" class="o_timeoff_dashboard">
|
||||
<t t-foreach="props.holidays" t-as="holiday" t-key="holiday[3]">
|
||||
<t t-foreach="state.holidays" t-as="holiday" t-key="holiday[3]">
|
||||
<TimeOffCard name="holiday[0]" data="holiday[1]" requires_allocation="holiday[2] == 'yes'" id="holiday[3]"/>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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()
|
||||
};
|
||||
},
|
||||
|
||||
});
|
||||
-33
@@ -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;
|
||||
},
|
||||
|
||||
});
|
||||
-15
@@ -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);
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div t-name="hr_holidays.LeaveStatsComponent" owl="1" class="o_leave_stats">
|
||||
<div t-if="employee" id="o_leave_stats_employee">
|
||||
<div class="o_hr_leave_subtitle">
|
||||
<t t-esc="employee.display_name"/> in <t t-esc="thisYear"/>
|
||||
<t t-esc="employee[1]"/> in <t t-esc="thisYear"/>
|
||||
</div>
|
||||
<div t-if="state.leaves.length === 0">
|
||||
None
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<div t-if="department" id="o_leave_stats_department">
|
||||
<div class="o_horizontal_separator o_hr_leave_subtitle">
|
||||
<t t-esc="department.display_name"/>
|
||||
<t t-esc="department[1]"/>
|
||||
</div>
|
||||
<div t-if="state.departmentLeaves.length === 0">
|
||||
None
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="hr_holidays.CalendarController" t-inherit="web.CalendarController" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//DatePicker" position="replace"/>
|
||||
</t>
|
||||
|
||||
<t t-name="hr_holidays.CalendarController.controlButtons" t-inherit="web.CalendarController.controlButtons" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//span[hasclass('o_calendar_scale_buttons')]" position="after">
|
||||
<span class="o_timeoff_buttons">
|
||||
<button class="btn btn-primary btn-time-off mx-1" t-on-click="newTimeOffRequest" type="button">
|
||||
New Time Off
|
||||
</button>
|
||||
<button class="btn btn-secondary" t-on-click="newAllocationRequest" type="button">
|
||||
<t t-if="employeeId">New</t> Allocation Request
|
||||
</button>
|
||||
<span>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="hr_holidays.CalendarRenderer" owl="1">
|
||||
<div class="o_timeoff_calendar">
|
||||
<TimeOffDashboard t-if="showDashboard" employeeId="employeeId"/>
|
||||
<t t-call="web.CalendarRenderer"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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",
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="hr_holidays.CalendarFilterPanel" t-inherit="web.CalendarFilterPanel" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//t[@t-foreach='props.model.filterSections']" position="after">
|
||||
<div class="o_calendar_filter">
|
||||
<h5>Legend</h5>
|
||||
|
||||
<div class="d-flex flex-column">
|
||||
<span><img src="/hr/static/src/img/icons/plain.svg" width="30px"/> Validated</span>
|
||||
<span><img src="/hr/static/src/img/icons/hatched.svg" width="30px"/> To Approve</span>
|
||||
<span><img src="/hr/static/src/img/icons/line.svg" width="30px"/> Refused</span>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
|
||||
<t t-name="hr_holidays.CalendarFilterPanel.filter" t-inherit="web.CalendarFilterPanel.filter" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//span[@t-esc='filter.label']" position="replace">
|
||||
<span class="o_cw_filter_title text-truncate flex-grow">
|
||||
<t t-esc="filter.label"/>
|
||||
|
||||
<t t-if="env.isSmall">
|
||||
<t t-set="holiday" t-value="leaveState.holidays[filter.value]"/>
|
||||
<TimeOffCardMobile t-if="holiday" name="holiday[0]" id="holiday[3]" data="holiday[1]" requires_allocation="holiday[2] === 'yes'" />
|
||||
</t>
|
||||
</span>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="hr_holidays.FormViewDialog.buttons" t-inherit="web.FormViewDialog.ToOne.buttons" owl="1">
|
||||
<xpath expr="//button[contains(@class, 'o_form_button_save')]" position="replace">
|
||||
<button class="btn btn-primary o_form_button_save" t-on-click="save" data-hotkey="c">Save</button>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//button[contains(@class, 'o_form_button_cancel')]" position="after">
|
||||
<button class="btn btn-secondary" t-if="canDelete" t-on-click="deleteRecord" data-hotkey="x">Delete</button>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -1,72 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="hr_holidays.dashboard.calendar.button">
|
||||
<button class="btn btn-primary btn-time-off" type="button">
|
||||
<t t-esc="time_off"/>
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-allocation" type="button">
|
||||
<t t-esc="request"/>
|
||||
</button>
|
||||
</t>
|
||||
|
||||
<t t-name="hr_holidays.calendar.popover.placeholder">
|
||||
<div t-attf-class="o_cw_popover popover card shadow #{typeof color === 'number' ? _.str.sprintf('o_calendar_color_%s', color) : ''}" role="tooltip">
|
||||
<div class="arrow"/>
|
||||
<div class="card-header d-flex justify-content-between py-2 pe-2">
|
||||
<h4 class="popover-header border-0 p-0 pt-1"/>
|
||||
<div class="ms-4">
|
||||
<i t-if="calendarIcon" t-attf-class="fa {{calendarIcon}}"></i>
|
||||
<span class="o_cw_popover_close ms-1"><i class="fa fa-close small"/></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_cw_body">
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="hr_holidays.calendar.popover">
|
||||
<div class="o_cw_body">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li t-if="!widget.hideDate and widget.eventDate.date" class="list-group-item">
|
||||
<b class="text-capitalize" t-esc="widget.eventDate.date"/> <small t-if="widget.eventDate.duration"><b t-esc="_.str.sprintf('(%s)', widget.eventDate.duration)"/></small>
|
||||
</li>
|
||||
<li t-if="!widget.hideTime and widget.eventTime.time" class="list-group-item">
|
||||
<b t-esc="widget.eventTime.time"/> <small t-if="widget.eventTime.duration"><b t-esc="_.str.sprintf('(%s)', widget.eventTime.duration)"/></small>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="list-group list-group-flush o_cw_popover_fields_secondary" t-if="widget.display_name">
|
||||
<li class="list-group-item">
|
||||
<span class="o_field_char o_field_widget" t-esc="widget.display_name" />
|
||||
</li>
|
||||
</ul>
|
||||
<div class="card-footer border-top" t-if="widget.canEdit or widget.canDelete">
|
||||
<a t-if="widget.canEdit" href="#" class="btn btn-primary o_cw_popover_edit">Edit</a>
|
||||
<a t-if="widget.canDelete" href="#" class="btn btn-secondary o_cw_popover_delete ms-2">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="FieldRadioIcon.button">
|
||||
<div t-if="mode === 'edit'" class="form-check o_radio_item" aria-atomic="true">
|
||||
<input type="radio" class="form-check-input o_radio_input" t-att-checked="checked ? true : undefined" t-att-disabled="disabled ? true : undefined"
|
||||
t-att-name="name" t-att-data-value="value[0]" t-att-data-index="index" t-att-id="id"/>
|
||||
<label class="form-check-label o_form_label" t-att-for="id"><img t-attf-src="/hr_holidays/static/src/img/icons/{{value[1]}}" width="48" height="48" /></label>
|
||||
</div>
|
||||
<div t-else="">
|
||||
<img t-attf-src="/hr_holidays/static/src/img/icons/{{value[1]}}" width="48" height="48"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="TimeOff.CalendarView.extend" t-extend="CalendarView">
|
||||
<t t-jquery='.o_calendar_sidebar_container' t-operation="append">
|
||||
<div class="o_timeoff_legend">
|
||||
<h5>Legend</h5>
|
||||
<img src="/hr/static/src/img/icons/plain.svg" width="30px" class="mb-1"/><span>Validated</span><br/>
|
||||
<img src="/hr/static/src/img/icons/hatched.svg" width="30px" class="mb-1"/><span>To Approve</span><br/>
|
||||
<img src="/hr/static/src/img/icons/line.svg" width="30px" class="mb-1"/><span>Refused</span><br/>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -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: '<form string="Leave">' +
|
||||
'<field name="employee_id"/>' +
|
||||
'<field name="department_id"/>' +
|
||||
'<field name="date_from"/>' +
|
||||
'<widget name="hr_leave_stats"/>' +
|
||||
'</form>',
|
||||
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: '<form string="Leave">' +
|
||||
'<field name="employee_id"/>' +
|
||||
'<field name="department_id"/>' +
|
||||
'<field name="date_from"/>' +
|
||||
'<widget name="hr_leave_stats"/>' +
|
||||
'</form>',
|
||||
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: '<form string="Leave">' +
|
||||
'<field name="employee_id"/>' +
|
||||
'<field name="department_id"/>' +
|
||||
'<field name="date_from"/>' +
|
||||
'<widget name="hr_leave_stats"/>' +
|
||||
'</form>',
|
||||
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: '<form string="Leave">' +
|
||||
'<field name="employee_id"/>' +
|
||||
'<field name="department_id"/>' +
|
||||
'<field name="date_from"/>' +
|
||||
'<widget name="hr_leave_stats"/>' +
|
||||
'</form>',
|
||||
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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
<field name="model">hr.leave.allocation</field>
|
||||
<field name="priority">32</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Allocation Request" js_class="allocation_leave_form">
|
||||
<form string="Allocation Request">
|
||||
<field name="can_reset" invisible="1"/>
|
||||
<field name="can_approve" invisible="1"/>
|
||||
<field name="holiday_type" invisible="1"/>
|
||||
@@ -91,7 +91,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<div id="title" class="oe_title">
|
||||
<h2><field name="name" placeholder="e.g. Time Off type (From validity start to validity end / no limit)" attrs="{'invisible': [('state', 'not in', ('draft', 'confirm'))]}" required="1"/>
|
||||
<h2><field name="name" class="w-100" placeholder="e.g. Time Off type (From validity start to validity end / no limit)" attrs="{'invisible': [('state', 'not in', ('draft', 'confirm'))]}" required="1"/>
|
||||
<field name="name_validity" attrs="{'invisible': [('state', 'in', ('draft', 'confirm'))]}"/></h2>
|
||||
</div>
|
||||
<group>
|
||||
@@ -102,7 +102,7 @@
|
||||
attrs="{'readonly': ['|', ('is_officer', '=', False), ('state', 'not in', ('draft', 'confirm'))]}"/>
|
||||
<field name="is_officer" invisible="1"/>
|
||||
<field name="accrual_plan_id" attrs="{'invisible': [('allocation_type', '=', 'regular')], 'readonly': ['|', ('is_officer', '=', False), ('state', 'not in', ('draft', 'confirm'))]}"/>
|
||||
<div class="o_td_label">
|
||||
<div class="o_td_label" name="validity_label">
|
||||
<label for="date_from" string="Validity Period" attrs="{'invisible': ['|', ('allocation_type', '=', 'accrual'), ('state', 'not in', ('draft', 'confirm'))]}"/>
|
||||
<label for="date_from" string="Start Date" attrs="{'invisible': [('allocation_type', '=', 'regular')]}"/>
|
||||
</div>
|
||||
@@ -116,7 +116,7 @@
|
||||
|
||||
<field name="number_of_days" invisible="1"/>
|
||||
<div class="o_td_label">
|
||||
<label for="number_of_days" string="Duration"
|
||||
<label for="number_of_days_display" string="Duration"
|
||||
attrs="{'readonly': [('allocation_type', '=', 'accrual'), ('state', 'not in', ('draft', 'confirm'))]}"/>
|
||||
</div>
|
||||
<div name="duration_display">
|
||||
@@ -224,12 +224,16 @@
|
||||
<div name="button_box" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</div>
|
||||
<div name="validity_label" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</div>
|
||||
<div name="validity" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</div>
|
||||
<label for="date_from" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</label>
|
||||
<div class="oe_chatter" position="replace"/>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -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')]}"/>
|
||||
<field name="request_unit" widget="radio-inline"/>
|
||||
<field name="request_unit"/>
|
||||
<field name="support_document" string="Allow To Attach Supporting Document" />
|
||||
<field name="time_type" required="1"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
|
||||
@@ -220,7 +220,7 @@
|
||||
<field name="model">hr.leave</field>
|
||||
<field name="priority">32</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Time Off Request" class="o_hr_leave_form" js_class="allocation_leave_form">
|
||||
<form string="Time Off Request" class="o_hr_leave_form">
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="can_reset" invisible="1"/>
|
||||
<field name="can_approve" invisible="1"/>
|
||||
@@ -256,10 +256,10 @@
|
||||
<field name="leave_type_request_unit" invisible="1"/>
|
||||
<div class="o_hr_leave_content row">
|
||||
<div class="o_hr_leave_column col_left col-md-6 col-12">
|
||||
<div name="title" class="o_hr_leave_title">
|
||||
<field name="display_name" invisible="1"/>
|
||||
<div name="title" class="o_hr_leave_title" invisible="1">
|
||||
<field name="employee_id" nolabel="1" readonly="1" force_save="1" invisible="1"/>
|
||||
<field name="employee_ids" invisible="1"/>
|
||||
<field name="display_name" attrs="{'invisible': [('holiday_status_id', '=', False)]}"/>
|
||||
</div>
|
||||
<group name="col_left">
|
||||
<field name="holiday_status_id" force_save="1" domain="['|', ('requires_allocation', '=', 'no'), '&', ('has_valid_allocation', '=', True), '&', ('virtual_remaining_leaves', '>', 0), ('max_leaves', '>', '0')]" context="{'employee_id':employee_id, 'default_date_from':date_from, 'default_date_to':date_to}" options="{'no_create': True, 'no_open': True, 'request_type':'leave'}" class="w-100"/>
|
||||
@@ -397,7 +397,7 @@
|
||||
<field name="name">hr.leave.view.dashboard</field>
|
||||
<field name="model">hr.leave</field>
|
||||
<field name="arch" type="xml">
|
||||
<calendar js_class="time_off_calendar"
|
||||
<calendar js_class="time_off_calendar_dashboard"
|
||||
string="Time Off Request"
|
||||
form_view_id="%(hr_holidays.hr_leave_view_form_dashboard_new_time_off)d"
|
||||
event_open_popup="true"
|
||||
@@ -422,7 +422,17 @@
|
||||
<field name="name">hr.leave.view.dashboard</field>
|
||||
<field name="model">hr.leave</field>
|
||||
<field name="arch" type="xml">
|
||||
<calendar string="Time Off Request" js_class="time_off_employee_calendar" form_view_id="%(hr_holidays.hr_leave_view_form_dashboard_new_time_off)d" event_open_popup="true" date_start="date_from" date_stop="date_to" mode="year" quick_add="False" show_unusual_days="True" color="color" hide_time="True">
|
||||
<calendar string="Time Off Request"
|
||||
js_class="time_off_calendar_dashboard"
|
||||
form_view_id="%(hr_holidays.hr_leave_view_form_dashboard_new_time_off)d"
|
||||
event_open_popup="true"
|
||||
date_start="date_from"
|
||||
date_stop="date_to"
|
||||
mode="year"
|
||||
quick_add="False"
|
||||
show_unusual_days="True"
|
||||
color="color"
|
||||
hide_time="True">
|
||||
<field name="display_name"/>
|
||||
<field name="holiday_status_id" filters="1" invisible="1" color="color"/>
|
||||
<field name="state" invisible="1"/>
|
||||
@@ -440,6 +450,9 @@
|
||||
<field name="mode">primary</field>
|
||||
<field name="priority">16</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='title']" position="attributes">
|
||||
<attribute name="invisible">0</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='holiday_status_id']" position="before">
|
||||
<field name="holiday_type" string="Mode"
|
||||
groups="hr_holidays.group_hr_holidays_user"/>
|
||||
@@ -455,14 +468,6 @@
|
||||
'required': [('holiday_type', '=', 'department')],
|
||||
'invisible': [('holiday_type', '!=', 'department')]
|
||||
}"/>
|
||||
</xpath>
|
||||
<field name="holiday_status_id" position="replace"/>
|
||||
<div name="title" position="inside">
|
||||
<h1 class="d-flex flex-row align-items-end justify-content-between o_hr_leave_title">
|
||||
<field name="holiday_status_id" options="{'no_open': True}" context="{'request_type':'leave', 'from_manager_leave_form': True ,'employee_id': employee_id, 'default_date_from':date_from, 'default_date_to':date_to}"/>
|
||||
</h1>
|
||||
</div>
|
||||
<label id="label_dates" position="before">
|
||||
<field name="multi_employee" invisible="1" force_save="1"/>
|
||||
<field name="employee_id" groups="hr_holidays.group_hr_holidays_user" attrs="{
|
||||
'invisible': ['|', '|', ('holiday_type', '!=', 'employee'), ('state', '!=', 'validate'), ('employee_id', '=', False)]
|
||||
@@ -471,10 +476,10 @@
|
||||
'required': [('holiday_type', '=', 'employee'), ('state', 'in', ('draft', 'cancel', 'refuse'))],
|
||||
'invisible': ['|', ('holiday_type', '!=', 'employee'), '&', ('state', '=', 'validate'), ('employee_id', '!=', False)],
|
||||
}" widget="many2many_avatar_employee"/>
|
||||
</label>
|
||||
</xpath>
|
||||
<field name="name" position="replace"/>
|
||||
<field name="user_id" position="before">
|
||||
<field name="name"/>
|
||||
<field name="name" widget="text"/>
|
||||
</field>
|
||||
<xpath expr="//div[hasclass('col_left')]" position="after">
|
||||
<div class="o_hr_leave_column col_right col-md-6 col-12">
|
||||
@@ -495,7 +500,16 @@
|
||||
<field name="name">hr.leave.view.calendar</field>
|
||||
<field name="model">hr.leave</field>
|
||||
<field name="arch" type="xml">
|
||||
<calendar js_class="time_off_calendar_all" string="Time Off Request" form_view_id="%(hr_holidays.hr_leave_view_form_dashboard)d" event_open_popup="true" date_start="date_from" date_stop="date_to" mode="month" show_unusual_days="True" quick_add="False" color="color">
|
||||
<calendar js_class="time_off_calendar"
|
||||
string="Time Off Request"
|
||||
form_view_id="%(hr_holidays.hr_leave_view_form_dashboard)d"
|
||||
event_open_popup="true"
|
||||
date_start="date_from"
|
||||
date_stop="date_to"
|
||||
mode="month"
|
||||
show_unusual_days="True"
|
||||
quick_add="False"
|
||||
color="color">
|
||||
<field name="display_name"/>
|
||||
<field name="holiday_status_id" color="color" filters="1" invisible="1"/>
|
||||
<field name="employee_id" filters="1" invisible="1"/>
|
||||
|
||||
Reference in New Issue
Block a user