email for interview
This commit is contained in:
parent
4b3425f07e
commit
d90a8d5048
@ -62,6 +62,7 @@ INSTALLED_APPS = [
|
||||
"django_q",
|
||||
"widget_tweaks",
|
||||
"easyaudit",
|
||||
"secured_fields",
|
||||
|
||||
]
|
||||
|
||||
@ -538,3 +539,4 @@ LOGGING={
|
||||
}
|
||||
|
||||
|
||||
SECURED_FIELDS_KEY="kvaCwxrIMtVRouBH5mzf9g-uelv7XUD840ncAiOXkt4="
|
||||
@ -714,7 +714,7 @@ class BulkInterviewTemplateForm(forms.ModelForm):
|
||||
|
||||
class InterviewCancelForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Interview
|
||||
model = ScheduledInterview
|
||||
fields = ["cancelled_reason","cancelled_at"]
|
||||
widgets = {
|
||||
"cancelled_reason": forms.Textarea(
|
||||
@ -2032,3 +2032,74 @@ class SettingsForm(forms.ModelForm):
|
||||
if not value:
|
||||
raise forms.ValidationError("Setting value cannot be empty.")
|
||||
return value
|
||||
|
||||
|
||||
class InterviewEmailForm(forms.Form):
|
||||
"""Form for composing emails to participants about a candidate"""
|
||||
to = forms.MultipleChoiceField(
|
||||
widget=forms.CheckboxSelectMultiple(attrs={
|
||||
'class': 'form-check'
|
||||
}),
|
||||
label=_('Select Candidates'), # Use a descriptive label
|
||||
required=False
|
||||
)
|
||||
|
||||
subject = forms.CharField(
|
||||
max_length=200,
|
||||
widget=forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': 'Enter email subject',
|
||||
'required': True
|
||||
}),
|
||||
label=_('Subject'),
|
||||
required=True
|
||||
)
|
||||
|
||||
message = forms.CharField(
|
||||
widget=forms.Textarea(attrs={
|
||||
'class': 'form-control',
|
||||
'rows': 8,
|
||||
'placeholder': 'Enter your message here...',
|
||||
'required': True
|
||||
}),
|
||||
label=_('Message'),
|
||||
required=True
|
||||
)
|
||||
|
||||
def __init__(self, job, application,schedule, *args, **kwargs):
|
||||
applicant=application.person.user
|
||||
interview=schedule.interview
|
||||
|
||||
if application.hiring_agency:
|
||||
self.fields['to'].initial=application.hiring_agency.email
|
||||
else:
|
||||
self.fields['to'].initial=application.person.email
|
||||
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
# Set initial message with candidate and meeting info
|
||||
initial_message = f"""
|
||||
Dear {applicant.first_name} {applicant.last_name},
|
||||
|
||||
Your interview details are as follows:
|
||||
|
||||
Date: {interview.interview_date}
|
||||
Time: {interview.interview_time}
|
||||
Job: {job.title}
|
||||
|
||||
"""
|
||||
|
||||
if schedule.location_type == 'Remote':
|
||||
initial_message += "This is a remote schedule. You will receive the meeting link separately.\n\n"
|
||||
else:
|
||||
email_body += "This is an onsite schedule. Please arrive 10 minutes early.\n\n"
|
||||
|
||||
initial_message += """
|
||||
Best regards,
|
||||
HR Team
|
||||
"""
|
||||
|
||||
self.fields['message'].initial = initial_message
|
||||
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
# Generated by Django 5.2.7 on 2025-12-10 12:50
|
||||
|
||||
import secured_fields.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('recruitment', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='customuser',
|
||||
name='first_name',
|
||||
field=secured_fields.fields.EncryptedCharField(blank=True, max_length=150, verbose_name='first name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='customuser',
|
||||
name='phone',
|
||||
field=secured_fields.fields.EncryptedCharField(blank=True, null=True, verbose_name='Phone'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hiringagency',
|
||||
name='phone',
|
||||
field=secured_fields.fields.EncryptedCharField(blank=True, max_length=20, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='participants',
|
||||
name='phone',
|
||||
field=secured_fields.fields.EncryptedCharField(blank=True, max_length=12, null=True, verbose_name='Phone Number'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='person',
|
||||
name='first_name',
|
||||
field=secured_fields.fields.EncryptedCharField(max_length=255, verbose_name='First Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='person',
|
||||
name='phone',
|
||||
field=secured_fields.fields.EncryptedCharField(blank=True, null=True, verbose_name='Phone'),
|
||||
),
|
||||
]
|
||||
19
recruitment/migrations/0003_alter_person_national_id.py
Normal file
19
recruitment/migrations/0003_alter_person_national_id.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.2.7 on 2025-12-10 13:04
|
||||
|
||||
import secured_fields.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('recruitment', '0002_alter_customuser_first_name_alter_customuser_phone_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='person',
|
||||
name='national_id',
|
||||
field=secured_fields.fields.EncryptedCharField(help_text='Enter the national id or iqama number'),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,31 @@
|
||||
# Generated by Django 5.2.7 on 2025-12-10 13:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('recruitment', '0003_alter_person_national_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='interview',
|
||||
name='cancelled_at',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='interview',
|
||||
name='cancelled_reason',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='scheduledinterview',
|
||||
name='cancelled_at',
|
||||
field=models.DateTimeField(blank=True, null=True, verbose_name='Cancelled At'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='scheduledinterview',
|
||||
name='cancelled_reason',
|
||||
field=models.TextField(blank=True, null=True, verbose_name='Cancellation Reason'),
|
||||
),
|
||||
]
|
||||
@ -16,6 +16,7 @@ from django.db.models.fields.json import KeyTransform, KeyTextTransform
|
||||
from django_countries.fields import CountryField
|
||||
from django_ckeditor_5.fields import CKEditor5Field
|
||||
from django_extensions.db.fields import RandomCharField
|
||||
from secured_fields import EncryptedCharField
|
||||
|
||||
from typing import List, Dict, Any
|
||||
|
||||
@ -42,10 +43,12 @@ class CustomUser(AbstractUser):
|
||||
("candidate", _("Candidate")),
|
||||
]
|
||||
|
||||
first_name=EncryptedCharField(_("first name"), max_length=150, blank=True)
|
||||
|
||||
user_type = models.CharField(
|
||||
max_length=20, choices=USER_TYPES, default="staff", verbose_name=_("User Type")
|
||||
)
|
||||
phone = models.CharField(
|
||||
phone = EncryptedCharField(
|
||||
blank=True, null=True, verbose_name=_("Phone")
|
||||
)
|
||||
profile_image = models.ImageField(
|
||||
@ -514,7 +517,7 @@ class Person(Base):
|
||||
]
|
||||
|
||||
# Personal Information
|
||||
first_name = models.CharField(max_length=255, verbose_name=_("First Name"))
|
||||
first_name = EncryptedCharField(max_length=255, verbose_name=_("First Name"))
|
||||
last_name = models.CharField(max_length=255, verbose_name=_("Last Name"))
|
||||
middle_name = models.CharField(
|
||||
max_length=255, blank=True, null=True, verbose_name=_("Middle Name")
|
||||
@ -524,7 +527,7 @@ class Person(Base):
|
||||
db_index=True,
|
||||
verbose_name=_("Email"),
|
||||
)
|
||||
phone = models.CharField(
|
||||
phone = EncryptedCharField(
|
||||
blank=True, null=True, verbose_name=_("Phone")
|
||||
)
|
||||
date_of_birth = models.DateField(
|
||||
@ -540,7 +543,7 @@ class Person(Base):
|
||||
gpa = models.DecimalField(
|
||||
max_digits=3, decimal_places=2, verbose_name=_("GPA"),help_text=_("GPA must be between 0 and 4.")
|
||||
)
|
||||
national_id = models.CharField(
|
||||
national_id = EncryptedCharField(
|
||||
help_text=_("Enter the national id or iqama number")
|
||||
)
|
||||
nationality = CountryField(blank=True, null=True, verbose_name=_("Nationality"))
|
||||
@ -1124,9 +1127,7 @@ class Interview(Base):
|
||||
default=Status.WAITING,
|
||||
db_index=True
|
||||
)
|
||||
cancelled_at = models.DateTimeField(null=True, blank=True, verbose_name=_("Cancelled At"))
|
||||
cancelled_reason = models.TextField(blank=True, null=True, verbose_name=_("Cancellation Reason"))
|
||||
|
||||
|
||||
# Remote-specific (nullable)
|
||||
meeting_id = models.CharField(
|
||||
max_length=50, unique=True, null=True, blank=True, verbose_name=_("External Meeting ID")
|
||||
@ -1242,6 +1243,10 @@ class ScheduledInterview(Base):
|
||||
CANCELLED = "cancelled", _("Cancelled")
|
||||
COMPLETED = "completed", _("Completed")
|
||||
|
||||
cancelled_at = models.DateTimeField(null=True, blank=True, verbose_name=_("Cancelled At"))
|
||||
cancelled_reason = models.TextField(blank=True, null=True, verbose_name=_("Cancellation Reason"))
|
||||
|
||||
|
||||
application = models.ForeignKey(
|
||||
Application,
|
||||
on_delete=models.CASCADE,
|
||||
@ -1254,7 +1259,7 @@ class ScheduledInterview(Base):
|
||||
related_name="scheduled_interviews",
|
||||
db_index=True,
|
||||
)
|
||||
|
||||
|
||||
# Links to the specific, individual location/meeting details for THIS interview
|
||||
interview = models.OneToOneField(
|
||||
Interview,
|
||||
@ -1880,7 +1885,7 @@ class HiringAgency(Base):
|
||||
max_length=150, blank=True, verbose_name=_("Contact Person")
|
||||
)
|
||||
email = models.EmailField(unique=True)
|
||||
phone = models.CharField(max_length=20, blank=True,null=True)
|
||||
phone = EncryptedCharField(max_length=20, blank=True,null=True)
|
||||
website = models.URLField(blank=True)
|
||||
notes = models.TextField(blank=True, help_text=_("Internal notes about the agency"))
|
||||
country = CountryField(blank=True, null=True, blank_label=_("Select country"))
|
||||
@ -2278,7 +2283,7 @@ class Participants(Base):
|
||||
max_length=255, verbose_name=_("Participant Name"), null=True, blank=True
|
||||
)
|
||||
email =models.EmailField(verbose_name=_("Email"))
|
||||
phone = models.CharField(
|
||||
phone = EncryptedCharField(
|
||||
max_length=12, verbose_name=_("Phone Number"), null=True, blank=True
|
||||
)
|
||||
designation = models.CharField(
|
||||
|
||||
@ -84,6 +84,7 @@ urlpatterns = [
|
||||
path("interviews/<slug:slug>/", views.interview_detail, name="interview_detail"),
|
||||
path("interviews/<slug:slug>/update_interview_status", views.update_interview_status, name="update_interview_status"),
|
||||
path("interviews/<slug:slug>/cancel_interview_for_application", views.cancel_interview_for_application, name="cancel_interview_for_application"),
|
||||
path("interview/<slug:slug>/interview-email/",views.send_interview_email,name="send_interview_email"),
|
||||
|
||||
# Interview Creation
|
||||
path("interviews/create/<slug:application_slug>/", views.interview_create_type_selection, name="interview_create_type_selection"),
|
||||
|
||||
@ -63,6 +63,7 @@ from rest_framework import viewsets
|
||||
from easyaudit.models import CRUDEvent, LoginEvent, RequestEvent
|
||||
from django_q.tasks import async_task
|
||||
|
||||
|
||||
# Local Apps
|
||||
from .decorators import (
|
||||
agency_user_required,
|
||||
@ -88,7 +89,8 @@ from .forms import (
|
||||
OnsiteInterviewForm,
|
||||
BulkInterviewTemplateForm,
|
||||
SettingsForm,
|
||||
InterviewCancelForm
|
||||
InterviewCancelForm,
|
||||
InterviewEmailForm
|
||||
)
|
||||
from .utils import generate_random_password
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
@ -182,7 +184,7 @@ class PersonListView(StaffRequiredMixin, ListView,LoginRequiredMixin):
|
||||
search_query=self.request.GET.get('search','')
|
||||
if search_query:
|
||||
queryset=queryset.filter(
|
||||
Q(first_name__icontains=search_query) |
|
||||
Q(first_name=search_query) |
|
||||
Q(last_name__icontains=search_query) |
|
||||
Q(email__icontains=search_query)
|
||||
)
|
||||
@ -1834,7 +1836,7 @@ def applications_document_review_view(request, slug):
|
||||
search_query = request.GET.get('q', '')
|
||||
if search_query:
|
||||
applications = applications.filter(
|
||||
Q(person__first_name__icontains=search_query) |
|
||||
Q(person__first_name=search_query) |
|
||||
Q(person__last_name__icontains=search_query) |
|
||||
Q(person__email__icontains=search_query)
|
||||
)
|
||||
@ -2812,10 +2814,10 @@ def agency_portal_persons_list(request):
|
||||
search_query = request.GET.get("q", "")
|
||||
if search_query:
|
||||
persons = persons.filter(
|
||||
Q(first_name__icontains=search_query)
|
||||
Q(first_name=search_query)
|
||||
| Q(last_name__icontains=search_query)
|
||||
| Q(email__icontains=search_query)
|
||||
| Q(phone__icontains=search_query)
|
||||
| Q(phone=search_query)
|
||||
| Q(job__title__icontains=search_query)
|
||||
)
|
||||
|
||||
@ -3806,18 +3808,13 @@ def cancel_interview_for_application(request, slug):
|
||||
Handles POST request to cancel an interview, setting the status
|
||||
and saving the form data (likely a reason for cancellation).
|
||||
"""
|
||||
interview = get_object_or_404(Interview, slug=slug)
|
||||
scheduled_interview = get_object_or_404(ScheduledInterview, interview=interview)
|
||||
form = InterviewCancelForm(request.POST, instance=interview)
|
||||
|
||||
|
||||
scheduled_interview = get_object_or_404(ScheduledInterview)
|
||||
form = InterviewCancelForm(request.POST, instance=scheduled_interview)
|
||||
|
||||
if form.is_valid():
|
||||
|
||||
interview.status = interview.Status.CANCELLED
|
||||
scheduled_interview.status = scheduled_interview.InterviewStatus.CANCELLED
|
||||
scheduled_interview.save(update_fields=['status'])
|
||||
interview.save(update_fields=['status']) # Saves the new status
|
||||
scheduled_interview.save(update_fields=['status']) # Saves the new status
|
||||
|
||||
form.save() # Saves form data
|
||||
|
||||
@ -4360,7 +4357,7 @@ def interview_list(request):
|
||||
interviews = interviews.filter(job__slug=job_filter)
|
||||
if search_query:
|
||||
interviews = interviews.filter(
|
||||
Q(application__person__first_name__icontains=search_query) |
|
||||
Q(application__person__first_name=search_query) |
|
||||
Q(application__person__last_name__icontains=search_query) |
|
||||
Q(application__person__email=search_query)|
|
||||
Q(job__title__icontains=search_query)
|
||||
@ -4391,8 +4388,8 @@ def interview_detail(request, slug):
|
||||
interview = schedule.interview
|
||||
|
||||
reschedule_form = ScheduledInterviewForm()
|
||||
reschedule_form.initial['topic'] = interview.interview.topic
|
||||
meeting=interview.interview
|
||||
reschedule_form.initial['topic'] = interview.topic
|
||||
meeting=interview
|
||||
context = {
|
||||
'schedule': schedule,
|
||||
'interview': interview,
|
||||
@ -4582,7 +4579,7 @@ def job_applicants_view(request, slug):
|
||||
# Apply filters
|
||||
if search_query:
|
||||
applications = applications.filter(
|
||||
Q(person__first_name__icontains=search_query) |
|
||||
Q(person__first_name=search_query) |
|
||||
Q(person__last_name__icontains=search_query) |
|
||||
Q(person__email__icontains=search_query) |
|
||||
Q(email__icontains=search_query)
|
||||
@ -4909,10 +4906,10 @@ class JobApplicationListView(LoginRequiredMixin, StaffRequiredMixin, ListView):
|
||||
search_query = self.request.GET.get('search', '')
|
||||
if search_query:
|
||||
queryset = queryset.filter(
|
||||
Q(first_name__icontains=search_query) |
|
||||
Q(first_name=search_query) |
|
||||
Q(last_name__icontains=search_query) |
|
||||
Q(email__icontains=search_query) |
|
||||
Q(phone__icontains=search_query) |
|
||||
Q(phone=search_query) |
|
||||
Q(stage__icontains=search_query)
|
||||
)
|
||||
|
||||
@ -4944,10 +4941,10 @@ class ApplicationListView(LoginRequiredMixin, StaffRequiredMixin, ListView):
|
||||
stage = self.request.GET.get('stage', '')
|
||||
if search_query:
|
||||
queryset = queryset.filter(
|
||||
Q(person__first_name__icontains=search_query) |
|
||||
Q(person__first_name=search_query) |
|
||||
Q(person__last_name__icontains=search_query) |
|
||||
Q(person__email__icontains=search_query) |
|
||||
Q(person__phone__icontains=search_query)
|
||||
Q(person__phone=search_query)
|
||||
)
|
||||
if job:
|
||||
queryset = queryset.filter(job__slug=job)
|
||||
@ -5331,10 +5328,10 @@ def applications_offer_view(request, slug):
|
||||
search_query = request.GET.get('search', '')
|
||||
if search_query:
|
||||
applications = applications.filter(
|
||||
Q(first_name__icontains=search_query) |
|
||||
Q(first_name=search_query) |
|
||||
Q(last_name__icontains=search_query) |
|
||||
Q(email__icontains=search_query) |
|
||||
Q(phone__icontains=search_query)
|
||||
Q(phone=search_query)
|
||||
)
|
||||
|
||||
applications = applications.order_by('-created_at')
|
||||
@ -5361,10 +5358,10 @@ def applications_hired_view(request, slug):
|
||||
search_query = request.GET.get('search', '')
|
||||
if search_query:
|
||||
applications = applications.filter(
|
||||
Q(first_name__icontains=search_query) |
|
||||
Q(first_name=search_query) |
|
||||
Q(last_name__icontains=search_query) |
|
||||
Q(email__icontains=search_query) |
|
||||
Q(phone__icontains=search_query)
|
||||
Q(phone=search_query)
|
||||
)
|
||||
|
||||
applications = applications.order_by('-created_at')
|
||||
@ -5469,10 +5466,10 @@ def export_applications_csv(request, job_slug, stage):
|
||||
search_query = request.GET.get('search', '')
|
||||
if search_query:
|
||||
applications = applications.filter(
|
||||
Q(first_name__icontains=search_query) |
|
||||
Q(first_name=search_query) |
|
||||
Q(last_name__icontains=search_query) |
|
||||
Q(email__icontains=search_query) |
|
||||
Q(phone__icontains=search_query)
|
||||
Q(phone=search_query)
|
||||
)
|
||||
|
||||
applications = applications.order_by('-created_at')
|
||||
@ -5739,4 +5736,22 @@ def sync_history(request, job_slug=None):
|
||||
'job': job if job_slug else None,
|
||||
}
|
||||
|
||||
return render(request, 'recruitment/sync_history.html', context)
|
||||
return render(request, 'recruitment/sync_history.html', context)
|
||||
|
||||
|
||||
def send_interview_email(request,slug):
|
||||
schedule=get_object_or_404(ScheduledInterview,slug=slug)
|
||||
application=schedule.application.first()
|
||||
job=application.job
|
||||
form=InterviewEmailForm(job,application,schedule)
|
||||
if request.method=='POST':
|
||||
recipient=form.cleaned_data.get('to').strip()
|
||||
body_message=form.cleaned_data.get('message')
|
||||
sender=request.user
|
||||
job=job
|
||||
pass
|
||||
|
||||
|
||||
|
||||
# async_task('recruitment.tasks._task_send_individual_email', 'value1', 'value2')
|
||||
# def _task_send_individual_email(subject, body_message, recipient, attachments,sender,job):
|
||||
@ -223,7 +223,7 @@
|
||||
<a href="{% url 'job_detail' schedule.job.slug %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-briefcase me-1"></i> {% trans "View Job" %}
|
||||
</a>
|
||||
{% if interview.status != 'cancelled' %}
|
||||
{% if schedule.status != 'cancelled' %}
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#statusModal">
|
||||
@ -292,21 +292,15 @@
|
||||
<i class="fas fa-calendar-check me-2"></i> {% trans "Interview Details" %}
|
||||
</h5>
|
||||
<div class="d-flex gap-2">
|
||||
<span class="badge interview-type-badge
|
||||
{% if interview.location_type == 'Remote' %}bg-remote
|
||||
{% else %}bg-onsite
|
||||
{% endif %}">
|
||||
{% if interview.location_type == 'Remote' %}
|
||||
<i class="fas fa-video me-1"></i> {% trans "Remote" %}
|
||||
{% else %}
|
||||
<i class="fas fa-building me-1"></i> {% trans "Onsite" %}
|
||||
{% endif %}
|
||||
|
||||
<span class="bg-primary-theme badge status-badge text-white">
|
||||
{{interview.location_type}}
|
||||
</span>
|
||||
<span class="badge status-badge
|
||||
{% if schedule.status == 'SCHEDULED' %}bg-scheduled
|
||||
{% elif schedule.status == 'CONFIRMED' %}bg-confirmed
|
||||
{% elif schedule.status == 'CANCELLED' %}bg-cancelled
|
||||
{% elif schedule.status == 'COMPLETED' %}bg-completed
|
||||
{% if schedule.status == 'scheduled' %}bg-scheduled
|
||||
{% elif schedule.status == 'confirmed' %}bg-confirmed
|
||||
{% elif schedule.status == 'cancelled' %}bg-cancelled
|
||||
{% elif schedule.status == 'completed' %}bg-completed
|
||||
{% endif %}">
|
||||
{{ schedule.status }}
|
||||
</span>
|
||||
@ -400,7 +394,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if interview.status == 'CONFIRMED' %}
|
||||
|
||||
{% if schedule.status == 'confirmed' %}
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-content">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
@ -408,12 +403,12 @@
|
||||
<h6 class="mb-1">{% trans "Interview Confirmed" %}</h6>
|
||||
<p class="mb-0 text-muted">{% trans "Candidate has confirmed attendance" %}</p>
|
||||
</div>
|
||||
<small class="text-muted">{% trans "Recently" %}</small>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if interview.status == 'COMPLETED' %}
|
||||
{% if schedule.status == 'completed' %}
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-content">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
@ -421,20 +416,20 @@
|
||||
<h6 class="mb-1">{% trans "Interview Completed" %}</h6>
|
||||
<p class="mb-0 text-muted">{% trans "Interview has been completed" %}</p>
|
||||
</div>
|
||||
<small class="text-muted">{% trans "Recently" %}</small>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if interview.status == 'CANCELLED' %}
|
||||
{% if schedule.status == 'cancelled' %}
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-content">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<h6 class="mb-1">{% trans "Interview Cancelled" %}</h6>
|
||||
<p class="mb-0 text-muted">{% trans "Interview was cancelled on: " %}{{interview.interview.cancelled_at|date:"F j, Y"}}</p>
|
||||
<p class="mb-0 text-muted">{% trans "Interview was cancelled on: " %}{{ schedule.cancelled_at|date:"d-m-Y" }} {{ schedule.cancelled_at|date:"h:i A" }}</p>
|
||||
</div>
|
||||
<small class="text-muted">{% trans "Recently" %}</small>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -495,7 +490,7 @@
|
||||
<i class="fas fa-user-plus me-1"></i> {% trans "Add Participants" %}
|
||||
</button>
|
||||
</div> {% endcomment %}
|
||||
</div> {% endcomment %}
|
||||
|
||||
|
||||
<div class="kaauh-card shadow-sm p-4">
|
||||
<h5 class="mb-3" style="color: var(--kaauh-teal-dark); font-weight: 600;">
|
||||
@ -503,7 +498,7 @@
|
||||
</h5>
|
||||
|
||||
<div class="action-buttons">
|
||||
{% if schedule.status != 'CANCELLED' and schedule.status != 'COMPLETED' %}
|
||||
{% if schedule.status != 'cancelled' and schedule.status != 'completed' %}
|
||||
<button type="button" class="btn btn-main-action btn-sm"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#rescheduleModal">
|
||||
@ -517,7 +512,9 @@
|
||||
</button>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% if schedule.status == 'cancelled' %}
|
||||
<p class="text-danger">{% trans "This interview has been cancelled" %}</p>
|
||||
{% endif %}
|
||||
<hr class="w-100 mt-2 mb-2">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm w-100"
|
||||
data-bs-toggle="modal"
|
||||
@ -525,7 +522,7 @@
|
||||
<i class="fas fa-envelope me-1"></i> {% trans "Send Email" %}
|
||||
</button>
|
||||
|
||||
{% if schedule.status == 'COMPLETED' %}
|
||||
{% if schedule.status == 'completed' %}
|
||||
<button type="button" class="btn btn-outline-success btn-sm"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#resultModal">
|
||||
@ -611,23 +608,23 @@
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email_message" class="form-label">{% trans "Message" %}</label>
|
||||
<textarea class="form-control" id="email_message" name="message" rows="6">
|
||||
{% trans "Dear" %} {{ schedule.application.name }},
|
||||
<textarea class="form-control" id="email_message" name="message" rows="6">
|
||||
{% trans "Dear" %} {{ schedule.application.name }},
|
||||
|
||||
{% trans "Your interview details are as follows:" %}
|
||||
{% trans "Your interview details are as follows:" %}
|
||||
|
||||
{% trans "Date:" %} {{ schedule.interview_date|date:"d-m-Y" }}
|
||||
{% trans "Time:" %} {{ schedule.interview_time|date:"h:i A" }}
|
||||
{% trans "Job:" %} {{ schedule.job.title }}
|
||||
{% trans "Date:" %} {{ schedule.interview_date|date:"d-m-Y" }}
|
||||
{% trans "Time:" %} {{ schedule.interview_time|date:"h:i A" }}
|
||||
{% trans "Job:" %} {{ schedule.job.title }}
|
||||
|
||||
{% if interview.location_type == 'Remote' %}
|
||||
{% trans "This is a remote schedule. You will receive the meeting link separately." %}
|
||||
{% else %}
|
||||
{% trans "This is an onsite schedule. Please arrive 10 minutes early." %}
|
||||
{% endif %}
|
||||
{% if interview.location_type == 'Remote' %}
|
||||
{% trans "This is a remote schedule. You will receive the meeting link separately." %}
|
||||
{% else %}
|
||||
{% trans "This is an onsite schedule. Please arrive 10 minutes early." %}
|
||||
{% endif %}
|
||||
|
||||
{% trans "Best regards," %}
|
||||
{% trans "HR Team" %}
|
||||
{% trans "Best regards," %}
|
||||
{% trans "HR Team" %}
|
||||
</textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-main-action btn-sm">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user