[IMP] hr_skills: improve resume

- Replace "CV" with "Resume"
 - Allow to choose which information to show on the resume (Contact +
   Skills)
 - Use the company colors by default
 - Minor cosmetic changes on the template (reduced margins, better page
   break, etc.)

task-3198645

closes odoo/odoo#113218

Signed-off-by: Kevin Baptiste <kba@odoo.com>
This commit is contained in:
Kevin Baptiste
2023-02-28 20:39:11 +01:00
parent 76e0b18d50
commit 1aff45e35e
11 changed files with 138 additions and 70 deletions
+1
View File
@@ -23,6 +23,7 @@ This module introduces skills and resume management for employees.
'data/hr_resume_data.xml',
'data/hr_skill_data.xml',
'data/ir_actions_server_data.xml',
'data/report_paperformat.xml',
'report/hr_employee_skill_report_views.xml',
'report/hr_employee_cv_report.xml',
'views/hr_department_views.xml',
+10 -5
View File
@@ -4,13 +4,14 @@
import re
from odoo import _
from odoo.http import request, route, Controller, content_disposition
class HrEmployeeCV(Controller):
@route(["/print/cv"], type='http', auth='user')
def print_employee_cv(self, employee_ids='', color='#42a8c0', side_panel_position='right', **post):
def print_employee_cv(self, employee_ids='', color_primary='#666666', color_secondary='#666666', **post):
if not request.env.user.has_group('base.group_user') or not employee_ids or re.search("[^0-9|,]", employee_ids):
return request.not_found()
@@ -23,18 +24,22 @@ class HrEmployeeCV(Controller):
skill_type_language = request.env.ref('hr_skills.hr_skill_type_lang', raise_if_not_found=False)
report = request.env.ref('hr_skills.action_report_employee_cv', False)
pdf_content, dummy = request.env['ir.actions.report'].sudo()._render_qweb_pdf(
report, employees.ids, data={
'color': color,
'side_panel_position': side_panel_position,
'color_primary': color_primary,
'color_secondary': color_secondary,
'resume_type_education': resume_type_education,
'skill_type_language': skill_type_language,
'show_skills': 'show_skills' in post,
'show_contact': 'show_contact' in post,
'show_others': 'show_others' in post,
})
if len(employees) == 1:
report_name = _('CV %s', employees.name)
report_name = _('Resume %s', employees.name)
else:
report_name = _('CV Book')
report_name = _('Resumes')
pdfhttpheaders = [
('Content-Type', 'application/pdf'),
@@ -25,10 +25,11 @@ action['domain'] = [('department_id', '=', record.id)]
</record>
<record id="action_print_employees_cv" model="ir.actions.server">
<field name="name">Print CV</field>
<field name="name">Print Resume</field>
<field name="model_id" ref="hr.model_hr_employee"/>
<field name="binding_model_id" ref="hr.model_hr_employee"/>
<field name="binding_view_types">list,form</field>
<field name="binding_type">report</field>
<field name="state">code</field>
<field name="code">
action = env['ir.actions.act_window']._for_xml_id('hr_skills.action_hr_employee_cv_wizard')
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="paperformat_resume" model="report.paperformat">
<field name="name">Resume</field>
<field name="default" eval="True"/>
<field name="format">A4</field>
<field name="orientation">Portrait</field>
<field name="margin_top">12</field>
<field name="margin_bottom">12</field>
<field name="margin_left">5</field>
<field name="margin_right">5</field>
<field name="header_line" eval="False"/>
<field name="header_spacing">20</field>
<field name="dpi">90</field>
</record>
</odoo>
+1
View File
@@ -1,3 +1,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import hr_employee_cv_report
from . import hr_employee_skill_report
@@ -0,0 +1,28 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
from odoo import _, models
class EmployeeResumeReport(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,
}
@@ -1,11 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="action_report_employee_cv" model="ir.actions.report">
<field name="name">Employee CV</field>
<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>
@@ -23,8 +23,8 @@ div.o_employee_cv {
line-height: 1.5;
}
&:not(:first-child) .o_new_page {
page-break-before: always;
&:not(:last-child) .o_new_page {
page-break-after: always;
}
.o_sidebar {
@@ -42,6 +42,7 @@ div.o_employee_cv {
.o_profile {
padding: 30px;
padding-top: 15px;
background: rgba(0, 0, 0, 0.2);
text-align: center;
color: #fff;
@@ -183,10 +184,6 @@ div.o_employee_cv {
}
.o_main_panel_section:not(:last-of-type) {
margin-bottom: 60px;
}
.o_main_panel_resume_title {
position: relative;
overflow: hidden;
@@ -5,17 +5,10 @@
<t t-call="web.basic_layout">
<t t-foreach="docs" t-as="o">
<div class="o_employee_cv page">
<t t-call="hr_skills.report_employee_cv_company"/>
<t t-call="hr_skills.report_employee_cv_sidepanel"/>
<t t-call="hr_skills.report_employee_cv_main_panel"/>
<p class="o_new_page"/>
<t t-if="side_panel_position == 'right'">
<t t-call="hr_skills.report_employee_cv_company"/>
<t t-call="hr_skills.report_employee_cv_sidepanel"/>
<t t-call="hr_skills.report_employee_cv_main_panel"/>
</t>
<t t-else="">
<t t-call="hr_skills.report_employee_cv_sidepanel"/>
<t t-call="hr_skills.report_employee_cv_company"/>
<t t-call="hr_skills.report_employee_cv_main_panel"/>
</t>
</div>
</t>
</t>
@@ -23,24 +16,26 @@
<template id="report_employee_cv_company">
<div class="o_company row">
<div class="col-12 mb-4 mt-5">
<div class="col-12 mb-4">
<img t-if="o.company_id.logo" t-att-src="image_data_uri(o.company_id.logo)" style="max-height: 45px;" alt="Logo"/>
</div>
<div class="col-12 mb-4" style="font-weight: bold;">
<t t-esc="o.company_id.partner_id.name"/>
</div>
<div class="col-12" t-field="o.company_id.partner_id" t-options='{"widget": "contact", "fields": ["address"], "no_marker": True}'/>
</div>
</template>
<template id="report_employee_cv_sidepanel">
<div class="o_sidebar" t-attf-style="
background: #{color};
background-color: #{color};">
background: #{color_primary};
background-color: #{color_primary};">
<div class="o_profile">
<img class="img img-fluid rounded-circle o_profile_image" t-attf-src="data:image/png;base64,#{o.image_128}" alt=""/>
<img class="img img-fluid rounded-circle o_profile_image" t-attf-src="data:image/png;base64,#{o.image_128}" alt="" t-if="o.image_128"/>
<h1 class="o_profile_name mt-2" t-field="o.name"/>
<h3 class="o_profile_job mb-2" t-field="o.job_id"/>
</div>
<div class="o_sidebar_section">
<div class="o_sidebar_section" t-if="show_contact">
<ul class="o_social">
<li class="email" t-if="o.company_id.email">
<i class="fa fa-solid fa-envelope pe-2"/>
@@ -60,42 +55,40 @@
</template>
<template id="report_employee_cv_main_panel">
<div class="o_main_panel">
<div class="o_main_panel_section">
<t t-foreach="o.resume_line_ids.line_type_id" t-as="line_type">
<h2 class="o_main_panel_title mt-4" t-attf-style="color: #{color};">
<span class="o_main_panel_icon" t-attf-style="background: #{color};">
<t t-foreach="resume_lines[o]" t-as="line_type">
<div class="o_main_panel">
<div class="o_main_panel_section">
<h2 class="o_main_panel_title mt-4" t-attf-style="color: #{color_secondary};">
<span class="o_main_panel_icon" t-attf-style="background: #{color_secondary};">
<i class="fa fa-solid fa-briefcase"/>
</span>
<t t-esc="line_type.name"/>
<t t-esc="line_type"/>
</h2>
<t t-foreach="o.resume_line_ids" t-as="resume_line">
<t t-if="resume_line.line_type_id == line_type">
<div class="o_main_panel_resume_title ps-5">
<div class="o_main_panel_resume_title_job">
<h3 class="o_main_panel_resume_job" t-field="resume_line.name"/>
</div>
<div class="o_main_panel_resume_title_dates">
<t t-set="present">Present</t>
<div class="o_main_panel_resume_year">
<t t-esc="resume_line.date_start.year"/> - <t t-esc="resume_line.date_end.year if resume_line.date_end else present"/>
</div>
<t t-foreach="resume_lines[o][line_type]" t-as="resume_line">
<div class="o_main_panel_resume_title ps-5">
<div class="o_main_panel_resume_title_job">
<h3 class="o_main_panel_resume_job" t-field="resume_line.name"/>
</div>
<div class="o_main_panel_resume_title_dates">
<t t-set="present">Present</t>
<div class="o_main_panel_resume_year">
<t t-esc="resume_line.date_start.year"/> - <t t-esc="resume_line.date_end.year if resume_line.date_end else present"/>
</div>
</div>
<div class="o_main_panel_resume_description ps-5">
<p t-field="resume_line.description"/>
</div>
</t>
</div>
<div class="o_main_panel_resume_description ps-5">
<p t-field="resume_line.description"/>
</div>
</t>
</t>
</div>
</div>
</div>
</t>
<t t-set="has_languages" t-value="skill_type_language and any(skill_line.skill_type_id == skill_type_language for skill_line in o.employee_skill_ids)"/>
<div class="o_main_panel mt-4" t-if="has_languages">
<div class="o_main_panel_section" t-if="skill_type_language">
<h2 class="o_main_panel_title" t-attf-style="color: #{color};">
<span class="o_main_panel_icon" t-attf-style="background: #{color};">
<h2 class="o_main_panel_title" t-attf-style="color: #{color_secondary};">
<span class="o_main_panel_icon" t-attf-style="background: #{color_secondary};">
<i class="fa fa-solid fa-language"/>
</span>
Languages
@@ -112,11 +105,11 @@
</div>
</div>
<t t-set="has_skills" t-value="any(skill_line.skill_type_id != skill_type_language for skill_line in o.employee_skill_ids)"/>
<t t-set="has_skills" t-value="show_skills and any(skill_line.skill_type_id != skill_type_language for skill_line in o.employee_skill_ids)"/>
<div class="o_main_panel mt-4" t-if="has_skills">
<div class="o_main_panel_section">
<h2 class="o_main_panel_title" t-attf-style="color: #{color};">
<span class="o_main_panel_icon" t-attf-style="background: #{color};">
<h2 class="o_main_panel_title" t-attf-style="color: #{color_secondary};">
<span class="o_main_panel_icon" t-attf-style="background: #{color_secondary};">
<i class="fa fa-solid fa-rocket"/>
</span>Skills
</h2>
@@ -131,11 +124,11 @@
<div
class="o_main_panel_skill_progress_bar_color"
role="progressbar"
t-attf-style="width: #{skill_line.level_progress}%; background: #{color};"
t-attf-style="width: #{skill_line.level_progress}%; background: #{color_primary};"
t-att-aria-valuenow="skill_line.level_progress"
aria-valuemin="0"
aria-valuemax="100"/>
</div>
</div>
</div>
</t>
</div>
@@ -3,25 +3,42 @@
from werkzeug.urls import url_encode
from odoo import fields, models
from odoo import _, api, fields, models
class HrEmployeeCVWizard(models.TransientModel):
_name = 'hr.employee.cv.wizard'
_description = 'Print CV'
_description = 'Print Resume'
employee_ids = fields.Many2many('hr.employee')
color = fields.Char('Color Theme', default="#3a9bc8", required=True)
side_panel_position = fields.Selection([('left', 'Left'), ('right', 'Right')], default='right', required=True)
color_primary = fields.Char('Primary Color', default=lambda self: self.env.company.primary_color or "#666666", required=True)
color_secondary = fields.Char('Secondary Color', default=lambda self: self.env.company.secondary_color or "#666666", required=True)
show_skills = fields.Boolean(string='Skills', default=True)
show_contact = fields.Boolean(string='Contact Information', default=True)
show_others = fields.Boolean(string='Others', default=True)
can_show_others = fields.Boolean(compute='_compute_can_show_others')
can_show_skills = fields.Boolean(compute='_compute_can_show_others')
@api.depends('employee_ids')
def _compute_can_show_others(self):
for wizard in self:
wizard.can_show_others = wizard.employee_ids.resume_line_ids.filtered(lambda l: not l.line_type_id)
wizard.can_show_skills = wizard.employee_ids.skill_ids
def action_validate(self):
self.ensure_one()
return {
'name': 'Print CV',
'name': _('Print Resume'),
'type': 'ir.actions.act_url',
'url': '/print/cv?' + url_encode({
'employee_ids': ','.join(str(x) for x in self.employee_ids.ids),
'color': self.color,
'side_panel_position': self.side_panel_position,
'color_primary': self.color_primary,
'color_secondary': self.color_secondary,
'show_skills': 1 if self.show_skills else None,
'show_contact': 1 if self.show_contact else None,
'show_others': 1 if self.show_others else None,
})
}
@@ -4,12 +4,20 @@
<field name="name">hr.employee.cv.wizard.view.form</field>
<field name="model">hr.employee.cv.wizard</field>
<field name="arch" type="xml">
<form string="Print CV">
<form string="Print Resume">
<field name="can_show_others" invisible="1"/>
<field name="can_show_skills" invisible="1"/>
<group>
<group>
<field name="employee_ids" widget="many2many_tags" invisible="1"/>
<field name="color" widget="color"/>
<field name="side_panel_position"/>
<label for="color_primary" string="Colors"/>
<div class="d-flex flex-row">
<field name="color_primary" widget="color" nolabel="1" style="width: 35px;"/>
<field name="color_secondary" widget="color" nolabel="1"/>
</div>
<field name="show_contact"/>
<field name="show_others" attrs="{'invisible': [('can_show_others', '=', False)]}"/>
<field name="show_skills" attrs="{'invisible': [('can_show_skills', '=', False)]}"/>
</group>
</group>
<footer>
@@ -21,7 +29,7 @@
</record>
<record id="action_hr_employee_cv_wizard" model="ir.actions.act_window">
<field name="name">Print CV</field>
<field name="name">Print Resume</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">hr.employee.cv.wizard</field>
<field name="view_mode">form</field>