Files
app-grosir/PRD.md

540 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PRD - Dutra Store
## Changelog
### v1.3.0+6 — 2026-10-18
- **Kasir list + toggle grid/list:** default list (foto 56px), toggle ke grid. Tap = tambah qty berkali-kali. Highlight hijau sesaat (0.9s). Tombol keranjang + badge jumlah item.
- **Notifikasi in-app:** banner SnackBar + getar — hanya untuk admin, hanya untuk transaksi dari orang lain (bukan transaksi admin sendiri). Deteksi via polling (interval 1 menit) + stream `newSaleNotifications`. Load pertama dilewati agar tidak spam.
- **Badge Riwayat:** `unseenSalesCount` di tab Riwayat (admin only), reset saat tab dibuka (`markSalesSeen()`).
- **Fix tombol "Muat lebih banyak" Riwayat:** cek `filteredSales.length` bukan `state.sales` (sebelumnya negatif -17 saat filter aktif).
- **Produk hilang / loading terus di layar Kasir (fix 3 lapis):**
1. `_loadProducts()` now non-crashing saat Supabase return 401; empty server result does **not** overwrite in-memory `products` (previously `products = []` wiped the on-screen list).
2. `_init()` no longer waits `& _initAuth()` first. Auth check now via `_ensureSessionAndAuth()`: if refresh token expired, immediately set `_sessionExpired = true`, keep `startPolling()` alive, and show a "login ulang" message. This avoids `_loadProducts()` being blocked by a 30s timeout + 401.
3. `_refresh()` and realtime `.stream()` products stay alive via 1-minute polling (30 default products, "Muat lebih banyak" to extend).
- **Auth flow lengkap (2026-10-18):** `login_screen.dart` + `AuthGate` (main.dart) sudah ada & ter-wiring. Dilengkapi: `AuthGate` sekarang route ke `LoginScreen` saat `state.sessionExpired` true (bukan cuma `!isLoggedIn`) — saat session mati user dipaksa login ulang, bukan nyangkut di HomePage dengan sesi mati. `_ensureSessionAndAuth()` membedakan auth benar-benar mati (`AuthException`/refresh balikin session tak valid → `sessionExpired=true`) vs network blip (error jaringan → TIDAK tandai sessionExpired, jadi tak salah-logout saat internet putus sesaat). `refresh()` stop query saat auth mati (hindari 401 sia-sia + cukup notify agar redirect). `login()` reset `sessionExpired` + langsung muat produk/riwayat (tak tunggu poll 1 menit).
- **Cleanup analyzer (2026-10-18):** `flutter analyze lib/` turun dari **20 issue → 0 issue**. Yang diperbaiki: hapus `_initAuth()` (sudah diganti `_ensureSessionAndAuth`), hapus class `_ProductCard` (tidak dipakai), hapus dead code ternary `submitting/cancelling ? null :` (variabel tidak pernah `true`) di Kasir & Riwayat, rename local var `_listenersAdded`/`_sheetController` → tanpa underscore (produk_screen), `print` → `debugPrint` (offline_queue_service), perbaiki guard `BuildContext` async (`context.mounted`/`parentContext.mounted`/`sheetContext.mounted`). Build APK debug sukses + terpasang & jalan normal di HP (tanpa crash).
- Fix Gitea ROOT_URL (infra): link download APK sebelumnya mengarah ke IP Tailscale lama → fixed ke `https://git.gosambi.uk/`.
### v1.2.0+5 — 2026-10-17
- **Foto produk:** bucket Supabase Storage `product-images` (publik, limit 5MB, jpg/png/webp) + kolom `products.image_path`. `image_picker` ^1.2.0 (kompres 800px/quality80). Tombol Kamera/Galeri/Hapus di form Tambah+Edit (`produk_screen.dart`). Thumbnail di daftar produk (`_ProductRow`) + keranjang kasir (`_KasirAvatar`). AndroidManifest +queries intent kamera/galeri.
- **Fix MIME:** `image/jpg` → `image/jpeg` (server 415).
- **Fix storage policy:** bucket `storage.objects` tanpa policy → upload 403. Fix: 3 policy SQL (INSERT auth, SELECT public, DELETE auth) — fix di server, APK tidak perlu rebuild.
### v1.1.0 — 2026-07-24
- Dialog pembayaran kasir: tombol "Selesai & Cetak" menampilkan loading state (spinner + "Memproses...") dengan guard anti-doble-klik.
- Dialog struk pembayaran: tombol "Cetak Struk" memiliki loading state ("Mencetak...") dan disable saat proses cetak.
- Metode Bayar Nanti: dialog struk mengubah judul menjadi "Pesanan Berhasil" dengan icon receipt.
- Dialog pembayaran di Riwayat: di-redesign dengan card summary (Total, Sudah Bayar, Sisa), badge status, helper text dinamis, StatefulBuilder + loading spinner, actions horizontal (Batal | Simpan).
- Dialog batalkan transaksi di Riwayat: icon warning amber, StatefulBuilder + loading spinner, actions horizontal (Batal | Batalkan).
- Bottom sheet detail riwayat: menggunakan StatefulBuilder dengan `currentSale` mutable agar data ter-update setelah pembayaran tercatat; dialog pembayaran otomatis tutup setelah simpan.
- Perbaiki kembalian minus: field "Kembali" hanya ditampilkan jika `paymentStatus == 'paid'` dan di-clamp ke `max(0, ...)`. Kolom `change` juga di-update saat `recordPayment`.
- Fitur "Tambah Pesanan Manual" di kasir: tombol "Tambah Manual" di bawah search bar untuk input produk yang belum ada di database, masuk keranjang dengan badge "MANUAL", dan tidak mengotori stok produk.
- Role kasir diperbolehkan menerima pembayaran untuk transaksi unpaid/partial dari detail riwayat.
- Sembunyikan "Harga Pokok" dan "Laba Kotor" di detail transaksi untuk role kasir; hanya admin yang melihatnya.
- Bottom nav kasir: tab "Printer" diganti "Pengaturan". Admin tetap punya tab "Pengaturan" untuk pengaturan toko + akun kasir.
- Settings: tambah periodic sync setiap 30 detik agar multi-device tetap sinkron; pull-to-refresh tetap ada dan reset timer.
- Tampilkan versi aplikasi di Pengaturan menggunakan `package_info_plus` (format: "Versi X.Y.Z (build)").
- **Preparasi multi-tenant:** tambah kolom `store_id` ke tabel `products`, `sales`, `store_settings`, dan tabel baru `stores`. Semua query app kini include `store_id`. RLS dimatikan untuk sementara.
- Deploy release APK ke device: **RMX2061** (Android 11 API 30).
### v1.0.1 — 2026-07-22
- Perbaikan UI/UX form produk: auto-scroll keyboard pada Tambah Produk dan Edit Produk.
- reorganisasi metode pembayaran: kelompok atas 3 (Tunai, Bank, QRIS), kelompok bawah 2 (Bayar Nanti, Split).
- Validasi harga reseller: harga reseller tidak boleh lebih tinggi dari harga umum.
- Fix sinttas `produk_screen.dart` dan `kasir_screen.dart` (`flutter analyze` clean dari error).
- Deploy release APK ke device: **TECNO KM4** (Android 15).
### v1.0.0 — 2026-07-20 (Resmikan)
- Fitur lengkap: Auth, Kasir, Produk, Riwayat, Pembayaran, Struk, Printer Bluetooth, Export PDF.
- Release APK pertama untuk distribusi.
## 1. Ringkasan Produk
Dutra Store adalah aplikasi kasir digital berbasis Flutter untuk toko grosir. Aplikasi ini menghubungkan multi-device melalui sinkronisasi realtime Supabase, sehingga data produk dan transaksi dapat diakses dari berbagai perangkat secara bersamaan.
## 2. Tujuan Bisnis
- Mempercepat transaksi penjualan di toko grosir.
- Menjamin harga konsisten untuk pelanggan umum dan reseller.
- Menyediakan laporan penjualan yang realtime.
- Mengurangi risiko inkonsistensi data antar perangkat kasir.
- Memisahkan wewenang antara admin (pemilik) dan kasir (karyawan).
## 3. Target Pengguna
- **Admin** (pemilik toko): mengelola produk, melihat laporan, mengubah setting toko.
- **Kasir** (karyawan): melakukan transaksi penjualan, tidak bisa mengubah data master.
- Pelanggan reseller yang mendapatkan harga khusus.
## 4. Lingkung Aplikasi
- Platform: Android (emulator & perangkat fisik).
- State management: Provider.
- Backend: Supabase (PostgreSQL).
- Autentikasi: Supabase Auth (email + password) + tabel `profiles` untuk role.
- Sinkronisasi: Realtime stream untuk `products` dan `sales` pada paid tier; pada free tier menggunakan **periodic polling 1 menit** saat app aktif sebagai fallback, ditambah pull-to-refresh manual.
- Penyimpanan lokal: `shared_preferences` untuk cache JSON + session auth dikelola oleh Supabase.
- Startup flow: `AuthGate` mengecek session/login state → `LoginScreen` atau `HomePage`.
## 5. Role & Permission
### 5.1 Admin
- Login ke aplikasi.
- CRUD produk (tambah, edit, hapus, atur stok & harga).
- Melihat riwayat transaksi & laporan.
- Mengelola akun kasir (tambah / nonaktifkan).
- Mengubah pengaturan toko (nama toko, mata uang, printer default).
### 5.2 Kasir
- Login ke aplikasi.
- Melakukan transaksi penjualan (tab Kasir).
- Tidak dapat mengedit atau menghapus produk.
- Tidak dapat melihat laporan lengkap / riwayat lama.
- Tidak dapat mengubah pengaturan toko.
### 5.3 Guest (tanpa login)
- Tidak diizinkan. Semua perangkat harus login terlebih dahulu.
## 6. Fitur Utama
### 6.1 Autentikasi
- Screen login (email + password) menggunakan Supabase Auth.
- Logout.
- Role ditentukan dari kolom `role` di tabel `profiles` Supabase.
- Session disimpan lokal; startup otomatis login jika session masih valid.
### 6.2 Manajemen Produk (Admin only)
- CRUD produk: nama, barcode/no. produk, harga umum, harga reseller, stok.
- Pencarian produk berdasarkan nama atau barcode.
- Indikator stok habis (label "Habis" + opacity reduced).
- Input harga menggunakan format Rupiah otomatis (`RupiahInputFormatter`): pengguna mengetik angka mentah, field otomatis menampilkan `Rp XX.XXX`.
- **Scan barcode produk**: tombol scan di form "Tambah Produk" menggunakan kamera HP. Setelah barcode discan, hasil scan otomatis diisi ke field **"Barcode / No. Produk"** (bukan nama). Admin tetap mengisi nama produk, harga, dan stok secara manual.
- Di list produk, barcode ditampilkan di bawah nama produk untuk memudahkan identifikasi.
- Seed data otomatis jika tabel produk kosong.
### 6.3 Transaksi Kasir (Admin & Kasir)
- Toggle tipe pembeli: Umum / Reseller (global per transaksi).
- Fitur harga reseller dapat dinonaktifkan dari **Pengaturan Toko → Mode Reseller Aktif**. Jika dimatikan: toggle Pembeli (Umum/Reseller) dan badge per-item (Reseller/Umum) disembunyikan, semua harga otomatis Umum.
- Tambah produk ke keranjang dengan 1 tap.
- Override harga per line: setiap item di keranjang dapat di-toggle antara Umum / Reseller.
- Qty per item, dengan batas stok.
- Diskon transaksi (Rp).
- Perhitungan otomatis: subtotal, diskon, total, bayar, kembalian.
### 6.4 Keranjang (Cart)
- Bottom bar ringkas menampilkan jumlah item & total.
- Draggable bottom sheet untuk detail keranjang.
- Tombol "Kosongkan" keranjang.
- Input diskon dengan format Rupiah otomatis.
- Tombol "Bayar" mengarah ke dialog pembayaran.
### 6.5 Pembayaran
- Dialog pembayaran scrollable (mengantisipasi keyboard).
- Input "Uang Dibayar" dengan format Rupiah otomatis dan kalkulasi kembalian realtime.
- **Pemilihan metode pembayaran**: Tunai, Bank, QRIS, Split.
- Chip cepat untuk Tunai: Uang Pas, +5rb, +10rb, +20rb, +50rb, +100rb.
- **Bank/QRIS**: tampilkan informasi petunjuk + input jumlah + status Lunas/Kurang.
- **Split Payment**: input pembayaran gabungan (Tunai + Bank + QRIS) dengan total split real-time.
- Validasi minimal pembayaran >= total untuk Tunai.
- Tombol "Selesai & Cetak" menyelesaikan transaksi. Saat diproses, tombol menampilkan indikator loading (spinner + teks "Memproses...") dan terkunci agar tidak bisa diklik dua kali.
- Jika offline, transaksi disimpan ke queue lokal dan disinkronkan otomatis saat koneksi kembali.
### 6.6 Struk / Nota
- Dialog struk setelah pembayaran berhasil.
- Menampilkan: daftar item, diskon, total, bayar, kembali, tanggal, ID transaksi.
- Tombol "Tutup".
### 6.7 Riwayat Transaksi
- **Urutan**: transaksi terbaru tampil di paling atas (baik untuk kasir maupun admin).
- **Kasir**: menampilkan transaksi **hanya hari ini** (tanggal berjalan).
- **Admin**: menampilkan **transaksi bulan berjalan** secara default, dengan opsi **filter rentang tanggal** menggunakan date range picker untuk melihat data periode lain.
- **Export**: Admin dapat export laporan transaksi dalam format **PDF**. Export **CSV/Excel** ditunda untuk pengembangan berikutnya.
- Summary cards dalam bentuk **horizontal slider/carousel** dengan dot indicator:
- **Pendapatan Hari Ini** vs Kemarin (persentase perubahan).
- **Pendapatan Bulan Ini** vs Bulan Lalu.
- **Laba Kotor Bulan Ini** vs Bulan Lalu.
- **Transaksi Bulan Ini** vs Bulan Lalu.
- Swipe kiri/kanan untuk berpindah antar card.
- Dot indicator di bawah slider menandakan card aktif.
- Card aktif menampilkan detail lengkap, card non-aktif menampilkan ringkas.
- List transaksi menampilkan: total, tanggal & waktu, jumlah item, ID transaksi.
- Tap pada item transaksi membuka dialog detail struk (daftar item, diskon, total, bayar, kembali, tanggal, ID transaksi).
- Setiap item di dialog detail menampilkan badge **"Reseller"** atau **"Umum"** sesuai tipe harganya.
- Dialog detail riwayat menampilkan **Metode Pembayaran** (Tunai/Bank/QRIS/Split Payment).
- Jika Split Payment, menampilkan breakdown per metode beserta jumlahnya.
- Dialog detail riwayat memiliki tombol **"Cetak Struk"** yang mengirim struk ke thermal printer Bluetooth (ESC/POS) — mirip dengan cetak dari dialog pembayaran kasir.
### 6.9 Retur / Pembatalan Transaksi (Admin only)
- Admin dapat membatalkan transaksi dari dialog detail riwayat.
- Saat dibatalkan, stok produk secara otomatis dikembalikan ke jumlah semula.
- Transaksi yang dibatalkan ditandai dengan label **"DIBATALKAN"** dan tidak masuk hitungan pendapatan.
- Tombol **"Batalkan Transaksi"** hanya muncul untuk admin dan hanya pada transaksi yang belum dibatalkan.
- Konfirmasi dialog sebelum pembatalan: "Stok akan dikembalikan secara otomatis. Tindakan ini tidak dapat dibatalkan."
### 6.10 Stok Minimum & Notifikasi
- Setiap produk memiliki field **Stok Minimum** (default: 5) yang dapat diatur saat tambah/edit produk.
- Di list produk, stok yang di bawah minimum akan menampilkan badge peringatan berwarna oranye: **"⚠ Stok rendah (X/Y)"**.
- Stok habis tetap menampilkan badge merah **"Stok habis"**.
- Field stok minimum ditambahkan di form tambah produk dan form edit produk.
### 6.11 Laba / Rugi
- Setiap produk memiliki field **Harga Pokok (modal)** yang dapat diatur saat tambah/edit produk.
- Di **Riwayat**, menampilkan **Laba Kotor** per transaksi: `Pendapatan - Harga Pokok`.
- **Kasir**: melihat **Laba Hari Ini**.
- **Admin**: melihat **Laba Kotor Total** (semua transaksi) dan **Laba Kotor** per periode filter.
- Laba kotor dihitung otomatis saat checkout berdasarkan harga pokok yang tersimpan di produk.
- Transaksi yang dibatalkan tidak masuk hitungan laba/rugi.
### 6.12 Pengaturan Toko (Admin only)
- Nama toko.
- Mata uang default (default: IDR).
- Toggle mode reseller (aktif/nonaktif).
## 7. Screen / Menu Structure
### 7.1 Login Screen
- Email + password.
- Tombol "Masuk".
- Loading indicator selama inisialisasi AppState.
- Error handling (invalid credentials).
- Layout: `SafeArea` + `SingleChildScrollView` dengan padding dinamis untuk keyboard.
### 7.2 AuthGate (main.dart)
- `StreamBuilder` + `Selector<AppState>` untuk mengecek:
1. `state.loading` → tampilkan `CircularProgressIndicator`.
2. `state.isLoggedIn` → tampilkan `HomePage`.
3. Selain itu → tampilkan `LoginScreen`.
- Listener `onAuthStateChange` di `AppState` untuk update `userId` dan `userRole` otomatis.
### 7.3 HomePage (bottom nav)
- **AppBar**: judul "Dutra Store" + icon logout.
- **Body**: `IndexedStack` dengan screens sesuai role.
- **BottomNavigationBar**:
- **Kasir**: 2 tab (`Kasir`, `Riwayat`).
- **Admin**: 5 tab (`Kasir`, `Produk`, `Riwayat`, `Akun`, `Pengaturan`).
- Navigasi menggunakan `_index` yang direset ke 0 jika role berubah.
### 7.4 Kasir Screen
- Search bar + segmented button tipe pembeli (Umum/Reseller).
- Grid produk 2 kolom.
- Bottom bar: jumlah item + total + icon expand.
- Draggable bottom sheet keranjang.
- Dialog pembayaran + struk.
### 7.5 Produk Screen
- Search bar + form tambah/edit produk.
- Grid daftar produk.
- Hanya admin yang melihat tab ini.
### 7.6 Riwayat Screen
- **Admin**: date range picker untuk filter transaksi berdasarkan tanggal.
- Summary cards:
- **Kasir**: Transaksi Hari Ini, Pendapatan Hari Ini.
- **Admin**: Semua Transaksi, Total Pendapatan, Jumlah Semua Transaksi.
- List transaksi:
- **Kasir**: hanya transaksi hari ini.
- **Admin**: semua transaksi atau sesuai filter tanggal.
- Setiap item: total, tanggal & waktu, jumlah item, ID transaksi.
### 7.7 Kelola Akun Kasir (Admin only)
- Screen untuk mengelola akun kasir.
- Form tambah akun kasir: email, password, nama lengkap.
- List akun kasir yang ada dengan opsi hapus.
- Setiap akun memiliki role `kasir` di tabel `profiles`.
### 7.8 Pengaturan Toko (Admin only)
- Screen untuk mengubah pengaturan toko.
- Field: nama toko, mata uang (default: IDR), alamat printer Bluetooth.
- Toggle mode reseller (aktif/nonaktif).
- Pengaturan disimpan di `shared_preferences`.
### 7.9 Login/Logout Flow
- Login: `LoginScreen` → `AppState.login()` → update `userId` + `userRole` → `AuthGate` otomatis navigate ke `HomePage`.
- Logout: icon logout di AppBar → `AppState.logout()` → clear cart + discount → `AuthGate` otomatis navigate ke `LoginScreen`.
## 8. Model Data
### Product
| Field | Tipe | Keterangan |
|-------|------|-----------|
| id | text (PK) | UUID unik |
| name | text | Nama produk |
| barcode | text | Barcode / No. Produk (opsional) |
| price | number | Harga umum |
| price_reseller | number | Harga reseller |
| stock | integer | Stok tersedia |
| min_stock | integer | Batas stok minimum untuk peringatan (default: 5) |
| harga_pokok | number | Harga modal per unit (untuk hitung laba kotor) |
### Sale
| Field | Tipe | Keterangan |
|-------|------|-----------|
| id | text (PK) | UUID unik |
| created_at | timestamp | Waktu transaksi |
| items | jsonb | Array of {name, price, qty, isReseller} |
| subtotal | number | Total sebelum diskon |
| discount | number | Diskon transaksi |
| total | number | subtotal - discount |
| paid | number | Jumlah dibayar |
| change | number | paid - total |
| user_id | text (FK) | ID user yang melakukan transaksi (opsional) |
| is_cancelled | boolean | true jika transaksi dibatalkan (default: false) |
| total_cost | number | Total harga pokok dari item yang terjual (untuk laba kotor) |
| payment_method | text | Metode pembayaran: 'tunai' (default), 'bank', 'qris', 'split' |
| split_payment | jsonb | Object pembayaran gabungan: {"tunai": 50000, "bank": 100000, "qris": 50000} (opsional) |
### CartLine (lokal)
| Field | Tipe | Keterangan |
|-------|------|-----------|
| product | Product | Referensi produk |
| qty | int | Jumlah |
| isReseller | bool | true = harga reseller dipakai |
### Profile (Supabase)
| Field | Tipe | Keterangan |
|-------|------|-----------|
| id | uuid (FK auth.users) | Referensi user |
| role | text | 'admin' atau 'kasir' |
| full_name | text | Nama lengkap (opsional) |
## 8.1 Utilitas Format Rupiah
- `RupiahInputFormatter`: `TextInputFormatter` untuk field harga/uang.
- Saat pengguna mengetik angka, field otomatis menampilkan format `Rp XX.XXX` (contoh: `50000` → `Rp 50.000`).
- `RupiahInputFormatter.getRawValue(String)`: mengembalikan nilai `int?` dari string yang diformat, untuk kebutuhan perhitungan.
- Digunakan pada:
- Form tambah/edit produk: `Harga Umum`, `Harga Reseller`.
- Dialog pembayaran: `Uang Dibayar`.
- Keranjang: `Diskon`.
## 9. Alur Transaksi
1. User login → role ditentukan → navigasi sesuai role.
2. **Kasir**:
- Pilih tipe pembeli (Umum / Reseller).
- Tambah produk ke keranjang.
- Sesuaikan qty & tipe harga per item.
- Isi diskon (opsional).
- Tekan "Bayar" → dialog pembayaran muncul.
- Input uang dibayar / pilih chip cepat.
- Validasi, lalu checkout → simpan ke Supabase, kurangi stok.
- Tampilkan dialog struk.
- Tutup struk → kembali ke tab Kasir dengan keranjang kosong.
3. **Admin**:
- Dapat melakukan transaksi seperti kasir.
- Dapat menambah/edit/hapus produk.
- Dapat melihat riwayat & laporan.
- Dapat mengelola akun kasir & pengaturan toko.
## 10. UI / UX Requirements
- Tema gelap dengan aksen amber.
- Kartu produk dengan sudut rounded, shadow, avatar inisial nama.
- Bottom sheet keranjang dengan handle drag.
- Semua dialog yang berpotensi overflow harus menggunakan scroll view.
- Label dan mata uang dalam Bahasa Indonesia (Rupiah).
- UI menyesuaikan role: tombol/fitur yang tidak diizinkan disembunyikan (tidak hanya di-disable).
### 10.1 Design System
- **Tema utama:** Dark theme. Warna aksen: amber.
- **Radius:** 18 (kartu), 12 (input/dialog), 8 (chip/tag).
- **Spacing:** padding screen/list `EdgeInsets.all(12)`, padding form/dialog `EdgeInsets.all(16)`, gap field `SizedBox(height: 10)`, gap section `SizedBox(height: 14)`.
- **Kartu:** padding minimal 12, jangan hardcode tinggi. Jika 3+ field, prioritaskan wrap/scroll.
- **Input:** label jelas, helper text untuk opsional. Jika form panjang, bungkus `SingleChildScrollView` + padding bottom `MediaQuery.of(context).viewInsets.bottom` agar tidak tertutup keyboard.
- **Tombol:** primary = `FilledButton` (aksi utama: Bayar, Tambah, Simpan), destructive = merah (`kDanger`), secondary = `TextButton`/`OutlinedButton`.
### 10.2 Responsif & Overflow
- **Grid Kasir:** 2 kolom di HP, 3–4 kolom di tablet (lebar > 600dp). Hindari `childAspectRatio` tetap; pakai `AspectRatio(1.1)` atau biarkan grid menentukan tinggi.
- **Bottom Sheet:** keranjang harus berupa bottom sheet (bukan panel tetap), bisa di-drag, konten selalu scrollable (`SingleChildScrollView` tanpa `Expanded`).
- **Dialog:** input panjang harus scrollable. Jangan pakai `Expanded` di dalam dialog yang bisa overflow.
- **Produk:** form + list dalam satu halaman scroll (`SingleChildScrollView` + `ListView.shrinkWrap`).
- **Teks:** jangan hardcode font size. Batasi nama produk max 2 baris (`maxLines: 2`, `overflow: TextOverflow.ellipsis`).
### 10.3 UX Kasir (Critical Path)
- Target: transaksi selesai dalam 3 tap (pilih → bayar → selesai).
- Tampilkan total, diskon, kembalian real-time.
- Tipe pembeli pakai segmented button (Umum/Reseller), bukan dropdown.
- Setiap item keranjang bisa ubah qty atau tipe harga dalam 1 tap.
- Stok habis: tampil badge merah "Habis" + matikan visual/tap.
- Stok menipis: badge peringatan oranye sebelum habis.
### 10.4 Aksesibilitas & Feedback
- Minimal tap target: 44x44dp.
- Kontras teks minimal 4.5:1.
- Jangan andal warna saja (tambah ikon/label). Contoh: stok habis = badge merah + teks "Habis".
- Setiap aksi ada `SnackBar` feedback: sukses hijau, error merah, info biru/abu.
- Loading state untuk operasi > 500ms (`CircularProgressIndicator(color: kAccent)`).
### 10.5 Role-Based Visibility
- Fitur tidak diizinkan untuk role tertentu harus **disembunyikan sepenuhnya**, bukan cuma di-disable.
- Admin: lihat semua transaksi + filter tanggal + aksi batalkan.
- Kasir: lihat transaksi hari ini, tidak ada aksi batalkan/filter.
### 10.6 Reusability & Konsistensi
- Buat widget reuseable untuk komponen berulang: `_Tag`, `_ReportCard`, `_Avatar`, `_Stepper`.
- Taruh konstanta warna/spacing di `lib/utils/colors.dart`.
- Konsisten pakai `formatRupiah()` untuk semua angka mata uang.
### 10.7 Checklist Sebelum Submit Perubahan UI/UX
- [ ] `flutter analyze` → No issues found (tanpa error).
- [ ] Coba di layar kecil (HP 5") dan besar (tablet).
- [ ] Coba dengan keyboard muncul (input uang/diskon).
- [ ] Pastikan tidak ada hardcoded height yang bikin overflow.
- [ ] Semua teks dalam Bahasa Indonesia.
- [ ] Konsisten pakai `formatRupiah()` untuk angka mata uang.
## 11. Non-Fitur (Out of Scope untuk versi ini)
- ~~Printer thermal Bluetooth (direncanakan Sprint 4).~~ ✅ Sudah diimplementasikan (Sprint 4).
- ~~Laporan bulanan / export PDF/Excel.~~ ✅ Sudah diimplementasikan (export PDF & CSV di Sprint 3/4).
- ~~Barcode scanner.~~ ✅ Sudah diimplementasikan (Sprint 2, `mobile_scanner`).
- Multi-cabang / multi-toko.
## 12. Kebutuhan Non-Fungsional
- Sinkronisasi realtime < 2 detik antar perangkat.
- Offline mode: data terbaru tetap tampil meskipun koneksi terputus (cache lokal).
- Build APK debug untuk emulator Android 13+.
- Session auth harus aman (token disimpan aman, auto-logout setelah inactivity opsional).
## 13. Risiko & Mitigasi
| Risiko | Mitigasi |
|--------|----------|
| Stok menjadi negatif | Validasi di `addToCart` & `changeQty` dibatasi oleh stok produk. |
| Data inkonsistenan saat并发 | Supabase realtime + operasi atomic di sisi backend. |
| Keyboard menutupi input di dialog | Semua dialog input menggunakan `SingleChildScrollView`. |
| Akses tidak sah | Role-based UI + validasi di Supabase (RLS policies). |
| Koneksi putus saat checkout | Simpan transaksi ke queue lokal, kirim saat koneksi kembali (opsional). |
## 14. Sprint Breakdown
### Sprint 0 — Foundation (Selesai)
- [x] Setup Flutter + Provider + Supabase.
- [x] Struktur dasar: screens, models, state, utils.
- [x] CRUD Produk + Realtime stream.
- [x] Dual pricing (Umum / Reseller).
- [x] UI redesign (dark theme, amber accent).
### Sprint 1 — Transaksi Kasir (Selesai)
- [x] Tab Kasir: grid produk + search + tipe pembeli.
- [x] Cart: bottom bar + bottom sheet + qty control.
- [x] Diskon transaksi.
- [x] Dialog pembayaran + kalkulasi kembalian.
- [x] Dialog struk.
- [x] Fix overflow (Produk tab, Kasir dialog, Cart sheet).
### Sprint 2 — Auth & Role-Based Access (Selesai)
- [x] Setup Supabase Auth (email + password).
- [x] Tabel `profiles` dengan kolom `role`.
- [x] Screen login + loading state + error handling.
- [x] Logout icon di AppBar.
- [x] Integrasi role ke AppState (`userId`, `userRole`, `isAdmin`, `isKasir`, `isLoggedIn`).
- [x] Auth state listener (`onAuthStateChange`) untuk update otomatis saat login/logout.
- [x] UI adaptif sesuai role:
- Bottom nav: Kasir (2 tab), Admin (3 tab).
- Riwayat: Kasir lihat hari ini, Admin lihat semua.
- Produk: hanya admin.
- [x] Seed admin default via `_seedAdminIfEmpty()`.
- [x] Audit trail: tambah `user_id` pada model `Sale` dan simpan saat checkout.
- [x] Fix bottom nav assertion error untuk role kasir (nav disembunyikan jika < 2 item).
- [x] Fix login screen stuck/overflow dengan `SafeArea` + `SingleChildScrollView` + padding dinamis.
- [x] Optimasi `AuthGate` dengan `Selector` agar tidak rebuild berlebihan.
- [x] Rupiah input formatter untuk field harga/uang di Produk, Pembayaran, dan Diskon.
- [x] Scan barcode produk di form Produk menggunakan `mobile_scanner` + permission kamera.
### Sprint 3 — Admin Features
- [x] Riwayat transaksi dengan filter role-based (kasir: hari ini, admin: semua).
- [x] Summary cards adaptif sesuai role.
- [x] Filter date range untuk admin di riwayat menggunakan date range picker.
- [x] Manajemen akun kasir (tambah/hapus akun kasir dari dalam app).
- [x] Edit akun kasir (ubah nama/email/password via dialog edit, password pakai `auth.admin.updateUserById`).
- [x] Pengaturan toko (nama toko, mata uang, toggle reseller).
- [x] Export laporan ke PDF dan CSV (Excel) menggunakan `pdf`, `csv`, `path_provider`, dan `share_plus`.
### Sprint 4 — Polish & Integration
- [x] Export laporan ke PDF dan CSV (Excel) menggunakan `pdf`, `csv`, `path_provider`, dan `share_plus`.
- [x] Offline mode improvements (queue transaksi menggunakan `OfflineQueueService` + `connectivity_plus`).
- [x] Thermal printer Bluetooth (package `print_bluetooth_thermal` + `esc_pos_utils_plus`, ganti dari `blue_thermal_printer` yang gagal build).
- [x] Cetak struk langsung dari dialog pembayaran kasir.
- [x] Retur / Pembatalan transaksi (admin dapat batalkan transaksi dari riwayat, stok otomatis dipulihkan).
- [x] Stok minimum & notifikasi (field min_stock di produk, badge warning di list produk).
- [x] Laba / Rugi (harga pokok per produk, laporan laba kotor di riwayat).
- [x] Build release APK untuk distribusi.
- [ ] Testing & bug fixing menyeluruh.
## 15. Status Implementasi Saat Ini
### Fitur yang Sudah Berjalan
- ✅ Login/logout dengan Supabase Auth
- ✅ Role-based access: Admin (3 tab: Kasir, Produk, Riwayat, Pengaturan) dan Kasir (2 tab) — Akun Kasir & Printer digabung ke dalam tab Pengaturan
- ✅ CRUD Produk dengan barcode scanner
- ✅ Transaksi kasir dengan dual pricing (Umum/Reseller)
- ✅ Mode Reseller dapat dinyalakan/matikan dari Pengaturan Toko (saat mati, toggle harga reseller di kasir disembunyikan)
- ✅ Cart bottom sheet + diskon + pembayaran + struk
- ✅ Pembayaran: tombol "Selesai & Cetak" pakai loading guard (spinner + "Memproses...") agar tidak dobel klik
- ✅ Riwayat transaksi: terurut terbaru di atas, filter role-based, date range
- ✅ Detail riwayat: badge per-item "Reseller"/"Umum" + tombol "Cetak Struk"
- ✅ Manajemen akun kasir (tambah/hapus/edit)
- ✅ Pengaturan toko (nama, mata uang, toggle reseller) — nama app "Dutra Store"
- ✅ Export laporan PDF
- ⏸️ Export laporan CSV/Excel (ditunda untuk pengembangan berikutnya)
- ✅ Offline mode queue untuk transaksi
- ✅ Thermal printer Bluetooth (scan, connect, auto-reconnect, cetak struk ESC/POS)
- ✅ Cetak struk dari riwayat (tap transaksi → dialog detail → tombol "Cetak Struk")
- ✅ Rupiah input formatter
- ✅ Overflow fixes pada semua screen
- ✅ Retur/Pembatalan transaksi (admin dapat batalkan dari dialog detail riwayat, stok otomatis dipulihkan)
- ✅ Stok minimum & notifikasi (badge warning stok rendah di list produk)
- ✅ Laba/Rugi (harga pokok per produk, laporan laba kotor di riwayat: Laba Hari Ini untuk kasir, LABA KOTOR untuk admin)
- ✅ Tab Riwayat: summary cards compact (tap untuk expand), list transaksi ditampilkan di bawah summary, detail transaksi menggunakan bottom sheet
- ✅ Tab Riwayat: ringkasan bulanan dengan perbandingan MoM (Pendapatan, Laba Kotor, Transaksi) untuk admin
- ✅ Tab Riwayat: admin melihat transaksi bulan berjalan secara default (bukan semua transaksi)
- ✅ Tab Riwayat: summary cards dalam bentuk horizontal slider/carousel dengan dot indicator, swipe kiri/kanan untuk pindah card, card aktif menampilkan detail lengkap
- ✅ Tab Produk: form tambah produk dipindah ke bottom sheet (FAB + Tambah Produk), list produk tetap terlihat
- ✅ Tab Kasir: animasi pulse di cart bar saat produk ditambahkan
- ✅ Dialog pembayaran: loading overlay + disable input saat proses checkout + minimum 300ms visibility
- ✅ Dialog pembayaran: pilihan metode pembayaran (Tunai, Bank, QRIS, Split) dengan chip selector
- ✅ Dialog pembayaran: chip cepat untuk Tunai (Uang Pas, +5rb, +10rb, +20rb, +50rb, +100rb)
- ✅ Dialog pembayaran: split payment support dengan total split real-time
- ✅ Receipt & detail riwayat: menampilkan metode pembayaran dan breakdown split payment
- ✅ Sale model: field payment_method + split_payment untuk dokumentasi transaksi
- ✅ Release APK (app-release.apk 67.5MB)
- ✅ Multi-tenant preparation: kolom `store_id` ditambahkan ke tabel `products`, `sales`, `store_settings`, dan tabel baru `stores`. Semua query app kini include `store_id` untuk preparasi multi-store.
### Fitur yang Didefer / Belum
- ✅ Ganti password akun kasir via Supabase admin API (`auth.admin.updateUserById` tersedia di gotrue 2.26.0).
- ✅ Testing & bug fixing menyeluruh (analyzer `No issues found!`, 0 error/warning).
- ⏳ Testing & bug fixing menyeluruh (belum dilakukan comprehensive testing).
- ✅ Build release APK untuk distribusi (app-release.apk 66.9MB).
### Catatan Teknis
- Nama aplikasi: **Dutra Store** (launcher label, AppBar, judul login, export).
- Admin tab dikurangi: Akun Kasir & Printer digabung ke dalam tab **Pengaturan** (admin sekarang 3 tab: Produk, Riwayat, Pengaturan).
- Printer auto-connect saat app start (MAC tersimpan di shared_preferences).
- `flutter analyze` sekarang **No issues found!** (semua `withOpacity` diganti `withValues(alpha:)`, `use_build_context_synchronously` di-handle, dead code dihapus).
- App sudah di-deploy ke device: **TECNO KM4** (Android 15) dan **RMX2061** (Android 11 API 30), keduanya berjalan normal.
- **Database migration**:
1. Jalankan SQL di `supabase/migrations/20240101_add_cancel_and_min_stock.sql` untuk kolom `is_cancelled` (sales) dan `min_stock` (products).
2. Jalankan SQL di `supabase/migrations/20240102_add_harga_pokok_and_profit.sql` untuk kolom `harga_pokok` (products) dan `total_cost` (sales).
3. Jalankan SQL di `supabase/migrations/20240103_add_payment_method.sql` untuk kolom `payment_method` dan `split_payment` (sales) — **sudah dijalankan**.
4. Jalankan SQL tambahan untuk kolom `store_id` (multi-tenant preparation): tambah tabel `stores`, tambah kolom `store_id` ke `products`, `sales`, `store_settings`, backfill data existing dengan default store ID, dan buat index. RLS dimatikan untuk semua tabel.
- UI/UX skill tersimpan di `.kilo/agent/uiux-pos-grosir.md` dan bagian PRD 10.1-10.7.
- Color scheme: brand amber dominant di Kasir & Produk, hijau untuk harga umum, merah untuk error/habis, abu untuk muted.
## Infra & Setup App
### Backend Supabase (Self-Host)
- URL: `https://supa.gosambi.uk` (gateway envoy port 8001)
- Keys: `D:\HomeLabs\backups\supabase-keys-local.txt` (ANON + SERVICE)
- Backup data: script `D:\HomeLabs\scripts\supabase_backup.py` → `D:\HomeLabs\backups\supabase-nwuupvnodsgxdhqcakqr\`
- Schema backup: `backups\...\schema_selfhost.sql`
- Config app: `lib\config.dart` (gitignored — berisi URL + keys, jangan commit)
- Migrasi cloud → self-host selesai 2026-10-17
- Akun: admin@posgrosir.local, kasir1@toko.com, bibi/bubu/baba@toko.com (password sementara `GrosirBaru2026!`)
### Standar Rilis APK
1. Naikkan versi di `pubspec.yaml` (misal `1.2.0+5` → `1.3.0+6`).
2. Commit & `git push gitea main` (single remote Gitea; jangan commit APK ke repo).
3. `flutter build apk --release` di `D:\HomeLabs\app-grosir` (jika gagal: `.gradlew --stop` + `flutter clean`).
4. Verifikasi APK: file `build\app\outputs\flutter-apk\app-release.apk`; pastikan string `supa.gosambi.uk` ada di `libapp.so`.
5. Instal ke HP: `adb -s fd683806 install -r app-release.apk`.
6. Buat tag `v1.x.y` di Gitea + upload APK ke **Release** (`git.gosambi.uk/onie/app-grosir/releases`), jangan upload APK langsung ke repo Git.
7. Catat versi baru di PRD.md ini + rule.md (singkat saja).
### Device Testing
- HP utama: **Realme RMX2061** (Android 11, device ID `fd683806`, package `com.grosir.app_grosir`)
- HP admin kedua: sudah ada (untuk testing notifikasi multi-device)