Merge pull request 'recruitment changes' (#29) from feature/share_module into srivyn_test

Reviewed-on: https://gitea.ftprotech.in/administrator/odoo18/pulls/29
This commit is contained in:
pranay 2026-07-29 20:06:50 +05:30
commit a4c3f40b8d
4 changed files with 112 additions and 612 deletions

View File

@ -161,11 +161,11 @@ class HRJobRecruitment(models.Model):
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company, tracking=True, exportable=False) company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company, tracking=True, exportable=False)
contract_type_id = fields.Many2one('hr.contract.type', string='Employment Type', tracking=True) contract_type_id = fields.Many2one('hr.contract.type', string='Employment Type', tracking=True)
user_id = fields.Many2one('res.users', "Recruiter", user_id = fields.Many2one('res.users', "Recruiter",
domain="[('share', '=', False), ('company_ids', 'in', company_id)]", domain=lambda self:[('share', '=', False), ('company_ids', 'in', self.env.company.id),('groups_id', 'in', self.env.ref('hr_recruitment.group_hr_recruitment_user').id)],
default=lambda self: self.env.user, default=lambda self: self.env.user,
tracking=True, help="The Recruiter will be the default value for all Applicants in this job \ tracking=True, help="The Recruiter will be the default value for all Applicants in this job \
position. The Recruiter is automatically added to all meetings with the Applicant.") position. The Recruiter is automatically added to all meetings with the Applicant.")
interviewer_ids = fields.Many2many('res.users', string='Interviewers', domain="[('share', '=', False), ('company_ids', 'in', company_id)]", tracking=True, help="The Interviewers set on the job position can see all Applicants in it. They have access to the information, the attachments, the meeting management and they can refuse him. You don't need to have Recruitment rights to be set as an interviewer.") interviewer_ids = fields.Many2many('res.users', string='Interviewers', domain=lambda self:[('share', '=', False), ('company_ids', 'in', self.env.company.id),('groups_id', 'in', self.env.ref('hr_recruitment.group_hr_recruitment_user').id)], tracking=True, help="The Interviewers set on the job position can see all Applicants in it. They have access to the information, the attachments, the meeting management and they can refuse him. You don't need to have Recruitment rights to be set as an interviewer.")
skill_ids = fields.Many2many('hr.skill','hr_job_recruitment_hr_primary_skill_rel','job_id', 'user_id', string="Primary Skills", tracking=True) skill_ids = fields.Many2many('hr.skill','hr_job_recruitment_hr_primary_skill_rel','job_id', 'user_id', string="Primary Skills", tracking=True)
address_id = fields.Many2one( address_id = fields.Many2one(
'res.partner', "Job Location", default=_default_address_id, 'res.partner', "Job Location", default=_default_address_id,

View File

@ -1,537 +1,3 @@
# # Part of Odoo. See LICENSE file for full copyright and licensing details.
#
# import warnings
# from datetime import datetime
# from dateutil.relativedelta import relativedelta
# from operator import itemgetter
# from werkzeug.urls import url_encode
#
# from odoo import http, _
# from odoo.addons.website_hr_recruitment.controllers.main import WebsiteHrRecruitment
# from odoo.osv.expression import AND
# from odoo.http import request
# from odoo.tools import email_normalize
# from odoo.tools.misc import groupby
# import ast
# import base64
#
#
#
# class WebsiteJobHrRecruitment(WebsiteHrRecruitment):
# _jobs_per_page = 12
#
# def sitemap_jobs(env, rule, qs):
# if not qs or qs.lower() in '/jobs':
# yield {'loc': '/jobs'}
#
# @http.route([
# '/jobs',
# '/jobs/page/<int:page>',
# ], type='http', auth="public", website=True, sitemap=sitemap_jobs)
# def jobs(self, country_id=None, department_id=None, office_id=None, contract_type_id=None,
# is_remote=False, is_other_department=False, is_untyped=None, page=1, search=None, **kwargs):
# env = request.env(context=dict(request.env.context, show_address=True, no_tag_br=True))
#
# Country = env['res.country']
# Jobs = env['hr.job.recruitment']
# Department = env['hr.department']
#
# country = Country.browse(int(country_id)) if country_id else None
# department = Department.browse(int(department_id)) if department_id else None
# office_id = int(office_id) if office_id else None
# contract_type_id = int(contract_type_id) if contract_type_id else None
#
# # Default search by user country
# if not (country or department or office_id or contract_type_id or kwargs.get('all_countries')):
# if request.geoip.country_code:
# countries_ = Country.search([('code', '=', request.geoip.country_code)])
# country = countries_[0] if countries_ else None
# if country:
# country_count = Jobs.search_count(AND([
# request.website.website_domain(),
# [('address_id.country_id', '=', country.id)]
# ]))
# if not country_count:
# country = False
#
# options = {
# 'displayDescription': True,
# 'allowFuzzy': not request.params.get('noFuzzy'),
# 'country_id': country.id if country else None,
# 'department_id': department.id if department else None,
# 'office_id': office_id,
# 'contract_type_id': contract_type_id,
# 'is_remote': is_remote,
# 'is_other_department': is_other_department,
# 'is_untyped': is_untyped,
# }
# total, details, fuzzy_search_term = request.website._search_with_fuzzy("job_requests", search,
# limit=1000, order="is_published desc, sequence, no_of_recruitment desc", options=options)
# # Browse jobs as superuser, because address is restricted
# jobs = details[0].get('results', Jobs).sudo()
#
# def sort(records_list, field_name):
# """ Sort records in the given collection according to the given
# field name, alphabetically. None values instead of records are
# placed at the end.
#
# :param list records_list: collection of records or None values
# :param str field_name: field on which to sort
# :return: sorted list
# """
# return sorted(
# records_list,
# key=lambda item: (item is None, item.sudo()[field_name] if item and item.sudo()[field_name] else ''),
# )
#
# # Countries
# if country or is_remote:
# cross_country_options = options.copy()
# cross_country_options.update({
# 'allowFuzzy': False,
# 'country_id': None,
# 'is_remote': False,
# })
# cross_country_total, cross_country_details, _ = request.website._search_with_fuzzy("jobs",
# fuzzy_search_term or search, limit=1000, order="is_published desc, sequence, no_of_recruitment desc",
# options=cross_country_options)
# # Browse jobs as superuser, because address is restricted
# cross_country_jobs = cross_country_details[1].get('results', Jobs).sudo()
# else:
# cross_country_total = total
# cross_country_jobs = jobs
# country_offices = set(j.address_id or None for j in cross_country_jobs)
# countries = sort(set(o and o.country_id or None for o in country_offices), 'name')
# count_per_country = {'all': cross_country_total}
# for c, jobs_list in groupby(cross_country_jobs, lambda job: job.address_id.country_id):
# count_per_country[c] = len(jobs_list)
# count_remote = len(cross_country_jobs.filtered(lambda job: not job.address_id))
# if count_remote:
# count_per_country[None] = count_remote
#
# # Departments
# if department or is_other_department:
# cross_department_options = options.copy()
# cross_department_options.update({
# 'allowFuzzy': False,
# 'department_id': None,
# 'is_other_department': False,
# })
# cross_department_total, cross_department_details, _ = request.website._search_with_fuzzy("jobs",
# fuzzy_search_term or search, limit=1000, order="is_published desc, sequence, no_of_recruitment desc",
# options=cross_department_options)
# cross_department_jobs = cross_department_details[1].get('results', Jobs)
# else:
# cross_department_total = total
# cross_department_jobs = jobs
# departments = sort(set(j.department_id or None for j in cross_department_jobs), 'name')
# count_per_department = {'all': cross_department_total}
# for d, jobs_list in groupby(cross_department_jobs, lambda job: job.department_id):
# count_per_department[d] = len(jobs_list)
# count_other_department = len(cross_department_jobs.filtered(lambda job: not job.department_id))
# if count_other_department:
# count_per_department[None] = count_other_department
#
# # Offices
# if office_id or is_remote:
# cross_office_options = options.copy()
# cross_office_options.update({
# 'allowFuzzy': False,
# 'office_id': None,
# 'is_remote': False,
# })
# cross_office_total, cross_office_details, _ = request.website._search_with_fuzzy("jobs",
# fuzzy_search_term or search, limit=1000, order="is_published desc, sequence, no_of_recruitment desc",
# options=cross_office_options)
# # Browse jobs as superuser, because address is restricted
# cross_office_jobs = cross_office_details[1].get('results', Jobs).sudo()
# else:
# cross_office_total = total
# cross_office_jobs = jobs
# offices = sort(set(j.address_id or None for j in cross_office_jobs), 'city')
# count_per_office = {'all': cross_office_total}
# for o, jobs_list in groupby(cross_office_jobs, lambda job: job.address_id):
# count_per_office[o] = len(jobs_list)
# count_remote = len(cross_office_jobs.filtered(lambda job: not job.address_id))
# if count_remote:
# count_per_office[None] = count_remote
#
# # Employment types
# if contract_type_id or is_untyped:
# cross_type_options = options.copy()
# cross_type_options.update({
# 'allowFuzzy': False,
# 'contract_type_id': None,
# 'is_untyped': False,
# })
# cross_type_total, cross_type_details, _ = request.website._search_with_fuzzy("jobs",
# fuzzy_search_term or search, limit=1000, order="is_published desc, sequence, no_of_recruitment desc",
# options=cross_type_options)
# cross_type_jobs = cross_type_details[1].get('results', Jobs)
# else:
# cross_type_total = total
# cross_type_jobs = jobs
# employment_types = sort(set(j.contract_type_id for j in jobs if j.contract_type_id), 'name')
# count_per_employment_type = {'all': cross_type_total}
# for t, jobs_list in groupby(cross_type_jobs, lambda job: job.contract_type_id):
# count_per_employment_type[t] = len(jobs_list)
# count_untyped = len(cross_type_jobs.filtered(lambda job: not job.contract_type_id))
# if count_untyped:
# count_per_employment_type[None] = count_untyped
#
# pager = request.website.pager(
# url=request.httprequest.path.partition('/page/')[0],
# url_args=request.httprequest.args,
# total=total,
# page=page,
# step=self._jobs_per_page,
# )
# offset = pager['offset']
# jobs = jobs[offset:offset + self._jobs_per_page]
#
# office = env['res.partner'].browse(int(office_id)) if office_id else None
# contract_type = env['hr.contract.type'].browse(int(contract_type_id)) if contract_type_id else None
# # Render page
# return request.render("website_hr_recruitment_extended.recruitment_index", {
# 'jobs': jobs,
# 'countries': countries,
# 'departments': departments,
# 'offices': offices,
# 'employment_types': employment_types,
# 'country_id': country,
# 'department_id': department,
# 'office_id': office,
# 'contract_type_id': contract_type,
# 'is_remote': is_remote,
# 'is_other_department': is_other_department,
# 'is_untyped': is_untyped,
# 'pager': pager,
# 'search': fuzzy_search_term or search,
# 'search_count': total,
# 'original_search': fuzzy_search_term and search,
# 'count_per_country': count_per_country,
# 'count_per_department': count_per_department,
# 'count_per_office': count_per_office,
# 'count_per_employment_type': count_per_employment_type,
# })
#
# @http.route('/jobs/add', type='json', auth="user", website=True)
# def jobs_add(self, **kwargs):
# # avoid branding of website_description by setting rendering_bundle in context
# job = request.env['hr.job.recruitment'].with_context(rendering_bundle=True).create({
# 'name': _('Job Title'),
# })
# return f"/jobs/{request.env['ir.http']._slug(job)}"
#
# @http.route('''/jobs/detail/<model("hr.job.recruitment"):job>''', type='http', auth="public", website=True, sitemap=True)
# def jobs_detail(self, job, **kwargs):
# redirect_url = f"/jobs/{request.env['ir.http']._slug(job)}"
# return request.redirect(redirect_url, code=301)
#
# @http.route('''/jobs/<model("hr.job.recruitment"):job>''', type='http', auth="public", website=True, sitemap=True)
# def job(self, job, **kwargs):
# return request.render("website_hr_recruitment_extended.recruitment_detail", {
# 'job': job,
# 'main_object': job,
# })
#
# @http.route('''/jobs/apply/<model("hr.job.recruitment"):job>''', type='http', auth="public", website=True, sitemap=True)
# def jobs_apply(self, job, **kwargs):
# error = {}
# default = {}
# if 'website_hr_recruitment_error' in request.session:
# error = request.session.pop('website_hr_recruitment_error')
# default = request.session.pop('website_hr_recruitment_default')
# return request.render("website_hr_recruitment_extended.recruitment_apply", {
# 'job': job,
# 'error': error,
# 'default': default,
# })
#
# # Compatibility routes
#
# @http.route([
# '/jobs/country/<model("res.country"):country>',
# '/jobs/department/<model("hr.department"):department>',
# '/jobs/country/<model("res.country"):country>/department/<model("hr.department"):department>',
# '/jobs/office/<int:office_id>',
# '/jobs/country/<model("res.country"):country>/office/<int:office_id>',
# '/jobs/department/<model("hr.department"):department>/office/<int:office_id>',
# '/jobs/country/<model("res.country"):country>/department/<model("hr.department"):department>/office/<int:office_id>',
# '/jobs/employment_type/<int:contract_type_id>',
# '/jobs/country/<model("res.country"):country>/employment_type/<int:contract_type_id>',
# '/jobs/department/<model("hr.department"):department>/employment_type/<int:contract_type_id>',
# '/jobs/office/<int:office_id>/employment_type/<int:contract_type_id>',
# '/jobs/country/<model("res.country"):country>/department/<model("hr.department"):department>/employment_type/<int:contract_type_id>',
# '/jobs/country/<model("res.country"):country>/office/<int:office_id>/employment_type/<int:contract_type_id>',
# '/jobs/department/<model("hr.department"):department>/office/<int:office_id>/employment_type/<int:contract_type_id>',
# '/jobs/country/<model("res.country"):country>/department/<model("hr.department"):department>/office/<int:office_id>/employment_type/<int:contract_type_id>',
# ], type='http', auth="public", website=True, sitemap=False)
# def jobs_compatibility(self, country=None, department=None, office_id=None, contract_type_id=None, **kwargs):
# """
# Deprecated since Odoo 16.3: those routes are kept by compatibility.
# They should not be used in Odoo code anymore.
# """
# warnings.warn(
# "This route is deprecated since Odoo 16.3: the jobs list is now available at /jobs or /jobs/page/XXX",
# DeprecationWarning
# )
# url_params = {
# 'country_id': country and country.id,
# 'department_id': department and department.id,
# 'office_id': office_id,
# 'contract_type_id': contract_type_id,
# **kwargs,
# }
# return request.redirect(
# '/jobs?%s' % url_encode(url_params),
# code=301,
# )
#
#
# @http.route('/hr_recruitment_extended/fetch_hr_recruitment_degree', type='json', auth="public", website=True)
# def fetch_recruitment_degrees(self):
# degrees = {}
# all_degrees = http.request.env['hr.recruitment.degree'].sudo().search([])
# if all_degrees:
# for degree in all_degrees:
# degrees[degree.id] = degree.name
# return degrees
#
# @http.route('/hr_recruitment_extended/fetch_preferred_locations', type='json', auth="public", website=True)
# def fetch_preferred_locations(self, loc_ids):
# locations = {}
# for id in loc_ids:
# location = http.request.env['hr.location'].sudo().browse(id)
# if location:
# locations[location.id] = location.location_name
# return locations
#
# @http.route('/hr_recruitment_extended/fetch_preferred_skills', type='json', auth="public", website=True)
# def fetch_preferred_skills(self, skill_ids,fetch_others=False):
# skills = {}
# Skill = http.request.env['hr.skill'].sudo()
# SkillLevel = http.request.env['hr.skill.level'].sudo()
#
# if fetch_others:
# for skill in Skill.search([('id','not in',skill_ids)]):
# levels = SkillLevel.search([('skill_type_id', '=', skill.skill_type_id.id)],order='sequence')
# skills[skill.id] = {
# 'id': skill.id,
# 'name': skill.name,
# 'levels': [{'id': lvl.id, 'name': lvl.name, 'percentage': lvl.level_progress, 'sequence': lvl.sequence} for lvl in levels]
# }
# else:
# for skill in Skill.browse(skill_ids):
# levels = SkillLevel.search([('skill_type_id', '=', skill.skill_type_id.id)],order='sequence')
# skills[skill.id] = {
# 'id': skill.id,
# 'name': skill.name,
# 'levels': [{'id': lvl.id, 'name': lvl.name, 'percentage': lvl.level_progress, 'sequence': lvl.sequence} for lvl in levels]
# }
#
# return skills
#
# @http.route('/website_hr_recruitment_extended/check_recent_application', type='json', auth="public", website=True)
# def check_recent_application(self, value, job_id):
# # Function to check if the applicant has an existing record based on email, phone, or linkedin
# if value:
# def refused_applicants_condition(applicant):
# return not applicant.active \
# and applicant.hr_job_recruitment.id == int(job_id) \
# and applicant.create_date >= (datetime.now() - relativedelta(months=6))
# # Search for applicants with the same email, phone, or linkedin (only if the value is not False/None)
# applicants_with_similar_info = http.request.env['hr.applicant'].sudo().search([
# ('hr_job_recruitment','=',int(job_id)),
# '|',
# ('email_normalized', '=', email_normalize(value)),
# '|',
# ('partner_phone', '=', value),
# ('linkedin_profile', '=ilike', value),
# ], order='create_date DESC')
#
# if not applicants_with_similar_info:
# return {'message':None}
# # Group applications by their status
# applications_by_status = applicants_with_similar_info.grouped('application_status')
#
# # Check for refused applicants with the same value within the last 6 months
# refused_applicants = applications_by_status.get('refused', http.request.env['hr.applicant'])
# if any(applicant for applicant in refused_applicants if refused_applicants_condition(applicant)):
# return {
# 'message': _(
# 'We\'ve found a previous closed application in our system within the last 6 months.'
# ' Please consider before applying in order not to duplicate efforts.'
# )
# }
#
# # Check for ongoing applications with the same value
# ongoing_applications = applications_by_status.get('ongoing', [])
# if ongoing_applications:
# ongoing_application = ongoing_applications[0]
# if ongoing_application.hr_job_recruitment.id == int(job_id):
# recruiter_contact = "" if not ongoing_application.user_id else _(
# ' In case of issue, contact %(contact_infos)s',
# contact_infos=", ".join(
# [value for value in itemgetter('name', 'email', 'phone')(ongoing_application.user_id) if value]
# ))
#
# error_message = 'An application already exists for %s Duplicates might be rejected. %s '%(value,recruiter_contact)
# print(error_message)
# return {
# 'message': _(error_message)
# }
#
# # If no existing application found, show the following message
# return {
# 'message': _(
# 'We found a recent application with a similar name, email, phone number.'
# ' You can continue if it\'s not a mistake.'
# )
# }
# else:
# return {'message': None}
#
# def _should_log_authenticate_message(self, record):
# if record._name == "hr.applicant" and not request.session.uid:
# return False
# return super()._should_log_authenticate_message(record)
#
# def extract_data(self, model, values):
# candidate = False
# extracted_resume = values.pop('resume_base64', None)
# current_ctc = values.pop('current_ctc', None)
# expected_ctc = values.pop('expected_ctc', None)
# available_joining_date = values.pop('available_joining_date', None)
# exp_type = values.pop('exp_type', None)
# current_location = values.pop('current_location', None)
# preferred_locations_str = values.pop('preferred_locations', '')
# department_id = values.pop('department_id',None)
# hr_job_recruitment = values.pop('job_id', None)
# preferred_locations = [int(x) for x in preferred_locations_str.split(',')] if len(
# preferred_locations_str) > 0 else []
# current_organization = values.pop('current_organization', None)
# notice_period = values.pop('notice_period', 0)
# notice_period_type = values.pop('notice_period_type', 'day')
# experience_years = values.pop('experience_years',0)
# experience_months = values.pop('experience_months',0)
#
# # If there are months, convert everything to months
# if int(experience_months) > 0:
# total_experience = (int(experience_years) * 12) + int(experience_months)
# total_experience_type = 'month'
# else:
# total_experience = int(experience_years)
# total_experience_type = 'year'
#
# if extracted_resume:
# attachment = request.env.ref("hr_recruitment_extended.employee_recruitment_attachments_preview")
# file = attachment.sudo().write({
# 'datas': extracted_resume,
# })
# if file:
# resume_type = attachment.mimetype
# resume_name = attachment.name
# else:
# resume_type = ''
# resume_name = ''
# else:
# resume_type = ''
# resume_name = ''
#
# skill_dict = {key: ast.literal_eval(value) for key,value in values.items() if "skill" in key and value != '0'}
#
# if model.model == 'hr.applicant':
# partner_name = values.pop('full_name', None)
# partner_phone = values.pop('partner_phone', None)
# alternate_phone = values.pop('alternate_phone', None)
# partner_email = values.pop('email_from', None)
# degree = values.pop('degree', None)
# if partner_phone and partner_email:
# candidate = request.env['hr.candidate'].sudo().search([
# '|', ('email_from', '=', partner_email),
# ('partner_phone', '=', partner_phone),
# ], limit=1)
#
# if candidate:
# candidate.sudo().write({
# 'partner_name': partner_name,
# 'alternate_phone': alternate_phone,
# 'email_from': partner_email,
# 'partner_phone': partner_phone,
# 'type_id': int(degree) if degree.isdigit() else False,
# 'resume': extracted_resume,
# 'resume_type': resume_type,
# 'resume_name': resume_name,
# })
# if not candidate:
# candidate = request.env['hr.candidate'].sudo().create({
# 'partner_name': partner_name,
# 'email_from': partner_email,
# 'partner_phone': partner_phone,
# 'alternate_phone': alternate_phone,
# 'type_id': int(degree) if degree.isdigit() else False,
# 'resume': extracted_resume,
# 'resume_type': resume_type,
# 'resume_name': resume_name,
# })
#
# if len(skill_dict) > 0:
# # candidate_skills_list = []
# for key, value in skill_dict.items():
# candidate_skills = dict()
# skill_type_id = request.env['hr.skill'].sudo().browse(int(key.split("_")[1])).skill_type_id.id
# candidate_skills['candidate_id'] = candidate.id
# candidate_skills['skill_id'] = int(key.split("_")[1])
# candidate_skills['skill_level_id'] = value[0]
# candidate_skills['level_progress'] = value[1]
# candidate_skills['skill_type_id'] = skill_type_id
# # skill = request.env['hr.candidate.skill'].sudo().create(candidate_skills)
# # candidate_skills_list.append(skill.id)
# if candidate.candidate_skill_ids:
# if candidate_skills['skill_id'] not in candidate.candidate_skill_ids.skill_id.ids:
# candidate.write({'candidate_skill_ids':[(0,4,candidate_skills)]})
# else:
# candidate.write({'candidate_skill_ids':[(0,4,candidate_skills)]})
#
#
# else:
# skills = None
#
# values['partner_name'] = partner_name
# if partner_phone:
# values['partner_phone'] = partner_phone
# if partner_email:
# values['email_from'] = partner_email
# notice_period_str = 'N/A'
# if notice_period and notice_period_type:
# notice_period_str = str(notice_period) + ' ' + str(notice_period_type)
# data = super().extract_data(model, values)
# data['record']['current_ctc'] = float(current_ctc if current_ctc else 0)
# data['record']['salary_expected'] = float(expected_ctc if expected_ctc else 0)
# data['record']['exp_type'] = exp_type if exp_type else 'fresher'
# data['record']['current_location'] = current_location if current_location else ''
# data['record']['current_organization'] = current_organization if current_organization else ''
# data['record']['notice_period'] = notice_period_str if notice_period_str else 'N/A'
# data['record']['notice_period_type'] = notice_period_type if notice_period_type else 'day'
# data['record']['hr_job_recruitment'] = int(hr_job_recruitment) if str(hr_job_recruitment).isdigit() else ''
# data['record']['department_id'] = int(department_id) if str(department_id).isdigit() else ''
# data['record']['availability'] = datetime.strptime(available_joining_date, '%Y-%m-%d').date() if available_joining_date else ''
#
# data['record']['total_exp'] = total_experience if total_experience else 0
# data['record']['total_exp_type'] = total_experience_type if total_experience_type else 'year'
# # data['record']['resume'] = resume if resume else None
# if len(preferred_locations_str) > 0:
# data['record']['preferred_location'] = preferred_locations
# if candidate:
# data['record']['candidate_id'] = candidate.id
# data['record']['type_id'] = candidate.type_id.id
#
# return data
#
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import warnings import warnings
from datetime import datetime from datetime import datetime
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
@ -606,13 +72,20 @@ class WebsiteJobHrRecruitment(WebsiteHrRecruitment):
search = search.strip() search = search.strip()
custom_jobs = Jobs.sudo().search([ custom_jobs_domain = [
'|', '|', '|', '|', '|', '|',
('name', 'ilike', search), ('name', 'ilike', search),
('address_id.city', 'ilike', search), ('address_id.city', 'ilike', search),
('skill_ids.name', 'ilike', search), ('skill_ids.name', 'ilike', search),
('secondary_skill_ids.name', 'ilike', search), ('secondary_skill_ids.name', 'ilike', search),
]) ]
if not request.env.user.has_group('hr_recruitment.group_hr_recruitment_user'):
custom_jobs_domain = AND([
request.website.website_domain(),
[('is_published', '=', True)],
custom_jobs_domain,
])
custom_jobs = Jobs.sudo().search(custom_jobs_domain)
# Merge fuzzy jobs + custom jobs # Merge fuzzy jobs + custom jobs
jobs = (jobs | custom_jobs).sorted( jobs = (jobs | custom_jobs).sorted(
@ -650,11 +123,11 @@ class WebsiteJobHrRecruitment(WebsiteHrRecruitment):
'country_id': None, 'country_id': None,
'is_remote': False, 'is_remote': False,
}) })
cross_country_total, cross_country_details, _ = request.website._search_with_fuzzy("jobs", cross_country_total, cross_country_details, _ = request.website._search_with_fuzzy("job_requests",
fuzzy_search_term or search, limit=1000, order="is_published desc, sequence, no_of_recruitment desc", fuzzy_search_term or search, limit=1000, order="is_published desc, sequence, no_of_recruitment desc",
options=cross_country_options) options=cross_country_options)
# Browse jobs as superuser, because address is restricted # Browse jobs as superuser, because address is restricted
cross_country_jobs = cross_country_details[1].get('results', Jobs).sudo() cross_country_jobs = cross_country_details[0].get('results', Jobs).sudo()
else: else:
cross_country_total = total cross_country_total = total
cross_country_jobs = jobs cross_country_jobs = jobs
@ -675,10 +148,10 @@ class WebsiteJobHrRecruitment(WebsiteHrRecruitment):
'department_id': None, 'department_id': None,
'is_other_department': False, 'is_other_department': False,
}) })
cross_department_total, cross_department_details, _ = request.website._search_with_fuzzy("jobs", cross_department_total, cross_department_details, _ = request.website._search_with_fuzzy("job_requests",
fuzzy_search_term or search, limit=1000, order="is_published desc, sequence, no_of_recruitment desc", fuzzy_search_term or search, limit=1000, order="is_published desc, sequence, no_of_recruitment desc",
options=cross_department_options) options=cross_department_options)
cross_department_jobs = cross_department_details[1].get('results', Jobs) cross_department_jobs = cross_department_details[0].get('results', Jobs)
else: else:
cross_department_total = total cross_department_total = total
cross_department_jobs = jobs cross_department_jobs = jobs
@ -698,11 +171,11 @@ class WebsiteJobHrRecruitment(WebsiteHrRecruitment):
'office_id': None, 'office_id': None,
'is_remote': False, 'is_remote': False,
}) })
cross_office_total, cross_office_details, _ = request.website._search_with_fuzzy("jobs", cross_office_total, cross_office_details, _ = request.website._search_with_fuzzy("job_requests",
fuzzy_search_term or search, limit=1000, order="is_published desc, sequence, no_of_recruitment desc", fuzzy_search_term or search, limit=1000, order="is_published desc, sequence, no_of_recruitment desc",
options=cross_office_options) options=cross_office_options)
# Browse jobs as superuser, because address is restricted # Browse jobs as superuser, because address is restricted
cross_office_jobs = cross_office_details[1].get('results', Jobs).sudo() cross_office_jobs = cross_office_details[0].get('results', Jobs).sudo()
else: else:
cross_office_total = total cross_office_total = total
cross_office_jobs = jobs cross_office_jobs = jobs
@ -722,10 +195,10 @@ class WebsiteJobHrRecruitment(WebsiteHrRecruitment):
'contract_type_id': None, 'contract_type_id': None,
'is_untyped': False, 'is_untyped': False,
}) })
cross_type_total, cross_type_details, _ = request.website._search_with_fuzzy("jobs", cross_type_total, cross_type_details, _ = request.website._search_with_fuzzy("job_requests",
fuzzy_search_term or search, limit=1000, order="is_published desc, sequence, no_of_recruitment desc", fuzzy_search_term or search, limit=1000, order="is_published desc, sequence, no_of_recruitment desc",
options=cross_type_options) options=cross_type_options)
cross_type_jobs = cross_type_details[1].get('results', Jobs) cross_type_jobs = cross_type_details[0].get('results', Jobs)
else: else:
cross_type_total = total cross_type_total = total
cross_type_jobs = jobs cross_type_jobs = jobs
@ -781,20 +254,44 @@ class WebsiteJobHrRecruitment(WebsiteHrRecruitment):
}) })
return f"/jobs/{request.env['ir.http']._slug(job)}" return f"/jobs/{request.env['ir.http']._slug(job)}"
@http.route('''/jobs/detail/<model("hr.job.recruitment"):job>''', type='http', auth="public", website=True, sitemap=True) def _get_website_job_from_slug(self, job_slug):
def jobs_detail(self, job, **kwargs): _, job_id = request.env['ir.http']._unslug(job_slug)
if not job_id:
return request.env['hr.job.recruitment'].sudo()
job = request.env['hr.job.recruitment'].sudo().browse(job_id).exists()
if not job:
return job
if request.env.user.has_group('hr_recruitment.group_hr_recruitment_user'):
return job
if not job.is_published:
return request.env['hr.job.recruitment'].sudo()
if job.website_id and job.website_id != request.website:
return request.env['hr.job.recruitment'].sudo()
return job
@http.route('''/jobs/detail/<string:job_slug>''', type='http', auth="public", website=True, sitemap=True)
def jobs_detail(self, job_slug, **kwargs):
job = self._get_website_job_from_slug(job_slug)
if not job:
return request.not_found()
redirect_url = f"/jobs/{request.env['ir.http']._slug(job)}" redirect_url = f"/jobs/{request.env['ir.http']._slug(job)}"
return request.redirect(redirect_url, code=301) return request.redirect(redirect_url, code=301)
@http.route('''/jobs/<model("hr.job.recruitment"):job>''', type='http', auth="public", website=True, sitemap=True) @http.route('''/jobs/<string:job_slug>''', type='http', auth="public", website=True, sitemap=True)
def job(self, job, **kwargs): def job(self, job_slug, **kwargs):
job = self._get_website_job_from_slug(job_slug)
if not job:
return request.not_found()
return request.render("website_hr_recruitment_extended.recruitment_detail", { return request.render("website_hr_recruitment_extended.recruitment_detail", {
'job': job, 'job': job,
'main_object': job, 'main_object': job,
}) })
@http.route('''/jobs/apply/<model("hr.job.recruitment"):job>''', type='http', auth="public", website=True, sitemap=True) @http.route('''/jobs/apply/<string:job_slug>''', type='http', auth="public", website=True, sitemap=True)
def jobs_apply(self, job, **kwargs): def jobs_apply(self, job_slug, **kwargs):
job = self._get_website_job_from_slug(job_slug)
if not job:
return request.not_found()
error = {} error = {}
default = {} default = {}
if 'website_hr_recruitment_error' in request.session: if 'website_hr_recruitment_error' in request.session:

View File

@ -36,7 +36,6 @@ class JobRecruitment(models.Model):
prefetch=False, prefetch=False,
sanitize_overridable=True, sanitize_overridable=True,
sanitize_attributes=False, sanitize_form=False) sanitize_attributes=False, sanitize_form=False)
website_published = fields.Boolean(help='Set if the application is published on the website of the company.', tracking=True)
website_description = fields.Html( website_description = fields.Html(
'Website description', translate=html_translate, 'Website description', translate=html_translate,
default=_get_default_website_description, prefetch=False, default=_get_default_website_description, prefetch=False,
@ -86,7 +85,7 @@ class JobRecruitment(models.Model):
@api.model @api.model
def _search_get_detail(self, website, order, options): def _search_get_detail(self, website, order, options):
requires_sudo = False requires_sudo = True
with_description = options['displayDescription'] with_description = options['displayDescription']
country_id = options.get('country_id') country_id = options.get('country_id')
department_id = options.get('department_id') department_id = options.get('department_id')
@ -97,9 +96,10 @@ class JobRecruitment(models.Model):
is_untyped = options.get('is_untyped') is_untyped = options.get('is_untyped')
domain = [website.website_domain()] domain = [website.website_domain()]
if not self.env.user.has_group('hr_recruitment.group_hr_recruitment_user'):
domain.append([('is_published', '=', True)])
if country_id: if country_id:
domain.append([('address_id.country_id', '=', int(country_id))]) domain.append([('address_id.country_id', '=', int(country_id))])
requires_sudo = True
if department_id: if department_id:
domain.append([('department_id', '=', int(department_id))]) domain.append([('department_id', '=', int(department_id))])
elif is_other_department: elif is_other_department:
@ -113,15 +113,11 @@ class JobRecruitment(models.Model):
elif is_untyped: elif is_untyped:
domain.append([('contract_type_id', '=', None)]) domain.append([('contract_type_id', '=', None)])
if requires_sudo and not self.env.user.has_group('hr_recruitment.group_hr_recruitment_user'): search_fields = ['recruitment_sequence', 'name']
# Rule must be reinforced because of sudo. fetch_fields = ['recruitment_sequence', 'name', 'website_url']
domain.append([('website_published', '=', True)])
search_fields = ['recruitment_sequence']
fetch_fields = ['recruitment_sequence', 'website_url']
mapping = { mapping = {
'recruitment_sequence': {'name': 'recruitment_sequence', 'type': 'text', 'match': True}, 'recruitment_sequence': {'name': 'recruitment_sequence', 'type': 'text', 'match': True},
'name': {'name': 'name', 'type': 'text', 'match': True},
'website_url': {'name': 'website_url', 'type': 'text', 'truncate': False}, 'website_url': {'name': 'website_url', 'type': 'text', 'truncate': False},
} }
# if with_description: # if with_description:

View File

@ -1,10 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<odoo> <odoo>
<data noupdate="1"> <function name="write" model="ir.model.data">
<function name="search" model="ir.model.data">
<value eval="[('module', '=', 'website_hr_recruitment_extended'), ('name', 'in', ['hr_job_recruitment_public', 'hr_job_recruitment_portal', 'hr_job_recruitment_officer'])]"/>
</function>
<value eval="{'noupdate': False}"/>
</function>
<data>
<record id="hr_job_recruitment_public" model="ir.rule"> <record id="hr_job_recruitment_public" model="ir.rule">
<field name="name">Job Recruitment Positions: Public</field> <field name="name">Job Recruitment Positions: Public</field>
<field name="model_id" ref="hr_recruitment_extended.model_hr_job_recruitment"/> <field name="model_id" ref="hr_recruitment_extended.model_hr_job_recruitment"/>
<field name="domain_force">[('website_published', '=', True)]</field> <field name="domain_force">[('is_published', '=', True)]</field>
<field name="groups" eval="[(4, ref('base.group_public'))]"/> <field name="groups" eval="[(4, ref('base.group_public'))]"/>
<field name="perm_read" eval="True"/> <field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/> <field name="perm_write" eval="False"/>
@ -14,7 +21,7 @@
<record id="hr_job_recruitment_portal" model="ir.rule"> <record id="hr_job_recruitment_portal" model="ir.rule">
<field name="name">Job Recruitment Positions: Portal</field> <field name="name">Job Recruitment Positions: Portal</field>
<field name="model_id" ref="hr_recruitment_extended.model_hr_job_recruitment"/> <field name="model_id" ref="hr_recruitment_extended.model_hr_job_recruitment"/>
<field name="domain_force">[('website_published', '=', True)]</field> <field name="domain_force">[('is_published', '=', True)]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/> <field name="groups" eval="[(4, ref('base.group_portal'))]"/>
<field name="perm_read" eval="True"/> <field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/> <field name="perm_write" eval="False"/>