[IMP] hr_holidays: Improve usability

This commit improves screens usability in the Time Off app.
The following points have been changed:

1. For leaves and allocations, add buttons on the kanban
card to directly approve or refuse if it's not already
validated.

2. On the leave kanban card, only display the dates,
not the full datetime.

3. Add a description to the activity created when a
leave/allocation needs to be approved by someone.

4. Allocation are now sorted in the list view, more recent firsts.

5. In the allocation form view, add a stat button to know
how many leaves has been allocated and how many has been taken
for the allocation leave type. The button redirects to the employee's
leaves of the leave type. Only shown for holiday type 'emloyee'.

6. In the leave form view, there are currently two duration fields
for managers. The readonly duration is an informative field for regular
employees and the editable duration is meant for managers, it's used if
the number of days needs to be manually set for some reason.
Make sure the manager only sees the editable duration fields.

7. From "My Dashboard", the creation form view should not be the
manager form view (default view) which has additional unecessary fields
when requesting a leave for yourself (`employee_id`, `holiday_type`, ect).
It's now showing the form view for regular employees.

Task 1961713
This commit is contained in:
Lucas Lefèvre
2019-05-17 11:22:18 +02:00
committed by Yannick Tivisse
parent 29e6a42df4
commit 09e515eecc
6 changed files with 149 additions and 31 deletions
+5 -3
View File
@@ -138,8 +138,7 @@ class HolidaysRequest(models.Model):
default=fields.Datetime.now,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, tracking=True)
number_of_days = fields.Float(
'Duration (Days)', copy=False, readonly=True, tracking=True,
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
'Duration (Days)', copy=False, tracking=True,
help='Number of days of the time off request. Used in the calculation. To manually correct the duration, use this field.')
number_of_days_display = fields.Float(
'Duration in days', compute='_compute_number_of_days_display', copy=False, readonly=True,
@@ -469,7 +468,7 @@ class HolidaysRequest(models.Model):
target = leave.employee_id.name
res.append(
(leave.id,
_("%s on %s :%.2f day(s)") %
_("%s on %s: %.2f day(s)") %
(target, leave.holiday_status_id.name, leave.number_of_days))
)
return res
@@ -817,16 +816,19 @@ class HolidaysRequest(models.Model):
def activity_update(self):
to_clean, to_do = self.env['hr.leave'], self.env['hr.leave']
for holiday in self:
note = _('New %s Request created by %s from %s to %s') % (holiday.holiday_status_id.name, holiday.create_uid.name, fields.Datetime.to_string(holiday.date_from), fields.Datetime.to_string(holiday.date_to))
if holiday.state == 'draft':
to_clean |= holiday
elif holiday.state == 'confirm':
holiday.activity_schedule(
'hr_holidays.mail_act_leave_approval',
note=note,
user_id=holiday.sudo()._get_responsible_for_approval().id)
elif holiday.state == 'validate1':
holiday.activity_feedback(['hr_holidays.mail_act_leave_approval'])
holiday.activity_schedule(
'hr_holidays.mail_act_leave_second_approval',
note=note,
user_id=holiday.sudo()._get_responsible_for_approval().id)
elif holiday.state == 'validate':
to_do |= holiday
@@ -21,6 +21,7 @@ class HolidaysAllocation(models.Model):
""" Allocation Requests Access specifications: similar to leave requests """
_name = "hr.leave.allocation"
_description = "Time Off Allocation"
_order = "create_date desc"
_inherit = ['mail.thread', 'mail.activity.mixin']
_mail_post_access = 'read'
@@ -126,6 +127,8 @@ class HolidaysAllocation(models.Model):
('years', 'Year(s)')
], string="Unit of time between two intervals", default='weeks', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
nextcall = fields.Date("Date of the next accrual allocation", default=False, readonly=True)
max_leaves = fields.Float(compute='_compute_leaves')
leaves_taken = fields.Float(compute='_compute_leaves')
_sql_constraints = [
('type_value',
@@ -197,6 +200,14 @@ class HolidaysAllocation(models.Model):
holiday.write(values)
@api.multi
@api.depends('employee_id', 'holiday_status_id')
def _compute_leaves(self):
for allocation in self:
leave_type = allocation.holiday_status_id.with_context(employee_id=allocation.employee_id.id)
allocation.max_leaves = leave_type.max_leaves
allocation.leaves_taken = leave_type.leaves_taken
@api.multi
@api.depends('number_of_days')
def _compute_number_of_days_display(self):
@@ -565,16 +576,19 @@ class HolidaysAllocation(models.Model):
def activity_update(self):
to_clean, to_do = self.env['hr.leave.allocation'], self.env['hr.leave.allocation']
for allocation in self:
note = _('New Allocation Request created by %s: %s Days of %s') % (allocation.create_uid.name, allocation.number_of_days, allocation.holiday_status_id.name)
if allocation.state == 'draft':
to_clean |= allocation
elif allocation.state == 'confirm':
allocation.activity_schedule(
'hr_holidays.mail_act_leave_allocation_approval',
note=note,
user_id=allocation.sudo()._get_responsible_for_approval().id)
elif allocation.state == 'validate1':
allocation.activity_feedback(['hr_holidays.mail_act_leave_allocation_approval'])
allocation.activity_schedule(
'hr_holidays.mail_act_leave_allocation_second_approval',
note=note,
user_id=allocation.sudo()._get_responsible_for_approval().id)
elif allocation.state == 'validate':
to_do |= allocation
@@ -150,13 +150,8 @@ odoo.define('hr_holidays.dashboard.view_custo', function(require) {
*/
_onNewTimeOff: function () {
var self = this;
this.do_action({
type: 'ir.actions.act_window',
res_model: 'hr.leave',
view_type: 'form',
views: [[false,'form']],
target: 'new',
}, {
this.do_action('hr_holidays.hr_leave_action_my_request', {
on_close: function () {
self.reload();
}
@@ -55,11 +55,32 @@
<form string="Allocation Request">
<field name="can_reset" invisible="1"/>
<field name="can_approve" invisible="1"/>
<field name="holiday_type" invisible="1"/>
<header>
<button string="Confirm" name="action_confirm" states="draft" type="object" class="oe_highlight"/>
<field name="state" widget="statusbar" statusbar_visible="draft,confirm,validate"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button
class="oe_stat_button"
icon="fa-calendar"
type="action"
attrs="{'invisible': [('holiday_type', '!=', 'employee')]}"
name="%(hr_leave_action_all)d"
context="{'search_default_employee_id': [employee_id], 'search_default_holiday_status_id': [holiday_status_id]}"
help="Leaves Taken/Total Allocated">
<div class="o_stat_info">
<span class="o_stat_value">
<field name="leaves_taken" digits="[42,1]"/>/
<field name="max_leaves" digits="[42,1]"/>
</span>
<span class="o_stat_text">
Time Off
</span>
</div>
</button>
</div>
<div class="oe_title">
<h2><field name="display_name"/></h2>
</div>
@@ -230,6 +251,8 @@
<field name="name"/>
<field name="number_of_days"/>
<field name="can_approve"/>
<field name="state"/>
<field name="holiday_status_id"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click container">
@@ -245,7 +268,7 @@
<span class="badge badge-pill float-right mt4 mr16"><t t-esc="record.number_of_days.value"/> days</span>
<strong class="o_kanban_record_title"><t t-esc="record.employee_id.value"/></strong>
<div class="text-muted o_kanban_record_subtitle">
<t t-esc="record.name.value"/>
<t t-esc="record.holiday_status_id.value"/>
</div>
<div class="o_dropdown_kanban dropdown" groups="base.group_user">
<a role="button" class="dropdown-toggle o-no-caret btn" data-toggle="dropdown" href="#" aria-label="Dropdown menu" title="Dropdown menu">
@@ -254,14 +277,25 @@
<div class="dropdown-menu" role="menu">
<a t-if="widget.editable" role="menuitem" type="edit" class="dropdown-item">Edit Allocation</a>
<a t-if="widget.deletable" role="menuitem" type="delete" class="dropdown-item">Delete</a>
<a t-if="record.can_approve.value" name="action_approve" type="object"
role="menuitem" class="dropdown-item">Approve</a>
<a t-if="record.can_approve.value" name="action_refuse" type="object"
role="menuitem" class="dropdown-item">Refuse</a>
</div>
</div>
</div>
</div>
<div class="row no-gutters" t-if="['validate', 'refuse'].includes(record.state.raw_value)">
<div class="col-2"/>
<div class="col-10">
<span t-if="record.state.raw_value === 'validate'" class="fa fa-check text-muted"/>
<span t-else="" class="fa fa-ban text-muted"/>
<span class="text-muted"><t t-esc="record.state.value"/></span>
</div>
</div>
<div class="o_kanban_record_bottom" t-if="record.can_approve.raw_value" >
<div class="oe_kanban_bottom_left" t-if="record.can_approve.raw_value">
<button t-if="record.state.raw_value === 'confirm'" name="action_approve" type="object" class="btn btn-primary btn-sm mt8">Approve</button>
<button t-if="record.state.raw_value === 'validate1'" name="action_validate" type="object" class="btn btn-primary btn-sm mt8">Validate</button>
<button t-if="['confirm', 'validate1'].includes(record.state.raw_value)" name="action_refuse" type="object" class="btn btn-secondary btn-sm mt8">Refuse</button>
</div>
</div>
</div>
</t>
</templates>
@@ -3,9 +3,15 @@
<template id="assets_backend" inherit_id="web.assets_backend">
<xpath expr="script[last()]" position="after">
<script type="text/javascript" src="/hr_holidays/static/src/js/time_off_calendar.js"></script>
<script type="text/javascript" src="/hr_holidays/static/src/js/leave_stats_widget.js"></script>
</xpath>
<xpath expr="link[last()]" position="after">
<link rel="stylesheet" type="text/scss" href="/hr_holidays/static/scss/hr_leave_mobile.scss"/>
</xpath>
</template>
<template id="qunit_suite" name="hr_holidays_qunit_suite" inherit_id="web.qunit_suite">
<xpath expr="." position="inside">
<script type="text/javascript" src="/hr_holidays/static/tests/test_leave_stats_widget.js"></script>
</xpath>
</template>
</odoo>
+83 -16
View File
@@ -46,6 +46,7 @@
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<filter string="Today Activities" name="activities_today"
context="{'kanban_view_ref': 'hr_holidays.kanban_actty_view'}"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Future Activities" name="activities_upcoming_all"
domain="[('activity_ids.date_deadline', '&gt;', context_today().strftime('%Y-%m-%d'))
@@ -75,6 +76,8 @@
<field name="name"/>
<field name="number_of_days"/>
<field name="can_approve"/>
<field name="holiday_status_id"/>
<field name="state"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click container">
@@ -90,7 +93,7 @@
<span class="badge badge-pill float-right mt4 mr16"><t t-esc="record.number_of_days.value"/> days</span>
<strong class="o_kanban_record_title"><t t-esc="record.employee_id.value"/></strong>
<div class="text-muted o_kanban_record_subtitle">
<t t-esc="record.name.value"/>
<t t-esc="record.holiday_status_id.value"/>
</div>
<div class="o_dropdown_kanban dropdown" groups="base.group_user">
<a role="button" class="dropdown-toggle o-no-caret btn" data-toggle="dropdown" href="#" aria-label="Dropdown menu" title="Dropdown menu">
@@ -99,20 +102,33 @@
<div class="dropdown-menu" role="menu">
<a t-if="widget.editable" role="menuitem" type="edit" class="dropdown-item">Edit Time Off</a>
<a t-if="widget.deletable" role="menuitem" type="delete" class="dropdown-item">Delete</a>
<a t-if="record.can_approve.value" name="action_approve" type="object"
role="menuitem" class="dropdown-item">Approve</a>
<a t-if="record.can_approve.value" name="action_refuse" type="object"
role="menuitem" class="dropdown-item">Refuse</a>
</div>
</div>
</div>
</div>
<div class="row no-gutters justify-content-end">
<div class="col-2"><span class="text-muted">from</span></div>
<div class="col-8"><t t-esc="record.date_from.value"/></div>
<div class="w-100"></div>
<div class="col-2"><span class="text-muted">to</span></div>
<div class="col-8"><t t-esc="record.date_to.value"/></div>
<div class="col-2"/>
<div class="col-10">
<span class="text-muted">from </span>
<t t-esc="moment(record.date_from.raw_value).format('L')"/>
<span class="text-muted">to </span>
<t t-esc="moment(record.date_to.raw_value).format('L')"/>
</div>
</div>
<div class="row no-gutters" t-if="['validate', 'refuse'].includes(record.state.raw_value)">
<div class="col-2"/>
<div class="col-10">
<span t-if="record.state.raw_value === 'validate'" class="fa fa-check text-muted"/>
<span t-else="" class="fa fa-ban text-muted"/>
<span class="text-muted"><t t-esc="record.state.value"/></span>
</div>
</div>
<div class="o_kanban_record_bottom" t-if="record.can_approve.raw_value">
<div class="oe_kanban_bottom_left">
<button t-if="record.state.raw_value === 'confirm'" name="action_approve" type="object" class="btn btn-primary btn-sm mt8">Approve</button>
<button t-if="record.state.raw_value === 'validate1'" name="action_validate" type="object" class="btn btn-primary btn-sm mt8">Validate</button>
<button t-if="['confirm', 'validate1'].includes(record.state.raw_value)" name="action_refuse" type="object" class="btn btn-secondary btn-sm mt8">Refuse</button>
</div>
</div>
</div>
</t>
@@ -188,6 +204,11 @@
</header>
<sheet>
<field name="leave_type_request_unit" invisible="1"/>
<div class="oe_title" attrs="{'invisible': [('display_name', '=', False)]}">
<h1>
<field name="display_name"/>
</h1>
</div>
<group>
<group>
<field name="holiday_status_id" domain="['|',('allocation_type', 'in', ['fixed_allocation', 'no']),'&amp;',('allocation_type', '=', 'fixed'), ('max_leaves', '>', '0')]" context="{'employee_id':employee_id, 'default_date_from':date_from}" options="{'no_create': True, 'no_open': True}"/>
@@ -258,13 +279,16 @@
</div>
</div>
<field name="number_of_days" invisible="1"/>
<label for="number_of_days" class="oe_edit_only" string="Duration (days)" groups="hr_holidays.group_hr_holidays_manager"/>
<field name="number_of_days" class="oe_edit_only" nolabel="1" groups="hr_holidays.group_hr_holidays_manager"/>
<label for="number_of_days_display" string="Duration"/>
<!-- When the user is leave manager, he should always see `number_of_days` to allow
him to edit the value. `number_of_hours_display` is only an informative field -->
<label for="number_of_days" string="Duration"/>
<div>
<div attrs="{'invisible': [('leave_type_request_unit', '=', 'hour')]}">
<field name="number_of_days_display" nolabel="1" class="oe_inline"/>
<div groups="!hr_holidays.group_hr_holidays_manager" attrs="{'invisible': [('leave_type_request_unit', '=', 'hour')]}">
<field name="number_of_days_display" nolabel="1" readonly="1" class="oe_inline"/>
<span class="ml8">Days</span>
</div>
<div groups="hr_holidays.group_hr_holidays_manager">
<field name="number_of_days" nolabel="1" class="oe_inline"/>
<span class="ml8">Days</span>
</div>
<div attrs="{'invisible': [('leave_type_request_unit', '!=', 'hour')]}">
@@ -291,6 +315,16 @@
</field>
</record>
<record id="hr_leave_view_dashboard" model="ir.ui.view">
<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" string="Time Off Request" readonly_form_view_id="%(hr_holidays.hr_leave_view_form)d" form_view_id="%(hr_holidays.hr_leave_view_form)d" event_open_popup="true" date_start="date_from" date_stop="date_to" mode="month" quick_add="False" color="employee_id">
<field name="display_name"/>
</calendar>
</field>
</record>
<record id="hr_leave_view_form_manager" model="ir.ui.view">
<field name="name">hr.leave.view.form.manager</field>
<field name="model">hr.leave</field>
@@ -493,6 +527,20 @@
action="hr_leave_action_new_request"
sequence="2"/>
<record id="hr_leave_action_my_request" model="ir.actions.act_window">
<field name="name">Time Off Requests</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">hr.leave</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<record id="hr_leave_action_my_request_view_form" model="ir.actions.act_window.view">
<field name="view_mode">form</field>
<field name="act_window_id" ref="hr_leave_action_my_request"/>
<field name="view_id" ref="hr_leave_view_form"/>
</record>
<record id="hr_leave_action_my" model="ir.actions.act_window">
<field name="name">Time Off Requests</field>
<field name="type">ir.actions.act_window</field>
@@ -561,6 +609,25 @@
</field>
</record>
<record id="action_view_tree_manager_approve" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="hr_leave_view_tree"/>
<field name="act_window_id" ref="hr_leave_action_action_approve_department"/>
</record>
<record id="action_view_kanban_manager_approve" model="ir.actions.act_window.view">
<field name="sequence" eval="2"/>
<field name="view_mode">kanban</field>
<field name="view_id" ref="hr_leave_view_kanban"/>
<field name="act_window_id" ref="hr_leave_action_action_approve_department"/>
</record>
<record id="action_view_form_manager_approve" model="ir.actions.act_window.view">
<field name="sequence" eval="3"/>
<field name="view_mode">form</field>
<field name="view_id" ref="hr_leave_view_form_manager_approve"/>
<field name="act_window_id" ref="hr_leave_action_action_approve_department"/>
</record>
<record id="hr_leave_action_action_department" model="ir.actions.act_window">
<field name="name">Time Off Analysis</field>
<field name="res_model">hr.leave</field>