Recruitment changes, Menu Contral Center xml change, attachment preview changes

This commit is contained in:
Pranay 2026-02-04 11:57:31 +05:30
parent ad5967d420
commit 4b85cc0f59
23 changed files with 751 additions and 310 deletions

View File

@ -24,4 +24,4 @@ class CandidateExperience(models.Model):
@api.depends('experience_code', 'experience_from', 'experience_to')
def _compute_display_name(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}")

View File

@ -7,6 +7,7 @@ import datetime
class HRJobRecruitment(models.Model):
_name = 'hr.job.recruitment'
_description = 'Recruitment'
_inherit = ['mail.thread', 'mail.activity.mixin']
_inherits = {'hr.job': 'job_id'}
_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)
@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')
def onchange_job_id(self):
for rec in self:

View File

@ -6,19 +6,52 @@ from odoo.exceptions import ValidationError
class RecruitmentRequisition(models.Model):
_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')
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):
self.hr_job_recruitment = self.env['hr.job.recruitment'].create({
'job_id': self.job_id.id,
'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
})
@api.onchange('recruitment_status')
def _onchange_recruitment_status(self):
for rec in self:
if rec.recruitment_status != 'open':
rec.hr_job_recruitment.website_published = False
@api.onchange('assign_to')
def onchange_assign_to(self):
for rec in self:
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'

View File

@ -31,5 +31,4 @@ access_application_stage_status,application.stage.status,model_application_stage
access_ats_invite_mail_template_wizard,ats.invite.mail.template.wizard.user,hr_recruitment_extended.model_ats_invite_mail_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_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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
31
32
33
34

View File

@ -12,7 +12,7 @@
<field name="recruitment_sequence"/>
<field name="job_id"/>
<field name="recruitment_type" optional="hide"/>
<field name="department_id"/>
<field name="requested_by"/>
<field name="no_of_recruitment"/>
<field name="no_of_eligible_submissions" optional="hide"/>
<field name="application_count" string="Applications"
@ -84,7 +84,7 @@
name="%(hr_recruitment.action_hr_job_sources)d" icon="fa-bar-chart-o"
context="{'default_job_recruitment_id': id}">
<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>
</button>
</div>
@ -101,7 +101,7 @@
<button class="oe_stat_button" type="action" name="233" icon="fa-bar-chart-o"
context="{'default_job_recruitment_id': id}">
<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>
</button>
<field name="is_published" widget="website_redirect_button" on_change="1"/>
@ -236,6 +236,7 @@
<field name="requested_by"/>
<separator/>
<filter string="Published Records" name="published_records" domain="[('website_published','=',True)]"/>
<filter string="UnPublished Records" name="unpublished_records" domain="[('website_published','=',False)]"/>
<separator/>
<filter string="My Assignments" name="my_assignments" domain="['|',('user_id', '=', uid), ('interviewer_ids','in',uid)]"/>
<separator/>
@ -261,9 +262,9 @@
domain="[('recruitment_status','=', 'closed')]"/>
<filter name="hold_status" string="Hold"
domain="[('recruitment_status','=', 'hold')]"/>
<filter name="modified_status" string="Open"
<filter name="modified_status" string="Modified"
domain="[('recruitment_status','=', 'modified')]"/>
<filter name="cancelled_status" string="Open"
<filter name="cancelled_status" string="Cancelled"
domain="[('recruitment_status','=', 'cancelled')]"/>
<separator/>
@ -429,12 +430,28 @@
<record id="action_hr_job_recruitment" model="ir.actions.act_window">
<field name="name">Job Positions Recruitment</field>
<record id="action_hr_job_recruitment_awaiting_published" model="ir.actions.act_window">
<field name="name">UnPublished 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_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">
<p class="o_view_nocontent_smiling_face">
Ready to recruit more efficiently?
@ -454,13 +471,26 @@
active="0"
sequence="2"/>
<menuitem name="JD"
id="menu_hr_job_descriptions"
parent="hr_recruitment.menu_hr_recruitment_root"
sequence="1"
groups="base.group_user"/>
<menuitem
name="Job Description"
id="menu_hr_job_recruitment_interviewer"
parent="hr_recruitment.menu_hr_recruitment_root"
action="action_hr_job_recruitment"
name="Awaiting Publication"
id="menu_hr_job_recruitment_awaiting_publication"
parent="menu_hr_job_descriptions"
action="action_hr_job_recruitment_awaiting_published"
sequence="1"
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
name="Job Positions"

View File

@ -13,7 +13,15 @@
<attribute name="required">1</attribute>
</xpath>
<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>
</field>
</record>

View File

@ -15,13 +15,13 @@
'security/ir.model.access.csv',
'data/data.xml',
'views/masters.xml',
'views/groups.xml',
# 'views/groups.xml',
'views/login.xml',
'views/menu_access_control_views.xml',
],
# 'assets': {
# 'web.assets_backend': [
# 'menu_control_center/static/src/js/menu_service.js',
# 'menu_control_center/static/src/js/login.js',
# ],
# },
'installable': True,

View File

@ -1,28 +1,53 @@
from odoo import http, _
from odoo.http import request
from odoo.addons.web.controllers.home import Home
import werkzeug
class CustomMasterLogin(Home):
@http.route()
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')
response = super(CustomMasterLogin, self).web_login(*args, **kw)
# We only modify the QWeb response (GET request)
if response.is_qweb:
# load your masters
masters = request.env['master.control'].sudo().search([])
response.qcontext['masters'] = masters
response.qcontext['masters'] = request.env['master.control'].sudo().search([])
request.env['ir.ui.menu'].sudo().clear_caches()
request.env['ir.ui.menu'].sudo()._visible_menu_ids()
# After successful login
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

View File

@ -1,5 +1,5 @@
from . import masters
from . import ir_http
from . import groups
# from . import groups
from . import models
from . import menu

View File

@ -7,8 +7,7 @@ class MasterControl(models.Model):
sequence = fields.Integer()
name = fields.Char(string='Master Name', required=True)
code = fields.Char(string='Code', required=True)
default_show = fields.Boolean(default=True)
access_group_ids = fields.One2many('group.access.line','master_control_id',string='Roles')
access_group_ids = fields.Many2many('res.groups',string='Roles')
@api.depends('name', 'code')
def _compute_display_name(self):
@ -18,87 +17,87 @@ class MasterControl(models.Model):
else:
record.display_name = False
def action_generate_groups(self):
"""Generate category → groups list"""
for rec in self:
# def action_generate_groups(self):
# """Generate category → groups list"""
# 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()
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,
}
}
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')
#
# 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')

View File

@ -53,119 +53,143 @@ class MenuAccessControl(models.Model):
all_subs |= self._get_all_submenus(sm)
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):
"""
Generate main menus and all submenus (recursive),
Generate main menus and all submenus (SQL-based),
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_sub_menu_line_ids.unlink()
active_menus = self.env['ir.ui.menu'].search([
('parent_id', '=', False),
('active', '=', True)
])
menus = rec._get_all_menus_sql()
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_line = self.env['menu.access.line'].create({
# ---------- Main menus ----------
for main in children_map.get(None, []):
main_line = MenuLine.create({
'access_control_id': rec.id,
'menu_id': menu.id,
'menu_id': main['id'],
'is_main_menu': True,
})
# 2⃣ Fetch all recursive submenus
submenus = self._get_all_submenus(menu)
# 3⃣ Create submenu lines with correct parent
for sm in submenus:
self.env['menu.access.line'].create({
# ---------- Submenus ----------
stack = children_map.get(main['id'], [])
while stack:
sm = stack.pop()
MenuLine.create({
'access_control_id': rec.id,
'menu_id': sm.id,
'menu_id': sm['id'],
'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):
line = self.env['menu.access.line']
menu = self.env['ir.ui.menu']
MenuLine = self.env['menu.access.line']
for rec in self:
created_count = 0
# All existing menu IDs across BOTH One2manys
# Existing menu IDs
existing_menu_ids = set(
rec.access_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 ----------
top_menus = menu.search([
('parent_id', '=', False),
('active', '=', True),
])
menus = rec._get_all_menus_sql()
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_line = line.search([
# ---------- Main menus ----------
for main in children_map.get(None, []):
main_line = MenuLine.search([
('access_control_id', '=', rec.id),
('menu_id', '=', menu.id),
('access_line_id', '=', False)
('menu_id', '=', main['id']),
('access_line_id', '=', False),
], limit=1)
if not main_line:
main_line = line.create({
main_line = MenuLine.create({
'access_control_id': rec.id,
'menu_id': menu.id,
'menu_id': main['id'],
'is_main_menu': True,
})
created_count += 1
existing_menu_ids.add(menu.id)
existing_menu_ids.add(main['id'])
# ---------- Step 2: Ensure all SUBMENUS exist ----------
submenus = rec._get_all_submenus(menu)
# ---------- Submenus ----------
stack = children_map.get(main['id'], [])
while stack:
sm = stack.pop()
for sm in submenus:
# If submenu is missing → create it
if sm.id not in existing_menu_ids:
line.create({
if sm['id'] not in existing_menu_ids:
MenuLine.create({
'access_control_id': rec.id,
'menu_id': sm.id,
'menu_id': sm['id'],
'is_main_menu': True,
'access_line_id': main_line.id,
})
created_count += 1
existing_menu_ids.add(sm.id)
existing_menu_ids.add(sm['id'])
stack.extend(children_map.get(sm['id'], []))
# ---------- Notification ----------
if created_count:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Success'),
'message': _('Added %s new menu(s) (including submenus)') % created_count,
'type': 'success',
'sticky': False,
}
}
else:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Info'),
'message': _('No new menus found to add.'),
'type': 'info',
'sticky': False,
}
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Success') if created_count else _('Info'),
'message': (
_('Added %s new menu(s) (including submenus)') % created_count
if created_count else
_('No new menus found to add.')
),
'type': 'success' if created_count else 'info',
'sticky': False,
}
}
class MenuAccessLine(models.Model):

View File

@ -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_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
group_access_line_access,group_access_line_access,model_group_access_line,,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
4 access_menu_control_units access.menu.control.units model_menu_control_units hr.group_hr_manager 1 1 1 1
5 access_master_control_public master.control.public model_master_control base.group_public 1 0 0 0
6 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

View File

@ -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;
},
});
});

View File

@ -18,34 +18,28 @@
<field name="model">master.control</field>
<field name="arch" type="xml">
<form>
<header>
<button name="action_generate_groups"
type="object"
string="Generate Groups"
class="btn-primary"/>
<!-- <header>-->
<!-- <button name="action_generate_groups"-->
<!-- type="object"-->
<!-- string="Generate Groups"-->
<!-- class="btn-primary"/>-->
<button name="action_update_groups"
type="object"
string="Update Groups"
class="btn-secondary"/>
</header>
<!-- <button name="action_update_groups"-->
<!-- type="object"-->
<!-- string="Update Groups"-->
<!-- class="btn-secondary"/>-->
<!-- </header>-->
<sheet>
<group>
<field name="name"/>
<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>
<notebook>
<page string="Roles">
<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>
<field name="access_group_ids" widget="one2many_search"/>
</page>
</notebook>
</sheet>

View File

@ -96,7 +96,7 @@
</group>
<notebook>
<page name="main_menus" string="Menus">
<field name="access_menu_line_ids">
<field name="access_menu_line_ids" widget="one2many_search">
<list editable="bottom">
<field name="menu_id"/>
<field name="is_main_menu"/>
@ -106,8 +106,8 @@
</field>
</page>
<page name="sub_menus" string="Sub Menus">
<field name="access_sub_menu_line_ids">
<list create="0" default_group_by="parent_menu">
<field name="access_sub_menu_line_ids" widget="one2many_search">
<list create="0" default_group_by="parent_menu" editable="bottom">
<field name="menu_id" readonly="1" force_save="1"/>
<field name="parent_menu"/>
<field name="is_main_menu" string="Show Menu"/>

View File

@ -9,7 +9,7 @@
'website': "https://www.ftprotech.com",
'category': 'Recruitment',
'version': '0.1',
'depends': ['hr_recruitment', 'mail','base','hr'],
'depends': ['hr_recruitment', 'mail','base','hr','account'],
'data': [
'security/ir.model.access.csv',
'data/ir_sequence.xml',

View File

@ -12,26 +12,46 @@
<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 />
<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>
<li>
<strong>Requisition Name:</strong>
<t t-out="object.name or ''">New</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 />
<br/>
<t t-if="object.notes">
<strong>Notes:</strong> <t t-out="object.notes or ''">Requirement</t>
<br />
<strong>Notes:</strong>
<t t-out="object.notes or ''">Requirement</t>
<br/>
</t>
<br />
Please <a t-att-href="'%s/web#id=%d&amp;model=recruitment.requisition&amp;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 />
<br/>
Please <a
t-att-href="'%s/web#id=%d&amp;model=recruitment.requisition&amp;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 />
<br/>
<t t-out="object.requested_by.name or ''">Emily Clark</t>
</p>
</div>
@ -40,8 +60,6 @@
<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"/>
@ -50,23 +68,216 @@
<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>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&amp;model=recruitment.requisition&amp;view_type=form' % object.id" style="color: #1a73e8; text-decoration: none;">
<a t-att-href="'/web#id=%d&amp;model=recruitment.requisition&amp;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>
<p>
<t t-out="object.requested_by.name or ''">Requested By</t>
</p>
</div>
</field>
</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&amp;model=recruitment.requisition&amp;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&amp;model=recruitment.requisition&amp;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&amp;model=recruitment.requisition&amp;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 &amp; 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&amp;model=recruitment.requisition&amp;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>
</odoo>

View File

@ -1,6 +1,5 @@
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
from odoo.exceptions import ValidationError, UserError
class RecruitmentRequisition(models.Model):
@ -8,75 +7,133 @@ class RecruitmentRequisition(models.Model):
_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')
jd_file = fields.Binary(string="JD")
jd_file_name = fields.Char(string="JD")
name = fields.Char(string="Requisition Name", required=True, default="New", tracking=True)
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_job = fields.Many2one('')
position_title = fields.Char(string="Position Title", required=True)
number_of_positions = fields.Integer(string="Number of Positions", required=True)
finance_manager_id = fields.Many2one('res.users',string="Finance Manager", compute="_compute_finance_manager")
is_hr = fields.Boolean(compute="_compute_check_access")
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_id = fields.Many2one('hr.job',"JD ID")
job_id = fields.Many2one('hr.job',"Job Position", tracking=True)
state = fields.Selection([
('draft', 'Draft'),
('waiting_approval', 'Waiting Approval'),
('approved', 'Approved'),
('done', 'Done'),
('hr', 'HR Manager'),
('finance', 'Finance'),
('final', 'Final Approval'),
('jd_created', 'JD Created'),
('rejected', 'Rejected'),
('cancel', 'Cancelled')
], default='draft', track_visibility='onchange')
notes = fields.Text(string="Notes")
], default='draft', tracking=True)
notes = fields.Text(string="Notes", tracking=True)
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",
'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: [
('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')
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):
@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.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
if template:
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:
if not rec.assign_to:
raise ValidationError(_("Please Assign a recruitment user"))
rec.state = 'approved'
rec.button_create_jd()
raise ValidationError(_("Please assign a recruitment user."))
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):
return {
'type': 'ir.actions.act_window',
'res_model': 'recruitment.requisition.cancel.wizard',
'name': _('Cancellation Reason'),
'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):

View File

@ -9,6 +9,9 @@ class ResConfigSettings(models.TransientModel):
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)])
('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)])

View File

@ -1,28 +1,18 @@
<?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 &amp; 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"/>
<button string="Submit" type="object" name="action_submit_hr" invisible=" state != 'draft'"/>
<button string="Send to FM" type="object" name="action_send_finance" invisible=" state != 'hr' or not is_hr"/>
<button string="Approve &amp; Proceed" type="object" name="action_send_hr" invisible=" state != 'finance' or not is_finance_manager"/>
<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."/>
<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>
<sheet>
<div class="row justify-content-between position-relative w-100 m-0 mb-2">
@ -34,25 +24,45 @@
</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="requested_by" readonly="not is_hr" widget="many2one_avatar_user"/>
<field name="client_id" readonly="not is_hr" invisible="not is_hr" force_save="1"/>
<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="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>
<field name="position_title" readonly="state != 'draft'"/>
<field name="number_of_positions" 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' 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="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"/>
<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 == 'done'"/>
</group>
<group string="Job Summary">
<field name="job_description" readonly="state == 'done'" nolabel="1" placeholder="Please enter the JD Hear"/>
<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>
</page>
</notebook>
</sheet>
<chatter reload_on_follower="True"/>
</form>
@ -65,7 +75,6 @@
<field name="arch" type="xml">
<list string="Recruitment Requisitions">
<field name="name"/>
<field name="department_id"/>
<field name="requested_by"/>
<field name="state"/>
</list>
@ -77,6 +86,13 @@
<field name="view_mode">list,form</field>
</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"/>
</odoo>

View File

@ -8,10 +8,16 @@
<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."
help="Select the HR responsible for Requisitions."
id="requisition_hr_access_control">
<field name="requisition_hr_id" options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
</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>
</xpath>
</field>

View File

@ -7,7 +7,7 @@
<field name="arch" type="xml">
<form string="Cancel Requisition">
<group>
<field name="cancellation_reason" placeholder="Enter the reason for cancellation..."/>
<field name="cancellation_reason" nolabel="1" placeholder="Enter the reason for cancellation..."/>
</group>
<footer>
<button string="Submit" type="object" name="submit_cancellation" class="btn-primary"/>

View File

@ -3,12 +3,15 @@
<t t-name="universal_attachment_preview.PopupPreview">
<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')">
<iframe t-att-src="props.url"
style="width:100%; height:500px;"
frameborder="0">
</iframe>
</t>
<t t-elif="props.mimetype.startsWith('image')">
<img t-att-src="props.url"