19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:31:00 +01:00
parent a1137a1456
commit e1d89e11e3
2789 changed files with 1093187 additions and 605897 deletions

View file

@ -1,3 +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

View file

@ -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)
})

View file

@ -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>

View file

@ -0,0 +1,29 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
from odoo import _, models
class ReportHr_SkillsReport_Employee_Cv(models.AbstractModel):
_name = 'report.hr_skills.report_employee_cv'
_description = 'Employee Resume'
def _get_report_values(self, docids, data=None):
show_others = (data or {}).get('show_others')
employees = self.env['hr.employee'].browse(docids)
resume_lines = {}
for employee in employees:
resume_lines[employee] = defaultdict(self.env['hr.resume.line'].browse)
for line in employee.resume_line_ids:
if not show_others and not line.line_type_id:
continue
resume_lines[employee][line.line_type_id.name or _('Other')] |= line
return {
'doc_ids': docids,
'doc_model': 'hr.employee',
'docs': employees,
'resume_lines': resume_lines,
}

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="action_report_employee_cv" model="ir.actions.report">
<field name="name">Employee Resume</field>
<field name="model">hr.employee</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">hr_skills.report_employee_cv</field>
<field name="report_file">hr_skills.report_employee_cv</field>
<field name="paperformat_id" ref="hr_skills.paperformat_resume"/>
<field name="print_report_name">'CV - %s' % (object.name)</field>
<field name="binding_model_id" eval="False"/> <!-- Don't display it under Print menu -->
<field name="binding_type">report</field>
</record>
</odoo>

View file

@ -0,0 +1,68 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, tools
class HrEmployeeSkillReport(models.BaseModel):
_name = 'hr.employee.skill.history.report'
_auto = False
_description = 'Employee Skills Report'
employee_id = fields.Many2one('hr.employee', readonly=True)
date = fields.Date()
skill_id = fields.Many2one('hr.skill', readonly=True)
skill_type_id = fields.Many2one('hr.skill.type', readonly=True)
level_progress = fields.Float(readonly=True)
def init(self):
tools.drop_view_if_exists(self.env.cr, self._table)
self.env.cr.execute("""
CREATE OR REPLACE VIEW %s AS (
WITH
individual_skill AS (
SELECT
valid_from,
valid_to,
employee_id,
skill_id
FROM hr_employee_skill
ORDER BY employee_id, skill_id, valid_from ASC
),
date_table AS (
SELECT
date,
employee_id
FROM (
SELECT
valid_from as date,
employee_id
FROM individual_skill AS start
UNION
SELECT
valid_to as date,
employee_id
FROM individual_skill AS stop
WHERE stop.valid_to IS NOT NULL
) AS date_table
ORDER BY date ASC
)
SELECT DISTINCT ON(date_table.date, emp_skill_level.employee_id, emp_skill_level.skill_id)
date_table.date AS date,
emp_skill_level.employee_id,
emp_skill_level.skill_id,
emp_skill_level.skill_type_id,
emp_skill_level.level_progress
FROM date_table
CROSS JOIN (
SELECT
emp_skill.*,
level.level_progress
FROM hr_employee_skill AS emp_skill
INNER JOIN hr_skill_level AS level
ON emp_skill.skill_level_id = level.id
) AS emp_skill_level
WHERE date_table.date >= emp_skill_level.valid_from AND date_table.employee_id = emp_skill_level.employee_id AND (emp_skill_level.valid_to IS NULL OR date_table.date <= emp_skill_level.valid_to)
ORDER BY date_table.date, emp_skill_level.employee_id, emp_skill_level.skill_id, emp_skill_level.valid_from DESC
)
""" % (self._table, ))

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hr_employee_skill_history_report_view_graph" model="ir.ui.view">
<field name="model">hr.employee.skill.history.report</field>
<field name="arch" type="xml">
<graph js_class="skills_graph" type="line" stacked="0" disable_linking="1">
<field name="date" interval="day" type="row"/>
<field name="skill_id" type="row"/>
<field name="level_progress" type="measure"/>
</graph>
</field>
</record>
<record id="hr_employee_skill_history_report_view_search" model="ir.ui.view">
<field name="model">hr.employee.skill.history.report</field>
<field name="arch" type="xml">
<search>
<field name="skill_id"/>
<field name="skill_type_id"/>
<separator/>
<filter string="Skill" name="group_by_skill_id" domain="[]" context="{'group_by': 'skill_id'}"/>
<filter string="Skill Type" name="group_by_skill_type_id" domain="[]"
context="{'group_by': 'skill_type_id'}"/>
<filter string="Date" name="group_by_date" domain="[]" context="{'group_by': 'date'}"/>
</search>
</field>
</record>
</odoo>

View file

@ -1,23 +1,24 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, tools
from odoo import api, fields, models, tools
class HrEmployeeSkillReport(models.BaseModel):
_auto = False
_name = 'hr.employee.skill.report'
_auto = False
_inherit = ["hr.manager.department.report"]
_description = 'Employee Skills Report'
_order = 'employee_id, level_progress desc'
id = fields.Id()
display_name = fields.Char(related='employee_id.name')
employee_id = fields.Many2one('hr.employee', readonly=True)
company_id = fields.Many2one('res.company', readonly=True)
department_id = fields.Many2one('hr.department', readonly=True)
job_id = fields.Many2one('hr.job', 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, group_operator='avg')
level_progress = fields.Float(readonly=True, aggregator='avg')
active = fields.Boolean(related='employee_id.active')
def init(self):
tools.drop_view_if_exists(self.env.cr, self._table)
@ -28,13 +29,25 @@ class HrEmployeeSkillReport(models.BaseModel):
row_number() OVER () AS id,
e.id AS employee_id,
e.company_id AS company_id,
e.department_id AS department_id,
v.department_id AS department_id,
v.job_id AS job_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
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 st.active IS True AND st.is_certification IS NOT TRUE AND s.valid_to IS NULL
)
""" % (self._table, ))
@api.model
def formatted_read_grouping_sets(self, domain, grouping_sets, aggregates=(), *, order=None):
# In the pivot view, we don't want the hierarchical naming of department_id (hr.department)
self_contexted = self.with_context(hierarchical_naming=False)
return super(HrEmployeeSkillReport, self_contexted).formatted_read_grouping_sets(
domain, grouping_sets, aggregates, order=order,
)

View file

@ -4,24 +4,37 @@
<field name="model">hr.employee.skill.report</field>
<field name="arch" type="xml">
<pivot disable_linking="True">
<field name="department_id" type="col"/>
<field name="employee_id" type="col"/>
<field name="skill_type_id" type="row"/>
<field name="skill_id" type="row"/>
<field name="level_progress" type="measure" widget="percentage" string="Current Level"/>
</pivot>
</field>
</record>
<record id="hr_employee_skill_report_view_graph" model="ir.ui.view">
<field name="model">hr.employee.skill.report</field>
<field name="arch" type="xml">
<graph>
<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"/>
</pivot>
<field name="level_progress" type="measure" widget="percentage" string="Current Level"/>
</graph>
</field>
</record>
<record id="hr_employee_skill_report_view_list" model="ir.ui.view">
<field name="model">hr.employee.skill.report</field>
<field name="arch" type="xml">
<tree expand="1">
<field name="employee_id"/>
<field name="skill_type_id"/>
<field name="skill_id"/>
<field name="skill_level"/>
<field name="level_progress" widget="percentage"/>
</tree>
<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}"/>
<field name="skill_id" options="{'no_create': True, 'no_open': True}"/>
<field name="skill_level" options="{'no_create': True, 'no_open': True}"/>
<field name="level_progress" widget="percentage" options="{'no_create': True, 'no_open': True}"/>
</list>
</field>
</record>
@ -34,11 +47,11 @@
<field name="skill_id"/>
<field name="skill_type_id"/>
<separator/>
<filter string="Employees with Skills" name="employees_with_skills" domain="[('skill_id', '!=', False)]"/>
<filter string="Employees without Skills" name="employees_without_skills" domain="[('skill_id', '=', False)]"/>
<filter string="Archived" 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'}"/>
<filter string="Jobs" name="jobs" context="{'group_by': 'job_id'}"/>
<separator/>
<filter string="Skill Type" name="skill_type" context="{'group_by': 'skill_type_id'}"/>
<filter string="Skill" name="skill" context="{'group_by': 'skill_id'}"/>
@ -47,20 +60,35 @@
</record>
<record id="hr_employee_skill_report_action" model="ir.actions.act_window">
<field name="name">Employee Skills</field>
<field name="name">Skills Inventory</field>
<field name="res_model">hr.employee.skill.report</field>
<field name="search_view_id" ref="hr_employee_skill_report_view_search"/>
<field name="view_mode">tree,pivot</field>
<field name="view_mode">list,pivot</field>
<field name="context">{
'search_default_employee': 1,
'search_default_employees_with_skills': 1,
'search_default_skill_type': 1,
'search_default_skill': 2,
}</field>
<field name="help" type="html">
<p class="o_view_nocontent_empty_folder">
</p><p>
This report will give you an overview of the skills per Employee.
Create them in configuration and add them on the Employee.
</p>
</field>
</record>
<record id="action_hr_employee_skill_log_department" model="ir.actions.act_window">
<field name="name">Skill History Report</field>
<field name="res_model">hr.employee.skill.report</field>
<field name="view_mode">graph,pivot,list</field>
<field name="context">{'fill_temporal': 0, 'search_default_group_by_skill_type_id': 1, 'search_default_group_by_skill_id': 2}</field>
<field name="target">current</field>
</record>
<menuitem
id="hr_employee_skill_report_menu"
name="Skills"
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>