Tambah asesmen per komponen, batasi Dasbor/Rekap ke kaprodi, penugasan per kelas
- Halaman nilai: tombol isi-cepat per komponen penilaian (bagian G RPS) yang menyalin nilai ke seluruh Sub-CPMK terkait; komponen berulang (Kuis, Tugas, Presentasi, Tes Lisan) mendukung banyak sesi dengan rata-rata otomatis, komponen sekali-jalan (UTS/UAS) mengedit sesi yang sama. Sub-CPMK yang diukur >1 komponen digulung rata tertimbang bobot (hitung.skor_dari_sesi). - Hapus jalur unggah Excel & mode "ketik rata-rata kelas" (agregat) dari input nilai — proses diseragamkan: hanya grid manual + tombol komponen, keduanya bersumber dari daftar mahasiswa impor kaprodi. - Dasbor Monitoring, Rekap CPL/CPMK, dan Riwayat kini khusus kaprodi (dosen tidak lagi melihat rollup lintas-MK yang bukan miliknya). - Penugasan dosen ke MK bisa dibatasi per kelas (kolom kelas baru di tabel penugasan, migrasi otomatis & aman untuk DB yang sudah berjalan) — dosen MK dengan banyak kelas paralel hanya melihat & menilai kelasnya sendiri; hapus nilai MK penuh dikunci untuk dosen yang dibatasi kelas.
This commit is contained in:
@@ -138,6 +138,15 @@ def _migrasi_role_pengguna(conn: sqlite3.Connection) -> None:
|
||||
conn.execute("DROP TABLE pengguna_lama")
|
||||
|
||||
|
||||
def _migrasi_kelas_penugasan(conn: sqlite3.Connection) -> None:
|
||||
"""Skema lama penugasan hanya (user_id, mk_kode) — tambah kolom kelas
|
||||
(kosong = seluruh kelas MK ini, dipakai dosen_rps_mk; terisi = dosen_mk
|
||||
yang hanya mengampu kelas tertentu)."""
|
||||
kolom = {r["name"] for r in conn.execute("PRAGMA table_info(penugasan)")}
|
||||
if "kelas" not in kolom:
|
||||
conn.execute("ALTER TABLE penugasan ADD COLUMN kelas TEXT NOT NULL DEFAULT ''")
|
||||
|
||||
|
||||
def init_db() -> None:
|
||||
with get_conn() as conn:
|
||||
for tabel in ("rps", "portofolio", "nilai"):
|
||||
@@ -195,6 +204,7 @@ def init_db() -> None:
|
||||
PRIMARY KEY (user_id, mk_kode)
|
||||
)"""
|
||||
)
|
||||
_migrasi_kelas_penugasan(conn)
|
||||
conn.execute(
|
||||
"""CREATE TABLE IF NOT EXISTS mahasiswa (
|
||||
npm TEXT NOT NULL,
|
||||
@@ -391,12 +401,15 @@ def daftar_pengguna() -> list[dict]:
|
||||
"ORDER BY role, nama"
|
||||
).fetchall()
|
||||
tugas = conn.execute(
|
||||
"SELECT user_id, mk_kode FROM penugasan ORDER BY mk_kode"
|
||||
"SELECT user_id, mk_kode, kelas FROM penugasan ORDER BY mk_kode"
|
||||
).fetchall()
|
||||
per_user: dict[int, list[str]] = {}
|
||||
per_user_kode: dict[int, list[str]] = {}
|
||||
per_user_kelas: dict[int, dict[str, str]] = {}
|
||||
for t in tugas:
|
||||
per_user.setdefault(t["user_id"], []).append(t["mk_kode"])
|
||||
return [{**dict(r), "mk": per_user.get(r["id"], [])} for r in rows]
|
||||
per_user_kode.setdefault(t["user_id"], []).append(t["mk_kode"])
|
||||
per_user_kelas.setdefault(t["user_id"], {})[t["mk_kode"]] = t["kelas"] or ""
|
||||
return [{**dict(r), "mk": per_user_kode.get(r["id"], []),
|
||||
"kelas_map": per_user_kelas.get(r["id"], {})} for r in rows]
|
||||
|
||||
|
||||
def hapus_pengguna(user_id: int) -> None:
|
||||
@@ -413,12 +426,14 @@ def ubah_password(user_id: int, pw_hash: str) -> None:
|
||||
conn.execute("DELETE FROM sesi WHERE user_id=?", (user_id,))
|
||||
|
||||
|
||||
def set_penugasan(user_id: int, kode_list: list[str]) -> None:
|
||||
def set_penugasan(user_id: int, penugasan: list[tuple[str, str]]) -> None:
|
||||
"""penugasan = [(mk_kode, kelas), ...] — kelas '' berarti seluruh kelas MK
|
||||
ini; boleh CSV ('A,B') bila dosen mengampu beberapa kelas MK yang sama."""
|
||||
with get_conn() as conn:
|
||||
conn.execute("DELETE FROM penugasan WHERE user_id=?", (user_id,))
|
||||
conn.executemany(
|
||||
"INSERT OR IGNORE INTO penugasan (user_id, mk_kode) VALUES (?,?)",
|
||||
[(user_id, k) for k in kode_list],
|
||||
"INSERT OR IGNORE INTO penugasan (user_id, mk_kode, kelas) VALUES (?,?,?)",
|
||||
[(user_id, k, kelas) for k, kelas in penugasan],
|
||||
)
|
||||
|
||||
|
||||
@@ -430,6 +445,22 @@ def mk_diampu(user_id: int) -> set[str]:
|
||||
return {r["mk_kode"] for r in rows}
|
||||
|
||||
|
||||
def kelas_diampu(user_id: int, mk_kode: str) -> set[str] | None:
|
||||
"""Kelas yang diizinkan dosen ini untuk MK tsb. None = seluruh kelas
|
||||
(tak dibatasi); set kosong = tidak ditugaskan sama sekali ke MK ini."""
|
||||
with get_conn() as conn:
|
||||
row = conn.execute(
|
||||
"SELECT kelas FROM penugasan WHERE user_id=? AND mk_kode=?",
|
||||
(user_id, mk_kode),
|
||||
).fetchone()
|
||||
if not row:
|
||||
return set()
|
||||
kelas = (row["kelas"] or "").strip()
|
||||
if not kelas:
|
||||
return None
|
||||
return {k.strip() for k in kelas.split(",") if k.strip()}
|
||||
|
||||
|
||||
def pengampu_per_mk() -> dict[str, list[str]]:
|
||||
"""{mk_kode: [nama dosen, ...]} — untuk kolom pengampu di beranda."""
|
||||
with get_conn() as conn:
|
||||
|
||||
Reference in New Issue
Block a user