from odoo import models, fields, api, _, tools from odoo.exceptions import ValidationError, UserError from datetime import date from datetime import timedelta import datetime # # class Job(models.Model): # _inherit = 'hr.job' # # hiring_history = fields.One2many('recruitment.status.history', 'job_id', string='History') class HrCandidate(models.Model): _inherit = "hr.candidate" _sql_constraints = [ ('unique_candidate_sequence', 'UNIQUE(candidate_sequence)', 'Candidate sequence must be unique!'), ] #personal Details candidate_sequence = fields.Char(string='Candidate Sequence', readonly=False, default='/', copy=False) first_name = fields.Char(string='First Name',required=False, help="This is the person's first name, given at birth or during a naming ceremony. It’s the name people use to address you.") middle_name = fields.Char(string='Middle Name', help="This is an extra name that comes between the first name and last name. Not everyone has a middle name") last_name = fields.Char(string='Last Name',required=False, help="This is the family name, shared with other family members. It’s usually the last name.") alternate_phone = fields.Char(string='Alternate Phone') candidate_image = fields.Image() employee_code = fields.Char(related="employee_id.employee_id") resume = fields.Binary() applications_stages_stat = fields.Many2many('application.stage.status',string="Applications History", compute="_compute_applications_stages_stat") # availability_status = fields.Selection([('available','Available'),('not_available','Not Available'),('hired','Hired'),('abscond','Abscond')]) def action_open_applications(self): self.ensure_one() return { 'name': _('Applications'), 'type': 'ir.actions.act_window', 'res_model': 'hr.applicant', 'view_mode': 'list,kanban,form,pivot,graph,calendar,activity', 'domain': [('id', 'in', self.applicant_ids.ids)], 'context': { 'active_test': False, 'search_default_job_recruitment_stage': 1, }, } @api.depends('applicant_ids') def _compute_applications_stages_stat(self): for rec in self: if rec.applicant_ids: stage_status_records = self.env['application.stage.status'].with_context(active_test=False).search([ ('applicant_id', 'in', rec.applicant_ids.ids) ]) rec.applications_stages_stat = [(6, 0, stage_status_records.ids)] else: rec.applications_stages_stat = [(5,)] @api.depends('partner_name', 'candidate_sequence') def _compute_display_name(self): for rec in self: rec.display_name = rec.partner_name if not rec.candidate_sequence else f"{rec.partner_name} ({rec.candidate_sequence})" @api.constrains('email_from', 'partner_phone', 'alternate_phone') def _candidate_unique_constraints(self): for rec in self: # Check for unique email if rec.email_from: existing_email = self.sudo().search([('id', '!=', rec.id), ('email_from', '=', rec.email_from)], limit=1) if existing_email: raise ValidationError(_("A candidate with the email '%s' already exists, sourced by %s %s.") % ( existing_email.email_from, existing_email.user_id.name, existing_email.candidate_sequence if existing_email.candidate_sequence else '')) # Check for unique phone number (partner_phone or alternate_phone) if rec.partner_phone: existing_phone = self.sudo().search( [('id', '!=', rec.id), '|', ('partner_phone', '=', rec.partner_phone), ('alternate_phone', '=', rec.partner_phone)], limit=1) if existing_phone: raise ValidationError(_("A candidate with the phone number '%s' already exists, sourced by %s %s.") % ( existing_phone.partner_phone, existing_phone.user_id.name, existing_phone.candidate_sequence if existing_phone.candidate_sequence else '')) if rec.alternate_phone: existing_al_phone = self.sudo().search( [('id', '!=', rec.id), '|', ('partner_phone', '=', rec.alternate_phone), ('alternate_phone', '=', rec.alternate_phone)], limit=1) if existing_al_phone: raise ValidationError( _("A candidate with the alternate phone number '%s' already exists, sourced by %s %s.") % ( existing_al_phone.alternate_phone, existing_al_phone.user_id.name, existing_al_phone.candidate_sequence if existing_al_phone.candidate_sequence else '')) @api.model_create_multi def create(self, vals_list): for vals in vals_list: if vals.get('candidate_sequence', '/') == '/': vals['candidate_sequence'] = self.env['ir.sequence'].next_by_code( 'hr.job.candidate.sequence') or '/' return super(HrCandidate, self).create(vals_list) def create_employee_from_candidate(self): self.ensure_one() self._check_interviewer_access() if not self.partner_id: if not self.partner_name: raise UserError(_('Please provide an candidate name.')) self.partner_id = self.env['res.partner'].create({ 'is_company': False, 'name': self.partner_name, 'email': self.email_from, }) action = self.env['ir.actions.act_window']._for_xml_id('hr.open_view_employee_list') employee = self.env['hr.employee'].create(self._get_employee_create_vals()) action['res_id'] = employee.id employee.write({ 'image_1920': self.candidate_image}) return action # # doj = fields.Date(tracking=True) # gender = fields.Selection([ # ('male', 'Male'), # ('female', 'Female'), # ('other', 'Other') # ], tracking=True) # birthday = fields.Date(tracking=True) # # blood_group = fields.Selection([ # ('A+', 'A+'), # ('A-', 'A-'), # ('B+', 'B+'), # ('B-', 'B-'), # ('O+', 'O+'), # ('O-', 'O-'), # ('AB+', 'AB+'), # ('AB-', 'AB-'), # ], string="Blood Group") # # private_street = fields.Char(string="Private Street", groups="hr.group_hr_user") # private_street2 = fields.Char(string="Private Street2", groups="hr.group_hr_user") # private_city = fields.Char(string="Private City", groups="hr.group_hr_user") # private_state_id = fields.Many2one( # "res.country.state", string="Private State", # domain="[('country_id', '=?', private_country_id)]", # groups="hr.group_hr_user") # private_zip = fields.Char(string="Private Zip", groups="hr.group_hr_user") # private_country_id = fields.Many2one("res.country", string="Private Country", groups="hr.group_hr_user") # # permanent_street = fields.Char(string="permanent Street", groups="hr.group_hr_user") # permanent_street2 = fields.Char(string="permanent Street2", groups="hr.group_hr_user") # permanent_city = fields.Char(string="permanent City", groups="hr.group_hr_user") # permanent_state_id = fields.Many2one( # "res.country.state", string="permanent State", # domain="[('country_id', '=?', private_country_id)]", # groups="hr.group_hr_user") # permanent_zip = fields.Char(string="permanent Zip", groups="hr.group_hr_user") # permanent_country_id = fields.Many2one("res.country", string="permanent Country", groups="hr.group_hr_user") # # marital = fields.Selection( # selection='_get_marital_status_selection', # string='Marital Status', # groups="hr.group_hr_user", # default='single', # required=True, # tracking=True) # # marriage_anniversary_date = fields.Date(string='Anniversary Date' ,tracking=True) # # #bank Details: # # full_name_as_in_bank = fields.Char(string='Full Name (as per bank)' ,tracking=True) # bank_name = fields.Char(string='Bank Name' ,tracking=True) # bank_branch = fields.Char(string='Bank Branch' ,tracking=True) # bank_account_no = fields.Char(string='Bank Account N0' ,tracking=True) # bank_ifsc_code = fields.Char(string='Bank IFSC Code' ,tracking=True) # # # #passport details: # passport_no = fields.Char(string="Passport No",tracking=True) # passport_start_date = fields.Date(string="Start Date",tracking=True) # passport_end_date = fields.Date(string="End Date",tracking=True) # passport_issued_location = fields.Char(string="Start Date",tracking=True) # # #authotentication Details # pan_no = fields.Char(string='PAN No',tracking=True) # identification_id = fields.Char(string='Aadhar No',tracking=True) # previous_company_pf_no = fields.Char(string='Previous Company PF No',tracking=True) # previous_company_uan_no = fields.Char(string='Previous Company UAN No',tracking=True) # @api.constrains('partner_name') def partner_name_constrain(self): for rec in self: if any(char.isdigit() for char in rec.partner_name): raise ValidationError(_("Enter Valid Name")) class HRApplicant(models.Model): _inherit = 'hr.applicant' current_location = fields.Char('Current Location') preferred_location = fields.Many2many('hr.location',string="Preferred Location's") current_organization = fields.Char('Current Organization') alternate_phone = fields.Char(related="candidate_id.alternate_phone", readonly=False) exp_type = fields.Selection([('fresher','Fresher'),('experienced','Experienced')], default='fresher', required=True) total_exp = fields.Float(string="Total Experience") relevant_exp = fields.Float(string="Relevant Experience") total_exp_type = fields.Selection([('month',"Month's"),('year',"Year's")], default='year') relevant_exp_type = fields.Selection([('month',"Month's"),('year',"Year's")], default='year') notice_period = fields.Char(string="Notice Period") notice_period_type = fields.Selection([('day',"Day's"),('month',"Month's"),('year',"Year's")], string='Type', default='day', invisible=True) current_ctc = fields.Float(string="Current CTC", aggregator="avg", help="Applicant Current Salary", tracking=True, groups="hr_recruitment.group_hr_recruitment_user") salary_expected = fields.Float("Expected CTC", aggregator="avg", help="Salary Expected by Applicant", tracking=True, groups="hr_recruitment.group_hr_recruitment_user") salary_negotiable = fields.Boolean(string="Salary Negotiable") np_negotiable = fields.Boolean(string="NP Negotiable") holding_offer = fields.Char(string="Holding Offer") applicant_comments = fields.Text(string='Applicant Comments') recruiter_comments = fields.Text(string='Recruiter Comments') medium_id = fields.Many2one(string='Mode') doj = fields.Date(tracking=True) gender = fields.Selection([ ('male', 'Male'), ('female', 'Female'), ('other', 'Other') ], tracking=True) birthday = fields.Date(tracking=True) blood_group = fields.Selection([ ('A+', 'A+'), ('A-', 'A-'), ('B+', 'B+'), ('B-', 'B-'), ('O+', 'O+'), ('O-', 'O-'), ('AB+', 'AB+'), ('AB-', 'AB-'), ], string="Blood Group") marital = fields.Selection( selection='_get_marital_status_selection', string='Marital Status', groups="hr.group_hr_user", default='single', required=True, tracking=True) marriage_anniversary_date = fields.Date(string='Anniversary Date', tracking=True) personal_details_status = fields.Selection([('pending', 'Pending'), ('validated', 'Validated')], default='pending') #contact details private_street = fields.Char(string="Private Street", groups="hr.group_hr_user") private_street2 = fields.Char(string="Private Street2", groups="hr.group_hr_user") private_city = fields.Char(string="Private City", groups="hr.group_hr_user") private_state_id = fields.Many2one( "res.country.state", string="Private State", domain="[('country_id', '=?', private_country_id)]", groups="hr.group_hr_user") private_zip = fields.Char(string="Private Zip", groups="hr.group_hr_user") private_country_id = fields.Many2one("res.country", string="Private Country", groups="hr.group_hr_user") permanent_street = fields.Char(string="permanent Street", groups="hr.group_hr_user") permanent_street2 = fields.Char(string="permanent Street2", groups="hr.group_hr_user") permanent_city = fields.Char(string="permanent City", groups="hr.group_hr_user") permanent_state_id = fields.Many2one( "res.country.state", string="permanent State", domain="[('country_id', '=?', private_country_id)]", groups="hr.group_hr_user") permanent_zip = fields.Char(string="permanent Zip", groups="hr.group_hr_user") permanent_country_id = fields.Many2one("res.country", string="permanent Country", groups="hr.group_hr_user") contact_details_status = fields.Selection([('pending', 'Pending'), ('validated', 'Validated')], default='pending') # bank Details: full_name_as_in_bank = fields.Char(string='Full Name (as per bank)', tracking=True) bank_name = fields.Char(string='Bank Name', tracking=True) bank_branch = fields.Char(string='Bank Branch', tracking=True) bank_account_no = fields.Char(string='Bank Account N0', tracking=True) bank_ifsc_code = fields.Char(string='Bank IFSC Code', tracking=True) bank_details_status = fields.Selection([('pending', 'Pending'), ('validated', 'Validated')], default='pending') # passport details: passport_no = fields.Char(string="Passport No", tracking=True) passport_start_date = fields.Date(string="Start Date", tracking=True) passport_end_date = fields.Date(string="End Date", tracking=True) passport_issued_location = fields.Char(string="Start Date", tracking=True) passport_details_status = fields.Selection([('pending', 'Pending'), ('validated', 'Validated')], default='pending') # authentication Details pan_no = fields.Char(string='PAN No', tracking=True) identification_id = fields.Char(string='Aadhar No', tracking=True) previous_company_pf_no = fields.Char(string='Previous Company PF No', tracking=True) previous_company_uan_no = fields.Char(string='Previous Company UAN No', tracking=True) authentication_details_status = fields.Selection([('pending', 'Pending'), ('validated', 'Validated')], default='pending') def _get_marital_status_selection(self): return [ ('single', _('Single')), ('married', _('Married')), ('cohabitant', _('Legal Cohabitant')), ('widower', _('Widower')), ('divorced', _('Divorced')), ] def action_validate_personal_details(self): for rec in self: if rec.employee_id: vals = dict() vals['doj'] = rec.doj if rec.doj else '' vals['gender'] = rec.gender if rec.gender else '' vals['birthday'] = rec.birthday if rec.birthday else '' vals['blood_group'] = rec.blood_group if rec.blood_group else '' vals['marital'] = rec.marital if rec.marital else '' vals['marriage_anniversary_date'] = rec.marriage_anniversary_date if rec.marriage_anniversary_date else '' vals = {k: v for k, v in vals.items() if v != '' and v != 0} if len(vals) > 0: rec.personal_details_status = 'validated' else: raise ValidationError(_("No values to validate")) rec.employee_id.write(vals) def action_validate_contact_details(self): for rec in self: if rec.employee_id: vals = dict() # Current Address vals['private_street'] = rec.private_street if rec.private_street else '' vals['private_street2'] = rec.private_street2 if rec.private_street2 else '' vals['private_city'] = rec.private_city if rec.private_city else '' vals['private_state_id'] = rec.private_state_id.id if rec.private_state_id else '' vals['private_zip'] = rec.private_zip if rec.private_zip else '' vals['private_country_id'] = rec.private_country_id.id if rec.private_country_id else '' # Permanent Address vals['permanent_street'] = rec.permanent_street if rec.permanent_street else '' vals['permanent_street2'] = rec.permanent_street2 if rec.permanent_street2 else '' vals['permanent_city'] = rec.permanent_city if rec.permanent_city else '' vals['permanent_state_id'] = rec.permanent_state_id.id if rec.permanent_state_id else '' vals['permanent_zip'] = rec.permanent_zip if rec.permanent_zip else '' vals['permanent_country_id'] = rec.permanent_country_id.id if rec.permanent_country_id else '' # Remove empty/False values from dict vals = {k: v for k, v in vals.items() if v not in ['', False, 0]} if len(vals) > 0: rec.contact_details_status = 'validated' rec.employee_id.write(vals) else: raise ValidationError(_("No values to validate")) def action_validate_bank_details(self): for rec in self: if rec.employee_id and rec.full_name_as_in_bank and rec.bank_name and rec.bank_branch and rec.bank_account_no and rec.bank_ifsc_code: account_no = self.env['res.partner.bank'].sudo().search([('acc_number','=',rec.bank_account_no)],limit=1) if account_no: rec.employee_id.bank_account_id = account_no.id else: bank = self.env['res.bank'].sudo().search([('bic','=',rec.bank_ifsc_code)],limit=1) if bank: bank_id = bank else: bank_id = self.env['res.bank'].sudo().create({ 'name': rec.bank_name, 'bic': rec.bank_ifsc_code, 'branch': rec.bank_branch }) partner_bank = rec.env['res.partner.bank'].sudo().create({ 'acc_number': rec.bank_account_no, 'bank_id': bank_id.id, 'full_name': rec.full_name_as_in_bank, 'partner_id': rec.employee_id.work_contact_id.id | rec.employee_id.user_id.partner_id.id }) rec.employee_id.bank_account_id = partner_bank.id rec.bank_details_status = 'validated' else: raise ValidationError(_("Please Provide all the Bank Related Details")) def action_validate_passport_details(self): for rec in self: if rec.employee_id: vals = dict() # Current Address vals['passport_id'] = rec.passport_no if rec.passport_no else '' vals['passport_start_date'] = rec.passport_start_date if rec.passport_start_date else '' vals['passport_end_date'] = rec.passport_end_date if rec.passport_end_date else '' vals['passport_issued_location'] = rec.passport_issued_location if rec.passport_issued_location else '' # Remove empty/False values from dict vals = {k: v for k, v in vals.items() if v not in ['', False, 0]} if len(vals) > 0: rec.passport_details_status = 'validated' rec.employee_id.write(vals) else: raise ValidationError(_("No values to validate")) def action_validate_authentication_details(self): for rec in self: if rec.employee_id: vals = dict() # Current Address vals['pan_no'] = rec.pan_no if rec.pan_no else '' vals['identification_id'] = rec.identification_id if rec.identification_id else '' vals['previous_company_pf_no'] = rec.previous_company_pf_no if rec.previous_company_pf_no else '' vals['previous_company_uan_no'] = rec.previous_company_uan_no if rec.previous_company_uan_no else '' # Remove empty/False values from dict vals = {k: v for k, v in vals.items() if v not in ['', False, 0]} if len(vals) > 0: rec.authentication_details_status = 'validated' rec.employee_id.write(vals) else: raise ValidationError(_("No values to validate")) class Location(models.Model): _name = 'hr.location' _rec_name = 'location_name' # SQL Constraint to ensure the combination of location_name, zip_code, country_id, and state is unique _sql_constraints = [ ('unique_location_zip_state_country', 'UNIQUE(location_name, zip_code, country_id, state)', 'The selected Location, Zip Code, Country, and State combination already exists.') ] location_name = fields.Char(string='Location', required=True) zip_code = fields.Char(string = 'Zip Code') country_id = fields.Many2one('res.country','Country',groups="hr.group_hr_user") state = fields.Many2one("res.country.state", string="State", domain="[('country_id', '=?', country_id)]", groups="hr.group_hr_user") @api.constrains('location_name') def _check_location_name(self): for record in self: if record.location_name.isdigit(): raise ValidationError("Location name should not be a number. Please enter a valid location name.") @api.constrains('zip_code') def _check_zip_code(self): for record in self: if record.zip_code and not record.zip_code.isdigit(): # Check if zip_code exists and is not digit raise ValidationError("Zip Code should contain only numeric characters. Please enter a valid zip code.") # # class RecruitmentHistory(models.Model): # _name='recruitment.status.history' # # date_from = fields.Date(string='Date From') # date_end = fields.Date(string='Date End') # target = fields.Integer(string='Target') # job_id = fields.Many2one('hr.job', string='Job Position') # Ensure this field exists # hired = fields.Many2many('hr.applicant') # # @api.depends('date_from', 'date_end', 'job_id') # def _total_hired_users(self): # for rec in self: # if rec.date_from: # # Use `date_end` or today's date if `date_end` is not provided # date_end = rec.date_end or date.today() # # # Search for applicants matching the conditions # hired_applicants = self.env['hr.applicant'].search([ # ('date_closed', '>=', rec.date_from), # ('date_closed', '<=', date_end), # ('job_id', '=', rec.job_id.id) # ]) # rec.hired = hired_applicants # else: # rec.hired = False # class RecruitmentCategory(models.Model): _name = 'job.category' _rec_name = "category_name" category_name = fields.Char(string="Category Name") default_user = fields.Many2one('res.users') class ApplicationsStageStatus(models.Model): _name = 'application.stage.status' _rec_name = 'applicant_id' _auto = False applicant_id = fields.Many2one('hr.applicant') job_request = fields.Many2one('hr.job.recruitment', related='applicant_id.hr_job_recruitment') stage_id = fields.Many2one('hr.recruitment.stage', related="applicant_id.recruitment_stage_id") application_status = fields.Selection([ ('ongoing', 'Ongoing'), ('hired', 'Hired'), ('refused', 'Refused'), ('archived', 'Archived'), ], related='applicant_id.application_status') stage_color = fields.Char(related='applicant_id.stage_color', widget='color') stage_color_int = fields.Integer("Stage Color Int", compute="_compute_stage_color") @api.depends("application_status") def _compute_stage_color(self): for record in self: if record.application_status == 'hired': record.stage_color_int = 10 elif record.application_status == 'ongoing': record.stage_color_int = 3 elif record.application_status == 'refused': record.stage_color_int = 1 elif record.application_status == 'archived': record.stage_color_int = 9 else: record.stage_color_int = 0 @api.depends('applicant_id', 'stage_id', 'application_status', 'job_request', 'stage_color_int') def _compute_display_name(self): for rec in self: rec.display_name = rec.applicant_id.display_name if not rec.stage_id else f"({rec.job_request.recruitment_sequence}){rec.applicant_id.display_name} {rec.stage_color_int} - ({rec.stage_id.name}, {rec.application_status})" def init(self): """ Create the SQL view for application.stage.status """ tools.drop_view_if_exists(self.env.cr, self._table) self.env.cr.execute(""" CREATE OR REPLACE VIEW %s AS ( SELECT a.id AS id, -- Ensuring a unique primary key a.id AS applicant_id FROM hr_applicant a WHERE a.active = 't' or a.active = 'f' ); """ % (self._table))