add scheduler

This commit is contained in:
ismail 2025-10-07 18:33:49 +03:00
parent 48f61f173f
commit d26c18fefd
12 changed files with 81 additions and 49 deletions

Binary file not shown.

View File

@ -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)

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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">