From 3faad0a7a057c9e620e3b6f58d191f64bece6fb8 Mon Sep 17 00:00:00 2001 From: sofiagvaladze Date: Tue, 4 Apr 2023 12:24:54 +0000 Subject: [PATCH] [IMP] hr_recruitment(_skills/_survey): grant interviewer more rights MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Purpose: In order to increase visibility of the interviewer group, it is moved from hidden group, to recruitment group. Moreover, we grant the interviewer additional rights in order to manage the application process properly and be able to execute following actions • Send an interview • Check interview results • Add/delete/modify skills • Create a meeting In order to allow above mentioned actions on skills to the interviewer, we grant them all the rights to the hr_applicant_skill. In order to let the interviewer send the survey we grant them access to the private addresses and now recruitment-interviewer implies survey-officer group as well. Additionally, for the security purposes, we make a change, so that the survey officer is not able to read recruitment interview results unless they are also an interviewer for a specific applicant. task - 3179332 closes odoo/odoo#118897 Related: odoo/enterprise#40696 Related: odoo/upgrade#4624 Signed-off-by: Kevin Baptiste --- addons/hr_recruitment/models/hr_applicant.py | 20 +++++++++---- addons/hr_recruitment/models/ir_ui_menu.py | 4 ++- .../security/hr_recruitment_security.xml | 29 ++++++++++++++++--- .../tests/test_recruitment_interviewer.py | 5 ---- .../hr_recruitment_skills_security.xml | 8 +++++ .../security/ir.model.access.csv | 3 +- .../views/hr_applicant_views.xml | 2 +- .../models/hr_applicant.py | 14 ++++++--- .../hr_recruitment_survey_security.xml | 24 +++++++++++++-- .../views/hr_applicant_views.xml | 2 +- .../wizard/survey_invite.py | 4 +-- 11 files changed, 88 insertions(+), 27 deletions(-) diff --git a/addons/hr_recruitment/models/hr_applicant.py b/addons/hr_recruitment/models/hr_applicant.py index 4daf2848773..4ea632695be 100644 --- a/addons/hr_recruitment/models/hr_applicant.py +++ b/addons/hr_recruitment/models/hr_applicant.py @@ -280,7 +280,7 @@ class Applicant(models.Model): applicant.date_closed = False def _check_interviewer_access(self): - if self.user_has_groups('hr_recruitment.group_hr_recruitment_interviewer'): + if self.user_has_groups('hr_recruitment.group_hr_recruitment_interviewer') and not self.user_has_groups('hr_recruitment.group_hr_recruitment_user'): raise AccessError(_('You are not allowed to perform this action.')) @api.model_create_multi @@ -367,19 +367,20 @@ class Applicant(models.Model): def _notify_get_recipients(self, message, msg_vals, **kwargs): """ - Do not notify members of the Recruitment Interviewer group, as this + Do not notify members of the Recruitment Interviewer group that are not part of + Recruitment User group as well, as this might leak some data they shouldn't have access to. """ recipients = super()._notify_get_recipients(message, msg_vals, **kwargs) interviewer_group = self.env.ref('hr_recruitment.group_hr_recruitment_interviewer').id - return [recipient for recipient in recipients if interviewer_group not in recipient['groups']] + user_group = self.env.ref('hr_recruitment.group_hr_recruitment_user').id + return [recipient for recipient in recipients if not (interviewer_group in recipient['groups'] and user_group not in recipient['groups'])] def action_makeMeeting(self): """ This opens Meeting's calendar view to schedule meeting on current applicant @return: Dictionary value for created Meeting view """ self.ensure_one() - if not self.partner_id: if not self.partner_name: raise UserError(_('You must define a Contact Name for this applicant.')) @@ -392,11 +393,20 @@ class Applicant(models.Model): 'mobile': self.partner_mobile }) - partners = self.partner_id | self.user_id.partner_id | self.department_id.manager_id.user_id.partner_id + partners = self.partner_id | self.department_id.manager_id.user_id.partner_id + if self.user_has_groups('hr_recruitment.group_hr_recruitment_interviewer') and not self.user_has_groups('hr_recruitment.group_hr_recruitment_user'): + partners |= self.env.user.partner_id + else: + partners |= self.user_id.partner_id category = self.env.ref('hr_recruitment.categ_meet_interview') res = self.env['ir.actions.act_window']._for_xml_id('calendar.action_calendar_event') + # As we are redirected from the hr.applicant, calendar checks rules on "hr.applicant", + # in order to decide whether to allow creation of a meeting. + # As interviewer does not have create right on the hr.applicant, in order to allow them + # to create a meeting for an applicant, we pass 'create': True to the context. res['context'] = { + 'create': True, 'default_applicant_id': self.id, 'default_partner_ids': partners.ids, 'default_user_id': self.env.uid, diff --git a/addons/hr_recruitment/models/ir_ui_menu.py b/addons/hr_recruitment/models/ir_ui_menu.py index 8a376826a5c..571b850c68a 100644 --- a/addons/hr_recruitment/models/ir_ui_menu.py +++ b/addons/hr_recruitment/models/ir_ui_menu.py @@ -9,6 +9,8 @@ class IrUiMenu(models.Model): def _load_menus_blacklist(self): res = super()._load_menus_blacklist() - if self.env.user.has_group('hr_recruitment.group_hr_recruitment_interviewer'): + if self.env.user.has_group('hr_recruitment.group_hr_recruitment_interviewer') and not self.env.user.has_group('hr_recruitment.group_hr_recruitment_user'): res.append(self.env.ref('hr_recruitment.menu_hr_job_position').id) + elif self.env.user.has_group('hr_recruitment.group_hr_recruitment_user'): + res.append(self.env.ref('hr_recruitment.menu_hr_job_position_interviewer').id) return res diff --git a/addons/hr_recruitment/security/hr_recruitment_security.xml b/addons/hr_recruitment/security/hr_recruitment_security.xml index 17099c3c87c..2b337c5e3d5 100644 --- a/addons/hr_recruitment/security/hr_recruitment_security.xml +++ b/addons/hr_recruitment/security/hr_recruitment_security.xml @@ -2,7 +2,7 @@ - The user interacting with the application as interviewer don't need any specific access. They'll have access thanks to their interviewer assignation. + Interviewer right will give access to all job position/applications where the employee is defined. It will allow to refuse, plan meetings. Chatter content will not be available. 11 @@ -14,15 +14,15 @@ - Recruitment Interviewer - + Interviewer + Officer: Manage all applicants - + @@ -59,6 +59,20 @@ + + User: All Applicants + + [(1, '=', 1)] + + + + + User: All Applicants + + [(1, '=', 1)] + + + Interviewer: No Applicant Chatter @@ -74,4 +88,11 @@ + + + User: All Chatter + + [(1, '=', 1)] + + diff --git a/addons/hr_recruitment/tests/test_recruitment_interviewer.py b/addons/hr_recruitment/tests/test_recruitment_interviewer.py index 0e13a596e61..eeab5a9d3a3 100644 --- a/addons/hr_recruitment/tests/test_recruitment_interviewer.py +++ b/addons/hr_recruitment/tests/test_recruitment_interviewer.py @@ -67,11 +67,6 @@ class TestRecruitmentInterviewer(MailCommon): applicant.interviewer_ids = False self.assertFalse(interviewer_group.id in self.simple_user.groups_id.ids, "Simple User should be removed from interviewer") - # A Manager should not be added to the Interviewer group - self.assertFalse(interviewer_group.id in self.manager_user.groups_id.ids, "Manager User should not be interviewer") - applicant.interviewer_ids = self.manager_user.ids - self.assertFalse(interviewer_group.id in self.manager_user.groups_id.ids, "Manager User should not be added in Interviewer group") - def test_interviewer_access_rights(self): applicant = self.env['hr.applicant'].create({ 'name': 'toto', diff --git a/addons/hr_recruitment_skills/security/hr_recruitment_skills_security.xml b/addons/hr_recruitment_skills/security/hr_recruitment_skills_security.xml index 3d2eba831db..8352c26e65b 100644 --- a/addons/hr_recruitment_skills/security/hr_recruitment_skills_security.xml +++ b/addons/hr_recruitment_skills/security/hr_recruitment_skills_security.xml @@ -11,4 +11,12 @@ ] + + + Applicant Skill: Officer + + [(1, '=', 1)] + + + diff --git a/addons/hr_recruitment_skills/security/ir.model.access.csv b/addons/hr_recruitment_skills/security/ir.model.access.csv index ec8287d0e9a..f11e20132bf 100644 --- a/addons/hr_recruitment_skills/security/ir.model.access.csv +++ b/addons/hr_recruitment_skills/security/ir.model.access.csv @@ -1,3 +1,2 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -hr_recruitment_skills.access_hr_applicant_skill,access_hr_applicant_skill,hr_recruitment_skills.model_hr_applicant_skill,hr_recruitment.group_hr_recruitment_user,1,1,1,1 -hr_recruitment_skills.access_hr_applicant_skill_interviewer,access_hr_applicant_skill_interviewer,hr_recruitment_skills.model_hr_applicant_skill,hr_recruitment.group_hr_recruitment_interviewer,1,0,0,0 +hr_recruitment_skills.access_hr_applicant_skill_interviewer,access_hr_applicant_skill_interviewer,hr_recruitment_skills.model_hr_applicant_skill,hr_recruitment.group_hr_recruitment_interviewer,1,1,1,1 diff --git a/addons/hr_recruitment_skills/views/hr_applicant_views.xml b/addons/hr_recruitment_skills/views/hr_applicant_views.xml index aac87508aec..bd02884f5c3 100644 --- a/addons/hr_recruitment_skills/views/hr_applicant_views.xml +++ b/addons/hr_recruitment_skills/views/hr_applicant_views.xml @@ -12,7 +12,7 @@ + context="{'default_applicant_id': id}"> diff --git a/addons/hr_recruitment_survey/models/hr_applicant.py b/addons/hr_recruitment_survey/models/hr_applicant.py index 7b62eb205b4..acbdd795753 100644 --- a/addons/hr_recruitment_survey/models/hr_applicant.py +++ b/addons/hr_recruitment_survey/models/hr_applicant.py @@ -18,12 +18,18 @@ class Applicant(models.Model): .filtered(lambda i: i.survey_id == self.survey_id)\ .sorted(lambda i: i.create_date, reverse=True) if not sorted_interviews: - return self.survey_id.action_print_survey() + action = self.survey_id.action_print_survey() + action['target'] = 'new' + return action answered_interviews = sorted_interviews.filtered(lambda i: i.state == 'done') if answered_interviews: - return self.survey_id.action_print_survey(answer=answered_interviews[0]) - return self.survey_id.action_print_survey(answer=sorted_interviews[0]) + action = self.survey_id.action_print_survey(answer=answered_interviews[0]) + action['target'] = 'new' + return action + action = self.survey_id.action_print_survey(answer=sorted_interviews[0]) + action['target'] = 'new' + return action def action_send_survey(self): self.ensure_one() @@ -32,7 +38,7 @@ class Applicant(models.Model): if not self.partner_id: if not self.partner_name: raise UserError(_('Please provide an applicant name.')) - self.partner_id = self.env['res.partner'].create({ + self.partner_id = self.env['res.partner'].sudo().create({ 'is_company': False, 'type': 'private', 'name': self.partner_name, diff --git a/addons/hr_recruitment_survey/security/hr_recruitment_survey_security.xml b/addons/hr_recruitment_survey/security/hr_recruitment_survey_security.xml index 4df1366f2e5..66b739eadb7 100644 --- a/addons/hr_recruitment_survey/security/hr_recruitment_survey_security.xml +++ b/addons/hr_recruitment_survey/security/hr_recruitment_survey_security.xml @@ -1,8 +1,28 @@ - - + + + It will also allow to send surveys and see the resume. + + + Survey user input: officer: read all non private survey answers + [('applicant_id', '=', False)] + + + Survey user input: recruitment interview: read survey answers for which they are set as interviewer + + [ + ('applicant_id', '!=', False), + '|', + ('applicant_id.interviewer_ids', 'in', user.id), + ('applicant_id.job_id.interviewer_ids', 'in', user.id), + ] + + + + + diff --git a/addons/hr_recruitment_survey/views/hr_applicant_views.xml b/addons/hr_recruitment_survey/views/hr_applicant_views.xml index 54680b5d034..fab2019ac64 100644 --- a/addons/hr_recruitment_survey/views/hr_applicant_views.xml +++ b/addons/hr_recruitment_survey/views/hr_applicant_views.xml @@ -26,7 +26,7 @@ icon="fa-pencil-square-o" type="object" help="See interview report" - attrs="{'invisible':['|',('survey_id','=',False),('response_ids','=',False)]}"> + attrs="{'invisible':['|',('survey_id','=',False),('response_ids','=',[])]}">
Consult Interview diff --git a/addons/hr_recruitment_survey/wizard/survey_invite.py b/addons/hr_recruitment_survey/wizard/survey_invite.py index 6d01cc484d5..3c1a9bae9b8 100644 --- a/addons/hr_recruitment_survey/wizard/survey_invite.py +++ b/addons/hr_recruitment_survey/wizard/survey_invite.py @@ -31,8 +31,8 @@ class SurveyInvite(models.TransientModel): survey = self.survey_id.with_context(clean_context(self.env.context)) if not self.applicant_id.response_ids.filtered(lambda res: res.survey_id.id == self.survey_id.id): - self.applicant_id.write({ - 'response_ids': (self.applicant_id.response_ids | survey._create_answer(partner=self.applicant_id.partner_id, + self.applicant_id.sudo().write({ + 'response_ids': (self.applicant_id.response_ids | survey.sudo()._create_answer(partner=self.applicant_id.partner_id, **self._get_answers_values())).ids })