diff --git a/app/db.py b/app/db.py index 9a7ff39..76b7543 100644 --- a/app/db.py +++ b/app/db.py @@ -185,6 +185,16 @@ def init_db() -> None: updated_by TEXT NOT NULL )""" ) + conn.execute( + """CREATE TABLE IF NOT EXISTS nilai_arsip_hapus ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + mk_kode TEXT NOT NULL, + tahun TEXT NOT NULL, + data TEXT NOT NULL, + dihapus_at TEXT NOT NULL, + dihapus_oleh TEXT NOT NULL + )""" + ) conn.execute( """CREATE TABLE IF NOT EXISTS pengaturan ( kunci TEXT PRIMARY KEY, @@ -292,6 +302,19 @@ def simpan_dok(tabel: str, mk_kode: str, data: dict, status: str, updated_by: st ) +def arsipkan_nilai_hapus(mk_kode: str, tahun: str, data: dict, dihapus_oleh: str) -> None: + """Simpan salinan nilai sebelum dihapus (jaring pengaman) — tak ada UI + pemulihan otomatis, tapi baris ini tetap ikut ke-backup lewat /cadangan.db + dan bisa diambil manual bila suatu saat perlu dipulihkan.""" + now = datetime.now(WIB).strftime("%Y-%m-%d %H:%M") + with get_conn() as conn: + conn.execute( + """INSERT INTO nilai_arsip_hapus (mk_kode, tahun, data, dihapus_at, dihapus_oleh) + VALUES (?,?,?,?,?)""", + (mk_kode, tahun, json.dumps(data, ensure_ascii=False), now, dihapus_oleh), + ) + + def hapus_dok(tabel: str, mk_kode: str, tahun: str | None = None) -> None: assert tabel in _TABEL_DOK tahun = tahun or tahun_aktif() @@ -362,11 +385,11 @@ def dump_semua() -> dict: """Seluruh isi database sebagai dict (untuk cadangan JSON).""" out = {} with get_conn() as conn: - for tabel in (*_TABEL_DOK, "rps_arsip", "pengaturan", + for tabel in (*_TABEL_DOK, "rps_arsip", "nilai_arsip_hapus", "pengaturan", "pengguna", "penugasan", "mahasiswa"): rows = conn.execute(f"SELECT * FROM {tabel}").fetchall() out[tabel] = [dict(r) for r in rows] - for tabel in _TABEL_DOK + ("rps_arsip",): + for tabel in _TABEL_DOK + ("rps_arsip", "nilai_arsip_hapus"): for r in out.get(tabel, []): r["data"] = json.loads(r["data"]) return out diff --git a/app/main.py b/app/main.py index b1b7488..aba9c35 100644 --- a/app/main.py +++ b/app/main.py @@ -1165,10 +1165,19 @@ async def simpan_nilai(request: Request, kode: str): return _konteks_nilai(request, kode, peringatan=[ "Anda hanya diampukan sebagian kelas MK ini — menghapus seluruh nilai " "hanya bisa dilakukan kaprodi atau dosen yang mengampu seluruh kelas."]) + akun = db.pengguna_by_id(request.state.user["id"]) + password = (form.get("password") or "").strip() + if not akun or not auth.verifikasi_password(password, akun["pw_hash"]): + return _konteks_nilai(request, kode, peringatan=[ + "Password salah — nilai TIDAK jadi dihapus."]) + lama = db.ambil_dok("nilai", kode) + if lama: + db.arsipkan_nilai_hapus(kode, db.tahun_aktif(), lama["data"], updated_by) db.hapus_dok("nilai", kode) return _konteks_nilai( request, kode, - pesan="Seluruh nilai tersimpan MK ini dihapus — silakan mulai dari awal.") + pesan="Seluruh nilai tersimpan MK ini dihapus (salinan sebelumnya " + "tersimpan otomatis sebagai arsip) — silakan mulai dari awal.") if aksi == "komponen": kelas_dosen = _kelas_dosen(request.state.user, kode) diff --git a/app/templates/form_nilai.html b/app/templates/form_nilai.html index fba2cbc..c76f6b5 100644 --- a/app/templates/form_nilai.html +++ b/app/templates/form_nilai.html @@ -54,15 +54,21 @@ {% endif %} {% if tersimpan %} + {% set n_mhs = tersimpan.data.mahasiswa|length if tersimpan.data.mode == 'per_mahasiswa' else none %}
Data tersimpan: mode - {% if tersimpan.data.mode == 'per_mahasiswa' %}PER MAHASISWA ({{ tersimpan.data.mahasiswa|length }} mahasiswa){% else %}BUKTI AGREGAT (rata-rata kelas){% endif %} + {% if tersimpan.data.mode == 'per_mahasiswa' %}PER MAHASISWA ({{ n_mhs }} mahasiswa){% else %}BUKTI AGREGAT (rata-rata kelas){% endif %} — terakhir disimpan {{ tersimpan.updated_at }}{% if tersimpan.updated_by %} oleh {{ tersimpan.updated_by }}{% endif %}. {% if riwayat_ringkas %}
Ringkasan pengisian — team teaching: {% for k in riwayat_ringkas %}{{ k.komponen }}: {% for d in k.per_dosen %}{{ d.n }}× {{ d.nama }}{{ ', ' if not loop.last }}{% endfor %}{{ ' · ' if not loop.last }}{% endfor %}.{% endif %} -
-
@@ -90,6 +96,11 @@ {% endif %} {% set ada_kelas = grid and grid|selectattr('kelas')|list|length > 0 %} + + {% if grid and tabel_komponen and komponen_daftar %} +

NILAI PER KOMPONEN — nilai mahasiswa berdasarkan komponen

+

Angka = rata-rata skor mentah tiap sesi/tugas komponen itu yang diikuti mahasiswa (bukan lewat Sub-CPMK). Ketik untuk menimpa — + ditulis rata ke semua Sub-CPMK komponen itu; rincian per Sub-CPMK tetap presisi di grid bawah.

{% if ada_kelas %} {% set semua_kelas = grid|map(attribute='kelas')|select|unique|sort|list %}
@@ -103,11 +114,6 @@
{% endif %} - - {% if grid and tabel_komponen and komponen_daftar %} -

NILAI PER KOMPONEN — nilai mahasiswa berdasarkan komponen

-

Angka = rata-rata skor mentah tiap sesi/tugas komponen itu yang diikuti mahasiswa (bukan lewat Sub-CPMK). Ketik untuk menimpa — - ditulis rata ke semua Sub-CPMK komponen itu; rincian per Sub-CPMK tetap presisi di grid bawah.

@@ -155,6 +161,19 @@ {% if grid %} {% set per_mhs = tersimpan and tersimpan.data.mode == 'per_mahasiswa' %}

NILAI PER MAHASISWA — nilai mahasiswa berdasarkan Sub-CPMK

+ {% if ada_kelas and not (tabel_komponen and komponen_daftar) %} + {% set semua_kelas = grid|map(attribute='kelas')|select|unique|sort|list %} +
+ + + +
+ {% endif %}
{% set n_kiri = 4 if ada_kelas else 3 %}