Merge pull request 'Recruimtent changes & Vendor update' (#30) from feature/share_module into srivyn_test

Reviewed-on: https://gitea.ftprotech.in/administrator/odoo18/pulls/30
This commit is contained in:
pranay 2026-07-30 13:49:08 +05:30
commit bc925cac3b
10 changed files with 288 additions and 83 deletions

View File

@ -1906,8 +1906,9 @@ class HrRecruitmentAutoDocWizard(models.TransientModel):
"target_to": self._parse_date_value(parsed_data.get("end_date")),
"job_category": int(job_category_id) if job_category_id and job_category_id.isdigit() else False,
"address_id":False,
"recruitment_type": 'external'
"recruitment_type": 'external',
"requested_by": parsed_data.get("requested_by") or False,
"no_of_recruitment": parsed_data.get("no_of_positions") or 1,
}
if request_id:
create_vals["recruitment_sequence"] = request_id
@ -2207,6 +2208,8 @@ class HrRecruitmentAutoDocWizardLine(models.TransientModel):
budget = fields.Char()
experience_years = fields.Float()
job_category = fields.Char()
requested_by = fields.Many2one('res.partner',domain="[('contact_type', '=', 'external')]")
no_of_positions = fields.Integer('Number of Positions')
def action_save_line_changes(self):
self.ensure_one()
@ -2286,6 +2289,8 @@ class HrRecruitmentAutoDocWizardLine(models.TransientModel):
"budget": self.budget,
"experience_years": self.experience_years,
"job_category": self.job_category,
"requested_by": self.requested_by.id if self.requested_by else False,
"no_of_positions": self.no_of_positions if self.no_of_positions else 1,
})
else:
data.update({

View File

@ -160,14 +160,20 @@
<field name="end_date"/>
</group>
<group>
<field name="requested_by"/>
<field name="no_of_positions"/>
<field name="budget"/>
<field name="experience_years"/>
<field name="job_category"/>
</group>
</group>
<group>
<field name="job_summary" nolabel="1" placeholder="Job Summary"/>
<field name="requirements" nolabel="1" placeholder="Requirements"/>
<group>
<field name="job_summary" nolabel="1" placeholder="Job Summary"/>
</group>
<group>
<field name="requirements" nolabel="1" placeholder="Requirements"/>
</group>
</group>
</page>
<page string="Skills">

View File

@ -284,12 +284,12 @@ class HRApplicant(models.Model):
'applicant_id',
string='Request Forms'
)
post_onboarding_form_status = fields.Selection([('draft','Draft'),('email_sent_to_candidate','Email Sent to Candidate'),('done','Done')], default='draft')
doc_requests_form_status = fields.Selection(
[('draft', 'Draft'), ('email_sent_to_candidate', 'Email Sent to Candidate'), ('done', 'Done')],
default='draft',
)
legend_blocked = fields.Char(related='recruitment_stage_id.legend_blocked', string='Kanban Blocked')
post_onboarding_form_status = fields.Selection([('draft','Draft'),('email_sent_to_candidate','Email Sent to Candidate'),('done','Done')], default='draft')
doc_requests_form_status = fields.Selection(
[('draft', 'Draft'), ('email_sent_to_candidate', 'Email Sent to Candidate'), ('done', 'Done')],
default='draft',
)
legend_blocked = fields.Char(related='recruitment_stage_id.legend_blocked', string='Kanban Blocked')
legend_done = fields.Char(related='recruitment_stage_id.legend_done', string='Kanban Valid')
legend_normal = fields.Char(related='recruitment_stage_id.legend_normal', string='Kanban Ongoing')
employee_code = fields.Char(related="employee_id.employee_id")
@ -304,14 +304,14 @@ class HRApplicant(models.Model):
('validated', 'Validated')], default='pending')
approval_required = fields.Boolean(related='recruitment_stage_id.require_approval')
application_submitted = fields.Boolean(string="Application Submitted")
send_second_application_form = fields.Boolean(string="Send Second Application Form")
send_post_onboarding_form = fields.Boolean(string="Send Post Onboarding Form")
second_application_form_status = fields.Selection(
[('draft', 'Draft'), ('email_sent_to_candidate', 'Email Sent to Candidate'), ('done', 'Done')],
default='draft',
)
resume = fields.Binary(related='candidate_id.resume', readonly=False, compute_sudo=True)
application_submitted = fields.Boolean(string="Application Submitted")
send_second_application_form = fields.Boolean(string="Send Second Application Form")
send_post_onboarding_form = fields.Boolean(string="Send Post Onboarding Form")
second_application_form_status = fields.Selection(
[('draft', 'Draft'), ('email_sent_to_candidate', 'Email Sent to Candidate'), ('done', 'Done')],
default='draft',
)
resume = fields.Binary(related='candidate_id.resume', readonly=False, compute_sudo=True)
resume_type = fields.Char(related='candidate_id.resume_type', readonly=False, compute_sudo=True)
resume_name = fields.Char(related='candidate_id.resume_name', readonly=False, compute_sudo=True)
@ -360,11 +360,11 @@ class HRApplicant(models.Model):
'joining_attachment_ids': [(0, 0, values) for values in latest_attachments.values()]
})
def action_share_applicant(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': _('Share Applicant'),
def action_share_applicant(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': _('Share Applicant'),
'res_model': 'client.submission.mails.template.wizard',
'view_mode': 'form',
'view_id': self.env.ref('hr_recruitment_extended.view_client_submission_mails_template_wizard_form').id,
@ -372,15 +372,15 @@ class HRApplicant(models.Model):
'context': {
'default_template_id': self.env.ref(
'hr_recruitment_extended.application_client_submission_email_template'
).id,
},
}
def submit_to_client(self):
return self.action_share_applicant()
def submit_for_approval(self):
for rec in self:
).id,
},
}
def submit_to_client(self):
return self.action_share_applicant()
def submit_for_approval(self):
for rec in self:
manager_id = self.env['ir.config_parameter'].sudo().get_param('requisitions.requisition_manager')
if not manager_id:
raise ValidationError(_("Recruitment Manager is not selected please go into the Configuration->Settings and add the Manager"))
@ -389,8 +389,7 @@ class HRApplicant(models.Model):
render_ctx = dict(recruitment_manager=manager_id)
mail_template.with_context(render_ctx).send_mail(
self.id,
force_send=True,
email_layout_xmlid='mail.mail_notification_light')
force_send=True)
rec.application_submitted = True
def approve_applicant(self):
@ -432,11 +431,11 @@ class HRApplicant(models.Model):
template.send_mail(applicant.id, force_send=True)
applicant.second_application_form_status = 'email_sent_to_candidate'
def send_jod_form_to_employee(self):
for rec in self:
if not rec.employee_id:
raise ValidationError(_('You must first create the employee before before Sending the Post Onboarding Form'))
def send_jod_form_to_employee(self):
for rec in self:
if not rec.employee_id:
raise ValidationError(_('You must first create the employee before before Sending the Post Onboarding Form'))
elif not rec.employee_id.employee_id:
raise ValidationError(_('Employee Code for the Employee (%s) is missing')%(rec.employee_id.name))
return {
@ -445,17 +444,17 @@ class HRApplicant(models.Model):
'res_model': 'post.onboarding.attachment.wizard',
'view_mode': 'form',
'view_type': 'form',
'target': 'new',
'context': {'default_req_attachment_ids': []}
}
def send_post_onboarding_form_to_candidate(self):
return self.send_jod_form_to_employee()
def send_pre_onboarding_doc_request_form_to_candidate(self):
return {
'type': 'ir.actions.act_window',
'target': 'new',
'context': {'default_req_attachment_ids': []}
}
def send_post_onboarding_form_to_candidate(self):
return self.send_jod_form_to_employee()
def send_pre_onboarding_doc_request_form_to_candidate(self):
return {
'type': 'ir.actions.act_window',
'name': 'Select Attachments',
'res_model': 'post.onboarding.attachment.wizard',
'view_mode': 'form',

View File

@ -5,4 +5,46 @@ class ResPartner(models.Model):
_inherit = 'res.partner'
contact_type = fields.Selection([('internal','In-House'),('external','Client-Side')], required=True, default='internal')
company_id = fields.Many2one('res.company', default=lambda self: self.env.user.company_id)
company_id = fields.Many2one('res.company', default=lambda self: self.env.user.company_id)
# Vendor Statistics Fields
total_positions_requested = fields.Integer(compute='_compute_vendor_statistics', string='Positions Requested')
total_submitted = fields.Integer(compute='_compute_vendor_statistics', string='Total Submitted')
total_hired = fields.Integer(compute='_compute_vendor_statistics', string='Total Hired')
total_rejected = fields.Integer(compute='_compute_vendor_statistics', string='Total Rejected')
job_recruitment_ids = fields.One2many('hr.job.recruitment', 'requested_by', string='Job Requests')
@api.depends('contact_type')
def _compute_vendor_statistics(self):
for partner in self:
if partner.contact_type == 'external':
# Get all job recruitments requested by this vendor
job_recruitments = self.env['hr.job.recruitment'].search([
('requested_by', '=', partner.id)
])
partner.total_positions_requested = sum(job_recruitments.mapped('no_of_recruitment'))
partner.total_submitted = sum(job_recruitments.mapped('no_of_submissions'))
partner.total_hired = sum(job_recruitments.mapped('no_of_hired_employee'))
partner.total_rejected = sum(job_recruitments.mapped('no_of_refused_submissions'))
else:
partner.total_positions_requested = 0
partner.total_submitted = 0
partner.total_hired = 0
partner.total_rejected = 0
def action_create_job_request(self):
"""Action to create a new job request from vendor"""
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': _('Create Job Request'),
'res_model': 'hr.job.recruitment',
'view_mode': 'form',
'target': 'current',
'context': {
'default_requested_by': self.id,
'default_recruitment_type': 'external',
'default_address_id': self.id if self.is_company else (self.parent_id.id if self.parent_id else False),
}
}

View File

@ -218,6 +218,17 @@
<xpath expr="//filter[@name='refused']" position="attributes">
<attribute name="string">Rejected</attribute>
</xpath>
<xpath expr="//group" position="before">
<searchtab name="inprogress_records_tab" string="In Progress">
<filter string="In Progress" name="ongoing" domain="[('date_closed', '=', False), ('active', '=', True), ('refuse_reason_id', '=', False)]"/>
</searchtab>
<searchtab name="hired_records_tab" string="Hired">
<filter string="Hired" name="hired" domain="[('date_closed', '!=', False)]"/>
</searchtab>
<searchtab name="refused_records_tab" string="Refused">
<filter string="Refused" name="refused" domain="[('active', '=', False), ('refuse_reason_id', '!=', False)]"/>
</searchtab>
</xpath>
<xpath expr="//filter[@name='refuse_reason_id']" position="attributes">
<attribute name="string">Reject Reason</attribute>
</xpath>

View File

@ -246,6 +246,20 @@
<filter string="Unpublished" name="unpublished_records_tab_filter"
domain="[('website_published','=',False)]"/>
</searchtab>
<searchtab name="open_records_tab" string="Open">
<filter name="open_status" string="Open"
domain="[('recruitment_status','=', 'open')]"/>
</searchtab>
<searchtab name="closed_records_tab" string="Closed">
<filter name="closed_status" string="Closed"
domain="[('recruitment_status','=', 'closed')]"/>
</searchtab>
<searchtab name="hold_records_tab" string="Hold">
<filter name="hold_status" string="Hold"
domain="[('recruitment_status','=', 'hold')]"/>
</searchtab>
<filter string="Published Records" name="published_records" domain="[('website_published','=',True)]"/>
<filter string="UnPublished Records" name="unpublished_records" domain="[('website_published','=',False)]"/>
<separator/>

View File

@ -268,6 +268,19 @@
<field name="model">hr.candidate</field>
<field name="inherit_id" ref="hr_recruitment.hr_candidate_view_search"/>
<field name="arch" type="xml">
<xpath expr="//group" position="before">
<searchtab name="inprogress_records_tab" string="Application In Progress">
<filter string="Application in Progress" name="application_in_progress" domain="[('applicant_ids.application_status', '=', 'ongoing')]"/>
</searchtab>
<searchtab name="hired_records_tab" string="Hired">
<filter string="Hired" name="hired" domain="[('applicant_ids.application_status', '=', 'hired')]"/>
</searchtab>
<searchtab name="refused_records_tab" string="Refused">
<filter string="Refused" name="refused" context="{'active_test': False}" domain="[('applicant_ids.application_status', '=', 'refused')]"/>
</searchtab>
</xpath>
<xpath expr="//field[@name='partner_name']" position="after">
<field name="candidate_sequence"/>
</xpath>

View File

@ -98,6 +98,58 @@
</group>
<div class="row" invisible="contact_type != 'external'">
<div class="col-12">
<div class="card bg-secondary">
<div class="card-header bg-primary text-white" style="color: white;">
<h5 class="mb-0" style="color: white;"><i class="fa fa-chart-bar me-2"/>Recruitment Statistics</h5>
</div>
<div class="card-body" style="background-color: #f8f9fa;">
<div class="row">
<div class="col-md-3">
<div class="text-center p-3">
<div class="text-muted small">Positions Requested</div>
<div class="display-4 fw-bold text-primary">
<field name="total_positions_requested"/>
</div>
</div>
</div>
<div class="col-md-3">
<div class="text-center p-3">
<div class="text-muted small">Total Submitted</div>
<div class="display-4 fw-bold text-success">
<field name="total_submitted"/>
</div>
</div>
</div>
<div class="col-md-3">
<div class="text-center p-3">
<div class="text-muted small">Total Hired</div>
<div class="display-4 fw-bold text-info">
<field name="total_hired"/>
</div>
</div>
</div>
<div class="col-md-3">
<div class="text-center p-3">
<div class="text-muted small">Total Rejected</div>
<div class="display-4 fw-bold text-danger">
<field name="total_rejected"/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="oe_button_box" invisible="contact_type != 'external'">
<button name="action_create_job_request" type="object" class="btn btn-primary">
<i class="fa fa-plus me-1"/>Create Job Request
</button>
</div>
<notebook>
<page string="Contacts &amp; Addresses">
@ -137,6 +189,22 @@
</page>
<page string="Job Requests" invisible="contact_type != 'external'">
<field name="id" invisible="1"/>
<field name="contact_type" invisible="1"/>
<field name="job_recruitment_ids" widget="many2many">
<list>
<field name="recruitment_sequence"/>
<field name="name"/>
<field name="no_of_recruitment"/>
<field name="no_of_submissions"/>
<field name="no_of_hired_employee"/>
<field name="no_of_refused_submissions"/>
<field name="recruitment_status"/>
</list>
</field>
</page>
</notebook>
</sheet>
@ -182,43 +250,88 @@
<field name="name"/>
<field name="phone"/>
<field name="email"/>
<field name="total_positions_requested"/>
<field name="total_submitted"/>
<field name="total_hired"/>
<field name="total_rejected"/>
<templates>
<t t-name="card">
<div class="oe_kanban_global_click d-flex p-2">
<!-- Avatar -->
<div class="me-2">
<field name="image_128"
widget="image"
class="rounded"
options="{'size': [48,48]}"/>
<div class="oe_kanban_global_click">
<div class="o_kanban_card_header">
<div class="o_kanban_card_header_title">
<div class="me-2">
<field name="image_128"
widget="image"
class="rounded-circle oe_avatar"
style="width:64px; height:64px; min-width:64px; min-height:64px; object-fit:cover;"
options="{'size': [64, 64]}"/>
</div>
<div class="flex-grow-1">
<strong class="o_kanban_record_title">
<field name="name"/>
</strong>
<div t-if="record.email.raw_value" class="text-muted small">
<i class="fa fa-envelope me-1"/>
<field name="email"/>
</div>
<div t-if="record.phone.raw_value" class="text-muted small">
<i class="fa fa-phone me-1"/>
<field name="phone"/>
</div>
</div>
</div>
</div>
<!-- Details -->
<div class="flex-grow-1 overflow-hidden">
<div class="fw-bold text-truncate">
<field name="name"/>
<div class="o_kanban_card_content">
<div class="row g-2 mb-2">
<div class="col-6">
<div class="card bg-light border-0" style="background-color: #f8f9fa;">
<div class="card-body p-2 text-center">
<div class="text-muted small mb-1" style="color: black;">Requested</div>
<div class="fw-bold text-primary fs-5">
<field name="total_positions_requested"/>
</div>
</div>
</div>
</div>
<div class="col-6">
<div class="card bg-light border-0" style="background-color: #f8f9fa;">
<div class="card-body p-2 text-center">
<div class="text-muted small mb-1" style="color: black;">Submitted</div>
<div class="fw-bold text-success fs-5">
<field name="total_submitted"/>
</div>
</div>
</div>
</div>
<div class="col-6">
<div class="card bg-light border-0" style="background-color: #f8f9fa;">
<div class="card-body p-2 text-center">
<div class="text-muted small mb-1" style="color: black;">Hired</div>
<div class="fw-bold text-info fs-5">
<field name="total_hired"/>
</div>
</div>
</div>
</div>
<div class="col-6">
<div class="card bg-light border-0" style="background-color: #f8f9fa;">
<div class="card-body p-2 text-center">
<div class="text-muted small mb-1" style="color: black;">Rejected</div>
<div class="fw-bold text-danger fs-5">
<field name="total_rejected"/>
</div>
</div>
</div>
</div>
</div>
<div t-if="record.phone.raw_value"
class="text-muted small">
<i class="fa fa-phone me-1"/>
<field name="phone"/>
</div>
<div t-if="record.email.raw_value"
class="text-muted small text-truncate">
<i class="fa fa-envelope me-1"/>
<field name="email"/>
</div>
</div>
<div class="o_kanban_card_manage">
<button name="action_create_job_request" type="object" class="btn btn-primary btn-sm">
<i class="fa fa-plus me-1"/>Create Job Request
</button>
</div>
</div>
</t>
</templates>
</kanban>

View File

@ -571,7 +571,8 @@
<page string="Jobs">
<field name="applicant_history_ids" nolabel="1" readonly="1">
<list>
<field name="job_id"/>
<field name="hr_job_recruitment"/>
<field name="job_id" invisible="1" column_invisible="1"/>
<field name="stage_id"/>
<field name="user_id"/>
<field name="create_date"/>

View File

@ -343,7 +343,8 @@
<page string="Jobs">
<field name="applicant_ids" nolabel="1">
<list>
<field name="job_id"/>
<field name="hr_job_recruitment"/>
<field name="job_id" invisible="1" column_invisible="1"/>
<field name="stage_id"/>
<field name="user_id"/>
<field name="create_date"/>