Recruitment changes, Menu Contral Center xml change, attachment preview changes
This commit is contained in:
parent
ad5967d420
commit
4b85cc0f59
|
|
@ -24,4 +24,4 @@ class CandidateExperience(models.Model):
|
||||||
@api.depends('experience_code', 'experience_from', 'experience_to')
|
@api.depends('experience_code', 'experience_from', 'experience_to')
|
||||||
def _compute_display_name(self):
|
def _compute_display_name(self):
|
||||||
for template in self:
|
for template in self:
|
||||||
template.display_name = False if not template.experience_code else f"{template.experience_code} ({template.experience_from} - {template.experience_to} Years)"
|
template.display_name = False if not template.experience_code else (f"{template.experience_code} ({template.experience_from} - {template.experience_to} Years)" if template.experience_to > 0 else f"{template.experience_code}")
|
||||||
|
|
@ -7,6 +7,7 @@ import datetime
|
||||||
|
|
||||||
class HRJobRecruitment(models.Model):
|
class HRJobRecruitment(models.Model):
|
||||||
_name = 'hr.job.recruitment'
|
_name = 'hr.job.recruitment'
|
||||||
|
_description = 'Recruitment'
|
||||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||||
_inherits = {'hr.job': 'job_id'}
|
_inherits = {'hr.job': 'job_id'}
|
||||||
_rec_name = 'recruitment_sequence'
|
_rec_name = 'recruitment_sequence'
|
||||||
|
|
@ -233,6 +234,12 @@ class HRJobRecruitment(models.Model):
|
||||||
|
|
||||||
recruitment_status = fields.Selection([('open','Open'),('closed','Closed'),('hold','Hold'),('modified','Modified'),('cancelled','Cancelled')], default='open', tracking=True)
|
recruitment_status = fields.Selection([('open','Open'),('closed','Closed'),('hold','Hold'),('modified','Modified'),('cancelled','Cancelled')], default='open', tracking=True)
|
||||||
|
|
||||||
|
@api.onchange('recruitment_status')
|
||||||
|
def _onchange_recruitment_status(self):
|
||||||
|
for rec in self:
|
||||||
|
if rec.recruitment_status != 'open':
|
||||||
|
rec.website_published == False
|
||||||
|
|
||||||
@api.onchange('job_id','job_category')
|
@api.onchange('job_id','job_category')
|
||||||
def onchange_job_id(self):
|
def onchange_job_id(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,52 @@ from odoo.exceptions import ValidationError
|
||||||
class RecruitmentRequisition(models.Model):
|
class RecruitmentRequisition(models.Model):
|
||||||
_inherit = 'recruitment.requisition'
|
_inherit = 'recruitment.requisition'
|
||||||
|
|
||||||
hr_job_recruitment = fields.Many2one('hr.job.recruitment')
|
hr_job_recruitment = fields.Many2one('hr.job.recruitment', tracking=True)
|
||||||
position_title = fields.Char(string="Position Title", required=False,related='job_id.name')
|
position_title = fields.Char(string="Position Title", required=False,related='job_id.name')
|
||||||
|
job_category = fields.Many2one('job.category', tracking=True)
|
||||||
|
job_priority = fields.Selection([
|
||||||
|
('low', 'Low'),
|
||||||
|
('medium', 'Medium'),
|
||||||
|
('high', 'High')
|
||||||
|
], string='Priority', tracking=True)
|
||||||
|
experience = fields.Many2one('candidate.experience', string="Experience", tracking=True)
|
||||||
|
recruitment_status = fields.Selection([('open','Open'),('closed','Closed'),('hold','Hold'),('modified','Modified'),('cancelled','Cancelled')],related='hr_job_recruitment.recruitment_status',readonly=False, tracking=True)
|
||||||
|
|
||||||
def button_create_jd(self):
|
@api.onchange('recruitment_status')
|
||||||
self.hr_job_recruitment = self.env['hr.job.recruitment'].create({
|
def _onchange_recruitment_status(self):
|
||||||
'job_id': self.job_id.id,
|
for rec in self:
|
||||||
'department_id': self.department_id.id,
|
if rec.recruitment_status != 'open':
|
||||||
'no_of_recruitment':self.number_of_positions,
|
rec.hr_job_recruitment.website_published = False
|
||||||
'description':self.job_description,
|
|
||||||
'skill_ids': [(6, 0, self.primary_skill_ids.ids)],
|
@api.onchange('assign_to')
|
||||||
'secondary_skill_ids': [(6, 0, self.secondary_skill_ids.ids)],
|
def onchange_assign_to(self):
|
||||||
'requested_by': self.requested_by.partner_id.id,
|
for rec in self:
|
||||||
'user_id': self.assign_to.id
|
if rec.hr_job_recruitment and rec.state == 'jd_created':
|
||||||
})
|
rec.hr_job_recruitment.user_id = rec.assign_to.id
|
||||||
|
|
||||||
|
def action_final_approve(self):
|
||||||
|
res = super().action_final_approve()
|
||||||
|
for rec in self:
|
||||||
|
if not rec.hr_job_recruitment:
|
||||||
|
rec.hr_job_recruitment = self.env['hr.job.recruitment'].create({
|
||||||
|
'job_id': rec.job_id.id if rec.job_id else False,
|
||||||
|
'job_category': rec.job_category.id if rec.job_category else False,
|
||||||
|
'no_of_recruitment': rec.number_of_positions,
|
||||||
|
'description': rec.job_description,
|
||||||
|
'skill_ids': [(6, 0, rec.primary_skill_ids.ids)],
|
||||||
|
'secondary_skill_ids': [(6, 0, rec.secondary_skill_ids.ids)],
|
||||||
|
'requested_by': rec.requested_by.partner_id.id if rec.recruitment_type == 'internal' else rec.client_id.id,
|
||||||
|
'recruitment_type': rec.recruitment_type,
|
||||||
|
'contract_type_id': rec.contract_type_id.id if rec.contract_type_id else False,
|
||||||
|
'address_id': rec.client_id.id if rec.client_id.company_type == 'company' else (rec.client_id.parent_id.id if rec.client_id.parent_id else False),
|
||||||
|
'user_id': rec.assign_to.id,
|
||||||
|
'address_id': rec.requested_by.company_id.partner_id.id if rec.requested_by.company_id else '',
|
||||||
|
'job_priority': rec.job_priority,
|
||||||
|
'experience': rec.experience.id if rec.experience else False,
|
||||||
|
'budget':rec.budget,
|
||||||
|
'target_from': rec.target_startdate if rec.target_startdate else fields.Date.today(),
|
||||||
|
'target_to': rec.target_deadline if rec.target_deadline else False,
|
||||||
|
})
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
self.state ='done'
|
|
||||||
|
|
|
||||||
|
|
@ -32,4 +32,3 @@ access_ats_invite_mail_template_wizard,ats.invite.mail.template.wizard.user,hr_r
|
||||||
access_client_submission_mails_template_wizard,client.submission.mails.template.wizard.user,hr_recruitment_extended.model_client_submission_mails_template_wizard,,1,1,1,1
|
access_client_submission_mails_template_wizard,client.submission.mails.template.wizard.user,hr_recruitment_extended.model_client_submission_mails_template_wizard,,1,1,1,1
|
||||||
access_hr_application_public,hr.applicant.public.access,hr_recruitment.model_hr_applicant,base.group_public,1,0,0,0
|
access_hr_application_public,hr.applicant.public.access,hr_recruitment.model_hr_applicant,base.group_public,1,0,0,0
|
||||||
access_hr_application_group_hr,hr.applicant.hr.access,hr_recruitment.model_hr_applicant,hr.group_hr_manager,1,1,0,0
|
access_hr_application_group_hr,hr.applicant.hr.access,hr_recruitment.model_hr_applicant,hr.group_hr_manager,1,1,0,0
|
||||||
,,,,,,,
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<field name="recruitment_sequence"/>
|
<field name="recruitment_sequence"/>
|
||||||
<field name="job_id"/>
|
<field name="job_id"/>
|
||||||
<field name="recruitment_type" optional="hide"/>
|
<field name="recruitment_type" optional="hide"/>
|
||||||
<field name="department_id"/>
|
<field name="requested_by"/>
|
||||||
<field name="no_of_recruitment"/>
|
<field name="no_of_recruitment"/>
|
||||||
<field name="no_of_eligible_submissions" optional="hide"/>
|
<field name="no_of_eligible_submissions" optional="hide"/>
|
||||||
<field name="application_count" string="Applications"
|
<field name="application_count" string="Applications"
|
||||||
|
|
@ -84,7 +84,7 @@
|
||||||
name="%(hr_recruitment.action_hr_job_sources)d" icon="fa-bar-chart-o"
|
name="%(hr_recruitment.action_hr_job_sources)d" icon="fa-bar-chart-o"
|
||||||
context="{'default_job_recruitment_id': id}">
|
context="{'default_job_recruitment_id': id}">
|
||||||
<div class="o_field_widget o_stat_info">
|
<div class="o_field_widget o_stat_info">
|
||||||
<span class="o_stat_text">Trackers</span>
|
<span class="o_stat_text">Job Publish Channels</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -101,7 +101,7 @@
|
||||||
<button class="oe_stat_button" type="action" name="233" icon="fa-bar-chart-o"
|
<button class="oe_stat_button" type="action" name="233" icon="fa-bar-chart-o"
|
||||||
context="{'default_job_recruitment_id': id}">
|
context="{'default_job_recruitment_id': id}">
|
||||||
<div class="o_field_widget o_stat_info">
|
<div class="o_field_widget o_stat_info">
|
||||||
<span class="o_stat_text">Trackers</span>
|
<span class="o_stat_text">Job Publish Channels</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<field name="is_published" widget="website_redirect_button" on_change="1"/>
|
<field name="is_published" widget="website_redirect_button" on_change="1"/>
|
||||||
|
|
@ -236,6 +236,7 @@
|
||||||
<field name="requested_by"/>
|
<field name="requested_by"/>
|
||||||
<separator/>
|
<separator/>
|
||||||
<filter string="Published Records" name="published_records" domain="[('website_published','=',True)]"/>
|
<filter string="Published Records" name="published_records" domain="[('website_published','=',True)]"/>
|
||||||
|
<filter string="UnPublished Records" name="unpublished_records" domain="[('website_published','=',False)]"/>
|
||||||
<separator/>
|
<separator/>
|
||||||
<filter string="My Assignments" name="my_assignments" domain="['|',('user_id', '=', uid), ('interviewer_ids','in',uid)]"/>
|
<filter string="My Assignments" name="my_assignments" domain="['|',('user_id', '=', uid), ('interviewer_ids','in',uid)]"/>
|
||||||
<separator/>
|
<separator/>
|
||||||
|
|
@ -261,9 +262,9 @@
|
||||||
domain="[('recruitment_status','=', 'closed')]"/>
|
domain="[('recruitment_status','=', 'closed')]"/>
|
||||||
<filter name="hold_status" string="Hold"
|
<filter name="hold_status" string="Hold"
|
||||||
domain="[('recruitment_status','=', 'hold')]"/>
|
domain="[('recruitment_status','=', 'hold')]"/>
|
||||||
<filter name="modified_status" string="Open"
|
<filter name="modified_status" string="Modified"
|
||||||
domain="[('recruitment_status','=', 'modified')]"/>
|
domain="[('recruitment_status','=', 'modified')]"/>
|
||||||
<filter name="cancelled_status" string="Open"
|
<filter name="cancelled_status" string="Cancelled"
|
||||||
domain="[('recruitment_status','=', 'cancelled')]"/>
|
domain="[('recruitment_status','=', 'cancelled')]"/>
|
||||||
|
|
||||||
<separator/>
|
<separator/>
|
||||||
|
|
@ -429,12 +430,28 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<record id="action_hr_job_recruitment" model="ir.actions.act_window">
|
<record id="action_hr_job_recruitment_awaiting_published" model="ir.actions.act_window">
|
||||||
<field name="name">Job Positions Recruitment</field>
|
<field name="name">UnPublished Recruitments</field>
|
||||||
<field name="res_model">hr.job.recruitment</field>
|
<field name="res_model">hr.job.recruitment</field>
|
||||||
<field name="view_mode">kanban,list,form,search</field>
|
<field name="view_mode">kanban,list,form,search</field>
|
||||||
<field name="search_view_id" ref="view_job_recruitment_filter"/>
|
<field name="search_view_id" ref="view_job_recruitment_filter"/>
|
||||||
<field name="context">{"search_default_Current":1,"search_default_my_assignments":1,"search_default_published_records":1,'no_of_eligible_submissions': 0}</field>
|
<field name="context">{"search_default_open_status":1,"search_default_my_assignments":1,"search_default_unpublished_records":1,'no_of_eligible_submissions': 0}</field>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="o_view_nocontent_smiling_face">
|
||||||
|
Ready to recruit more efficiently?
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Let's create a job position Recruitment Requests.
|
||||||
|
</p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_hr_job_recruitment_published" model="ir.actions.act_window">
|
||||||
|
<field name="name">Published Recruitments</field>
|
||||||
|
<field name="res_model">hr.job.recruitment</field>
|
||||||
|
<field name="view_mode">kanban,list,form,search</field>
|
||||||
|
<field name="search_view_id" ref="view_job_recruitment_filter"/>
|
||||||
|
<field name="context">{"search_default_open_status":1,"search_default_my_assignments":1,"search_default_published_records":1,'no_of_eligible_submissions': 0}</field>
|
||||||
<field name="help" type="html">
|
<field name="help" type="html">
|
||||||
<p class="o_view_nocontent_smiling_face">
|
<p class="o_view_nocontent_smiling_face">
|
||||||
Ready to recruit more efficiently?
|
Ready to recruit more efficiently?
|
||||||
|
|
@ -454,13 +471,26 @@
|
||||||
active="0"
|
active="0"
|
||||||
sequence="2"/>
|
sequence="2"/>
|
||||||
|
|
||||||
|
<menuitem name="JD"
|
||||||
|
id="menu_hr_job_descriptions"
|
||||||
|
parent="hr_recruitment.menu_hr_recruitment_root"
|
||||||
|
sequence="1"
|
||||||
|
groups="base.group_user"/>
|
||||||
|
|
||||||
<menuitem
|
<menuitem
|
||||||
name="Job Description"
|
name="Awaiting Publication"
|
||||||
id="menu_hr_job_recruitment_interviewer"
|
id="menu_hr_job_recruitment_awaiting_publication"
|
||||||
parent="hr_recruitment.menu_hr_recruitment_root"
|
parent="menu_hr_job_descriptions"
|
||||||
action="action_hr_job_recruitment"
|
action="action_hr_job_recruitment_awaiting_published"
|
||||||
sequence="1"
|
sequence="1"
|
||||||
groups="base.group_user"/>
|
groups="base.group_user"/>
|
||||||
|
<menuitem
|
||||||
|
name="Published"
|
||||||
|
id="menu_hr_job_recruitment_published"
|
||||||
|
parent="menu_hr_job_descriptions"
|
||||||
|
action="action_hr_job_recruitment_published"
|
||||||
|
sequence="2"
|
||||||
|
groups="base.group_user"/>
|
||||||
|
|
||||||
<menuitem
|
<menuitem
|
||||||
name="Job Positions"
|
name="Job Positions"
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,15 @@
|
||||||
<attribute name="required">1</attribute>
|
<attribute name="required">1</attribute>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='job_id']" position="after">
|
<xpath expr="//field[@name='job_id']" position="after">
|
||||||
<field name="hr_job_recruitment" readonly="1" force_save="1"/>
|
<field name="job_category" required="1" readonly="state not in ['draft']"/>
|
||||||
|
<field name="hr_job_recruitment" string="Job Recruitment ID" readonly="not is_hr or state == 'jd_created'" force_save="1" invisible="state not in ['final','jd_created']" options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
|
||||||
|
<field name="recruitment_status" widget="statusbar" readonly="1" force_save="1" invisible="not hr_job_recruitment" options="{'clickable': '1', 'fold_field': 'fold'}"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='requested_on']" position="after">
|
||||||
|
<field name="job_priority" readonly="state in ['jd_created']"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='secondary_skill_ids']" position="after">
|
||||||
|
<field name="experience" readonly="state in ['jd_created']"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,13 @@
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'data/data.xml',
|
'data/data.xml',
|
||||||
'views/masters.xml',
|
'views/masters.xml',
|
||||||
'views/groups.xml',
|
# 'views/groups.xml',
|
||||||
'views/login.xml',
|
'views/login.xml',
|
||||||
'views/menu_access_control_views.xml',
|
'views/menu_access_control_views.xml',
|
||||||
],
|
],
|
||||||
# 'assets': {
|
# 'assets': {
|
||||||
# 'web.assets_backend': [
|
# 'web.assets_backend': [
|
||||||
# 'menu_control_center/static/src/js/menu_service.js',
|
# 'menu_control_center/static/src/js/login.js',
|
||||||
# ],
|
# ],
|
||||||
# },
|
# },
|
||||||
'installable': True,
|
'installable': True,
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,53 @@
|
||||||
from odoo import http, _
|
from odoo import http, _
|
||||||
from odoo.http import request
|
from odoo.http import request
|
||||||
from odoo.addons.web.controllers.home import Home
|
from odoo.addons.web.controllers.home import Home
|
||||||
import werkzeug
|
|
||||||
|
|
||||||
|
|
||||||
class CustomMasterLogin(Home):
|
class CustomMasterLogin(Home):
|
||||||
|
|
||||||
@http.route()
|
@http.route()
|
||||||
def web_login(self, *args, **kw):
|
def web_login(self, *args, **kw):
|
||||||
# Call the original Odoo login
|
request.env['ir.ui.menu'].sudo().clear_caches()
|
||||||
master_selected = kw.get('master_select')
|
master_selected = kw.get('master_select')
|
||||||
|
|
||||||
response = super(CustomMasterLogin, self).web_login(*args, **kw)
|
response = super(CustomMasterLogin, self).web_login(*args, **kw)
|
||||||
|
|
||||||
# We only modify the QWeb response (GET request)
|
|
||||||
if response.is_qweb:
|
if response.is_qweb:
|
||||||
# load your masters
|
response.qcontext['masters'] = request.env['master.control'].sudo().search([])
|
||||||
masters = request.env['master.control'].sudo().search([])
|
|
||||||
response.qcontext['masters'] = masters
|
|
||||||
request.env['ir.ui.menu'].sudo().clear_caches()
|
request.env['ir.ui.menu'].sudo().clear_caches()
|
||||||
request.env['ir.ui.menu'].sudo()._visible_menu_ids()
|
request.env['ir.ui.menu'].sudo()._visible_menu_ids()
|
||||||
|
|
||||||
# After successful login
|
|
||||||
if request.session.uid and master_selected:
|
if request.session.uid and master_selected:
|
||||||
request.session['active_master'] = master_selected
|
user = request.env.user
|
||||||
|
master = request.env['master.control'].sudo().search(
|
||||||
|
[('code', '=', master_selected)], limit=1
|
||||||
|
)
|
||||||
|
|
||||||
|
if master.exists() and master.access_group_ids:
|
||||||
|
if not (user.groups_id & master.access_group_ids):
|
||||||
|
request.session.logout(keep_db=True)
|
||||||
|
|
||||||
|
# Create a response with JavaScript alert
|
||||||
|
html = f"""
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
alert("{_("You don't have access to login to '%s'. Please contact the administrator.") % master.display_name}");
|
||||||
|
window.location.href = '/web/login';
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
return http.Response(html, content_type='text/html')
|
||||||
|
# request.session.uid = None
|
||||||
|
# request.params['login_success'] = False
|
||||||
|
# response.qcontext['error'] = _(
|
||||||
|
# "You don't have access to login to '%s'. "
|
||||||
|
# "Please contact the administrator."
|
||||||
|
# ) % master.display_name
|
||||||
|
# return response
|
||||||
|
|
||||||
|
request.session['active_master'] = master.code
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from . import masters
|
from . import masters
|
||||||
from . import ir_http
|
from . import ir_http
|
||||||
from . import groups
|
# from . import groups
|
||||||
from . import models
|
from . import models
|
||||||
from . import menu
|
from . import menu
|
||||||
|
|
@ -7,8 +7,7 @@ class MasterControl(models.Model):
|
||||||
sequence = fields.Integer()
|
sequence = fields.Integer()
|
||||||
name = fields.Char(string='Master Name', required=True)
|
name = fields.Char(string='Master Name', required=True)
|
||||||
code = fields.Char(string='Code', required=True)
|
code = fields.Char(string='Code', required=True)
|
||||||
default_show = fields.Boolean(default=True)
|
access_group_ids = fields.Many2many('res.groups',string='Roles')
|
||||||
access_group_ids = fields.One2many('group.access.line','master_control_id',string='Roles')
|
|
||||||
|
|
||||||
@api.depends('name', 'code')
|
@api.depends('name', 'code')
|
||||||
def _compute_display_name(self):
|
def _compute_display_name(self):
|
||||||
|
|
@ -18,87 +17,87 @@ class MasterControl(models.Model):
|
||||||
else:
|
else:
|
||||||
record.display_name = False
|
record.display_name = False
|
||||||
|
|
||||||
def action_generate_groups(self):
|
# def action_generate_groups(self):
|
||||||
"""Generate category → groups list"""
|
# """Generate category → groups list"""
|
||||||
for rec in self:
|
# for rec in self:
|
||||||
|
#
|
||||||
|
# # Clear old groups
|
||||||
|
# rec.access_group_ids.unlink()
|
||||||
|
#
|
||||||
|
# groups = self.env['res.groups'].sudo().search([],order='category_id')
|
||||||
|
# show_group = True if rec.default_show else False
|
||||||
|
# print(show_group)
|
||||||
|
# for grp in groups:
|
||||||
|
# self.env['group.access.line'].create({
|
||||||
|
# 'master_control_id': rec.id,
|
||||||
|
# 'group_id': grp.id,
|
||||||
|
# 'show_group': show_group,
|
||||||
|
# })
|
||||||
|
#
|
||||||
|
# return {
|
||||||
|
# 'type': 'ir.actions.client',
|
||||||
|
# 'tag': 'display_notification',
|
||||||
|
# 'params': {
|
||||||
|
# 'title': _('Success'),
|
||||||
|
# 'message': _('Groups generated successfully!'),
|
||||||
|
# 'type': 'success',
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# # -----------------------------------------
|
||||||
|
# # UPDATE GROUPS (Detect new groups)
|
||||||
|
# # -----------------------------------------
|
||||||
|
# def action_update_groups(self):
|
||||||
|
# import pdb
|
||||||
|
# pdb.set_trace()
|
||||||
|
# for rec in self:
|
||||||
|
# created_count = 0
|
||||||
|
#
|
||||||
|
# existing_ids = set(rec.access_group_ids.mapped('group_id.id'))
|
||||||
|
#
|
||||||
|
# categories = self.env['ir.module.category'].search([])
|
||||||
|
#
|
||||||
|
# for category in categories:
|
||||||
|
# groups = self.env['res.groups'].search([
|
||||||
|
# ('category_id', '=', category.id)
|
||||||
|
# ])
|
||||||
|
#
|
||||||
|
# for grp in groups:
|
||||||
|
# # create only missing group
|
||||||
|
# if grp.id not in existing_ids:
|
||||||
|
# rec.access_group_ids.create({
|
||||||
|
# 'master_control_id': rec.id,
|
||||||
|
# 'category_id': category.id,
|
||||||
|
# 'group_id': grp.id,
|
||||||
|
# 'show_group': True if rec.default_show else False,
|
||||||
|
# })
|
||||||
|
# created_count += 1
|
||||||
|
# existing_ids.add(grp.id)
|
||||||
|
#
|
||||||
|
# if created_count:
|
||||||
|
# message = f"Added {created_count} new groups."
|
||||||
|
# msg_type = "success"
|
||||||
|
# else:
|
||||||
|
# message = "No new groups found."
|
||||||
|
# msg_type = "info"
|
||||||
|
#
|
||||||
|
# return {
|
||||||
|
# 'type': 'ir.actions.client',
|
||||||
|
# 'tag': 'display_notification',
|
||||||
|
# 'params': {
|
||||||
|
# 'title': _('Group Update'),
|
||||||
|
# 'message': _(message),
|
||||||
|
# 'type': msg_type,
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
|
||||||
# Clear old groups
|
#
|
||||||
rec.access_group_ids.unlink()
|
# class GroupsAccessLine(models.Model):
|
||||||
|
# _name = 'group.access.line'
|
||||||
groups = self.env['res.groups'].sudo().search([],order='category_id')
|
# _description = 'Group Access Line'
|
||||||
show_group = True if rec.default_show else False
|
# _rec_name = 'group_id'
|
||||||
print(show_group)
|
#
|
||||||
for grp in groups:
|
# category_id = fields.Many2one('ir.module.category', related='group_id.category_id')
|
||||||
self.env['group.access.line'].create({
|
# group_id = fields.Many2one('res.groups', string="Role")
|
||||||
'master_control_id': rec.id,
|
# show_group = fields.Boolean(string="Show", default=True)
|
||||||
'group_id': grp.id,
|
# master_control_id = fields.Many2one('master.control')
|
||||||
'show_group': show_group,
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
'type': 'ir.actions.client',
|
|
||||||
'tag': 'display_notification',
|
|
||||||
'params': {
|
|
||||||
'title': _('Success'),
|
|
||||||
'message': _('Groups generated successfully!'),
|
|
||||||
'type': 'success',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# -----------------------------------------
|
|
||||||
# UPDATE GROUPS (Detect new groups)
|
|
||||||
# -----------------------------------------
|
|
||||||
def action_update_groups(self):
|
|
||||||
import pdb
|
|
||||||
pdb.set_trace()
|
|
||||||
for rec in self:
|
|
||||||
created_count = 0
|
|
||||||
|
|
||||||
existing_ids = set(rec.access_group_ids.mapped('group_id.id'))
|
|
||||||
|
|
||||||
categories = self.env['ir.module.category'].search([])
|
|
||||||
|
|
||||||
for category in categories:
|
|
||||||
groups = self.env['res.groups'].search([
|
|
||||||
('category_id', '=', category.id)
|
|
||||||
])
|
|
||||||
|
|
||||||
for grp in groups:
|
|
||||||
# create only missing group
|
|
||||||
if grp.id not in existing_ids:
|
|
||||||
rec.access_group_ids.create({
|
|
||||||
'master_control_id': rec.id,
|
|
||||||
'category_id': category.id,
|
|
||||||
'group_id': grp.id,
|
|
||||||
'show_group': True if rec.default_show else False,
|
|
||||||
})
|
|
||||||
created_count += 1
|
|
||||||
existing_ids.add(grp.id)
|
|
||||||
|
|
||||||
if created_count:
|
|
||||||
message = f"Added {created_count} new groups."
|
|
||||||
msg_type = "success"
|
|
||||||
else:
|
|
||||||
message = "No new groups found."
|
|
||||||
msg_type = "info"
|
|
||||||
|
|
||||||
return {
|
|
||||||
'type': 'ir.actions.client',
|
|
||||||
'tag': 'display_notification',
|
|
||||||
'params': {
|
|
||||||
'title': _('Group Update'),
|
|
||||||
'message': _(message),
|
|
||||||
'type': msg_type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class GroupsAccessLine(models.Model):
|
|
||||||
_name = 'group.access.line'
|
|
||||||
_description = 'Group Access Line'
|
|
||||||
_rec_name = 'group_id'
|
|
||||||
|
|
||||||
category_id = fields.Many2one('ir.module.category', related='group_id.category_id')
|
|
||||||
group_id = fields.Many2one('res.groups', string="Role")
|
|
||||||
show_group = fields.Boolean(string="Show", default=True)
|
|
||||||
master_control_id = fields.Many2one('master.control')
|
|
||||||
|
|
@ -53,119 +53,143 @@ class MenuAccessControl(models.Model):
|
||||||
all_subs |= self._get_all_submenus(sm)
|
all_subs |= self._get_all_submenus(sm)
|
||||||
return all_subs
|
return all_subs
|
||||||
|
|
||||||
|
def _get_all_menus_sql(self):
|
||||||
|
"""
|
||||||
|
Fetch all active menus with hierarchy using SQL
|
||||||
|
Returns list of dicts:
|
||||||
|
[
|
||||||
|
{id, parent_id}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
self.env.cr.execute("""
|
||||||
|
WITH RECURSIVE menu_tree AS (
|
||||||
|
SELECT id, parent_id
|
||||||
|
FROM ir_ui_menu
|
||||||
|
WHERE parent_id IS NULL
|
||||||
|
AND active = true
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT m.id, m.parent_id
|
||||||
|
FROM ir_ui_menu m
|
||||||
|
JOIN menu_tree mt ON mt.id = m.parent_id
|
||||||
|
WHERE m.active = true
|
||||||
|
)
|
||||||
|
SELECT id, parent_id
|
||||||
|
FROM menu_tree
|
||||||
|
ORDER BY parent_id NULLS FIRST, id
|
||||||
|
""")
|
||||||
|
return self.env.cr.dictfetchall()
|
||||||
|
|
||||||
def action_generate_menus(self):
|
def action_generate_menus(self):
|
||||||
"""
|
"""
|
||||||
Generate main menus and all submenus (recursive),
|
Generate main menus and all submenus (SQL-based),
|
||||||
and set access_line_id for every submenu.
|
and set access_line_id for every submenu.
|
||||||
"""
|
"""
|
||||||
for rec in self:
|
MenuLine = self.env['menu.access.line']
|
||||||
|
|
||||||
# clear old menus
|
for rec in self:
|
||||||
|
# Clear old menus
|
||||||
rec.access_menu_line_ids.unlink()
|
rec.access_menu_line_ids.unlink()
|
||||||
rec.access_sub_menu_line_ids.unlink()
|
rec.access_sub_menu_line_ids.unlink()
|
||||||
|
|
||||||
active_menus = self.env['ir.ui.menu'].search([
|
menus = rec._get_all_menus_sql()
|
||||||
('parent_id', '=', False),
|
|
||||||
('active', '=', True)
|
|
||||||
])
|
|
||||||
|
|
||||||
for menu in active_menus:
|
# Map: parent_id -> children
|
||||||
|
children_map = {}
|
||||||
|
for m in menus:
|
||||||
|
children_map.setdefault(m['parent_id'], []).append(m)
|
||||||
|
|
||||||
# 1️⃣ Create main menu line
|
# ---------- Main menus ----------
|
||||||
main_line = self.env['menu.access.line'].create({
|
for main in children_map.get(None, []):
|
||||||
|
|
||||||
|
main_line = MenuLine.create({
|
||||||
'access_control_id': rec.id,
|
'access_control_id': rec.id,
|
||||||
'menu_id': menu.id,
|
'menu_id': main['id'],
|
||||||
'is_main_menu': True,
|
'is_main_menu': True,
|
||||||
})
|
})
|
||||||
|
|
||||||
# 2️⃣ Fetch all recursive submenus
|
# ---------- Submenus ----------
|
||||||
submenus = self._get_all_submenus(menu)
|
stack = children_map.get(main['id'], [])
|
||||||
|
while stack:
|
||||||
# 3️⃣ Create submenu lines with correct parent
|
sm = stack.pop()
|
||||||
for sm in submenus:
|
MenuLine.create({
|
||||||
self.env['menu.access.line'].create({
|
|
||||||
'access_control_id': rec.id,
|
'access_control_id': rec.id,
|
||||||
'menu_id': sm.id,
|
'menu_id': sm['id'],
|
||||||
'is_main_menu': True,
|
'is_main_menu': True,
|
||||||
'access_line_id': main_line.id, # important
|
'access_line_id': main_line.id,
|
||||||
})
|
})
|
||||||
|
stack.extend(children_map.get(sm['id'], []))
|
||||||
|
|
||||||
def action_update_menus(self):
|
def action_update_menus(self):
|
||||||
line = self.env['menu.access.line']
|
MenuLine = self.env['menu.access.line']
|
||||||
menu = self.env['ir.ui.menu']
|
|
||||||
|
|
||||||
for rec in self:
|
for rec in self:
|
||||||
created_count = 0
|
created_count = 0
|
||||||
|
|
||||||
# All existing menu IDs across BOTH One2manys
|
# Existing menu IDs
|
||||||
existing_menu_ids = set(
|
existing_menu_ids = set(
|
||||||
rec.access_menu_line_ids.mapped('menu_id.id') +
|
rec.access_menu_line_ids.mapped('menu_id.id') +
|
||||||
rec.access_sub_menu_line_ids.mapped('menu_id.id')
|
rec.access_sub_menu_line_ids.mapped('menu_id.id')
|
||||||
)
|
)
|
||||||
|
|
||||||
# ---------- Step 1: Ensure all top-level menus exist ----------
|
menus = rec._get_all_menus_sql()
|
||||||
top_menus = menu.search([
|
|
||||||
('parent_id', '=', False),
|
|
||||||
('active', '=', True),
|
|
||||||
])
|
|
||||||
|
|
||||||
for menu in top_menus:
|
# Map parent -> children
|
||||||
|
children_map = {}
|
||||||
|
for m in menus:
|
||||||
|
children_map.setdefault(m['parent_id'], []).append(m)
|
||||||
|
|
||||||
# Create missing MAIN MENU
|
# ---------- Main menus ----------
|
||||||
main_line = line.search([
|
for main in children_map.get(None, []):
|
||||||
|
|
||||||
|
main_line = MenuLine.search([
|
||||||
('access_control_id', '=', rec.id),
|
('access_control_id', '=', rec.id),
|
||||||
('menu_id', '=', menu.id),
|
('menu_id', '=', main['id']),
|
||||||
('access_line_id', '=', False)
|
('access_line_id', '=', False),
|
||||||
], limit=1)
|
], limit=1)
|
||||||
|
|
||||||
if not main_line:
|
if not main_line:
|
||||||
main_line = line.create({
|
main_line = MenuLine.create({
|
||||||
'access_control_id': rec.id,
|
'access_control_id': rec.id,
|
||||||
'menu_id': menu.id,
|
'menu_id': main['id'],
|
||||||
'is_main_menu': True,
|
'is_main_menu': True,
|
||||||
})
|
})
|
||||||
created_count += 1
|
created_count += 1
|
||||||
existing_menu_ids.add(menu.id)
|
existing_menu_ids.add(main['id'])
|
||||||
|
|
||||||
# ---------- Step 2: Ensure all SUBMENUS exist ----------
|
# ---------- Submenus ----------
|
||||||
submenus = rec._get_all_submenus(menu)
|
stack = children_map.get(main['id'], [])
|
||||||
|
while stack:
|
||||||
|
sm = stack.pop()
|
||||||
|
|
||||||
for sm in submenus:
|
if sm['id'] not in existing_menu_ids:
|
||||||
|
MenuLine.create({
|
||||||
# If submenu is missing → create it
|
|
||||||
if sm.id not in existing_menu_ids:
|
|
||||||
line.create({
|
|
||||||
'access_control_id': rec.id,
|
'access_control_id': rec.id,
|
||||||
'menu_id': sm.id,
|
'menu_id': sm['id'],
|
||||||
'is_main_menu': True,
|
'is_main_menu': True,
|
||||||
'access_line_id': main_line.id,
|
'access_line_id': main_line.id,
|
||||||
})
|
})
|
||||||
created_count += 1
|
created_count += 1
|
||||||
existing_menu_ids.add(sm.id)
|
existing_menu_ids.add(sm['id'])
|
||||||
|
|
||||||
|
stack.extend(children_map.get(sm['id'], []))
|
||||||
|
|
||||||
# ---------- Notification ----------
|
# ---------- Notification ----------
|
||||||
if created_count:
|
return {
|
||||||
return {
|
'type': 'ir.actions.client',
|
||||||
'type': 'ir.actions.client',
|
'tag': 'display_notification',
|
||||||
'tag': 'display_notification',
|
'params': {
|
||||||
'params': {
|
'title': _('Success') if created_count else _('Info'),
|
||||||
'title': _('Success'),
|
'message': (
|
||||||
'message': _('Added %s new menu(s) (including submenus)') % created_count,
|
_('Added %s new menu(s) (including submenus)') % created_count
|
||||||
'type': 'success',
|
if created_count else
|
||||||
'sticky': False,
|
_('No new menus found to add.')
|
||||||
}
|
),
|
||||||
}
|
'type': 'success' if created_count else 'info',
|
||||||
else:
|
'sticky': False,
|
||||||
return {
|
|
||||||
'type': 'ir.actions.client',
|
|
||||||
'tag': 'display_notification',
|
|
||||||
'params': {
|
|
||||||
'title': _('Info'),
|
|
||||||
'message': _('No new menus found to add.'),
|
|
||||||
'type': 'info',
|
|
||||||
'sticky': False,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class MenuAccessLine(models.Model):
|
class MenuAccessLine(models.Model):
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,3 @@ access_menu_access_line,access.menu.access.line,model_menu_access_line,hr.group_
|
||||||
access_menu_control_units,access.menu.control.units,model_menu_control_units,hr.group_hr_manager,1,1,1,1
|
access_menu_control_units,access.menu.control.units,model_menu_control_units,hr.group_hr_manager,1,1,1,1
|
||||||
access_master_control_public,master.control.public,model_master_control,base.group_public,1,0,0,0
|
access_master_control_public,master.control.public,model_master_control,base.group_public,1,0,0,0
|
||||||
access_master_control_hr,master.control.hr,model_master_control,hr.group_hr_manager,1,1,1,1
|
access_master_control_hr,master.control.hr,model_master_control,hr.group_hr_manager,1,1,1,1
|
||||||
group_access_line_access,group_access_line_access,model_group_access_line,,1,1,1,1
|
|
||||||
|
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
odoo.define('menu_control_center.login', function (require) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var WebClient = require('web.WebClient');
|
||||||
|
|
||||||
|
WebClient.include({
|
||||||
|
show_application: function() {
|
||||||
|
var self = this;
|
||||||
|
var res = this._super.apply(this, arguments);
|
||||||
|
|
||||||
|
// Check for flash messages in session
|
||||||
|
var flash_msg = sessionStorage.getItem('flash_message');
|
||||||
|
var flash_type = sessionStorage.getItem('flash_type');
|
||||||
|
|
||||||
|
if (flash_msg) {
|
||||||
|
// Show notification
|
||||||
|
this.do_notify(flash_msg, '', true);
|
||||||
|
|
||||||
|
// Clear stored messages
|
||||||
|
sessionStorage.removeItem('flash_message');
|
||||||
|
sessionStorage.removeItem('flash_type');
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -18,34 +18,28 @@
|
||||||
<field name="model">master.control</field>
|
<field name="model">master.control</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form>
|
<form>
|
||||||
<header>
|
<!-- <header>-->
|
||||||
<button name="action_generate_groups"
|
<!-- <button name="action_generate_groups"-->
|
||||||
type="object"
|
<!-- type="object"-->
|
||||||
string="Generate Groups"
|
<!-- string="Generate Groups"-->
|
||||||
class="btn-primary"/>
|
<!-- class="btn-primary"/>-->
|
||||||
|
|
||||||
<button name="action_update_groups"
|
<!-- <button name="action_update_groups"-->
|
||||||
type="object"
|
<!-- type="object"-->
|
||||||
string="Update Groups"
|
<!-- string="Update Groups"-->
|
||||||
class="btn-secondary"/>
|
<!-- class="btn-secondary"/>-->
|
||||||
</header>
|
<!-- </header>-->
|
||||||
|
|
||||||
<sheet>
|
<sheet>
|
||||||
<group>
|
<group>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="code"/>
|
<field name="code"/>
|
||||||
<field name="default_show" help="Upon Generating this value be placed in Show Option"/>
|
<!-- <field name="default_show" help="Upon Generating this value be placed in Show Option"/>-->
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<notebook>
|
<notebook>
|
||||||
<page string="Roles">
|
<page string="Roles">
|
||||||
<field name="access_group_ids" widget="one2many_search">
|
<field name="access_group_ids" widget="one2many_search"/>
|
||||||
<list editable="bottom">
|
|
||||||
<field name="category_id"/>
|
|
||||||
<field name="group_id"/>
|
|
||||||
<field name="show_group"/>
|
|
||||||
</list>
|
|
||||||
</field>
|
|
||||||
</page>
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
</sheet>
|
</sheet>
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@
|
||||||
</group>
|
</group>
|
||||||
<notebook>
|
<notebook>
|
||||||
<page name="main_menus" string="Menus">
|
<page name="main_menus" string="Menus">
|
||||||
<field name="access_menu_line_ids">
|
<field name="access_menu_line_ids" widget="one2many_search">
|
||||||
<list editable="bottom">
|
<list editable="bottom">
|
||||||
<field name="menu_id"/>
|
<field name="menu_id"/>
|
||||||
<field name="is_main_menu"/>
|
<field name="is_main_menu"/>
|
||||||
|
|
@ -106,8 +106,8 @@
|
||||||
</field>
|
</field>
|
||||||
</page>
|
</page>
|
||||||
<page name="sub_menus" string="Sub Menus">
|
<page name="sub_menus" string="Sub Menus">
|
||||||
<field name="access_sub_menu_line_ids">
|
<field name="access_sub_menu_line_ids" widget="one2many_search">
|
||||||
<list create="0" default_group_by="parent_menu">
|
<list create="0" default_group_by="parent_menu" editable="bottom">
|
||||||
<field name="menu_id" readonly="1" force_save="1"/>
|
<field name="menu_id" readonly="1" force_save="1"/>
|
||||||
<field name="parent_menu"/>
|
<field name="parent_menu"/>
|
||||||
<field name="is_main_menu" string="Show Menu"/>
|
<field name="is_main_menu" string="Show Menu"/>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
'website': "https://www.ftprotech.com",
|
'website': "https://www.ftprotech.com",
|
||||||
'category': 'Recruitment',
|
'category': 'Recruitment',
|
||||||
'version': '0.1',
|
'version': '0.1',
|
||||||
'depends': ['hr_recruitment', 'mail','base','hr'],
|
'depends': ['hr_recruitment', 'mail','base','hr','account'],
|
||||||
'data': [
|
'data': [
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'data/ir_sequence.xml',
|
'data/ir_sequence.xml',
|
||||||
|
|
|
||||||
|
|
@ -12,26 +12,46 @@
|
||||||
<div style="margin: 0px; padding: 0px;">
|
<div style="margin: 0px; padding: 0px;">
|
||||||
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||||
Dear <t t-out="object.hr_manager_id.name or ''">John Doe</t>,
|
Dear <t t-out="object.hr_manager_id.name or ''">John Doe</t>,
|
||||||
<br /><br />
|
<br/>
|
||||||
|
<br/>
|
||||||
A new requisition has been submitted for your review:
|
A new requisition has been submitted for your review:
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Requisition Name:</strong> <t t-out="object.name or ''">New</t></li>
|
<li>
|
||||||
<li><strong>Department:</strong> <t t-out="object.department_id.name or ''">Sales</t></li>
|
<strong>Requisition Name:</strong>
|
||||||
<li><strong>Position Title:</strong> <t t-out="object.position_title or ''">Sales Manager</t></li>
|
<t t-out="object.name or ''">New</t>
|
||||||
<li><strong>Number of Positions:</strong> <t t-out="object.number_of_positions or 0">3</t></li>
|
</li>
|
||||||
<li><strong>Requested By:</strong> <t t-out="object.requested_by.name or ''">Emily Clark</t></li>
|
<li>
|
||||||
<li><strong>Requested On:</strong> <t t-out="object.requested_on or ''">2024-12-31</t></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>
|
</ul>
|
||||||
<br />
|
<br/>
|
||||||
<t t-if="object.notes">
|
<t t-if="object.notes">
|
||||||
<strong>Notes:</strong> <t t-out="object.notes or ''">Requirement</t>
|
<strong>Notes:</strong>
|
||||||
<br />
|
<t t-out="object.notes or ''">Requirement</t>
|
||||||
|
<br/>
|
||||||
</t>
|
</t>
|
||||||
<br />
|
<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.
|
Please <a
|
||||||
<br /><br />
|
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,
|
Regards,
|
||||||
<br />
|
<br/>
|
||||||
<t t-out="object.requested_by.name or ''">Emily Clark</t>
|
<t t-out="object.requested_by.name or ''">Emily Clark</t>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -40,8 +60,6 @@
|
||||||
<field name="auto_delete" eval="True"/>
|
<field name="auto_delete" eval="True"/>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<odoo>
|
|
||||||
<data>
|
|
||||||
<record id="mail_template_recruitment_requisition_cancellation" model="mail.template">
|
<record id="mail_template_recruitment_requisition_cancellation" model="mail.template">
|
||||||
<field name="name">Recruitment Requisition Cancellation</field>
|
<field name="name">Recruitment Requisition Cancellation</field>
|
||||||
<field name="model_id" ref="requisitions.model_recruitment_requisition"/>
|
<field name="model_id" ref="requisitions.model_recruitment_requisition"/>
|
||||||
|
|
@ -50,23 +68,216 @@
|
||||||
<field name="subject">Requisition Cancelled: {{ object.name }}</field>
|
<field name="subject">Requisition Cancelled: {{ object.name }}</field>
|
||||||
<field name="body_html" type="html">
|
<field name="body_html" type="html">
|
||||||
<div>
|
<div>
|
||||||
<p>Dear <t t-out="object.hr_manager_id.name or ''">HR Manager</t>,</p>
|
<p>Dear <t t-out="object.hr_manager_id.name or ''">HR Manager</t>,
|
||||||
<p>The requisition <strong><t t-out="object.name or ''">New</t></strong> has been cancelled for the following reason:</p>
|
</p>
|
||||||
<p><t t-out="object.notes or 'No reason provided.'"/></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>
|
<p>
|
||||||
You can view the requisition details by clicking the link below:
|
You can view the requisition details by clicking the link below:
|
||||||
<br/>
|
<br/>
|
||||||
<a t-att-href="'/web#id=%d&model=recruitment.requisition&view_type=form' % object.id" style="color: #1a73e8; text-decoration: none;">
|
<a t-att-href="'/web#id=%d&model=recruitment.requisition&view_type=form' % object.id"
|
||||||
|
style="color: #1a73e8; text-decoration: none;">
|
||||||
View Requisition
|
View Requisition
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p>Regards,</p>
|
<p>Regards,</p>
|
||||||
<p><t t-out="object.requested_by.name or ''">Requested By</t></p>
|
<p>
|
||||||
|
<t t-out="object.requested_by.name or ''">Requested By</t>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
</data>
|
|
||||||
</odoo>
|
|
||||||
|
<record id="mail_template_recruitment_requisition_finance" model="mail.template">
|
||||||
|
<field name="name">Recruitment Requisition: Sent to Finance</field>
|
||||||
|
<field name="model_id" ref="requisitions.model_recruitment_requisition"/>
|
||||||
|
<field name="email_from">{{ object.hr_manager_id.email_formatted or user.email_formatted }}</field>
|
||||||
|
<field name="email_to">{{ object.finance_manager_id.email }}</field>
|
||||||
|
<field name="subject">Requisition Awaiting Finance Approval: {{ object.name }}</field>
|
||||||
|
<field name="body_html" type="html">
|
||||||
|
<div>
|
||||||
|
<p>Dear
|
||||||
|
<t t-out="object.finance_manager_id.name"/>
|
||||||
|
,
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>The following recruitment requisition has been reviewed by HR and is now awaiting your
|
||||||
|
approval:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Requisition:</strong>
|
||||||
|
<t t-out="object.name"/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Position:</strong>
|
||||||
|
<t t-out="object.job_id.name"/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Positions Required:</strong>
|
||||||
|
<t t-out="object.number_of_positions"/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Requested By:</strong>
|
||||||
|
<t t-out="object.requested_by.name"/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<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">
|
||||||
|
View Requisition
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Regards,<br/>HR Team
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="mail_template_recruitment_requisition_final" model="mail.template">
|
||||||
|
<field name="name">Recruitment Requisition: Finance Approved</field>
|
||||||
|
<field name="model_id" ref="requisitions.model_recruitment_requisition"/>
|
||||||
|
<field name="email_from">{{ object.finance_manager_id.email_formatted or user.email_formatted }}</field>
|
||||||
|
<field name="email_to">{{ object.hr_manager_id.email }}</field>
|
||||||
|
<field name="subject">Finance Approved: {{ object.name }}</field>
|
||||||
|
<field name="body_html" type="html">
|
||||||
|
<div>
|
||||||
|
<p>Dear
|
||||||
|
<t t-out="object.hr_manager_id.name"/>
|
||||||
|
,
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>The finance team has approved the following recruitment requisition:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Requisition:</strong>
|
||||||
|
<t t-out="object.name"/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Position:</strong>
|
||||||
|
<t t-out="object.job_id.name"/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Finance Comments:</strong>
|
||||||
|
<t t-out="object.finance_notes or '—'"/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Please proceed with final approval.</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<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">
|
||||||
|
Review Requisition
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Regards,<br/>Finance Team
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<record id="mail_template_recruitment_requisition_finance_reject" model="mail.template">
|
||||||
|
<field name="name">Recruitment Requisition: Finance Rejected</field>
|
||||||
|
<field name="model_id" ref="requisitions.model_recruitment_requisition"/>
|
||||||
|
<field name="email_from">{{ object.finance_manager_id.email_formatted or user.email_formatted }}</field>
|
||||||
|
<field name="email_to">{{ object.hr_manager_id.email }}</field>
|
||||||
|
<field name="subject">Finance Rejected: {{ object.name }}</field>
|
||||||
|
<field name="body_html" type="html">
|
||||||
|
<div>
|
||||||
|
<p>Dear
|
||||||
|
<t t-out="object.hr_manager_id.name"/>
|
||||||
|
,
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>The finance team has Rejected the following recruitment requisition:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Requisition:</strong>
|
||||||
|
<t t-out="object.name"/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Position:</strong>
|
||||||
|
<t t-out="object.job_id.name"/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Finance Comments:</strong>
|
||||||
|
<t t-out="object.finance_notes or '—'"/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<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">
|
||||||
|
Review Requisition
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Regards,<br/>Finance Team
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="mail_template_recruitment_requisition_jd" model="mail.template">
|
||||||
|
<field name="name">Recruitment Requisition: Approved & JD Creation</field>
|
||||||
|
<field name="model_id" ref="requisitions.model_recruitment_requisition"/>
|
||||||
|
<field name="email_from">{{ user.email_formatted }}</field>
|
||||||
|
<field name="email_to">{{ object.assign_to.email }}</field>
|
||||||
|
<field name="subject">JD Creation Required: {{ object.name }}</field>
|
||||||
|
<field name="body_html" type="html">
|
||||||
|
<div>
|
||||||
|
<p>Dear
|
||||||
|
<t t-out="object.assign_to.name"/>
|
||||||
|
,
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>The following recruitment requisition has received final approval.</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Requisition:</strong>
|
||||||
|
<t t-out="object.name"/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Position:</strong>
|
||||||
|
<t t-out="object.job_id.name"/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Please proceed with Job Description creation.</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<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">
|
||||||
|
Open Requisition
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Regards,<br/>HR Team
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
</data>
|
</data>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
from odoo import models, fields, api, _
|
from odoo import models, fields, api, _
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError, UserError
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RecruitmentRequisition(models.Model):
|
class RecruitmentRequisition(models.Model):
|
||||||
|
|
@ -8,75 +7,133 @@ class RecruitmentRequisition(models.Model):
|
||||||
_description = 'Recruitment Requisition'
|
_description = 'Recruitment Requisition'
|
||||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||||
|
|
||||||
name = fields.Char(string="Requisition Name", required=True, default="New")
|
jd_file = fields.Binary(string="JD")
|
||||||
department_id = fields.Many2one('hr.department', string="Department", required=True)
|
jd_file_name = fields.Char(string="JD")
|
||||||
requested_by = fields.Many2one('res.users', string="Requested By", default=lambda self: self.env.user)
|
name = fields.Char(string="Requisition Name", required=True, default="New", tracking=True)
|
||||||
requested_on = fields.Date(string='Requested On')
|
requested_by = fields.Many2one('res.users', string="Requested By", default=lambda self: self.env.user, tracking=True)
|
||||||
|
client_id = fields.Many2one('res.partner', string="Client", default=lambda self: self.env.user.company_id.partner_id, tracking=True)
|
||||||
|
recruitment_type = fields.Selection([('internal','In-House'),('external','Client-Side')], default='internal')
|
||||||
|
requested_on = fields.Date(string='Requested On', tracking=True)
|
||||||
hr_manager_id = fields.Many2one('res.users', string="HR Manager", compute='_compute_hr_manager')
|
hr_manager_id = fields.Many2one('res.users', string="HR Manager", compute='_compute_hr_manager')
|
||||||
hr_job = fields.Many2one('')
|
finance_manager_id = fields.Many2one('res.users',string="Finance Manager", compute="_compute_finance_manager")
|
||||||
position_title = fields.Char(string="Position Title", required=True)
|
is_hr = fields.Boolean(compute="_compute_check_access")
|
||||||
number_of_positions = fields.Integer(string="Number of Positions", required=True)
|
is_finance_manager = fields.Boolean(compute="_compute_check_access")
|
||||||
|
|
||||||
|
@api.onchange('client_id')
|
||||||
|
def onchange_client_id(self):
|
||||||
|
for rec in self:
|
||||||
|
if rec.client_id != rec.requested_by.company_id.partner_id and rec.client_id != rec.requested_by.partner_id:
|
||||||
|
rec.recruitment_type = 'external'
|
||||||
|
else:
|
||||||
|
rec.recruitment_type = 'internal'
|
||||||
|
|
||||||
|
@api.depends('hr_manager_id','finance_manager_id')
|
||||||
|
def _compute_check_access(self):
|
||||||
|
for rec in self:
|
||||||
|
rec.is_hr = False
|
||||||
|
rec.is_finance_manager = False
|
||||||
|
if (rec.hr_manager_id and rec.hr_manager_id == self.env.user) or self.env.user.id == 2:
|
||||||
|
rec.is_hr = True
|
||||||
|
if (rec.finance_manager_id and rec.finance_manager_id == self.env.user) or self.env.user.id == 2:
|
||||||
|
rec.is_finance_manager = True
|
||||||
|
|
||||||
|
|
||||||
|
hr_job = fields.Many2one('hr.job', tracking=True)
|
||||||
|
number_of_positions = fields.Integer(string="Number of Positions", required=True, tracking=True)
|
||||||
job_description = fields.Html(string="Job Summary")
|
job_description = fields.Html(string="Job Summary")
|
||||||
job_id = fields.Many2one('hr.job',"JD ID")
|
job_id = fields.Many2one('hr.job',"Job Position", tracking=True)
|
||||||
state = fields.Selection([
|
state = fields.Selection([
|
||||||
('draft', 'Draft'),
|
('draft', 'Draft'),
|
||||||
('waiting_approval', 'Waiting Approval'),
|
('hr', 'HR Manager'),
|
||||||
('approved', 'Approved'),
|
('finance', 'Finance'),
|
||||||
('done', 'Done'),
|
('final', 'Final Approval'),
|
||||||
|
('jd_created', 'JD Created'),
|
||||||
|
('rejected', 'Rejected'),
|
||||||
('cancel', 'Cancelled')
|
('cancel', 'Cancelled')
|
||||||
], default='draft', track_visibility='onchange')
|
], default='draft', tracking=True)
|
||||||
notes = fields.Text(string="Notes")
|
notes = fields.Text(string="Notes", tracking=True)
|
||||||
primary_skill_ids = fields.Many2many('hr.skill', "recruitment_requisition_primary_hr_skill_rel",
|
primary_skill_ids = fields.Many2many('hr.skill', "recruitment_requisition_primary_hr_skill_rel",
|
||||||
'hr_job_id', 'hr_skill_id', "Primary Skills", required=True)
|
'hr_job_id', 'hr_skill_id', "Primary Skills", required=True, tracking=True)
|
||||||
secondary_skill_ids = fields.Many2many('hr.skill', "recruitment_requisition_secondary_hr_skill_rel",
|
secondary_skill_ids = fields.Many2many('hr.skill', "recruitment_requisition_secondary_hr_skill_rel",
|
||||||
'hr_job_id', 'hr_skill_id', "Secondary Skills")
|
'hr_job_id', 'hr_skill_id', "Secondary Skills", tracking=True)
|
||||||
assign_to = fields.Many2one('res.users', domain=lambda self: [
|
assign_to = fields.Many2one('res.users', domain=lambda self: [
|
||||||
('groups_id', 'in', self.env.ref('hr_recruitment.group_hr_recruitment_user').id)])
|
('groups_id', 'in', self.env.ref('hr_recruitment.group_hr_recruitment_user').id)], tracking=True)
|
||||||
|
finance_notes = fields.Text(string="Finance Comments", tracking=True)
|
||||||
|
budget = fields.Char(string="Budget",tracking=True)
|
||||||
|
requested_deadline = fields.Date(string="Required Before",tracking=True)
|
||||||
|
target_deadline = fields.Date(string="Target Deadline",tracking=True)
|
||||||
|
target_startdate = fields.Date(string="Start Date",tracking=True)
|
||||||
|
contract_type_id = fields.Many2one('hr.contract.type',string="Employment Type")
|
||||||
|
|
||||||
|
def action_finance_reject(self):
|
||||||
|
for rec in self:
|
||||||
|
if not rec.finance_notes:
|
||||||
|
raise UserError(_("Please enter rejection comments before rejecting."))
|
||||||
|
|
||||||
|
rec.state = 'hr'
|
||||||
|
template = self.env.ref(
|
||||||
|
'requisitions.mail_template_recruitment_requisition_finance_reject',
|
||||||
|
raise_if_not_found=False
|
||||||
|
)
|
||||||
|
if template:
|
||||||
|
template.send_mail(rec.id, force_send=True)
|
||||||
|
|
||||||
@api.depends('requested_by')
|
@api.depends('requested_by')
|
||||||
def _compute_hr_manager(self):
|
def _compute_hr_manager(self):
|
||||||
hr_id = self.env['ir.config_parameter'].sudo().get_param('requisitions.requisition_hr_id')
|
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
|
self.hr_manager_id = self.env['res.users'].sudo().browse(int(hr_id)) if hr_id else False
|
||||||
|
|
||||||
def action_submit(self):
|
@api.depends('requested_by')
|
||||||
|
def _compute_finance_manager(self):
|
||||||
|
finance_id = self.env['ir.config_parameter'].sudo().get_param('requisitions.requisition_finance_manager_id')
|
||||||
|
self.finance_manager_id = self.env['res.users'].sudo().browse(int(finance_id)) if finance_id else False
|
||||||
|
|
||||||
|
def action_submit_hr(self):
|
||||||
self.name = self.env['ir.sequence'].next_by_code('hr.requisitions')
|
self.name = self.env['ir.sequence'].next_by_code('hr.requisitions')
|
||||||
self.requested_on = fields.Date.today()
|
self.requested_on = fields.Date.today()
|
||||||
self.state = 'waiting_approval'
|
self.state = 'hr'
|
||||||
|
if self.requested_deadline:
|
||||||
|
self.target_deadline = self.requested_deadline
|
||||||
|
|
||||||
template = self.env.ref('requisitions.mail_template_recruitment_requisition_notification') # Replace `module_name` with your module name
|
template = self.env.ref('requisitions.mail_template_recruitment_requisition_notification') # Replace `module_name` with your module name
|
||||||
if template:
|
if template:
|
||||||
template.send_mail(self.id, force_send=True)
|
template.send_mail(self.id, force_send=True)
|
||||||
|
|
||||||
def action_approve(self):
|
def action_send_finance(self):
|
||||||
|
self.state = 'finance'
|
||||||
|
template = self.env.ref('requisitions.mail_template_recruitment_requisition_finance', raise_if_not_found=False)
|
||||||
|
if template:
|
||||||
|
template.send_mail(self.id, force_send=True)
|
||||||
|
|
||||||
|
def action_send_hr(self):
|
||||||
|
self.state = 'final'
|
||||||
|
template = self.env.ref('requisitions.mail_template_recruitment_requisition_final', raise_if_not_found=False)
|
||||||
|
if template:
|
||||||
|
template.send_mail(self.id, force_send=True)
|
||||||
|
|
||||||
|
def action_final_approve(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if not rec.assign_to:
|
if not rec.assign_to:
|
||||||
raise ValidationError(_("Please Assign a recruitment user"))
|
raise ValidationError(_("Please assign a recruitment user."))
|
||||||
rec.state = 'approved'
|
|
||||||
rec.button_create_jd()
|
rec.state = 'jd_created'
|
||||||
|
template = self.env.ref(
|
||||||
|
'requisitions.mail_template_recruitment_requisition_jd',
|
||||||
|
raise_if_not_found=False
|
||||||
|
)
|
||||||
|
if template:
|
||||||
|
template.send_mail(rec.id, force_send=True)
|
||||||
|
|
||||||
def action_cancel(self):
|
def action_cancel(self):
|
||||||
return {
|
return {
|
||||||
'type': 'ir.actions.act_window',
|
'type': 'ir.actions.act_window',
|
||||||
'res_model': 'recruitment.requisition.cancel.wizard',
|
'res_model': 'recruitment.requisition.cancel.wizard',
|
||||||
|
'name': _('Cancellation Reason'),
|
||||||
'view_mode': 'form',
|
'view_mode': 'form',
|
||||||
'target': 'new',
|
'target': 'new',
|
||||||
'context': {'active_id': self.id},
|
'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):
|
class HRJob(models.Model):
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ class ResConfigSettings(models.TransientModel):
|
||||||
|
|
||||||
requisition_hr_id = fields.Many2one('res.users',config_parameter='requisitions.requisition_hr_id', string='Requisition HR',
|
requisition_hr_id = fields.Many2one('res.users',config_parameter='requisitions.requisition_hr_id', string='Requisition HR',
|
||||||
domain=lambda self: [
|
domain=lambda self: [
|
||||||
('groups_id', 'in', self.env.ref('hr_recruitment.group_hr_recruitment_manager').id)])
|
('groups_id', 'in', self.env.ref('hr.group_hr_manager').id)])
|
||||||
|
|
||||||
# requisition_md_id = fields.Many2one('res.users', string='Requisition MD')
|
requisition_finance_manager = fields.Many2one('res.users',config_parameter='requisitions.requisition_finance_manager_id', string='Requisition Finance Manager',
|
||||||
|
domain=lambda self: [
|
||||||
|
('groups_id', 'in',
|
||||||
|
self.env.ref('account.group_account_manager').id)])
|
||||||
|
|
@ -1,28 +1,18 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<odoo>
|
<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">
|
<record id="view_requisition_form" model="ir.ui.view">
|
||||||
<field name="name">requisition.form</field>
|
<field name="name">requisition.form</field>
|
||||||
<field name="model">recruitment.requisition</field>
|
<field name="model">recruitment.requisition</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Recruitment Requisition">
|
<form string="Recruitment Requisition">
|
||||||
<header>
|
<header>
|
||||||
<button string="Submit" type="object" name="action_submit" invisible=" state != 'draft'"/>
|
<button string="Submit" type="object" name="action_submit_hr" 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 string="Send to FM" type="object" name="action_send_finance" invisible=" state != 'hr' or not is_hr"/>
|
||||||
<button name="button_create_jd" type="object" string="Create JD" invisible=" state != 'approved'"/>
|
<button string="Approve & Proceed" type="object" name="action_send_hr" invisible=" state != 'finance' or not is_finance_manager"/>
|
||||||
<button string="Cancel" type="object" name="action_cancel" invisible=" state in ('draft','done','cancel')"/>
|
<button string="Reject" type="object" name="action_finance_reject" invisible="state != 'finance' or not is_finance_manager" confirm="Are you sure you want to reject? Please ensure Finance comments are added."/>
|
||||||
<field name="state" widget="statusbar" statusbar_visible="draft,waiting_approval,done" readonly="1"/>
|
<button string="Approve" type="object" name="action_final_approve" invisible=" state != 'final'"/>
|
||||||
|
<button string="Cancel" type="object" name="action_cancel" invisible="state in ('draft','jd_created','cancel') or not is_hr"/>
|
||||||
|
<field name="state" widget="statusbar" statusbar_visible="draft,hr,finance,final,jd_created" readonly="1"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<div class="row justify-content-between position-relative w-100 m-0 mb-2">
|
<div class="row justify-content-between position-relative w-100 m-0 mb-2">
|
||||||
|
|
@ -34,25 +24,45 @@
|
||||||
</div>
|
</div>
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field name="department_id" readonly="state != 'draft'"/>
|
<field name="requested_by" readonly="not is_hr" widget="many2one_avatar_user"/>
|
||||||
<field name="requested_by" readonly="1" widget="many2one_avatar_user"/>
|
<field name="client_id" readonly="not is_hr" invisible="not is_hr" force_save="1"/>
|
||||||
<field name="requested_on" invisible="requested_on == False"/>
|
<field name="recruitment_type" readonly="not is_hr" force_save="1"/>
|
||||||
|
<field name="requested_on" invisible="requested_on == False" readonly="not is_hr" force_save="1"/>
|
||||||
|
<field name="requested_deadline" readonly="state != 'draft'" invisible="state != 'draft' and not requested_deadline"/>
|
||||||
<field name="hr_manager_id" widget="many2one_avatar_user"/>
|
<field name="hr_manager_id" widget="many2one_avatar_user"/>
|
||||||
|
<field name="finance_manager_id" widget="many2one_avatar_user"/>
|
||||||
|
<field name="jd_file" filename="jd_file_name"
|
||||||
|
widget="binary" force_save="1"/>
|
||||||
|
<field name="jd_file_name" invisible="1" force_save="1"/>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field name="position_title" readonly="state != 'draft'"/>
|
<field name="job_id" invisible="job_id == False" readonly="1" force_save="1" options="{'no_quick_create':True,'no_open':True}"/>
|
||||||
<field name="number_of_positions" readonly="state != 'draft'"/>
|
<field name="number_of_positions" readonly="(state != 'draft' and not is_hr) or state == 'jd_created'"/>
|
||||||
<field name="primary_skill_ids" widget="many2many_tags" options="{'color_field': 'color'}" 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="secondary_skill_ids" widget="many2many_tags" options="{'color_field': 'color'}" readonly="state != 'draft'"/>
|
||||||
|
<field name="contract_type_id" readonly="state in ['jd_created']"/>
|
||||||
|
<field name="is_hr" invisible="1" force_save="1"/>
|
||||||
|
<field name="is_finance_manager" invisible="1" force_save="1"/>
|
||||||
|
<field name="assign_to" invisible="state not in ['final','jd_created']" options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
|
||||||
|
<field name="target_startdate" invisible="1" readonly="state in ['jd_created']"/>
|
||||||
|
<field name="target_deadline" widget="daterange" string="Target Deadline" options="{'start_date_field': 'target_startdate'}" on_change="1" readonly="state in ['jd_created']"/>
|
||||||
|
</group>
|
||||||
|
<field name="notes" placeholder="Remarks" readonly="state == 'jd_created'" invisible="not notes"/>
|
||||||
|
</group>
|
||||||
|
<notebook>
|
||||||
|
<page string="Job Summary" name="job_summary">
|
||||||
|
<field name="job_description" readonly="state == 'jd_created'" nolabel="1" placeholder="Please enter the JD Hear"/>
|
||||||
|
</page>
|
||||||
|
<page name="finance_notes" string="Finance Comments" invisible="state == 'draft' or (not is_hr and not is_finance_manager)">
|
||||||
|
<group>
|
||||||
|
<field name="budget" placeholder="15.5 - 17.5LPA"/>
|
||||||
|
</group>
|
||||||
|
<group string="Comments">
|
||||||
|
<field name="finance_notes" nolabel="1" string="Comments" placeholder="Enter the Finance Comments in Hear..."/>
|
||||||
|
</group>
|
||||||
|
|
||||||
<field name="assign_to" invisible="state not in ['approved','waiting_approval','done']" options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
|
</page>
|
||||||
<field name="job_id" invisible="job_id == False" readonly="1" force_save="1"/>
|
</notebook>
|
||||||
</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>
|
</sheet>
|
||||||
<chatter reload_on_follower="True"/>
|
<chatter reload_on_follower="True"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -65,7 +75,6 @@
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<list string="Recruitment Requisitions">
|
<list string="Recruitment Requisitions">
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="department_id"/>
|
|
||||||
<field name="requested_by"/>
|
<field name="requested_by"/>
|
||||||
<field name="state"/>
|
<field name="state"/>
|
||||||
</list>
|
</list>
|
||||||
|
|
@ -77,6 +86,13 @@
|
||||||
<field name="view_mode">list,form</field>
|
<field name="view_mode">list,form</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<menuitem id="menu_recruitment_requisition" name="Recruitment Requisition" active='0' parent="hr_recruitment.menu_hr_recruitment_root"/>
|
<menuitem
|
||||||
|
name="Recruitment"
|
||||||
|
id="menu_hr_recruitment_root"
|
||||||
|
web_icon="hr_recruitment,static/description/icon.png"
|
||||||
|
groups="hr_recruitment.group_hr_recruitment_user,hr_recruitment.group_hr_recruitment_interviewer"
|
||||||
|
sequence="210"/>
|
||||||
|
|
||||||
|
<menuitem id="menu_recruitment_requisition" name="Recruitment Requisition" active='1' sequence="0" parent="hr_recruitment.menu_hr_recruitment_root"/>
|
||||||
<menuitem id="menu_recruitment_requisition_main" name="Requisitions" parent="menu_recruitment_requisition" action="action_recruitment_requisition"/>
|
<menuitem id="menu_recruitment_requisition_main" name="Requisitions" parent="menu_recruitment_requisition" action="action_recruitment_requisition"/>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,16 @@
|
||||||
<xpath expr="//block[@name='recruitment_in_app_purchases']" position="after">
|
<xpath expr="//block[@name='recruitment_in_app_purchases']" position="after">
|
||||||
<block title="Requisition Access Control" name="requisition_access_block">
|
<block title="Requisition Access Control" name="requisition_access_block">
|
||||||
<setting string="Requisition HR Approval Access"
|
<setting string="Requisition HR Approval Access"
|
||||||
help="Select the HR responsible for approving the recruitment requisitions."
|
help="Select the HR responsible for Requisitions."
|
||||||
id="requisition_hr_access_control">
|
id="requisition_hr_access_control">
|
||||||
<field name="requisition_hr_id" options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
|
<field name="requisition_hr_id" options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
|
||||||
</setting>
|
</setting>
|
||||||
|
<setting string="Requisition Finance Approval Access"
|
||||||
|
help="Select the Finance Manager responsible for Requisitions."
|
||||||
|
id="requisition_finance_access_control">
|
||||||
|
<field name="requisition_finance_manager" options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
|
||||||
|
</setting>
|
||||||
|
|
||||||
</block>
|
</block>
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Cancel Requisition">
|
<form string="Cancel Requisition">
|
||||||
<group>
|
<group>
|
||||||
<field name="cancellation_reason" placeholder="Enter the reason for cancellation..."/>
|
<field name="cancellation_reason" nolabel="1" placeholder="Enter the reason for cancellation..."/>
|
||||||
</group>
|
</group>
|
||||||
<footer>
|
<footer>
|
||||||
<button string="Submit" type="object" name="submit_cancellation" class="btn-primary"/>
|
<button string="Submit" type="object" name="submit_cancellation" class="btn-primary"/>
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,15 @@
|
||||||
|
|
||||||
<t t-name="universal_attachment_preview.PopupPreview">
|
<t t-name="universal_attachment_preview.PopupPreview">
|
||||||
<Dialog title="props.filename" size="'lg'">
|
<Dialog title="props.filename" size="'lg'">
|
||||||
|
<p class="text-muted mt-2">
|
||||||
|
<t t-esc="props.mimetype"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
<t t-if="props.mimetype.endsWith('pdf')">
|
<t t-if="props.mimetype.endsWith('pdf')">
|
||||||
<iframe t-att-src="props.url"
|
<iframe t-att-src="props.url"
|
||||||
style="width:100%; height:500px;"
|
style="width:100%; height:500px;"
|
||||||
frameborder="0">
|
frameborder="0">
|
||||||
</iframe>
|
</iframe>
|
||||||
|
|
||||||
</t>
|
</t>
|
||||||
<t t-elif="props.mimetype.startsWith('image')">
|
<t t-elif="props.mimetype.startsWith('image')">
|
||||||
<img t-att-src="props.url"
|
<img t-att-src="props.url"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue