Initial commit: Hr packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:50 +02:00
commit 62531cd146
2820 changed files with 1432848 additions and 0 deletions

View file

@ -0,0 +1,40 @@
/** @odoo-module */
import { registry } from "@web/core/registry";
import { formatDate } from "@web/core/l10n/dates";
import { SkillsX2ManyField } from "./skills_one2many";
import { CommonSkillsListRenderer } from "../views/skills_list_renderer";
export class ResumeListRenderer extends CommonSkillsListRenderer {
get groupBy() {
return 'line_type_id';
}
get colspan() {
if (this.props.activeActions) {
return 3;
}
return 2;
}
formatDate(date) {
return formatDate(date);
}
setDefaultColumnWidths() {}
}
ResumeListRenderer.template = 'hr_skills.ResumeListRenderer';
ResumeListRenderer.rowsTemplate = "hr_skills.ResumeListRenderer.Rows";
ResumeListRenderer.recordRowTemplate = "hr_skills.ResumeListRenderer.RecordRow";
export class ResumeX2ManyField extends SkillsX2ManyField {}
ResumeX2ManyField.components = {
...SkillsX2ManyField.components,
ListRenderer: ResumeListRenderer,
};
registry.category("fields")
.add("resume_one2many", ResumeX2ManyField);

View file

@ -0,0 +1,50 @@
.o_field_resume_one2many {
$o-hrs-timeline-entry-padding: .5rem;
$o-hrs-timeline-dot-size: .6rem;
.o_data_row {
border-bottom: none;
}
.o_data_row td {
padding: $o-hrs-timeline-entry-padding;
&.o_resume_timeline_cell {
div {
width: $o-hrs-timeline-dot-size;
height: $o-hrs-timeline-dot-size;
}
&:before {
@include o-position-absolute(0, $left: ($o-hrs-timeline-dot-size * .5 + $o-hrs-timeline-entry-padding));
width: 1px;
height: 100%;
margin-left: 2.1rem;
background-color: $border-color;
content: "";
}
}
}
.o_resume_line_title, .o_resume_line_desc {
white-space: normal;
}
.o_resume_line_title, .o_resume_line_dates {
line-height: 1;
}
.o_resume_group_header + .o_data_row .o_resume_timeline_cell:before {
top: $o-hrs-timeline-entry-padding;
}
.o_data_row.o_data_row_last {
.o_resume_line_desc {
margin-bottom: $headings-margin-bottom;
}
.o_resume_timeline_cell:before {
height: $o-hrs-timeline-entry-padding;
}
}
}

View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<t t-name="hr_skills.ResumeListRenderer" owl="1" t-inherit-mode="primary" t-inherit="hr_skills.SkillsListRenderer">
<xpath expr="//table" position="attributes">
<attribute name="t-attf-class" add="table-borderless {{ !showTable ? 'd-none' : ''}}" remove="table-striped" separator=" "/>
</xpath>
<xpath expr="//thead/tr" position="replace">
<tr>
<th style="width: 32px; min-width: 32px;"></th>
<th class="w-100"></th>
<th t-if="isEditable" class="o_list_actions_header" style="width: 32px; min-width: 32px"></th>
</tr>
</xpath>
</t>
<t t-name="hr_skills.ResumeListRenderer.Rows" owl="1" t-inherit-mode="primary" t-inherit="hr_skills.SkillsListRenderer.Rows">
<xpath expr="//tr" position="attributes">
<attribute name="class" add="o_resume_group_header" separator=" "/>
</xpath>
<xpath expr="//th[hasclass('o_group_name')]" position="after">
<th></th>
</xpath>
</t>
<t t-name="hr_skills.ResumeListRenderer.RecordRow" owl="1" t-inherit-mode="primary" t-inherit="web.ListRenderer.RecordRow">
<xpath expr="//t[@t-foreach='getColumns(record)']" position="replace">
<t t-set="data" t-value="record.data"/>
<t t-if="data.display_type === 'classic'" id='row'>
<td class="o_resume_timeline_cell position-relative pe-lg-2" id='hiii'>
<div class="rounded-circle bg-info position-relative"/>
</td>
<td class="o_data_cell pt-0" t-on-click="(ev) => this.onCellClicked(record, null, ev)">
<div t-attf-class="o_resume_line {{data.display_type == 'certification' ? 'o_resume_line_display_certification' : ''}}" t-att-data-id="id">
<small class="o_resume_line_dates fw-bold">
<t t-out="formatDate(data.date_start)"/> -
<t t-if="data.date_end" t-out="formatDate(data.date_end)"/>
<t t-else="">Current</t>
</small>
<h4 class="o_resume_line_title mt-2" t-esc="data.name"/>
<p t-if="data.description" class="o_resume_line_desc" t-out="data.description" t-ref="link-target-blank"/>
</div>
</td>
</t>
</xpath>
</t>
</odoo>

View file

@ -0,0 +1,44 @@
/** @odoo-module */
import { X2ManyField } from "@web/views/fields/x2many/x2many_field";
import { registry } from "@web/core/registry";
import { CommonSkillsListRenderer } from "../views/skills_list_renderer";
export class SkillsListRenderer extends CommonSkillsListRenderer {
get groupBy() {
return 'skill_type_id';
}
calculateColumnWidth(column) {
if (column.name != 'skill_level_id') {
return {
type: 'absolute',
value: '90px',
}
}
return super.calculateColumnWidth(column);
}
}
SkillsListRenderer.template = 'hr_skills.SkillsListRenderer';
export class SkillsX2ManyField extends X2ManyField {
async onAdd({ context, editable } = {}) {
const employeeId = this.props.record.resId;
return super.onAdd({
editable,
context: {
...context,
default_employee_id: employeeId,
}
});
}
}
SkillsX2ManyField.components = {
...X2ManyField.components,
ListRenderer: SkillsListRenderer,
};
registry.category("fields").add("skills_one2many", SkillsX2ManyField);

View file

@ -0,0 +1,23 @@
.o_field_skills_one2many, .o_field_resume_one2many {
.o_progress {
background: $gray-300;
border: 0;
height: 5px;
}
.o_progressbar_value {
font-size: $font-size-sm;
font-weight: bold;
}
}
table.o_skill_table, .o_hr_skills_dialog_form {
.o_progressbar {
display: flex;
align-items: center;
.o_progressbar_value input {
width: auto;
}
}
}

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<t t-name="hr_skills.SkillsListRenderer" owl="1" t-inherit-mode="primary" t-inherit="web.ListRenderer">
<xpath expr="//table" position="attributes">
<attribute name="t-attf-class" add="mb-1 {{ !isEditable ? 'cursor-default' : '' }} {{ !showTable ? 'd-none' : ''}} o_skill_table" separator=" "/>
</xpath>
<xpath expr="//thead" position="attributes">
<attribute name="style">visibility: collapse;</attribute>
</xpath>
<xpath expr="//table" position="after">
<t t-if="!showTable">
<button t-on-click="props.onAdd" class="btn btn-secondary ms-4 mt-3" role="button" t-if="isEditable">
Create a new entry
</button>
</t>
</xpath>
</t>
<t t-name="hr_skills.SkillsListRenderer.Rows" owl="1">
<t t-foreach="Object.entries(groupedList)" t-as="skill_group" t-key="skill_group[0]">
<tr class="o_group_has_content o_group_header">
<th tabindex="-1" class="o_group_name" t-att-colspan="colspan">
<div class="d-flex justify-content-between align-items-center">
<span t-esc="skill_group[1].name"/>
<button class="btn btn-secondary btn-sm"
t-if="isEditable"
t-on-click="() => props.onAdd({ context: { default_skill_type_id: skill_group[1].id }})"
role="button">ADD</button>
</div>
</th>
</tr>
<t t-foreach="skill_group[1].list.records" t-as="record" t-key="record.id">
<t t-set="group" t-value="skill_group[1]"/>
<t t-call="{{ constructor.recordRowTemplate }}"/>
</t>
</t>
</t>
</odoo>

View file

@ -0,0 +1,12 @@
.o_hr_skills_group {
padding: 0 calc(var(--gutter-x) * 1)!important;
&:first-child {
padding-left: calc(var(--gutter-x) * .5)!important;
}
&:last-child {
padding-right: calc(var(--gutter-x) * .5)!important;
}
.o_list_renderer {
overflow-x: hidden!important;
}
}

View file

@ -0,0 +1,24 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { GraphRenderer } from "@web/views/graph/graph_renderer";
import { graphView } from "@web/views/graph/graph_view";
export class SkillsGraphRenderer extends GraphRenderer {
getScaleOptions() {
const scaleOptions = super.getScaleOptions();
if ('yAxes' in scaleOptions) {
scaleOptions['yAxes'][0]['ticks']['suggestedMax'] = 100;
}
return scaleOptions;
}
}
export const skillsGraphView = {
...graphView,
Renderer: SkillsGraphRenderer,
};
registry.category("views").add("skills_graph", skillsGraphView);

View file

@ -0,0 +1,57 @@
/** @odoo-module */
import { ListRenderer } from "@web/views/list/list_renderer";
export class CommonSkillsListRenderer extends ListRenderer {
get colspan() {
const span = this.allColumns.length;
if (this.isEditable) {
return span + 1;
}
return span;
}
get groupBy() {
return '';
}
get groupedList() {
const grouped = {};
for (const record of this.list.records) {
const data = record.data;
const group = data[this.groupBy];
if (grouped[group[1]] === undefined) {
grouped[group[1]] = {
id: parseInt(group[0]),
name: group[1] || this.env._t('Other'),
list: {
records: [],
},
};
}
grouped[group[1]].list.records.push(record);
}
return grouped;
}
get showTable() {
return this.props.list.records.length;
}
get isEditable() {
return this.props.editable !== false;
}
async onCellClicked(record, column, ev) {
if (!this.isEditable) {
return;
}
return await super.onCellClicked(record, column, ev);
}
}
CommonSkillsListRenderer.rowsTemplate = "hr_skills.SkillsListRenderer.Rows";

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="hr_resume_data_row">
<tr class="o_data_row cursor-default" t-attf-class="o_data_row #{is_last? 'o_data_row_last' : ''}" t-att-data-id="id">
<t t-if="data.display_type === 'classic'">
<td class="o_resume_timeline_cell position-relative pe-lg-2">
<div class="rounded-circle bg-info position-relative"/>
</td>
<td class="o_data_cell pt-0 w-100">
<div class="o_resume_line" t-att-data-id="id">
<small class="o_resume_line_dates">
<b t-esc="data.date_start"/> - <b t-esc="data.date_end"/>
</small>
<h4 class="o_resume_line_title mt-2" t-esc="data.name"/>
<p t-if="data.description" class="o_resume_line_desc" t-esc="data.description"/>
</div>
</td>
</t>
</tr>
</t>
<t t-name="hr_trash_button">
<td class="o_list_record_remove pe-3">
<button name="delete" arial-label="Delete row" class="btn btn-link text-danger">
<i class="fa fa-trash"/>
</button>
</td>
</t>
<t t-name="hr_resume_group_row">
<tr class="o_resume_group_header">
<td class="o_group_name" colspan="100%"><span class="o_horizontal_separator my-0" t-esc="display_name"/></td>
</tr>
</t>
<t t-name="group_add_item">
<t t-set="empty" t-value="Object.keys(context).length == 2"/>
<div t-attf-class="o_field_x2many_list_row_add #{empty? 'd-block w-100' : 'd-inline float-end'}">
<a href="#"
role="button"
t-attf-class="btn btn-secondary o-kanban-button-new #{empty? 'btn-primary mt-3' : 'btn-secondary btn-sm'}"
t-attf-data-context="{{ context }}">
<t t-if="empty">CREATE A NEW ENTRY</t>
<t t-else="">ADD</t>
</a>
</div>
</t>
</templates>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="hr_skill_data_row">
<tr class="o_data_row cursor-default" t-att-data-id="id">
<td class="o_data_cell o_skill_cell w-100">
<t t-esc="data.skill_id.data.display_name"/>
</td>
<td class="o_data_cell o_skill_cell pe-3">
<t t-esc="data.skill_level_id.data.display_name"/>
</td>
</tr>
</t>
<t t-name="hr_default_group_row">
<tr class="o_group_header o_group_has_content">
<td class="o_group_name border-0 pe-2" colspan="99">
<b t-esc="display_name"/>
</td>
</tr>
</t>
</templates>