# 🧪 UAT Modul Opname — Metode 5W+1H

## Ringkasan Cakupan UAT

| Dimensi | Cakupan |
|---------|---------|
| **What** | Fitur apa yang diuji |
| **Who** | Siapa aktor/role yang terlibat |
| **When** | Kapan/kondisi apa yang memicu skenario |
| **Where** | Di halaman/modul mana terjadi |
| **Why** | Mengapa ini penting diuji (risiko bisnis) |
| **How** | Bagaimana langkah pengujiannya + kriteria pass/fail |

---

## AREA 1: Halaman Index & Validasi Pra-Opname

### TC-1.1 — Cek Transaksi Gantung Sebelum Opname

| Dimensi | Detail |
|---------|--------|
| **What** | Validasi bahwa user tidak bisa memulai opname jika ada transaksi yang belum selesai (gantung) |
| **Who** | Petugas Gudang (`c_gudang`) |
| **When** | Saat membuka halaman index opname, dan ada transaksi distribusi/penerimaan yang belum complete |
| **Where** | `opname/Opname/index/Produk` |
| **Why** | Stok opname akan salah jika ada transaksi in-flight yang belum memutasi stok. Ini proteksi integritas data |
| **How** | 1. Buat transaksi distribusi/penerimaan dan biarkan di status pending<br>2. Buka halaman opname<br>3. ✅ **PASS**: Muncul modal dialog "Transaksi yang harus diselesaikan sebelum stok opname" dengan daftar transaksi gantung<br>4. ❌ **FAIL**: Halaman opname terbuka tanpa peringatan |

### TC-1.2 — Index Opname Tanpa Transaksi Gantung

| Dimensi | Detail |
|---------|--------|
| **What** | Halaman opname bisa diakses normal ketika tidak ada transaksi gantung |
| **Who** | Petugas Gudang |
| **When** | Semua transaksi sudah complete/tidak ada yang pending |
| **Where** | `opname/Opname/index/Produk` |
| **Why** | Memastikan validasi tidak memblokir alur normal |
| **How** | 1. Pastikan tidak ada transaksi pending<br>2. Buka halaman opname<br>3. ✅ **PASS**: Halaman tampil normal dengan pilihan jenis (Produk/Supplies/Produk Rakitan)<br>4. ❌ **FAIL**: Halaman error atau masih muncul modal padahal tidak ada gantungan |

### TC-1.3 — Validasi Akses Berdasarkan Cabang

| Dimensi | Detail |
|---------|--------|
| **What** | Jenis transaksi opname sesuai cabang user yang login |
| **Who** | Petugas Gudang di berbagai cabang |
| **When** | Saat halaman index opname dimuat |
| **Where** | `opname/Opname/index/Produk` |
| **Why** | Cabang pusat (`-1`) punya kode `1119`, cabang lain punya kode `2229`. Salah mapping = salah jurnal akuntansi |
| **How** | 1. Login sebagai user **Pusat** (cabang_id = -1) → Cek jenis_tr = `1119`<br>2. Login sebagai user **Cabang** (cabang_id selain -1) → Cek jenis_tr = `2229`<br>3. ✅ **PASS**: Kode transaksi sesuai cabang<br>4. ❌ **FAIL**: Kode transaksi tidak sesuai |

---

## AREA 2: Download Excel (doPrint)

### TC-2.1 — Download Excel Produk Berhasil

| Dimensi | Detail |
|---------|--------|
| **What** | Download file Excel berisi daftar produk + stok buku untuk diisi stok riil |
| **Who** | Petugas Gudang |
| **When** | Pertama kali download untuk gudang tertentu (belum ada sesi opname aktif) |
| **Where** | `opname/Opname/doPrint/Produk` |
| **Why** | File Excel adalah dokumen kerja utama opname — harus akurat |
| **How** | 1. Pilih cabang, gudang, dan kategori<br>2. Klik tombol download<br>3. ✅ **PASS jika semua terpenuhi**:<br>&nbsp;&nbsp;— File `.xlsx` terdownload<br>&nbsp;&nbsp;— Kolom: cID, gID, jenis, pID, sku, produk, indoor, outdoor, kategori, supplier, stok (buku), stok riil, serial number<br>&nbsp;&nbsp;— Kolom "stok riil" kosong (siap diisi)<br>&nbsp;&nbsp;— Kolom "stok (buku)" berisi angka dari sistem<br>&nbsp;&nbsp;— Kolom "serial number" terisi jika produk punya serial<br>&nbsp;&nbsp;— Record `dashboard_opname` ter-insert di database<br>&nbsp;&nbsp;— Record `dashboard_opname_data` ter-insert (batch per 500) |

### TC-2.2 — Stok Buku Akurat Sesuai Rekening Pembantu

| Dimensi | Detail |
|---------|--------|
| **What** | Angka stok buku di Excel cocok dengan saldo di rekening pembantu produk |
| **Who** | Petugas Gudang + Akuntan (cross-check) |
| **When** | Setelah download Excel |
| **Where** | File Excel yang didownload vs database `_rek_pembantu_produk_cache` |
| **Why** | Jika stok buku salah, selisih opname akan salah → jurnal adjustment salah → laporan keuangan salah |
| **How** | 1. Download Excel opname<br>2. Bandingkan kolom "stok (buku)" dengan query:<br>`SELECT extern_id, qty_debet FROM _rek_pembantu_produk_cache WHERE periode='forever' AND rekening='1010030030' AND gudang_id=X AND cabang_id=Y`<br>3. ✅ **PASS**: Semua angka cocok<br>4. ❌ **FAIL**: Ada perbedaan angka |

### TC-2.3 — Serial Number Terisi Pada Produk Ber-Serial

| Dimensi | Detail |
|---------|--------|
| **What** | Kolom serial number di Excel terisi untuk produk yang memiliki serial (`jml_serial > 0`) |
| **Who** | Petugas Gudang |
| **When** | Saat download Excel produk |
| **Where** | File Excel yang didownload |
| **Why** | Serial number penting untuk tracing & validasi fisik unit tertentu |
| **How** | 1. Pastikan ada produk dengan `jml_serial > 0` dan punya serial aktif di cache<br>2. Download Excel<br>3. ✅ **PASS**: Kolom serial number terisi (dipisah titik koma) untuk produk ber-serial<br>4. ❌ **FAIL**: Kolom serial number kosong padahal ada serial di database |

### TC-2.4 — Proteksi Download Ganda (Gudang Sudah Didownload)

| Dimensi | Detail |
|---------|--------|
| **What** | Tombol download di-disable jika gudang sudah punya sesi opname aktif |
| **Who** | Petugas Gudang |
| **When** | Saat membuka halaman view setelah gudang sudah didownload |
| **Where** | `opname/Opname/view/Produk` |
| **Why** | Mencegah duplikasi sesi opname yang bisa mengacaukan data |
| **How** | 1. Download Excel untuk gudang X<br>2. Buka kembali halaman view<br>3. ✅ **PASS**: Radio button gudang X menjadi `disabled` dengan pesan "data opname sudah didownload oleh [nama] jam [waktu]"<br>4. ❌ **FAIL**: Gudang X masih bisa dipilih dan didownload ulang |

### TC-2.5 — Semua Gudang Sudah Didownload

| Dimensi | Detail |
|---------|--------|
| **What** | Jika semua gudang sudah didownload, tombol download utama di-disable |
| **Who** | Petugas Gudang |
| **When** | Semua gudang (good + project) sudah ada sesi opname aktif |
| **Where** | `opname/Opname/view/Produk` |
| **Why** | UX — mencegah user bingung kenapa tidak bisa download apapun |
| **How** | 1. Download semua gudang yang tersedia<br>2. ✅ **PASS**: Tombol download disabled + pesan "Sesi opname sedang berlangsung, sudah tidak bisa download. Silahkan lihat History download jika akan download ulang"<br>3. ❌ **FAIL**: Tombol masih aktif |

---

## AREA 3: Upload Excel (doUpload)

### TC-3.1 — Upload File XLSX Valid

| Dimensi | Detail |
|---------|--------|
| **What** | Upload file Excel (.xlsx) yang sudah diisi stok riil berhasil diproses |
| **Who** | Petugas Gudang |
| **When** | Setelah mengisi stok riil di file Excel yang didownload |
| **Where** | `opname/Opname/doUpload/Produk` |
| **Why** | Ini titik masuk data dari lapangan ke sistem |
| **How** | 1. Isi kolom "stok riil" di file Excel<br>2. Upload file<br>3. ✅ **PASS**: Muncul swal "Membaca & Memproses Data..." dan proses berlanjut ke executeOpname<br>4. Record tersimpan di tabel `opname_xls` (file_name, full_path, oleh_id, jenis_tr) |

### TC-3.2 — Upload File Format Salah

| Dimensi | Detail |
|---------|--------|
| **What** | Sistem menolak file selain xlsx/csv/xls/ods |
| **Who** | Petugas Gudang |
| **When** | Saat upload file dengan format salah (pdf, doc, jpg, dll) |
| **Where** | `opname/Opname/doUpload/Produk` |
| **Why** | Keamanan — mencegah upload file berbahaya |
| **How** | 1. Coba upload file .pdf atau .jpg<br>2. ✅ **PASS**: Muncul pesan error "format file yang diupload harus berextensi xlsx"<br>3. ❌ **FAIL**: File diterima dan diproses |

### TC-3.3 — Upload File Ukuran Melebihi Batas

| Dimensi | Detail |
|---------|--------|
| **What** | Sistem menolak file di atas 1 MB (`max_size = 1024` KB) |
| **Who** | Petugas Gudang |
| **When** | Saat upload file yang terlalu besar |
| **Where** | `opname/Opname/doUpload/Produk` |
| **Why** | Membatasi beban server |
| **How** | 1. Buat file xlsx > 1 MB<br>2. Upload<br>3. ✅ **PASS**: Muncul pesan error ukuran file<br>4. ❌ **FAIL**: File diproses tanpa peringatan |

---

## AREA 4: Execute Opname (Proses Pencocokan)

### TC-4.1 — Hitung Selisih Stok Benar (Stok Riil > Stok Buku)

| Dimensi | Detail |
|---------|--------|
| **What** | Kalkulasi selisih LEBIH (debet) benar: `qty_debet = qty_opname - stok_buku` |
| **Who** | Sistem (otomatis) |
| **When** | Saat executeOpname memproses data Excel |
| **Where** | `opname/Opname/executeOpname/Produk` |
| **Why** | Selisih lebih artinya ada stok yang belum tercatat → harus masuk sebagai **Laba Lain-lain** (COA 7010150) |
| **How** | 1. Isi stok riil **lebih besar** dari stok buku (misal: buku=10, riil=15)<br>2. Upload dan proses<br>3. ✅ **PASS**: `qty_debet = 5`, `qty_kredit = 0`, `debet = hpp × 5`<br>4. ❌ **FAIL**: Kalkulasi tidak sesuai |

### TC-4.2 — Hitung Selisih Stok Benar (Stok Buku > Stok Riil)

| Dimensi | Detail |
|---------|--------|
| **What** | Kalkulasi selisih KURANG (kredit) benar: `qty_kredit = stok_buku - qty_opname` |
| **Who** | Sistem (otomatis) |
| **When** | Saat executeOpname memproses data Excel |
| **Where** | `opname/Opname/executeOpname/Produk` |
| **Why** | Selisih kurang artinya ada stok hilang → harus masuk sebagai **Kerugian** (COA 7020020) |
| **How** | 1. Isi stok riil **lebih kecil** dari stok buku (misal: buku=10, riil=7)<br>2. Upload dan proses<br>3. ✅ **PASS**: `qty_kredit = 3`, `qty_debet = 0`, `kredit = hpp_avg × 3`<br>4. ❌ **FAIL**: Kalkulasi tidak sesuai |

### TC-4.3 — Stok Riil = Stok Buku (Tidak Ada Selisih)

| Dimensi | Detail |
|---------|--------|
| **What** | Produk dengan stok riil = stok buku tidak menghasilkan selisih |
| **Who** | Sistem (otomatis) |
| **When** | Saat executeOpname memproses produk yang stoknya cocok |
| **Where** | `opname/Opname/executeOpname/Produk` |
| **Why** | Produk tanpa selisih tetap harus terekam di opname tapi tidak boleh memutasi stok atau jurnal |
| **How** | 1. Isi stok riil **sama** dengan stok buku<br>2. Upload dan proses<br>3. ✅ **PASS**: `qty_debet = 0`, `qty_kredit = 0`, tidak ada mutasi stok/jurnal untuk produk ini<br>4. ❌ **FAIL**: Ada mutasi padahal tidak ada selisih |

### TC-4.4 — Update Dashboard Opname Data

| Dimensi | Detail |
|---------|--------|
| **What** | Data stok riil dari Excel tersimpan ke `dashboard_opname_data.jml_stok_opname` |
| **Who** | Sistem (otomatis) |
| **When** | Setelah executeOpname selesai |
| **Where** | Database tabel `dashboard_opname_data` |
| **Why** | Data ini menjadi referensi untuk validasi dan audit |
| **How** | 1. Upload Excel dengan stok riil terisi<br>2. Query: `SELECT produk_id, jml_stok_buku, jml_stok_opname FROM dashboard_opname_data WHERE dashboard_opname_id = X`<br>3. ✅ **PASS**: `jml_stok_opname` sesuai isian Excel, `dashboard_opname.done_id` terisi ID user yang upload<br>4. ❌ **FAIL**: Data tidak terupdate atau salah |

### TC-4.5 — Parsing Serial Number dari Excel

| Dimensi | Detail |
|---------|--------|
| **What** | Serial number yang diisi user di Excel terbaca dengan benar (multi-separator) |
| **Who** | Sistem (otomatis) |
| **When** | Saat executeOpname mem-parse kolom serial number |
| **Where** | `opname/Opname/executeOpname/Produk` |
| **Why** | User bisa menggunakan berbagai pemisah — sistem harus toleran |
| **How** | 1. Isi kolom serial number dengan berbagai format:<br>&nbsp;&nbsp;— `SN001, SN002, SN003` (koma)<br>&nbsp;&nbsp;— `SN001; SN002; SN003` (titik koma)<br>&nbsp;&nbsp;— `SN001\nSN002\nSN003` (enter/newline)<br>&nbsp;&nbsp;— `SN001|SN002|SN003` (pipe)<br>2. Upload dan proses<br>3. ✅ **PASS**: Semua serial number terparsing dengan benar sebagai item terpisah<br>4. ❌ **FAIL**: Serial number tergabung atau hilang |

### TC-4.6 — File XLSX Hanya Format yang Diterima oleh executeOpname

| Dimensi | Detail |
|---------|--------|
| **What** | executeOpname hanya memproses file XLSX, bukan format lain |
| **Who** | Sistem (otomatis) |
| **When** | Saat executeOpname membaca file extension |
| **Where** | `opname/Opname/executeOpname/Produk` |
| **Why** | Parser menggunakan `PHPExcel_Reader_Excel2007` yang khusus untuk XLSX |
| **How** | 1. Perhatikan bahwa doUpload menerima xlsx/csv/xls/ods, tetapi executeOpname hanya handle xlsx<br>2. ✅ **PASS**: File non-xlsx ditolak dengan pesan "hanya menghandel file XLSX"<br>3. ⚠️ **Catatan**: Ada gap antara format yang diterima upload vs yang bisa diproses |

---

## AREA 5: Approval Chain (FollowUp)

### TC-5.1 — Authorization Step 1 (1119r → 1119ro)

| Dimensi | Detail |
|---------|--------|
| **What** | Transaksi opname berhasil di-approve tahap 1 |
| **Who** | User dengan role `c_holding` (supervisor/manager) |
| **When** | Setelah transaksi opname dibuat (step 1) |
| **Where** | `opname/FollowUp/doFollowup/1119/...` |
| **Why** | Approval pertama memvalidasi data opname dan mengekstrak serial number |
| **How** | 1. Buka daftar transaksi opname pending step 1<br>2. Preview transaksi<br>3. Klik Approve<br>4. ✅ **PASS**:<br>&nbsp;&nbsp;— Status berubah dari `1119r` ke `1119ro`<br>&nbsp;&nbsp;— PreProcessor `ProdukSerialNumberExtractorOpname` berjalan<br>&nbsp;&nbsp;— PostProcessor `Opname` + `OpnameData` berjalan (update dashboard)<br>&nbsp;&nbsp;— State label: "pending approval 2" (warna oranye) |

### TC-5.2 — Authorization Step 2 / Final (1119ro → 1119)

| Dimensi | Detail |
|---------|--------|
| **What** | Transaksi opname berhasil di-approve tahap 2 (final) — jurnal dan stok termutasi |
| **Who** | User dengan role `c_holding` |
| **When** | Setelah step 1 sudah di-approve |
| **Where** | `opname/FollowUp/doFollowup/1119/...` |
| **Why** | **INI STEP PALING KRITIS** — di sinilah mutasi stok dan jurnal akuntansi terjadi |
| **How** | 1. Buka transaksi opname di step 2<br>2. Preview dan approve<br>3. ✅ **PASS jika SEMUA terpenuhi**:<br>&nbsp;&nbsp;— Status berubah ke `1119` (Complete, hijau)<br>&nbsp;&nbsp;— **Jurnal debet**: COA 1010030030 + 7010150 terbentuk (untuk selisih lebih)<br>&nbsp;&nbsp;— **Jurnal kredit**: COA 1010030030 + 7020020 terbentuk (untuk selisih kurang)<br>&nbsp;&nbsp;— **Rekening Pembantu Produk**: `qty_debet` bertambah (produk masuk), `qty_kredit` bertambah (produk keluar) sesuai selisih<br>&nbsp;&nbsp;— **Rek Pembantu LR Lain-lain**: Detail laba opname tercatat<br>&nbsp;&nbsp;— **Rek Pembantu Kerugian**: Detail kerugian opname tercatat<br>&nbsp;&nbsp;— FifoAverageOpname menghitung `hpp_avg` untuk kredit |

### TC-5.3 — Edit Qty Saat Approval (allowEdit = true)

| Dimensi | Detail |
|---------|--------|
| **What** | Approver bisa mengedit qty item saat approval step 2 dan 3 |
| **Who** | User dengan role `c_holding` |
| **When** | Saat preview transaksi di step 2 atau 3 |
| **Where** | `opname/FollowUp/followupPreview/...` |
| **Why** | Approver mungkin menemukan kesalahan input dan perlu koreksi sebelum finalisasi |
| **How** | 1. Buka preview transaksi opname di step 2<br>2. Edit qty salah satu item<br>3. Approve<br>4. ✅ **PASS**: Qty terupdate dan selisih dihitung ulang berdasarkan qty baru<br>5. ❌ **FAIL**: Qty tidak bisa diedit atau selisih tidak dihitung ulang |

### TC-5.4 — Revert Transaksi Opname

| Dimensi | Detail |
|---------|--------|
| **What** | Transaksi opname bisa di-revert (dikembalikan ke step sebelumnya) |
| **Who** | User dengan role `c_holding` |
| **When** | Saat ditemukan kesalahan yang memerlukan pengembalian ke step sebelumnya |
| **Where** | `opname/FollowUp/doRevert/...` |
| **Why** | Safety net — kesalahan approval harus bisa dibatalkan |
| **How** | 1. Approve transaksi ke step 2<br>2. Lakukan revert<br>3. ✅ **PASS**: Transaksi kembali ke step 1 tanpa mutasi stok/jurnal<br>4. ❌ **FAIL**: Revert gagal atau meninggalkan data tidak konsisten |

---

## AREA 6: Dashboard Monitoring Opname

### TC-6.1 — Dashboard Status Opname Real-Time

| Dimensi | Detail |
|---------|--------|
| **What** | Dashboard menampilkan progress opname per cabang/jenis/gudang |
| **Who** | Supervisor / Management |
| **When** | Kapan saja selama sesi opname berlangsung |
| **Where** | `opname/Opname/viewOpnameAktive` |
| **Why** | Monitoring progress agar management tahu cabang mana yang sudah/belum selesai |
| **How** | 1. Buka halaman dashboard opname<br>2. ✅ **PASS**:<br>&nbsp;&nbsp;— Tampil matriks cabang × jenis × gudang × status (download/upload/acc1/acc2)<br>&nbsp;&nbsp;— Status "ok" tampil jika tahap sudah dilalui<br>&nbsp;&nbsp;— Halaman auto-refresh setiap 60 detik<br>3. ❌ **FAIL**: Data tidak akurat atau tidak auto-refresh |

### TC-6.2 — Konfirmasi Opname Selesai

| Dimensi | Detail |
|---------|--------|
| **What** | Supervisor bisa mengkonfirmasi seluruh sesi opname selesai |
| **Who** | Supervisor |
| **When** | Setelah semua cabang/gudang sudah complete |
| **Where** | `opname/Opname/doConfirmOpname` |
| **Why** | Menutup sesi opname agar bisa membuka sesi baru di periode berikutnya |
| **How** | 1. Pastikan semua opname sudah di step final<br>2. Klik tombol konfirmasi<br>3. ✅ **PASS**: `dashboard_opname.confirm_id` terisi, `dtime_confirm` terisi, muncul swal "Berhasil"<br>4. ❌ **FAIL**: Konfirmasi gagal atau sesi tidak tertutup |

---

## AREA 7: History & Re-Download

### TC-7.1 — History Download Opname

| Dimensi | Detail |
|---------|--------|
| **What** | Menampilkan riwayat 10 download terakhir |
| **Who** | Petugas Gudang |
| **When** | Saat user perlu melihat file yang sudah pernah didownload |
| **Where** | `opname/Opname/historyDownloadOpname` |
| **Why** | Audit trail + kemampuan re-download jika file hilang |
| **How** | 1. Buka halaman history download<br>2. ✅ **PASS**: Tampil daftar max 10 download terakhir (urut desc by id)<br>3. ❌ **FAIL**: Data kosong atau lebih dari 10 |

### TC-7.2 — Re-Download File Excel dari History

| Dimensi | Detail |
|---------|--------|
| **What** | User bisa mendownload ulang file Excel dari history |
| **Who** | Petugas Gudang |
| **When** | Saat file original hilang atau perlu referensi ulang |
| **Where** | `opname/Opname/reDownloadXls/{dashboard_id}/Produk` |
| **Why** | Recovery — file bisa hilang dari device user |
| **How** | 1. Buka history, klik re-download pada salah satu record<br>2. ✅ **PASS**: File XLSX terdownload dengan data yang sesuai sesi opname tersebut<br>3. ❌ **FAIL**: Error atau data tidak sesuai |

---

## AREA 8: Edge Cases & Keamanan

### TC-8.1 — Session Expired Saat Proses Opname

| Dimensi | Detail |
|---------|--------|
| **What** | Penanganan saat session habis di tengah proses opname |
| **Who** | Petugas Gudang |
| **When** | User idle terlalu lama dan session PHP expired |
| **Where** | Semua halaman opname |
| **Why** | Data yang sedang diproses tidak boleh hilang atau corrupt |
| **How** | 1. Buka halaman opname, tunggu sampai session habis<br>2. Lakukan aksi (klik tombol)<br>3. ✅ **PASS**: Redirect ke halaman login, data dashboard_opname tetap aman<br>4. ❌ **FAIL**: Error 500 atau data corrupt |

### TC-8.2 — Upload File Excel yang Bukan Hasil Download Opname

| Dimensi | Detail |
|---------|--------|
| **What** | Sistem menangani file Excel yang formatnya tidak sesuai template opname |
| **Who** | Petugas Gudang (salah file) |
| **When** | Saat upload file yang bukan dari download opname |
| **Where** | `opname/Opname/executeOpname` |
| **Why** | Mencegah data sampah masuk ke sistem |
| **How** | 1. Upload file Excel dengan kolom yang berbeda (tidak ada kolom pID, stok riil, dll)<br>2. ✅ **PASS**: Proses gagal dengan pesan error yang jelas<br>3. ❌ **FAIL**: Proses lanjut dengan data kosong/null atau error 500 |

### TC-8.3 — Upload Opname ke Gudang yang Sudah Tidak Aktif

| Dimensi | Detail |
|---------|--------|
| **What** | Validasi saat file Excel berisi gudang_id yang tidak match dengan sesi opname aktif |
| **Who** | Petugas Gudang |
| **When** | Saat upload file yang dimodifikasi manual (gid diubah) |
| **Where** | `opname/Opname/executeOpname` |
| **Why** | Integritas data — file harus cocok dengan sesi opname yang terdaftar |
| **How** | 1. Download Excel gudang A, ubah kolom gID menjadi gudang B<br>2. Upload<br>3. ✅ **PASS**: Validasi mendeteksi ketidakcocokan<br>4. ⚠️ **Catatan**: Di kode saat ini validasi di line 2204-2212 belum memiliki penanganan error eksplisit (blok if-elseif kosong) — ini perlu diverifikasi |

### TC-8.4 — Opname Produk dengan HPP Nol

| Dimensi | Detail |
|---------|--------|
| **What** | Penanganan produk yang memiliki HPP = 0 saat kalkulasi selisih |
| **Who** | Sistem (otomatis) |
| **When** | Saat executeOpname memproses produk tanpa history pembelian |
| **Where** | `opname/Opname/executeOpname` |
| **Why** | HPP = 0 berarti selisih bernilai Rp 0 — jurnal terbentuk tapi nilainya nol. Perlu keputusan: apakah ini diterima? |
| **How** | 1. Pastikan ada produk dengan HPP = 0 di `harga_produk`<br>2. Set stok riil berbeda dari stok buku<br>3. Upload dan proses<br>4. ✅ **PASS**: Proses berhasil, `debet/kredit = 0`, jurnal terbentuk dengan nilai 0<br>5. ⚠️ Verifikasi: Apakah bisnis menerima jurnal bernilai 0? |

### TC-8.5 — Opname Concurrent (2 User Upload Bersamaan)

| Dimensi | Detail |
|---------|--------|
| **What** | Penanganan jika 2 user upload opname untuk sesi yang sama |
| **Who** | 2 Petugas Gudang |
| **When** | Upload bersamaan atau hampir bersamaan |
| **Where** | `opname/Opname/executeOpname` |
| **Why** | Bisa menyebabkan duplikasi transaksi atau data tidak konsisten |
| **How** | 1. Proteksi download ganda sudah ada di sisi download<br>2. Cek apakah proteksi di sisi upload juga ada (`jml_upload == 1` di line 2194)<br>3. ✅ **PASS**: Proses kedua ditolak karena `jml_upload > 1`<br>4. ❌ **FAIL**: Dua transaksi opname terbentuk untuk sesi yang sama |

---

## AREA 9: Validasi Jurnal Akuntansi (Post-Approval)

### TC-9.1 — Jurnal Balance (Debet = Kredit Total)

| Dimensi | Detail |
|---------|--------|
| **What** | Total debet = total kredit pada jurnal yang terbentuk |
| **Who** | Akuntan (cross-check) |
| **When** | Setelah step final (authorization 2) selesai |
| **Where** | Tabel jurnal di database |
| **Why** | **Prinsip dasar akuntansi double-entry** — jika tidak balance, laporan keuangan salah |
| **How** | 1. Selesaikan opname sampai step final<br>2. Query jurnal untuk transaksi opname tersebut<br>3. ✅ **PASS**: SUM(debet) = SUM(kredit)<br>4. ❌ **FAIL**: Tidak balance — **CRITICAL BUG** |

### TC-9.2 — Rekening Pembantu Produk Termutasi Sesuai Selisih

| Dimensi | Detail |
|---------|--------|
| **What** | Saldo stok di rekening pembantu berubah sesuai selisih opname |
| **Who** | Akuntan |
| **When** | Setelah step final |
| **Where** | Tabel `_rek_pembantu_produk` |
| **Why** | Stok di rekening pembantu harus sinkron dengan hasil opname |
| **How** | 1. Catat saldo stok sebelum opname (query rekening pembantu)<br>2. Selesaikan opname<br>3. Query ulang saldo stok<br>4. ✅ **PASS**: Saldo baru = saldo lama + qty_debet - qty_kredit (per produk)<br>5. ❌ **FAIL**: Saldo tidak berubah atau berubah tidak sesuai |

### TC-9.3 — HPP Average Digunakan untuk Kredit (Selisih Kurang)

| Dimensi | Detail |
|---------|--------|
| **What** | Nilai kredit menggunakan `hpp_avg` (bukan hpp biasa) untuk produk yang stoknya kurang |
| **Who** | Akuntan |
| **When** | Saat step final memproses FifoAverageOpname |
| **Where** | Config `coTransaksiCore.php` line 38: `"kredit" => "hpp_avg*qty_kredit"` |
| **Why** | HPP Average merefleksikan biaya rata-rata pembelian — ini standar akuntansi untuk pengeluaran persediaan |
| **How** | 1. Siapkan produk dengan beberapa transaksi pembelian harga berbeda<br>2. Set stok riil < stok buku (ada selisih kurang)<br>3. Proses sampai final<br>4. ✅ **PASS**: Nilai kredit = `hpp_avg × qty_kredit`, bukan hpp terakhir<br>5. ❌ **FAIL**: Menggunakan hpp terakhir bukan rata-rata |

---

## Checklist Ringkasan

| # | Area | Test Case | Prioritas |
|---|------|-----------|-----------|
| 1.1 | Index | Cek transaksi gantung | 🔴 High |
| 1.2 | Index | Index tanpa gantungan | 🟡 Medium |
| 1.3 | Index | Validasi akses cabang | 🔴 High |
| 2.1 | Download | Download Excel berhasil | 🔴 High |
| 2.2 | Download | Stok buku akurat | 🔴 High |
| 2.3 | Download | Serial number terisi | 🟡 Medium |
| 2.4 | Download | Proteksi download ganda per gudang | 🟡 Medium |
| 2.5 | Download | Semua gudang sudah didownload | 🟢 Low |
| 3.1 | Upload | Upload XLSX valid | 🔴 High |
| 3.2 | Upload | Tolak format salah | 🟡 Medium |
| 3.3 | Upload | Tolak file terlalu besar | 🟢 Low |
| 4.1 | Execute | Selisih lebih (debet) benar | 🔴 High |
| 4.2 | Execute | Selisih kurang (kredit) benar | 🔴 High |
| 4.3 | Execute | Tidak ada selisih | 🟡 Medium |
| 4.4 | Execute | Update dashboard_opname_data | 🔴 High |
| 4.5 | Execute | Parsing serial number multi-separator | 🟡 Medium |
| 4.6 | Execute | Hanya XLSX yang diproses | 🟡 Medium |
| 5.1 | Approval | Authorization step 1 | 🔴 High |
| 5.2 | Approval | Authorization step 2 (Final) | 🔴 High |
| 5.3 | Approval | Edit qty saat approval | 🟡 Medium |
| 5.4 | Approval | Revert transaksi | 🟡 Medium |
| 6.1 | Dashboard | Status real-time | 🟡 Medium |
| 6.2 | Dashboard | Konfirmasi selesai | 🟡 Medium |
| 7.1 | History | History download | 🟢 Low |
| 7.2 | History | Re-download Excel | 🟢 Low |
| 8.1 | Edge Case | Session expired | 🟡 Medium |
| 8.2 | Edge Case | File bukan template opname | 🟡 Medium |
| 8.3 | Edge Case | Gudang_id tidak match | 🔴 High |
| 8.4 | Edge Case | HPP nol | 🟡 Medium |
| 8.5 | Edge Case | Concurrent upload | 🔴 High |
| 9.1 | Jurnal | Balance debet = kredit | 🔴 High |
| 9.2 | Jurnal | Rekening pembantu termutasi | 🔴 High |
| 9.3 | Jurnal | HPP Average untuk kredit | 🔴 High |

> [!IMPORTANT]
> **Prioritas tertinggi (🔴 High)** — 14 test case yang berkaitan dengan integritas data, kalkulasi nilai, dan jurnal akuntansi. Gagal di area ini = **dampak ke laporan keuangan**.

> [!WARNING]  
> **Temuan dari kode** — Validasi di `executeOpname()` line 2204-2212 (`if ($db_cabang_id != $dt_gdid)`, `elseif ($db_jenis != $dt_jenis)`, dll) memiliki blok kosong tanpa penanganan error. Ini berarti jika data tidak match, proses **diam-diam lanjut** tanpa peringatan. Pertimbangkan untuk diuji secara khusus di TC-8.3.
