TIF_E41253310/Backend/app/controllers/alumni.py

355 lines
16 KiB
Python

import math
from typing import Optional
from sqlalchemy.orm import Session
from app.services.alumni import AlumniService
from app.schemas.alumni import (
AlumniCreate,
AlumniUpdate,
AlumniResponse,
AlumniDetailResponse,
)
from app.utils.helpers import format_response
class AlumniController:
"""
Controller handling HTTP requests and response formatting for the Alumni entity.
"""
@staticmethod
def get_alumni(db: Session, alumni_id: int):
"""Get an alumni record with full details (pekerjaan, kompetensi, tracer responses, clustering)."""
alumni = AlumniService.get_by_id_detailed(db, alumni_id)
alumni_schema = AlumniDetailResponse.model_validate(alumni)
return format_response(True, "Berhasil mengambil data detail alumni", alumni_schema.model_dump())
@staticmethod
def get_all_alumni(
db: Session,
skip: int = 0,
limit: int = 10,
search: Optional[str] = None,
kode_prodi: Optional[str] = None,
tahun_lulus: Optional[int] = None,
status_kerja: Optional[str] = None,
):
"""Get filtered alumni list with pagination metadata."""
alumni_list, total = AlumniService.get_filtered(
db=db,
skip=skip,
limit=limit,
search=search,
kode_prodi=kode_prodi,
tahun_lulus=tahun_lulus,
status_kerja=status_kerja,
)
data = [AlumniResponse.model_validate(a).model_dump() for a in alumni_list]
page = (skip // limit) + 1 if limit > 0 else 1
total_pages = math.ceil(total / limit) if limit > 0 else 1
pagination_data = {
"items": data,
"total": total,
"page": page,
"limit": limit,
"total_pages": total_pages,
}
return format_response(True, "Berhasil mengambil data alumni", pagination_data)
@staticmethod
def create_alumni(db: Session, alumni_in: AlumniCreate):
"""Create a new alumni record and format the response."""
from fastapi import HTTPException
try:
new_alumni = AlumniService.create(db, alumni_in)
alumni_schema = AlumniResponse.model_validate(new_alumni)
return format_response(True, "Berhasil menambahkan data alumni", alumni_schema.model_dump())
except HTTPException:
raise
except Exception as e:
db.rollback()
raise HTTPException(status_code=400, detail=f"Gagal menambahkan data alumni: {str(e)}")
@staticmethod
def update_alumni(db: Session, alumni_id: int, alumni_in: AlumniUpdate):
"""Update an existing alumni record and format the response."""
from fastapi import HTTPException
try:
updated_alumni = AlumniService.update(db, alumni_id, alumni_in)
alumni_schema = AlumniResponse.model_validate(updated_alumni)
return format_response(True, "Berhasil memperbarui data alumni", alumni_schema.model_dump())
except HTTPException:
raise
except Exception as e:
db.rollback()
raise HTTPException(status_code=400, detail=f"Gagal memperbarui data alumni: {str(e)}")
@staticmethod
def delete_alumni(db: Session, alumni_id: int):
"""Delete an alumni record and format the response cleanly with child table cleanup."""
from app.models.models import Pekerjaan, Kompetensi, TracerResponse, ClusteringResult
db.query(Pekerjaan).filter(Pekerjaan.alumni_id == alumni_id).delete(synchronize_session=False)
db.query(Kompetensi).filter(Kompetensi.alumni_id == alumni_id).delete(synchronize_session=False)
db.query(TracerResponse).filter(TracerResponse.alumni_id == alumni_id).delete(synchronize_session=False)
db.query(ClusteringResult).filter(ClusteringResult.alumni_id == alumni_id).delete(synchronize_session=False)
db.commit()
deleted_alumni = AlumniService.delete(db, alumni_id)
alumni_schema = AlumniResponse.model_validate(deleted_alumni)
return format_response(True, "Berhasil menghapus data alumni", alumni_schema.model_dump())
@staticmethod
def public_tracer_lookup(db: Session, lookup_in):
"""
Public lookup to verify NIM + Gmail for Google Form style response editing.
Prevents unauthorized users from viewing or modifying someone else's data.
"""
from fastapi import HTTPException
from app.models.alumni import Alumni
from app.models.models import Pekerjaan, TracerResponse
nim_clean = lookup_in.nim.strip()
alumni = db.query(Alumni).filter(Alumni.nim.ilike(nim_clean)).first()
if not alumni:
return format_response(
True,
"NIM belum terdaftar. Silakan isi kuesioner baru.",
{"exists": False, "verified": False}
)
# Temukan email terdaftar (dari tabel alumni atau tracer_response)
registered_email = (alumni.email or "").strip().lower()
if not registered_email:
tr = db.query(TracerResponse).filter(TracerResponse.alumni_id == alumni.id).first()
if tr and tr.email:
registered_email = tr.email.strip().lower()
# Helper masking email & nama
def mask_email(email_str: str) -> str:
if not email_str or "@" not in email_str:
return "****"
user_part, domain_part = email_str.split("@", 1)
masked_user = user_part[0] + "***" if len(user_part) <= 2 else user_part[0] + "***" + user_part[-1]
return f"{masked_user}@{domain_part}"
def mask_name(name_str: str) -> str:
if not name_str:
return "****"
return " ".join([w[0] + "*" * (len(w) - 1) if len(w) > 1 else w + "*" for w in name_str.split()])
# Jika pengguna mengirim email untuk verifikasi
if lookup_in.email and lookup_in.email.strip():
input_email = lookup_in.email.strip().lower()
if registered_email and input_email != registered_email:
raise HTTPException(
status_code=403,
detail=f"Email ({input_email}) tidak cocok dengan NIM yang terdaftar ({mask_email(registered_email)}). Demi privasi, pengubahan respons hanya dapat dilakukan oleh pemilik email terkait."
)
# Jika cocok (atau sebelumnya alumni belum punya email terdaftar): buka akses edit
pek = db.query(Pekerjaan).filter(Pekerjaan.alumni_id == alumni.id).first()
tr = db.query(TracerResponse).filter(TracerResponse.alumni_id == alumni.id).first()
data_extra_val = {}
if tr and tr.data_extra:
try:
import json
data_extra_val = json.loads(tr.data_extra)
except Exception:
data_extra_val = {}
alumni_data = {
"id": alumni.id,
"nim": alumni.nim,
"nama": alumni.nama,
"kode_prodi": alumni.kode_prodi,
"tahun_lulus": alumni.tahun_lulus,
"email": registered_email or input_email,
"no_hp": alumni.no_hp or (tr.telepon if tr else ""),
"status_kerja": alumni.status_kerja or (pek.status_pekerjaan if pek else "bekerja"),
"nama_perusahaan": pek.nama_perusahaan if pek else (alumni.instansi or ""),
"jabatan": pek.jabatan if pek else (alumni.pekerjaan or ""),
"jenis_perusahaan": pek.jenis_perusahaan if pek else (tr.jenis_perusahaan_full if tr else ""),
"tingkatan_tempat_kerja": pek.tingkatan_tempat_kerja if pek else "",
"hubungan_studi": tr.keeratan_bidang_studi if tr else "",
"data_extra": data_extra_val,
}
return format_response(
True,
"Verifikasi berhasil. Data respons Anda siap diedit.",
{"exists": True, "verified": True, "data": alumni_data}
)
# Jika belum memasukkan email (cek status kepemilikan awal)
return format_response(
True,
"NIM ditemukan. Masukkan Email yang terdaftar untuk verifikasi kepemilikan data.",
{
"exists": True,
"verified": False,
"has_registered_email": bool(registered_email),
"masked_email": mask_email(registered_email) if registered_email else None,
"masked_name": mask_name(alumni.nama),
"kode_prodi": alumni.kode_prodi,
"tahun_lulus": alumni.tahun_lulus,
}
)
@staticmethod
def public_tracer_submit(db: Session, submit_in):
"""
Public submit handler for alumni tracer study form (Create or Update / Upsert).
"""
from fastapi import HTTPException
from app.models.alumni import Alumni
from app.models.models import Pekerjaan, TracerResponse
nim_clean = submit_in.nim.strip()
alumni = db.query(Alumni).filter(Alumni.nim.ilike(nim_clean)).first()
teks_pek = f"{submit_in.status_kerja or ''} {submit_in.jenis_perusahaan or ''} {submit_in.nama_perusahaan or ''} {submit_in.jabatan or ''}".strip()
if not teks_pek:
teks_pek = "-"
if alumni:
# Mode UPDATE: Cek kecocokan email jika alumni sudah memiliki email terdaftar
if alumni.email and alumni.email.strip():
if alumni.email.strip().lower() != submit_in.email.strip().lower():
raise HTTPException(
status_code=403,
detail="Email yang Anda masukkan tidak cocok dengan data pendaftaran sebelumnya. Perubahan ditolak."
)
# Update data alumni
alumni.nama = submit_in.nama.strip()
alumni.kode_prodi = submit_in.kode_prodi
alumni.tahun_lulus = submit_in.tahun_lulus
alumni.email = submit_in.email.strip().lower()
alumni.no_hp = submit_in.no_hp.strip() if submit_in.no_hp else alumni.no_hp
alumni.status_kerja = submit_in.status_kerja
alumni.pekerjaan = submit_in.jabatan
alumni.instansi = submit_in.nama_perusahaan
# Update / Create Pekerjaan
pek = db.query(Pekerjaan).filter(Pekerjaan.alumni_id == alumni.id).first()
if not pek:
pek = Pekerjaan(alumni_id=alumni.id)
db.add(pek)
pek.status_pekerjaan = submit_in.status_kerja
pek.nama_perusahaan = submit_in.nama_perusahaan
pek.jabatan = submit_in.jabatan
pek.jenis_perusahaan = submit_in.jenis_perusahaan
pek.tingkatan_tempat_kerja = submit_in.tingkatan_tempat_kerja
pek.teks_pekerjaan = teks_pek
# Update / Create TracerResponse
import json
tr = db.query(TracerResponse).filter(TracerResponse.alumni_id == alumni.id).first()
if not tr:
tr = TracerResponse(alumni_id=alumni.id, nim=alumni.nim)
db.add(tr)
tr.email = submit_in.email.strip().lower()
tr.telepon = submit_in.no_hp
tr.kode_pt = submit_in.kode_pt or "005019"
tr.nik = submit_in.nik
tr.npwp = submit_in.npwp
tr.status_saat_ini = submit_in.status_kerja
tr.bulan_dapat_kerja = submit_in.bulan_dapat_kerja
tr.rata_pendapatan = submit_in.rata_pendapatan
tr.provinsi_kerja = submit_in.provinsi_kerja
tr.kab_kota_kerja = submit_in.kab_kota_kerja
tr.nama_perusahaan = submit_in.nama_perusahaan
tr.jabatan = submit_in.jabatan
tr.jenis_perusahaan_full = submit_in.jenis_perusahaan
tr.tingkatan_tempat_kerja = submit_in.tingkatan_tempat_kerja
tr.sumber_biaya_studi = submit_in.sumber_biaya_studi
tr.pt_studi_lanjut = submit_in.pt_studi_lanjut
tr.prodi_studi_lanjut = submit_in.prodi_studi_lanjut
tr.sumber_biaya_kuliah = submit_in.sumber_biaya_kuliah
tr.keeratan_bidang_studi = submit_in.hubungan_studi
tr.tingkat_pendidikan_tepat = submit_in.tingkat_pendidikan_tepat
if submit_in.data_extra:
tr.data_extra = json.dumps(submit_in.data_extra, ensure_ascii=False)
db.commit()
db.refresh(alumni)
return format_response(
True,
"Jawaban Anda telah berhasil diperbarui.",
{"action": "update", "nim": alumni.nim, "nama": alumni.nama, "email": alumni.email}
)
else:
# Mode CREATE: Data baru
new_alumni = Alumni(
nim=nim_clean,
nama=submit_in.nama.strip(),
kode_prodi=submit_in.kode_prodi,
tahun_lulus=submit_in.tahun_lulus,
email=submit_in.email.strip().lower(),
no_hp=submit_in.no_hp.strip() if submit_in.no_hp else None,
status_kerja=submit_in.status_kerja,
pekerjaan=submit_in.jabatan,
instansi=submit_in.nama_perusahaan,
)
db.add(new_alumni)
db.flush()
# Buat record Pekerjaan
pek = Pekerjaan(
alumni_id=new_alumni.id,
status_pekerjaan=submit_in.status_kerja,
nama_perusahaan=submit_in.nama_perusahaan,
jabatan=submit_in.jabatan,
jenis_perusahaan=submit_in.jenis_perusahaan,
tingkatan_tempat_kerja=submit_in.tingkatan_tempat_kerja,
provinsi=submit_in.provinsi_kerja,
kabupaten_kota=submit_in.kab_kota_kerja,
teks_pekerjaan=teks_pek,
)
db.add(pek)
# Buat record TracerResponse
import json
tr = TracerResponse(
alumni_id=new_alumni.id,
nim=new_alumni.nim,
email=new_alumni.email,
telepon=new_alumni.no_hp,
kode_pt=submit_in.kode_pt or "005019",
nik=submit_in.nik,
npwp=submit_in.npwp,
status_saat_ini=submit_in.status_kerja,
bulan_dapat_kerja=submit_in.bulan_dapat_kerja,
rata_pendapatan=submit_in.rata_pendapatan,
provinsi_kerja=submit_in.provinsi_kerja,
kab_kota_kerja=submit_in.kab_kota_kerja,
nama_perusahaan=submit_in.nama_perusahaan,
jabatan=submit_in.jabatan,
jenis_perusahaan_full=submit_in.jenis_perusahaan,
tingkatan_tempat_kerja=submit_in.tingkatan_tempat_kerja,
sumber_biaya_studi=submit_in.sumber_biaya_studi,
pt_studi_lanjut=submit_in.pt_studi_lanjut,
prodi_studi_lanjut=submit_in.prodi_studi_lanjut,
sumber_biaya_kuliah=submit_in.sumber_biaya_kuliah,
keeratan_bidang_studi=submit_in.hubungan_studi,
tingkat_pendidikan_tepat=submit_in.tingkat_pendidikan_tepat,
data_extra=json.dumps(submit_in.data_extra, ensure_ascii=False) if submit_in.data_extra else None,
)
db.add(tr)
db.commit()
db.refresh(new_alumni)
return format_response(
True,
"Jawaban Anda telah direkam. Terima kasih atas partisipasi Anda dalam Tracer Study JTI Polije.",
{"action": "create", "nim": new_alumni.nim, "nama": new_alumni.nama, "email": new_alumni.email}
)