[IMP] hr_contract: Improve general UX (back2basics)

- hr_contract: Add kanban view on contract history action
- hr_contract: Display the avg wage, not the sum on aggregates
- hr_contract: Improve contract history list view
- hr_contract: Improve contract history tree view
- hr_contract: Improve contract history search view
- hr_contract: Display wage on contract kanban view
- hr_contract: Improve contract tree view
- hr_contract: Improve contract search view
- hr_contract: Make hr_responsible required
It is useful in multiple HR flows.
- hr_contract: Improve contract history form view
- hr_contract: Improve contract form view
- hr_work_entry: Improve work entry computed name
- hr_work_entry: Improve work entries tree view
- hr_work_entry: Improve work entry type form view

closes odoo/odoo#72402

Taskid: 2558171
Related: odoo/enterprise#19124
Related: odoo/upgrade#2583
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
Yannick Tivisse
2021-06-28 14:39:44 +00:00
parent a223bc66d1
commit e91e6307e6
9 changed files with 116 additions and 45 deletions
+6
View File
@@ -33,6 +33,7 @@ class Contract(models.Model):
default=lambda self: self.env.company.resource_calendar_id.id, copy=False, index=True,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
wage = fields.Monetary('Wage', required=True, tracking=True, help="Employee's monthly gross wage.")
contract_wage = fields.Monetary('Contract Wage', compute='_compute_contract_wage')
notes = fields.Html('Notes')
state = fields.Selection([
('draft', 'New'),
@@ -193,6 +194,11 @@ class Contract(models.Model):
for contract in self:
contract.employee_id.sudo().write({'contract_id': contract.id})
@api.depends('wage')
def _compute_contract_wage(self):
for contract in self:
contract.contract_wage = contract._get_contract_wage()
def _get_contract_wage(self):
self.ensure_one()
return self[self._get_contract_wage_field()]
@@ -9,6 +9,7 @@ class ContractHistory(models.Model):
_name = 'hr.contract.history'
_description = 'Contract history'
_auto = False
_order = 'is_under_contract'
# Even though it would have been obvious to use the reference contract's id as the id of the
# hr.contract.history model, it turned out it was a bad idea as this id could change (for instance if a
@@ -22,6 +23,7 @@ class ContractHistory(models.Model):
date_start = fields.Date('Start Date', readonly=True)
date_end = fields.Date('End Date', readonly=True)
employee_id = fields.Many2one('hr.employee', string='Employee', readonly=True)
active_employee = fields.Boolean('Active Employee', readonly=True)
is_under_contract = fields.Boolean('Is Currently Under Contract', readonly=True)
department_id = fields.Many2one('hr.department', string='Department', readonly=True)
structure_type_id = fields.Many2one('hr.payroll.structure.type', string='Salary Structure Type', readonly=True)
@@ -33,35 +35,42 @@ class ContractHistory(models.Model):
('close', 'Expired'),
('cancel', 'Cancelled')
], string='Status', readonly=True)
resource_calendar_id = fields.Many2one('resource.calendar', readonly=True)
wage = fields.Monetary('Wage', help="Employee's monthly gross wage.", readonly=True)
resource_calendar_id = fields.Many2one('resource.calendar', string="Working Schedule", readonly=True)
wage = fields.Monetary('Wage', help="Employee's monthly gross wage.", readonly=True, group_operator="avg")
company_id = fields.Many2one('res.company', string='Company', readonly=True)
company_country_id = fields.Many2one('res.country', string="Company country", related='company_id.country_id', readonly=True)
country_code = fields.Char(related='company_country_id.code', readonly=True)
currency_id = fields.Many2one(string='Currency', related='company_id.currency_id', readonly=True)
contract_type_id = fields.Many2one('hr.contract.type', 'Contract Type', readonly=True)
contract_ids = fields.One2many('hr.contract', string='Contracts', compute='_compute_contract_ids', readonly=True)
contract_count = fields.Integer(compute='_compute_contract_count', string="# Contracts")
under_contract_state = fields.Selection([
('done', 'Under Contract'),
('blocked', 'Not Under Contract')
], string='Contractual Status', compute='_compute_under_contract_state')
activity_state = fields.Selection(related='contract_id.activity_state')
@api.depends('contract_ids')
def _compute_contract_count(self):
for history in self:
history.contract_count = len(history.contract_ids)
@api.depends('is_under_contract')
def _compute_under_contract_state(self):
for contract in self:
contract.under_contract_state = 'done' if contract.is_under_contract else 'blocked'
for history in self:
history.under_contract_state = 'done' if history.is_under_contract else 'blocked'
@api.depends('employee_id.name')
def _compute_display_name(self):
for contract in self:
contract.display_name = _("%s's Contracts History", contract.employee_id.name)
for history in self:
history.display_name = _("%s's Contracts History", history.employee_id.name)
@api.model
def _get_fields(self):
return ','.join('contract.%s' % name for name, field in self._fields.items()
if field.store
and field.type not in ['many2many', 'one2many', 'related']
and field.name not in ['id', 'contract_id', 'employee_id', 'date_hired', 'is_under_contract'])
and field.name not in ['id', 'contract_id', 'employee_id', 'date_hired', 'is_under_contract', 'active_employee'])
def init(self):
tools.drop_view_if_exists(self.env.cr, self._table)
@@ -92,6 +101,7 @@ class ContractHistory(models.Model):
)
SELECT employee.id AS id,
employee.id AS employee_id,
employee.active AS active_employee,
contract.id AS contract_id,
contract_information.is_under_contract::bool AS is_under_contract,
employee.first_contract_date AS date_hired,
@@ -102,7 +112,6 @@ class ContractHistory(models.Model):
ON contract_information.employee_id = employee.id
AND contract.company_id = employee.company_id
WHERE employee.employee_type IN ('employee', 'student')
AND (employee.active = true OR contract.state='draft')
)""" % (self._table, self._get_fields()))
@api.depends('employee_id.contract_ids')
@@ -12,11 +12,16 @@
<field name="resource_calendar_id"/>
<field name="state"/>
<field name="is_under_contract"/>
<filter string="Running Contracts" name="open_contracts" domain="[('state', '=', 'open')]"/>
<filter string="Contracts to Review" name="contract_to_review" domain="['|', ('state', 'in', ['draft', 'close', 'cancel']), ('is_under_contract', '!=', True)]"/>
<filter string="No Contracts" name="no_contracts" domain="[('contract_id', '=', False)]"/>
<filter string="Currently Under Contract" name="currently_under_contract" domain="[('is_under_contract', '=', True)]"/>
<filter string="Contracts to Review" name="to_review" domain="['|', ('state', '=', 'draft'), ('is_under_contract', '!=', True)]"/>
<filter string="Active Employees" name="active_employees" domain="[('active_employee', '=', True)]"/>
<group expand="0" string="Group By">
<filter string="Job Position" name="job" domain="[]" context="{'group_by': 'job_id'}"/>
<filter string="Status" name='group_by_state' domain="[]" context="{'group_by': 'state'}"/>
<filter string="Reference Working Time" name="group_by_resource_calendar_id" domain="[]" context="{'group_by': 'resource_calendar_id'}"/>
<filter string="Salary Structure Type" name="group_by_structure_type_id" domain="[]" context="{'group_by': 'structure_type_id'}"/>
</group>
</search>
</field>
@@ -25,6 +30,7 @@
<field name="name">Contracts</field>
<field name="res_model">hr.contract.history</field>
<field name="view_mode">form</field>
<field name="context">{'search_default_active_employees': 1}</field>
</record>
<record id="hr_contract_history_view_form" model="ir.ui.view">
<field name="name">hr.contract.history.form</field>
@@ -37,7 +43,7 @@
duplicate="false"
import="false">
<header>
<button name="hr_contract_view_form_new_action" string="Create" type="object" groups="hr_contract.group_hr_contract_manager"/>
<button name="hr_contract_view_form_new_action" string="Create" type="object" groups="hr_contract.group_hr_contract_manager" class="btn-primary"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box"/>
@@ -59,11 +65,10 @@
<field name="contract_id" invisible="1"/>
<field name="company_country_id" invisible="1"/>
<field name="country_code" invisible="1"/>
<field name="date_hired"/>
<field name="structure_type_id"/>
<field string="Reference Working Time" name="resource_calendar_id"/>
<field name="resource_calendar_id"/>
<field name="currency_id" invisible="1"/>
<field name="wage"/>
<field name="wage" invisible="1"/>
</group>
<group>
<field name="department_id"/>
@@ -87,13 +92,14 @@
<field name="date_end"/>
<field name="resource_calendar_id"/>
<field name="currency_id" invisible="1"/>
<field name="wage"/>
<field name="wage" string="Monthly Wage"/>
<field name="state" widget="badge" decoration-info="state == 'draft'" decoration-warning="state == 'close'" decoration-success="state == 'open'"/>
</tree>
</field>
</page>
<page string="Other Information" name="contract_others">
<page string="Employee Information" name="contract_others">
<group>
<field name="date_hired"/>
<field name="hr_responsible_id"/>
<field name="company_id"/>
</group>
@@ -104,10 +110,11 @@
</field>
</record>
<record id="hr_contract_history_view_list_action" model="ir.actions.act_window">
<field name="name">Contracts</field>
<field name="name">Employees</field>
<field name="res_model">hr.contract.history</field>
<field name="view_mode">tree,form</field>
<field name="view_mode">tree,kanban,form</field>
<field name="search_view_id" ref="hr_contract_history_view_search"/>
<field name="context">{'search_default_active_employees': 1}</field>
</record>
<record id="hr_contract_history_to_review_view_list_action" model="ir.actions.act_window">
<field name="name">Contracts to Review</field>
@@ -117,6 +124,7 @@
<field name="context">
{
'search_default_to_review': 1,
'search_default_active_employees': 1
}
</field>
</record>
@@ -125,28 +133,65 @@
<field name="model">hr.contract.history</field>
<field name="arch" type="xml">
<tree string="Contracts"
default_order = 'date_start desc'
default_order = 'is_under_contract, date_start desc'
create="false"
edit="false"
delete="false"
duplicate="false"
import="false">
<field name="under_contract_state" widget="state_selection"/>
<field name="employee_id" widget="many2one_avatar_employee"/>
<field name="date_hired"/>
<field name="is_under_contract" invisible="1"/>
<field name="name"/>
<field name="date_start"/>
<field string="Reference Working Time" name="resource_calendar_id"/>
<field string="Reference Working Time" name="resource_calendar_id" optional="hide"/>
<field name="under_contract_state" widget="state_selection" optional="hide"/>
<field name="structure_type_id" optional="hide"/>
<field name="currency_id" invisible="1"/>
<field name="wage"/>
<field name="wage" optional="hide"/>
<field name="state"
widget="badge"
decoration-info="state == 'draft'"
decoration-warning="state == 'close'"
decoration-success="state == 'open'"/>
<field name="contract_count"/>
</tree>
</field>
</record>
<record id="hr_contract_history_view_kanban" model="ir.ui.view">
<field name="name">hr.contract.history.view.kanban</field>
<field name="model">hr.contract.history</field>
<field name="arch" type="xml">
<kanban class="o_kanban_small_column" default_order="date_end" sample="1">
<field name="employee_id"/>
<field name="activity_state"/>
<field name="state"/>
<progressbar field="activity_state" colors='{"planned": "success", "today": "warning", "overdue": "danger"}'/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_card oe_kanban_global_click">
<div class="oe_kanban_content">
<div class="o_hr_contract_state">
<strong class="o_kanban_record_title">
<field name="display_name"/>
</strong>
</div>
<div class="text-muted o_kanban_record_subtitle o_hr_contract_job_id">
<field name="job_id"/>
</div>
<div class="oe_kanban_bottom_right">
<span class="float-right">
<field name="employee_id" widget="many2one_avatar_employee"/>
</span>
</div>
</div>
<div class="oe_clear"></div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<menuitem
id="hr_menu_contract_history"
+16 -12
View File
@@ -103,7 +103,7 @@
<field name="department_id" operator="child_of"/>
<field name="resource_calendar_id"/>
<filter string="Running" name="running" domain="[('state', '=', 'open')]"/>
<filter string="Need Action" name="to_renew" domain="['&amp;', ('state', '=', 'open'), ('kanban_state', '=', 'blocked')]"/>
<filter string="Not Running" name="not_running" domain="[('state', '!=', 'open')]"/>
<separator />
<filter string="Employed" name="current_employee" domain="[('employee_id.active', '=', True)]"/>
<separator/>
@@ -117,9 +117,12 @@
<filter string="Future Activities" name="activities_upcoming_all"
domain="[('my_activity_date_deadline', '&gt;', context_today().strftime('%Y-%m-%d'))]"/>
<group expand="0" string="Group By">
<filter string="Employee" name="employee" domain="[]" context="{'group_by': 'employee_id'}"/>
<filter string="Job Position" name="job" domain="[]" context="{'group_by': 'job_id'}"/>
<filter string="Status" name='group_by_state' domain="[]" context="{'group_by': 'state'}"/>
<filter string="Status" name="group_by_state" domain="[]" context="{'group_by': 'state'}"/>
<filter string="Employee" name="group_by_employee" domain="[]" context="{'group_by': 'employee_id'}"/>
<filter string="Start Date" name="group_by_date_start" domain="[]" context="{'group_by': 'date_start'}"/>
<filter string="Job Position" name="group_by_job" domain="[]" context="{'group_by': 'job_id'}"/>
<filter string="Working Schedule" name="group_by_resource_calendar_id" domain="[]" context="{'group_by': 'resource_calendar_id'}"/>
<filter string="Salary Structure Type" name="group_by_structure_type_id" domain="[]" context="{'group_by': 'structure_type_id'}"/>
</group>
</search>
</field>
@@ -149,8 +152,8 @@
<group name="top_info_left">
<field name="active" invisible="1"/>
<field name="employee_id"/>
<field name="date_start"/>
<field name="date_end"/>
<field name="date_start" string="Contract Start Date"/>
<field name="date_end" string="Contract End Date"/>
<field name="company_country_id" invisible="1"/>
<field name="country_code" invisible="1"/>
<field name="structure_type_id" domain="['|', ('country_id', '=', False), ('country_id', '=', company_country_id)]"/>
@@ -169,11 +172,12 @@
<group name="top_info_right">
<field name="department_id"/>
<field name="job_id"/>
<field name="hr_responsible_id"/>
<field name="hr_responsible_id" required="1"/>
</group>
</group>
<notebook>
<page string="Contract Details" name="other">
<group><group name="contract_details"/></group>
<group name="notes_group" string="Notes">
<field name="notes" nolabel="1"/>
</group>
@@ -205,18 +209,18 @@
<field name="name">hr.contract.tree</field>
<field name="model">hr.contract</field>
<field name="arch" type="xml">
<tree string="Contracts" multi_edit="1" sample="1" default_order = 'date_start desc'>
<tree string="Contracts" multi_edit="1" sample="1" default_order='date_start ASC'>
<field name="name" readonly="1"/>
<field name="employee_id" readonly="1" widget="many2one_avatar_employee"/>
<field name="job_id"/>
<field name="resource_calendar_id"/>
<field name="date_start" readonly="1"/>
<field name="date_end" readonly="1"/>
<field name="resource_calendar_id" optional="show"/>
<field name="structure_type_id" optional="show"/>
<field name="state" widget="badge" decoration-info="state == 'draft'" decoration-warning="state == 'close'" decoration-success="state == 'open'"/>
<field name="kanban_state" widget="state_selection" readonly="1"/>
<field name="wage" invisible="1"/>
<field name="message_needaction" invisible="1"/>
<field name="company_id" groups="base.group_multi_company" readonly="1"/>
<field name="company_id" groups="base.group_multi_company" readonly="1" optional="show"/>
</tree>
</field>
</record>
@@ -248,7 +252,7 @@
<field name="name"/>
</strong>
</div>
<div class="text-muted o_kanban_record_subtitle o_hr_contract_job_id">
<div class="text-muted o_kanban_record_subtitle o_hr_contract_job_id" name="div_job_id">
<field name="job_id"/>
</div>
<div class="oe_kanban_bottom_right">
+6 -3
View File
@@ -5,7 +5,7 @@ from contextlib import contextmanager
from dateutil.relativedelta import relativedelta
from psycopg2 import OperationalError
from odoo import api, fields, models
from odoo import api, fields, models, _
class HrWorkEntry(models.Model):
@@ -61,7 +61,10 @@ class HrWorkEntry(models.Model):
@api.depends('work_entry_type_id', 'employee_id')
def _compute_name(self):
for work_entry in self:
work_entry.name = "%s: %s" % (work_entry.work_entry_type_id.name, work_entry.employee_id.name)
if not work_entry.employee_id:
work_entry.name = _('Undefined')
else:
work_entry.name = "%s: %s" % (work_entry.work_entry_type_id.name or _('Undefined Type'), work_entry.employee_id.name)
@api.depends('state')
def _compute_conflict(self):
@@ -213,7 +216,7 @@ class HrWorkEntryType(models.Model):
_description = 'HR Work Entry Type'
name = fields.Char(required=True, translate=True)
code = fields.Char(required=True)
code = fields.Char(required=True, help="Carefull, the Code is used in many references, changing it could lead to unwanted changes.")
color = fields.Integer(default=0)
sequence = fields.Integer(default=25)
active = fields.Boolean(
@@ -81,6 +81,7 @@
<field name="duration" readonly="1"/>
<field name="state"/>
<field name="date_start" string="Beginning" readonly="1"/>
<field name="date_stop" string="End" readonly="1"/>
</tree>
</field>
</record>
@@ -137,7 +138,6 @@
<field name="model">hr.work.entry.type</field>
<field name="arch" type="xml">
<tree>
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="code"/>
<field name="color" widget="color_picker"/>
@@ -161,10 +161,13 @@
<group name="identification">
<field name="code"/>
<field name="active" invisible="1"/>
<field name="sequence"/>
<field name="sequence" groups="base.group_no_one"/>
<field name="color" widget="color_picker"/>
</group>
</group>
<group name="other">
<group name="time_off" string="Time Off Options"/>
</group>
</sheet>
</form>
</field>
@@ -152,4 +152,5 @@ class HrWorkEntryType(models.Model):
_inherit = 'hr.work.entry.type'
_description = 'HR Work Entry Type'
is_leave = fields.Boolean(default=False, string="Time Off")
is_leave = fields.Boolean(
default=False, string="Time Off", help="Allow the work entry type to be linked with time off types.")
@@ -31,11 +31,9 @@
<field name="model">hr.work.entry.type</field>
<field name="inherit_id" ref="hr_work_entry.hr_work_entry_type_view_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='identification']" position="after">
<group>
<field name="is_leave"/>
</group>
</xpath>
<group name="time_off" position="inside">
<field name="is_leave"/>
</group>
</field>
</record>
@@ -89,4 +89,6 @@ class HrWorkEntryType(models.Model):
_inherit = 'hr.work.entry.type'
_description = 'HR Work Entry Type'
leave_type_ids = fields.One2many('hr.leave.type', 'work_entry_type_id', string='Time Off Type')
leave_type_ids = fields.One2many(
'hr.leave.type', 'work_entry_type_id', string='Time Off Type',
help="Every new time off type in this list will be reported as select work entry in payslip.")