540 lines
33 KiB
Markdown
540 lines
33 KiB
Markdown
# 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)
|
||
|
||
|