add scheduler
This commit is contained in:
parent
48f61f173f
commit
d26c18fefd
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -5,7 +5,7 @@ from django.utils import timezone
|
|||||||
from .models import (
|
from .models import (
|
||||||
JobPosting, Candidate, TrainingMaterial, ZoomMeeting,
|
JobPosting, Candidate, TrainingMaterial, ZoomMeeting,
|
||||||
FormTemplate, FormStage, FormField, FormSubmission, FieldResponse,
|
FormTemplate, FormStage, FormField, FormSubmission, FieldResponse,
|
||||||
SharedFormTemplate, Source, HiringAgency, IntegrationLog
|
SharedFormTemplate, Source, HiringAgency, IntegrationLog,InterviewSchedule
|
||||||
)
|
)
|
||||||
|
|
||||||
class FormFieldInline(admin.TabularInline):
|
class FormFieldInline(admin.TabularInline):
|
||||||
@ -260,5 +260,5 @@ class FormSubmissionAdmin(admin.ModelAdmin):
|
|||||||
admin.site.register(FormStage)
|
admin.site.register(FormStage)
|
||||||
admin.site.register(FormField)
|
admin.site.register(FormField)
|
||||||
admin.site.register(FieldResponse)
|
admin.site.register(FieldResponse)
|
||||||
admin.site.register(SharedFormTemplate)
|
admin.site.register(InterviewSchedule)
|
||||||
# admin.site.register(HiringAgency)
|
# admin.site.register(HiringAgency)
|
||||||
|
|||||||
@ -413,7 +413,7 @@ class FormTemplateForm(forms.ModelForm):
|
|||||||
class InterviewScheduleForm(forms.ModelForm):
|
class InterviewScheduleForm(forms.ModelForm):
|
||||||
candidates = forms.ModelMultipleChoiceField(
|
candidates = forms.ModelMultipleChoiceField(
|
||||||
queryset=Candidate.objects.none(),
|
queryset=Candidate.objects.none(),
|
||||||
widget=forms.CheckboxSelectMultiple,
|
widget=forms.CheckboxSelectMultiple(attrs={'class': 'form-check'}),
|
||||||
required=True
|
required=True
|
||||||
)
|
)
|
||||||
working_days = forms.MultipleChoiceField(
|
working_days = forms.MultipleChoiceField(
|
||||||
@ -426,9 +426,9 @@ class InterviewScheduleForm(forms.ModelForm):
|
|||||||
(5, 'Saturday'),
|
(5, 'Saturday'),
|
||||||
(6, 'Sunday'),
|
(6, 'Sunday'),
|
||||||
],
|
],
|
||||||
widget=forms.CheckboxSelectMultiple,
|
widget=forms.CheckboxSelectMultiple(attrs={'class': 'form-check'}),
|
||||||
required=True
|
required=True
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InterviewSchedule
|
model = InterviewSchedule
|
||||||
@ -438,18 +438,27 @@ class InterviewScheduleForm(forms.ModelForm):
|
|||||||
'interview_duration', 'buffer_time'
|
'interview_duration', 'buffer_time'
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'start_date': forms.DateInput(attrs={'type': 'date'}),
|
'start_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
||||||
'end_date': forms.DateInput(attrs={'type': 'date'}),
|
'end_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
||||||
'start_time': forms.TimeInput(attrs={'type': 'time'}),
|
'start_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
||||||
'end_time': forms.TimeInput(attrs={'type': 'time'}),
|
'end_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
||||||
'break_start_time': forms.TimeInput(attrs={'type': 'time'}),
|
'break_start_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
||||||
'break_end_time': forms.TimeInput(attrs={'type': 'time'}),
|
'break_end_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, slug, *args, **kwargs):
|
def __init__(self, slug, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
# Filter candidates based on the selected job
|
# Filter candidates based on the selected job
|
||||||
self.fields['candidates'].queryset = Candidate.objects.filter(
|
self.fields['candidates'].queryset = Candidate.objects.filter(
|
||||||
job_slug=slug,
|
job__slug=slug,
|
||||||
stage='Interview'
|
stage='Interview'
|
||||||
)
|
)
|
||||||
|
self.helper = FormHelper()
|
||||||
|
self.helper.form_method = 'post'
|
||||||
|
self.helper.form_class = 'form-horizontal'
|
||||||
|
self.helper.label_class = 'col-md-3'
|
||||||
|
self.helper.field_class = 'col-md-9'
|
||||||
|
def clean_working_days(self):
|
||||||
|
working_days = self.cleaned_data.get('working_days')
|
||||||
|
# Convert string values to integers
|
||||||
|
return [int(day) for day in working_days]
|
||||||
@ -393,12 +393,13 @@ def schedule_interviews(schedule):
|
|||||||
Returns the number of interviews successfully scheduled.
|
Returns the number of interviews successfully scheduled.
|
||||||
"""
|
"""
|
||||||
candidates = list(schedule.candidates.all())
|
candidates = list(schedule.candidates.all())
|
||||||
|
print(candidates)
|
||||||
if not candidates:
|
if not candidates:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
# Calculate available time slots
|
# Calculate available time slots
|
||||||
available_slots = get_available_time_slots(schedule)
|
available_slots = get_available_time_slots(schedule)
|
||||||
|
print(available_slots)
|
||||||
if len(available_slots) < len(candidates):
|
if len(available_slots) < len(candidates):
|
||||||
raise ValueError(f"Not enough available slots. Required: {len(candidates)}, Available: {len(available_slots)}")
|
raise ValueError(f"Not enough available slots. Required: {len(candidates)}, Available: {len(available_slots)}")
|
||||||
|
|
||||||
@ -474,7 +475,8 @@ def get_available_time_slots(schedule):
|
|||||||
end_date = schedule.end_date
|
end_date = schedule.end_date
|
||||||
|
|
||||||
# Convert working days to a set for quick lookup
|
# Convert working days to a set for quick lookup
|
||||||
working_days_set = set(schedule.working_days)
|
# working_days should be a list of integers where 0=Monday, 1=Tuesday, etc.
|
||||||
|
working_days_set = set(int(day) for day in schedule.working_days)
|
||||||
|
|
||||||
# Parse times
|
# Parse times
|
||||||
start_time = schedule.start_time
|
start_time = schedule.start_time
|
||||||
@ -485,31 +487,51 @@ def get_available_time_slots(schedule):
|
|||||||
# Calculate slot duration (interview duration + buffer time)
|
# Calculate slot duration (interview duration + buffer time)
|
||||||
slot_duration = timedelta(minutes=schedule.interview_duration + schedule.buffer_time)
|
slot_duration = timedelta(minutes=schedule.interview_duration + schedule.buffer_time)
|
||||||
|
|
||||||
|
# Debug output - remove in production
|
||||||
|
print(f"Working days: {working_days_set}")
|
||||||
|
print(f"Date range: {current_date} to {end_date}")
|
||||||
|
print(f"Time range: {start_time} to {end_time}")
|
||||||
|
print(f"Slot duration: {slot_duration}")
|
||||||
|
|
||||||
while current_date <= end_date:
|
while current_date <= end_date:
|
||||||
# Check if current day is a working day
|
# Check if current day is a working day
|
||||||
if current_date.weekday() in working_days_set:
|
weekday = current_date.weekday() # Monday is 0, Sunday is 6
|
||||||
|
print(f"Checking {current_date}, weekday: {weekday}, in working days: {weekday in working_days_set}")
|
||||||
|
|
||||||
|
if weekday in working_days_set:
|
||||||
# Generate slots for this day
|
# Generate slots for this day
|
||||||
current_time = start_time
|
current_time = start_time
|
||||||
|
|
||||||
while current_time + slot_duration <= end_time:
|
while True:
|
||||||
# Check if slot is during break time
|
# Calculate the end time of this slot
|
||||||
if break_start and break_end:
|
slot_end_time = (datetime.combine(current_date, current_time) + slot_duration).time()
|
||||||
if current_time >= break_start and current_time < break_end:
|
|
||||||
current_time = break_end
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Add this slot to available slots
|
# Check if the slot fits within the working hours
|
||||||
slots.append({
|
if slot_end_time > end_time:
|
||||||
'date': current_date,
|
break
|
||||||
'time': current_time
|
|
||||||
})
|
# Check if slot conflicts with break time
|
||||||
|
conflict_with_break = False
|
||||||
|
if break_start and break_end:
|
||||||
|
# Check if the slot overlaps with break time
|
||||||
|
if not (current_time >= break_end or slot_end_time <= break_start):
|
||||||
|
conflict_with_break = True
|
||||||
|
print(f"Slot {current_time}-{slot_end_time} conflicts with break {break_start}-{break_end}")
|
||||||
|
|
||||||
|
if not conflict_with_break:
|
||||||
|
# Add this slot to available slots
|
||||||
|
slots.append({
|
||||||
|
'date': current_date,
|
||||||
|
'time': current_time
|
||||||
|
})
|
||||||
|
print(f"Added slot: {current_date} {current_time}")
|
||||||
|
|
||||||
# Move to next slot
|
# Move to next slot
|
||||||
current_datetime = datetime.combine(current_date, current_time)
|
current_datetime = datetime.combine(current_date, current_time) + slot_duration
|
||||||
current_datetime += slot_duration
|
|
||||||
current_time = current_datetime.time()
|
current_time = current_datetime.time()
|
||||||
|
|
||||||
# Move to next day
|
# Move to next day
|
||||||
current_date += timedelta(days=1)
|
current_date += timedelta(days=1)
|
||||||
|
|
||||||
|
print(f"Total slots generated: {len(slots)}")
|
||||||
return slots
|
return slots
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import requests
|
import requests
|
||||||
|
from rich import print
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.views.decorators.http import require_http_methods
|
from django.views.decorators.http import require_http_methods
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
|
|||||||
@ -75,7 +75,7 @@
|
|||||||
<button type="submit" name="confirm_schedule" class="btn btn-success">
|
<button type="submit" name="confirm_schedule" class="btn btn-success">
|
||||||
<i class="fas fa-check"></i> Confirm Schedule
|
<i class="fas fa-check"></i> Confirm Schedule
|
||||||
</button>
|
</button>
|
||||||
<a href="{% url 'schedule_interviews' job_id=job.id %}" class="btn btn-secondary">
|
<a href="{% url 'schedule_interviews' slug=job.slug %}" class="btn btn-secondary">
|
||||||
<i class="fas fa-arrow-left"></i> Back to Edit
|
<i class="fas fa-arrow-left"></i> Back to Edit
|
||||||
</a>
|
</a>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -88,7 +88,7 @@
|
|||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<button type="submit" class="btn btn-primary">Schedule Interviews</button>
|
<button type="submit" class="btn btn-primary">Schedule Interviews</button>
|
||||||
<a href="{% url 'job_detail' pk=job.slug %}" class="btn btn-secondary">Cancel</a>
|
<a href="{% url 'job_detail' slug=job.slug %}" class="btn btn-secondary">Cancel</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -189,7 +189,7 @@
|
|||||||
data-class="{'bg-primary': $stage == 'Applied', 'bg-info': $stage == 'Exam', 'bg-warning': $stage == 'Interview', 'bg-success': $stage == 'Offer'}"
|
data-class="{'bg-primary': $stage == 'Applied', 'bg-info': $stage == 'Exam', 'bg-warning': $stage == 'Interview', 'bg-success': $stage == 'Offer'}"
|
||||||
data-signals-stage="'{{ candidate.stage }}'">
|
data-signals-stage="'{{ candidate.stage }}'">
|
||||||
{% trans "Stage:" %}
|
{% trans "Stage:" %}
|
||||||
<span data-text="'{{ candidate.stage }}'">{{ candidate.stage }}</span>
|
<span data-text="$stage"></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<small class="text-white opacity-75">
|
<small class="text-white opacity-75">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user