Skip to content
3 changes: 1 addition & 2 deletions addons/hr/models/hr_employee.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,8 +579,7 @@ def _compute_legal_name(self):
@api.depends('current_version_id')
@api.depends_context('version_id')
def _compute_version_id(self):
context_version_id = self.env.context.get('version_id', False)
context_version = self.env['hr.version'].browse(context_version_id).exists() if context_version_id else self.env['hr.version']
context_version = self.env['hr.version'].browse(self.env.context.get('version_id', False))

for employee in self:
if context_version.employee_id == self:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<t t-name="hr.ButtonNewContract">
<span class="w-100 d-flex justify-content-end">
<button class="btn btn-link p-0 o_field_widget text-end w-auto" t-on-click="onClickNewContractBtn"
t-ref="datetime-picker-target-new-contract" t-if="props.record.resId and props.record.data.contract_date_start">New Contract</button>
t-ref="datetime-picker-target-new-contract" t-if="props.record.resId">New Contract</button>
</span>
</t>
</template>
21 changes: 0 additions & 21 deletions addons/hr/tests/test_hr_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,27 +523,6 @@ def test_multi_edit_other_and_contract_date_sync(self):
self.assertEqual(version.job_id.id, jobB.id)
self.assertEqual(version.contract_date_end, date(2020, 9, 30))

def test_delete_version(self):
employee = self.env['hr.employee'].create({
'name': 'John Doe',
'date_version': '2020-01-01',
})
v1 = employee.version_id
v2 = employee.create_version({
'date_version': '2021-01-01',
})
v3 = employee.create_version({
'date_version': '2022-01-01',
})
self.assertEqual(employee.current_version_id, v3)

v3.unlink()
self.assertEqual(employee.current_version_id, v2)
v1.unlink()
self.assertEqual(employee.current_version_id, v2)
with self.assertRaises(ValidationError):
v2.unlink()

def test_multi_edit_multi_employees_no_contract(self):
"""
Test the multi-edit when there is one version per employee, without contract
Expand Down
2 changes: 1 addition & 1 deletion addons/hr/views/hr_employee_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@
<span invisible="not contract_date_start">to</span>
<field name="contract_date_end" string="End Date" placeholder="Indefinite" widget="date_dynamic_min" options="{'min_date_field': 'contract_date_start'}"
class="o_hr_narrow_field ms-3" invisible="not contract_date_start"/>
<widget name="button_new_contract"/>
<widget name="button_new_contract" invisible="not contract_date_start"/>
</div>
<label for="wage"/>
<div class="o_row" name="wage">
Expand Down
5 changes: 3 additions & 2 deletions addons/hr_holidays/models/hr_leave_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,14 +275,15 @@ def _search_max_leaves(self, operator, value):
return [('id', 'in', valid_leaves)]

def _search_virtual_remaining_leaves(self, operator, value):
def is_valid(leave_type):
return not leave_type.requires_allocation or op(leave_type.virtual_remaining_leaves, value)
op = PY_OPERATORS.get(operator)
if not op:
return NotImplemented
if operator != 'in':
value = float(value)
leave_types = self.env['hr.leave.type'].search([])

def is_valid(leave_type):
return not leave_type.requires_allocation or op(leave_type.virtual_remaining_leaves, value)
return [('id', 'in', leave_types.filtered(is_valid).ids)]

@api.depends_context('uid', 'employee_id', 'default_employee_id', 'leave_date_from', 'default_date_from')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
}

.o_accrual {
.o_field_accrual, .o_field_selection, .o_field_day_selection, .o_field_filterable_selection {
.o_field_accrual, .o_field_selection, .o_field_filterable_selection {
width: fit-content !important;

&:not(.o_readonly_modifier) > *:first-child {
Expand All @@ -20,7 +20,7 @@
field-sizing: content;
}

&:not(.o_field_selection, .o_field_day_selection, .o_field_filterable_selection) > *:first-child {
&:not(.o_field_selection, .o_field_filterable_selection) > *:first-child {
max-width: 8ch;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,31 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { SelectionField, selectionField } from "@web/views/fields/selection/selection_field";

export class DaySelectionField extends SelectionField {
export class DaySelection extends SelectionField {

static props = {
...SelectionField.props,
monthField: String,
monthField: { type: String},
};
/**
* @override
* return the available days in the carryover_month
* e.g. February -> [1, 29], april -> [1, 30]
*/

get options() {
let options = super.options;
const carryover_month = this.props.record.data[this.props.monthField];
// lastDay is the last day of the current_month for the leap year 2020
const lastDay = new Date(2020, carryover_month, 0).getDate();
options = options.filter((option) => option[0] <= lastDay);
return options;
const month = this.props.record.data[this.props.monthField];

const allDays = this.props.record.fields[this.props.name].selection;

const maxDays = new Date(2024, month, 0).getDate();
return allDays.filter(opt => parseInt(opt[0]) <= maxDays);
}
}

export const daySelectionField = {
export const daySelection={
...selectionField,
component: DaySelectionField,
extractProps({ attrs }) {
return {
...selectionField.extractProps(...arguments),
monthField: attrs.month_field,
};
},
fieldDependencies: ({ attrs }) => [
{
name: attrs.month_field,
type: "selection",
},
],
};
component: DaySelection,

extractProps: ({ options }) => ({
monthField: options.month_field,
}),
}

registry.category("fields").add("day_selection", daySelectionField);
registry.category("fields").add("day_selection", daySelection);
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">

<t t-name="hr_holidays.DaySelection" t-inherit="web.SelectionField" t-inherit-mode="primary"/>
</templates>
1 change: 1 addition & 0 deletions addons/hr_holidays/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@
from . import test_time_off_allocation_tour
from . import test_flexible_resource_calendar
from . import test_calendar_leaves_count
from . import test_time_off_type
57 changes: 57 additions & 0 deletions addons/hr_holidays/tests/test_time_off_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import tagged

from odoo.addons.hr_holidays.tests.common import TestHrHolidaysCommon


@tagged('moali_test', '-at_install')
class TestTimeOffType(TestHrHolidaysCommon):

def test_time_off_type(self):
""" A test file to check for the time-off type field breaking on time off creation from smart button """
trackable_enough_leave_type = self.env['hr.leave.type'].create({
'name': 'Trackable Time Off Type 1',
'requires_allocation': True,
'request_unit': 'day',
'unit_of_measure': 'day',
})

trackable_not_enough_leave_type = self.env['hr.leave.type'].create({
'name': 'Trackable Time Off Type 2',
'requires_allocation': True,
'request_unit': 'day',
'unit_of_measure': 'day',
})

untrackable_leave_type = self.env['hr.leave.type'].create({
'name': 'Untrackable Time Off Type',
'requires_allocation': False,
'request_unit': 'day',
'unit_of_measure': 'day',
})

employee = self.env['hr.employee'].create({
'name': 'Test user',
})

enough_allocation = self.env['hr.leave.allocation'].create({
'employee_id': employee.id,
'number_of_days': 5,
'holiday_status_id': trackable_enough_leave_type.id,
'state': 'confirm',
}).action_approve()

not_enough_allocation = self.env['hr.leave.allocation'].create({
'employee_id': employee.id,
'number_of_days': 2,
'holiday_status_id': trackable_not_enough_leave_type.id,
'state': 'confirm',
}).action_approve()

available_types = self.env['hr.leave.type'].with_context(
employee_id=employee.id).search([('virtual_remaining_leaves', '>', 3)])

self.assertNotIn(trackable_not_enough_leave_type.id, available_types.ids)
self.assertIn(trackable_enough_leave_type.id, available_types.ids)
self.assertIn(untrackable_leave_type.id, available_types.ids)

11 changes: 6 additions & 5 deletions addons/hr_holidays/views/hr_leave_accrual_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,17 @@
</span>
<span name="biyearly" invisible="frequency != 'biyearly'">
on the
<field nolabel="1" name="first_month_day" class="o_field_accrual" widget="day_selection" month_field="first_month" placeholder="select a day" required="frequency == 'biyearly'"/>
<field nolabel="1" name="first_month_day" class="o_field_accrual" placeholder="select a day" required="frequency == 'biyearly'" widget="day_selection" options="{'month_field': 'first_month'}"/>
of
<field name="first_month" class="o_field_accrual" placeholder="select a month" required="frequency == 'biyearly'"/>
and the
<field nolabel="1" name="second_month_day" class="o_field_accrual" widget="day_selection" month_field="second_month" placeholder="select a day" required="frequency == 'biyearly'"/>
<field nolabel="1" name="second_month_day" class="o_field_accrual" placeholder="select a day" required="frequency == 'biyearly'" widget="day_selection" options="{'month_field': 'second_month'}"/>
of
<field nolabel="1" name="second_month" class="o_field_accrual" placeholder="select a month" required="frequency == 'biyearly'"/>
</span>
<span name="yearly" invisible="frequency != 'yearly'">
on the
<field nolabel="1" name="yearly_day" class="o_field_accrual" widget="day_selection" month_field="yearly_month" required="frequency == 'yearly'" placeholder="select a day"/>
<field nolabel="1" name="yearly_day" class="o_field_accrual" required="frequency == 'yearly'" placeholder="select a day" widget="day_selection" options="{'month_field': 'yearly_month'}"/>
of
<field nolabel="1" name="yearly_month" class="o_field_accrual" required="frequency == 'yearly'" placeholder="select a month"/>
</span>
Expand Down Expand Up @@ -192,8 +192,9 @@
options="{'links': {'other': 'carryover_custom_date'}, 'observe': 'carryover'}"/>
<span id="carryover_custom_date">
: the
<field name="carryover_day" widget="day_selection" month_field="carryover_month" placeholder="select a day"
required="carryover_date == 'other'"/>
<field name="carryover_day" placeholder="select a day"
required="carryover_date == 'other'" widget="day_selection" options="{'month_field': 'carryover_month'}"
class="d-inline-block w-auto"/>
of
<field name="carryover_month" placeholder="select a month"
required="carryover_date == 'other'"/>
Expand Down
1 change: 1 addition & 0 deletions addons/hr_skills/report/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from . import hr_employee_certification_report
from . import hr_employee_cv_report
from . import hr_employee_skill_history_report
from . import hr_employee_skill_report
47 changes: 47 additions & 0 deletions addons/hr_skills/report/hr_employee_certification_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import fields, models, tools


class HrEmployeeCertificationReport(models.BaseModel):
_name = 'hr.employee.certification.report'
_auto = False
_inherit = ["hr.manager.department.report"]
_description = 'Employee Certification Report'
_order = 'employee_id, level_progress desc'

company_id = fields.Many2one('res.company', readonly=True)
department_id = fields.Many2one('hr.department', readonly=True)

skill_id = fields.Many2one('hr.skill', readonly=True)
skill_type_id = fields.Many2one('hr.skill.type', readonly=True)
skill_level = fields.Char(readonly=True)
level_progress = fields.Float(readonly=True, aggregator='avg')
active = fields.Boolean(readonly=False)

def init(self):
tools.drop_view_if_exists(self.env.cr, self._table)

self.env.cr.execute("""
CREATE OR REPLACE VIEW %(table)s AS (
SELECT
row_number() OVER () AS id,
e.id AS employee_id,
e.company_id AS company_id,
v.department_id AS department_id,
s.skill_id AS skill_id,
s.skill_type_id AS skill_type_id,
sl.level_progress / 100.0 AS level_progress,
sl.name AS skill_level,
(s.valid_to IS NULL OR s.valid_to >= '%(date)s') AND s.valid_from <= '%(date)s' AS active
FROM hr_employee e
LEFT JOIN hr_version v ON e.current_version_id = v.id
LEFT OUTER JOIN hr_employee_skill s ON e.id = s.employee_id
LEFT OUTER JOIN hr_skill_level sl ON sl.id = s.skill_level_id
LEFT OUTER JOIN hr_skill_type st ON st.id = sl.skill_type_id
WHERE e.active AND st.active IS True AND st.is_certification IS TRUE
)
""" % {
'table': self._table,
'date': fields.Date.context_today(self)
})
71 changes: 71 additions & 0 deletions addons/hr_skills/report/hr_employee_certification_report_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hr_employee_certification_report_view_pivot" model="ir.ui.view">
<field name="model">hr.employee.certification.report</field>
<field name="arch" type="xml">
<pivot disable_linking="True">
<field name="employee_id" type="row"/>
<field name="skill_type_id" type="col"/>
<field name="skill_id" type="col"/>
<field name="level_progress" type="measure" widget="percentage" string="Current Level"/>
</pivot>
</field>
</record>

<record id="hr_employee_certification_report_view_list" model="ir.ui.view">
<field name="model">hr.employee.certification.report</field>
<field name="arch" type="xml">
<list expand="0" edit="1" editable="bottom">
<field name="employee_id" widget="many2one_avatar_employee" options="{'no_create': True, 'no_open': True}"/>
<field name="skill_type_id" options="{'no_create': True, 'no_open': True}" string="Certification Type"/>
<field name="skill_id" options="{'no_create': True, 'no_open': True}" string="Certification"/>
<field name="skill_level" options="{'no_create': True, 'no_open': True}" string="Certification Level"/>
<field name="level_progress" widget="percentage" options="{'no_create': True, 'no_open': True}" string="Current Level"/>
</list>
</field>
</record>

<record id="hr_employee_certification_report_view_search" model="ir.ui.view">
<field name="model">hr.employee.certification.report</field>
<field name="arch" type="xml">
<search>
<field name="employee_id"/>
<field name="department_id"/>
<field name="skill_id" string="Certification"/>
<field name="skill_type_id" string="Certification Type"/>
<separator/>
<filter string="Expired Certification" name="archived" domain="[('active', '=', False)]"/>
<separator/>
<filter string="Employee" name="employee" context="{'group_by': 'employee_id'}"/>
<filter string="Department" name="department" context="{'group_by': 'department_id'}"/>
<separator/>
<filter string="Certification Type" name="skill_type" context="{'group_by': 'skill_type_id'}"/>
<filter string="Certification" name="skill" context="{'group_by': 'skill_id'}"/>
</search>
</field>
</record>

<record id="hr_employee_certification_report_action" model="ir.actions.act_window">
<field name="name">Certification</field>
<field name="res_model">hr.employee.certification.report</field>
<field name="search_view_id" ref="hr_employee_certification_report_view_search"/>
<field name="view_mode">list,pivot</field>
<field name="context">{
'search_default_employee': 1,
}</field>
<field name="help" type="html">
<p class="o_view_nocontent_empty_folder">
</p><p>
This report will give you an overview of the certification per Employee.
Create them in configuration and add them on the Employee.
</p>
</field>
</record>

<menuitem
id="hr_employee_certification_report_menu"
name="Certifications"
action="hr_employee_certification_report_action"
parent="hr_skills.hr_employee_skill_report_menu"
sequence="25"/>
</odoo>
2 changes: 1 addition & 1 deletion addons/hr_skills/report/hr_employee_skill_report_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,6 @@
id="hr_employee_skill_inventory_report_menu"
name="Skills Inventory"
action="hr_employee_skill_report_action"
parent="hr.hr_menu_hr_reports"
parent="hr_skills.hr_employee_skill_report_menu"
sequence="15"/>
</odoo>
Loading