diff --git a/addons/hr/models/hr_employee.py b/addons/hr/models/hr_employee.py index 4c6f4cbf43082f..72a5d8c9e29eda 100644 --- a/addons/hr/models/hr_employee.py +++ b/addons/hr/models/hr_employee.py @@ -649,8 +649,7 @@ def _compute_split_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: diff --git a/addons/hr/static/src/components/button_new_contract/button_new_contract.xml b/addons/hr/static/src/components/button_new_contract/button_new_contract.xml index c3109a1bfee67c..8ba725c044d98e 100644 --- a/addons/hr/static/src/components/button_new_contract/button_new_contract.xml +++ b/addons/hr/static/src/components/button_new_contract/button_new_contract.xml @@ -3,7 +3,7 @@ New Contract + t-ref="this.datetimePickerTargetRef" t-if="this.props.record.resId">New Contract diff --git a/addons/hr/tests/test_hr_version.py b/addons/hr/tests/test_hr_version.py index 2e6c241a4a615e..26190aa4d07201 100644 --- a/addons/hr/tests/test_hr_version.py +++ b/addons/hr/tests/test_hr_version.py @@ -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 diff --git a/addons/hr/views/hr_employee_views.xml b/addons/hr/views/hr_employee_views.xml index 6bc745d2a06895..69fda10997f59c 100644 --- a/addons/hr/views/hr_employee_views.xml +++ b/addons/hr/views/hr_employee_views.xml @@ -383,7 +383,7 @@ to - + diff --git a/addons/hr_holidays/static/src/components/accrual_level/accrual_levels.scss b/addons/hr_holidays/static/src/components/accrual_level/accrual_levels.scss index 8e052ffe5bbf7d..f6a930fd221024 100644 --- a/addons/hr_holidays/static/src/components/accrual_level/accrual_levels.scss +++ b/addons/hr_holidays/static/src/components/accrual_level/accrual_levels.scss @@ -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 { @@ -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; } } diff --git a/addons/hr_holidays/static/src/components/day_selection/day_selection.js b/addons/hr_holidays/static/src/components/day_selection/day_selection.js deleted file mode 100644 index 5a599d35ae7ab2..00000000000000 --- a/addons/hr_holidays/static/src/components/day_selection/day_selection.js +++ /dev/null @@ -1,46 +0,0 @@ -import { t, useProps } from "@odoo/owl"; -import { registry } from "@web/core/registry"; -import { - SelectionField, - selectionField, - selectionFieldProps, -} from "@web/views/fields/selection/selection_field"; - -export class DaySelectionField extends SelectionField { - props = useProps({ - ...selectionFieldProps, - monthField: t.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; - } -} - -export const daySelectionField = { - ...selectionField, - component: DaySelectionField, - extractProps({ attrs }) { - return { - ...selectionField.extractProps(...arguments), - monthField: attrs.month_field, - }; - }, - fieldDependencies: ({ attrs }) => [ - { - name: attrs.month_field, - type: "selection", - }, - ], -}; - -registry.category("fields").add("day_selection", daySelectionField); diff --git a/addons/hr_holidays/static/src/components/month_selection/month_day_selection.js b/addons/hr_holidays/static/src/components/month_selection/month_day_selection.js new file mode 100644 index 00000000000000..902dee8e33c064 --- /dev/null +++ b/addons/hr_holidays/static/src/components/month_selection/month_day_selection.js @@ -0,0 +1,49 @@ +import { t, useProps } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { + SelectionField, + selectionField, + selectionFieldProps, +} from "@web/views/fields/selection/selection_field"; + +export class MonthDaySelectionField extends SelectionField { + props = useProps({ + ...selectionFieldProps, + month_field: t.string().optional(), + }); + + get #top() { + const monthValue = this.props.record.data[this.props.month_field] + // 2024 is a leap year + return new Date(2024, parseInt(monthValue ?? 0), 0).getDate(); + } + + /** + * @override + */ + get options() { + const options = super.options; + const top = this.#top; + + return options.filter(option => Number(option[1]) <= top); + } +} + +export const monthDaySelectionField = { + ...selectionField, + component: MonthDaySelectionField, + supportedOptions: [ + { + label: "Month", + name: "month_field", + type: "string", + }, + ], + extractProps({ options }) { + const props = selectionField.extractProps(...arguments); + props.month_field = options.month_field; + return props; + }, +}; + +registry.category("fields").add("month_day_selection", monthDaySelectionField); diff --git a/addons/hr_holidays/static/tests/tours/month_selection_for_february_tour.js b/addons/hr_holidays/static/tests/tours/month_selection_for_february_tour.js new file mode 100644 index 00000000000000..1a9031d3c3bd7a --- /dev/null +++ b/addons/hr_holidays/static/tests/tours/month_selection_for_february_tour.js @@ -0,0 +1,61 @@ +import { registry } from "@web/core/registry"; +import { stepUtils } from "@web_tour/tour_utils"; + +registry.category("web_tour.tours").add("month_selection_for_february_tour", { + steps: () => [ + stepUtils.showAppsMenuItem(), + { + content: "Open Time Off App", + trigger: ".o_app[data-menu-xmlid='hr_holidays.menu_hr_holidays_root']", + run: "click", + }, + { + content: "Open Configuration menu", + trigger: ".o-dropdown[data-menu-xmlid='hr_holidays.menu_hr_holidays_configuration']", + run: "click", + }, + { + content: "Go to Accurals", + trigger: ".o-dropdown-item[data-menu-xmlid='hr_holidays.hr_holidays_accrual_menu_configuration']", + run: "click", + }, + { + content: "Click 'New' Button", + trigger: ".o_list_button_add:contains('New')", + run: "click", + }, + { + content: "Click select a carryover month", + trigger: ".o_input[id='carryover_month_0']", + run: "click", + }, + { + content: "Click February", + trigger: ".o-dropdown-item[role='menuitem']:contains('February')", + run: "click", + }, + { + content: "Click select a carryover day", + trigger: ".o_input[id='carryover_day_0']", + run: "click", + }, + { + content: "Check if 29 is available", + trigger: ".o_select_menu_menu:has(.o_select_menu_item:contains(29))", + }, + { + content: "Check if 30 is not available", + trigger: ".o_select_menu_menu:not(:has(.o_select_menu_item:contains(30)))", + }, + { + content: "Select 29", + trigger: ".o_select_menu_menu:has(.o_select_menu_item:contains(29))", + run: "click", + }, + { + content: "Discard the form", + trigger: ".o_form_button_cancel", + run: "click", + }, + ] +}); \ No newline at end of file diff --git a/addons/hr_holidays/tests/__init__.py b/addons/hr_holidays/tests/__init__.py index 2131051075109c..0510faab056b51 100644 --- a/addons/hr_holidays/tests/__init__.py +++ b/addons/hr_holidays/tests/__init__.py @@ -38,3 +38,4 @@ from . import test_timeoff_overview_my_department_tour from . import test_hr_leave_report from . import test_member_of_department +from . import test_month_selection_for_february_tour diff --git a/addons/hr_holidays/tests/common.py b/addons/hr_holidays/tests/common.py index 4ebefb3e4bc8a7..5b074fb9b2502c 100644 --- a/addons/hr_holidays/tests/common.py +++ b/addons/hr_holidays/tests/common.py @@ -156,6 +156,16 @@ def _take_leave(self, employee, work_entry_type, date_from, date_to): }) return leave + def _allocate_leave(self, employee, work_entry_type, num_days, valid_from, valid_to=False): + return self.env['hr.leave.allocation'].sudo().create({ + 'name': 'Alloc', + 'employee_id': employee.id, + 'work_entry_type_id': work_entry_type.id, + 'number_of_days': num_days, + 'date_from': valid_from, + 'date_to': valid_to, + }) + def _create_form_test_accrual_allocation(self, work_entry_type, date_from, employee, accrual_plan, date_to=None, creator_user=None): allocation = self.env['hr.leave.allocation'] if creator_user: diff --git a/addons/hr_holidays/tests/test_hr_work_entry_type.py b/addons/hr_holidays/tests/test_hr_work_entry_type.py index aca98678c86190..ef9f8901ac5a91 100644 --- a/addons/hr_holidays/tests/test_hr_work_entry_type.py +++ b/addons/hr_holidays/tests/test_hr_work_entry_type.py @@ -232,3 +232,30 @@ def test_change_count_days_as(self): with self.assertRaises(ValidationError): work_entry_type.count_days_as = 'working' + + def _get_type(self, name, code): + return self.env['hr.work.entry.type'].sudo().create({ + 'name': name, + 'code': code, + 'requires_allocation': True, + 'count_days_as': 'working', + 'include_public_holidays_in_duration': False, + }) + + def test_search_virtual_remaining_leaves_works_with_gt(self): + self.env = self.env(user=self.employee_hruser.user_id) + emp = self.employee_hruser + + type1 = self._get_type('Type 1', 'T1') + type2 = self._get_type('Type 2', 'T2') + type3 = self._get_type('Type 3', 'T3') + + self._allocate_leave(emp, type1, 5, '2026-01-01').action_approve() + self._allocate_leave(emp, type2, 2, '2026-01-01').action_approve() + self._allocate_leave(emp, type3, 5, '2026-01-01').action_approve() + + searchedLeaves = self.env["hr.work.entry.type"].search([("virtual_remaining_leaves", ">", 20)]) + + self.assertIn(type1, searchedLeaves) + self.assertNotIn(type2, searchedLeaves) + self.assertIn(type3, searchedLeaves) diff --git a/addons/hr_holidays/tests/test_month_selection_for_february_tour.py b/addons/hr_holidays/tests/test_month_selection_for_february_tour.py new file mode 100644 index 00000000000000..5a989355933f5d --- /dev/null +++ b/addons/hr_holidays/tests/test_month_selection_for_february_tour.py @@ -0,0 +1,8 @@ +from odoo.tests import HttpCase, tagged + + +@tagged("post_install", "-at_install") +class TestMonthSelectionForFebruaryTour(HttpCase): + + def test_month_selection_for_february_tour(self): + self.start_tour("/odoo", "month_selection_for_february_tour", login="admin") diff --git a/addons/hr_holidays/views/hr_leave_accrual_views.xml b/addons/hr_holidays/views/hr_leave_accrual_views.xml index 3fe5368e17ac9a..f71bff5d39cd3e 100644 --- a/addons/hr_holidays/views/hr_leave_accrual_views.xml +++ b/addons/hr_holidays/views/hr_leave_accrual_views.xml @@ -36,17 +36,20 @@ on the - + of and the - + of on the - + of @@ -204,8 +207,8 @@ options="{'links': {'other': 'carryover_custom_date'}, 'observe': 'carryover'}"/> : the - + of diff --git a/addons/hr_skills/__manifest__.py b/addons/hr_skills/__manifest__.py index b696abb334f5d9..c0d52672c3dc32 100644 --- a/addons/hr_skills/__manifest__.py +++ b/addons/hr_skills/__manifest__.py @@ -22,6 +22,7 @@ 'data/ir_actions_server_data.xml', 'data/ir_cron_data.xml', 'data/report_paperformat.xml', + 'report/hr_employee_certification_report_views.xml', 'report/hr_employee_skill_history_report_views.xml', 'report/hr_employee_skill_report_views.xml', 'report/hr_employee_cv_report.xml', diff --git a/addons/hr_skills/report/__init__.py b/addons/hr_skills/report/__init__.py index 8e82b9a1d6f113..bad5c5e9967fe7 100644 --- a/addons/hr_skills/report/__init__.py +++ b/addons/hr_skills/report/__init__.py @@ -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 diff --git a/addons/hr_skills/report/hr_employee_certification_report.py b/addons/hr_skills/report/hr_employee_certification_report.py new file mode 100644 index 00000000000000..b70b034e5db953 --- /dev/null +++ b/addons/hr_skills/report/hr_employee_certification_report.py @@ -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) + }) diff --git a/addons/hr_skills/report/hr_employee_certification_report_views.xml b/addons/hr_skills/report/hr_employee_certification_report_views.xml new file mode 100644 index 00000000000000..079ef1f790fd91 --- /dev/null +++ b/addons/hr_skills/report/hr_employee_certification_report_views.xml @@ -0,0 +1,71 @@ + + + + hr.employee.certification.report + + + + + + + + + + + + hr.employee.certification.report + + + + + + + + + + + + + hr.employee.certification.report + + + + + + + + + + + + + + + + + + + + Certification + hr.employee.certification.report + + list,pivot + { + 'search_default_employee': 1, + } + + + + This report will give you an overview of the certification per Employee. + Create them in configuration and add them on the Employee. + + + + + + diff --git a/addons/hr_skills/report/hr_employee_skill_report_views.xml b/addons/hr_skills/report/hr_employee_skill_report_views.xml index 86b997da5ffbbe..bddca497eb6efa 100644 --- a/addons/hr_skills/report/hr_employee_skill_report_views.xml +++ b/addons/hr_skills/report/hr_employee_skill_report_views.xml @@ -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"/> diff --git a/addons/hr_skills/views/hr_views.xml b/addons/hr_skills/views/hr_views.xml index 618511b765c046..a59eff8f1fdd11 100644 --- a/addons/hr_skills/views/hr_views.xml +++ b/addons/hr_skills/views/hr_views.xml @@ -595,7 +595,7 @@ hr.employee.skill certifications [('is_certification', '=', True), ('company_id', 'in', allowed_company_ids)] - {'show_employee': True} + {'show_employee': True, 'search_default_group_by_type': 1} list,form + +
+
+ This report will give you an overview of the certification per Employee. + Create them in configuration and add them on the Employee. +