[IMP] hr_*, sms: improve ux

Reorder smart buttons on the hr_employee to have them in the following order:
1. Documents
2. Holiday status
3. Time Off
4. Planning
5. Timesheets
6. Equipments
7. Cars
8. Contracts
9. Appraisal
10. Work Entries
11. Payroll
12. Attendance
13. Extra Hours

- Log in the chatter the name of the plan that has been started via the button "Launch Plan"

- When on mobile, open the Time Off Calendar in month view

- When sending a sms without any message, only display Message once in the invalid fields

- Avoid the sms-help info disappearing when clicking on it and there are characters in the body of the message

- Update the tooltip of phone number in the send sms wizard

- Avoid the employee name to be displayed on two lines on mobile if the name is too long. Reduce the font size instead

task-2735679

closes odoo/odoo#83745

Related: odoo/enterprise#23886
Signed-off-by: Kevin Baptiste <kba@odoo.com>
This commit is contained in:
Philippe Wauthy
2022-03-02 18:45:59 +00:00
parent bc4127ad69
commit 08a5bf761f
21 changed files with 134 additions and 123 deletions
+4
View File
@@ -60,8 +60,12 @@
'hr/static/src/js/m2x_avatar_employee.js',
'hr/static/src/js/standalone_m2o_avatar_employee.js',
'hr/static/src/js/user_menu.js',
'hr/static/src/js/work_permit_upload.js',
'hr/static/src/models/*/*.js',
],
'web.assets_qweb': [
'hr/static/src/xml/*.xml',
],
'web.qunit_suite_tests': [
'hr/static/tests/helpers/*.js',
'hr/static/tests/m2x_avatar_employee_tests.js',
+3 -3
View File
@@ -66,7 +66,7 @@ class HrEmployeePrivate(models.Model):
], string='Marital Status', groups="hr.group_hr_user", default='single', tracking=True)
spouse_complete_name = fields.Char(string="Spouse Complete Name", groups="hr.group_hr_user", tracking=True)
spouse_birthdate = fields.Date(string="Spouse Birthdate", groups="hr.group_hr_user", tracking=True)
children = fields.Integer(string='Number of Children', groups="hr.group_hr_user", tracking=True)
children = fields.Integer(string='Number of Dependant Children', groups="hr.group_hr_user", tracking=True)
place_of_birth = fields.Char('Place of Birth', groups="hr.group_hr_user", tracking=True)
country_of_birth = fields.Many2one('res.country', string="Country of Birth", groups="hr.group_hr_user", tracking=True)
birthday = fields.Date('Date of Birth', groups="hr.group_hr_user", tracking=True)
@@ -96,8 +96,8 @@ class HrEmployeePrivate(models.Model):
], 'Certificate Level', default='other', groups="hr.group_hr_user", tracking=True)
study_field = fields.Char("Field of Study", groups="hr.group_hr_user", tracking=True)
study_school = fields.Char("School", groups="hr.group_hr_user", tracking=True)
emergency_contact = fields.Char("Emergency Contact", groups="hr.group_hr_user", tracking=True)
emergency_phone = fields.Char("Emergency Phone", groups="hr.group_hr_user", tracking=True)
emergency_contact = fields.Char("Contact Name", groups="hr.group_hr_user", tracking=True)
emergency_phone = fields.Char("Contact Phone", groups="hr.group_hr_user", tracking=True)
km_home_work = fields.Integer(string="Home-Work Distance", groups="hr.group_hr_user", tracking=True)
job_id = fields.Many2one(tracking=True)
@@ -0,0 +1,10 @@
/** @odoo-module **/
import basicFields from 'web.basic_fields';
import fieldRegistry from 'web.field_registry';
const WorkPermitUpload = basicFields.FieldBinaryFile.extend({
template: "hr.WorkPermitUpload",
});
fieldRegistry.add('work_permit_upload', WorkPermitUpload);
+5
View File
@@ -58,3 +58,8 @@
.o_form_label.o_hr_form_label {
cursor: default;
}
.hr_presence {
font-size: 0.75em;
vertical-align: middle;
}
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="hr.WorkPermitUpload" t-inherit="web.FieldBinaryFile" t-inherit-mode="primary">
<xpath expr="//button[@title='Select']" position="attributes">
<attribute name="class" remove="btn-primary" add="btn-secondary" separator=" "/>
</xpath>
</t>
</templates>
+67 -67
View File
@@ -53,37 +53,20 @@
</header>
<sheet>
<div name="button_box" class="oe_button_box">
<button
id="hr_presence_button"
class="oe_stat_button"
disabled="1"
attrs="{'invisible': ['|', ('last_activity', '=', False), ('user_id', '=', False)]}">
<div role="img" class="fa fa-fw fa-circle text-success o_button_icon" attrs="{'invisible': [('hr_presence_state', '!=', 'present')]}" aria-label="Available" title="Available"/>
<div role="img" class="fa fa-fw fa-circle text-warning o_button_icon" attrs="{'invisible': [('hr_presence_state', '!=', 'to_define')]}" aria-label="Away" title="Away"/>
<div role="img" class="fa fa-fw fa-circle text-danger o_button_icon" attrs="{'invisible': [('hr_presence_state', '!=', 'absent')]}" aria-label="Not available" title="Not available"/>
<div class="o_stat_info" attrs="{'invisible': [('hr_presence_state', '=', 'present')]}">
<span class="o_stat_text">
Not Connected
</span>
</div>
<div class="o_stat_info" attrs="{'invisible': [('hr_presence_state', '!=', 'present')]}">
<span class="o_stat_value" attrs="{'invisible': [('last_activity_time', '=', False)]}">
<field name="last_activity_time"/>
</span>
<span class="o_stat_value" attrs="{'invisible': [('last_activity_time', '!=', False)]}">
<field name="last_activity"/>
</span>
<span class="o_stat_text">Present Since</span>
</div>
</button>
</div>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<field name="avatar_128" invisible="1"/>
<field name="image_1920" widget='image' class="oe_avatar" options='{"zoom": true, "preview_image":"avatar_128"}'/>
<div class="oe_title">
<h1 class="d-flex">
<field name="name" placeholder="Employee's Name" required="True"/>
<field name="name" placeholder="Employee's Name" required="True" style="font-size: min(4vw, 2.6rem);" />
<field name="last_activity_time" invisible="1"/>
<field name="last_activity" invisible="1"/>
<div id="hr_presence_status" attrs="{'invisible': ['|', ('last_activity', '=', False), ('user_id', '=', False)]}">
<div role="img" class="fa fa-fw fa-circle text-success o_button_icon hr_presence" attrs="{'invisible': [('hr_presence_state', '!=', 'present')]}" aria-label="Available" title="Available"/>
<div role="img" class="fa fa-2xs fa-fw fa-circle text-warning o_button_icon hr_presence" attrs="{'invisible': [('hr_presence_state', '!=', 'to_define')]}" aria-label="Away" title="Away"/>
<div role="img" class="fa fa-2xs fa-fw fa-circle text-danger o_button_icon hr_presence" attrs="{'invisible': [('hr_presence_state', '!=', 'absent')]}" aria-label="Not available" title="Not available"/>
</div>
<a title="Chat" icon="fa-comments" href="#" class="ml8 o_employee_chat_btn" invisible="not context.get('chat_icon')" attrs="{'invisible': [('user_id','=', False)]}" role="button"><i class="fa fa-comments"/></a>
</h1>
<h2>
@@ -135,55 +118,72 @@
<page name="personal_information" string="Private Information" groups="hr.group_hr_user">
<group>
<group string="Private Contact">
<field name="address_home_id"
context="{
'show_address': 1,
'default_type': 'private',
'form_view_ref': 'base.res_partner_view_form_private'}"
options='{"always_reload": True, "highlight_first_line": True}'/>
<field name="private_email" string="Email"/>
<field name="phone" class="o_force_ltr" groups="hr.group_hr_user" string="Phone" readonly="True"/>
<group>
<field name="address_home_id"
context="{
'show_address': 1,
'default_type': 'private',
'form_view_ref': 'base.res_partner_view_form_private'}"
options='{"always_reload": True, "highlight_first_line": True}'/>
<field name="private_email" string="Email"/>
<field name="phone" class="o_force_ltr" groups="hr.group_hr_user" string="Phone" readonly="True"/>
<field name="bank_account_id" context="{'default_partner_id': address_home_id}"/>
</group>
<group col="4" colspan="2">
<field name="lang" string="Language"/>
<field name="bank_account_id" context="{'default_partner_id': address_home_id}"/>
<label for="km_home_work"/>
<label for="km_home_work" />
<div class="o_row" name="div_km_home_work">
<field name="km_home_work" groups="hr.group_hr_user"/>
<span>Km</span>
</div>
</group>
</group>
<group>
<group string="Family Status" colspan="2">
<field name="marital"/>
<field name="spouse_complete_name" attrs="{'invisible': [('marital', 'not in', ['married', 'cohabitant'])]}" groups="hr.group_hr_user"/>
<field name="spouse_birthdate" attrs="{'invisible': [('marital', 'not in', ['married', 'cohabitant'])]}" groups="hr.group_hr_user"/>
<field name="children"/>
</group>
<group string="Emergency" colspan="2">
<field name="emergency_contact"/>
<field name="emergency_phone" class="o_force_ltr"/>
</group>
</group>
<group>
<group string="Education" colspan="2">
<field name="certificate"/>
<field name="study_field"/>
<field name="study_school"/>
</group>
<group string="Work Permit" name="has_work_permit" colspan="2">
<group colspan="2">
<field name="visa_no"/>
<field name="permit_no"/>
</group>
<group col="4" colspan="2">
<field name="visa_expire"/>
<field name="work_permit_expiration_date"/>
</group>
<group colspan="2">
<field name="has_work_permit" widget="work_permit_upload"/>
</group>
</group>
</group>
<group string="Citizenship">
<field name="country_id" options='{"no_open": True, "no_create": True}'/>
<field name="identification_id" groups="hr.group_hr_user"/>
<field name="passport_id" groups="hr.group_hr_user"/>
<field name="gender"/>
<field name="birthday"/>
<field name="place_of_birth" groups="hr.group_hr_user"/>
<field name="country_of_birth" groups="hr.group_hr_user"/>
</group>
<group string="Marital Status">
<field name="marital"/>
<field name="spouse_complete_name" attrs="{'invisible': [('marital', 'not in', ['married', 'cohabitant'])]}" groups="hr.group_hr_user"/>
<field name="spouse_birthdate" attrs="{'invisible': [('marital', 'not in', ['married', 'cohabitant'])]}" groups="hr.group_hr_user"/>
</group>
<group string="Dependant">
<field name="children"/>
</group>
<group string="Emergency">
<field name="emergency_contact"/>
<field name="emergency_phone" class="o_force_ltr"/>
</group>
<group string="Work Permit" name="has_work_permit">
<field name="visa_no"/>
<field name="permit_no"/>
<field name="visa_expire"/>
<field name="work_permit_expiration_date"/>
<field name="has_work_permit"/>
</group>
<group string="Education">
<field name="certificate"/>
<field name="study_field"/>
<field name="study_school"/>
<group colspan="2">
<field name="country_id" options='{"no_open": True, "no_create": True}'/>
<field name="identification_id" groups="hr.group_hr_user"/>
<field name="passport_id" groups="hr.group_hr_user"/>
</group>
<group col="4" colspan="2" name="gender_birthday">
<field name="gender"/>
<field name="birthday"/>
</group>
<group colspan="2">
<field name="place_of_birth" groups="hr.group_hr_user"/>
<field name="country_of_birth" groups="hr.group_hr_user"/>
</group>
</group>
</group>
</page>
+4 -1
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
from odoo import fields, models, _
class HrPlanWizard(models.TransientModel):
@@ -33,6 +33,9 @@ class HrPlanWizard(models.TransientModel):
date_deadline=date_deadline
)
for plan in self:
plan.employee_id.message_post(body=_('The plan %s has been started', plan.plan_id.name))
return {
'type': 'ir.actions.act_window',
'res_model': 'hr.employee',
@@ -4,39 +4,16 @@
<field name="name">hr.employee</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="priority">20</field>
<field name="priority">110</field>
<field name="groups_id" eval="[(4,ref('hr_attendance.group_hr_attendance_user'))]"/>
<field name="arch" type="xml">
<xpath expr="//button[@id='hr_presence_button']" position="attributes">
<xpath expr="//div[@id='hr_presence_status']" position="attributes">
<attribute name="attrs">
{'invisible': ['|', '|', ('user_id', '=', False), ('hr_presence_state', '=', 'absent'), ('attendance_state', '=', 'checked_in')]}
</attribute>
</xpath>
<xpath expr="//div[@name='button_box']" position="inside">
<field name="attendance_state" invisible="1"/>
<button name="%(hr_attendance_action_employee)d"
id="hr_attendance_button"
class="oe_stat_button"
type="action" attrs="{'invisible': [
'|', ('attendance_state', '=', False),
'&amp;',
('hr_presence_state', '=', 'present'),
('attendance_state', '=', 'checked_out')]}"
context="{'search_default_employee_id': id}">
<div role="img" id="oe_hr_attendance_status" class="fa fa-fw fa-circle o_button_icon oe_hr_attendance_status_green" attrs="{'invisible': [('attendance_state', '=', 'checked_out')]}" aria-label="Available" title="Available"/>
<div role="img" id="oe_hr_attendance_status" class="fa fa-fw fa-circle o_button_icon oe_hr_attendance_status_red" attrs="{'invisible': [('attendance_state', '=', 'checked_in')]}" aria-label="Not available" title="Not available"/>
<div class="o_stat_info" attrs="{'invisible': ['|', ('last_check_in', '=', False), ('last_check_out', '!=', False)]}">
<span class="o_stat_text">Attended Since</span>
<span class="o_stat_value"><field name="last_check_in" widget="relative_time"/></span>
</div>
<div class="o_stat_info" attrs="{'invisible': [('last_check_out', '=', False)]}">
<span class="o_stat_text">Not Attended Since</span>
<span class="o_stat_value"><field name="last_check_out" widget="relative_time"/></span>
</div>
<div class="o_stat_info" attrs="{'invisible': [('last_check_in', '!=', False)]}">
<span class="o_stat_text">Attendance</span>
</div>
</button>
<button name="%(hr_attendance_action)d"
class="oe_stat_button"
icon="fa-clock-o"
@@ -5,6 +5,7 @@
<field name="name">hr.hr.employee.view.form2</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="priority" eval="70" />
<field name="arch" type="xml">
<data>
<div name="button_box" position="inside">
+1
View File
@@ -5,6 +5,7 @@
<field name="name">hr.employee.form.inherit.hr.fleet</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form" />
<field name="priority" eval="60" />
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button name="action_open_employee_cars" type="object"
@@ -1,6 +1,7 @@
/** @odoo-module **/
import CalendarView from "web.CalendarView";
import config from 'web.config';
import { TimeOffCalendarController } from "./time_off_calendar_controller";
import { TimeOffCalendarRenderer } from "./time_off_calendar_renderer";
import { TimeOffPopoverRenderer } from "./time_off_popover_renderer";
@@ -11,6 +12,16 @@ export const TimeOffCalendarView = CalendarView.extend({
Controller: TimeOffCalendarController,
Renderer: TimeOffCalendarRenderer,
}),
/**
* @override
*/
init: function (viewInfo, params) {
this._super(viewInfo, params);
if(config.device.isMobile) {
this.loadParams.mode = "month";
}
}
});
/**
+4 -3
View File
@@ -154,8 +154,9 @@
<field name="name">hr.employee.leave.form.inherit</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name = "priority" eval="20" />
<field name="arch" type="xml">
<xpath expr="//button[@id='hr_presence_button']" position="attributes">
<xpath expr="//div[@id='hr_presence_status']" position="attributes">
<attribute name="attrs">
{'invisible': ['|', '|', '|', ('last_activity', '=', False), ('is_absent', '=', True), ('user_id', '=', False), ('id', '=', False)]}
</attribute>
@@ -166,7 +167,7 @@
<xpath expr="//group[@name='managers']" position="attributes">
<attribute name="invisible">0</attribute>
</xpath>
<div name="button_box" position="inside">
<xpath expr="//div[@name='button_box']" position="inside">
<field name="show_leaves" invisible="1"/>
<field name="is_absent" invisible="1"/>
<field name="hr_icon_display" invisible="1"/>
@@ -210,7 +211,7 @@
</span>
</div>
</button>
</div>
</xpath>
</field>
</record>
@@ -35,6 +35,7 @@
<field name="name">hr.holidays.attendance.employee.view.form.inherit</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="priority" eval="120"/>
<field name="arch" type="xml">
<xpath expr="//header" position="inside">
<button
@@ -52,20 +53,10 @@
<field name="name">hr.employee.holidays.attendance.inherit</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr_attendance.view_employee_form_inherit_hr_attendance"/>
<field name="priority" eval="125" />
<field name="arch" type="xml">
<!-- Hide Attendance button -->
<xpath expr="//button[@id='hr_attendance_button']" position="attributes">
<attribute name="attrs">
{'invisible': ['|', '|', '&amp;',
('hr_presence_state', '=', 'present'),
('attendance_state', '=', 'checked_out'),
('is_absent', '=', True),
('id', '=', False),
]}
</attribute>
</xpath>
<!-- Merge invisible attr of both module -->
<xpath expr="//button[@id='hr_presence_button']" position="attributes">
<xpath expr="//div[@id='hr_presence_status']" position="attributes">
<attribute name="attrs">
{'invisible': ['|', '|', '|',
('is_absent', '=', True),
+1
View File
@@ -4,6 +4,7 @@
<field name="name">hr.employee.view.form.inherit.maintenance</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="priority" eval="50"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button name="%(maintenance.hr_equipment_action)d"
@@ -38,12 +38,9 @@
<t t-set="empty" t-value="Object.keys(context).length == 2"/>
<div t-attf-class="o_field_x2many_list_row_add #{empty? 'd-block w-100' : 'd-inline float-end'}">
<div t-if="empty" class="o_resume_empty_helper o_horizontal_separator text-muted my-0">
<em>Resumé empty</em>
</div>
<a href="#"
role="button"
t-attf-class="btn o-kanban-button-new #{empty? 'btn-primary mt-3' : 'btn-secondary btn-sm'}"
t-attf-class="btn btn-secondary o-kanban-button-new #{empty? 'btn-primary mt-3' : 'btn-secondary btn-sm'}"
t-attf-data-context="{{ context }}">
<t t-if="empty">CREATE A NEW ENTRY</t>
<t t-else="">ADD</t>
+2 -1
View File
@@ -59,7 +59,8 @@
<xpath expr="//page[@name='public']" position="before">
<page name="public" string="Resumé">
<div class="row">
<div class="o_hr_skills_editable o_hr_skills_group o_group_resume col-lg-7 d-flex">
<div class="o_hr_skills_editable o_hr_skills_group o_group_resume col-lg-7 d-flex flex-column">
<separator string="Resume"/>
<!-- This field uses a custom tree view rendered by the 'hr_resume' widget.
Adding fields in the tree arch below makes them accessible to the widget
-->
+3 -2
View File
@@ -31,6 +31,7 @@
<field name="name">hr.employee.form.timesheet</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="priority" eval="40"/>
<field name="arch" type="xml">
<group name="application_group" position="attributes">
<attribute name="string">Application Settings</attribute>
@@ -42,13 +43,13 @@
<field name="currency_id" invisible="1"/>
</div>
</group>
<div name="button_box" position="inside">
<xpath expr="//div[@name='button_box']" position="inside">
<button class="oe_stat_button" type="action" name="%(timesheet_action_from_employee)d" icon="fa-calendar" groups="hr_timesheet.group_hr_timesheet_user">
<div class="o_stat_info">
<span class="o_stat_text">Timesheets</span>
</div>
</button>
</div>
</xpath>
</field>
</record>
@@ -4,6 +4,7 @@
<field name="name">hr.employee.view.form.inherit.hr.work.entry</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="priority" eval="90"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button type="object" class="oe_stat_button" id="open_work_entries"
@@ -44,7 +44,6 @@ var SmsWidget = FieldTextEmojis.extend({
var def = this._super.apply(this, arguments);
this._compute();
$('.o_sms_container').remove();
var $sms_container = $('<div class="o_sms_container"/>');
$sms_container.append(this._renderSMSInfo());
$sms_container.append(this._renderIAPButton());
+1 -1
View File
@@ -62,7 +62,7 @@ class SendSMS(models.TransientModel):
recipient_single_number_itf = fields.Char(
'Recipient Number', compute='_compute_recipient_single',
readonly=False, compute_sudo=False, store=True,
help='UX field allowing to edit the recipient number. If changed it will be stored onto the recipient.')
help='Phone number of the recipient. If changed, it will be recorded on recipient\'s profile.')
recipient_single_valid = fields.Boolean("Is valid", compute='_compute_recipient_single_valid', compute_sudo=False)
number_field_name = fields.Char('Number Field')
numbers = fields.Char('Recipients (Numbers)')
+2 -3
View File
@@ -47,9 +47,8 @@
<field name="recipient_single_description" class="oe_inline"/>
<field name="recipient_single_number_itf" class="oe_inline" nolabel="1" options="{'onchange_on_keydown': True}" placeholder="e.g. +1 415 555 0100"/>
</div>
<field name="body" widget="sms_widget" attrs="{'invisible': ['|', ('comment_single_recipient', '=', False), ('recipient_single_valid', '=', True)]}"/>
<field name="body" widget="sms_widget" attrs="{'invisible': [('comment_single_recipient', '=', True), ('recipient_single_valid', '=', False)]}" default_focus="1"/>
<field name="body" widget="sms_widget" attrs="{'invisible': ['|', ('comment_single_recipient', '=', False), ('recipient_single_valid', '=', True)], 'required': ['|', ('comment_single_recipient', '=', False), ('recipient_single_valid', '=', True)]}"/>
<field name="body" widget="sms_widget" attrs="{'invisible': [('comment_single_recipient', '=', True), ('recipient_single_valid', '=', False)], 'required': [('comment_single_recipient', '=', True), ('recipient_single_valid', '=', False)]}" default_focus="1"/>
<field name="mass_keep_log" invisible="1"/>
</group>
</sheet>