355 lines
16 KiB
Python
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}
|
|
)
|
|
|