oca-ocb-hr/odoo-bringout-oca-ocb-hr_recruitment/hr_recruitment/tests/test_recruitment.py
Ernad Husremovic e1d89e11e3 19.0 vanilla
2026-03-09 09:31:00 +01:00

494 lines
20 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from odoo import Command
from odoo.fields import Domain
from odoo.tests import tagged, TransactionCase, Form
@tagged('recruitment')
class TestRecruitment(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.company = cls.env['res.company'].create({
'name': 'Company Test',
'country_id': cls.env.ref('base.us').id,
})
cls.env.user.company_id = cls.company
cls.env.user.company_ids = [Command.set(cls.company.ids)]
cls.TEXT = base64.b64encode(bytes("hr_recruitment", 'utf-8'))
cls.Attachment = cls.env['ir.attachment']
def test_infer_applicant_lang_from_context(self):
# Prerequisites
self.env['res.lang']._activate_lang('pl_PL')
self.env['res.lang']._activate_lang('en_US')
self.env['ir.default'].set('res.partner', 'lang', 'en_US')
# Creating an applicant will create a partner (email_from inverse)
applicant = self.env['hr.applicant'].sudo().with_context(lang='pl_PL').create({
'partner_name': 'Test Applicant',
'email_from': "test_aplicant@example.com"
})
self.assertEqual(applicant.partner_id.lang, 'pl_PL', 'Context langague not used for partner creation')
def test_duplicate_email(self):
# Tests that duplicate email matching is case insesitive
dup1, dup2, no_dup = self.env['hr.applicant'].create([
{
'partner_name': 'Application 1',
'email_from': 'laurie.poiret@aol.ru',
},
{
'partner_name': 'Application 2',
'email_from': 'laurie.POIRET@aol.ru',
},
{
'partner_name': 'Application 3',
'email_from': 'laure.poiret@aol.ru',
},
])
self.assertEqual(dup1.application_count, 2)
self.assertEqual(dup2.application_count, 2)
self.assertEqual(no_dup.application_count, 1)
def test_similar_applicants_count(self):
"""Test that we find same applicant based on simmilar mail or phone."""
A, B, C, D, E, F, _ = self.env['hr.applicant'].create([
{
'active': False, # Refused/archived application should still count
'partner_name': 'Application A',
'email_from': 'abc@odoo.com',
'partner_phone': '123',
},
{
'partner_name': 'Application B',
'partner_phone': '456',
},
{
'partner_name': 'Application C',
'email_from': 'def@odoo.com',
'partner_phone': '123',
},
{
'partner_name': 'Application D',
'email_from': 'abc@odoo.com',
'partner_phone': '456',
},
{
'partner_name': 'Application E',
'partner_phone': '',
},
{
'partner_name': 'Application F',
'email_from': 'ghi@odoo.com',
'partner_phone': '789',
},
{
'partner_name': 'Application G',
},
])
self.assertEqual(A.application_count, 3) # A, C, D
self.assertEqual(B.application_count, 2) # B, D
self.assertEqual(C.application_count, 2) # C, A
self.assertEqual(D.application_count, 3) # D, A, B
self.assertEqual(E.application_count, 0) # Should not match with E and G as there is no data to use for matching.
self.assertEqual(F.application_count, 1) # F
def test_talent_pool_count(self):
tp_A, tp_B = self.env["hr.talent.pool"].create([{"name": "Cool Pool"}, {"name": "Other Pool"}])
t_A, t_B = self.env["hr.applicant"].create(
[
{
"partner_name": "Talent A",
"email_from": "abc@example.com",
"partner_phone": "1234",
"linkedin_profile": "linkedin/talent",
"talent_pool_ids": [tp_A.id, tp_B.id],
},
{
"partner_name": "Talent B",
"email_from": "talent_b@example.com",
"partner_phone": "9999",
"talent_pool_ids": [tp_B.id],
},
]
)
# The only way to create a talent is through the wizards. Talents that are
# created through the wizard also assign their own ID as pool_applicant_id
t_A.pool_applicant_id = t_A.id
t_B.pool_applicant_id = t_B.id
A, B, C, D, E, F, G = self.env["hr.applicant"].create(
[
{"partner_name": "A", "pool_applicant_id": t_A.id},
{
"partner_name": "B",
"email_from": "def@example.com",
"partner_phone": "6789",
"linkedin_profile": "linkedin/b",
"pool_applicant_id": t_A.id,
},
{
"partner_name": "C",
"email_from": "def@example.com",
},
{
"partner_name": "D",
"partner_phone": "6789",
},
{
"partner_name": "E",
"linkedin_profile": "linkedin/b",
},
{
"partner_name": "F",
"email_from": "not_linked@example.com",
"partner_phone": "00000",
"linkedin_profile": "linkedin/not_linked",
},
{"partner_name": "G", "pool_applicant_id": t_B.id},
]
)
self.assertEqual(t_A.talent_pool_count, 2)
self.assertEqual(t_B.talent_pool_count, 1)
self.assertEqual(A.talent_pool_count, 2)
self.assertEqual(B.talent_pool_count, 2)
self.assertEqual(C.talent_pool_count, 2)
self.assertEqual(D.talent_pool_count, 2)
self.assertEqual(E.talent_pool_count, 2)
self.assertEqual(F.talent_pool_count, 0)
self.assertEqual(G.talent_pool_count, 1)
def test_compute_and_search_is_applicant_in_pool(self):
"""
Test that the _compute_is_applicant_in_pool and _search_is_applicant_in_pool
methods return correct information.
An application is considered to be in a pool if it is either directly linked
to a pool (through pool_applicant_id or talents_pool_ids) or shares a phone number,
email or linkedin with another directly linked application.
"""
talent_pool = self.env["hr.talent.pool"].create({"name": "Cool Pool"})
job = self.env["hr.job"].create(
{
"name": "Cool Job",
}
)
A, B, C, D, E, F, G, H = self.env["hr.applicant"].create(
[
{
"partner_name": "Talent A",
"email_from": "mainTalentEmail@example.com",
"talent_pool_ids": talent_pool.ids,
},
{
"partner_name": "Applicant 1 B",
"email_from": "otherTalentEmail@example.com",
"partner_phone": "1234",
"linkedin_profile": "linkedin.com/in/applicant",
"job_id": job.id,
},
{
"partner_name": "Applicant 1 C",
"email_from": "otherTalentEmail@example.com",
"job_id": job.id,
},
{
"partner_name": "Applicant 1 D",
"partner_phone": "1234",
"job_id": job.id,
},
{
"partner_name": "Applicant 1 E",
"linkedin_profile": "linkedin.com/in/applicant",
"job_id": job.id,
},
{
"partner_name": "A different applicant F",
"email_from": "differentEmail@example.com",
"partner_phone": "9876",
"linkedin_profile": "linkedin.com/in/NotAnApplicant",
"job_id": job.id,
},
{
"partner_name": "Talent With No information G",
"talent_pool_ids": talent_pool.ids,
},
{
"partner_name": "Applicant With No information H",
},
]
)
B.pool_applicant_id = A.id
H.pool_applicant_id = G.id
# Testing the compute
# A is directly linked to Cool Pool through talent_pool_ids
self.assertTrue(A.is_applicant_in_pool)
# B is directly linked to Cool Pool through pool_applicant_id
self.assertTrue(B.is_applicant_in_pool)
# C is indirectly linked through email to B who is directly linked
self.assertTrue(C.is_applicant_in_pool)
# D is indirectly linked through phone to B who is directly linked
self.assertTrue(D.is_applicant_in_pool)
# E is indirectly linked through linkedin to B who is directly linked
self.assertTrue(E.is_applicant_in_pool)
# F is not linked to a Pool
self.assertFalse(F.is_applicant_in_pool)
# G is directly linked to Cool Pool through talent_pool_ids
self.assertTrue(G.is_applicant_in_pool)
# H is directly linked to Cool Pool through pool_applicant_id
self.assertTrue(H.is_applicant_in_pool)
# Testing the search
# Note: For some reason testing the search does not work if the compute
# is not tested first which is why these two tests are in one test.
applicant = self.env["hr.applicant"]
in_pool_domain = applicant._search_is_applicant_in_pool("in", [True])
in_pool_applicants = applicant.search(Domain.AND([in_pool_domain, [("company_id", "=", self.env.company.id)]]))
out_of_pool_applicants = applicant.search(Domain.AND([~Domain(in_pool_domain), [("company_id", "=", self.env.company.id)]]))
self.assertCountEqual(in_pool_applicants, A | B | C | D | E | G | H)
self.assertCountEqual(out_of_pool_applicants, F)
def test_application_no_partner_duplicate(self):
""" Test that when applying, the existing partner
doesn't get duplicated.
"""
applicant_data = {
'partner_name': 'Test',
'email_from': 'test@thisisatest.com',
}
# First application, a partner should be created
self.env['hr.applicant'].create(applicant_data)
partner_count = self.env['res.partner'].search_count([('email', '=', 'test@thisisatest.com')])
self.assertEqual(partner_count, 1)
# Second application, no partner should be created
self.env['hr.applicant'].create(applicant_data)
partner_count = self.env['res.partner'].search_count([('email', '=', 'test@thisisatest.com')])
self.assertEqual(partner_count, 1)
def test_target_on_application_hiring(self):
"""
Test that the target is updated when hiring an applicant
"""
job = self.env['hr.job'].create({
'name': 'Test Job',
'no_of_recruitment': 1,
})
applicant = self.env['hr.applicant'].create({
'partner_name': 'Test Applicant',
'job_id': job.id,
})
stage_new = self.env['hr.recruitment.stage'].create({
'name': 'New',
'sequence': 0,
'hired_stage': False,
})
stage_hired = self.env['hr.recruitment.stage'].create({
'name': 'Hired',
'sequence': 1,
'hired_stage': True,
})
self.assertEqual(job.no_of_recruitment, 1)
applicant.stage_id = stage_hired
self.assertEqual(job.no_of_recruitment, 0)
applicant.stage_id = stage_new
self.assertEqual(job.no_of_recruitment, 1)
def test_open_refuse_applicant_wizard_without_partner_name(self):
"""Test opening the refuse wizard when the applicant has no partner_name."""
applicant = self.env['hr.applicant'].create({
'partner_phone': '123',
})
wizard = Form(self.env['applicant.get.refuse.reason'].with_context(
default_applicant_ids=[applicant.id], active_test=False))
wizard_applicant = wizard.applicant_ids[0]
self.assertFalse(wizard_applicant.partner_name)
def test_applicant_refuse_reason(self):
refuse_reason = self.env['hr.applicant.refuse.reason'].create([{'name': 'Fired'}])
app_1, app_2 = self.env['hr.applicant'].create([
{
'partner_name': 'Laurie Poiret',
'email_from': 'laurie.poiret@aol.ru',
},
{
'partner_name': 'Mitchell Admin',
'email_from': 'mitchell_admin@example.com',
},
])
applicant_get_refuse_reason = self.env['applicant.get.refuse.reason'].create([{
'refuse_reason_id': refuse_reason.id,
'applicant_ids': [app_1.id],
'duplicates': True
}])
applicant_get_refuse_reason.action_refuse_reason_apply()
self.assertFalse(self.env['hr.applicant'].search([('email_from', 'ilike', 'laurie.poiret@aol.ru')]))
self.assertEqual(
self.env['hr.applicant'].search([('email_from', 'ilike', 'mitchell_admin@example.com')]),
app_2
)
def test_applicant_refuse_mail_from_template(self):
mail_template = self.env['mail.template'].create({
'name': 'Test template',
'model_id': self.env['ir.model']._get('hr.applicant').id,
'email_from': 'test@test.test',
})
refuse_reason = self.env['hr.applicant.refuse.reason'].create({
'name': 'Not good',
})
applicant = self.env['hr.applicant'].create({
'partner_name': 'Laurie Poiret',
'email_from': 'laurie.poiret@aol.ru',
})
applicant_get_refuse_reason = self.env['applicant.get.refuse.reason'].create([{
'refuse_reason_id': refuse_reason.id,
'applicant_ids': applicant.ids,
'duplicates': True,
}])
mail_values = applicant_get_refuse_reason._prepare_mail_values(applicant)
self.assertEqual(mail_values['email_from'], self.env.user.email_formatted)
refuse_reason_template = self.env['hr.applicant.refuse.reason'].create({
'name': 'Fired',
'template_id': mail_template.id,
})
applicant_get_refuse_reason.refuse_reason_id = refuse_reason_template
mail_values = applicant_get_refuse_reason._prepare_mail_values(applicant)
self.assertEqual(mail_values['email_from'], 'test@test.test')
def test_copy_attachments_while_creating_employee(self):
"""
Test that attachments are copied when creating an employee from an applicant
"""
applicant_1 = self.env['hr.applicant'].create({
'partner_name': 'Applicant 1',
'email_from': 'test_applicant@example.com'
})
applicant_attachment = self.Attachment.create({
'datas': self.TEXT,
'name': 'textFile.txt',
'mimetype': 'text/plain',
'res_model': applicant_1._name,
'res_id': applicant_1.id
})
employee_applicant = applicant_1.create_employee_from_applicant()
self.assertTrue(employee_applicant['res_id'])
attachment_employee_applicant = self.Attachment.search([
('res_model', '=', employee_applicant['res_model']),
('res_id', '=', employee_applicant['res_id']),
])
self.assertEqual(applicant_attachment['datas'], attachment_employee_applicant['datas'])
def test_other_applications_count(self):
"""
Test that the application_count field does not change
when archiving or refusing a linked application.
"""
A1, A2, A3 = self.env["hr.applicant"].create(
[
{"partner_name": "test", "email_from": "test@example.com"},
{"partner_name": "test", "email_from": "test@example.com"},
{"partner_name": "test", "email_from": "test@example.com"},
]
)
self.assertEqual(A1.application_count, 3)
# Archive A2
A2.action_archive()
self.assertEqual(
A1.application_count,
3,
"Application_count should not change when archiving a linked application",
)
# Refuse A3
refuse_reason = self.env["hr.applicant.refuse.reason"].create([{"name": "Fired"}])
applicant_get_refuse_reason = self.env["applicant.get.refuse.reason"].create(
[
{
"refuse_reason_id": refuse_reason.id,
"applicant_ids": [A3.id],
}
]
)
applicant_get_refuse_reason.action_refuse_reason_apply()
self.assertEqual(
A1.application_count,
3,
"The other_applications_count should not change when refusing an application",
)
def test_open_other_applications_count(self):
"""
The smart button labeled 'Other Applications N' (where N represents the number of
other job applications linked to the same applicant) should, when clicked, open a list view
displaying all related applications.
This list should include both the N other applications and the current one,
resulting in a total of N + 1 records.
"""
A1, _, _ = self.env["hr.applicant"].create(
[
{"partner_name": "test", "email_from": "test@example.com"},
{"partner_name": "test", "email_from": "test@example.com"},
{"partner_name": "test", "email_from": "test@example.com"},
]
)
res = A1.action_open_applications()
self.assertEqual(len(res['domain'][0][2]), 3, "The list view should display 3 applications")
def test_applicant_modify_email_number(self):
applicant = self.env['hr.applicant'].create({
'partner_name': 'Mary Applicant',
'email_from': 'applicant@example.com',
'partner_phone': '123456789',
})
self.assertEqual(applicant.partner_id.email, 'applicant@example.com', "Email should have been set on the partner.")
self.assertEqual(applicant.partner_id.phone, '123456789', "Phone should have been set on the partner.")
applicant.email_from = 'applicant_diff@example.com'
self.assertEqual(applicant.partner_id.email, 'applicant_diff@example.com', "Email should have been updated on the partner.")
applicant.partner_phone = '987654321'
self.assertEqual(applicant.partner_id.phone, '987654321', "Phone should have been updated on the partner.")
def test_send_mail_when_refuse_applicant(self):
mail_template = self.env['mail.template'].create({
'name': 'Test template',
'model_id': self.env['ir.model']._get('hr.applicant').id,
'subject': 'Application refused: {{ object.partner_name }}',
})
refuse_reason = self.env['hr.applicant.refuse.reason'].create([{
'name': 'Not good',
'template_id': mail_template.id,
}])
app_1 = self.env['hr.applicant'].create({
'partner_name': 'Mario',
'email_from': 'super@mario.bros',
})
applicant_get_refuse_reason = self.env['applicant.get.refuse.reason'].create({
'refuse_reason_id': refuse_reason.id,
'send_mail': True,
'applicant_ids': [(6, 0, [app_1.id])],
})
applicant_get_refuse_reason._prepare_send_refusal_mails()
mail = self.env['mail.mail'].search([('subject', '=', 'Application refused: Mario')], limit=1)
self.assertEqual(mail.partner_ids, app_1.partner_id)