FIX: Leaves bug
This commit is contained in:
parent
ac90760034
commit
89295a4e5a
|
|
@ -0,0 +1,4 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import controllers
|
||||||
|
from . import models
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': "HR Recruitment Extended",
|
||||||
|
|
||||||
|
'summary': "Extention of HR Recruitment Module",
|
||||||
|
|
||||||
|
'description': """
|
||||||
|
Extention of HR Recruitment MOdule
|
||||||
|
""",
|
||||||
|
|
||||||
|
'author': "Pranay",
|
||||||
|
'website': "https://www.ftprotech.com",
|
||||||
|
|
||||||
|
# Categories can be used to filter modules in modules listing
|
||||||
|
# Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml
|
||||||
|
# for the full list
|
||||||
|
'category': 'Human Resources/Recruitment',
|
||||||
|
'version': '0.1',
|
||||||
|
|
||||||
|
# any module necessary for this one to work correctly
|
||||||
|
'depends': ['hr_recruitment','hr','hr_recruitment_skills','website_hr_recruitment','requisitions'],
|
||||||
|
|
||||||
|
# always loaded
|
||||||
|
'data': [
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
'data/cron.xml',
|
||||||
|
'data/mail_template.xml',
|
||||||
|
'views/hr_recruitment.xml',
|
||||||
|
'views/hr_location.xml',
|
||||||
|
'views/website_hr_recruitment_application_templates.xml',
|
||||||
|
'views/stages.xml',
|
||||||
|
],
|
||||||
|
'assets': {
|
||||||
|
'web.assets_frontend': [
|
||||||
|
'hr_recruitment_extended/static/src/js/website_hr_applicant_form.js',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import controllers
|
||||||
|
|
@ -0,0 +1,167 @@
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class WebsiteRecruitmentApplication(WebsiteHrRecruitment):
|
||||||
|
|
||||||
|
@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('/website_hr_recruitment/check_recent_application', type='json', auth="public", website=True)
|
||||||
|
def check_recent_application(self, field, value, job_id):
|
||||||
|
def refused_applicants_condition(applicant):
|
||||||
|
return not applicant.active \
|
||||||
|
and applicant.job_id.id == int(job_id) \
|
||||||
|
and applicant.create_date >= (datetime.now() - relativedelta(months=6))
|
||||||
|
|
||||||
|
field_domain = {
|
||||||
|
'name': [('partner_name', '=ilike', value)],
|
||||||
|
'email': [('email_normalized', '=', email_normalize(value))],
|
||||||
|
'phone': [('partner_phone', '=', value)],
|
||||||
|
'linkedin': [('linkedin_profile', '=ilike', value)],
|
||||||
|
}.get(field, [])
|
||||||
|
|
||||||
|
applications_by_status = http.request.env['hr.applicant'].sudo().search(AND([
|
||||||
|
field_domain,
|
||||||
|
[
|
||||||
|
('job_id.website_id', 'in', [http.request.website.id, False]),
|
||||||
|
'|',
|
||||||
|
('application_status', '=', 'ongoing'),
|
||||||
|
'&',
|
||||||
|
('application_status', '=', 'refused'),
|
||||||
|
('active', '=', False),
|
||||||
|
]
|
||||||
|
]), order='create_date DESC').grouped('application_status')
|
||||||
|
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.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if 'ongoing' not in applications_by_status:
|
||||||
|
return {'message': None}
|
||||||
|
|
||||||
|
ongoing_application = applications_by_status.get('ongoing')[0]
|
||||||
|
if ongoing_application.job_id.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]
|
||||||
|
))
|
||||||
|
return {
|
||||||
|
'message': _(
|
||||||
|
'An application already exists for %(value)s.'
|
||||||
|
' Duplicates might be rejected. %(recruiter_contact)s',
|
||||||
|
value=value,
|
||||||
|
recruiter_contact=recruiter_contact
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'message': _(
|
||||||
|
'We found a recent application with a similar name, email, phone number.'
|
||||||
|
' You can continue if it\'s not a mistake.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
current_ctc = values.pop('current_ctc', None)
|
||||||
|
expected_ctc = values.pop('expected_ctc', None)
|
||||||
|
exp_type = values.pop('exp_type', None)
|
||||||
|
current_location = values.pop('current_location', None)
|
||||||
|
preferred_locations_str = values.pop('preferred_locations', '')
|
||||||
|
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')
|
||||||
|
if model.model == 'hr.applicant':
|
||||||
|
# pop the fields since there are only useful to generate a candidate record
|
||||||
|
# partner_name = values.pop('partner_name')
|
||||||
|
first_name = values.pop('first_name', None)
|
||||||
|
middle_name = values.pop('middle_name', None)
|
||||||
|
last_name = values.pop('last_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': f"{first_name + ' '+ ((middle_name + ' ') if middle_name else '') + last_name}",
|
||||||
|
'first_name': first_name,
|
||||||
|
'middle_name': middle_name,
|
||||||
|
'last_name': last_name,
|
||||||
|
'alternate_phone': alternate_phone,
|
||||||
|
'type_id': int(degree) if degree.isdigit() else False
|
||||||
|
})
|
||||||
|
if not candidate:
|
||||||
|
candidate = request.env['hr.candidate'].sudo().create({
|
||||||
|
'partner_name': f"{first_name + ' '+ ((middle_name + ' ') if middle_name else '') + last_name}",
|
||||||
|
'email_from': partner_email,
|
||||||
|
'partner_phone': partner_phone,
|
||||||
|
'first_name': first_name,
|
||||||
|
'middle_name': middle_name,
|
||||||
|
'last_name': last_name,
|
||||||
|
'alternate_phone': alternate_phone,
|
||||||
|
'type_id': int(degree) if degree.isdigit() else False
|
||||||
|
})
|
||||||
|
values['partner_name'] = f"{first_name + ' '+ ((middle_name + ' ') if middle_name else '') + last_name}"
|
||||||
|
if partner_phone:
|
||||||
|
values['partner_phone'] = partner_phone
|
||||||
|
if partner_email:
|
||||||
|
values['email_from'] = partner_email
|
||||||
|
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 if notice_period else 0
|
||||||
|
data['record']['notice_period_type'] = notice_period_type if notice_period_type else 'day'
|
||||||
|
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
|
||||||
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<data noupdate="1">
|
||||||
|
<record model="ir.cron" id="hr_job_end_date_update">
|
||||||
|
<field name="name">JOB: End date</field>
|
||||||
|
<field name="model_id" ref="hr.model_hr_job"/>
|
||||||
|
<field name="state">code</field>
|
||||||
|
<field name="code">model.hr_job_end_date_update()</field>
|
||||||
|
<field name="user_id" ref="base.user_root"/>
|
||||||
|
<field name="interval_number">1</field>
|
||||||
|
<field name="interval_type">days</field>
|
||||||
|
<field name="nextcall" eval="(DateTime.now().replace(hour=1, minute=0) + timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record id="template_recruitment_deadline_alert" model="mail.template">
|
||||||
|
<field name="name">Recruitment Deadline Alert</field>
|
||||||
|
<field name="model_id" ref="hr.model_hr_job"/>
|
||||||
|
<field name="email_from">{{ object.company_id.email or user.email_formatted }}</field>
|
||||||
|
<field name="email_to">{{ object.user_id.email }}
|
||||||
|
</field>
|
||||||
|
<field name="subject">Reminder: Recruitment Process Ending Soon - {{ object.name }}</field>
|
||||||
|
<field name="description">Notification sent to recruiters when a job's recruitment deadline is approaching.</field>
|
||||||
|
<field name="body_html" type="html">
|
||||||
|
<t t-set="user_names" t-value="', '.join(object.user_id.mapped('name')) if object.user_id else 'Recruiter'"/>
|
||||||
|
<t t-set="location_names" t-value="', '.join(object.locations.mapped('name')) if object.locations else 'N/A'"/>
|
||||||
|
<div style="margin: 0px; padding: 0px;">
|
||||||
|
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||||
|
Dear <t t-esc="user_names">Recruiter</t>,
|
||||||
|
<br /><br />
|
||||||
|
This is a friendly reminder that the recruitment process for the position
|
||||||
|
<strong><t t-esc="object.name or ''">Job Title</t></strong> is approaching its end:
|
||||||
|
<ul>
|
||||||
|
<li><strong>Job Position:</strong> <t t-esc="object.name or ''">Job Name</t></li>
|
||||||
|
<li><strong>Target End Date:</strong> <t t-esc="object.target_to or ''">End Date</t></li>
|
||||||
|
<li><strong>Location(s):</strong> <t t-esc="location_names">Locations</t></li>
|
||||||
|
</ul>
|
||||||
|
<br />
|
||||||
|
Please ensure all recruitment activities are completed before the deadline.
|
||||||
|
<br /><br />
|
||||||
|
<a t-att-href="'%s/web#id=%d&model=hr.job&view_type=form' % (object.env['ir.config_parameter'].sudo().get_param('web.base.url'), object.id)" target="_blank">
|
||||||
|
Click here to view the job details.
|
||||||
|
</a>
|
||||||
|
<br /><br />
|
||||||
|
Regards,<br />
|
||||||
|
<t t-esc="user.name or 'System Admin'">System Admin</t>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</field>
|
||||||
|
<field name="auto_delete" eval="True"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<!--
|
||||||
|
<record id="object0" model="hr_recruitment_extended.hr_recruitment_extended">
|
||||||
|
<field name="name">Object 0</field>
|
||||||
|
<field name="value">0</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="object1" model="hr_recruitment_extended.hr_recruitment_extended">
|
||||||
|
<field name="name">Object 1</field>
|
||||||
|
<field name="value">10</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="object2" model="hr_recruitment_extended.hr_recruitment_extended">
|
||||||
|
<field name="name">Object 2</field>
|
||||||
|
<field name="value">20</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="object3" model="hr_recruitment_extended.hr_recruitment_extended">
|
||||||
|
<field name="name">Object 3</field>
|
||||||
|
<field name="value">30</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="object4" model="hr_recruitment_extended.hr_recruitment_extended">
|
||||||
|
<field name="name">Object 4</field>
|
||||||
|
<field name="value">40</field>
|
||||||
|
</record>
|
||||||
|
-->
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import hr_recruitment
|
||||||
|
from . import stages
|
||||||
|
from . import hr_applicant
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
from odoo import models, fields, api, _
|
||||||
|
|
||||||
|
|
||||||
|
class HRApplicant(models.Model):
|
||||||
|
_inherit = 'hr.applicant'
|
||||||
|
|
||||||
|
refused_state = fields.Many2one('hr.recruitment.stage', readonly=True, force_save=True)
|
||||||
|
|
||||||
|
def reset_applicant(self):
|
||||||
|
""" Reinsert the applicant into the recruitment pipe in the first stage"""
|
||||||
|
res = super(HRApplicant, self).reset_applicant()
|
||||||
|
for applicant in self:
|
||||||
|
applicant.write(
|
||||||
|
{'refused_state': False})
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicantGetRefuseReason(models.TransientModel):
|
||||||
|
_inherit = 'applicant.get.refuse.reason'
|
||||||
|
|
||||||
|
|
||||||
|
def action_refuse_reason_apply(self):
|
||||||
|
res = super(ApplicantGetRefuseReason, self).action_refuse_reason_apply()
|
||||||
|
refused_applications = self.applicant_ids
|
||||||
|
refused_applications.write({'refused_state': refused_applications.stage_id.id})
|
||||||
|
return res
|
||||||
|
|
@ -0,0 +1,197 @@
|
||||||
|
from odoo import models, fields, api, _
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
from datetime import date
|
||||||
|
from datetime import timedelta
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class Job(models.Model):
|
||||||
|
_inherit = 'hr.job'
|
||||||
|
|
||||||
|
secondary_skill_ids = fields.Many2many('hr.skill', "hr_job_secondary_hr_skill_rel",
|
||||||
|
'hr_job_id', 'hr_skill_id', "Secondary Skills")
|
||||||
|
locations = fields.Many2many('hr.location')
|
||||||
|
target_from = fields.Date(string="This is the date in which we starting the recruitment process", default=fields.Date.today)
|
||||||
|
target_to = fields.Date(string='This is the target end date')
|
||||||
|
hiring_history = fields.One2many('recruitment.status.history', 'job_id', string='History')
|
||||||
|
|
||||||
|
def buttion_view_applicants(self):
|
||||||
|
if self.skill_ids:
|
||||||
|
a = self.env['hr.candidate'].search([])
|
||||||
|
applicants = self.env['hr.candidate']
|
||||||
|
for i in a:
|
||||||
|
if all(skill in i.skill_ids for skill in self.skill_ids):
|
||||||
|
applicants += i
|
||||||
|
|
||||||
|
else:
|
||||||
|
applicants = self.env['hr.candidate'].search([])
|
||||||
|
action = self.env['ir.actions.act_window']._for_xml_id('hr_recruitment.action_hr_candidate')
|
||||||
|
action['domain'] = [('id', 'in', applicants.ids)]
|
||||||
|
action['context'] = dict(self._context)
|
||||||
|
return action
|
||||||
|
|
||||||
|
def hr_job_end_date_update(self):
|
||||||
|
# Find all jobs where the target_to is today's date
|
||||||
|
hr_jobs = self.sudo().search([('target_to', '=', fields.Date.today() - timedelta(days=1))])
|
||||||
|
# stage_ids = self.env['hr.recruitment.stage'].sudo().search([('hired_stage','=',True)])
|
||||||
|
for job in hr_jobs:
|
||||||
|
# Determine the hiring period
|
||||||
|
date_from = job.target_from
|
||||||
|
date_end = job.target_to
|
||||||
|
# Fetch hired applicants related to this job
|
||||||
|
hired_applicants = self.env['hr.applicant'].search([
|
||||||
|
('job_id', '=', job.id),
|
||||||
|
('stage_id.hired_stage', '=', True)
|
||||||
|
])
|
||||||
|
# Get today's date in the datetime format (with time set to midnight)
|
||||||
|
today_start = fields.Datetime.today()
|
||||||
|
|
||||||
|
# Get today's date at the end of the day (23:59:59) to include all records created today
|
||||||
|
today_end = fields.Datetime.today().now()
|
||||||
|
|
||||||
|
# Search for records where create_date is today
|
||||||
|
hiring_history_today = self.env['recruitment.status.history'].sudo().search([
|
||||||
|
('create_date', '>=', today_start),
|
||||||
|
('create_date', '<=', today_end),
|
||||||
|
('job_id','=',job.id)
|
||||||
|
])
|
||||||
|
# Create a hiring history record
|
||||||
|
if not hiring_history_today:
|
||||||
|
self.env['recruitment.status.history'].create({
|
||||||
|
'date_from': date_from,
|
||||||
|
'date_end': date_end,
|
||||||
|
'target': len(hired_applicants), # Number of hired applicants
|
||||||
|
'job_id': job.id,
|
||||||
|
'hired': [(6, 0, hired_applicants.ids)] # Many2many write operation
|
||||||
|
})
|
||||||
|
|
||||||
|
tomorrow_date = fields.Date.today() + timedelta(days=1)
|
||||||
|
jobs_ending_tomorrow = self.sudo().search([('target_to', '=', tomorrow_date)])
|
||||||
|
|
||||||
|
for job in jobs_ending_tomorrow:
|
||||||
|
# Fetch recruiters (assuming job has a field recruiter_id or similar)
|
||||||
|
recruiter = job.sudo().user_id # Replacne with the appropriate field name
|
||||||
|
if recruiter:
|
||||||
|
# Send mail
|
||||||
|
template = self.env.ref(
|
||||||
|
'hr_recruitment_extended.template_recruitment_deadline_alert') # Replace with your email template XML ID
|
||||||
|
if template:
|
||||||
|
template.sudo().send_mail(recruiter.id, force_send=True)
|
||||||
|
recruitment_history = self.env['recruitment.status.history'].sudo().search([('job_id','!=',False)])
|
||||||
|
|
||||||
|
for recruitment in recruitment_history:
|
||||||
|
# Determine the hiring period
|
||||||
|
if recruitment.date_from and recruitment.job_id:
|
||||||
|
# Use `date_end` or today's date if `date_end` is not provided
|
||||||
|
date_end = fields.Datetime.to_datetime(fields.Date.to_string(recruitment.date_end)) + datetime.timedelta(days=1,seconds=-1) or fields.Datetime.today().now()
|
||||||
|
current_hired_applicants = recruitment.hired
|
||||||
|
# Search for applicants matching the conditions
|
||||||
|
hired_applicants = self.env['hr.applicant'].search([
|
||||||
|
('date_closed', '>=', fields.Datetime.to_datetime(fields.Date.to_string(recruitment.date_from))),
|
||||||
|
('date_closed', '<=', date_end),
|
||||||
|
('job_id', '=', recruitment.job_id.id)
|
||||||
|
])
|
||||||
|
# Filter out the applicants that are already in the 'hired' field
|
||||||
|
new_hired_applicants = hired_applicants - current_hired_applicants
|
||||||
|
|
||||||
|
# Add the missing applicants to the 'hired' field
|
||||||
|
recruitment.hired = current_hired_applicants | new_hired_applicants
|
||||||
|
return True
|
||||||
|
|
||||||
|
class HrCandidate(models.Model):
|
||||||
|
_inherit = "hr.candidate"
|
||||||
|
|
||||||
|
first_name = fields.Char(string='First Name',required=True, help="This is the person's first name, given at birth or during a naming ceremony. It’s the name people use to address you.")
|
||||||
|
middle_name = fields.Char(string='Middle Name', help="This is an extra name that comes between the first name and last name. Not everyone has a middle name")
|
||||||
|
last_name = fields.Char(string='Last Name',required=True, help="This is the family name, shared with other family members. It’s usually the last name.")
|
||||||
|
alternate_phone = fields.Char(string='Alternate Phone')
|
||||||
|
|
||||||
|
@api.constrains('partner_name')
|
||||||
|
def partner_name_constrain(self):
|
||||||
|
for rec in self:
|
||||||
|
if any(char.isdigit() for char in rec.partner_name):
|
||||||
|
raise ValidationError(_("Enter Valid Name"))
|
||||||
|
|
||||||
|
|
||||||
|
class HRApplicant(models.Model):
|
||||||
|
_inherit = 'hr.applicant'
|
||||||
|
|
||||||
|
current_location = fields.Char('Current Location')
|
||||||
|
preferred_location = fields.Many2many('hr.location',string="Preferred Location's")
|
||||||
|
current_organization = fields.Char('Current Organization')
|
||||||
|
alternate_phone = fields.Char(related="candidate_id.alternate_phone", readonly=False)
|
||||||
|
|
||||||
|
|
||||||
|
exp_type = fields.Selection([('fresher','Fresher'),('experienced','Experienced')], default='fresher', required=True)
|
||||||
|
|
||||||
|
total_exp = fields.Integer(string="Total Experience")
|
||||||
|
relevant_exp = fields.Integer(string="Relevant Experience")
|
||||||
|
total_exp_type = fields.Selection([('month',"Month's"),('year',"Year's")])
|
||||||
|
relevant_exp_type = fields.Selection([('month',"Month's"),('year',"Year's")])
|
||||||
|
notice_period = fields.Integer(string="Notice Period")
|
||||||
|
notice_period_type = fields.Selection([('day',"Day's"),('month',"Month's"),('year',"Year's")], string='Type')
|
||||||
|
|
||||||
|
current_ctc = fields.Float(string="Current CTC", aggregator="avg", help="Applicant Current Salary", tracking=True, groups="hr_recruitment.group_hr_recruitment_user")
|
||||||
|
salary_expected = fields.Float("Expected CTC", aggregator="avg", help="Salary Expected by Applicant", tracking=True, groups="hr_recruitment.group_hr_recruitment_user")
|
||||||
|
salary_negotiable = fields.Boolean(string="Salary Negotiable")
|
||||||
|
np_negotiable = fields.Boolean(string="NP Negotiable")
|
||||||
|
holding_offer = fields.Char(string="Holding Offer")
|
||||||
|
applicant_comments = fields.Text(string='Applicant Comments')
|
||||||
|
recruiter_comments = fields.Text(string='Recruiter Comments')
|
||||||
|
|
||||||
|
class Location(models.Model):
|
||||||
|
_name = 'hr.location'
|
||||||
|
_rec_name = 'location_name'
|
||||||
|
# SQL Constraint to ensure the combination of location_name, zip_code, country_id, and state is unique
|
||||||
|
_sql_constraints = [
|
||||||
|
('unique_location_zip_state_country',
|
||||||
|
'UNIQUE(location_name, zip_code, country_id, state)',
|
||||||
|
'The selected Location, Zip Code, Country, and State combination already exists.')
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
location_name = fields.Char(string='Location', required=True)
|
||||||
|
zip_code = fields.Char(string = 'Zip Code')
|
||||||
|
country_id = fields.Many2one('res.country','Country',groups="hr.group_hr_user")
|
||||||
|
state = fields.Many2one("res.country.state", string="State",
|
||||||
|
domain="[('country_id', '=?', country_id)]",
|
||||||
|
groups="hr.group_hr_user")
|
||||||
|
|
||||||
|
@api.constrains('location_name')
|
||||||
|
def _check_location_name(self):
|
||||||
|
for record in self:
|
||||||
|
if record.location_name.isdigit():
|
||||||
|
raise ValidationError("Location name should not be a number. Please enter a valid location name.")
|
||||||
|
|
||||||
|
@api.constrains('zip_code')
|
||||||
|
def _check_zip_code(self):
|
||||||
|
for record in self:
|
||||||
|
if record.zip_code and not record.zip_code.isdigit(): # Check if zip_code exists and is not digit
|
||||||
|
raise ValidationError("Zip Code should contain only numeric characters. Please enter a valid zip code.")
|
||||||
|
|
||||||
|
|
||||||
|
class RecruitmentHistory(models.Model):
|
||||||
|
_name='recruitment.status.history'
|
||||||
|
|
||||||
|
date_from = fields.Date(string='Date From')
|
||||||
|
date_end = fields.Date(string='Date End')
|
||||||
|
target = fields.Integer(string='Target')
|
||||||
|
job_id = fields.Many2one('hr.job', string='Job Position') # Ensure this field exists
|
||||||
|
hired = fields.Many2many('hr.applicant')
|
||||||
|
|
||||||
|
@api.depends('date_from', 'date_end', 'job_id')
|
||||||
|
def _total_hired_users(self):
|
||||||
|
for rec in self:
|
||||||
|
if rec.date_from:
|
||||||
|
# Use `date_end` or today's date if `date_end` is not provided
|
||||||
|
date_end = rec.date_end or date.today()
|
||||||
|
|
||||||
|
# Search for applicants matching the conditions
|
||||||
|
hired_applicants = self.env['hr.applicant'].search([
|
||||||
|
('date_closed', '>=', rec.date_from),
|
||||||
|
('date_closed', '<=', date_end),
|
||||||
|
('job_id', '=', rec.job_id.id)
|
||||||
|
])
|
||||||
|
rec.hired = hired_applicants
|
||||||
|
else:
|
||||||
|
rec.hired = False
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
from odoo import models, fields, api, _
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
class RecruitmentStage(models.Model):
|
||||||
|
_inherit = 'hr.recruitment.stage'
|
||||||
|
|
||||||
|
is_default_field = fields.Boolean(string='Is Default', help='Upon Activating this it will automatically come upon JD Creation', default=True)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self, vals):
|
||||||
|
res = super(RecruitmentStage, self).create(vals)
|
||||||
|
if 'job_ids' in vals:
|
||||||
|
|
||||||
|
jobs = list()
|
||||||
|
for job_id in vals['job_ids']:
|
||||||
|
jobs.append(job_id[1] if len(job_id)>1 else job_id)
|
||||||
|
job_ids = self.env['hr.job'].browse(jobs)
|
||||||
|
for job_id in job_ids:
|
||||||
|
job_id.write({'recruitment_stage_ids': [(4, res.id)]})
|
||||||
|
return res
|
||||||
|
|
||||||
|
def write(self, vals, model=None):
|
||||||
|
res = super(RecruitmentStage, self).write(vals)
|
||||||
|
|
||||||
|
if model:
|
||||||
|
if 'job_ids' in vals:
|
||||||
|
previous_job_ids = self.job_ids
|
||||||
|
|
||||||
|
jobs = list()
|
||||||
|
for job_id in vals['job_ids']:
|
||||||
|
jobs.append(job_id[1] if len(job_id)>1 else job_id)
|
||||||
|
|
||||||
|
|
||||||
|
new_job_ids = self.env['hr.job'].browse(jobs)
|
||||||
|
for stage_id in new_job_ids:
|
||||||
|
stage_id.write({'recruitment_stage_ids': [(4, self.id)]})
|
||||||
|
# Remove jobs from stages no longer related
|
||||||
|
for stage in previous_job_ids:
|
||||||
|
if stage.id not in new_job_ids.ids:
|
||||||
|
stage.write({'recruitment_stage_ids': [(3, self.id)]})
|
||||||
|
return res
|
||||||
|
|
||||||
|
def unlink(self):
|
||||||
|
# Remove stage from all related jobs when stage is deleted
|
||||||
|
for stage in self:
|
||||||
|
for job in stage.job_ids:
|
||||||
|
job.write({'recruitment_stage_ids': [(3, stage.id)]})
|
||||||
|
return super(RecruitmentStage, self).unlink()
|
||||||
|
|
||||||
|
|
||||||
|
class Job(models.Model):
|
||||||
|
_inherit = 'hr.job'
|
||||||
|
|
||||||
|
recruitment_stage_ids = fields.Many2many('hr.recruitment.stage')
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def default_get(self, fields_list):
|
||||||
|
""" Override default_get to set default values """
|
||||||
|
res = super(Job, self).default_get(fields_list)
|
||||||
|
# Check if recruitment_stage_ids is not set
|
||||||
|
if 'recruitment_stage_ids' in fields_list:
|
||||||
|
# Search for the default stage (e.g., a stage called 'Default Stage')
|
||||||
|
default_stages = self.env['hr.recruitment.stage'].search([('is_default_field', '=', True)],order='sequence')
|
||||||
|
if default_stages:
|
||||||
|
# Set the default stage in the recruitment_stage_ids field
|
||||||
|
res['recruitment_stage_ids'] = default_stages # Add the default stage to the many2many field
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self,vals):
|
||||||
|
res = super(Job, self).create(vals)
|
||||||
|
if 'recruitment_stage_ids' in vals:
|
||||||
|
stages = list()
|
||||||
|
for stage_id in vals['recruitment_stage_ids']:
|
||||||
|
stages.append(stage_id[1] if len(stage_id)>1 else stage_id)
|
||||||
|
|
||||||
|
stage_ids = self.env['hr.recruitment.stage'].browse(stages)
|
||||||
|
for stage_id in stage_ids:
|
||||||
|
stage_id.write({'job_ids': [(4, res.id)]})
|
||||||
|
return res
|
||||||
|
|
||||||
|
def write(self, vals, model=None):
|
||||||
|
res = super(Job, self).write(vals)
|
||||||
|
if model:
|
||||||
|
if 'recruitment_stage_ids' in vals:
|
||||||
|
previous_stage_ids = self.recruitment_stage_ids.ids
|
||||||
|
stages = list()
|
||||||
|
for stage_id in vals['recruitment_stage_ids']:
|
||||||
|
stages.append(stage_id[1] if len(stage_id)>1 else stage_id)
|
||||||
|
new_stage_ids = self.env['hr.recruitment.stage'].browse(stages)
|
||||||
|
for stage_id in new_stage_ids:
|
||||||
|
stage_id.write({'job_ids': [(4, self.id)]})
|
||||||
|
# Remove jobs from stages no longer related
|
||||||
|
for stage in previous_stage_ids:
|
||||||
|
if stage.id not in new_stage_ids.ids:
|
||||||
|
stage.write({'job_ids': [(3, self.id)]})
|
||||||
|
return res
|
||||||
|
|
||||||
|
def unlink(self):
|
||||||
|
# Remove stage from all related jobs when stage is deleted
|
||||||
|
for job in self:
|
||||||
|
for stage in stage.recruitment_stage_ids:
|
||||||
|
stage.write({'job_ids': [(3, job.id)]})
|
||||||
|
return super(Job, self).unlink()
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_hr_location,hr.location,model_hr_location,base.group_user,1,1,1,1
|
||||||
|
access_recruitment_status_history,recruitment.status.history,model_recruitment_status_history,base.group_user,1,1,1,1
|
||||||
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import publicWidget from "@web/legacy/js/public/public_widget";
|
||||||
|
import { _t } from "@web/core/l10n/translation";
|
||||||
|
import { rpc } from "@web/core/network/rpc";
|
||||||
|
|
||||||
|
// Inherit the hrRecruitment widget
|
||||||
|
publicWidget.registry.CustomHrRecruitment = publicWidget.registry.hrRecruitment.extend({
|
||||||
|
selector: '#hr_recruitment_form',
|
||||||
|
events: {
|
||||||
|
'focusout #recruitmentctc' : '_onFocusOutCTC',
|
||||||
|
'focusout #recruitmentctc2' : '_onFocusOutCTC2',
|
||||||
|
'focusout #recruitmentphone' : '_onFocusOutPhoneNumber',
|
||||||
|
'focusout #recruitmentphone2': '_onFocusOutPhone2Number',
|
||||||
|
'change [name="exp_type"]': '_onExperienceTypeChange' // Add event listener for Experience Type change
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async _onFocusOutCTC(ev) {
|
||||||
|
const regex = /^[\d]*$/;
|
||||||
|
const field='ctc'
|
||||||
|
const value = ev.currentTarget.value;
|
||||||
|
const messageContainerId = "#ctcwarning-message";
|
||||||
|
if (value && !regex.test(value)) {
|
||||||
|
this.showWarningMessage(ev.currentTarget, messageContainerId, "InValid CTC");
|
||||||
|
} else {
|
||||||
|
this.hideWarningMessage(ev.currentTarget, messageContainerId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async _onFocusOutCTC2(ev) {
|
||||||
|
const regex = /^[\d]*$/;
|
||||||
|
const field='ctc'
|
||||||
|
const value = ev.currentTarget.value;
|
||||||
|
const messageContainerId = "#ctc2warning-message";
|
||||||
|
if (value && !regex.test(value)) {
|
||||||
|
this.showWarningMessage(ev.currentTarget, messageContainerId, "InValid CTC");
|
||||||
|
} else {
|
||||||
|
this.hideWarningMessage(ev.currentTarget, messageContainerId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async _onFocusOutPhoneNumber (ev) {
|
||||||
|
const regex = /^[\d\+\-\s]*$/;
|
||||||
|
const field = "phone"
|
||||||
|
const value = ev.currentTarget.value;
|
||||||
|
const messageContainerId = "#phone1-warning";
|
||||||
|
await this.checkRedundant(ev.currentTarget, field, messageContainerId);
|
||||||
|
if (value && !regex.test(value)) {
|
||||||
|
this.showWarningMessage(ev.currentTarget, messageContainerId, "Invalid Number");
|
||||||
|
} else {
|
||||||
|
this.hideWarningMessage(ev.currentTarget, messageContainerId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async _onFocusOutPhone2Number (ev) {
|
||||||
|
const regex = /^[\d\+\-\s]*$/;
|
||||||
|
const field = "phone"
|
||||||
|
const value = ev.currentTarget.value;
|
||||||
|
const messageContainerId = "#phone2-warning";
|
||||||
|
await this.checkRedundant(ev.currentTarget, field, messageContainerId);
|
||||||
|
if (value && !regex.test(value)) {
|
||||||
|
this.showWarningMessage(ev.currentTarget, messageContainerId, "Invalid Number");
|
||||||
|
} else {
|
||||||
|
this.hideWarningMessage(ev.currentTarget, messageContainerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// Function to toggle visibility of current_ctc and current_organization based on Experience Type
|
||||||
|
_onExperienceTypeChange(ev) {
|
||||||
|
const expType = ev.currentTarget.value; // Get selected Experience Type
|
||||||
|
const currentCtcField = $('#current_ctc_field');
|
||||||
|
const currentOrgField = $('#current_organization_field');
|
||||||
|
const noticePeriodField = $('#notice_period_field');
|
||||||
|
const ctcInput = $('#recruitmentctc');
|
||||||
|
const orgInput = $('#current_organization');
|
||||||
|
const noticePeriodInput = $('#notice_period')
|
||||||
|
|
||||||
|
if (expType === 'fresher') {
|
||||||
|
currentCtcField.hide();
|
||||||
|
currentOrgField.hide();
|
||||||
|
noticePeriodField.hide();
|
||||||
|
|
||||||
|
ctcInput.val('')
|
||||||
|
orgInput.val('')
|
||||||
|
noticePeriodInput.val('')
|
||||||
|
} else {
|
||||||
|
currentCtcField.show();
|
||||||
|
currentOrgField.show();
|
||||||
|
noticePeriodField.show();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async _renderPreferredLocations() {
|
||||||
|
console.log("hello world")
|
||||||
|
console.log(this)
|
||||||
|
const value = $('#preferred_locations_container').data('job_id');
|
||||||
|
console.log(value)
|
||||||
|
console.log("Job ID:", value); // You can now use this jobId
|
||||||
|
if (value){
|
||||||
|
let locationsArray = value.match(/\d+/g).map(Number); // [1, 2, 4, 5]
|
||||||
|
console.log(locationsArray)
|
||||||
|
const locations_data = await rpc("/hr_recruitment_extended/fetch_preferred_locations", {
|
||||||
|
loc_ids : locationsArray
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
// Assuming location_ids is a many2many field in hr.job
|
||||||
|
const locationsField = $('#preferred_locations_container');
|
||||||
|
locationsField.empty(); // Clear any existing options
|
||||||
|
|
||||||
|
// Add checkboxes for each location
|
||||||
|
Object.keys(locations_data).forEach(key => {
|
||||||
|
const value = locations_data[key]; // value for the current key
|
||||||
|
const checkboxHtml = `
|
||||||
|
<div class="checkbox-container">
|
||||||
|
<input type="checkbox" name="preferred_locations" value="${key}" id="location_${key}">
|
||||||
|
<label for="location_${key}">${value}</label>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
locationsField.append(checkboxHtml);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching locations:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.log("no values")
|
||||||
|
const preferredLocation = $('#preferred_location_field');
|
||||||
|
preferredLocation.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async _hrRecruitmentDegrees() {
|
||||||
|
try {
|
||||||
|
const degrees_data = await rpc("/hr_recruitment_extended/fetch_hr_recruitment_degree", {
|
||||||
|
});
|
||||||
|
// Assuming location_ids is a many2many field in hr.job
|
||||||
|
const degreesSelection = $('#fetch_hr_recruitment_degree');
|
||||||
|
degreesSelection.empty(); // Clear any existing options
|
||||||
|
|
||||||
|
// Add checkboxes for each location
|
||||||
|
Object.keys(degrees_data).forEach(key => {
|
||||||
|
const value = degrees_data[key]; // value for the current key
|
||||||
|
const checkboxHtml = `
|
||||||
|
|
||||||
|
<option value="${key}">${value}</option>
|
||||||
|
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
degreesSelection.append(checkboxHtml);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching degrees:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
this._super(...arguments);
|
||||||
|
await this._renderPreferredLocations(); // Render the preferred locations checkboxes
|
||||||
|
await this._hrRecruitmentDegrees();
|
||||||
|
const currentCtcField = $('#current_ctc_field');
|
||||||
|
const currentOrgField = $('#current_organization_field');
|
||||||
|
const noticePeriodField = $('#notice_period_field');
|
||||||
|
const ctcInput = $('#recruitmentctc');
|
||||||
|
const orgInput = $('#current_organization');
|
||||||
|
const noticePeriodInput = $('#notice_period')
|
||||||
|
|
||||||
|
currentCtcField.hide();
|
||||||
|
currentOrgField.hide();
|
||||||
|
noticePeriodField.hide();
|
||||||
|
|
||||||
|
ctcInput.val('')
|
||||||
|
orgInput.val('')
|
||||||
|
noticePeriodInput.val('')
|
||||||
|
},
|
||||||
|
|
||||||
|
// You can also override the _onClickApplyButton method if needed
|
||||||
|
// _onClickApplyButton(ev) {
|
||||||
|
// this._super(ev); // Call the parent method if you want to retain its functionality
|
||||||
|
// console.log("Custom behavior after clicking apply button!");
|
||||||
|
// // Add your custom logic here if needed
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<!-- Tree View (List View) -->
|
||||||
|
<record id="view_hr_location_tree" model="ir.ui.view">
|
||||||
|
<field name="name">hr.location.tree</field>
|
||||||
|
<field name="model">hr.location</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list string="Location">
|
||||||
|
<field name="location_name"/>
|
||||||
|
<field name="zip_code"/>
|
||||||
|
<field name="country_id"/>
|
||||||
|
<field name="state"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Form View -->
|
||||||
|
<record id="view_hr_location_form" model="ir.ui.view">
|
||||||
|
<field name="name">hr.location.form</field>
|
||||||
|
<field name="model">hr.location</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Location">
|
||||||
|
<sheet>
|
||||||
|
<div class="row justify-content-between position-relative w-100 m-0 mb-2">
|
||||||
|
<div class="oe_title mw-75 ps-0 pe-2">
|
||||||
|
<h1 class="d-flex flex-row align-items-center">
|
||||||
|
<field name="location_name" placeholder="Location Name" required="True" style="font-size: min(4vw, 2.6rem);"/>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="country_id"/>
|
||||||
|
<field name="state"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="zip_code"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Action to open Locations -->
|
||||||
|
<record id="action_hr_location" model="ir.actions.act_window">
|
||||||
|
<field name="name">Locations</field>
|
||||||
|
<field name="res_model">hr.location</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="o_view_nocontent_smiling_face">
|
||||||
|
Create a new Location
|
||||||
|
</p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem
|
||||||
|
id="hr_location_menu"
|
||||||
|
name="Locations"
|
||||||
|
action="action_hr_location"
|
||||||
|
parent="hr_recruitment.menu_hr_recruitment_config_jobs"
|
||||||
|
sequence="5"/>
|
||||||
|
|
||||||
|
|
||||||
|
</data>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,220 @@
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="hr_view_hr_job_form_extended">
|
||||||
|
<field name="name">hr.job.form.extended</field>
|
||||||
|
<field name="model">hr.job</field>
|
||||||
|
<field name="inherit_id" ref="hr.view_hr_job_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//notebook" position="inside">
|
||||||
|
<page string="History" name="hiring_history_page">
|
||||||
|
<field name="hiring_history">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="date_from"/>
|
||||||
|
<field name="date_end"/>
|
||||||
|
<field name="target"/>
|
||||||
|
<field name="hired" widget="many2many_tags"/>
|
||||||
|
<field name="job_id" invisible="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
</xpath>
|
||||||
|
<!-- <xpath expr="//page[@name='job_description_page']" position="after">-->
|
||||||
|
<!-- <page string="History" name="hiring_history_page">-->
|
||||||
|
<!-- <field name="hiring_history">-->
|
||||||
|
<!-- <tree editable="top">-->
|
||||||
|
<!-- <field name="date_from"/>-->
|
||||||
|
<!-- <field name="date_end"/>-->
|
||||||
|
<!-- <field name="target"/>-->
|
||||||
|
<!-- <field name="hired"/>-->
|
||||||
|
<!-- </tree>-->
|
||||||
|
<!-- </field>-->
|
||||||
|
<!-- </page>-->
|
||||||
|
<!-- </xpath>-->
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="hr_recruitment_hr_job_survey_extended">
|
||||||
|
<field name="name">hr.job.survey.extended</field>
|
||||||
|
<field name="model">hr.job</field>
|
||||||
|
<field name="inherit_id" ref="hr_recruitment.hr_job_survey"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='date_from']" position="attributes">
|
||||||
|
<attribute name="invisible">1</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='date_from']" position="before">
|
||||||
|
<field name="target_from" widget="daterange" string="Mission Dates"
|
||||||
|
options="{'end_date_field': 'target_to'}"/>
|
||||||
|
<field name="target_to" invisible="1"/>
|
||||||
|
|
||||||
|
</xpath>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="hr_job_form_extended">
|
||||||
|
<field name="name">hr.job.form.extended</field>
|
||||||
|
<field name="model">hr.job</field>
|
||||||
|
<field name="inherit_id" ref="hr_recruitment_skills.hr_job_form_inherit_hr_recruitment_skills"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//div[hasclass('oe_button_box')]" position="inside">
|
||||||
|
<button name="buttion_view_applicants" type="object" class="oe_stat_button" string="Candidates" widget="statinfo" icon="fa-th-large"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='skill_ids']" position="after">
|
||||||
|
<field name="secondary_skill_ids" widget="many2many_tags" options="{'color_field': 'color'}"
|
||||||
|
context="{'search_default_group_skill_type_id': 1}"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//group[@name='recruitment2']" position="inside">
|
||||||
|
<field name="locations" widget="many2many_tags"/>
|
||||||
|
<field name="recruitment_stage_ids" widget="many2many_tags"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="hr_recruitment_hr_applicant_view_form_extend">
|
||||||
|
<field name="name">hr.applicant.view.form.extended</field>
|
||||||
|
<field name="model">hr.applicant</field>
|
||||||
|
<field name="inherit_id" ref="hr_recruitment.hr_applicant_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='partner_phone']" position="after">
|
||||||
|
<field name="alternate_phone"/>
|
||||||
|
</xpath>
|
||||||
|
|
||||||
|
<xpath expr="//field[@name='refuse_reason_id']" position="after">
|
||||||
|
<field name="refused_state" invisible="not refuse_reason_id"/>
|
||||||
|
</xpath>
|
||||||
|
|
||||||
|
|
||||||
|
<xpath expr="//field[@name='linkedin_profile']" position="after">
|
||||||
|
<field name="exp_type"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//group[@name='recruitment_contract']/label[@for='salary_expected']" position="before">
|
||||||
|
<field name="current_ctc"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//page[@name='application_details']" position="inside">
|
||||||
|
<group>
|
||||||
|
<group string="Location" name="location_details">
|
||||||
|
<field name="current_location"/>
|
||||||
|
<field name="preferred_location" widget="many2many_tags"/>
|
||||||
|
<field name="current_organization"/>
|
||||||
|
</group>
|
||||||
|
<group string="Experience" name="applicant_experience">
|
||||||
|
<label for="total_exp" string="Total Experience"/>
|
||||||
|
<div class="o_row">
|
||||||
|
<field name="total_exp" placeholder="Total Experience"/>
|
||||||
|
<field name="total_exp_type" placeholder="Experience Type" required="total_exp > 0"/>
|
||||||
|
</div>
|
||||||
|
<label for="relevant_exp" string="Relevant Experience"/>
|
||||||
|
<div class="o_row">
|
||||||
|
<field name="relevant_exp" placeholder="Relevant Experience"/>
|
||||||
|
<field name="relevant_exp_type" placeholder="Experience Type" required="relevant_exp > 0"/>
|
||||||
|
</div>
|
||||||
|
<label for="notice_period" string="Notice Period"/>
|
||||||
|
<div class="o_row">
|
||||||
|
<field name="notice_period" placeholder="Relevant Experience"/>
|
||||||
|
<field name="notice_period_type" placeholder="Experience Type" required="relevant_exp > 0"/>
|
||||||
|
</div>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<group string="Negotiation" name="negotiation_details">
|
||||||
|
<field name="salary_negotiable"/>
|
||||||
|
<field name="np_negotiable"/>
|
||||||
|
<field name="holding_offer"/>
|
||||||
|
</group>
|
||||||
|
<group string="Comments" name="comments">
|
||||||
|
<field name="applicant_comments"/>
|
||||||
|
<field name="recruiter_comments"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="hr_candidate_view_form_inherit">
|
||||||
|
<field name="name">hr.candidate.view.form.inherit</field>
|
||||||
|
<field name="model">hr.candidate</field>
|
||||||
|
<field name="inherit_id" ref="hr_recruitment.hr_candidate_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<!-- <xpath expr="//field[@name='partner_name']" position="attributes">-->
|
||||||
|
<!-- <attribute name="readonly">1</attribute>-->
|
||||||
|
<!-- </xpath>-->
|
||||||
|
|
||||||
|
<xpath expr="//form/sheet/group" position="before">
|
||||||
|
<group>
|
||||||
|
<group string="Candidate's Name">
|
||||||
|
<field name="first_name"/>
|
||||||
|
<field name="middle_name"/>
|
||||||
|
<field name="last_name"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='partner_phone']" position="after">
|
||||||
|
<field name="alternate_phone"/>
|
||||||
|
</xpath>
|
||||||
|
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- explicit list view definition -->
|
||||||
|
<!--
|
||||||
|
<record model="ir.ui.view" id="hr_recruitment_extended.list">
|
||||||
|
<field name="name">hr_recruitment_extended list</field>
|
||||||
|
<field name="model">hr_recruitment_extended.hr_recruitment_extended</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="value"/>
|
||||||
|
<field name="value2"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- actions opening views on models -->
|
||||||
|
<!--
|
||||||
|
<record model="ir.actions.act_window" id="hr_recruitment_extended.action_window">
|
||||||
|
<field name="name">hr_recruitment_extended window</field>
|
||||||
|
<field name="res_model">hr_recruitment_extended.hr_recruitment_extended</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
</record>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- server action to the one above -->
|
||||||
|
<!--
|
||||||
|
<record model="ir.actions.server" id="hr_recruitment_extended.action_server">
|
||||||
|
<field name="name">hr_recruitment_extended server</field>
|
||||||
|
<field name="model_id" ref="model_hr_recruitment_extended_hr_recruitment_extended"/>
|
||||||
|
<field name="state">code</field>
|
||||||
|
<field name="code">
|
||||||
|
action = {
|
||||||
|
"type": "ir.actions.act_window",
|
||||||
|
"view_mode": "tree,form",
|
||||||
|
"res_model": model._name,
|
||||||
|
}
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- Top menu item -->
|
||||||
|
<!--
|
||||||
|
<menuitem name="hr_recruitment_extended" id="hr_recruitment_extended.menu_root"/>
|
||||||
|
-->
|
||||||
|
<!-- menu categories -->
|
||||||
|
<!--
|
||||||
|
<menuitem name="Menu 1" id="hr_recruitment_extended.menu_1" parent="hr_recruitment_extended.menu_root"/>
|
||||||
|
<menuitem name="Menu 2" id="hr_recruitment_extended.menu_2" parent="hr_recruitment_extended.menu_root"/>
|
||||||
|
-->
|
||||||
|
<!-- actions -->
|
||||||
|
<!--
|
||||||
|
<menuitem name="List" id="hr_recruitment_extended.menu_1_list" parent="hr_recruitment_extended.menu_1"
|
||||||
|
action="hr_recruitment_extended.action_window"/>
|
||||||
|
<menuitem name="Server to list" id="hr_recruitment_extended" parent="hr_recruitment_extended.menu_2"
|
||||||
|
action="hr_recruitment_extended.action_server"/>
|
||||||
|
-->
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="hr_recruitment_stage_form_extended">
|
||||||
|
<field name="name">hr.recruitment.stage.form.extended</field>
|
||||||
|
<field name="model">hr.recruitment.stage</field>
|
||||||
|
<field name="inherit_id" ref="hr_recruitment.hr_recruitment_stage_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='fold']" position="before">
|
||||||
|
<field name="is_default_field" string="Is Default"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<menuitem
|
||||||
|
id="hr_recruitment.menu_hr_recruitment_stage"
|
||||||
|
name="Stages"
|
||||||
|
parent="hr_recruitment.menu_hr_recruitment_config_jobs"
|
||||||
|
action="hr_recruitment.hr_recruitment_stage_act"
|
||||||
|
groups="hr_recruitment.group_hr_recruitment_manager"
|
||||||
|
sequence="1"/>
|
||||||
|
|
||||||
|
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,364 @@
|
||||||
|
<odoo>
|
||||||
|
<template id="apply_extend" inherit_id="website_hr_recruitment.apply">
|
||||||
|
<xpath expr="//form[@id='hr_recruitment_form']" position="replace">
|
||||||
|
<!-- Your custom content here -->
|
||||||
|
<form id="hr_recruitment_form" action="/website/form/" method="post"
|
||||||
|
enctype="multipart/form-data" class="o_mark_required row"
|
||||||
|
data-mark="*" data-model_name="hr.applicant"
|
||||||
|
data-success-mode="redirect" data-success-page="/job-thank-you"
|
||||||
|
hide-change-model="true">
|
||||||
|
|
||||||
|
<div class="s_website_form_rows s_col_no_bgcolor">
|
||||||
|
<div class="s_website_form_rows row s_col_no_bgcolor">
|
||||||
|
<!-- Main Heading for Name Group -->
|
||||||
|
<!-- <div class="col-12">-->
|
||||||
|
<!-- <h3 class="section-heading">Name</h3>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- First Name, Middle Name, Last Name in a row -->
|
||||||
|
<div class="col-12 col-sm-4 mb-0 py-2 s_website_form_field s_website_form_required s_website_form_model_required"
|
||||||
|
data-type="char" data-name="Field">
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<label class="col-12 s_website_form_label" for="recruitment1">
|
||||||
|
<span class="s_website_form_label_content">First Name</span>
|
||||||
|
<span class="s_website_form_mark">*</span>
|
||||||
|
</label>
|
||||||
|
<div class="col-12">
|
||||||
|
<input id="recruitment1" type="text"
|
||||||
|
class="form-control s_website_form_input"
|
||||||
|
name="first_name" required=""
|
||||||
|
data-fill-with="first_name"
|
||||||
|
placeholder="e.g. Pranay"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-sm-4 mb-0 py-2 s_website_form_field"
|
||||||
|
data-type="char" data-name="Field">
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<label class="col-12 s_website_form_label" for="recruitment2">
|
||||||
|
<span class="s_website_form_label_content">Middle Name</span>
|
||||||
|
</label>
|
||||||
|
<div class="col-12">
|
||||||
|
<input id="recruitment2" type="text"
|
||||||
|
class="form-control s_website_form_input"
|
||||||
|
name="middle_name"
|
||||||
|
data-fill-with="middle_name"
|
||||||
|
placeholder="e.g. Kumar"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-sm-4 mb-0 py-2 s_website_form_field s_website_form_required s_website_form_model_required"
|
||||||
|
data-type="char" data-name="Field">
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<label class="col-12 s_website_form_label" for="recruitment3">
|
||||||
|
<span class="s_website_form_label_content">Last Name</span>
|
||||||
|
<span class="s_website_form_mark">*</span>
|
||||||
|
</label>
|
||||||
|
<div class="col-12">
|
||||||
|
<input id="recruitment3" type="text"
|
||||||
|
class="form-control s_website_form_input"
|
||||||
|
name="last_name" required=""
|
||||||
|
data-fill-with="last_name"
|
||||||
|
placeholder="e.g. Gadi (SURNAME)"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Contact Section (Email, Phone, Alternate Phone) -->
|
||||||
|
<div>
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<!-- Main Heading for Contact Group -->
|
||||||
|
<!-- <div class="col-12">-->
|
||||||
|
<!-- <h3 class="section-heading">Contact</h3>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- Email Field -->
|
||||||
|
<div class="col-12 mb-0 py-2 s_website_form_field s_website_form_required"
|
||||||
|
data-type="email" data-name="Field">
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<label class="col-4 col-sm-auto s_website_form_label" style="width: 200px"
|
||||||
|
for="recruitment2">
|
||||||
|
<span class="s_website_form_label_content">Email</span>
|
||||||
|
<span class="s_website_form_mark">*</span>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm">
|
||||||
|
<input id="recruitment2" type="email"
|
||||||
|
class="form-control s_website_form_input"
|
||||||
|
name="email_from" required=""
|
||||||
|
placeholder="e.g. abc@gmail.com"
|
||||||
|
data-fill-with="email_from"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Phone Number Field -->
|
||||||
|
<div class="col-12 mb-0 py-2 s_website_form_field s_website_form_required"
|
||||||
|
data-type="char" data-name="Field">
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<label class="col-4 col-sm-auto s_website_form_label" style="width: 200px"
|
||||||
|
for="recruitmentphone">
|
||||||
|
<span class="s_website_form_label_content">Phone Number</span>
|
||||||
|
<span class="s_website_form_mark">*</span>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm">
|
||||||
|
<input id="recruitmentphone" type="tel"
|
||||||
|
class="form-control s_website_form_input"
|
||||||
|
name="partner_phone" required=""
|
||||||
|
placeholder="+91 1112223334"
|
||||||
|
data-fill-with="partner_phone"/>
|
||||||
|
<div class="alert alert-warning mt-2 d-none" id="phone1-warning"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Alternate Phone Field -->
|
||||||
|
<div class="col-12 mb-0 py-2 s_website_form_field"
|
||||||
|
data-type="char" data-name="Field">
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<label class="col-4 col-sm-auto s_website_form_label" style="width: 200px"
|
||||||
|
for="recruitmentphone2">
|
||||||
|
<span class="s_website_form_label_content">Alternate Number</span>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm">
|
||||||
|
<input id="recruitmentphone2" type="tel"
|
||||||
|
class="form-control s_website_form_input"
|
||||||
|
name="alternate_phone"
|
||||||
|
placeholder="+91 1112223334"
|
||||||
|
data-fill-with="alternate_phone"/>
|
||||||
|
<div class="alert alert-warning mt-2 d-none" id="phone2-warning"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 mb-0 py-2 s_website_form_field s_website_form_required"
|
||||||
|
data-type="char" data-name="Field">
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<label class="col-4 col-sm-auto s_website_form_label" style="width: 200px">
|
||||||
|
<span class="s_website_form_label_content">Degree</span>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm">
|
||||||
|
<select id="fetch_hr_recruitment_degree" class="form-control s_website_form_input"
|
||||||
|
name="degree">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-12 mb-0 py-2 s_website_form_field s_website_form_required"
|
||||||
|
data-type="char" data-name="Field">
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<label class="col-4 col-sm-auto s_website_form_label" style="width: 200px">
|
||||||
|
<span class="s_website_form_label_content">Experience Type</span>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm">
|
||||||
|
<div class="o-row">
|
||||||
|
<select class="form-control s_website_form_input"
|
||||||
|
name="exp_type" required="">
|
||||||
|
<option value="fresher" selected="">Fresher</option>
|
||||||
|
<option value="experienced">Experienced</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-12 mb-0 py-2 s_website_form_field"
|
||||||
|
data-type="char" data-name="Field" id="current_organization_field">
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<label class="col-4 col-sm-auto s_website_form_label" style="width: 200px">
|
||||||
|
<span class="s_website_form_label_content">Current Organization</span>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm">
|
||||||
|
<input type="text"
|
||||||
|
class="form-control s_website_form_input"
|
||||||
|
name="current_organization"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-12 mb-0 py-2 s_website_form_field"
|
||||||
|
data-type="char" data-name="Field" id="current_ctc_field">
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<label class="col-4 col-sm-auto s_website_form_label" style="width: 200px"
|
||||||
|
for="recruitmentctc">
|
||||||
|
<span class="s_website_form_label_content">Current CTC (LPA)</span>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm">
|
||||||
|
<input id="recruitmentctc" type="number"
|
||||||
|
class="form-control s_website_form_input"
|
||||||
|
name="current_ctc"
|
||||||
|
data-fill-with="ctc"/>
|
||||||
|
<div class="alert alert-warning mt-2 d-none" id="ctcwarning-message"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 mb-0 py-2 s_website_form_field"
|
||||||
|
data-type="char" data-name="Field">
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<label class="col-4 col-sm-auto s_website_form_label" style="width: 200px"
|
||||||
|
for="recruitmentctc2">
|
||||||
|
<span class="s_website_form_label_content">Expected CTC (LPA)</span>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm">
|
||||||
|
<input id="recruitmentctc2" type="number"
|
||||||
|
class="form-control s_website_form_input"
|
||||||
|
name="expected_ctc"
|
||||||
|
/>
|
||||||
|
<div class="alert alert-warning mt-2 d-none" id="ctc2warning-message"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 mb-0 py-2 s_website_form_field"
|
||||||
|
data-type="char" data-name="Field">
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<label class="col-4 col-sm-auto s_website_form_label" style="width: 200px">
|
||||||
|
<span class="s_website_form_label_content">Current Location</span>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm">
|
||||||
|
<input type="text"
|
||||||
|
class="form-control s_website_form_input"
|
||||||
|
name="current_location"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 mb-0 py-2 s_website_form_field"
|
||||||
|
data-type="char" data-name="Field" id="preferred_location_field">
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<t t-set="job_id" t-value="job.id"/>
|
||||||
|
<label class="col-4 col-sm-auto s_website_form_label" style="width: 200px"
|
||||||
|
for="preferred_locations">
|
||||||
|
<span class="s_website_form_label_content">Preferred Locations</span>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm">
|
||||||
|
<div id="preferred_locations_container" t-att-data-job_id="job.locations">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 mb-0 py-2 s_website_form_field"
|
||||||
|
data-type="char" data-name="Field" id="notice_period_field">
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<label class="col-4 col-sm-auto s_website_form_label" style="width: 200px">
|
||||||
|
<span class="s_website_form_label_content">Notice Period</span>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm">
|
||||||
|
<div class="o-row">
|
||||||
|
<input type="number"
|
||||||
|
class="form-control s_website_form_input"
|
||||||
|
name="notice_period"
|
||||||
|
/>
|
||||||
|
<select class="form-control s_website_form_input"
|
||||||
|
name="notice_period_type">
|
||||||
|
<option value="day">Day's</option>
|
||||||
|
<option value="month">Month's</option>
|
||||||
|
<option value="year">Year's</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-12 mb-0 py-2 s_website_form_field s_website_form_required"
|
||||||
|
data-type="char" data-name="Field">
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<label class="col-4 col-sm-auto s_website_form_label" style="width: 200px"
|
||||||
|
for="recruitment4">
|
||||||
|
<span class="s_website_form_label_content">LinkedIn Profile</span>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm">
|
||||||
|
<i class="fa fa-linkedin fa-2x o_linkedin_icon"></i>
|
||||||
|
<input id="recruitment4" type="text"
|
||||||
|
class="form-control s_website_form_input pl64"
|
||||||
|
placeholder="e.g. https://www.linkedin.com/in/fpodoo"
|
||||||
|
style="padding-inline-start: calc(40px + 0.375rem)"
|
||||||
|
name="linkedin_profile"
|
||||||
|
data-fill-with="linkedin_profile"/>
|
||||||
|
<div class="alert alert-warning mt-2 d-none" id="linkedin-message"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 mb-0 py-2 s_website_form_field s_website_form_custom"
|
||||||
|
data-type="binary" data-name="Field">
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<label class="col-4 col-sm-auto s_website_form_label" style="width: 200px"
|
||||||
|
for="recruitment6">
|
||||||
|
<span class="s_website_form_label_content">Resume</span>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm">
|
||||||
|
<input id="recruitment6" type="file"
|
||||||
|
class="form-control s_website_form_input o_resume_input"
|
||||||
|
name="Resume"/>
|
||||||
|
<span class="text-muted small">Provide either a resume file or a linkedin profile</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 mb-0 py-2 s_website_form_field"
|
||||||
|
data-type="text" data-name="Field">
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<label class="col-4 col-sm-auto s_website_form_label" style="width: 200px"
|
||||||
|
for="recruitment5">
|
||||||
|
<span class="s_website_form_label_content">Short Introduction</span>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm">
|
||||||
|
<textarea id="recruitment5"
|
||||||
|
class="form-control s_website_form_input"
|
||||||
|
placeholder="Optional introduction, or any question you might have about the job…"
|
||||||
|
name="applicant_notes" rows="5"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 mb-0 py-2 s_website_form_field s_website_form_dnone"
|
||||||
|
data-type="record" data-model="hr.job">
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<label class="col-4 col-sm-auto s_website_form_label" style="width: 200px"
|
||||||
|
for="recruitment7">
|
||||||
|
<span class="s_website_form_label_content">Job</span>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm">
|
||||||
|
<input id="recruitment7" type="hidden"
|
||||||
|
class="form-control s_website_form_input"
|
||||||
|
name="job_id"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 mb-0 py-2 s_website_form_field s_website_form_dnone"
|
||||||
|
data-type="record" data-model="hr.department">
|
||||||
|
<div class="row s_col_no_resize s_col_no_bgcolor">
|
||||||
|
<label class="col-4 col-sm-auto s_website_form_label" style="width: 200px"
|
||||||
|
for="recruitment8">
|
||||||
|
<span class="s_website_form_label_content">Department</span>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm">
|
||||||
|
<input id="recruitment8" type="hidden"
|
||||||
|
class="form-control s_website_form_input"
|
||||||
|
name="department_id"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 s_website_form_submit mb64" data-name="Submit Button">
|
||||||
|
<div class="alert alert-warning mt-2 d-none" id="warning-message"></div>
|
||||||
|
<div style="width: 200px" class="s_website_form_label"/>
|
||||||
|
<a href="#" role="button" class="btn btn-primary btn-lg s_website_form_send" id="apply-btn">I'm
|
||||||
|
feeling lucky
|
||||||
|
</a>
|
||||||
|
<span id="s_website_form_result"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
# for the full list
|
# for the full list
|
||||||
'category': 'Human Resources/Time Off',
|
'category': 'Human Resources/Time Off',
|
||||||
'version': '0.1',
|
'version': '0.1',
|
||||||
'license': 'LGPL-3',
|
|
||||||
|
|
||||||
# any module necessary for this one to work correctly
|
# any module necessary for this one to work correctly
|
||||||
'depends': ['base','hr','hr_holidays','hr_employee_extended'],
|
'depends': ['base','hr','hr_holidays','hr_employee_extended'],
|
||||||
|
|
|
||||||
|
|
@ -343,6 +343,6 @@ class HRLeaveType(models.Model):
|
||||||
[('week', 'Week'),
|
[('week', 'Week'),
|
||||||
('month', 'Month'),
|
('month', 'Month'),
|
||||||
('year', 'Year')],
|
('year', 'Year')],
|
||||||
default='day', string="Limit Type", required=True,
|
default='month', string="Limit Type", required=True,
|
||||||
help="Specifies the type of time period (days, months, or years) for applying the leave request")
|
help="Specifies the type of time period (days, months, or years) for applying the leave request")
|
||||||
limit_emp_type = fields.Many2many('hr.contract.type', string="Employee Type")
|
limit_emp_type = fields.Many2many('hr.contract.type', string="Employee Type")
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
from . import models
|
||||||
|
from . import wizard
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': "Recruitment Requisition",
|
||||||
|
'summary': "To Raise a requist for recruitment",
|
||||||
|
'description': """
|
||||||
|
To Raise a requist for recruitment""",
|
||||||
|
|
||||||
|
'author': "Raman Marikanti",
|
||||||
|
'website': "https://www.ftprotech.com",
|
||||||
|
'category': 'Recruitment',
|
||||||
|
'version': '0.1',
|
||||||
|
'depends': ['hr_recruitment', 'mail','base','hr'],
|
||||||
|
'data': [
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
'data/ir_sequence.xml',
|
||||||
|
'data/mail_templates.xml',
|
||||||
|
'wizard/recruitment_cancel_wizard.xml',
|
||||||
|
'views/hr_requisition.xml',
|
||||||
|
'views/res_config_settings.xml'
|
||||||
|
],
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo noupdate="1">
|
||||||
|
|
||||||
|
<record id="seq_requisition" model="ir.sequence">
|
||||||
|
<field name="name">Requisitions</field>
|
||||||
|
<field name="code">hr.requisitions</field>
|
||||||
|
<field name="prefix">REQ/</field>
|
||||||
|
<field name="padding">5</field>
|
||||||
|
<field name="company_id" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- <record id="group_requisition_approver" model="res.groups">-->
|
||||||
|
<!-- <field name="name">Requisitions Approver</field>-->
|
||||||
|
<!-- <field name="category_id" ref="base.module_category_hidden"/>-->
|
||||||
|
<!-- </record>-->
|
||||||
|
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record id="mail_template_recruitment_requisition_notification" model="mail.template">
|
||||||
|
<field name="name">HR Requisition: Notification</field>
|
||||||
|
<field name="model_id" ref="requisitions.model_recruitment_requisition"/>
|
||||||
|
<field name="email_from">{{ object.requested_by.email_formatted or user.email_formatted }}</field>
|
||||||
|
<field name="email_to">{{ object.hr_manager_id.email }}</field>
|
||||||
|
<field name="subject">New Requisition Submitted: {{ object.name }}</field>
|
||||||
|
<field name="description">Notification sent to HR Manager when a new requisition is submitted.</field>
|
||||||
|
<field name="body_html" type="html">
|
||||||
|
|
||||||
|
<div style="margin: 0px; padding: 0px;">
|
||||||
|
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||||
|
Dear <t t-out="object.hr_manager_id.name or ''">John Doe</t>,
|
||||||
|
<br /><br />
|
||||||
|
A new requisition has been submitted for your review:
|
||||||
|
<ul>
|
||||||
|
<li><strong>Requisition Name:</strong> <t t-out="object.name or ''">New</t></li>
|
||||||
|
<li><strong>Department:</strong> <t t-out="object.department_id.name or ''">Sales</t></li>
|
||||||
|
<li><strong>Position Title:</strong> <t t-out="object.position_title or ''">Sales Manager</t></li>
|
||||||
|
<li><strong>Number of Positions:</strong> <t t-out="object.number_of_positions or 0">3</t></li>
|
||||||
|
<li><strong>Requested By:</strong> <t t-out="object.requested_by.name or ''">Emily Clark</t></li>
|
||||||
|
<li><strong>Requested On:</strong> <t t-out="object.requested_on or ''">2024-12-31</t></li>
|
||||||
|
</ul>
|
||||||
|
<br />
|
||||||
|
<t t-if="object.notes">
|
||||||
|
<strong>Notes:</strong> <t t-out="object.notes or ''">Requirement</t>
|
||||||
|
<br />
|
||||||
|
</t>
|
||||||
|
<br />
|
||||||
|
Please <a t-att-href="'%s/web#id=%d&model=recruitment.requisition&view_type=form' % (object.env['ir.config_parameter'].sudo().get_param('web.base.url'), object.id)" target="_blank">click here</a> to view the requisition details.
|
||||||
|
<br /><br />
|
||||||
|
Regards,
|
||||||
|
<br />
|
||||||
|
<t t-out="object.requested_by.name or ''">Emily Clark</t>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</field>
|
||||||
|
<field name="auto_delete" eval="True"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record id="mail_template_recruitment_requisition_cancellation" model="mail.template">
|
||||||
|
<field name="name">Recruitment Requisition Cancellation</field>
|
||||||
|
<field name="model_id" ref="requisitions.model_recruitment_requisition"/>
|
||||||
|
<field name="email_from">{{ object.requested_by.email_formatted or user.email_formatted }}</field>
|
||||||
|
<field name="email_to">{{ object.hr_manager_id.email }}</field>
|
||||||
|
<field name="subject">Requisition Cancelled: {{ object.name }}</field>
|
||||||
|
<field name="body_html" type="html">
|
||||||
|
<div>
|
||||||
|
<p>Dear <t t-out="object.hr_manager_id.name or ''">HR Manager</t>,</p>
|
||||||
|
<p>The requisition <strong><t t-out="object.name or ''">New</t></strong> has been cancelled for the following reason:</p>
|
||||||
|
<p><t t-out="object.notes or 'No reason provided.'"/></p>
|
||||||
|
<p>
|
||||||
|
You can view the requisition details by clicking the link below:
|
||||||
|
<br/>
|
||||||
|
<a t-att-href="'/web#id=%d&model=recruitment.requisition&view_type=form' % object.id" style="color: #1a73e8; text-decoration: none;">
|
||||||
|
View Requisition
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>Regards,</p>
|
||||||
|
<p><t t-out="object.requested_by.name or ''">Requested By</t></p>
|
||||||
|
</div>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
from . import hr_requisition
|
||||||
|
from . import res_config_settings
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
from odoo import models, fields, api, _
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class RecruitmentRequisition(models.Model):
|
||||||
|
_name = 'recruitment.requisition'
|
||||||
|
_description = 'Recruitment Requisition'
|
||||||
|
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||||
|
|
||||||
|
name = fields.Char(string="Requisition Name", required=True, default="New")
|
||||||
|
department_id = fields.Many2one('hr.department', string="Department", required=True)
|
||||||
|
requested_by = fields.Many2one('res.users', string="Requested By", default=lambda self: self.env.user)
|
||||||
|
requested_on = fields.Date(string='Requested On')
|
||||||
|
hr_manager_id = fields.Many2one('res.users', string="HR Manager", compute='_compute_hr_manager')
|
||||||
|
hr_job = fields.Many2one('')
|
||||||
|
position_title = fields.Char(string="Position Title", required=True)
|
||||||
|
number_of_positions = fields.Integer(string="Number of Positions", required=True)
|
||||||
|
job_description = fields.Html(string="Job Summary")
|
||||||
|
job_id = fields.Many2one('hr.job',"JD ID")
|
||||||
|
state = fields.Selection([
|
||||||
|
('draft', 'Draft'),
|
||||||
|
('waiting_approval', 'Waiting Approval'),
|
||||||
|
('approved', 'Approved'),
|
||||||
|
('done', 'Done'),
|
||||||
|
('cancel', 'Cancelled')
|
||||||
|
], default='draft', track_visibility='onchange')
|
||||||
|
notes = fields.Text(string="Notes")
|
||||||
|
primary_skill_ids = fields.Many2many('hr.skill', "recruitment_requisition_primary_hr_skill_rel",
|
||||||
|
'hr_job_id', 'hr_skill_id', "Primary Skills", required=True)
|
||||||
|
secondary_skill_ids = fields.Many2many('hr.skill', "recruitment_requisition_secondary_hr_skill_rel",
|
||||||
|
'hr_job_id', 'hr_skill_id', "Secondary Skills")
|
||||||
|
assign_to = fields.Many2one('res.users', domain=lambda self: [
|
||||||
|
('groups_id', 'in', self.env.ref('hr_recruitment.group_hr_recruitment_user').id)])
|
||||||
|
|
||||||
|
@api.depends('requested_by')
|
||||||
|
def _compute_hr_manager(self):
|
||||||
|
hr_id = self.env['ir.config_parameter'].sudo().get_param('requisitions.requisition_hr_id')
|
||||||
|
self.hr_manager_id = self.env['res.users'].sudo().browse(int(hr_id)) if hr_id else False
|
||||||
|
|
||||||
|
def action_submit(self):
|
||||||
|
self.name = self.env['ir.sequence'].next_by_code('hr.requisitions')
|
||||||
|
self.requested_on = fields.Date.today()
|
||||||
|
self.state = 'waiting_approval'
|
||||||
|
|
||||||
|
template = self.env.ref('requisitions.mail_template_recruitment_requisition_notification') # Replace `module_name` with your module name
|
||||||
|
if template:
|
||||||
|
template.send_mail(self.id, force_send=True)
|
||||||
|
|
||||||
|
def action_approve(self):
|
||||||
|
for rec in self:
|
||||||
|
if not rec.assign_to:
|
||||||
|
raise ValidationError(_("Please Assign a recruitment user"))
|
||||||
|
rec.state = 'approved'
|
||||||
|
rec.button_create_jd()
|
||||||
|
|
||||||
|
def action_cancel(self):
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': 'recruitment.requisition.cancel.wizard',
|
||||||
|
'view_mode': 'form',
|
||||||
|
'target': 'new',
|
||||||
|
'context': {'active_id': self.id},
|
||||||
|
}
|
||||||
|
|
||||||
|
def button_create_jd(self):
|
||||||
|
self.job_id = self.env['hr.job'].create({
|
||||||
|
'name': self.position_title,
|
||||||
|
'department_id': self.department_id.id,
|
||||||
|
'no_of_recruitment':self.number_of_positions,
|
||||||
|
'description':self.job_description,
|
||||||
|
'skill_ids': [(6, 0, self.primary_skill_ids.ids)],
|
||||||
|
'secondary_skill_ids': [(6, 0, self.secondary_skill_ids.ids)],
|
||||||
|
'requested_by': self.requested_by.partner_id.id,
|
||||||
|
'user_id': self.assign_to.id
|
||||||
|
})
|
||||||
|
|
||||||
|
self.state ='done'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class HRJob(models.Model):
|
||||||
|
_inherit = 'hr.job'
|
||||||
|
|
||||||
|
requested_by = fields.Many2one('res.partner', string="Requested By", default=lambda self: self.env.user.partner_id)
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import fields, models, api, _
|
||||||
|
|
||||||
|
|
||||||
|
class ResConfigSettings(models.TransientModel):
|
||||||
|
_inherit = 'res.config.settings'
|
||||||
|
|
||||||
|
requisition_hr_id = fields.Many2one('res.users',config_parameter='requisitions.requisition_hr_id', string='Requisition HR',
|
||||||
|
domain=lambda self: [
|
||||||
|
('groups_id', 'in', self.env.ref('hr_recruitment.group_hr_recruitment_manager').id)])
|
||||||
|
|
||||||
|
# requisition_md_id = fields.Many2one('res.users', string='Requisition MD')
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_requisition_user,access_requisition_user,model_recruitment_requisition,base.group_user,1,1,1,1
|
||||||
|
access_requisition_manager,access_requisition_manager,model_recruitment_requisition,base.group_user,1,1,1,1
|
||||||
|
|
||||||
|
access_recruitment_requisition_cancel_wizard,recruitment.requisition.cancel.wizard,model_recruitment_requisition_cancel_wizard,base.group_user,1,1,1,1
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
|
||||||
|
<record id="view_hr_job_form" model="ir.ui.view">
|
||||||
|
<field name="name">view.hr.job.form</field>
|
||||||
|
<field name="model">hr.job</field>
|
||||||
|
<field name="inherit_id" ref="hr.view_hr_job_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='department_id']" position="before">
|
||||||
|
<field name="requested_by"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="view_requisition_form" model="ir.ui.view">
|
||||||
|
<field name="name">requisition.form</field>
|
||||||
|
<field name="model">recruitment.requisition</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Recruitment Requisition">
|
||||||
|
<header>
|
||||||
|
<button string="Submit" type="object" name="action_submit" invisible=" state != 'draft'"/>
|
||||||
|
<button string="Approve & Create JD" type="object" name="action_approve" invisible=" state != 'waiting_approval'" groups="hr_recruitment.group_hr_recruitment_manager"/>
|
||||||
|
<button name="button_create_jd" type="object" string="Create JD" invisible=" state != 'approved'"/>
|
||||||
|
<button string="Cancel" type="object" name="action_cancel" invisible=" state in ('draft','done','cancel')"/>
|
||||||
|
<field name="state" widget="statusbar" statusbar_visible="draft,waiting_approval,done" readonly="1"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<div class="row justify-content-between position-relative w-100 m-0 mb-2">
|
||||||
|
<div class="oe_title mw-75 ps-0 pe-2">
|
||||||
|
<h1 class="d-flex flex-row align-items-center">
|
||||||
|
<field name="name" placeholder="Serial Number" required="True" readonly="1" force_save="1"/>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="department_id" readonly="state != 'draft'"/>
|
||||||
|
<field name="requested_by" readonly="1" widget="many2one_avatar_user"/>
|
||||||
|
<field name="requested_on" invisible="requested_on == False"/>
|
||||||
|
<field name="hr_manager_id" widget="many2one_avatar_user"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="position_title" readonly="state != 'draft'"/>
|
||||||
|
<field name="number_of_positions" readonly="state != 'draft'"/>
|
||||||
|
<field name="primary_skill_ids" widget="many2many_tags" options="{'color_field': 'color'}" readonly="state != 'draft'"/>
|
||||||
|
<field name="secondary_skill_ids" widget="many2many_tags" options="{'color_field': 'color'}" readonly="state != 'draft'"/>
|
||||||
|
|
||||||
|
<field name="assign_to" invisible="state not in ['approved','waiting_approval','done']" options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
|
||||||
|
<field name="job_id" invisible="job_id == False" readonly="1" force_save="1"/>
|
||||||
|
</group>
|
||||||
|
<field name="notes" placeholder="Remarks" readonly="state == 'done'"/>
|
||||||
|
</group>
|
||||||
|
<group string="Job Summary">
|
||||||
|
<field name="job_description" readonly="state == 'done'" nolabel="1" placeholder="Please enter the JD Hear"/>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
<chatter reload_on_follower="True"/>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_requisition_tree" model="ir.ui.view">
|
||||||
|
<field name="name">requisition.tree</field>
|
||||||
|
<field name="model">recruitment.requisition</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list string="Recruitment Requisitions">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="department_id"/>
|
||||||
|
<field name="requested_by"/>
|
||||||
|
<field name="state"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="action_recruitment_requisition" model="ir.actions.act_window">
|
||||||
|
<field name="name">Recruitment Requisitions</field>
|
||||||
|
<field name="res_model">recruitment.requisition</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_recruitment_requisition" name="Recruitment Requisition" parent="hr_recruitment.menu_hr_recruitment_root"/>
|
||||||
|
<menuitem id="menu_recruitment_requisition_main" name="Requisitions" parent="menu_recruitment_requisition" action="action_recruitment_requisition"/>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="res_config_settings_view_form_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">res.config.settings.view.form.requisitions.access</field>
|
||||||
|
<field name="model">res.config.settings</field>
|
||||||
|
<field name="inherit_id" ref="hr_recruitment.res_config_settings_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//block[@name='recruitment_in_app_purchases']" position="after">
|
||||||
|
<block title="Requisition Access Control" name="requisition_access_block">
|
||||||
|
<setting string="Requisition HR Approval Access"
|
||||||
|
help="Select the HR responsible for approving the recruitment requisitions."
|
||||||
|
id="requisition_hr_access_control">
|
||||||
|
<field name="requisition_hr_id" options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
|
||||||
|
</setting>
|
||||||
|
</block>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
from . import recruitment_cancel_wizard
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
class RecruitmentRequisitionCancelWizard(models.TransientModel):
|
||||||
|
_name = 'recruitment.requisition.cancel.wizard'
|
||||||
|
_description = 'Requisition Cancellation Wizard'
|
||||||
|
|
||||||
|
cancellation_reason = fields.Text(string="Cancellation Reason", required=True)
|
||||||
|
|
||||||
|
def submit_cancellation(self):
|
||||||
|
requisition = self.env['recruitment.requisition'].browse(self.env.context.get('active_id'))
|
||||||
|
requisition.write({
|
||||||
|
'state': 'cancel',
|
||||||
|
'notes': self.cancellation_reason,
|
||||||
|
})
|
||||||
|
# Send cancellation email
|
||||||
|
template = self.env.ref('requisitions.mail_template_recruitment_requisition_cancellation') # Replace with your module name
|
||||||
|
if template:
|
||||||
|
template.send_mail(requisition.id, force_send=True)
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record id="view_recruitment_requisition_cancel_wizard_form" model="ir.ui.view">
|
||||||
|
<field name="name">recruitment.requisition.cancel.wizard.form</field>
|
||||||
|
<field name="model">recruitment.requisition.cancel.wizard</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Cancel Requisition">
|
||||||
|
<group>
|
||||||
|
<field name="cancellation_reason" placeholder="Enter the reason for cancellation..."/>
|
||||||
|
</group>
|
||||||
|
<footer>
|
||||||
|
<button string="Submit" type="object" name="submit_cancellation" class="btn-primary"/>
|
||||||
|
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
Loading…
Reference in New Issue