# 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). - **Kasir: indikator loading produk + empty/error state (2026-10-18):** setelah login user sempat melihat daftar kosong lalu produk muncul beberapa saat kemudian. Sebab otentik: `AuthGate → HomePage` render duluan, `refresh() → _loadProducts()` jalan belakangan tanpa indikator. Perbaikan kini: `AppState.productsLoading/dataReady/productsError` + `retryProducts()`; Kasir menampilkan skeleton daftar `_ProductsLoading` khusus saat muat awal, banner `Gagal memuat produk` + tombol `Coba lagi` saat error, dan empty-state `Belum ada produk`/`Tidak ditemukan` sesuai kasus. `refresh()` diberi retry 1x + try-catch internal sehingga `_loadSales()` tidak men-stop loop; LoginScreen tetap dipakai (tanpa loading global) agar pesan error/login tidak hilang. - 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` 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)