[ADD] RECRUITMENT: Mail template setup

This commit is contained in:
Horilla
2023-12-21 17:15:25 +05:30
parent 501cbc72c2
commit 91841708cf
15 changed files with 523 additions and 47 deletions

View File

@@ -450,8 +450,11 @@ def check_owner(employee, instance):
return False
def generate_pdf(template_path, context):
html = render_to_string(template_path, context)
def generate_pdf(template_path, context, path=True,title=None):
html = template_path
title = f"""{context.get("employee")}'s payslip for {context.get("range")}.pdf""" if not title else title
if path:
html = render_to_string(template_path, context)
result = io.BytesIO()
pdf = pisa.pisaDocument(io.BytesIO(html.encode("utf-8")), result)
@@ -460,6 +463,6 @@ def generate_pdf(template_path, context):
response = HttpResponse(result.getvalue(), content_type="application/pdf")
response[
"Content-Disposition"
] = f'''attachment;filename="{context.get("employee")}'s payslip for {context.get("range")}.pdf"'''
] = f'''attachment;filename="{title}"'''
return response
return None

View File

@@ -10,6 +10,7 @@ from recruitment.models import (
Candidate,
RecruitmentSurvey,
RecruitmentSurveyAnswer,
RecruitmentMailTemplate
)
@@ -21,3 +22,4 @@ admin.site.register(Recruitment)
admin.site.register(Candidate)
admin.site.register(RecruitmentSurveyAnswer)
admin.site.register(RecruitmentSurvey)
admin.site.register(RecruitmentMailTemplate)

View File

@@ -33,10 +33,12 @@ from recruitment.models import (
StageNote,
JobPosition,
RecruitmentSurvey,
RecruitmentMailTemplate,
)
from recruitment import widgets
from base.methods import reload_queryset
class ModelForm(forms.ModelForm):
"""
Overriding django default model form to apply some styles
@@ -575,3 +577,18 @@ class CandidateExportForm(forms.Form):
"joining_date",
],
)
class OfferLetterForm(ModelForm):
"""
OfferLetterForm
"""
class Meta:
model = RecruitmentMailTemplate
fields = "__all__"
widgets = {
"body": forms.Textarea(
attrs={"data-summernote": "", "style": "display:none;"}
),
}

View File

@@ -302,6 +302,16 @@ class Candidate(models.Model):
],
)
sequence = models.IntegerField(null=True, default=0)
offerletter_status = models.CharField(
max_length=20,
choices=[
("not_sent", "Not sent"),
("waiting", "Waiting Confirmation"),
("accepted", "Accepted / Confirmed"),
("rejected", "Rejected / Canceled"),
],
default="not_sent",
)
objects = HorillaCompanyManager(related_company_field="recruitment_id__company_id")
def __str__(self):
@@ -328,6 +338,28 @@ class Candidate(models.Model):
return url
def get_company(self):
"""
This method is used to return the company
"""
return getattr(
getattr(getattr(self, "recruitment_id", None), "company_id", None),
"company",
None,
)
def get_job_position(self):
"""
This method is used to return the job position of the candidate
"""
return self.job_position_id.job_position
def get_email(self):
"""
Return email
"""
return self.email
def tracking(self):
"""
This method is used to return the tracked history of the instance
@@ -374,7 +406,9 @@ class StageNote(models.Model):
description = models.TextField(verbose_name=_("Description"))
stage_id = models.ForeignKey(Stage, on_delete=models.CASCADE)
updated_by = models.ForeignKey(Employee, on_delete=models.CASCADE)
objects = HorillaCompanyManager(related_company_field="candidate_id__recruitment_id__company_id")
objects = HorillaCompanyManager(
related_company_field="candidate_id__recruitment_id__company_id"
)
def __str__(self) -> str:
return f"{self.description}"
@@ -464,3 +498,14 @@ class RecruitmentSurveyAnswer(models.Model):
def __str__(self) -> str:
return f"{self.candidate_id.name}-{self.recruitment_id}"
class RecruitmentMailTemplate(models.Model):
title = models.CharField(max_length=25, unique=True)
body = models.TextField()
company_id = models.ForeignKey(
Company, null=True, blank=True, on_delete=models.CASCADE,verbose_name="Company"
)
def __str__(self) -> str:
return self.title

View File

@@ -0,0 +1,4 @@
{% extends "index.html" %}
{% block content %}
{{form}}
{% endblock content %}

View File

@@ -0,0 +1,32 @@
{% if form.instance.id %}
<form id="editForm" hx-post="{% url "view-mail-template" form.instance.id %}" hx-target="#viewTemplateModalBody">
{% else %}
<form id="createForm" hx-post="{% url "create-mail-template" %}" hx-target="#viewTemplateModalBody">
{% endif %}
<section>
<div id="viewTemplateModalContainer">
<div class="my-3" id="keyResultCard">
<div class="oh-card oh-card--no-shadow oh-card__body">
<div class="oh-input__group">
<label class="oh-input__label mt-0" for="keyTitle">Title</label>
{{ form.title }}
</div>
<div class="oh-input__group">
<label class="oh-input__label mt-2" for="keyTitle">Body</label>
{{ form.body }}
</div>
<div class="oh-input__group">
<label class="oh-input__label mt-2" for="keyTitle">Company</label>
{{ form.company_id }}
</div>
</div>
</div>
</div>
</section>
<button id="submitFormButton" type="submit" hidden ></button>
</form>
<script>
$(document).ready(function () {
setModalLabel({{form.instance.title}}, "#viewTemplateModalLabel")
});
</script>

View File

@@ -0,0 +1,4 @@
{% extends "index.html" %}
{% block content %}
{{instance.body}}
{% endblock content %}

View File

@@ -0,0 +1,75 @@
{% extends 'index.html' %}
{% block content %}
<style>
.truncated-text {
position: relative;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.truncated-text::after {
content: "";
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1) 80%);
pointer-events: none;
/* Allow interaction with the text beneath the overlay */
}
</style>
<section class="oh-wrapper oh-main__topbar">
<div class="oh-main__titlebar oh-main__titlebar--left oh-d-flex-column--resp oh-mb-3--small">
<h1 class="oh-main__titlebar-title fw-bold">Mail Templates</h1>
</div>
<div class="oh-main__titlebar oh-main__titlebar--right oh-d-flex-column--resp oh-mb-3--small">
<a href="#" data-toggle="oh-modal-toggle" data-target="#addTemplateModal" class="oh-btn oh-btn--secondary ml-2"><ion-icon name="add" class="mr-1 md hydrated" role="img" aria-label="add"></ion-icon>Add</a>
</div>
</section>
<div class="oh-wrapper oh-faq-cards">
{% for template in templates %}
<div class="oh-faq-card">
<h3 class="oh-faq-card__title d-flex justify-content-between"><span>{{ template.title }}</span> <a href="{% url "delete-mail-template" %}?ids={{template.id}}" class="text-danger" style="cursor: pointer;" onclick="return confirm('Do you want to delete this template?')"><ion-icon name="trash-outline"></ion-icon></a></h3>
<p class="oh-faq-card__desc oh-card__footer--border-top">
<div style="max-height: 350px;overflow: hidden;" class="truncated-text">{{ template.body|safe }}</div>
</p>
<a hx-get="{% url 'view-mail-template' template.id %}" hx-target="#viewTemplateModalBody" data-toggle="oh-modal-toggle" data-target="#viewTemplateModal" class="oh-btn oh-btn--secondary oh-btn--block">View Template</a>
</div>
{% endfor %}
</div>
<div class="oh-modal" id="viewTemplateModal" role="dialog" aria-labelledby="viewTemplateModal" aria-hidden="true">
<div class="oh-modal__dialog">
<div class="oh-modal__dialog-header">
<span class="oh-modal__dialog-title" id="viewTemplateModalLabel"></span>
<button class="oh-modal__close" aria-label="Close"><ion-icon name="close-outline"></ion-icon></button>
</div>
<div class="oh-modal__dialog-body" id="viewTemplateModalBody"></div>
<div class="oh-modal__dialog-footer">
<button type="submit" onclick="$('#submitFormButton')[0].click()" class="oh-btn oh-btn--secondary oh-btn--shadow">Save Preferences</button>
</div>
</div>
</div>
<div class="oh-modal" id="addTemplateModal" role="dialog" aria-labelledby="addTemplateModal" aria-hidden="true">
<div class="oh-modal__dialog">
<div class="oh-modal__dialog-header">
<span class="oh-modal__dialog-title" id="addTemplateModalLabel">Add Template</span>
<button class="oh-modal__close" aria-label="Close"><ion-icon name="close-outline"></ion-icon></button>
</div>
<div class="oh-modal__dialog-body" id="addTemplateModalBody">
{% include 'offerletter/htmx/form.html' %}
</div>
<div class="oh-modal__dialog-footer">
<button type="submit" onclick="$('#submitFormButton')[0].click()" class="oh-btn oh-btn--secondary oh-btn--shadow">Save Preferences</button>
</div>
</div>
</div>
<script>
function setModalLabel(label, modalTarget) {
$(modalTarget).html(label)
}
</script>
{% endblock %}

View File

@@ -1,13 +1,4 @@
{% load i18n %}
{% if messages %}
<div class="oh-alert-container">
{% for message in messages %}
<div class="oh-alert oh-alert--animated {{message.tags}}">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% if recruitment_form.errors %}
<!-- form errors -->
<div class="oh-wrapper">

View File

@@ -1,4 +1,7 @@
{% extends 'index.html' %} {% load i18n %} {% block content %} {% load static %} {% load recruitmentfilters %}
<div id="messages" class="oh-alert-container">
</div>
<style>
.avatars {
display: flex;

View File

@@ -1,39 +1,120 @@
{% load i18n %}
<div id="ack-message-{{cand.id}}" >
</div>
<form hx-post='{% url "send-acknowledgement" %} ' class="oh-general__tab-target oh-profile-section" id = 'ack-form-{{cand.id}}' hx-target="#ack-message-{{cand.id}}" hx-replace-all>
{% load i18n %}
<div id="ack-message-{{cand.id}}">
</div>
<form hx-post='{% url "send-acknowledgement" %} ' class="oh-general__tab-target oh-profile-section"
id='ack-form-{{cand.id}}' hx-target="#ack-message-{{cand.id}}" hx-encoding="multipart/form-data">
<input type="hidden" value="{{cand.id}}" name="id">
<div class="modal-body">
<div class="form-group">
<label for="to-email"><h6>{% trans "To" %}</h6></label>
<input type="email" value="{{cand.email}}"name='to' required class="oh-input w-100" id="to-email" placeholder="Enter email">
<div class="oh-timeoff-modal__profile-content">
<div class="oh-profile mb-2">
<div class="oh-profile__avatar">
<img src="https://ui-avatars.com/api/?name={{cand.name}}&amp;background=random"
class="oh-profile__image me-2">
</div>
<div class="oh-timeoff-modal__profile-info">
<span class="oh-timeoff-modal__user fw-bold">{{cand.name}}</span>
<span class="oh-timeoff-modal__user m-0" style="font-size: 18px; color: #4d4a4a">
{{cand.job_position_id.job_position}} /
{{cand.recruitment_id}}</span>
</div>
</div>
</div>
<div class="form-group">
<label for="subject"><h6>{% trans "Subject" %}</h6></label>
<input type="text" value="Congratulations..."name='subject' class="oh-input w-100" id="subject" placeholder="Subject">
<div class="form-group mt-2">
<label for="subject">
<h6>{% trans "Subject" %}</h6>
</label>
<input required type="text" placeholder="Congrats..." name='subject' class="oh-input w-100" id="subject"
placeholder="Subject">
</div>
<div class="form-group mt-2">
<label for="template">
<h6>{% trans "Template" %}</h6>
</label>
<select name="template" class="w-100 oh-select" id="template">
<option value="">----</option>
{% for template in templates %}
<option value="{{template.id}}">{{template.title}}</option>
{% endfor %}
</select>
</div>
<div class="form-group mt-2">
<label for="body">
<h6>{% trans "Message Body" %}</h6>
</label>
<textarea hidden data-summernote name="body" required class="oh-input w-100" id="body" cols="30"
rows="2"></textarea>
</div>
<div class="form-group mt-2">
<label for="template_attachments">
<h6>{% trans "Template as Attachment" %}</h6>
</label>
<select name="template_attachments" class="w-100 oh-select" id="template_attachments" multiple>
{% for template in templates %}
<option value="{{template.id}}">{{template.title}}</option>
{% endfor %}
</select>
</div>
<div class="form-group mt-2">
<label for="other_attachments">
<h6>{% trans "Other Attachments" %}</h6>
</label>
<input type="file" name="other_attachments" id="other_attachments" multiple style="display: block;">
</div>
<div class="form-group">
<label for="body"><h6>{% trans "Message Body" %}</h6></label>
<textarea name="body" required class="oh-input w-100" id="body" cols="30" rows="2">
</textarea>
</div>
<div class="modal-footer d-flex flex-row-reverse mt-3">
<input type="submit" class="oh-btn oh-btn--secondary submit-send" data-message-id="ack-message-{{cand.id}}" name="submit" id="submit" onclick="sendMail()" value="{% trans 'Send Mail' %}">
<input type="submit" class="oh-btn oh-btn--secondary submit-send" data-message-id="ack-message-{{cand.id}}"
name="submit" id="submit" onclick="sendMail()" value="{% trans 'Send Mail' %}">
</div>
</form>
</form>
<script>
button = document.querySelector('.submit-send')
button.onclick = function(event) {
button = document.querySelector('.submit-send')
button.onclick = function (event) {
var element = event.target;
messageId = $(element).attr('data-message-id');
$(`#${messageId}`).append(`
<div class="alert alert-primary m-2" role="alert">
Processing...
var valid = true
if (!$("#subject").val().length) {
valid=false
$(`#messages`).html($(`
<div class="oh-alert oh-alert--animated oh-alert--danger" role="alert">
The message subject is required
</div>
`);
};
</script>
`));
}
else if (!$("#body").val().length) {
valid=false
$(`#messages`).html($(`
<div class="oh-alert oh-alert--animated oh-alert--danger" role="alert">
The message body is required
</div>
`));
}
if (valid) {
$(`#messages`).html($(`
<div class="oh-alert oh-alert--animated oh-alert--info" role="alert">
Processing...
</div>
`));
}
};
$(document).ready(function () {
$("#template").change(function (e) {
var id = $(this).val();
if (id.length) {
$.ajax({
type: "get",
url: `/recruitment/get-template/${id}/`,
data: { "candidate_id": "{{cand.id}}" },
dataType: "Json",
success: function (response) {
console.log(response.body);
$('#ack-form-{{cand.id}} [name="body"]').html(response.body).change()
$('#ack-form-{{cand.id}} [class="note-editable"]').html(response.body)
}
});
}
});
});
</script>

View File

@@ -0,0 +1,102 @@
<p dir="ltr" style="line-height:1.3800000000000001;text-align: justify;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<span style="font-size:12pt;font-family:Calibri,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Dear&nbsp;</span><span style="font-size:12pt;font-family:Calibri,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">{{ instance.get_full_name }}</span><span style="font-size:12pt;font-family:Calibri,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">,</span>
</p>
<p dir="ltr" style="line-height:1.3800000000000001;text-align: justify;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<span style="font-size:12pt;font-family:Calibri,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">&nbsp;</span>
</p>
<p dir="ltr" style="line-height:1.3800000000000001;text-align: justify;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<span style="font-size:12pt;font-family:Calibri,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Good day!</span>
</p>
<p dir="ltr" style="line-height:1.3800000000000001;text-align: justify;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<span style="font-size:12pt;font-family:Calibri,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">&nbsp;</span>
</p>
<p dir="ltr" style="line-height:1.3800000000000001;text-align: justify;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<span style="font-size:12pt;font-family:Calibri,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;"><br /></span><span style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-variant-position: normal; font-size: 12pt; font-family: Calibri, sans-serif; background-color: transparent; vertical-align: baseline; white-space-collapse: preserve;">I am pleased to inform you that you have been chosen to work with our company,&nbsp;</span><span style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-variant-position: normal; font-size: 12pt; font-family: Calibri, sans-serif; background-color: transparent; font-weight: 700; vertical-align: baseline; white-space-collapse: preserve;">{{ instance.get_company }}</span><span style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-variant-position: normal; font-size: 12pt; font-family: Calibri, sans-serif; background-color: transparent; vertical-align: baseline; white-space-collapse: preserve;">. For the position of&nbsp;</span><span style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-variant-position: normal; font-size: 12pt; font-family: Calibri, sans-serif; background-color: transparent; font-weight: 700; vertical-align: baseline; white-space-collapse: preserve;">{{ instance.get_job_position }}</span><span style="font-variant-numeric: normal; font-variant-east-asian: normal; font-variant-alternates: normal; font-variant-position: normal; font-size: 12pt; font-family: Calibri, sans-serif; background-color: transparent; vertical-align: baseline; white-space-collapse: preserve;">. As a part of our employee policy, we provide free accommodation to all our employees, and hence you as part of your organization are also eligible for the same.</span>
</p>
<p dir="ltr" style="line-height:1.3800000000000001;text-align: justify;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<span style="font-size:12pt;font-family:Calibri,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">&nbsp;</span>
</p>
<p dir="ltr" style="line-height:1.3800000000000001;text-align: justify;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<span style="font-size:12pt;font-family:Calibri,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Attached hereto is the employment contract with all the required details, your offer letter, and accommodation details. Should you accept this offer, we expect you to revert back to this email within two days upon receipt.</span>
</p>
<p dir="ltr" style="line-height:1.3800000000000001;text-align: justify;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<span style="font-size:12pt;font-family:Calibri,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">&nbsp;</span>
</p>
<p dir="ltr" style="line-height:1.3800000000000001;text-align: justify;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<span style="font-size:12pt;font-family:Calibri,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Welcome aboard! We look forward to having you on our team.&nbsp;</span>
</p>
<p dir="ltr" style="line-height:1.3800000000000001;text-align: justify;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<span style="font-size:12pt;font-family:Calibri,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">&nbsp;</span>
</p>
<p dir="ltr" style="line-height:1.3800000000000001;text-align: justify;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<span style="font-size:12pt;font-family:Calibri,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Regards,</span>
</p>
<p dir="ltr" style="line-height:1.3800000000000001;text-align: justify;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<span style="font-size:12pt;font-family:Calibri,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">&nbsp;</span>
</p>
<p dir="ltr" style="line-height:1.3800000000000001;text-align: justify;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<span style="font-size:12pt;font-family:Calibri,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Regards,</span>
</p>
<p dir="ltr" style="line-height:1.3800000000000001;text-align: justify;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<span style="font-size:12pt;font-family:Calibri,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Adam Luis</span>
</p>
<p dir="ltr" style="line-height:1.3800000000000001;text-align: justify;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<span style="font-size:12pt;font-family:Calibri,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">HR Manager</span><span style="font-size:12pt;font-family:Calibri,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">,&nbsp;</span><span style="font-size:12pt;font-family:Calibri,sans-serif;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">{{ self.get_company }}</span>
</p>
<p dir="ltr" style="line-height:1.3800000000000001;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<br />
</p>
<p dir="ltr" style="line-height:1.3800000000000001;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<br />
</p>
<p dir="ltr" style="line-height:1.3800000000000001;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<br />
</p>
<p dir="ltr" style="line-height:1.3800000000000001;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<br />
</p>
<p dir="ltr" style="line-height:1.3800000000000001;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<br />
</p>
<p dir="ltr" style="line-height:1.3800000000000001;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<br />
</p>
<p dir="ltr" style="line-height:1.3800000000000001;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<br />
</p>
<p dir="ltr" style="line-height:1.3800000000000001;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<br />
</p>
<p dir="ltr" style="line-height:1.3800000000000001;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<br />
</p>
<p dir="ltr" style="line-height:1.3800000000000001;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<br />
</p>
<p dir="ltr" style="line-height:1.3800000000000001;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<br />
</p>
<p dir="ltr" style="line-height:1.3800000000000001;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<br />
</p>
<p dir="ltr" style="line-height:1.3800000000000001;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<br />
</p>
<p dir="ltr" style="line-height:1.3800000000000001;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<br />
</p>
<p dir="ltr" style="line-height:1.3800000000000001;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<br />
</p>
<p dir="ltr" style="line-height:1.3800000000000001;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<br />
</p>
<p dir="ltr" style="line-height:1.3800000000000001;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<br />
</p>
<p dir="ltr" style="line-height:1.3800000000000001;background-color:#ffffff;margin-top:0pt;margin-bottom:0pt;">
<br />
</p>
<p>
<span id="docs-internal-guid-ac0edfad-7fff-5812-3bc2-3630dd38a672"><br /></span>
</p>

View File

@@ -11,6 +11,13 @@ import recruitment.views.actions
import recruitment.views.dashboard
import recruitment.views.search
import recruitment.views.surveys
from recruitment.views.mail_templates import (
view_letter,
view_mail_templates,
create_letter,
delete_mail_templates,
get_template
)
urlpatterns = [
path("recruitment-create", views.recruitment, name="recruitment-create"),
@@ -132,13 +139,13 @@ urlpatterns = [
"candidate-view/<int:cand_id>/",
views.candidate_view_individual,
name="candidate-view-individual",
kwargs={'model':Candidate}
kwargs={"model": Candidate},
),
path(
"candidate-update/<int:cand_id>/",
views.candidate_update,
name="rec-candidate-update",
kwargs={'model':Candidate}
kwargs={"model": Candidate},
),
path(
"delete-profile-image/<int:obj_id>/",
@@ -251,7 +258,15 @@ urlpatterns = [
recruitment.views.surveys.single_survey,
name="single-survey-view",
),
path('candidate-select/', views.candidate_select, name='candidate-select'),
path('candidate-select-filter/', views.candidate_select_filter, name='candidate-select-filter'),
path("candidate-select/", views.candidate_select, name="candidate-select"),
path(
"candidate-select-filter/",
views.candidate_select_filter,
name="candidate-select-filter",
),
path("view-mail-templates/", view_mail_templates, name="view-mail-templates"),
path("view-mail-template/<int:obj_id>/", view_letter, name="view-mail-template"),
path("create-mail-template/", create_letter, name="create-mail-template"),
path("delete-mail-template/", delete_mail_templates, name="delete-mail-template"),
path("get-template/<int:obj_id>/", get_template, name="get-template"),
]

View File

@@ -0,0 +1,91 @@
"""
offerletter.py
This module is related offerletter feature in Horilla
"""
from django.http import HttpResponse, JsonResponse
from django.shortcuts import redirect, render
from django.contrib import messages
from django.views.decorators.http import require_http_methods
from horilla.decorators import login_required, permission_required
from recruitment.models import Candidate, RecruitmentMailTemplate
from recruitment.forms import OfferLetterForm
@login_required
@permission_required("recruitment.view_offerletterdelete")
def view_mail_templates(request):
"""
This method will render template to disply the offerletter templates
"""
templates = RecruitmentMailTemplate.objects.all()
form = OfferLetterForm()
return render(
request,
"offerletter/view_templates.html",
{"templates": templates, "form": form},
)
@login_required
@permission_required("recruitment.change_offerletterdelete")
def view_letter(request, obj_id):
"""
This method is used to display the template/form to edit
"""
template = RecruitmentMailTemplate.objects.get(id=obj_id)
form = OfferLetterForm(instance=template)
if request.method == "POST":
form = OfferLetterForm(request.POST, instance=template)
if form.is_valid():
form.save()
messages.success(request, "Template updated")
return HttpResponse("<script>window.location.reload()</script>")
return render(request, "offerletter/htmx/form.html", {"form": form})
@login_required
@require_http_methods(["POST"])
@permission_required("recruitment.add_offerletterdelete")
def create_letter(request):
"""
This method is used to create offerletter template
"""
if request.method == "POST":
form = OfferLetterForm(request.POST)
if form.is_valid():
instance = form.save()
instance.save()
messages.success(request, "Template created")
return HttpResponse("<script>window.location.reload()</script>")
return redirect(view_mail_templates)
@login_required
@permission_required("recruitment.delete_offerlettertemplate")
def delete_mail_templates(request):
ids = request.GET.getlist("ids")
result = RecruitmentMailTemplate.objects.filter(id__in=ids).delete()
return redirect(view_mail_templates)
from django import template
@login_required
def get_template(request, obj_id):
"""
This method is used to return the mail template
"""
body = RecruitmentMailTemplate.objects.get(id=obj_id).body
candidate_id = request.GET.get("candidate_id")
if candidate_id:
candidate_obj = Candidate.objects.get(id=candidate_id)
template_bdy = template.Template(body)
context = template.Context(
{"instance": candidate_obj, "self": request.user.employee_get}
)
body = template_bdy.render(context)
return JsonResponse({"body": body})

View File

@@ -137,7 +137,18 @@
>{% trans "Recruitment Survey" %}</a
>
</li>
{% endif %} {% if perms.recruitment.view_candidate %}
{% endif %}
{% if perms.recruitment.view_offerlettertemplate %}
<li class="oh-sidebar__submenu-item">
<a
class="oh-sidebar__submenu-link"
href="{% url 'view-mail-templates' %}"
class="oh-sidebar__submenu-link"
>{% trans "Mail Templates" %}</a
>
</li>
{% endif %}
{% if perms.recruitment.view_candidate %}
<li class="oh-sidebar__submenu-item">
<a
class="oh-sidebar__submenu-link"