[IMP] hr_recruitment_survey: add interview template
This commit adds a new mail template to be sent by default for interviews. task-2884526 closes odoo/odoo#94178 Related: odoo/upgrade#3731 Signed-off-by: Kevin Baptiste <kba@odoo.com>
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
'depends': ['survey', 'hr_recruitment'],
|
||||
'data': [
|
||||
'security/hr_recruitment_survey_security.xml',
|
||||
'data/mail_template_data.xml',
|
||||
'views/hr_job_views.xml',
|
||||
'views/hr_applicant_views.xml',
|
||||
'views/res_config_setting_views.xml',
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="mail_template_applicant_interview_invite" model="mail.template">
|
||||
<field name="name">Applicant: Interview</field>
|
||||
<field name="model_id" ref="model_survey_user_input" />
|
||||
<field name="subject">Participate to {{ object.survey_id.display_name }} interview</field>
|
||||
<field name="email_to">{{ (object.partner_id.email_formatted or object.email) }}</field>
|
||||
<field name="body_html" type="html">
|
||||
<div style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||
Dear <t t-out="object.partner_id.name or 'applicant'">[applicant name]</t><br/><br/>
|
||||
<t>
|
||||
You've progressed through the recruitment process and we would like you to answer some questions.
|
||||
</t>
|
||||
<div style="margin: 16px 0px 16px 0px;">
|
||||
<a t-att-href="(object.get_start_url())"
|
||||
style="background-color: #875A7B; padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px; font-size:13px;">
|
||||
<t>
|
||||
Start the written interview
|
||||
</t>
|
||||
</a>
|
||||
</div>
|
||||
<t t-if="object.deadline">
|
||||
Please answer the interview for <t t-out="format_date(object.deadline)">[deadline date]</t>.<br/><br/>
|
||||
</t>
|
||||
<t>
|
||||
We wish you good luck! Thank you in advance for your participation.
|
||||
</t>
|
||||
</p>
|
||||
</div>
|
||||
</field>
|
||||
<field name="lang">{{ object.partner_id.lang }}</field>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,5 +1,6 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import timedelta
|
||||
from odoo import fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
@@ -8,13 +9,21 @@ class Applicant(models.Model):
|
||||
_inherit = "hr.applicant"
|
||||
|
||||
survey_id = fields.Many2one('survey.survey', related='job_id.survey_id', string="Survey", readonly=True)
|
||||
response_id = fields.Many2one('survey.user_input', "Response", ondelete="set null", copy=False)
|
||||
response_state = fields.Selection(related='response_id.state', readonly=True)
|
||||
response_ids = fields.One2many('survey.user_input', 'applicant_id', string="Responses")
|
||||
|
||||
def action_print_survey(self):
|
||||
""" If response is available then print this response otherwise print survey form (print template of the survey) """
|
||||
self.ensure_one()
|
||||
return self.survey_id.action_print_survey(answer=self.response_id)
|
||||
sorted_interviews = self.response_ids\
|
||||
.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()
|
||||
|
||||
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])
|
||||
|
||||
def action_send_survey(self):
|
||||
self.ensure_one()
|
||||
@@ -31,4 +40,24 @@ class Applicant(models.Model):
|
||||
'phone': self.partner_phone,
|
||||
'mobile': self.partner_mobile
|
||||
})
|
||||
return self.survey_id.with_context(default_applicant_id=self.id, default_partner_ids=self.partner_id.ids).action_send_survey()
|
||||
|
||||
self.survey_id.check_validity()
|
||||
template = self.env.ref('hr_recruitment_survey.mail_template_applicant_interview_invite', raise_if_not_found=False)
|
||||
local_context = dict(
|
||||
default_applicant_id=self.id,
|
||||
default_partner_ids=self.partner_id.ids,
|
||||
default_survey_id=self.survey_id.id,
|
||||
default_use_template=bool(template),
|
||||
default_template_id=template and template.id or False,
|
||||
default_email_layout_xmlid='mail.mail_notification_light',
|
||||
default_deadline=fields.Datetime.now() + timedelta(days=15)
|
||||
)
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _("Send an interview"),
|
||||
'view_mode': 'form',
|
||||
'res_model': 'survey.invite',
|
||||
'target': 'new',
|
||||
'context': local_context,
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@ from odoo import fields, models, _
|
||||
class SurveyUserInput(models.Model):
|
||||
_inherit = "survey.user_input"
|
||||
|
||||
applicant_ids = fields.One2many('hr.applicant', 'response_id', string='Applicant')
|
||||
applicant_id = fields.Many2one('hr.applicant', string='Applicant')
|
||||
|
||||
def _mark_done(self):
|
||||
odoobot = self.env.ref('base.partner_root')
|
||||
for user_input in self:
|
||||
if user_input.applicant_ids:
|
||||
body = _('The applicant "%s" has finished the survey.', user_input.applicant_ids[:1].partner_name)
|
||||
user_input.applicant_ids.message_post(body=body, author_id=odoobot.id)
|
||||
if user_input.applicant_id:
|
||||
body = _('The applicant "%s" has finished the survey.', user_input.applicant_id.partner_name)
|
||||
user_input.applicant_id.message_post(body=body, author_id=odoobot.id)
|
||||
return super()._mark_done()
|
||||
|
||||
@@ -37,7 +37,7 @@ class TestRecruitmentSurvey(common.SingleTransactionCase):
|
||||
|
||||
def test_send_survey(self):
|
||||
# We ensure that response is False because we don't know test order
|
||||
self.job_sysadmin.response_id = False
|
||||
self.job_sysadmin.response_ids = False
|
||||
Answer = self.env['survey.user_input']
|
||||
answers = Answer.search([('survey_id', '=', self.survey_sysadmin.id)])
|
||||
answers.unlink()
|
||||
@@ -52,19 +52,19 @@ class TestRecruitmentSurvey(common.SingleTransactionCase):
|
||||
invite.action_invite()
|
||||
|
||||
self.assertEqual(invite.applicant_id, self.job_sysadmin)
|
||||
self.assertNotEqual(self.job_sysadmin.response_id.id, False)
|
||||
self.assertNotEqual(self.job_sysadmin.response_ids.ids, False)
|
||||
answers = Answer.search([('survey_id', '=', self.survey_sysadmin.id)])
|
||||
self.assertEqual(len(answers), 1)
|
||||
self.assertEqual(self.job_sysadmin.response_id, answers)
|
||||
self.assertEqual(self.job_sysadmin.response_ids, answers)
|
||||
self.assertEqual(
|
||||
set(answers.mapped('email')),
|
||||
set([self.job_sysadmin.email_from]))
|
||||
|
||||
def test_print_survey(self):
|
||||
# We ensure that response is False because we don't know test order
|
||||
self.job_sysadmin.response_id = False
|
||||
self.job_sysadmin.response_ids = False
|
||||
action_print = self.job_sysadmin.action_print_survey()
|
||||
self.assertEqual(action_print['type'], 'ir.actions.act_url')
|
||||
self.job_sysadmin.response_id = self.env['survey.user_input'].create({'survey_id': self.survey_sysadmin.id})
|
||||
self.job_sysadmin.response_ids = self.env['survey.user_input'].create({'survey_id': self.survey_sysadmin.id})
|
||||
action_print_with_response = self.job_sysadmin.action_print_survey()
|
||||
self.assertIn(self.job_sysadmin.response_id.access_token, action_print_with_response['url'])
|
||||
self.assertIn(self.job_sysadmin.response_ids.access_token, action_print_with_response['url'])
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='source_id']" position="after">
|
||||
<field name="survey_id" invisible="1"/>
|
||||
<field name="response_id" invisible="1"/>
|
||||
<field name="response_ids" invisible="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
@@ -18,7 +18,7 @@
|
||||
<field name="inherit_id" ref="hr_recruitment.hr_applicant_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[@name='archive_applicant']" position="before">
|
||||
<button name="action_send_survey" string="SEND INTERVIEW" type="object" attrs="{'invisible': ['|', ('active', '=', False), ('survey_id', '=', False)]}"/>
|
||||
<button name="action_send_survey" string="Send Interview" type="object" attrs="{'invisible': ['|', ('active', '=', False), ('survey_id', '=', False)]}"/>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='action_makeMeeting']" position="after">
|
||||
<button name="action_print_survey"
|
||||
@@ -26,7 +26,7 @@
|
||||
icon="fa-pencil-square-o"
|
||||
type="object"
|
||||
help="See interview report"
|
||||
attrs="{'invisible':['|',('survey_id','=',False),('response_id','=',False)]}">
|
||||
attrs="{'invisible':['|',('survey_id','=',False),('response_ids','=',False)]}">
|
||||
<div class="o_field_widget o_stat_info">
|
||||
<span class="o_stat_text">Consult</span>
|
||||
<span class="o_stat_text">Interview</span>
|
||||
@@ -35,8 +35,7 @@
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='job_id']" position="before">
|
||||
<field name="survey_id" invisible="1"/>
|
||||
<field name="response_id" invisible="1"/>
|
||||
<field name="response_state" invisible="1"/>
|
||||
<field name="response_ids" invisible="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -9,14 +9,28 @@ class SurveyInvite(models.TransientModel):
|
||||
|
||||
applicant_id = fields.Many2one('hr.applicant', string='Applicant')
|
||||
|
||||
def _get_done_partners_emails(self, existing_answers):
|
||||
partners_done, emails_done, answers = super()._get_done_partners_emails(existing_answers)
|
||||
if self.applicant_id.response_ids.filtered(lambda res: res.survey_id.id == self.survey_id.id):
|
||||
if existing_answers and self.existing_mode == 'resend':
|
||||
partners_done |= self.applicant_id.partner_id
|
||||
return partners_done, emails_done, answers
|
||||
|
||||
def _send_mail(self, answer):
|
||||
mail = super()._send_mail(answer)
|
||||
if answer.applicant_id:
|
||||
answer.applicant_id.message_post(body=mail.body_html)
|
||||
return mail
|
||||
|
||||
def action_invite(self):
|
||||
self.ensure_one()
|
||||
if self.applicant_id:
|
||||
survey = self.survey_id.with_context(clean_context(self.env.context))
|
||||
|
||||
if not self.applicant_id.response_id:
|
||||
if not self.applicant_id.response_ids.filtered(lambda res: res.survey_id.id == self.survey_id.id):
|
||||
self.applicant_id.write({
|
||||
'response_id': survey._create_answer(partner=self.applicant_id.partner_id).id
|
||||
'response_ids': (self.applicant_id.response_ids | survey._create_answer(partner=self.applicant_id.partner_id,
|
||||
**self._get_answers_values())).ids
|
||||
})
|
||||
|
||||
partner = self.applicant_id.partner_id
|
||||
|
||||
@@ -902,8 +902,7 @@ class Survey(models.Model):
|
||||
# ACTIONS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def action_send_survey(self):
|
||||
""" Open a window to compose an email, pre-filled with the survey message """
|
||||
def check_validity(self):
|
||||
# Ensure that this survey has at least one question.
|
||||
if not self.question_ids:
|
||||
raise UserError(_('You cannot send an invitation for a survey that has no questions.'))
|
||||
@@ -918,6 +917,10 @@ class Survey(models.Model):
|
||||
if not self.active:
|
||||
raise exceptions.UserError(_("You cannot send invitations for closed surveys."))
|
||||
|
||||
def action_send_survey(self):
|
||||
""" Open a window to compose an email, pre-filled with the survey message """
|
||||
self.check_validity()
|
||||
|
||||
template = self.env.ref('survey.mail_template_user_input_invite', raise_if_not_found=False)
|
||||
|
||||
local_context = dict(
|
||||
|
||||
@@ -48,7 +48,7 @@ class SurveyInvite(models.TransientModel):
|
||||
existing_mode = fields.Selection([
|
||||
('new', 'New invite'), ('resend', 'Resend invite')],
|
||||
string='Handle existing', default='resend', required=True)
|
||||
existing_text = fields.Text('Resend Comment', compute='_compute_existing_text', readonly=True, store=False)
|
||||
existing_text = fields.Text('Resend Comment', compute='_compute_existing_text')
|
||||
# technical info
|
||||
mail_server_id = fields.Many2one('ir.mail_server', 'Outgoing mail server')
|
||||
# survey
|
||||
@@ -71,30 +71,33 @@ class SurveyInvite(models.TransientModel):
|
||||
|
||||
@api.depends('partner_ids', 'survey_id')
|
||||
def _compute_existing_partner_ids(self):
|
||||
self.existing_partner_ids = list(set(self.survey_id.user_input_ids.partner_id.ids) & set(self.partner_ids.ids))
|
||||
for wizard in self:
|
||||
wizard.existing_partner_ids = list(set(wizard.survey_id.user_input_ids.partner_id.ids) & set(wizard.partner_ids.ids))
|
||||
|
||||
@api.depends('emails', 'survey_id')
|
||||
def _compute_existing_emails(self):
|
||||
emails = list(set(emails_split.split(self.emails or "")))
|
||||
existing_emails = self.survey_id.mapped('user_input_ids.email')
|
||||
self.existing_emails = '\n'.join(email for email in emails if email in existing_emails)
|
||||
for wizard in self:
|
||||
emails = list(set(emails_split.split(wizard.emails or "")))
|
||||
existing_emails = wizard.survey_id.mapped('user_input_ids.email')
|
||||
wizard.existing_emails = '\n'.join(email for email in emails if email in existing_emails)
|
||||
|
||||
@api.depends('existing_partner_ids', 'existing_emails')
|
||||
def _compute_existing_text(self):
|
||||
existing_text = False
|
||||
if self.existing_partner_ids:
|
||||
existing_text = '%s: %s.' % (
|
||||
_('The following customers have already received an invite'),
|
||||
', '.join(self.mapped('existing_partner_ids.name'))
|
||||
)
|
||||
if self.existing_emails:
|
||||
existing_text = '%s\n' % existing_text if existing_text else ''
|
||||
existing_text += '%s: %s.' % (
|
||||
_('The following emails have already received an invite'),
|
||||
self.existing_emails
|
||||
)
|
||||
for wizard in self:
|
||||
existing_text = False
|
||||
if wizard.existing_partner_ids:
|
||||
existing_text = '%s: %s.' % (
|
||||
_('The following customers have already received an invite'),
|
||||
', '.join(wizard.mapped('existing_partner_ids.name'))
|
||||
)
|
||||
if wizard.existing_emails:
|
||||
existing_text = '%s\n' % existing_text if existing_text else ''
|
||||
existing_text += '%s: %s.' % (
|
||||
_('The following emails have already received an invite'),
|
||||
wizard.existing_emails
|
||||
)
|
||||
|
||||
self.existing_text = existing_text
|
||||
wizard.existing_text = existing_text
|
||||
|
||||
@api.depends('survey_id.access_token')
|
||||
def _compute_survey_start_url(self):
|
||||
@@ -170,13 +173,23 @@ class SurveyInvite(models.TransientModel):
|
||||
# ------------------------------------------------------
|
||||
|
||||
def _prepare_answers(self, partners, emails):
|
||||
answers = self.env['survey.user_input']
|
||||
existing_answers = self.env['survey.user_input'].search([
|
||||
'&', ('survey_id', '=', self.survey_id.id),
|
||||
'|',
|
||||
('partner_id', 'in', partners.ids),
|
||||
('email', 'in', emails)
|
||||
])
|
||||
partners_done, emails_done, answers = self._get_done_partners_emails(existing_answers)
|
||||
|
||||
for new_partner in partners - partners_done:
|
||||
answers |= self.survey_id._create_answer(partner=new_partner, check_attempts=False, **self._get_answers_values())
|
||||
for new_email in [email for email in emails if email not in emails_done]:
|
||||
answers |= self.survey_id._create_answer(email=new_email, check_attempts=False, **self._get_answers_values())
|
||||
|
||||
return answers
|
||||
|
||||
def _get_done_partners_emails(self, existing_answers):
|
||||
answers = self.env['survey.user_input']
|
||||
partners_done = self.env['res.partner']
|
||||
emails_done = []
|
||||
if existing_answers:
|
||||
@@ -195,13 +208,7 @@ class SurveyInvite(models.TransientModel):
|
||||
answers |= next(existing_answer for existing_answer in
|
||||
existing_answers.sorted(lambda answer: answer.create_date, reverse=True)
|
||||
if existing_answer.email == email_done)
|
||||
|
||||
for new_partner in partners - partners_done:
|
||||
answers |= self.survey_id._create_answer(partner=new_partner, check_attempts=False, **self._get_answers_values())
|
||||
for new_email in [email for email in emails if email not in emails_done]:
|
||||
answers |= self.survey_id._create_answer(email=new_email, check_attempts=False, **self._get_answers_values())
|
||||
|
||||
return answers
|
||||
return (partners_done, emails_done, answers)
|
||||
|
||||
def _get_answers_values(self):
|
||||
return {
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
}"
|
||||
placeholder="e.g. 'Rick Sanchez' <rick_sanchez@example.com>, hictor_vugo@example.com"/>
|
||||
</group>
|
||||
<field name="existing_partner_ids" invisible="1"/>
|
||||
<field name="existing_emails" invisible="1"/>
|
||||
<div col="2" class="alert alert-warning" role="alert"
|
||||
attrs="{'invisible': ['|', ('survey_access_mode', '=', 'public'), ('existing_text', '=', False)]}">
|
||||
<field name="existing_text"/>
|
||||
@@ -43,8 +45,6 @@
|
||||
<p attrs="{'invisible': [('existing_mode', '!=', 'new')]}">Customers will receive a new token and be able to completely retake the survey.</p>
|
||||
</div>
|
||||
</group>
|
||||
<field name="existing_partner_ids" invisible="1"/>
|
||||
<field name="existing_emails" invisible="1"/>
|
||||
</div>
|
||||
<group col="2">
|
||||
<field name="subject" attrs="{'invisible': [('send_email', '=', False)]}" placeholder="Subject..."/>
|
||||
@@ -58,7 +58,7 @@
|
||||
<field name="attachment_ids" widget="many2many_binary"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="deadline"/>
|
||||
<field name="deadline" widget="date"/>
|
||||
<field name="template_id" label="Use template"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
Reference in New Issue
Block a user