feat(student-management): kelola siswa round-trip CSV + bulk delete (PG)
- Export siswa terpilih/seluruh + progress ke CSV round-trip (token tidak
pernah diekspor; teacher tidak ikut; urutan deterministik)
- Import preview all-or-nothing: siswa existing di-restore (nama+progress,
token lama dipertahankan), siswa baru dibuat dari student_id kosong +
token; conflict tidak menyebut raw token/digest
- Bulk delete multi-seleksi dengan validasi ketat (teacher terlindungi,
zero-delete bila ada ID bermasalah)
- fix(import): deteksi delimiter berbasis schema — menerima titik koma
(;) maupun koma (,), ekspor tetap ';' + BOM; header tab/campuran ditolak
dengan pesan jelas (regresi: file Excel/Sheets kini bisa diimpor)
- refactor: progress_status.py — kontrak status legacy tunggal dipakai
storage PG, export, dan import; report siswa hanya role student
- UI /progress: dialog import/export + bulk delete, selection store,
respon import tanpa raw token; APP_VERSION 22 -> 26
- ops: ORIGIN env var di podman-compose.yml
- docs: README section 9 (workflow round-trip + delimiter) dan
docs/11-database-migration (kelola siswa via round-trip CSV)
- test: parser round-trip (45), repositories, route management (24,
PostgreSQL), frontend 91, fixtures CSV valid/invalid
2026-08-09 07:29:19 +07:00
|
|
|
"""
|
|
|
|
|
Parsing/format status progress — kontrak legacy tunggal yang dipakai UI, export,
|
|
|
|
|
import, dan storage backend PostgreSQL:
|
|
|
|
|
|
|
|
|
|
- belum mulai : kosong ("") atau "not_started"
|
|
|
|
|
- selesai : "completed"
|
|
|
|
|
- skor : "<earned>/<total>", misalnya "3/4"
|
|
|
|
|
|
|
|
|
|
Helper ini dipakai oleh importer round-trip (student_roundtrip) DAN storage
|
|
|
|
|
backend agar format status tidak divergen antar komponen.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
class ParsedProgress:
|
|
|
|
|
"""Status progress hasil parse (hanya state non-not_started yang dipertahankan)."""
|
|
|
|
|
|
|
|
|
|
state: str # 'completed' | 'scored'
|
|
|
|
|
score_earned: int | None = None
|
|
|
|
|
score_total: int | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_progress_status(value: str) -> tuple[str, int | None, int | None]:
|
|
|
|
|
"""Parse status legacy → (state, score_earned, score_total).
|
|
|
|
|
|
|
|
|
|
- "" / "not_started" → ("not_started", None, None)
|
|
|
|
|
- "completed" → ("completed", None, None)
|
2026-08-29 00:17:48 +07:00
|
|
|
- "done" → ("done", None, None)
|
|
|
|
|
- "in_progress" → ("in_progress", None, None)
|
feat(student-management): kelola siswa round-trip CSV + bulk delete (PG)
- Export siswa terpilih/seluruh + progress ke CSV round-trip (token tidak
pernah diekspor; teacher tidak ikut; urutan deterministik)
- Import preview all-or-nothing: siswa existing di-restore (nama+progress,
token lama dipertahankan), siswa baru dibuat dari student_id kosong +
token; conflict tidak menyebut raw token/digest
- Bulk delete multi-seleksi dengan validasi ketat (teacher terlindungi,
zero-delete bila ada ID bermasalah)
- fix(import): deteksi delimiter berbasis schema — menerima titik koma
(;) maupun koma (,), ekspor tetap ';' + BOM; header tab/campuran ditolak
dengan pesan jelas (regresi: file Excel/Sheets kini bisa diimpor)
- refactor: progress_status.py — kontrak status legacy tunggal dipakai
storage PG, export, dan import; report siswa hanya role student
- UI /progress: dialog import/export + bulk delete, selection store,
respon import tanpa raw token; APP_VERSION 22 -> 26
- ops: ORIGIN env var di podman-compose.yml
- docs: README section 9 (workflow round-trip + delimiter) dan
docs/11-database-migration (kelola siswa via round-trip CSV)
- test: parser round-trip (45), repositories, route management (24,
PostgreSQL), frontend 91, fixtures CSV valid/invalid
2026-08-09 07:29:19 +07:00
|
|
|
- "<earned>/<total>" → ("scored", earned, total) bila total > 0 dan
|
|
|
|
|
0 <= earned <= total
|
|
|
|
|
- selain itu → raise ValueError (status tidak dikenal)
|
|
|
|
|
"""
|
|
|
|
|
v = (value or "").strip()
|
|
|
|
|
if v in ("", "not_started"):
|
|
|
|
|
return ("not_started", None, None)
|
|
|
|
|
if v == "completed":
|
|
|
|
|
return ("completed", None, None)
|
2026-08-29 00:17:48 +07:00
|
|
|
if v == "done":
|
|
|
|
|
return ("done", None, None)
|
|
|
|
|
if v == "in_progress":
|
|
|
|
|
return ("in_progress", None, None)
|
feat(student-management): kelola siswa round-trip CSV + bulk delete (PG)
- Export siswa terpilih/seluruh + progress ke CSV round-trip (token tidak
pernah diekspor; teacher tidak ikut; urutan deterministik)
- Import preview all-or-nothing: siswa existing di-restore (nama+progress,
token lama dipertahankan), siswa baru dibuat dari student_id kosong +
token; conflict tidak menyebut raw token/digest
- Bulk delete multi-seleksi dengan validasi ketat (teacher terlindungi,
zero-delete bila ada ID bermasalah)
- fix(import): deteksi delimiter berbasis schema — menerima titik koma
(;) maupun koma (,), ekspor tetap ';' + BOM; header tab/campuran ditolak
dengan pesan jelas (regresi: file Excel/Sheets kini bisa diimpor)
- refactor: progress_status.py — kontrak status legacy tunggal dipakai
storage PG, export, dan import; report siswa hanya role student
- UI /progress: dialog import/export + bulk delete, selection store,
respon import tanpa raw token; APP_VERSION 22 -> 26
- ops: ORIGIN env var di podman-compose.yml
- docs: README section 9 (workflow round-trip + delimiter) dan
docs/11-database-migration (kelola siswa via round-trip CSV)
- test: parser round-trip (45), repositories, route management (24,
PostgreSQL), frontend 91, fixtures CSV valid/invalid
2026-08-09 07:29:19 +07:00
|
|
|
if "/" in v:
|
|
|
|
|
parts = v.split("/")
|
|
|
|
|
if len(parts) == 2:
|
|
|
|
|
try:
|
|
|
|
|
earned, total = int(parts[0]), int(parts[1])
|
|
|
|
|
except ValueError:
|
|
|
|
|
earned = total = None
|
|
|
|
|
if (
|
|
|
|
|
earned is not None
|
|
|
|
|
and total is not None
|
|
|
|
|
and total > 0
|
|
|
|
|
and 0 <= earned <= total
|
|
|
|
|
):
|
|
|
|
|
return ("scored", earned, total)
|
|
|
|
|
raise ValueError(f"status progress tidak dikenal: {v!r}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def format_progress_status(progress) -> str:
|
|
|
|
|
"""Render status ke string kontrak legacy.
|
|
|
|
|
|
|
|
|
|
Menerima None, ParsedProgress, tuple (state, earned, total), atau objek
|
|
|
|
|
dengan atribut state/score_earned/score_total (mis. model StudentProgress).
|
|
|
|
|
|
|
|
|
|
- None → ""
|
2026-08-29 00:17:48 +07:00
|
|
|
- done → persen komposit dibulatkan bila ada, else "done"
|
|
|
|
|
- in_progress → "" (harus kosong: tidak dihitung prasyarat lengkap)
|
feat(student-management): kelola siswa round-trip CSV + bulk delete (PG)
- Export siswa terpilih/seluruh + progress ke CSV round-trip (token tidak
pernah diekspor; teacher tidak ikut; urutan deterministik)
- Import preview all-or-nothing: siswa existing di-restore (nama+progress,
token lama dipertahankan), siswa baru dibuat dari student_id kosong +
token; conflict tidak menyebut raw token/digest
- Bulk delete multi-seleksi dengan validasi ketat (teacher terlindungi,
zero-delete bila ada ID bermasalah)
- fix(import): deteksi delimiter berbasis schema — menerima titik koma
(;) maupun koma (,), ekspor tetap ';' + BOM; header tab/campuran ditolak
dengan pesan jelas (regresi: file Excel/Sheets kini bisa diimpor)
- refactor: progress_status.py — kontrak status legacy tunggal dipakai
storage PG, export, dan import; report siswa hanya role student
- UI /progress: dialog import/export + bulk delete, selection store,
respon import tanpa raw token; APP_VERSION 22 -> 26
- ops: ORIGIN env var di podman-compose.yml
- docs: README section 9 (workflow round-trip + delimiter) dan
docs/11-database-migration (kelola siswa via round-trip CSV)
- test: parser round-trip (45), repositories, route management (24,
PostgreSQL), frontend 91, fixtures CSV valid/invalid
2026-08-09 07:29:19 +07:00
|
|
|
- scored → "<earned>/<total>"
|
|
|
|
|
- completed → "completed"
|
|
|
|
|
- not_started → "not_started"
|
2026-08-29 00:17:48 +07:00
|
|
|
- lainnya → state itu sendiri
|
feat(student-management): kelola siswa round-trip CSV + bulk delete (PG)
- Export siswa terpilih/seluruh + progress ke CSV round-trip (token tidak
pernah diekspor; teacher tidak ikut; urutan deterministik)
- Import preview all-or-nothing: siswa existing di-restore (nama+progress,
token lama dipertahankan), siswa baru dibuat dari student_id kosong +
token; conflict tidak menyebut raw token/digest
- Bulk delete multi-seleksi dengan validasi ketat (teacher terlindungi,
zero-delete bila ada ID bermasalah)
- fix(import): deteksi delimiter berbasis schema — menerima titik koma
(;) maupun koma (,), ekspor tetap ';' + BOM; header tab/campuran ditolak
dengan pesan jelas (regresi: file Excel/Sheets kini bisa diimpor)
- refactor: progress_status.py — kontrak status legacy tunggal dipakai
storage PG, export, dan import; report siswa hanya role student
- UI /progress: dialog import/export + bulk delete, selection store,
respon import tanpa raw token; APP_VERSION 22 -> 26
- ops: ORIGIN env var di podman-compose.yml
- docs: README section 9 (workflow round-trip + delimiter) dan
docs/11-database-migration (kelola siswa via round-trip CSV)
- test: parser round-trip (45), repositories, route management (24,
PostgreSQL), frontend 91, fixtures CSV valid/invalid
2026-08-09 07:29:19 +07:00
|
|
|
"""
|
|
|
|
|
if progress is None:
|
|
|
|
|
return ""
|
|
|
|
|
if isinstance(progress, tuple):
|
|
|
|
|
state, earned, total = progress
|
|
|
|
|
else:
|
|
|
|
|
state = progress.state
|
|
|
|
|
earned = getattr(progress, "score_earned", None)
|
|
|
|
|
total = getattr(progress, "score_total", None)
|
2026-08-29 00:17:48 +07:00
|
|
|
if state == "in_progress":
|
|
|
|
|
return ""
|
|
|
|
|
if state == "done":
|
|
|
|
|
composite = getattr(progress, "composite_percent", None)
|
|
|
|
|
if composite is not None:
|
|
|
|
|
return f"{round(composite)}"
|
|
|
|
|
return "done"
|
feat(student-management): kelola siswa round-trip CSV + bulk delete (PG)
- Export siswa terpilih/seluruh + progress ke CSV round-trip (token tidak
pernah diekspor; teacher tidak ikut; urutan deterministik)
- Import preview all-or-nothing: siswa existing di-restore (nama+progress,
token lama dipertahankan), siswa baru dibuat dari student_id kosong +
token; conflict tidak menyebut raw token/digest
- Bulk delete multi-seleksi dengan validasi ketat (teacher terlindungi,
zero-delete bila ada ID bermasalah)
- fix(import): deteksi delimiter berbasis schema — menerima titik koma
(;) maupun koma (,), ekspor tetap ';' + BOM; header tab/campuran ditolak
dengan pesan jelas (regresi: file Excel/Sheets kini bisa diimpor)
- refactor: progress_status.py — kontrak status legacy tunggal dipakai
storage PG, export, dan import; report siswa hanya role student
- UI /progress: dialog import/export + bulk delete, selection store,
respon import tanpa raw token; APP_VERSION 22 -> 26
- ops: ORIGIN env var di podman-compose.yml
- docs: README section 9 (workflow round-trip + delimiter) dan
docs/11-database-migration (kelola siswa via round-trip CSV)
- test: parser round-trip (45), repositories, route management (24,
PostgreSQL), frontend 91, fixtures CSV valid/invalid
2026-08-09 07:29:19 +07:00
|
|
|
if state == "scored":
|
|
|
|
|
return f"{earned}/{total}"
|
|
|
|
|
return state
|