[IMP] hr_holidays: improve dashboard

- Switch position of leaves taken / remaining
 - Change the "To" date when requesting a leave
 - Take into account the leaves to be validated in the dashboard
 - UX improvements to the popover

closes odoo/odoo#46210

Taskid: 2198615
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
Kevin Baptiste
2020-04-01 08:06:45 +00:00
parent 2cb77eb104
commit baf891a304
5 changed files with 103 additions and 6 deletions
+3
View File
@@ -318,6 +318,9 @@ class HolidaysRequest(models.Model):
self.date_to = False
return
if self.request_date_from > self.request_date_to:
self.request_date_to = self.request_date_from
resource_calendar_id = self.employee_id.resource_calendar_id or self.env.company.resource_calendar_id
domain = [('calendar_id', '=', resource_calendar_id.id), ('display_type', '=', False)]
attendances = self.env['resource.calendar.attendance'].read_group(domain, ['ids:array_agg(id)', 'hour_from:min(hour_from)', 'hour_to:max(hour_to)', 'week_type', 'dayofweek', 'day_period'], ['week_type', 'dayofweek', 'day_period'], lazy=False)
+9 -1
View File
@@ -63,6 +63,9 @@ class HolidaysType(models.Model):
virtual_remaining_leaves = fields.Float(
compute='_compute_leaves', search='_search_virtual_remaining_leaves', string='Virtual Remaining Time Off',
help='Maximum Time Off Allowed - Time Off Already Taken - Time Off Waiting Approval')
virtual_leaves_taken = fields.Float(
compute='_compute_leaves', string='Virtual Time Off Already Taken',
help='Sum of validated and non validated time off requests.')
group_days_allocation = fields.Float(
compute='_compute_group_days_allocation', string='Days Allocated')
group_days_leave = fields.Float(
@@ -187,7 +190,7 @@ class HolidaysType(models.Model):
def get_days(self, employee_id):
# need to use `dict` constructor to create a dict per id
result = dict((id, dict(max_leaves=0, leaves_taken=0, remaining_leaves=0, virtual_remaining_leaves=0)) for id in self.ids)
result = dict((id, dict(max_leaves=0, leaves_taken=0, remaining_leaves=0, virtual_remaining_leaves=0, virtual_leaves_taken=0)) for id in self.ids)
requests = self.env['hr.leave'].search([
('employee_id', '=', employee_id),
@@ -206,6 +209,9 @@ class HolidaysType(models.Model):
status_dict['virtual_remaining_leaves'] -= (request.number_of_hours_display
if request.leave_type_request_unit == 'hour'
else request.number_of_days)
status_dict['virtual_leaves_taken'] += (request.number_of_hours_display
if request.leave_type_request_unit == 'hour'
else request.number_of_days)
if request.state == 'validate':
status_dict['leaves_taken'] += (request.number_of_hours_display
if request.leave_type_request_unit == 'hour'
@@ -240,6 +246,7 @@ class HolidaysType(models.Model):
'virtual_remaining_leaves': ('%.2f' % lt.virtual_remaining_leaves).rstrip('0').rstrip('.'),
'max_leaves': ('%.2f' % lt.max_leaves).rstrip('0').rstrip('.'),
'leaves_taken': ('%.2f' % lt.leaves_taken).rstrip('0').rstrip('.'),
'virtual_leaves_taken': ('%.2f' % lt.virtual_leaves_taken).rstrip('0').rstrip('.'),
'request_unit': lt.request_unit,
}, lt.allocation_type, lt.validity_stop)
for lt in leave_types]
@@ -266,6 +273,7 @@ class HolidaysType(models.Model):
holiday_status.leaves_taken = result.get('leaves_taken', 0)
holiday_status.remaining_leaves = result.get('remaining_leaves', 0)
holiday_status.virtual_remaining_leaves = result.get('virtual_remaining_leaves', 0)
holiday_status.virtual_leaves_taken = result.get('virtual_leaves_taken', 0)
def _compute_group_days_allocation(self):
domain = [
@@ -2,6 +2,7 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) {
'use strict';
var core = require('web.core');
var CalendarPopover = require('web.CalendarPopover');
var CalendarController = require("web.CalendarController");
var CalendarRenderer = require("web.CalendarRenderer");
var CalendarView = require("web.CalendarView");
@@ -10,6 +11,18 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) {
var _t = core._t;
var QWeb = core.qweb;
var TimeOffCalendarPopover = CalendarPopover.extend({
template: 'hr_holidays.calendar.popover',
init: function (parent, eventInfo) {
this._super.apply(this, arguments);
const state = this.event.extendedProps.record.state;
this.canDelete = state && ['validate', 'refuse'].indexOf(state) === -1;
this.canEdit = state !== undefined;
this.display_name = this.event.extendedProps.record.display_name;
this.displayFields = [];
},
});
var TimeOffCalendarController = CalendarController.extend({
events: _.extend({}, CalendarController.prototype.events, {
@@ -83,7 +96,32 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) {
});
},
});
var TimeOffCalendarRenderer = CalendarRenderer.extend({
var TimeOffPopoverRenderer = CalendarRenderer.extend({
config: _.extend({}, CalendarRenderer.prototype.config, {
CalendarPopover: TimeOffCalendarPopover,
}),
_getPopoverParams: function (eventData) {
let params = this._super.apply(this, arguments);
let calendarIcon;
let state = eventData.extendedProps.record.state;
if (state === 'validate') {
calendarIcon = 'fa-calendar-check-o';
} else if (state === 'refuse') {
calendarIcon = 'fa-calendar-times-o';
} else if(state) {
calendarIcon = 'fa-calendar-o';
}
params['title'] = eventData.extendedProps.record.display_name.split(':').slice(0, -1).join(':');
params['template'] = QWeb.render('hr_holidays.calendar.popover.placeholder', {color: this.getColor(eventData.color_index), calendarIcon: calendarIcon});
return params;
},
});
var TimeOffCalendarRenderer = TimeOffPopoverRenderer.extend({
_render: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
@@ -108,5 +146,15 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) {
}),
});
/**
* Calendar shown in the "Everyone" menu
*/
var TimeOffCalendarAllView = CalendarView.extend({
config: _.extend({}, CalendarView.prototype.config, {
Renderer: TimeOffPopoverRenderer,
}),
});
viewRegistry.add('time_off_calendar', TimeOffCalendarView);
viewRegistry.add('time_off_calendar_all', TimeOffCalendarAllView);
});
@@ -13,17 +13,17 @@
<div class="mt-2">
<t t-if="need_allocation">
<span t-esc="timeoff[1]['leaves_taken']" class="o_timeoff_big o_timeoff_purple"/> / <span t-esc="timeoff[1]['max_leaves']"/> <t t-if="timeoff[1]['request_unit'] == 'hour'">Hours</t><t t-else="">Days</t>
<span t-esc="timeoff[1]['virtual_remaining_leaves']" class="o_timeoff_big o_timeoff_purple"/> / <span t-esc="timeoff[1]['max_leaves']"/> <t t-if="timeoff[1]['request_unit'] == 'hour'">Hours</t><t t-else="">Days</t>
</t>
<t t-else="">
<span t-esc="timeoff[1]['leaves_taken']" class="o_timeoff_big o_timeoff_purple"/> <t t-if="timeoff[1]['request_unit'] == 'hour'">Hours</t><t t-else="">Days</t>
<span t-esc="timeoff[1]['virtual_leaves_taken']" class="o_timeoff_big o_timeoff_purple"/> <t t-if="timeoff[1]['request_unit'] == 'hour'">Hours</t><t t-else="">Days</t>
</t>
</div>
<b><span t-esc="timeoff[0]" class="o_timeoff_name"/></b>
<span class="mb-4" t-if="need_allocation">
<span t-attf-class="mr-1 font-weight-bold {{ cl }}" t-esc="timeoff[1]['remaining_leaves']"/><span>remaining</span>
<span t-attf-class="mr-1 font-weight-bold {{ cl }}" t-esc="timeoff[1]['virtual_leaves_taken']"/><span>taken</span>
<t t-if="timeoff[3]"> (Expire on <span t-esc="moment(timeoff[3]).format('L')"/>)</t>
</span>
</div>
@@ -38,4 +38,41 @@
<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 pr-2">
<h4 class="popover-header border-0 p-0 pt-1"/>
<div class="ml-4">
<i t-if="calendarIcon" t-attf-class="fa {{calendarIcon}}"></i>
<span class="o_cw_popover_close ml-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 ml-2">Delete</a>
</div>
</div>
</t>
</templates>
+2 -1
View File
@@ -359,6 +359,7 @@
<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" quick_add="False" show_unusual_days="True" color="employee_id" hide_time="True">
<field name="display_name"/>
<field name="employee_id" filters="1" invisible="1"/>
<field name="state" invisible="1"/>
</calendar>
</field>
</record>
@@ -432,7 +433,7 @@
<field name="name">hr.leave.view.calendar</field>
<field name="model">hr.leave</field>
<field name="arch" type="xml">
<calendar string="Time Off Request" event_open_popup="true" date_start="date_from" date_stop="date_to" mode="month" quick_add="False" color="employee_id">
<calendar js_class="time_off_calendar_all" string="Time Off Request" event_open_popup="true" date_start="date_from" date_stop="date_to" mode="month" quick_add="False" color="employee_id">
<field name="display_name"/>
<field name="employee_id" filters="1" invisible="1"/>
</calendar>