[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:
committed by
Yannick Tivisse
parent
29e6a42df4
commit
09e515eecc
@@ -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>
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
domain="[('activity_ids.date_deadline', '<', 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', '>', 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']),'&',('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>
|
||||
|
||||
Reference in New Issue
Block a user