Lewati ke isi

SPMB Flow — Audit & Refactor Plan (Apr 2026)

Status: Audit complete + Opsi A executed (Apr 29, 2026). Wizard dihapus, Detail UI menjadi canonical, dead code dipangkas, kontrak route dilindungi unit test. Lihat §10 untuk ringkasan eksekusi. Scope: pendaftar portal /spmb/* di repo scola-fe-v2. Admin SPMB (/spmb-admin/*) di luar scope.

1. Latar

Fitur SPMB sudah melalui beberapa perombakan. User report (Apr 2026):

  1. Klik "Buka Upload Dokumen" di tab Detail menampilkan halaman "Sedang Dalam Pemeliharaan".
  2. Tab "Pembayaran & Hasil" perannya tidak jelas.
  3. Tombol "Submit Sekarang" + "Simpan Perubahan" muncul di semua tab.
  4. UI wizard pertama-kali berbeda dari UI detail.

Audit memetakan aset SPMB → identifikasi dead code & redundansi → proposal refactor terkonsolidasi.

2. Inventaris

2.1 Routes terdaftar (/Users/salfath/scola-fe-v2/src/router/spmbRoutes.js)

Path Component Auth Catatan
/spmb Public/SPMBLanding.vue no landing
/spmb/status Public/SPMBStatusCheck.vue no cek status by NISN
/spmb/statistics Public/SPMBStatistics.vue no statistik publik
/spmb/verify-email Public/SPMBVerifyEmail.vue no OTP
/spmb/login SPMB/PendaftarLogin.vue no login
/spmb/register SPMB/PendaftarRegister.vue no daftar akun
/spmb/wizard SPMB/SPMBWizard.vue yes wizard 5-step (1448 LOC)
/spmb/admission/:id Public/SPMBAdmissionDetail.vue yes detail post-create (989 LOC)
/spmb/admission/:id/enrollment Public/SPMBDaftarUlang.vue yes daftar ulang

2.2 View files orphan (eksis tapi TIDAK terdaftar)

File Refs Status
Public/SPMBDocuments.vue (338 LOC) 0* orphan
Public/SPMBAchievements.vue (482 LOC) 0 orphan
Public/SPMBComplaint.vue (453 LOC) 0 orphan
Public/SPMBTestSchedule.vue (292 LOC) 0* orphan
Public/SPMBRegisterList.vue 0 orphan
Public/SPMBCBTStart.vue 1 dipakai dari SPMBTestSchedule.vue (juga orphan) → de-facto orphan
_archive/SPMBAdmissionCreate.vue 0 archive
_archive/SPMBDashboard.vue 0 archive
_archive/SPMBLogin.vue 0 archive
_archive/SPMBRegister.vue 0 archive

* = direferensikan hanya sebagai komentar // from SPMBDocuments.vue di SPMBWizard.vue / spmb.store.js, bukan import nyata.

Akar bug user item #1: tombol goToDocuments, goToAchievements, goToComplaints, goToTests di Public/SPMBAdmissionDetail.vue:283-286 route ke /spmb/admission/:id/{documents,achievements,complaints,tests} — semua 404, jatuh ke NotFoundView ("Sedang Dalam Pemeliharaan").

2.3 Komponen src/components/spmb/

Komponen Refs Status
SPMBNotificationBell.vue 9 dipakai luas
SPMBStatusBanner.vue 1 hanya SPMBAdmissionDetail
SPMBProgressTimeline.vue 1 hanya SPMBAdmissionDetail
SPMBCountdownTimer.vue 2 Detail + DaftarUlang
SPMBActivityTimeline.vue 0 dead

2.4 Wizard internal (src/views/SPMB/)

SPMBWizard.vue (1448 LOC) berisi 5 step inline: - Step 1: Data Pribadi (form lengkap, mirror Detail tab Profil) - Step 2: Dokumen (upload base64; logic disalin dari SPMBDocuments.vue) - Step 3: Pembayaran (pending/paid/verified UI) - Step 4: Jadwal Tes (logic disalin dari SPMBTestSchedule.vue) - Step 5: Hasil (pengumuman)

Di onMounted, kalau application.id ada → redirect ke /spmb/admission/:id. Artinya wizard hanya aktif untuk pre-create flow (sampai user klik "Buat Pendaftaran" di akhir step 1). Setelah itu dia tak pernah dipakai user yang sama lagi.

Sub-komponen wizard (semua dipakai hanya oleh SPMBWizard.vue): - SPMB/components/Stepper.vue - SPMB/components/ChecklistSidebar.vue - SPMB/components/SPMBHelpModal.vue

2.5 Service methods (src/services/spmb/spmb.service.js)

37 method total. Yang 0 referensi = dead:

  • getAdmissionActivities
  • getComplaintDetail
  • getRegistersPublic
  • getResult
  • getScholarshipTypes
  • reapplyAdmission

Method dengan 1 referensi mostly dipakai langsung dari SPMBAdmissionDetail / SPMBWizard (sehat).

2.6 Store (src/stores/spmb.store.js, 539 LOC)

Action duplikat / overlap dengan SPMBService: - uploadDocumentBase64 (store) vs uploadDocument (service) — store versi memanggil service-nya lewat aliased duplicate. - getDocuments (store) wraps SPMBService.getDocuments tanpa nilai tambah signifikan. - Banyak action store (e.g. getPaymentStatus, createPayment, confirmEnrollment, getResult) hanya dipakai di SPMBWizard.vue → kalau wizard dipangkas, action ini ikut dead.

calculateStepFromState mapping: | state | step | |--------------|------| | draft | 1 | | submit | 2 | | confirm | 3 | | application | 4 | | admission | 4 | | done | 5 |

(Sumber: @/Users/salfath/scola-fe-v2/src/constants/spmb.constants.js:44-51.)

2.7 State machine op.admission (BE → FE labels)

state label Detail UI behavior
draft Draft form editable; Submit aktif
submit Disubmit read-only; Print Bukti tampil
confirm Dikonfirmasi read-only
application Proses Seleksi (*) read-only; tampilkan jadwal tes (*)
admission Diterima read-only
done Selesai read-only; tampilkan akun siswa+orang tua
reject Ditolak read-only
pending Cadangan read-only
cancel Dibatalkan read-only

(*) state application ada di BE & constants, tidak ditangani stateConfig di SPMBAdmissionDetail.vue:88-97 (gap UI: badge "Proses Seleksi" jatuh ke fallback gray).

3. Issue findings

3.1 Bug & broken behavior (P0)

ID Lokasi Gejala Akar masalah
P0-1 SPMBAdmissionDetail.vue:283-286 "Sedang Dalam Pemeliharaan" saat klik tab 4 child route tidak terdaftar
P0-2 SPMBAdmissionDetail.vue:88-97 Badge tidak rapi untuk state application Mapping kurang
P0-3 SPMBAdmissionDetail.vue:281 Logout pakai router.push("/spmb/login") Tidak panggil authStore.logout → session BE tetap hidup

3.2 UX issue (P1)

ID Lokasi Issue
P1-1 SPMBAdmissionDetail.vue:869-919 Tombol "Submit" + "Simpan Perubahan" muncul di semua tab (di luar v-show)
P1-2 SPMBAdmissionDetail.vue:147 Tab "Pembayaran & Hasil" — labelnya menyesatkan (tidak ada UI bayar)
P1-3 SPMBAdmissionDetail.vue:909-919 + 924-947 "Simpan" duplikat: tombol statis + floating bar
P1-4 SPMBWizard.vue vs SPMBAdmissionDetail.vue Layout/UI berbeda jauh untuk task serupa
P1-5 SPMBAdmissionDetail.vue tab Dokumen Hanya placeholder "Buka Upload Dokumen" — tidak inline; routing-based extra navigation

3.3 Dead code (P2 — cleanup)

Files (10): - src/views/AdmissionManagement/_archive/ — 4 file - src/views/AdmissionManagement/Public/SPMBRegisterList.vue - src/views/AdmissionManagement/Public/SPMBAchievements.vue (akan dihidupkan kembali jika opsi A; lihat §5) - src/views/AdmissionManagement/Public/SPMBComplaint.vue (idem) - src/views/AdmissionManagement/Public/SPMBDocuments.vue (idem) - src/views/AdmissionManagement/Public/SPMBTestSchedule.vue (idem) - src/components/spmb/SPMBActivityTimeline.vue

Service methods (6): lihat 2.5.

Store actions ikut dievaluasi setelah keputusan §5 diambil.

3.4 Redundansi

  • Logic upload dokumen DUPLIKAT antara SPMBWizard.vue:394+ dan SPMBDocuments.vue (orphan). Wizard menyalin dari Documents, tapi Documents tidak dihapus.
  • Logic test schedule DUPLIKAT antara SPMBWizard.vue:526+ dan SPMBTestSchedule.vue (orphan).
  • spmb.store.uploadDocumentBase64 = alias SPMBService.uploadDocument.
  • Detail page tab Profil + Wizard step 1 = form yang hampir sama dengan bidang tumpang tindih (different field shapes/order).

4. Mengapa wizard & detail terpisah?

Histori (dari komentar kode + commit log): 1. Awal: serangkaian halaman terpisah per langkah (SPMBDocuments, SPMBAchievements, SPMBComplaint, SPMBTestSchedule). 2. Refactor wave 1: dilebur jadi SPMBWizard.vue 5-step SPA. 3. Refactor wave 2: tambahan SPMBAdmissionDetail.vue sebagai "canonical post-create UX" — tab placeholder mereferensi halaman lama yang tidak pernah dihapus, tapi juga tidak diregistrasi.

Hasil net: kita punya 2 UI yang sama-sama hidup (wizard + detail) + 4 halaman kerangka (Documents/Achievements/Complaints/TestSchedule) yang tidak bisa diakses dari mana pun.

5. Pilihan Refactor

Detail page jadi UI tunggal kanonik untuk SELURUH siklus pendaftaran, termasuk pre-create. Hapus wizard.

Komponen: 1. Detail diperluas: jika :id = new (atau tanpa application) → tampilkan form pre-create dengan tombol "Buat Pendaftaran". 2. Tab Dokumen, Achievements, Complaints, Tests → register sebagai child route (/spmb/admission/:id/{documents,achievements,...}) memakai komponen SPMBDocuments.vue, dst. yang sudah ada (revive, lengkapi/perbarui sesuai design system). 3. Hapus SPMB/SPMBWizard.vue + SPMB/components/Stepper.vue, ChecklistSidebar.vue, SPMBHelpModal.vue. 4. Hapus action store yang khusus wizard.

Pros: UI konsisten, halaman lama hidup kembali, dead code bersih. Cons: kerja besar; Detail page butuh kemampuan pre-create (form awal).

Opsi B — "Wizard-first" (status quo+)

Wizard tetap tuan rumah pendaftaran; Detail hanya ringkasan post-submit.

Komponen: 1. Hapus 4 halaman orphan (SPMBDocuments.vue, dll.) sepenuhnya; konten diakses lewat wizard step. 2. Detail tab "Dokumen" jadi read-only summary list (bukan placeholder "Buka Upload Dokumen"). 3. Detail tab "Pembayaran & Hasil" → split jadi tab "Status & Hasil" (tetap), card pembayaran kalau ada invoice unpaid. 4. Sederhanakan tombol: "Simpan Perubahan" hanya di tab Profil; "Submit" di tab Status (hanya state=draft).

Pros: scope kecil, low-risk. Cons: 4 file SPMBxxx.vue (1565 LOC) harus dihapus; detail page jadi ringan tapi bisa terasa "kurang interaktif" untuk fitur lanjut (achievement banyak, complaint baru, dll. — harus tetap di wizard yang sudah submit?).

Opsi C — "Hybrid" (kompromi)

Wizard untuk pre-create flow (state=draft hingga submit), Detail untuk post-submit + interaksi lanjutan (Documents, Achievements, Complaints, Tests sebagai child routes).

Komponen: 1. Tetap pertahankan wizard; redirect-ke-detail tetap berlaku setelah submit. 2. Register 4 child routes Detail. 3. Cleanup: hapus 4 file _archive, SPMBRegisterList.vue, SPMBActivityTimeline.vue, 6 service method dead. 4. Cleanup tombol di Detail: pisah per tab.

Pros: minimal disruption; user yang sudah familiar dengan wizard tetap nyaman; orphan pages hidup kembali. Cons: tetap 2 UI, tetap ada duplikasi logic upload/test (wizard vs revived pages).

6. Rekomendasi

Eksekusi Opsi C dulu (immediate fix), dengan catatan migrasi ke Opsi A sebagai milestone berikutnya bila timeline mengizinkan.

Alasan: - Memperbaiki bug user-blocking (P0-1) tanpa risiko regresi besar. - Menghapus dead code yang clearly tidak dipakai (P2). - Memperbaiki UX tombol & tab (P1-1, P1-2, P1-3). - Membuka jalan ke konsolidasi UI tunggal di milestone berikutnya.

7. Action plan Opsi C

Phase 1 — Bug fixes & route registration

  1. Register 4 child route di spmbRoutes.js: /spmb/admission/:id/{documents,achievements,complaints,tests} menunjuk ke 4 file Public/SPMBxxx.vue yang sudah ada.
  2. Tambah unit test tests/menu/spmbDetailChildRoutesComposition.spec.js memastikan 4 path ini terdaftar.
  3. Logout di Detail pakai authStore.logout() (hapus stub manual).

Phase 2 — UI cleanup di Detail

  1. Pindahkan tombol "Simpan Perubahan" ke dalam tab Profil saja (atau tinggalkan hanya floating bar — lebih bersih).
  2. Pindahkan card "Submit Sekarang" ke tab "Status & Hasil" dan tampil hanya saat state=draft.
  3. Rename label tab "Pembayaran & Hasil" → "Status & Hasil".
  4. Tambah mapping application di stateConfig.

Phase 3 — Dead code removal

  1. Hapus src/views/AdmissionManagement/_archive/ (4 file).
  2. Hapus src/views/AdmissionManagement/Public/SPMBRegisterList.vue.
  3. Hapus src/components/spmb/SPMBActivityTimeline.vue.
  4. Hapus 6 service method dead.
  5. Audit store: hapus action yang setelah Phase 1-2 jadi 0-ref.

Phase 4 — Tests

  1. Tambah unit test contract: tombol "Submit"/"Simpan" hanya muncul di tab+state yang sesuai (SPMBAdmissionDetail.spec.js).
  2. Tambah tests/contract: spmbServiceMethods.spec.js audit no-dead service method.

Phase 5 — Future (opsional, milestone berikutnya)

  • Migrasi ke Opsi A: hapus wizard, Detail jadi UI tunggal pre+post create.
  • Konsolidasi form Profil + step 1 wizard.

8. Risk register

  • R1: 4 page yang akan di-register (Documents/Achievements/Complaints/ TestSchedule) terakhir disentuh sebelum refactor wave 2 — perlu QA cepat tiap halaman setelah register. Test BE endpoint yang dipakai: getDocuments, uploadDocument, deleteDocument, getAchievements, addAchievement, updateAchievement, deleteAchievement, getMyComplaints, createComplaint, getMyTestSchedules, getMyTestResults. Sudah ada di service, sebagian sudah dipakai wizard (jadi sehat).
  • R2: Penghapusan service method dead → hindari regresi via grep ulang sebelum delete + test contract.
  • R3: Logout fix bisa berdampak ke session-related tests; jalankan unit suite full setelah perubahan.

9. Verifikasi pasca-eksekusi

  • npm run lint:diff
  • npm run check:capability-diff
  • npm run type-check
  • npx vitest run
  • npm run test:contract
  • E2E manual ringan: login pendaftar → klik tiap tab di Detail → klik setiap CTA → verifikasi tidak ada 404.

10. Ringkasan Eksekusi (Apr 29, 2026)

Opsi A (canonical Detail UI) di-execute. Highlight:

10.1 Penambahan

  • src/views/AdmissionManagement/Public/SPMBAdmissionCreate.vue — pre-create form lean (~360 LOC) menggantikan /spmb/wizard 5-step.
  • 6 route baru di spmbRoutes.js: /spmb/admission/new, /spmb/admission/:id(\\d+)/{documents, achievements,complaints,tests,enrollment}.
  • tests/menu/spmbDetailChildRoutesComposition.spec.js — kontrak route registration + meta guard (3 tests).

10.2 Penghapusan dead code

  • 9 file dihapus:
  • src/views/SPMB/SPMBWizard.vue (1448 LOC)
  • src/views/SPMB/components/ChecklistSidebar.vue
  • src/views/SPMB/components/SPMBHelpModal.vue
  • src/views/AdmissionManagement/Public/SPMBRegisterList.vue
  • src/components/spmb/SPMBActivityTimeline.vue
  • 4 file di src/views/AdmissionManagement/_archive/ (folder dihapus)
  • src/stores/spmb.store.js: 539 LOC → 239 LOC. Hapus currentStep/completedSteps state, updateStep, setCurrentStep, calculateStepFromState, createApplication, dan 11 wrapper action yang dipakai hanya wizard. State sekarang fokus auth-derived + cache application/currentPeriod.
  • src/services/spmb/spmb.service.js: hapus 9 method dead (getAdmissionActivities, getComplaintDetail, getRegistersPublic, getResult, getScholarshipTypes, reapplyAdmission, confirmPayment, getSelectionResult, checkPaymentStatus).
  • src/constants/spmb.constants.js: hapus STATE_STEP_MAP.

10.3 Perubahan navigasi

  • Welcome.vue:51, roles.js:58, PendaftarLogin.vue:60,101/spmb/wizard diganti /spmb/admission/new.
  • routerGuardPolicy.js: hapus /spmb/wizard dari array prefix; ganti jadi /spmb/admission.

10.4 UI cleanup di Detail (SPMBAdmissionDetail.vue)

  • stateConfig: tambah entry untuk state application ("Proses Seleksi", warna violet) — sebelumnya jatuh ke fallback gray.
  • handleLogout: pakai authStore.logout() (BE /api/auth/logout + bersihkan session/persist), bukan stub router.push("/spmb/login").
  • Tab #3 di-rename "Pembayaran & Hasil" → "Status & Hasil" dengan id status (sebelumnya pembayaran).
  • Card "Submit Sekarang" dipindah dari global action bar ke dalam konten tab "Status & Hasil" — tampil hanya saat user di tab tsb. DAN state=draft.
  • Tombol statis "Simpan Perubahan" di bottom dihapus — floating bar yang muncul saat hasUnsavedChanges adalah satu-satunya UI Save (afford lebih jelas, tidak duplikatif).
  • Tab "Dokumen & Prestasi": placeholder lama 1-CTA diganti grid 2-card (Manajemen Dokumen + Prestasi) supaya keduanya equally accessible.

10.5 Verifikasi

  • npx vitest run tests/unit tests/menu → 187 file tes lulus (904 passed, 6 skipped, 0 fail).
  • tests/menu/spmbDetailChildRoutesComposition.spec.js (baru) lulus (3 tests).
  • Build/lint/type-check belum dieksekusi di sesi ini — perlu dijalankan di pre-PR validasi sebelum merge ke develop.

10.6 Reduksi LOC

Aset Sebelum Sesudah Δ
src/views/SPMB/SPMBWizard.vue 1448 0 (deleted) −1448
Wizard sub-components (2) ~250 0 −250
Dead views (5 file) ~1700 0 −1700
_archive/* (4 file) ~1500 0 −1500
src/stores/spmb.store.js 539 239 −300
src/services/spmb/spmb.service.js 271 231 −40
Plus SPMBAdmissionCreate.vue (baru) 0 360 +360
Net total ≈ −4878

10.7 Follow-up (opsional)

  • Konsolidasi field Profil dengan field pre-create (sekarang Create hanya minta minimum, sisanya diisi di Detail tab Profil — duplikasi pertanyaan rendah tapi ada).
  • Pindah src/views/SPMB/components/Stepper.vue ke src/components/ui/ karena sekarang hanya dipakai oleh NewsWizard.vue (di luar scope SPMB).
  • Update docs/qa/spmb-end-to-end-uat.md sesuai canonical flow baru (hapus referensi step wizard).

11. Hotfix — Upload Dokumen 413 (Apr 29, 2026)

User report pasca-Opsi A: klik "Upload Dokumen Lainnya" memunculkan POST /api/SPMB/.../document/upload 413 (Content Too Large) dari nginx, TANPA pesan ke user (uncaught promise rejection di console).

11.1 Root cause

Di SPMBDocuments.vue:handleFileUpload lama, try/catch outer membungkus reader.readAsDataURL(file) (sinkron) tapi panggilan SPMBService.uploadDocument berjalan di dalam reader.onload = async () => { await ... }. Rejection axios dari nginx 413 tidak pernah terpropagasi ke catch outer → unhandled promise rejection. Selain itu fallback batas ukuran 5MB tidak memperhitungkan inflasi base64 (~+33%) + envelope JSON-RPC, sehingga file dekat 5MB raw bisa melewati nginx client_max_body_size 5MB.

11.2 Fix FE (committed)

  • SPMBDocuments.vue: promisify FileReader (new Promise(...)) sehingga rejection upload masuk ke catch outer.
  • Mapping error → pesan friendly ID:
  • 413 (status atau substring "413 Request/Content Too Large" di response HTML nginx) → "File terlalu besar untuk diunggah ke server. Coba kompres ke bawah 3MB lalu unggah ulang."
  • ERR_NETWORK → "Koneksi terputus saat mengunggah."
  • default → message dari error.
  • Tampilkan via useNotification.error() + tetap set error.value inline.
  • Konstanta DEFAULT_MAX_FILE_SIZE_MB = 3 (turun dari 5). Berlaku saat reqDoc.max_file_size tidak tersedia (mis. tipe "other"). Validasi client-side menampilkan ukuran file aktual + saran kompres.
  • Tambah notifySuccess("Dokumen berhasil diunggah") untuk feedback positif.

11.3 Catatan infra (di luar repo FE)

nginx client_max_body_size perlu dinaikkan bila batas 3MB raw terlalu ketat untuk dokumen SPMB (mis. scan KK warna). Rekomendasi: client_max_body_size 10m; di vhost dev.gcgscola.id lalu naikkan DEFAULT_MAX_FILE_SIZE_MB ke 6–7. Biarkan ada margin minimal 30% untuk inflasi base64 + envelope. Lokasi config: di repo scola-be / infra ops, bukan FE.

11.4 Pola serupa di tempat lain

Audit menunjukkan banyak komponen lain (Inventory, HR, Counseling, News, dst.) menggunakan pola reader.onload = async () => { await serviceCall() } yang sama. Bila mereka pernah melewati nginx limit atau backend error, error juga akan tertelan.

Status (Apr 30, 2026): FE-wide audit telah dilakukan. Dua file Inventory yang masih bermasalah telah di-fix dengan pola promisify FileReader yang sama seperti SPMBDocuments.vue: - PurchaseRequestDetail.vueonFileSelected (upload attachment Purchase Request): reader.onload = async → promisify. - ReceiptDetail.vueonFileSelected (upload attachment Receipt): reader.onload = async + broken reader.onerror (throw di callback) → promisify.

File lain yang menggunakan reader.onload = async sudah memiliki try/catch internal (UploadSiswa, UploadGuru, bank-reconciliation.service). Tidak ada error tertelan di file-file tersebut.

11.5 Extension hotfix → SPMBAchievements.vue (Apr 29, 2026, sore)

User report: 413 juga muncul saat "Tambah Prestasi" mengunggah sertifikat. File SPMBAchievements.vue:handleSubmit sebelumnya cuma error.value = err.message (= "Request failed with status code 413" — tidak ramah, dan terkadang ketutupan modal). Pola fix yang sama diterapkan: - Validasi ekstensi (pdf,jpg,jpeg,png) + ukuran (≤3MB) di handleFileUpload dengan showWarningDialog + reset input. - Promisify FileReader agar reader.onerror terpropagasi. - handleSubmit: deteksi 413 / ERR_NETWORK → pesan friendly via useNotification.error() + tetap set error.value. - notifySuccess saat add/edit berhasil (sebelumnya tidak ada feedback positif).

11.6 Konsolidasi limit upload + hint UI (Apr 29, 2026, sore)

Konteks pertanyaan user: 1. client_max_body_size 5m itu per file atau total? 2. Bisa diatur di aplikasi, bukan nginx? 3. Tampilkan keterangan UI per field (best practice).

Jawaban: 1. Per HTTP request body. Karena upload SPMB = 1 file per POST, efektif per file (sudah termasuk overhead base64 + envelope). Bukan kuota cumulative. 2. Layered limit (best practice): - nginx = hard cap infra; tidak bisa di-bypass aplikasi. Saran: set tinggi (20m). - BE Odoo = setting bisnis per-jenis-dokumen via field op.admission.required.doc.max_file_size + allowed_extensions (sudah ada, sudah dikonsumsi FE). Panitia SPMB bisa atur tanpa redeploy FE. - FE = fallback + validasi client-side + hint UI. 3. Implementasi (commit ini):

File baru src/config/spmbUploadLimits.js — single source of truth client-side untuk default + helper: - SPMB_DEFAULT_MAX_FILE_SIZE_MB = 3 (tinggal ubah di sini saja saat nginx dinaikkan). - SPMB_DEFAULT_ALLOWED_EXTENSIONS = ["pdf","jpg","jpeg","png"]. - resolveMaxFileSizeMb(reqDoc) — utamakan reqDoc.max_file_size dari BE, fallback ke konstanta. - resolveAllowedExtensions(reqDoc) — parse CSV BE atau fallback. - formatFileSize(bytes) — "850 KB" / "1.20 MB". - buildUploadHint(reqDoc) — string hint standar "Format: PDF, JPG, PNG · Max 3MB".

Refactor: - SPMBDocuments.vue: import dari config sentral. Hint UI tampil per-required-doc (sumber BE) DAN di dropzone "Upload Dokumen Lainnya" (fallback). Placeholder lama "max 5MB" yang stale dihapus. - SPMBAchievements.vue: idem; hint tampil di dropzone sertifikat modal Add/Edit Prestasi.

Test: - tests/unit/config/spmbUploadLimits.spec.js (baru, 9 tests): kunci kontrak default, parsing, fallback, dan format helper supaya regresi tidak menaikkan default sembarangan.

Best practice yang diterapkan (UX upload): - Hint statis dekat input dropzone (pre-pilih). - Validasi client-side dengan ukuran file aktual + saran kompres. - Notifikasi toast (terlihat meski modal terbuka) + banner inline. - Source of truth dari BE → FE konsisten dengan kebijakan panitia. - Fallback konservatif yang aman terhadap nginx cap (lihat §11.7).

11.7 Perubahan nginx server dev.gcgscola.id (Apr 29, 2026)

Investigasi root cause infra: vhost /etc/nginx/sites-available/ dev.gcgscola.id ternyata tidak punya client_max_body_size sama sekali — jadi pakai default nginx 1MB (bukan 5MB seperti asumsi awal). Inilah akar 413 yang sebenarnya: file > ~750KB raw sudah ditolak nginx sebelum sampai BE.

Perubahan diterapkan via SSH (server 89.116.32.154, scola@): 1. Backup: dev.gcgscola.id.bak.20260429-072717. 2. Sisipkan client_max_body_size 20M; di server block 443 (juga di block 80 yang redirect — harmless tapi konsisten). 3. sudo nginx -t valid → sudo systemctl reload nginx (graceful). 4. Verifikasi functional dengan POST payload 6.6MB ke /api/SPMB/my/admission/document/upload → respons HTTP 200 + JSON-RPC Odoo Session Expired (auth-level, bukan 413). Pre-fix payload ini pasti ditolak nginx 413.

Dampak: - nginx cap dev: 1MB → 20MB (margin 14MB raw setelah base64 inflation). - FE SPMB_DEFAULT_MAX_FILE_SIZE_MB dinaikkan dari 3 → 5MB (cukup untuk dokumen realistis, tetap aman vs nginx 20MB). - Test tests/unit/config/spmbUploadLimits.spec.js masih lulus (assertion <= 5MB).

Vhost lain yang TIDAK diubah (di luar scope task ini, tapi worth diperiksa ops apakah punya masalah serupa): - app.gcgscola.id (FE produksi utama) - frontend.gcgscola.id - demo.gcgscola.id, jamesschool.*, montessori.*, sdn1pgt.*, smpn1pmk.*, spansa.*, smpn1dramagabogor.* - sadarapp.gcgscola.id

Vhost backend be-*.gcgscola.id sudah 200M semua — tidak terdampak.

Rekomendasi follow-up untuk ops: 1. Standardisasi: tambahkan client_max_body_size 20M; di semua vhost FE produksi (cegah 413 senyap di tenant lain). 2. Pertimbangkan masuk ke template global nginx.conf di http{} block sebagai default 20M, override per vhost bila perlu. 3. Field op.admission.required.doc.max_file_size di BE Odoo harus disurfacing di form admin SPMB supaya panitia bisa atur per-jenis-dokumen tanpa redeploy.

13. SPMB Admin Test-* endpoints HTTP 400 (Apr 29, 2026 — fixed)

Gejala

Halaman /admin/spmb/test/schedule, /admin/spmb/test/types, /admin/spmb/test/participants, /admin/spmb/test/results selalu gagal load dengan HTTP 400 pada POST ke endpoint terkait. Log Odoo BE menunjukkan:

WARNING odoo.http: No CSRF validation token provided for path
'/api/SPMB/test-schedules/list'
INFO werkzeug: "POST /api/SPMB/test-schedules/list HTTP/1.0" 400

Akar

Saat module load, Odoo melempar warning:

WARNING odoo.http: The endpoint
SPMBAssessmentSchedulesController.test_schedules_list changes the route
type, using the original type: 'http'.

Mekanismenya (/home/scola/odoo/odoo/http.py:864 _check_and_complete_route_definition): saat menjalin merged_routing melintasi MRO, parent method yang tidak ber-decorator akan auto-decorate dengan default @route()type='http'. Ketika child overlay (scola_admission_assessment) menulis @http.route(..., type='json', ...), Odoo menolak perubahan tipe dan memaksa tipe akhir kembali ke 'http'.

Konsekuensi: - Dispatcher yang dipakai = HttpDispatcher, bukan JsonRPCDispatcher. - HttpDispatcher.dispatch menjalankan CSRF check untuk metode unsafe. - POST dari FE tanpa csrf_tokenBadRequest (HTTP 400).

Pola yang sama dilaporkan sebelumnya untuk /api/SPMB/my/test-schedules (diselesaikan dengan memindahkan @http.route ke base — lihat catatan di scola_admission/controllers/spmb_api.py:1701). Tapi route admin test-* belum dirapikan.

Fix

Menambahkan stub @http.route(type='json', auth='user') (tanpa URL) pada method base di: - scola_admission/controllers/spmb_complaints_ranking_api.py — 9 method (test_schedules, test_schedules_list, test_schedule_options, test_schedule_detail, test_schedule_detail_read, test_schedule_create, test_schedule_update, test_schedule_delete, test_schedule_sync_cbt). - scola_admission/controllers/spmb_applicants_api.py — 11 method (test_types_*, test_participants_*, test_results_*).

Stub tidak mendaftarkan URL (routes kosong), hanya menetapkan type='json' di original_routing sehingga MRO merge meneruskan type ke child overlay alih-alih default 'http'. Topologi overlay-only tetap terjaga: ketika scola_admission_assessment tidak ter-install, base alone tidak meng-expose URL (Odoo log "is a controller endpoint without any route, skipping").

Verifikasi

curl -X POST https://be-dev.gcgscola.id/api/SPMB/test-schedules/list \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","method":"call","params":{}}'

Sebelum fix: HTTP 400 + <!doctype html>... (HttpDispatcher CSRF reject) atau redirect 303 ke /web/login.

Sesudah fix: HTTP 200 + {"jsonrpc":"2.0","error":{... "Session expired" ...}} (JsonRPCDispatcher menangani, gagal di auth karena curl tanpa cookie — perilaku benar).

Restart odoo-devscola (Apr 29 10:30:50 UTC) menghilangkan warning "changes the route type" untuk semua method test-*.