From d26c18fefdced0ffe92d90e90113ae7e2e7e0845 Mon Sep 17 00:00:00 2001 From: ismail Date: Tue, 7 Oct 2025 18:33:49 +0300 Subject: [PATCH] add scheduler --- db.sqlite3 | Bin 602112 -> 602112 bytes recruitment/__pycache__/admin.cpython-313.pyc | Bin 11317 -> 11345 bytes recruitment/__pycache__/forms.cpython-313.pyc | Bin 20729 -> 21447 bytes recruitment/__pycache__/utils.cpython-313.pyc | Bin 15295 -> 16432 bytes recruitment/__pycache__/views.cpython-313.pyc | Bin 33092 -> 33125 bytes recruitment/admin.py | 4 +- recruitment/forms.py | 31 ++++++---- recruitment/utils.py | 54 ++++++++++++------ recruitment/views.py | 1 + templates/interviews/preview_schedule.html | 2 +- templates/interviews/schedule_interviews.html | 2 +- templates/recruitment/candidate_detail.html | 36 ++++++------ 12 files changed, 81 insertions(+), 49 deletions(-) diff --git a/db.sqlite3 b/db.sqlite3 index 5f93f5ac52d5b8402e92ab7bdd6ec7283efe7f56..1d79938e6998078f8e47ba0bc8dd2671f2f0bb35 100644 GIT binary patch delta 1298 zcmb7@&2QUu7{~1-&DxE`ji88hlm=-`f+~*R9p}Q_ye)R#n=EyNlsS!)IEmvVb{Z$u z)K+8djzg2kodX39AgE{$`wKWA?Y87ZZxce35HBYV#og2)pa(eo9zD;a-%ro?`_c2; zIk~oTa_zyQ1+;$j=BgRLzJ0Llo`HUZ9z&0yPaGDg2kk=Fp@j2^^O586p~s^;=A8S0 z&7HQ=;F$f`zVPrH?^`qO_ssI4%eMep4uA5_gRjH0J!zxYmvu#I>!pTTe#5*mch}|H zclki`SdLvj>#xYH1)2iTGiZ9~+W8)O?N#dv$g^zzrWZpn(u*JnY8gNK!S3F-cdW-Lu8#x4&o0#VVyuCTKJ;06F-0RY}!UUC5r2Ow*TD7Rom z)kIh>_r$ua+^9WoZKz65()x-tW;vVtodP8^f@=-9Q>i|mjUrxz_Mk+7qyiWQlNjp9 zk@165+tqEe#{@_krb)ja!(L1{8-S1$MI$)P5V5u{^)_W`5ErP%t(Z~Ry0iO%j0&I&U*Hsk%t zXJ!K})dl~0DA&Kx3W30;W`ZDITA>334U;6!kmw5)XClt6py*30AttIu#8`9dj!F2t ze*<6m&%iYwTdwq5EwMi4IUDpp&<9QQ{s4x+6pd00auNElAu4}SH^E;vKwzOhlVTvKdo1DE210HWKuBh^eZPT!P|(?xQxps)b4#suLaVuK;`|e1s0SqjI2s@k d829^e5;>Z;|8na5dv9<4(%$^+r}J~3KLHW-jMD%B delta 495 zcmZp8pwjR_Wr7qF!>@@lPC#;F!g6^@X8xD_7x;JcFXCn5FXC_HSL65PyUw?ppJ%gz zLJ2P??*#@H$pB_^#_5jJ*hD99(v@T7YiE$uf7u09|Y_OeW0xP>fJ9h!|cJ2a}Z}yuRA8-k<`YfWeU{q{e783XCS2s>Xi{Qi^=%Gz zoc=(bO`%>d)vF>b%h%PvJkv2SC_vY(xY8{>$2`o$($BOk(JLy&J15j7I6NnyB2U-Y z%f&c9DmXF9vdXK>FU8xaAkV!hIVdMDG%O{@Fg3^6*&-w$*QqqnNjJ>gAgrpgw79~_ z&mc6d#J?~j*rO`Nr7W$?)6LD#JSef)FC{56!@WK=s4zU))vVC3!o@$`%+NSH%c(TL z&D_zT*fZG4%+=M>JUu_7s=TmVH^SU7)6dw`%COALJj>n6Jj&4`G}$fJDXYxIG%Ycz zIJ+P@*~2G4+|(~Hydb$ExTLZ?ATYz(C&a?Yz{phB&_LJ7P{GjH%E;Wx#7xiB)X2oZ ZjB7ii0Ncm^e5j&r0_<%99Loea6#&v8nGyg1 diff --git a/recruitment/__pycache__/admin.cpython-313.pyc b/recruitment/__pycache__/admin.cpython-313.pyc index 87dcd283ed4b296bc5b5ea1bc57b7bf4928b4f1c..6bb62fc07d1d0df2e6686846b4c1f919eff6e74a 100644 GIT binary patch delta 2765 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 LHTu1wgHO|c3!Fr6 diff --git a/recruitment/__pycache__/forms.cpython-313.pyc b/recruitment/__pycache__/forms.cpython-313.pyc index 7b08467ec986959f773990916d8fbf919ac7f5d7..ec10c536df3b4b8e44685e27df4d5a0035c124d7 100644 GIT binary patch delta 1115 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 QY21wE8-pVl? zVq&7X0a>t6;{$g!#D&IaG;!gMG9lD1fQ6X2tHKZP4ltl6Gjq;&?)S}{d(NHwEc`TO z-mR=O0c{F_B0b4Gu0zB|7VGCe13Jx1bBUYINVvpyv@g!B?Y?{yOMD!B99dI9kDDn9l}mm2eK#I}0F>=E{;(Xr=!$FUj-TJ$tRqX(_^uz6*KtzD3`{yLK%W zRN#9zN&qkqf&;f=)rNX;w#_+uS8v*l%$N9yO^y^K$GHULpL?y1s&TIXVP!x<_#Lo(ftuEhsuwoMM%?Ko`nPTFf~_szBHLm=?e3R7McOOV9r@dG_fWApv`BrgWy?BXbv&jXQAJk^ zzJ6;f+_pVXVYTm3pz*|pH@xNz=lf;v(5i(kTG)c6T?v@r2Gdz!I^`aw>mgNc0Cr~o zL+iF!xd|9+mW)p(3bkCpz|B$RFd*r5lc(GTDC=y$bO~>bgM%ylDL*uAVW9lG*{A64 V=>>ZLI{%D77u4;>L(sZU{R^DG;P3zd diff --git a/recruitment/__pycache__/views.cpython-313.pyc b/recruitment/__pycache__/views.cpython-313.pyc index b67b9f3c9a38a0c37d46e42773a590dcdbbfa983..708fe60d829b7630a48c25dc5f359c1e28d65017 100644 GIT binary patch delta 6531 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 M?AJ0c8*a?_KV92Bw*UYD 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" %}