163 lines
6.3 KiB
Python
163 lines
6.3 KiB
Python
from django.shortcuts import render, redirect, get_object_or_404
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.contrib import messages
|
|
from django.http import JsonResponse
|
|
from django.db.models import Q, Count, Avg
|
|
from decimal import Decimal
|
|
from datetime import date
|
|
import json
|
|
|
|
from .models import SpecialistCandidate, EvaluationRubric7D, SpecialistCategoryEnum, SeniorityEnum, CandidateStatusEnum
|
|
from .forms import SpecialistCandidateForm, EvaluationRubric7DForm
|
|
from .services.rubric import TalentScoringEngine
|
|
from expenses.chile_regions import REGIONS_COMMUNES
|
|
|
|
@login_required
|
|
def candidate_list(request):
|
|
candidates = SpecialistCandidate.objects.select_related().all().order_by('-created_at')
|
|
|
|
# Filter params
|
|
category = request.GET.get('category')
|
|
seniority = request.GET.get('seniority')
|
|
trl = request.GET.get('trl')
|
|
status = request.GET.get('status')
|
|
query = request.GET.get('q')
|
|
|
|
if category:
|
|
candidates = candidates.filter(category_code=category)
|
|
if seniority:
|
|
candidates = candidates.filter(seniority=seniority)
|
|
if trl:
|
|
candidates = candidates.filter(trl_level=int(trl))
|
|
if status:
|
|
candidates = candidates.filter(status=status)
|
|
if query:
|
|
candidates = candidates.filter(
|
|
Q(first_name__icontains=query) |
|
|
Q(last_name__icontains=query) |
|
|
Q(rut__icontains=query) |
|
|
Q(specialty_title__icontains=query) |
|
|
Q(summary__icontains=query)
|
|
)
|
|
|
|
# Statistics
|
|
stats = {
|
|
'total': SpecialistCandidate.objects.count(),
|
|
'approved': SpecialistCandidate.objects.filter(status=CandidateStatusEnum.APROBADO_CARTERA).count(),
|
|
'reserve': SpecialistCandidate.objects.filter(status=CandidateStatusEnum.EN_RESERVA).count(),
|
|
'evaluating': SpecialistCandidate.objects.filter(status=CandidateStatusEnum.EN_EVALUACION).count(),
|
|
}
|
|
|
|
return render(request, 'talent/candidate_list.html', {
|
|
'candidates': candidates,
|
|
'stats': stats,
|
|
'categories': SpecialistCategoryEnum.choices,
|
|
'seniorities': SeniorityEnum.choices,
|
|
'statuses': CandidateStatusEnum.choices,
|
|
'selected_category': category,
|
|
'selected_seniority': seniority,
|
|
'selected_trl': trl,
|
|
'selected_status': status,
|
|
'query': query,
|
|
})
|
|
|
|
@login_required
|
|
def candidate_create(request):
|
|
if request.method == 'POST':
|
|
form = SpecialistCandidateForm(request.POST)
|
|
if form.is_valid():
|
|
cand = form.save()
|
|
messages.success(request, f"Especialista {cand.full_name} registrado en cartera.")
|
|
return redirect('talent:candidate_detail', pk=cand.pk)
|
|
else:
|
|
form = SpecialistCandidateForm()
|
|
return render(request, 'talent/candidate_form.html', {
|
|
'form': form,
|
|
'title': 'Registrar Nuevo Especialista I+D+i',
|
|
'regions_json': json.dumps(REGIONS_COMMUNES)
|
|
})
|
|
|
|
@login_required
|
|
def candidate_edit(request, pk):
|
|
cand = get_object_or_404(SpecialistCandidate, pk=pk)
|
|
if request.method == 'POST':
|
|
form = SpecialistCandidateForm(request.POST, instance=cand)
|
|
if form.is_valid():
|
|
cand = form.save()
|
|
messages.success(request, f"Perfil de {cand.full_name} actualizado.")
|
|
return redirect('talent:candidate_detail', pk=cand.pk)
|
|
else:
|
|
form = SpecialistCandidateForm(instance=cand)
|
|
return render(request, 'talent/candidate_form.html', {
|
|
'form': form,
|
|
'title': f"Editar Especialista: {cand.full_name}",
|
|
'regions_json': json.dumps(REGIONS_COMMUNES)
|
|
})
|
|
|
|
@login_required
|
|
def candidate_detail(request, pk):
|
|
cand = get_object_or_404(SpecialistCandidate, pk=pk)
|
|
evaluations = cand.evaluations.select_related('evaluator').order_by('-evaluation_date', '-created_at')
|
|
latest_eval = evaluations.first()
|
|
|
|
return render(request, 'talent/candidate_detail.html', {
|
|
'candidate': cand,
|
|
'evaluations': evaluations,
|
|
'latest_eval': latest_eval,
|
|
})
|
|
|
|
@login_required
|
|
def evaluation_create(request, candidate_pk=None):
|
|
candidate = get_object_or_404(SpecialistCandidate, pk=candidate_pk) if candidate_pk else None
|
|
|
|
if request.method == 'POST':
|
|
form = EvaluationRubric7DForm(request.POST)
|
|
if form.is_valid():
|
|
ev = form.save(commit=False)
|
|
ev.evaluator = request.user
|
|
|
|
# Run Scoring Engine
|
|
result = TalentScoringEngine.evaluate(
|
|
dim_case_study=ev.dim_case_study,
|
|
dim_portfolio=ev.dim_portfolio,
|
|
dim_tech_specific=ev.dim_tech_specific,
|
|
dim_cardinal_skills=ev.dim_cardinal_skills,
|
|
dim_experience=ev.dim_experience,
|
|
dim_principles_safety=ev.dim_principles_safety,
|
|
dim_innovation_rndi=ev.dim_innovation_rndi,
|
|
evaluation_date=ev.evaluation_date
|
|
)
|
|
|
|
ev.total_weighted_score = result['weighted_score']
|
|
ev.is_knockout = result['is_knockout']
|
|
ev.result_status = result['result_status']
|
|
ev.valid_until = result['valid_until']
|
|
ev.save()
|
|
|
|
# Sync candidate status
|
|
cand_obj = ev.candidate
|
|
cand_obj.status = ev.result_status
|
|
cand_obj.save(update_fields=['status'])
|
|
|
|
if ev.is_knockout:
|
|
messages.error(request, f"Evaluación finalizada: {result['verdict_message']}")
|
|
else:
|
|
messages.success(request, f"Evaluación 7D guardada exitosamente. Score: {ev.total_weighted_score}/100 - {result['verdict_message']}")
|
|
|
|
return redirect('talent:candidate_detail', pk=cand_obj.pk)
|
|
else:
|
|
initial = {'evaluation_date': date.today()}
|
|
if candidate:
|
|
initial['candidate'] = candidate
|
|
form = EvaluationRubric7DForm(initial=initial)
|
|
|
|
return render(request, 'talent/evaluation_form.html', {
|
|
'form': form,
|
|
'candidate': candidate,
|
|
})
|
|
|
|
@login_required
|
|
def evaluation_detail(request, pk):
|
|
eval_obj = get_object_or_404(EvaluationRubric7D.objects.select_related('candidate', 'evaluator'), pk=pk)
|
|
return render(request, 'talent/evaluation_detail.html', {'eval': eval_obj})
|