[ADD] RECRUITMENT: Bulk mail send feature
This commit is contained in:
@@ -75,7 +75,7 @@
|
||||
data-job-position="{{cand.job_position_id}}">
|
||||
<div class="oh-sticky-table__sd" style="z-index: 11 !important;" onclick="event.stopPropagation()">
|
||||
<div class="centered-div">
|
||||
<input type="checkbox" id="65"
|
||||
<input type="checkbox" id="{{cand.id}}"
|
||||
class="oh-input candidate-checkbox oh-input__checkbox stage-candidate-row"
|
||||
onchange="highlightRow($(this));
|
||||
if (!$(this).is(':checked')) {
|
||||
|
||||
@@ -53,6 +53,12 @@
|
||||
|
||||
class="oh-dropdown__link">{% trans "Edit" %}</a>
|
||||
</li>
|
||||
<li class="oh-dropdown__item" style="cursor: pointer;">
|
||||
<a hx-get='{% url "send-mail" %}?stage_id={{stage.id}}' hx-target="#updateStageModalBody"
|
||||
data-toggle="oh-modal-toggle" data-target="#updateStageModal"
|
||||
|
||||
class="oh-dropdown__link">{% trans "Bulk mail" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.recruitment.delete_stage %}
|
||||
<li class="oh-dropdown__item">
|
||||
|
||||
@@ -1,136 +1,163 @@
|
||||
{% load i18n %}
|
||||
<div id="ack-message-{{cand.id}}">
|
||||
</div>
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="sendMailModalLabel"><h5>{% trans 'Send Mail' %}</h5></span>
|
||||
<button class="oh-modal__close" aria-label="Close"><ion-icon name="close-outline"></ion-icon></button>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-body">
|
||||
<form onsubmit="$(this).closest('.oh-modal--show').removeClass('oh-modal--show')"
|
||||
hx-post='{% url "send-acknowledgement" %} '
|
||||
hx-swap="none"
|
||||
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="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}}&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 mt-2">
|
||||
<label for="to">
|
||||
<h6>{% trans "To" %}</h6>
|
||||
</label>
|
||||
<input required type="text" value="{{cand.email}}" name='to' class="oh-input w-100" id="to"
|
||||
placeholder="Subject">
|
||||
</div>
|
||||
<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="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' %}">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
button = document.querySelector('.submit-send')
|
||||
button.onclick = function (event) {
|
||||
var element = event.target;
|
||||
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>
|
||||
`));
|
||||
}
|
||||
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) {
|
||||
Swal.fire({
|
||||
title: "Processing",
|
||||
text: "Mail will sent on the background",
|
||||
icon: "info"
|
||||
});
|
||||
}
|
||||
};
|
||||
$(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)
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
{% load i18n %}
|
||||
<div id="ack-message-{{cand.id}}">
|
||||
</div>
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="sendMailModalLabel"><h5>{% trans 'Send Mail' %}</h5></span>
|
||||
<button class="oh-modal__close" aria-label="Close"><ion-icon name="close-outline"></ion-icon></button>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-body">
|
||||
<form onsubmit="$(this).closest('.oh-modal--show').removeClass('oh-modal--show')"
|
||||
hx-post='{% url "send-acknowledgement" %} '
|
||||
hx-swap="none"
|
||||
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">
|
||||
{% if cand %}
|
||||
<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}}&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 mt-2">
|
||||
<label for="to">
|
||||
<h6>{% trans "To" %}</h6>
|
||||
</label>
|
||||
<input required type="text" value="{{cand.email}}" name='to' class="oh-input w-100" id="to"
|
||||
placeholder="Subject">
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-group mt-2">
|
||||
<label for="candidates">
|
||||
<h6>{% trans "Also send to" %}</h6>
|
||||
</label>
|
||||
<select class="oh-select oh-select-2" {% if not cand %} required {% endif %} name="candidates" id="candidates" multiple>
|
||||
{% for cand in candidates %}
|
||||
<option value="{{cand.id}}">{{cand}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<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="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' %}">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
{% if stage_id %}
|
||||
$(document).ready(function () {
|
||||
var idsArray = $("#candidateContainer{{stage_id}}")
|
||||
.find(".candidate-checkbox[type=checkbox]:checked")
|
||||
.map(function() {
|
||||
console.log($(this))
|
||||
return this.id;
|
||||
}).get();
|
||||
$("#candidates").val(idsArray).change()
|
||||
});
|
||||
{% else %}
|
||||
var selectedIds = JSON.parse($("#selectedInstances").attr("data-ids"));
|
||||
$("#candidates[name=candidates]select[multiple]").val(selectedIds).change()
|
||||
{% endif %}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
button = document.querySelector('.submit-send')
|
||||
button.onclick = function (event) {
|
||||
var element = event.target;
|
||||
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>
|
||||
`));
|
||||
}
|
||||
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) {
|
||||
Swal.fire({
|
||||
title: "Processing",
|
||||
text: "Mail will sent on the background",
|
||||
icon: "info"
|
||||
});
|
||||
}
|
||||
};
|
||||
$(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>
|
||||
@@ -198,6 +198,7 @@ urlpatterns = [
|
||||
name="note-delete-individual",
|
||||
),
|
||||
path("send-mail/<int:cand_id>/", views.form_send_mail, name="send-mail"),
|
||||
path("send-mail/", views.form_send_mail, name="send-mail"),
|
||||
path("candidate-view/", views.candidate_view, name="candidate-view"),
|
||||
path(
|
||||
"candidate-filter-view",
|
||||
|
||||
@@ -1426,16 +1426,32 @@ def candidate_history(request, cand_id):
|
||||
@login_required
|
||||
@hx_request_required
|
||||
@manager_can_enter(perm="recruitment.change_candidate")
|
||||
def form_send_mail(request, cand_id):
|
||||
def form_send_mail(request, cand_id=None):
|
||||
"""
|
||||
This method is used to render the bootstrap modal content body form
|
||||
"""
|
||||
candidate_obj = Candidate.objects.get(id=cand_id)
|
||||
candidate_obj = None
|
||||
stage_id = None
|
||||
if request.GET.get("stage_id"):
|
||||
stage_id = eval(request.GET.get("stage_id"))
|
||||
if cand_id:
|
||||
candidate_obj = Candidate.objects.get(id=cand_id)
|
||||
candidates = Candidate.objects.all()
|
||||
if stage_id and isinstance(stage_id, int):
|
||||
candidates = candidates.filter(stage_id__id=stage_id)
|
||||
else:
|
||||
stage_id = None
|
||||
|
||||
templates = RecruitmentMailTemplate.objects.all()
|
||||
return render(
|
||||
request,
|
||||
"pipeline/pipeline_components/send_mail.html",
|
||||
{"cand": candidate_obj, "templates": templates},
|
||||
{
|
||||
"cand": candidate_obj,
|
||||
"templates": templates,
|
||||
"candidates": candidates,
|
||||
"stage_id": stage_id,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -1445,58 +1461,67 @@ def send_acknowledgement(request):
|
||||
"""
|
||||
This method is used to send acknowledgement mail to the candidate
|
||||
"""
|
||||
candidate_id = request.POST["id"]
|
||||
candidate_id = request.POST.get("id")
|
||||
subject = request.POST.get("subject")
|
||||
bdy = request.POST.get("body")
|
||||
candidate_ids = request.POST.getlist("candidates")
|
||||
candidates = Candidate.objects.filter(id__in=candidate_ids)
|
||||
|
||||
other_attachments = request.FILES.getlist("other_attachments")
|
||||
attachments = [
|
||||
(file.name, file.read(), file.content_type) for file in other_attachments
|
||||
]
|
||||
email_backend = ConfiguredEmailBackend()
|
||||
host = email_backend.dynamic_username
|
||||
candidate_obj = Candidate.objects.get(id=candidate_id)
|
||||
if candidate_id:
|
||||
candidate_obj = Candidate.objects.filter(id=candidate_id)
|
||||
else:
|
||||
candidate_obj = Candidate.objects.none()
|
||||
candidates = (candidates | candidate_obj).distinct()
|
||||
|
||||
template_attachment_ids = request.POST.getlist("template_attachments")
|
||||
bodys = list(
|
||||
RecruitmentMailTemplate.objects.filter(
|
||||
id__in=template_attachment_ids
|
||||
).values_list("body", flat=True)
|
||||
)
|
||||
for html in bodys:
|
||||
# due to not having solid template we first need to pass the context
|
||||
template_bdy = template.Template(html)
|
||||
for candidate in candidates:
|
||||
bodys = list(
|
||||
RecruitmentMailTemplate.objects.filter(
|
||||
id__in=template_attachment_ids
|
||||
).values_list("body", flat=True)
|
||||
)
|
||||
for html in bodys:
|
||||
# due to not having solid template we first need to pass the context
|
||||
template_bdy = template.Template(html)
|
||||
context = template.Context(
|
||||
{"instance": candidate, "self": request.user.employee_get}
|
||||
)
|
||||
render_bdy = template_bdy.render(context)
|
||||
attachments.append(
|
||||
(
|
||||
"Document",
|
||||
generate_pdf(render_bdy, {}, path=False, title="Document").content,
|
||||
"application/pdf",
|
||||
)
|
||||
)
|
||||
|
||||
template_bdy = template.Template(bdy)
|
||||
context = template.Context(
|
||||
{"instance": candidate_obj, "self": request.user.employee_get}
|
||||
{"instance": candidate, "self": request.user.employee_get}
|
||||
)
|
||||
render_bdy = template_bdy.render(context)
|
||||
attachments.append(
|
||||
(
|
||||
"Document",
|
||||
generate_pdf(render_bdy, {}, path=False, title="Document").content,
|
||||
"application/pdf",
|
||||
)
|
||||
to = candidate.email
|
||||
email = EmailMessage(
|
||||
subject,
|
||||
render_bdy,
|
||||
host,
|
||||
[to],
|
||||
)
|
||||
email.content_subtype = "html"
|
||||
|
||||
template_bdy = template.Template(bdy)
|
||||
context = template.Context(
|
||||
{"instance": candidate_obj, "self": request.user.employee_get}
|
||||
)
|
||||
render_bdy = template_bdy.render(context)
|
||||
to = request.POST["to"]
|
||||
email = EmailMessage(
|
||||
subject,
|
||||
render_bdy,
|
||||
host,
|
||||
[to],
|
||||
)
|
||||
email.content_subtype = "html"
|
||||
|
||||
email.attachments = attachments
|
||||
try:
|
||||
email.send()
|
||||
messages.success(request, "Mail sent to candidate")
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
messages.error(request, "Something went wrong")
|
||||
email.attachments = attachments
|
||||
try:
|
||||
email.send()
|
||||
messages.success(request, "Mail sent to candidate")
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
messages.error(request, "Something went wrong")
|
||||
return HttpResponse("<script>window.location.reload()</script>")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user