diff --git a/db.sqlite3 b/db.sqlite3 index 5f93f5a..1d79938 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/recruitment/__pycache__/admin.cpython-313.pyc b/recruitment/__pycache__/admin.cpython-313.pyc index 87dcd28..6bb62fc 100644 Binary files a/recruitment/__pycache__/admin.cpython-313.pyc and b/recruitment/__pycache__/admin.cpython-313.pyc differ diff --git a/recruitment/__pycache__/forms.cpython-313.pyc b/recruitment/__pycache__/forms.cpython-313.pyc index 7b08467..ec10c53 100644 Binary files a/recruitment/__pycache__/forms.cpython-313.pyc and b/recruitment/__pycache__/forms.cpython-313.pyc differ diff --git a/recruitment/__pycache__/utils.cpython-313.pyc b/recruitment/__pycache__/utils.cpython-313.pyc index 0ce0d33..b60ea1b 100644 Binary files a/recruitment/__pycache__/utils.cpython-313.pyc and b/recruitment/__pycache__/utils.cpython-313.pyc differ diff --git a/recruitment/__pycache__/views.cpython-313.pyc b/recruitment/__pycache__/views.cpython-313.pyc index b67b9f3..708fe60 100644 Binary files a/recruitment/__pycache__/views.cpython-313.pyc and b/recruitment/__pycache__/views.cpython-313.pyc differ diff --git a/recruitment/admin.py b/recruitment/admin.py index 3bfdc84..9480c13 100644 --- a/recruitment/admin.py +++ b/recruitment/admin.py @@ -5,7 +5,7 @@ from django.utils import timezone from .models import ( JobPosting, Candidate, TrainingMaterial, ZoomMeeting, FormTemplate, FormStage, FormField, FormSubmission, FieldResponse, - SharedFormTemplate, Source, HiringAgency, IntegrationLog + SharedFormTemplate, Source, HiringAgency, IntegrationLog,InterviewSchedule ) class FormFieldInline(admin.TabularInline): @@ -260,5 +260,5 @@ class FormSubmissionAdmin(admin.ModelAdmin): admin.site.register(FormStage) admin.site.register(FormField) admin.site.register(FieldResponse) -admin.site.register(SharedFormTemplate) +admin.site.register(InterviewSchedule) # admin.site.register(HiringAgency) diff --git a/recruitment/forms.py b/recruitment/forms.py index 5ccb8d2..dab5dbc 100644 --- a/recruitment/forms.py +++ b/recruitment/forms.py @@ -413,7 +413,7 @@ class FormTemplateForm(forms.ModelForm): class InterviewScheduleForm(forms.ModelForm): candidates = forms.ModelMultipleChoiceField( queryset=Candidate.objects.none(), - widget=forms.CheckboxSelectMultiple, + widget=forms.CheckboxSelectMultiple(attrs={'class': 'form-check'}), required=True ) working_days = forms.MultipleChoiceField( @@ -426,9 +426,9 @@ class InterviewScheduleForm(forms.ModelForm): (5, 'Saturday'), (6, 'Sunday'), ], - widget=forms.CheckboxSelectMultiple, + widget=forms.CheckboxSelectMultiple(attrs={'class': 'form-check'}), required=True - ) +) class Meta: model = InterviewSchedule @@ -438,18 +438,27 @@ class InterviewScheduleForm(forms.ModelForm): 'interview_duration', 'buffer_time' ] widgets = { - 'start_date': forms.DateInput(attrs={'type': 'date'}), - 'end_date': forms.DateInput(attrs={'type': 'date'}), - 'start_time': forms.TimeInput(attrs={'type': 'time'}), - 'end_time': forms.TimeInput(attrs={'type': 'time'}), - 'break_start_time': forms.TimeInput(attrs={'type': 'time'}), - 'break_end_time': forms.TimeInput(attrs={'type': 'time'}), + 'start_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), + 'end_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), + 'start_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}), + 'end_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}), + 'break_start_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}), + 'break_end_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}), } def __init__(self, slug, *args, **kwargs): super().__init__(*args, **kwargs) # Filter candidates based on the selected job self.fields['candidates'].queryset = Candidate.objects.filter( - job_slug=slug, + job__slug=slug, stage='Interview' - ) \ No newline at end of file + ) + 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] \ No newline at end of file diff --git a/recruitment/utils.py b/recruitment/utils.py index a4b3b06..b252c57 100644 --- a/recruitment/utils.py +++ b/recruitment/utils.py @@ -393,12 +393,13 @@ def schedule_interviews(schedule): Returns the number of interviews successfully scheduled. """ candidates = list(schedule.candidates.all()) + print(candidates) if not candidates: return 0 # Calculate available time slots available_slots = get_available_time_slots(schedule) - + print(available_slots) if len(available_slots) < len(candidates): 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 # 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 start_time = schedule.start_time @@ -485,31 +487,51 @@ def get_available_time_slots(schedule): # Calculate slot duration (interview duration + 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: # 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 current_time = start_time - while current_time + slot_duration <= end_time: - # Check if slot is during break time - if break_start and break_end: - if current_time >= break_start and current_time < break_end: - current_time = break_end - continue + while True: + # Calculate the end time of this slot + slot_end_time = (datetime.combine(current_date, current_time) + slot_duration).time() - # Add this slot to available slots - slots.append({ - 'date': current_date, - 'time': current_time - }) + # Check if the slot fits within the working hours + if slot_end_time > end_time: + break + + # 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 - current_datetime = datetime.combine(current_date, current_time) - current_datetime += slot_duration + current_datetime = datetime.combine(current_date, current_time) + slot_duration current_time = current_datetime.time() # Move to next day current_date += timedelta(days=1) + print(f"Total slots generated: {len(slots)}") return slots \ No newline at end of file diff --git a/recruitment/views.py b/recruitment/views.py index 9b10a33..8b70168 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -1,5 +1,6 @@ import json import requests +from rich import print from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods from django.http import JsonResponse diff --git a/templates/interviews/preview_schedule.html b/templates/interviews/preview_schedule.html index ffd8e18..01b5ded 100644 --- a/templates/interviews/preview_schedule.html +++ b/templates/interviews/preview_schedule.html @@ -75,7 +75,7 @@ - + Back to Edit diff --git a/templates/interviews/schedule_interviews.html b/templates/interviews/schedule_interviews.html index 2588a74..b6fc580 100644 --- a/templates/interviews/schedule_interviews.html +++ b/templates/interviews/schedule_interviews.html @@ -88,7 +88,7 @@
- Cancel + Cancel
diff --git a/templates/recruitment/candidate_detail.html b/templates/recruitment/candidate_detail.html index 67bc81c..1756cd9 100644 --- a/templates/recruitment/candidate_detail.html +++ b/templates/recruitment/candidate_detail.html @@ -39,7 +39,7 @@ transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0,0,0,0.15); } - + /* Outlined Button Styles */ .btn-outline-primary { color: var(--kaauh-teal); @@ -58,7 +58,7 @@ color: white; border-color: var(--kaauh-teal-dark); } - .btn-outline-info { + .btn-outline-info { color: #17a2b8; border-color: #17a2b8; } @@ -75,7 +75,7 @@ box-shadow: 0 4px 12px rgba(0,0,0,0.06); background-color: white; } - + /* Candidate Header Card (The teal header) */ .candidate-header-card { background: linear-gradient(135deg, var(--kaauh-teal), #004d57); @@ -95,7 +95,7 @@ border-radius: 0.4rem; font-weight: 700; } - + /* Left Column Tabs (Main Content Tabs) */ .main-tabs { border-bottom: 1px solid var(--kaauh-border); @@ -130,11 +130,11 @@ } .right-column-card .nav-link { padding: 0.9rem 1rem; - font-size: 0.95rem; + font-size: 0.95rem; font-weight: 600; color: var(--kaauh-primary-text); border-right: 1px solid var(--kaauh-border); - border-bottom: 1px solid var(--kaauh-border); + border-bottom: 1px solid var(--kaauh-border); background-color: #f8f9fa; } .right-column-card .nav-item:last-child .nav-link { @@ -144,14 +144,14 @@ background-color: white; color: var(--kaauh-teal-dark); border-bottom: 3px solid var(--kaauh-teal); - border-right-color: transparent; + border-right-color: transparent; margin-bottom: -1px; } .right-column-card .tab-content { padding: 1.5rem 1.25rem; background-color: white; } - + /* ==================================== */ /* NEW: PARSED DATA GRID OPTIMIZATION */ /* ==================================== */ @@ -171,11 +171,11 @@ {% block content %}
- + {# LEFT COLUMN: MAIN CANDIDATE DETAILS AND TABS #}
- + {# HEADER SECTION (The original Candidate Header Card content, redesigned) #}
@@ -185,11 +185,11 @@ {{ candidate.applied|yesno:"Applied,Pending" }} - - {% trans "Stage:" %} - {{ candidate.stage }} + {% trans "Stage:" %} +
@@ -204,7 +204,7 @@ {% endif %}
- + {# LEFT TABS NAVIGATION #}
@@ -305,7 +305,7 @@ {# RIGHT COLUMN: ACTIONS AND PARSED DATA TABS #}
- + {# ACTIONS CARD (The new consolidated action card) #} {% if user.is_staff %}
@@ -345,7 +345,7 @@
- + {# TAB 1: PARSED DATA - UPDATED TO 2-COLUMN GRID #}
{% trans "Structured Resume Data" %}