Merge branch 'fitur/konfirmasi-hapus-nilai' into main
This commit is contained in:
@@ -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
|
||||
|
||||
+10
-1
@@ -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)
|
||||
|
||||
@@ -54,15 +54,21 @@
|
||||
{% endif %}
|
||||
|
||||
{% if tersimpan %}
|
||||
{% set n_mhs = tersimpan.data.mahasiswa|length if tersimpan.data.mode == 'per_mahasiswa' else none %}
|
||||
<div class="info">
|
||||
Data tersimpan: mode
|
||||
<b>{% if tersimpan.data.mode == 'per_mahasiswa' %}PER MAHASISWA ({{ tersimpan.data.mahasiswa|length }} mahasiswa){% else %}BUKTI AGREGAT (rata-rata kelas){% endif %}</b>
|
||||
<b>{% if tersimpan.data.mode == 'per_mahasiswa' %}PER MAHASISWA ({{ n_mhs }} mahasiswa){% else %}BUKTI AGREGAT (rata-rata kelas){% endif %}</b>
|
||||
— terakhir disimpan {{ tersimpan.updated_at }}{% if tersimpan.updated_by %} oleh {{ tersimpan.updated_by }}{% endif %}.
|
||||
{% if riwayat_ringkas %}<br>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 %}
|
||||
<form method="post" action="/nilai/{{ peta.kode }}" style="display:inline;margin-left:8px"
|
||||
onsubmit="return confirm('Hapus SELURUH nilai tersimpan MK ini untuk {{ tahun }}? Tindakan ini tidak bisa dibatalkan.')">
|
||||
<button class="tombol garis" type="submit" name="aksi" value="hapus"
|
||||
<form method="post" action="/nilai/{{ peta.kode }}" style="display:flex;align-items:center;gap:8px;margin-top:8px;flex-wrap:wrap"
|
||||
onsubmit="return confirm('Hapus SELURUH nilai{% if n_mhs %} {{ n_mhs }} mahasiswa{% endif %} tersimpan MK ini untuk {{ tahun }}?\n\nSalinan sebelumnya akan diarsipkan otomatis, tapi tidak ada tombol pulihkan di aplikasi — hubungi kaprodi/admin bila perlu dikembalikan.\n\nTindakan ini tidak bisa dibatalkan sendiri. Lanjutkan?')">
|
||||
<input type="hidden" name="aksi" value="hapus">
|
||||
<label style="font-size:13px;color:var(--grey)">Konfirmasi password Anda:
|
||||
<input type="password" name="password" required autocomplete="current-password"
|
||||
style="display:inline-block;width:auto;padding:3px 6px;font-size:13px;margin-left:4px">
|
||||
</label>
|
||||
<button class="tombol garis" type="submit"
|
||||
style="color:var(--red);border-color:var(--red);padding:2px 10px;font-size:13px">Mulai Ulang (hapus semua nilai)</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -90,6 +96,11 @@
|
||||
{% endif %}
|
||||
|
||||
{% set ada_kelas = grid and grid|selectattr('kelas')|list|length > 0 %}
|
||||
|
||||
{% if grid and tabel_komponen and komponen_daftar %}
|
||||
<h2 class="bagian">NILAI PER KOMPONEN <span style="font-weight:normal;font-style:italic;color:var(--grey)">— nilai mahasiswa berdasarkan komponen</span></h2>
|
||||
<p class="keterangan">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.</p>
|
||||
{% if ada_kelas %}
|
||||
{% set semua_kelas = grid|map(attribute='kelas')|select|unique|sort|list %}
|
||||
<div class="filter-baris">
|
||||
@@ -103,11 +114,6 @@
|
||||
<span id="filter-kelas-info" class="keterangan" style="margin:0"></span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if grid and tabel_komponen and komponen_daftar %}
|
||||
<h2 class="bagian">NILAI PER KOMPONEN <span style="font-weight:normal;font-style:italic;color:var(--grey)">— nilai mahasiswa berdasarkan komponen</span></h2>
|
||||
<p class="keterangan">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.</p>
|
||||
<form method="post" action="/nilai/{{ peta.kode }}">
|
||||
<input type="hidden" name="aksi" value="komponen">
|
||||
<div class="gulir">
|
||||
@@ -155,6 +161,19 @@
|
||||
{% if grid %}
|
||||
{% set per_mhs = tersimpan and tersimpan.data.mode == 'per_mahasiswa' %}
|
||||
<h2 class="bagian">NILAI PER MAHASISWA <span style="font-weight:normal;font-style:italic;color:var(--grey)">— nilai mahasiswa berdasarkan Sub-CPMK</span></h2>
|
||||
{% if ada_kelas and not (tabel_komponen and komponen_daftar) %}
|
||||
{% set semua_kelas = grid|map(attribute='kelas')|select|unique|sort|list %}
|
||||
<div class="filter-baris">
|
||||
<label for="filter-kelas">Tampilkan kelas:</label>
|
||||
<select id="filter-kelas" style="width:auto">
|
||||
<option value="">Semua</option>
|
||||
{% for k in semua_kelas %}
|
||||
<option value="{{ k }}">Kelas {{ k }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<span id="filter-kelas-info" class="keterangan" style="margin:0"></span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="post" action="/nilai/{{ peta.kode }}">
|
||||
<div class="gulir">
|
||||
{% set n_kiri = 4 if ada_kelas else 3 %}
|
||||
|
||||
Reference in New Issue
Block a user