# BUGSWEEP REPORT — RONDE 3 (2026-10-01) Sweep live E2E di host dev sesuai `ENVIRONMENT.md` (semua host adalah DEV, write-testing diizinkan) dan `HANDOFF_TESTING.md`. Sweep sebelumnya: ronde 1 = `BUGSWEEP_REPORT.md` (root), ronde 2 = `keluargamart-backend/docs/BUGSWEEP_REPORT_R2.md`. ## 0. Status dasar - Marker dev terverifikasi dua kali (termasuk setelah restart Freebuff): `GET https://api-km.trl.my.id/health` → `{"env":"development","status":"ok"}`; banner amber DEV tampil di POS. - Suite test semua hijau di HEAD masing-masing repo (backend `b61f405`, POS `1fffa20`, B2B `a390a6a`, ERP `07fcff6`): backend `go build`+`go vet`+ `go test ./...` 39 paket ok; POS 35/35; B2B 169/169 + typecheck bersih; ERP 391/391. - Login kasir seed `cashier@keluargamart.local` / `Admin@1234` (migration 000044) → OK. Shift SHF-202609-000008 sudah terbuka (dibiarkan terbuka — bukan shift buatan sweep ini). ## 1. E2E POS yang dijalankan (live, pos-km.trl.my.id) | # | Skenario | Hasil | |---|---|---| | 1 | Tunai, uang pas, Pelanggan Umum, Indomie 3.500 | ✅ SO-202610-000032; stok 76→75; dialog sukses bisa ditutup (fix `1fffa20` live) | | 2 | QRIS, Pelanggan Umum | ⛔ ditolak UI: "Pelanggan tidak valid. Pilih ulang pelanggan sebelum bayar." Tidak ada POST → tidak ada order yatim (lihat R3-3) | | 3 | Buat Member `QA Sweep Member 0110` | ✅ CUS-039 (id 141) dibuat + langsung terpilih; fix `233acdd` terverifikasi live | | 4 | Tunai uang pas member baru (Aqua 4.000) | ✅ SO-202610-000033 Rp 3.920; "Diskon member (Bronze) − Rp 80" tampil di keranjang, dialog bayar, dan struk | | 5 | Halaman Transaksi kasir | ❌ 403 — lihat R3-2 (P1) | Payload XSS/SQLi pelanggan buatan sweep lama (`…`, `'; DROP TABLE customers; --`) dirender sebagai teks mati di Pilih Pelanggan — tidak ada eksekusi. ## 2. Temuan R3 ### R3-2 — P1 — "Lihat Riwayat" kasir 403 (skew frontend vs 000103) **Gejala:** tombol "Lihat Riwayat" di dialog sukses membuka halaman Transaksi yang 403; router guard memantul diam-diam ke `/pos`. API: `GET /api/v1/sales-orders?store_id=1` → 403 untuk role cashier. **Akar:** migration `000103_revoke_operator_accounting_and_restore_hr.sql` sengaja mencabut `sales.sales-orders.view` dari cashier ("a till needs ITS order… not the sales desk's book") dan menambah endpoint sempit `/sales-orders/for-return` untuk dialog retur. Frontend POS masih punya rute `transactions` dengan `meta.permission: 'sales.sales-orders.view'` (`src/router/index.js:33`) dan tombol "Lihat Riwayat" yang mengarah ke sana. Sidebar sudah benar menyembunyikan menu-nya; jalur 403-nya adalah tombol di dua dialog sukses: `TransactionSuccess.vue` (market) dan `GalonSuccessDialog.vue` (galon). **STATUS: SUDAH DIPERBAIKI (POS frontend, opsi 1 — selaras maksud 000103).** - Kedua dialog kini merender tombol hanya bila `authStore.can('sales.sales-orders.view')` — sama dengan filter sidebar; role tanpa izin tidak lagi melihat jalur menuju 403, role berizin (admin/owner) tetap mendapat tombol. - Test baru `src/components/pos/TransactionSuccess.test.js` (4 kasus: tanpa izin → sembunyi, izin eksplisit → tampil, glob `sales.*` → tampil, permissions null → fail-closed). Catatan: `can()` di `src/utils/permissions.js` FAIL-CLOSED untuk permissions null; komentar "fail-open" di `src/stores/auth.js` sudah usang (niat perbaikan kecil, belum disentuh di ronde ini). - `npm test` POS: 39/39 pass (+4); `npm run build` OK (warning chunk size pre-existing). - Nit tersisa: notifikasi statis demo di `AppTopbar.vue:25` masih mem-link `/transactions` tanpa filter permission — data dummy, klik-nya memantul aman lewat router guard; layak dihapus saat notifikasi real dibuat. ERP/B2B tidak terdampak. ### R3-1 — P2 (hazard data + hardening) — diskon 2% diam-diam di semua penjualan walk-in **Gejala:** checkout walk-in Rp 3.500 menghasilkan struk total Rp 3.430 (−2%) tanpa baris diskon di keranjang/dialog (baru muncul barisnya kalau member dipilih eksplisit). **Akar:** `resolveWalkIn` (internal/sales/service.go:609) memetakan CustomerID=0 ke pelanggan seed "Pelanggan Umum"; pelanggan itu punya baris memberships aktif Bronze di data dev; resolver benefit (migration 000014, bronze=2%) menjalankan diskon per baris. UI hanya menampilkan diskon bila pelanggan member dipilih via Pilih Pelanggan — walk-in lolos tanpa indikasi. **STATUS: SUDAH DIPERBAIKI (kode).** - Backend: `ProcessPOS`/`Create` kini meneruskan `CustomerID=nil` ke resolver pricing untuk walk-in (hanya pelanggan yang dipilih eksplisit yang dapat benefit); `pricing.Resolve` juga fail-safe terhadap sentinel `0`. Regression tests: `TestProcessPOS_WalkInPricesWithoutMembershipLookup`, `TestProcessPOS_ExplicitCustomerKeepsPricingLookup`, `TestResolve_WalkInSentinelZeroSkipsMembership`, `TestResolve_NilCustomerSkipsMembership`. Full `go test ./...` hijau (40 paket). - Data dev (jalankan manual di DB dev; SELECT preview dulu, jangan hard-delete master data — sesuai ENVIRONMENT.md): ```sql -- preview dulu SELECT m.id, m.member_number, m.tier, m.is_active, c.code, c.name FROM memberships m JOIN customers c ON c.id = m.customer_id WHERE c.code = 'CUS-000'; -- nonaktifkan (bukan delete) UPDATE memberships SET is_active = false, updated_at = now() WHERE customer_id = (SELECT id FROM customers WHERE code = 'CUS-000') AND is_active = true; ``` ### R3-3 — P3 — QRIS tidak bisa untuk walk-in Validasi client menolak QRIS tanpa pelanggan nyata. Perlu keputusan produk: walk-in QRIS umum di retail; kalau memang harus, izinkan walk-in dan lengkapi jalur cancel (kasir TIDAK punya `sales.sales-orders.cancel`). Tidak dites ke selesainya (butuh Midtrans sandbox + pelanggan nyata + jalur cancel). ### R3-4 — P3 — hygiene artefak QA menumpuk di katalog/pelanggan Produk: `SWEEP NegPrice…` (Rp -1 tampil), `SWEEP NegMS/NegPur` (Rp 0), `SWEEP StrPrice/Product/Inv`, `Testo` (stok 499). Kategori: `QWIK Verify Cat 2`, `SWEEP-VERIFY-032218`, `makanan dan minuman test`. Pelanggan: `ppppp`, `mas mamad`, `aman aja`, `asdasdas`, payload script/SQLi, `QA Pelanggan`. Catatan: harga -1 lolos masuk DB — validasi harga negatif di path pembuatan produk layak dicek ulang. Saran script nonaktif-kan (bukan hard-delete, sesuai ENVIRONMENT.md) dengan preview SELECT dulu. ## 3. Tulisan & bersih-bersih QA (transparansi) - Dibuat: 1 pelanggan `QA Sweep Member 0110` (CUS-039) → **dinonaktifkan** via `PUT /api/v1/customers/141 {"is_active":false}` (200, terverifikasi). - 2 order: SO-202610-000032 (walk-in) & SO-202610-000033 (member QA) — keduanya TUNAI LUNAS, stok berkurang wajar; dibiarkan (bukan batal) karena itu penjualan valid; masuk drawer math shift SHF-202609-000008 yang memang masih terbuka. - Tidak membuat produk/kategori/user baru; tidak menutup shift bukan milik sweep. ## 4. Sesi kedua — live E2E ERP + sisa POS (2026-10-01) Sesi lanjutan menutup daftar "belum dites" ronde 3: ERP live (FK katalog lama, bulk approve, voucher penjualan), voucher POS end-to-end, galon, retur, kartu. Semua write-test live di host DEV (marker terverifikasi), prefix `QA`. Catatan penomoran: nomor temuan R3-4 asli di atas sudah dipakai "hygiene artefak QA". Temuan sesi kedua lanjut dari R3-5; temuan pertama sesi kedua (disebut "R3-4" di sesi kerja) ditulis di sini sebagai **R3-4b**. ### Verifikasi live (bukti) - **FK katalog lama: FIXED live.** Submit order dari katalog sebagai `sales@` (customer Budi Santoso CUS-001, atasan Eko Wahyudi SPV, gudang Gudang Pusat Medan) → `POST /sales-orders` **201 → SO-202610-000035** (id 412). Tanpa error "A referenced record does not exist". Catatan harga: form Rp 3.500 → tersimpan Rp 3.220 — diskon pricing backend, pola sama dengan R3-1 ("Pelanggan Umum" masih member Bronze di data dev; jalankan SQL R3-1). - **Confirm = approval.** SO-202610-000035 di-confirm sebagai sales → 200 → status **invoiced** (auto-invoice + auto-DO). Order dengan `submit_for_approval: true` masuk `pending_approval`; tanpa flag → `draft` (`internal/sales/model.go:168`, `service.go:1027-1029`). - **Bulk approve (UI) hanya efektif untuk admin:** tombol "Setujui Terpilih" di bulk bar + panel "Permintaan Persetujuan Order Masuk" (Setujui/Tolak per-row). Implementasinya loop PATCH `/sales-orders/{id}/status` per order (tidak ada endpoint bulk) — PATCH id 414/status → 200 → SO-202610-000037 approved → invoiced. Kenapa hanya admin: lihat R3-8. - **Voucher penjualan end-to-end:** admin buat voucher via `POST /sales-vouchers` → **QA-SWEEP-R3-5000** (Rp 5.000, sales_voucher, exp 2026-12-31) → kasir redeem di POS: divalidasi, diterapkan dengan cap ke total (Rp 3.500 − 5.000 = Rp 0, tidak minus), bayar Rp 0 Tunai sukses → **SO-202610-000038** (struk: Subtotal Rp 3.430, Voucher −Rp 5.000, Total Rp 0, Kembalian Rp 0). Diskon walk-in 2% (R3-1) tetap bekerja sebelum voucher. Cara admin bisa buat voucher padahal sesi ERP biasa 403: lihat R3-7. - **Galon cancel guard:** `POST /galon/{id}/cancel` tanpa Idempotency-Key → 422; dengan key → **409 BUSINESS_RULE_VIOLATION "invalid galon transaction status"** untuk transaksi completed (guard by design, `internal/galon/service.go:242-259` — hanya pending yang bisa cancel). Token kepala-toko lolos RBAC (bukan 403) → izin `galon.transactions.cancel` aktif. - **Retur & pembayaran kartu (sesi sebelumnya, tetap ronde 3):** retur RTN-202610-000001 sukses; pembayaran kartu → SO-202610-000034 sukses. ### Temuan baru #### R3-4b — P2 — Checkout walk-in cart gagal setelah "Transaksi Baru" (sentinel 'cust-000') **Gejala:** checkout KEDUA dst. dalam satu sesi (setelah "Transaksi Baru"/ "Batalkan") ditolak guard createOrder: "Pelanggan tidak valid. Pilih ulang pelanggan sebelum bayar." sampai halaman di-reload. **Akar:** `finishFlow()`/`clear()` di `src/stores/cart.js` reset customerId ke sentinel string `cust-000`, sedangkan resolusi numerik hanya jalan di mount POSPage. **STATUS: SUDAH DIPERBAIKI (POS frontend).** `checkout()` kini re-resolve general customer sebelum bayar, fail-closed: lookup gagal → error, tidak ada payment dan tidak ada order. Test baru `src/stores/cart.walkin.test.js` (4 kasus). #### R3-5 — P2 — Checkout galon walk-in selalu kirim sentinel 'cust-000' **Gejala:** semua checkout galon walk-in mengirim customerId non-numerik dan ditolak backend — tidak hanya setelah "Transaksi Baru", tapi juga pada halaman baru, karena `loadGeneralCustomer()` di `src/stores/galon.js` tidak PERNAH dipanggil dari mana pun (cart punya resolve di mount; galon tidak sama sekali). **STATUS: SUDAH DIPERBAIKI (POS frontend, pola R3-4b).** `load()` kini resolve sentinel up front (non-fatal), dan `checkout()` re-resolve fail-closed SEBELUM payment; `loadGeneralCustomer()` juga menangani kasus sentinel meski rawCustomer sudah terisi. Test baru `src/stores/galon.walkin.test.js` (7 kasus: load resolves, fresh checkout, finishFlow, clear, fail-closed checkout, fail-closed load, pelanggan eksplisit tak tersentuh). `npm test` POS 50/50 (+7), `npm run build` OK. #### R3-6 — P2 — GET /sales-orders/supervisors → 403 untuk Kepala Toko **Gejala:** submit Sales Order sebagai Kepala Toko diblokir fail-safe frontend: "Atasan belum dimuat — Daftar atasan kosong … Submit diblokir agar order tak nyangkut tanpa approver" (`SalesOrdersPage.vue:880`). **Akar:** route supervisors gated `sales.sales-orders.create` (`cmd/server/main.go:1071`), sedangkan live DB hanya memberi store_manager `sales.sales-orders.view` (000044/000089). Matriks role DIKELOLA RUNTIME via ERP Role Management (migration 000061 wipe-all + 000081), jadi live ≠ migrations: `sales@` justru PUNYA create/confirm di live. **STATUS TERBUKA (butuh keputusan + perubahan data):** beri `sales.sales-orders.create` (+ view) ke store_manager via Role Management DB dev, atau turunkan gate route supervisors ke `view`. Submit sebagai sales sendiri tetap berhasil (201) — hanya Kepala Toko yang terblokir. #### R3-7 — P2 — /sales-vouchers → 403 "Forbidden: application access required" untuk semua sesi ERP biasa **Akar:** route voucher dipagari `ecomGate` (`cmd/server/main.go:250-265`) yang menuntut application scope `AppECommerceAdmin` SEBELUM cek permission — sesi ERP biasa selalu 403. ERP frontend juga tidak punya halaman voucher penjualan (hanya purchasing vouchers di /purchasing/vouchers). Hanya admin (app_access=platform, bypass) yang bisa membuat voucher via API — begitulah cara QA-SWEEP-R3-5000 dibuat. **STATUS TERBUKA (keputusan produk):** voucher penjualan dikelola di ERP (tambah halaman + gate permission `sales.sales-vouchers.*` tanpa app-scope) atau memang disengaja e-commerce-only — konfirmasi dulu sebelum mengubah. #### R3-8 — P1 — Tombol approve Sales Order disembunyikan untuk semua approver; SPV/BM/GM tak bisa akses halaman sama sekali **Gejala:** sales tidak melihat tombol approve meski backend mengizinkan (confirm sebagai sales → 200). Lebih parah: SPV/BM/GM live TIDAK punya `sales.sales-orders.view`, `sales.sales-orders.confirm`, bahkan `dashboard.view` (diverifikasi via login API; 18/24/26 permission keys) → router guard ERP (meta.perm + fallback dashboard.view → clearSession → login?denied=1, `src/router/index.js:567-613`) memantulkan mereka sebelum halaman terbuka. Bulk approve praktis hanya bisa admin. **Akar:** (1) `canApproveOrders` di `src/stores/auth.js:57` hardcode `['spv','bm','gm','admin','system']` alih-alih cek permission; (2) matriks role live memang tidak memberi izin sales-orders ke role approver (satu akar dengan R3-6 — dikelola runtime, bukan migrations). **STATUS TERBUKA:** ganti hardcode dengan cek permission (`sales.sales-orders.confirm`), lalu beri izin view/confirm ke role approver via Role Management di DB dev. Sampai itu, approval order yang disubmit sales hanya bisa diproses admin. ### Data QA yang tertinggal (sengaja dibiarkan) | Artefak | Status | Alasan | |---|---|---| | SO-202610-000035 (id 412) | invoiced | bukti FK-katalog OK + confirm=sales; invoiced tak bisa dibatalkan | | SO-202610-000037 (id 414) | invoiced | bukti bulk approve (PATCH status) admin | | SO-202610-000036 (id 413) | cancelled | draft QA — dibatalkan (cleanup ✅) | | SO-202610-000038 | completed | bukti redeem voucher kasir (total Rp 0) | | Voucher QA-SWEEP-R3-5000 (id 4) | used | terpakai di SO-202610-000038; tidak dihapus (bukan artefak lagi) | | GLN-202610-000001 (id 4) | completed | bukti galon cancel guard 409 (completed tak bisa cancel) | | RTN-202610-000001 | sukses | bukti retur kasir | | SO-202610-000034 | completed | bukti pembayaran kartu | | SO-202610-000032 / 000033 | completed | (sesi pertama) walk-in & member QA; keduanya lunas valid | ## 5. Belum dites (setelah sesi kedua) - **B2B live terblokir:** tidak ada kredensial buyer seed di dev — butuh akun pembeli dibuat dulu sebelum sweep B2B live. - **Scan barcode POS:** tak ada produk berbarcode di katalog dev. - **QRIS end-to-end:** butuh Midtrans sandbox (R3-3 tetap terbuka). - **Bulk approve sebagai SPV/BM/GM:** terblokir oleh R3-8 (role tidak punya izin — bug itu sendiri); dites ulang setelah fix. - Login kasir non-seed (belum ada akun POS non-seed di dev). - R2-1/R2-6/R2-4/R2-3/#15 tetap di backlog. ## 6. Urutan kerja yang disarankan 1. ~~R3-2~~ — **SUDAH DIPERBAIKI** di working tree POS frontend (uncommitted). 2. ~~R3-1~~ — **SUDAH DIPERBAIKI** di working tree backend (uncommitted) + jalankan SQL data-dev di atas pada DB dev. 3. ~~R3-4b~~ — **SUDAH DIPERBAIKI** di working tree POS frontend (uncommitted, test `cart.walkin.test.js`). 4. ~~R3-5~~ — **SUDAH DIPERBAIKI** di working tree POS frontend (uncommitted, test `galon.walkin.test.js`, 50/50). 5. Script pembersihan artefak R3-4 (SELECT preview → UPDATE nonaktif). 6. R3-6 + R3-8 (satu paket): beri izin view/create/confirm sales-orders ke role approver (store_manager, spv, bm, gm) via ERP Role Management di DB dev; ganti `canApproveOrders` hardcode (`src/stores/auth.js:57`) dengan cek permission; dites ulang bulk approve sebagai SPV/BM/GM. 7. R3-7: putuskan pengelolaan voucher penjualan (halaman ERP + gate permission tanpa app-scope vs e-commerce-only). 8. Lanjut backlog R2. 9. Commit per repo setelah review (`fix(pos): …`, `fix(sales): …`), body menjelaskan "kenapa" (rujuk R3-2/R3-1/R3-4b/R3-5).