# Workflow KeluargaMart — pembagian peran antar aplikasi Status: **sedang dikerjakan** · Terakhir diperbarui 2026-09-03 Dokumen ini mengunci satu pertanyaan: **siapa mengerjakan apa, di aplikasi mana.** Semua keputusan teknis (role, permission, menu) mengikuti tabel di bawah, bukan sebaliknya. ## 1. Model bisnis KeluargaMart bekerja seperti jaringan minimarket: - **Kantor pusat** memegang katalog produk. Produk, harga jual, kategori, dan satuan **terpusat** — outlet tidak membuat produknya sendiri. - **Outlet (toko)** menjual barang dari katalog pusat. Yang berbeda antar outlet hanya **stok**, bukan katalog. - Pelanggan membeli **offline** lewat kasir di outlet, atau **online** lewat aplikasi e-commerce. Tiga aplikasi, tiga tanggung jawab: | Aplikasi | Untuk siapa | Isinya | |---|---|---| | **ERP** | manajemen | outlet, katalog pusat, stok, keanggotaan, pengguna, laporan | | **POS** | kasir di outlet | menjual, retur, shift/laci kas, daftarkan member | | **E-Commerce** | pembeli | belanja online | ## 2. Peran | Peran | Aplikasi | Lingkup | Wewenang | |---|---|---|---| | `owner` / `gm` | ERP | semua outlet | buat & nonaktifkan outlet; angkat kepala toko; katalog produk pusat; **atur tier member & diskonnya**; laporan konsolidasi (offline + online, per outlet) | | `store_manager` (kepala toko) | ERP | **satu outlet** | stok outlet (masuk/keluar/opname/transfer); buat & nonaktifkan **akun kasir** outlet; pengaturan & identitas outlet; laporan outlet | | `cashier` (kasir) | POS | satu outlet, satu shift | jual, retur, buka/tutup shift & kas, tambah pelanggan, **daftarkan member (tier masuk)** | | `buyer` | E-Commerce | akunnya sendiri | belanja online | Peran pendukung yang sudah ada (`finance`, `warehouse`, `hr_manager`, `hr_staff`, `employee`) tidak berubah. `admin` disimpan sebagai **akun teknis** saja — bukan peran operasional sehari-hari. Aturan yang tidak boleh dilanggar: - **Kasir tidak menyentuh master data.** Tidak membuat produk, tidak mengubah harga, tidak menyesuaikan stok, tidak membuat pengguna. - **Kepala toko tidak menyentuh katalog pusat.** Ia mengatur stok dan orang di outletnya saja. - **Tier member dinaikkan dari ERP**, bukan dari kasir — supaya `membership_tier_history` selalu punya aktor dan alasan yang jelas. ## 3. Alur utama ### 3.1 Membuka outlet baru (owner/GM, di ERP) 1. ERP → Sistem → Toko → **Tambah Toko** (kode, nama, alamat, telepon). Backend otomatis membuat gudang `WH-` dan mengikatnya ke outlet (`stores.warehouse_id`) dalam satu transaksi — tak perlu menyentuh daftar gudang katalog. 2. Buat akun kepala toko, tugaskan ke outlet tersebut. 3. Kepala toko masuk ke ERP, membuat akun kasir untuk outletnya. 4. Kepala toko memasukkan stok awal outlet (ke gudang outlet: masuk langsung atau transfer dari gudang pusat). ### 3.2 Sehari di kasir (POS) 1. Kasir login → **buka shift** dengan modal awal. 2. Jual: pindai/cari produk → keranjang → pilih pelanggan (default *Pelanggan Umum*) → bayar → cetak struk. Jika pelanggan adalah member, diskon tier terpakai otomatis dan **tersimpan permanen pada baris transaksi**, sehingga retur tidak salah hitung. 3. Pelanggan ingin jadi member: **Pelanggan → Tambah Pelanggan → Jadikan Member**. Terdaftar pada tier masuk (Bronze) dengan nomor member. 4. Retur: pilih transaksi selesai → tentukan qty & alasan → metode refund. 5. Akhir shift: **tutup shift**, rekonsiliasi kas diharapkan vs kas nyata. Yang **tidak** ada di POS: dashboard analitik, laporan, katalog produk, penyesuaian stok, manajemen pengguna. ### 3.3 Keanggotaan & diskon - Tier: **Bronze, Gold, Platinum**. Diskon tersimpan di tabel `membership_tiers` (`discount_type` + `discount_value`), bukan di kode. Nilai awal: Bronze 2%, Gold 5%, Platinum 8%. - Setiap perubahan diskon tercatat di `membership_tier_config_history` (nilai lama, nilai baru, alasan, siapa). - **Kasir**: mendaftarkan member baru pada tier masuk. - **Kepala toko / GM**: menaikkan-menurunkan tier seorang member (dengan alasan — tercatat di `membership_tier_history`). - **Owner / GM**: mengubah besaran diskon tiap tier. ### 3.4 Rekap penjualan Setiap `sales_orders` membawa `store_id` (outlet asal) dan `order_type` (`pos` untuk offline, `ecommerce` untuk online). Dari dua kolom itu laporan "penjualan offline + online per outlet" bisa dibuat tanpa perubahan skema. - Kepala toko melihat outletnya. - Owner/GM melihat semua outlet, dengan rincian per outlet dan per kanal. ## 4. Yang sudah berjalan vs yang masih kurang Diaudit pada 2026-09-03 terhadap `keluargamart-backend`, `keluargamart-erp-frontend`, dan repo ini. ### Sudah ada - Outlet (`stores`) + keanggotaan pengguna ke outlet (`store_users`), termasuk UI CRUD toko dan "Kelola pengguna toko" di ERP. - Katalog produk terpusat (produk tidak punya `store_id`). - **Stok per outlet** (backend migrasi `000045`): tiap `stores` terikat ke satu `warehouses` lewat `stores.warehouse_id`. `ProcessPOS` mengurangi stok dari gudang outlet kasir (bukan lagi `FirstActiveWarehouse` global), dan retur otomatis ikut karena `sales_orders.warehouse_id` sudah benar. `/auth/me` mengembalikan `store_details` (termasuk `warehouse_id`) supaya POS menamai outlet aktif dan menyaring tampilan stok ke gudang itu. - `sales_orders.store_id` + `order_type` → dasar rekap offline/online. - Membership lengkap di backend: tabel tier + audit konfigurasi, endpoint `/memberships` dan `/memberships/tiers`, resolver diskon saat checkout, dan snapshot diskon per baris transaksi yang bersifat *immutable*. - Pemisahan aplikasi (`users.application_access` + `RequireApp`). - **POS sudah dipangkas menjadi kasir murni** (rilis v0.9.0 repo ini). - **Role `cashier` dan `store_manager` sudah ada** (backend migrasi `000044`). Kasir otomatis beraplikasi `pos`, jadi akun kasir bisa dibuat dari halaman Pengguna ERP tanpa menyentuh SQL. Kepala toko dibatasi ke outletnya lewat `store_users` dan hanya boleh mengangkat kasir — bukan admin. - **Diskon tier adalah wewenang owner/GM** (`masterdata.memberships.tiers.manage`); kasir hanya bisa mendaftarkan member, kepala toko bisa menaikkan tier. ### Masih kurang | # | Kekurangan | Dampak | |---|---|---| | 1 | ~~ERP tidak punya halaman membership~~ **SELESAI** — ERP kini punya menu **Keanggotaan**: Daftar Member (ubah tier + alasan, riwayat tier) dan Pengaturan Diskon Tier | Alur member owner/GM sudah lengkap. Sisa: backend belum ekspos endpoint baca `membership_tier_config_history`, jadi riwayat perubahan diskon belum tampil | | 2 | Belum ada penyempitan lingkup per-outlet untuk peran manajemen | GM/BM/SPV punya `sales.*` global; kepala toko sudah tersempit lewat `store_users`, peran lain belum | | 3 | Belum ada laporan penjualan per outlet dengan pemisahan kanal | Datanya ada (`sales_orders.store_id` + `order_type`), layarnya belum | | 4 | `atlas.sum` belum di-hash ulang untuk migrasi `000044`–`000045` | `atlas migrate validate` akan gagal sampai `atlas migrate hash` dijalankan. Runner di dalam server tidak memakai `atlas.sum`, jadi apply saat boot tetap jalan | | 5 | `/inventory/stock` belum meng-scope gudang otomatis dari token | POS sudah mengirim `warehouse_id` outlet secara eksplisit, tapi caller ter-scope yang lupa param masih melihat semua gudang. Pengerasan menyusul di handler inventory | | 6 | ERP belum menampilkan gudang tertaut di halaman Toko | Pembuatan toko sudah otomatis membuat `WH-` di backend; ERP hanya perlu menampilkannya (read-only) + ringkasan stok | ### Rencana lanjutan 1. ~~**ERP** — halaman Keanggotaan dan Pengaturan Diskon Tier~~ **SELESAI.** Sisa terkait: minta backend menambah endpoint baca `membership_tier_config_history` supaya riwayat perubahan diskon bisa ditampilkan di ERP. 2. **ERP** — penanda kepala toko di halaman Toko, dan laporan penjualan per outlet dengan pemisahan kanal offline/online. 3. **Backend** — sempitkan lingkup GM/BM/SPV per cabang bila client memang menginginkannya (lihat pertanyaan A.3 di catatan client). 4. **Rilis** — jalankan `atlas migrate hash --dir file://migrations` sebelum merge, dan setelah deploy pastikan akun POS lama sudah menjadi `cashier` (migrasi melakukannya otomatis untuk `application_access = 'pos'` dan untuk akun demo `cashier@keluargamart.local`).