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

@ -0,0 +1,53 @@
import { Component, markup } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { STATIC_ACTIONS_GROUP_NUMBER } from "@web/search/action_menus/action_menus";
const cogMenuRegistry = registry.category("cogMenu");
/**
* 'Search Matching Applicants' menu
*
* This component is used to search matching skills among the all applicants
* It's only available in the kanban and list view of the applicants.
* @extends Component
*/
export class SearchJobApplicant extends Component {
static template = "hr_recruitment_skills.SearchJobApplicant";
static components = { DropdownItem };
static props = {};
setup() {
this.action = useService("action");
}
//---------------------------------------------------------------------
// Protected
//---------------------------------------------------------------------
async openMatchingJobApplicants() {
const { globalContext } = this.env.searchModel;
const action = await this.env.services.orm.call(
"hr.job",
"action_search_matching_applicants",
[globalContext.active_id]
);
action.help = markup(action.help);
return this.action.doAction(action);
}
}
export const searchJobApplicant = {
Component: SearchJobApplicant,
groupNumber: STATIC_ACTIONS_GROUP_NUMBER,
isDisplayed: ({ config, searchModel }) => {
return (
searchModel.resModel === "hr.applicant" &&
searchModel.globalContext.allow_search_matching_applicants &&
config.viewArch.classList.contains('o_search_matching_applicant')
);
},
};
cogMenuRegistry.add("search-job-applicants-menu", searchJobApplicant, { sequence: 11 });

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<templates id="template" xml:space="preserve">
<t t-name="hr_recruitment_skills.SearchJobApplicant">
<DropdownItem onSelected.bind="openMatchingJobApplicants">
<i class="fa fa-fw fa-search me-1" aria-hidden="true"></i>Search Matching Applicants
</DropdownItem>
</t>
</templates>

View file

@ -0,0 +1,94 @@
import { getCSSVariableValue } from "@html_editor/utils/formatting";
import { onWillStart } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { formatFloat } from "@web/views/fields/formatters";
import { GaugeField, gaugeField } from "@web/views/fields/gauge/gauge_field";
export class SkillMatchGaugeField extends GaugeField {
static template = "hr_recruitment.SkillMatchGaugeField";
setup() {
super.setup();
this.orm = useService("orm");
onWillStart(async () => {
const matching_job_id = this.props.record.evalContext.context.matching_job_id;
if (matching_job_id) {
const matching_job = await this.orm.read("hr.job", [matching_job_id], ["name"]);
this.matching_job_name = matching_job[0].name;
}
});
}
get formattedValue() {
return formatFloat(this.props.record.data[this.props.name], {
humanReadable: true,
decimals: 0,
});
}
renderChart() {
const gaugeValue = this.props.record.data[this.props.name];
let maxValue = this.props.maxValueField
? this.props.record.data[this.props.maxValueField]
: this.props.maxValue;
const fgColor =
gaugeValue > maxValue
? getCSSVariableValue("success", getComputedStyle(document.documentElement))
: getCSSVariableValue("primary", getComputedStyle(document.documentElement));
maxValue = Math.max(gaugeValue, maxValue);
if (gaugeValue === 0 && maxValue === 0) {
maxValue = 1;
}
const config = {
type: "doughnut",
data: {
datasets: [
{
data: [gaugeValue, maxValue - gaugeValue],
backgroundColor: [fgColor, "#dddddd"],
label: this.title,
},
],
},
options: {
circumference: 180,
rotation: 270,
responsive: true,
maintainAspectRatio: false,
cutout: "50%",
interaction: {
intersect: false,
mode: "dataset",
},
plugins: {
title: {
display: false,
},
tooltip: {
displayColors: false,
callbacks: {
title: (tooltipItem) => false,
label: (tooltipItem) =>
tooltipItem.dataIndex === 0 &&
_t("This score reflects skills and degree match"),
},
},
},
aspectRatio: 2,
},
};
this.chart = new Chart(this.canvasRef.el, config);
}
}
export const skillMatchGaugeField = {
...gaugeField,
component: SkillMatchGaugeField,
};
registry.category("fields").add("skill_match_gauge_field", skillMatchGaugeField);

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="hr_recruitment.SkillMatchGaugeField">
<div class="oe_gauge position-relative pt-3">
<canvas t-ref="canvas"/>
<span class="o_gauge_value position-absolute start-0 end-0 bottom-0 text-center fs-2 fw-bolder">
<t t-esc="this.formattedValue"/>%
</span>
</div>
<p class="text-center text-uppercase fw-bolder mt-2 mb-0">
Job Position Matching
</p>
<p class="text-center text-uppercase fw-bolder mt-0 mb-2"
t-if="this.matching_job_name">
<t t-esc="this.matching_job_name"/>
</p>
</t>
</templates>

View file

@ -0,0 +1,5 @@
.o_applicant_skills .skills_header {
/* Margin and padding classes are added to .skills_header to work in both HR and Recruitment contexts. */
/* However, the combined spacing is too much for either scenario, so one is removed depending on context. */
margin: 0 !important;
}