diff --git a/recruitment/__pycache__/models.cpython-312.pyc b/recruitment/__pycache__/models.cpython-312.pyc index fff953c..7e98f6b 100644 Binary files a/recruitment/__pycache__/models.cpython-312.pyc and b/recruitment/__pycache__/models.cpython-312.pyc differ diff --git a/recruitment/__pycache__/views_frontend.cpython-312.pyc b/recruitment/__pycache__/views_frontend.cpython-312.pyc index 4c1b0db..a19c31f 100644 Binary files a/recruitment/__pycache__/views_frontend.cpython-312.pyc and b/recruitment/__pycache__/views_frontend.cpython-312.pyc differ diff --git a/recruitment/models.py b/recruitment/models.py index 4ed1316..0b406f8 100644 --- a/recruitment/models.py +++ b/recruitment/models.py @@ -680,36 +680,8 @@ class Candidate(Base): return future_meetings or today_future_meetings @property - def check_and_retry_ai_scoring(self): - """ - Triggers an immediate save ONLY if: - 1. The resume hasn't been parsed yet. - 2. At least 5 minutes have passed since the last attempt. - Returns True if a save was performed, False otherwise. - """ - from datetime import timedelta - - min_delay = timedelta(minutes=5) - - time_since_last_attempt = timezone.now() - self.created_at - - if not self.is_resume_parsed and time_since_last_attempt >= min_delay: - - # 1. Update the retry timestamp - self.last_retry_attempt = timezone.now() - - - self.save() - - return True - - return False - - # @property - # def time_to_hire(self): - # time_to_hire=self.hired_date-self.created_at - # return time_to_hire - + def scoring_timeout(self): + return timezone.now() <= (self.created_at + timezone.timedelta(minutes=5)) class TrainingMaterial(Base): diff --git a/recruitment/views_frontend.py b/recruitment/views_frontend.py index b403475..729749e 100644 --- a/recruitment/views_frontend.py +++ b/recruitment/views_frontend.py @@ -223,10 +223,17 @@ class CandidateDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): def retry_scoring_view(request,slug): - if request.method == 'POST': - candidate = get_object_or_404(models.Candidate, slug=slug) - candidate.save() - return redirect('candidate_detail', slug=candidate.slug) + from django_q.tasks import async_task + + candidate = get_object_or_404(models.Candidate, slug=slug) + + async_task( + 'recruitment.tasks.handle_reume_parsing_and_scoring', + candidate.pk, + hook='recruitment.hooks.callback_ai_parsing', + sync=True, + ) + return redirect('candidate_detail', slug=candidate.slug) @@ -507,6 +514,15 @@ def dashboard_view(request): rotation_degrees = rotation_percent * 180 rotation_degrees_final = round(min(rotation_degrees, 180), 1) # Ensure max 180 degrees + # + hiring_source_counts = candidate_queryset.values('hiring_source').annotate(count=Count('stage')) + source_map= {item['hiring_source']: item['count'] for item in hiring_source_counts} + candidates_count_in_each_source = [ + source_map.get('Public', 0), source_map.get('Internal', 0), source_map.get('Agency', 0), + + ] + all_hiring_sources=["Public", "Internal", "Agency"] + # --- 8. CONTEXT RETURN --- @@ -555,6 +571,10 @@ def dashboard_view(request): 'jobs': all_jobs_queryset, 'current_job_id': selected_job_pk, 'current_job': current_job, + + + 'candidates_count_in_each_source': json.dumps(candidates_count_in_each_source), + 'all_hiring_sources': json.dumps(all_hiring_sources), } return render(request, 'recruitment/dashboard.html', context) diff --git a/templates/base.html b/templates/base.html index 7a394ab..eef4251 100644 --- a/templates/base.html +++ b/templates/base.html @@ -705,6 +705,5 @@ {% comment %} {% endif %} {% endcomment %} {% block customJS %}{% endblock %} - diff --git a/templates/jobs/partials/linkedin_content_form.html b/templates/jobs/partials/linkedin_content_form.html index 9a606e7..c3edd5a 100644 --- a/templates/jobs/partials/linkedin_content_form.html +++ b/templates/jobs/partials/linkedin_content_form.html @@ -1,6 +1,6 @@ {% load crispy_forms_tags %}