From f168ab4ba8c6c82e9140c21fb0ede18cba63dedd Mon Sep 17 00:00:00 2001 From: ismail Date: Sun, 30 Nov 2025 12:28:35 +0300 Subject: [PATCH] update the interview reschedule --- recruitment/forms.py | 35 ++++++++++ recruitment/models.py | 15 ++-- recruitment/tasks.py | 8 +-- recruitment/urls.py | 12 ++-- recruitment/utils.py | 4 +- recruitment/views.py | 68 +++++++++---------- templates/interviews/interview_detail.html | 24 +++---- .../interviews/partials/interview_list.html | 8 +-- .../applications_interview_view.html | 40 ++++++----- 9 files changed, 123 insertions(+), 91 deletions(-) diff --git a/recruitment/forms.py b/recruitment/forms.py index 49fa7d8..4585992 100644 --- a/recruitment/forms.py +++ b/recruitment/forms.py @@ -2787,3 +2787,38 @@ class OnsiteInterviewForm(forms.Form): # instance.save() # return instance + +class ScheduledInterviewForm(forms.Form): + topic = forms.CharField( + max_length=255, + required=False, + widget=forms.TextInput(attrs={ + 'class': 'form-control', + 'placeholder': 'e.g., Interview Topic' + }), + label=_('Interview Topic') + ) + start_time = forms.DateTimeField( + widget=forms.DateTimeInput(attrs={ + 'class': 'form-control', + 'type': 'datetime-local', + 'required': True + }), + label=_('Start Time') + ) + duration = forms.IntegerField( + min_value=1, + required=False, + widget=forms.NumberInput(attrs={ + 'class': 'form-control', + 'placeholder': 'Duration in minutes' + }), + label=_('Duration (minutes)') + ) + + def clean_start_time(self): + """Validate start time is not in the past""" + start_time = self.cleaned_data.get('start_time') + if start_time and start_time < timezone.now(): + raise forms.ValidationError(_('Start time cannot be in the past.')) + return start_time \ No newline at end of file diff --git a/recruitment/models.py b/recruitment/models.py index a8975a2..e4dafee 100644 --- a/recruitment/models.py +++ b/recruitment/models.py @@ -379,18 +379,18 @@ class JobPosting(Base): @property def all_applications(self): # 1. Define the safe JSON extraction and conversion expression - safe_score_expression = Cast( + safe_score_expression = Cast( Coalesce( # Extract the score explicitly as a text string (KeyTextTransform) KeyTextTransform( - 'match_score', + 'match_score', KeyTransform('analysis_data_en', 'ai_analysis_data') ), Value('0'), # Replace SQL NULL (from missing score) with the string '0' ), output_field=IntegerField() # Cast the resulting string ('90' or '0') to an integer ) - + # 2. Annotate the score using the safe expression return self.applications.annotate( sortable_score=safe_score_expression @@ -428,7 +428,7 @@ class JobPosting(Base): @property def all_applications_count(self): return self.all_applications.count() - + @property def screening_applications_count(self): @@ -766,7 +766,7 @@ class Application(Base): @property def resume_data_en(self): return self.ai_analysis_data.get("resume_data_en", {}) - + @property def resume_data_ar(self): return self.ai_analysis_data.get("resume_data_ar", {}) @@ -853,7 +853,7 @@ class Application(Base): def recommendation(self) -> str: """9. Provide a detailed final recommendation for the candidate.""" return self.analysis_data_en.get("recommendation", "") - + #for arabic @property @@ -910,7 +910,7 @@ class Application(Base): def recommendation_ar(self) -> str: """9. Provide a detailed final recommendation for the candidate.""" return self.analysis_data_ar.get("recommendation", "") - + # ==================================================================== # 🔄 HELPER METHODS @@ -1305,6 +1305,7 @@ class Interview(Base): ) password = models.CharField(max_length=20, blank=True, null=True) zoom_gateway_response = models.JSONField(blank=True, null=True) + details_url = models.JSONField(blank=True, null=True) participant_video = models.BooleanField(default=True) join_before_host = models.BooleanField(default=False) host_email = models.CharField(max_length=255, blank=True, null=True) diff --git a/recruitment/tasks.py b/recruitment/tasks.py index 4716133..f3f86fb 100644 --- a/recruitment/tasks.py +++ b/recruitment/tasks.py @@ -363,7 +363,7 @@ def safe_cast_to_float(value, default=0.0): # return # # Ensure the result is parsed as a Python dict (if ai_handler returns a JSON string) # data = result['data'] - + # if isinstance(data, str): # data = json.loads(data) # print(data) @@ -634,7 +634,7 @@ def handle_resume_parsing_and_scoring(pk: int): logger.error(f"AI handler returned error for candidate {instance.pk}") print(f"AI handler returned error for candidate {instance.pk}") return - + # Ensure the result is parsed as a Python dict data = result['data'] if isinstance(data, str): @@ -711,8 +711,8 @@ def create_interview_and_meeting( password=result["meeting_details"]["password"], location_type="Remote" ) - schedule.interviews = interview - schedule.status = "Remote" + schedule.interview = interview + schedule.status = "scheduled" schedule.save() diff --git a/recruitment/urls.py b/recruitment/urls.py index 36fdf30..19780ba 100644 --- a/recruitment/urls.py +++ b/recruitment/urls.py @@ -205,13 +205,11 @@ urlpatterns = [ views_frontend.test_source_connection, name="test_source_connection", ), - - - # path( - # "jobs///reschedule_meeting_for_application//", - # views.reschedule_meeting_for_application, - # name="reschedule_meeting_for_application", - # ), + path( + "jobs//reschedule_meeting_for_application/", + views.reschedule_meeting_for_application, + name="reschedule_meeting_for_application", + ), path( "jobs//update_application_exam_status/", views.update_application_exam_status, diff --git a/recruitment/utils.py b/recruitment/utils.py index 27e3b97..3e6a09a 100644 --- a/recruitment/utils.py +++ b/recruitment/utils.py @@ -586,7 +586,7 @@ def update_meeting(instance, updated_data): if result["status"] == "success": # Fetch the latest details from Zoom after successful update details_result = get_zoom_meeting_details(instance.meeting_id) - + if details_result["status"] == "success": zoom_details = details_result["meeting_details"] # Update instance with fetched details @@ -594,7 +594,7 @@ def update_meeting(instance, updated_data): instance.topic = zoom_details.get("topic", instance.topic) instance.duration = zoom_details.get("duration", instance.duration) - instance.details_url = zoom_details.get("join_url", instance.details_url) + # instance.details_url = zoom_details.get("join_url", instance.details_url) instance.password = zoom_details.get("password", instance.password) # Corrected status assignment: instance.status, not instance.password instance.status = zoom_details.get("status") diff --git a/recruitment/views.py b/recruitment/views.py index 5f3ecca..c6798f9 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -78,6 +78,7 @@ from .forms import ( PortalLoginForm, MessageForm, PersonForm, + ScheduledInterviewForm # OnsiteLocationForm, # OnsiteReshuduleForm, # OnsiteScheduleForm, @@ -131,8 +132,7 @@ from .models import ( Source, Message, Document, - # InterviewLocation, - # InterviewNote, + Interview ) @@ -2031,43 +2031,34 @@ def applications_document_review_view(request, slug): return render(request, "recruitment/applications_document_review_view.html", context) -# @staff_user_required -# def reschedule_meeting_for_application(request, slug, candidate_id, meeting_id): -# job = get_object_or_404(JobPosting, slug=slug) -# candidate = get_object_or_404(Application, pk=candidate_id) -# meeting = get_object_or_404(ZoomMeetingDetails, pk=meeting_id) -# form = ZoomMeetingForm(instance=meeting) +@require_POST +@staff_user_required +def reschedule_meeting_for_application(request, slug): + schedule_interview = get_object_or_404(ScheduledInterview, slug=slug) + if request.method == "POST": + form = ScheduledInterviewForm(request.POST) + if form.is_valid(): + topic = form.cleaned_data.get("topic") + start_time = form.cleaned_data.get("start_time") + duration = form.cleaned_data.get("duration") + updated_data = { + "topic": topic, + "start_time": start_time.isoformat() + "Z", + "duration": duration, + } + result = update_meeting(schedule_interview.interview, updated_data) -# if request.method == "POST": -# form = ZoomMeetingForm(request.POST, instance=meeting) -# if form.is_valid(): -# instance = form.save(commit=False) -# updated_data = { -# "topic": instance.topic, -# "start_time": instance.start_time.isoformat() + "Z", -# "duration": instance.duration, -# } -# if instance.start_time < timezone.now(): -# messages.error(request, "Start time must be in the future.") -# return redirect( -# "reschedule_meeting_for_application", -# slug=job.slug, -# candidate_id=candidate_id, -# meeting_id=meeting_id, -# ) + if result["status"] == "success": + messages.success(request, result["message"]) + else: + messages.error(request, result["message"]) + else: + print(form.errors) + messages.error(request, "Invalid data submitted.") + return redirect("interview_detail", slug=schedule_interview.slug) -# result = update_meeting(instance, updated_data) - - # if result["status"] == "success": - # messages.success(request, result["message"]) - # else: - # messages.error(request, result["message"]) - # return redirect( - # reverse("applications_interview_view", kwargs={"slug": job.slug}) - # ) - -# context = {"job": job, "candidate": candidate, "meeting": meeting, "form": form} -# return render(request, "meetings/reschedule_meeting.html", context) + # context = {"job": job, "application": application, "meeting": meeting, "form": form} + # return render(request, "meetings/reschedule_meeting.html", context) # @staff_user_required @@ -5872,8 +5863,11 @@ def interview_detail(request, slug): """View details of a specific interview""" interview = get_object_or_404(ScheduledInterview, slug=slug) + reschedule_form = ScheduledInterviewForm() + reschedule_form.initial['topic'] = interview.interview.topic context = { 'interview': interview, + 'reschedule_form':reschedule_form } return render(request, 'interviews/interview_detail.html', context) diff --git a/templates/interviews/interview_detail.html b/templates/interviews/interview_detail.html index b62c06c..855de95 100644 --- a/templates/interviews/interview_detail.html +++ b/templates/interviews/interview_detail.html @@ -1,5 +1,5 @@ {% extends 'base.html' %} -{% load static i18n %} +{% load static i18n crispy_forms_tags %} {% block title %}{{ interview.application.name }} - {% trans "Interview Details" %} - ATS{% endblock %} @@ -245,7 +245,7 @@ @@ -643,21 +643,21 @@