time_to_hire

This commit is contained in:
Faheed 2025-11-02 20:17:21 +03:00
parent 2428034684
commit 58d72844a6
19 changed files with 140 additions and 67 deletions

View File

@ -162,10 +162,17 @@ def send_interview_invitation_email(candidate, job, meeting_details=None, recipi
try:
# Prepare recipient list
recipients = []
if candidate.email:
if candidate.hiring_source == "Agency":
try:
recipients.append(candidate.hiring_agency.email)
except :
pass
else:
recipients.append(candidate.email)
if recipient_list:
recipients.extend(recipient_list)
if not recipients:
return {'success': False, 'error': 'No recipient email addresses provided'}

View File

@ -682,6 +682,32 @@ class Candidate(Base):
@property
def scoring_timeout(self):
return timezone.now() <= (self.created_at + timezone.timedelta(minutes=5))
@property
def get_interview_date(self):
if hasattr(self, 'scheduled_interview') and self.scheduled_interview:
return self.scheduled_interviews.first().interview_date
return None
@property
def get_interview_time(self):
if hasattr(self, 'scheduled_interview') and self.scheduled_interview:
return self.scheduled_interviews.first().interview_time
return None
@property
def time_to_hire_days(self):
if self.hired_date:
time_to_hire_in_days=timedelta(self.hired_date-self.created_at.date())
print(self.created_at.date)
return time_to_hire_in_days
return 0
class TrainingMaterial(Base):

View File

@ -62,6 +62,7 @@ urlpatterns = [
# Form Preview URLs
# path('forms/', views.form_list, name='form_list'),
path('forms/builder/', views.form_builder, name='form_builder'),
path('forms/builder/<slug:template_slug>/', views.form_builder, name='form_builder'),
path('forms/', views.form_templates_list, name='form_templates_list'),

View File

@ -449,7 +449,7 @@ def schedule_interviews(schedule):
interview_date=slot['date'],
interview_time=slot['time']
)
candidate.interview_date=interview_datetime
# Send email to candidate
send_interview_email(scheduled_interview)

View File

@ -858,13 +858,13 @@ def application_submit_form(request, template_slug):
if is_limit_exceeded:
messages.error(
request,
'Application limit reached: This job is no longer accepting new applications. Please explore other available positions.'
_('Application limit reached: This job is no longer accepting new applications.')
)
return redirect('application_detail',slug=job.slug)
if job.is_expired:
messages.error(
request,
'Application deadline passed: This job is no longer accepting new applications. Please explore other available positions.'
_('Application deadline passed: This job is no longer accepting new applications.')
)
return redirect('application_detail',slug=job.slug)
@ -1422,10 +1422,26 @@ def candidate_set_exam_date(request, slug):
def candidate_update_status(request, slug):
job = get_object_or_404(JobPosting, slug=slug)
mark_as = request.POST.get('mark_as')
if mark_as != '----------':
candidate_ids = request.POST.getlist("candidate_ids")
print(candidate_ids)
if c := Candidate.objects.filter(pk__in = candidate_ids):
c.update(stage=mark_as,exam_date=timezone.now(),applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant")
if mark_as=='Exam':
c.update(exam_date=timezone.now(),interview_date=None,offer_date=None,hired_date=None,stage=mark_as,applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant")
elif mark_as=='Interview':
# interview_date update when scheduling the interview
c.update(stage=mark_as,offer_date=None,hired_date=None,applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant")
elif mark_as=='Offer':
c.update(stage=mark_as,offer_date=timezone.now(),hired_date=None,applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant")
elif mark_as=='Hired':
print('hired')
c.update(stage=mark_as,hired_date=timezone.now(),applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant")
else:
c.update(stage=mark_as,exam_date=None,interview_date=None,offer_date=None,hired_date=None,applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant")
messages.success(request, f"Candidates Updated")
response = HttpResponse(redirect("candidate_screening_view", slug=job.slug))
@ -2971,11 +2987,12 @@ def agency_assignment_create(request,slug=None):
messages.success(request, f'Assignment created for {assignment.agency.name} - {assignment.job.title}!')
return redirect('agency_assignment_detail', slug=assignment.slug)
else:
messages.error(request, 'Please correct the errors below.')
messages.error(request, f'Please correct the errors below.{form.errors.as_text()}')
print(form.errors.as_json())
else:
form = AgencyJobAssignmentForm()
try:
from django.forms import HiddenInput
# from django.forms import HiddenInput
form.initial['agency'] = agency
# form.fields['agency'].widget = HiddenInput()
except HiringAgency.DoesNotExist:
@ -3084,6 +3101,7 @@ def agency_access_link_detail(request, slug):
AgencyAccessLink.objects.select_related('assignment__agency', 'assignment__job'),
slug=slug
)
context = {
'access_link': access_link,

View File

@ -257,6 +257,7 @@ def candidate_detail(request, slug):
if request.user.is_staff:
stage_form = forms.CandidateStageForm()
# parsed = JSON(json.dumps(parsed), indent=2, highlight=True, skip_keys=False, ensure_ascii=False, check_circular=True, allow_nan=True, default=None, sort_keys=False)
# parsed = json_to_markdown_table([parsed])
return render(request, 'recruitment/candidate_detail.html', {
@ -458,19 +459,25 @@ def dashboard_view(request):
# B. Efficiency & Conversion Metrics (Scoped)
hired_candidates = candidate_queryset.filter(
Q(offer_status="Accepted") | Q(stage='HIRED'),
join_date__isnull=False
stage='Hired'
)
print(hired_candidates)
lst=[c.time_to_hire_days for c in hired_candidates]
print(lst)
time_to_hire_query = hired_candidates.annotate(
time_diff=ExpressionWrapper(
F('join_date') - F('created_at__date'),
output_field=fields.DurationField()
)
).aggregate(avg_time_to_hire=Avg('time_diff'))
print(time_to_hire_query)
avg_time_to_hire_days = (
time_to_hire_query.get('avg_time_to_hire').days
if time_to_hire_query.get('avg_time_to_hire') else 0
)
print(avg_time_to_hire_days)
applied_count = candidate_queryset.filter(stage='Applied').count()
advanced_count = candidate_queryset.filter(stage__in=['Exam', 'Interview', 'Offer']).count()

View File

@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% translate "Application Submitted - Thank You" %}</title>
<title>{% trans "Application Submitted - Thank You" %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@ -166,25 +166,25 @@
</div>
<h1 class="text-success-header">{% translate "Thank You!" %}</h1>
<h2 class="h4" style="color: #333;">{% translate "Your application has been submitted successfully" %}</h2>
<h2 class="h4" style="color: #333;">{% trans "Your application has been submitted successfully" %}</h2>
{% comment %} {# JOB INFO BLOCK #}
<div class="job-info-block">
<p class="mb-2"><strong>{% translate "Position" %}:</strong> <span class="fw-bold">{{ job.title }}</span></p>
<p class="mb-2"><strong>{% translate "Job ID" %}:</strong> {{ job.internal_job_id }}</p>
<p class="mb-2"><strong>{% translate "Department" %}:</strong> {{ job.department|default:"Not specified" }}</p>
<p class="mb-2"><strong>{% trans "Position" %}:</strong> <span class="fw-bold">{{ job.title }}</span></p>
<p class="mb-2"><strong>{% trans "Job ID" %}:</strong> {{ job.internal_job_id }}</p>
<p class="mb-2"><strong>{% trans "Department" %}:</strong> {{ job.department|default:"Not specified" }}</p>
{% if job.application_deadline %}
<p><strong>{% translate "Application Deadline" %}:</strong> {{ job.application_deadline|date:"F j, Y" }}</p>
<p><strong>{% trans "Application Deadline" %}:</strong> {{ job.application_deadline|date:"F j, Y" }}</p>
{% endif %}
</div> {% endcomment %}
<p style="font-size: 1rem; line-height: 1.6; color: #555;">
{% translate "We appreciate your interest in joining our team. Our hiring team will review your application and contact you if there's a potential match for this position." %}
{% trans "We appreciate your interest in joining our team. Our hiring team will review your application and contact you if there's a potential match for this position." %}
</p>
<div style="margin-top: 30px;">
<a href="https://kaauh.edu.sa/career" class="btn btn-main-action btn-lg">
<i class="fas fa-arrow-left me-2"></i> {% translate "Return to Job Listings" %}
<a href="{% url 'kaauh_career' %}" class="btn btn-main-action btn-lg">
<i class="fas fa-arrow-left me-2"></i> {% trans "Return to Job Listings" %}
</a>
{# You can add a link to view the saved application here if applicable #}
{% comment %} <a href="{% url 'jobs:job_detail' job.internal_job_id %}" class="btn btn-secondary">View Job Details</a> {% endcomment %}

View File

@ -251,25 +251,25 @@
</div>
{# Description Blocks (Main Content) #}
{% if job.description %}
{% if job.has_description_content %}
<div class="mb-4">
<h5>{% trans "Job Description" %}</h5>
<div class="text-secondary">{{ job.description|safe }}</div>
</div>
{% endif %}
{% if job.qualifications %}
{% if job.has_qualifications_content %}
<div class="mb-4">
<h5>{% trans "Required Qualifications" %}</h5>
<div class="text-secondary">{{ job.qualifications|safe }}</div>
</div>
{% endif %}
{% if job.benefits %}
{% if job.has_benefits_content %}
<div class="mb-4">
<h5>{% trans "Benefits" %}</h5>
<div class="text-secondary">{{ job.benefits|safe}}</div>
</div>
{% endif %}
{% if job.application_instructions %}
{% if job.has_application_instructions_content %}
<div class="mb-4">
<h5>{% trans "Application Instructions" %}</h5>
<div class="text-secondary">{{ job.application_instructions|safe }}</div>
@ -347,11 +347,14 @@
<i class="fas fa-plus-circle me-1"></i> {% trans "Create New Form Template" %}
</a>
{% else %}
{% if job.form_template.is_active %}
<a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-outline-secondary w-100">
<i class="fas fa-list-alt me-1"></i> {% trans "View Form Template" %}
</a>
{% else %}
{% if job.form_template.is_active %}
<a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-outline-secondary w-100">
<i class="fas fa-list-alt me-1"></i> {% trans "View Form Template" %}
</a>
<a href="{% url 'form_builder' job.form_template.slug %}" class="btn btn-outline-secondary w-100">
<i class="fas fa-list-alt me-1"></i> {% trans "Manage Form Template" %}
</a>
{% else %}
<p>{% trans "This job status is not active, the form will appear once the job is made active"%}</p>
{% endif %}
@ -446,7 +449,7 @@
<div class="row g-3 stats-grid">
{# 1. Job Avg. Score #}
<div class="col-6">
<div class="col-4">
<div class="card text-center h-100 kpi-card">
<div class="card-body p-2">
<i class="fas fa-star text-primary mb-1 d-block" style="font-size: 1.2rem;"></i>
@ -457,7 +460,7 @@
</div>
{# 2. High Potential Count #}
<div class="col-6">
<div class="col-4">
<div class="card text-center h-100">
<div class="card-body p-2">
<i class="fas fa-trophy text-success mb-1 d-block" style="font-size: 1.2rem;"></i>
@ -468,7 +471,7 @@
</div>
{# 3. Avg. Time to Interview #}
<div class="col-6">
{% comment %} <div class="col-6">
<div class="card text-center h-100">
<div class="card-body p-2">
<i class="fas fa-calendar-alt text-info mb-1 d-block" style="font-size: 1.2rem;"></i>
@ -487,9 +490,9 @@
<small class="text-muted d-block">{% trans "Avg. Exam Review" %}</small>
</div>
</div>
</div>
</div> {% endcomment %}
<!--Vacancy fill rate-->
<div class="col-6">
<div class="col-4">
<div class="card text-center h-100">
<div class="card-body p-2">
<i class="fas fa-trophy text-secondary mb-1 d-block" style="font-size: 1.2rem;"></i>

View File

@ -325,7 +325,7 @@
</td>
{# CANDIDATE MANAGEMENT DATA - URLS NEUTRALIZED #}
<td class="candidate-data-cell text-primary-theme"><a href="#" class="text-primary-theme">{% if job.all_candidates.count %}{{ job.all_candidates.count }}{% else %}-{% endif %}</a></td>
<td class="candidate-data-cell text-primary-theme"><a href="{% url 'candidate_screening_view' job.slug %}" class="text-primary-theme">{% if job.all_candidates.count %}{{ job.all_candidates.count }}{% else %}-{% endif %}</a></td>
<td class="candidate-data-cell text-info"><a href="{% url 'candidate_screening_view' job.slug %}" class="text-info">{% if job.screening_candidates.count %}{{ job.screening_candidates.count }}{% else %}-{% endif %}</a></td>
<td class="candidate-data-cell text-success"><a href="{% url 'candidate_exam_view' job.slug %}" class="text-success">{% if job.exam_candidates.count %}{{ job.exam_candidates.count }}{% else %}-{% endif %}</a></td>
<td class="candidate-data-cell text-success"><a href="{% url 'candidate_interview_view' job.slug %}" class="text-success">{% if job.interview_candidates.count %}{{ job.interview_candidates.count }}{% else %}-{% endif %}</a></td>

View File

@ -5,7 +5,7 @@
{% block content %}
<div class="container-fluid py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="d-flex justify-content-between align-items-center mb-4 px-3 py-3">
<div>
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
<i class="fas fa-link me-2"></i>
@ -20,7 +20,7 @@
<div class="row">
<div class="col-md-8">
<div class="kaauh-card shadow-sm mb-4">
<div class="kaauh-card shadow-sm mb-4 px-3 py-3">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<h5 class="card-title mb-0">
@ -77,7 +77,7 @@
</div>
<div class="kaauh-card shadow-sm">
<div class="card-body">
<div class="card-body px-3 py-3">
<h5 class="card-title mb-3">
<i class="fas fa-key me-2 text-warning"></i>
{% trans "Access Credentials" %}
@ -125,7 +125,7 @@
</div>
<div class="col-md-4">
<div class="kaauh-card shadow-sm mb-4">
<div class="kaauh-card shadow-sm mb-4 px-3 py-3">
<div class="card-body">
<h5 class="card-title mb-3">
<i class="fas fa-chart-line me-2 text-info"></i>
@ -161,7 +161,7 @@
</div>
</div>
<div class="kaauh-card shadow-sm">
<div class="kaauh-card shadow-sm px-3 py-3">
<div class="card-body">
<h5 class="card-title mb-3">
<i class="fas fa-cog me-2 text-secondary"></i>

View File

@ -473,7 +473,7 @@
</div>
<!-- Quick Actions -->
<div class="card kaauh-card mb-4">
{% comment %} <div class="card kaauh-card mb-4">
<div class="card-header bg-white border-bottom">
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
<i class="fas fa-bolt me-2"></i>
@ -503,7 +503,7 @@
{% endif %}
</div>
</div>
</div>
</div> {% endcomment %}
<!-- Agency Information -->
<div class="card kaauh-card">

View File

@ -417,15 +417,16 @@
</div>
{% endif %}
{% if candidate.interview_date %}
{% if candidate.get_interview_date %}
<div class="timeline-item">
<div class="timeline-icon timeline-bg-applied"><i class="fas fas fa-comments"></i></div>
<div class="timeline-content">
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Interview" %}</p>
<small class="text-muted">
<i class="far fa-calendar-alt me-1"></i> {{ candidate.interview_date|date:"M d, Y" }}
<i class="far fa-calendar-alt me-1"></i> {{ candidate.get_interview_date}}
<span class="ms-2">|</span>
<i class="far fa-clock ms-2 me-1"></i> {{ candidate.interview_date|date:"h:i A" }}
<i class="far fa-clock ms-2 me-1"></i> {{ candidate.get_interview_time}}
</small>
</div>
@ -439,8 +440,21 @@
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Offer" %}</p>
<small class="text-muted">
<i class="far fa-calendar-alt me-1"></i> {{ candidate.offer_date|date:"M d, Y" }}
<span class="ms-2">|</span>
<i class="far fa-clock ms-2 me-1"></i> {{ candidate.offer_date|date:"h:i A" }}
</small>
</div>
</div>
{% endif %}
{% if candidate.hired_date %}
<div class="timeline-item">
<div class="timeline-icon timeline-bg-applied"><i class="fas fas fa-handshake"></i></div>
<div class="timeline-content">
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Offer" %}</p>
<small class="text-muted">
<i class="far fa-calendar-alt me-1"></i> {{ candidate.hired_date|date:"M d, Y" }}
</small>
</div>

View File

@ -265,7 +265,7 @@
<th style="width: 10%"><i class="fas fa-calendar me-1"></i> {% trans "Meeting Date" %}</th>
<th style="width: 7%"><i class="fas fa-video me-1"></i> {% trans "Link" %}</th>
<th style="width: 8%"><i class="fas fa-check-circle me-1"></i> {% trans "Meeting Status" %}</th>
<th style="width: 5%"><i class="fas fa-check-circle me-1"></i> {% trans "Interview Result" %}</th>
<th style="width: 5%"><i class="fas fa-check-circle me-1"></i> {% trans "Interview Result"%}</th>
<th style="width: 10%"><i class="fas fa-cog me-1"></i> {% trans "Actions" %}</th>
</tr>
</thead>
@ -276,11 +276,20 @@
<div class="form-check">
<input name="candidate_ids" value="{{ candidate.id }}" type="checkbox" class="form-check-input rowCheckbox" id="candidate-{{ candidate.id }}">
</div>
</td>
<td>
<div class="candidate-name">
<button type="button" class="btn btn-outline-secondary btn-sm"
data-bs-toggle="modal"
data-bs-target="#candidateviewModal"
hx-get="{% url 'candidate_criteria_view_htmx' candidate.pk %}"
hx-target="#candidateviewModalBody"
title="View Profile">
{{ candidate.name }}<i class="fas fa-eye ms-1"></i>
</button>
{% comment %} <div class="candidate-name">
{{ candidate.name }}
</div>
</div> {% endcomment %}
</td>
<td>
<div class="candidate-details">
@ -365,14 +374,14 @@
{% endif %}
</td>
<td>
<button type="button" class="btn btn-outline-secondary btn-sm"
{% comment %} <button type="button" class="btn btn-outline-secondary btn-sm"
data-bs-toggle="modal"
data-bs-target="#candidateviewModal"
hx-get="{% url 'candidate_criteria_view_htmx' candidate.pk %}"
hx-target="#candidateviewModalBody"
title="View Profile">
<i class="fas fa-eye"></i>
</button>
</button> {% endcomment %}
<button type="button" class="btn btn-outline-info btn-sm"
data-bs-toggle="modal"
data-bs-target="#emailModal"
@ -457,17 +466,14 @@
<form method="post" action="{% url 'candidate_interview_view' job.slug %}">
{% csrf_token %}
<<<<<<< HEAD
<div class="modal-body table-responsive">
=======
<div class="modal-body">
>>>>>>> c6fcb276135dc7e87bb0d065a93ff89091ff0207
{{ job.internal_job_id }} {{ job.title}}
<hr>
<<<<<<< HEAD
<table class="table tab table-bordered mt-3">
@ -490,18 +496,7 @@
</table>
=======
<h3>👥 {% trans "Participants" %}</h3>
{{ form.participants.errors }}
{{ form.participants }}
<hr>
<h3>🧑‍💼 {% trans "Users" %}</h3>
{{ form.users.errors }}
{{ form.users }}
>>>>>>> c6fcb276135dc7e87bb0d065a93ff89091ff0207
</div>
<div class="modal-footer">

View File

@ -146,7 +146,7 @@
<div class="row px-lg-4">
<div class="col-12">
<h1 class="h3 fw-bold dashboard-header">
<i class="fas fa-cogs me-3 text-accent"></i>{% trans "Admin Settings Dashboard" %}
<i class="fas fa-cogs me-3 text-accent"></i>{% trans "Staff Management Dashboard" %}
</h1>
</div>
</div>
@ -165,6 +165,7 @@
</div>
<div class="col-12 table-card">
<div class="table-responsive">
<table class="table table-striped table-hover align-middle mb-0">
@ -180,6 +181,7 @@
</tr>
</thead>
<tbody>
{% for user in staffs %}
<tr>
<td>{{ user.pk }}</td>