# Modul Pengguna & Peran (Users & Roles)

File: [userService.js](../../src/services/userService.js), [roleService.js](../../src/services/roleService.js), [UserForm.jsx](../../src/pages/users/UserForm.jsx), [RoleForm.jsx](../../src/pages/users/RoleForm.jsx), [UserList.jsx](../../src/pages/users/UserList.jsx), [RoleList.jsx](../../src/pages/users/RoleList.jsx)

> **Koreksi memori sebelumnya:** modul ini **sudah terhubung ke backend asli** — `userService.js` dan `roleService.js` **tidak punya mode dummy sama sekali** (tidak import `USE_DUMMY`, selalu panggil API langsung). Catatan lama yang bilang "intentionally stays as dummy" sudah tidak akurat — kemungkinan ini ditambahkan setelah catatan itu dibuat.

## 1. Pengguna (User)

**Field form (`UserForm.jsx`):** `username*`, `name*`, `email*`, `phone`, `role*` (dropdown dari `GET /v1/roles`), `branch_id*`, `is_active` (checkbox), `password` (wajib saat create, opsional saat edit — kosongkan = tidak ganti password), `confirm` (harus sama dengan password, validasi manual).

**Validasi sebelum submit (urutan):** `username` → `name` → `email` → `role` → `branch_id` wajib. Kalau create dan `password` kosong → wajib. Kalau `password` diisi tapi tidak sama dengan `confirm` → toast "Konfirmasi kata sandi tidak sesuai".

**Payload:** `{ username, name, email, phone?, role, branch_id, is_active, password? }` — `password` hanya disertakan kalau diisi.

**Catatan UX:** user di sistem ini **terikat ke 1 cabang** (`branch_id` singular) — bukan multi-cabang seperti Category/Product/Discount. Role disimpan sebagai string (`role`), bukan array.

**Endpoint:**
| Aksi | Method | Endpoint | Params/Body |
|---|---|---|---|
| List | GET | `/v1/users` | query `branch_id, search, page, per_page` (server-side, beda dari modul lain) |
| Detail | GET | `/v1/users/{id}` | — |
| Create | POST | `/v1/users` | body di atas |
| Update | PUT | `/v1/users/{id}` | body di atas |
| Delete | DELETE | `/v1/users/{id}` | — |
| List role (untuk dropdown) | GET | `/v1/roles` | return diolah jadi array string nama role |

**Catatan:** `userService.index` adalah **satu-satunya list di seluruh aplikasi** yang didesain untuk server-side pagination penuh (`page`, `per_page` dikirim ke backend) — beda dari mayoritas modul lain yang fetch semua data lalu paging di client. Kalau UI List tidak terlihat menerapkan ini, periksa `UserList.jsx` langsung untuk pastikan param benar-benar dikirim.

## 2. Peran (Role) — sistem permission granular

Ini form paling kompleks setelah Product. Role punya matriks permission per fitur CMS.

### Create (mode simpel)
Hanya field `name*` (auto-convert: lowercase + spasi jadi underscore, mis. "Admin Toko" → `admin_toko`). `POST /v1/roles` body `{ name }`. **Permission belum diisi saat create** — role baru lahir tanpa permission apapun, harus dibuka lagi via edit untuk konfigurasi detail.

### Edit (mode penuh — matriks permission)

**Struktur permission (`permissions_config`):**
```json
{
  "cms": { "<feature_id>": { "create": bool, "read": bool, "update": bool, "delete": bool } },
  "mobile": { "<feature_id>": { "create": bool, "read": bool, "update": bool, "delete": bool } },
  "custom": { "<feature_id>": { "export": bool, "import": bool } }
}
```

**Daftar fitur CMS** (16 fitur, masing-masing dengan 4 aksi create/read/update/delete): Produk, Inventaris-Stok, Inventaris-Bahan Baku, Master-Cabang/Modifier/Kategori/Satuan/Konversi Satuan/Meja, Diskon, Penjualan-Shift Log/Laporan Transaksi/Metode Pembayaran, Pengaturan-Pajak, Pengguna&Peran-Peran/Pengguna.

**Custom Aksi** (export/import per modul: Produk, Stok, Bahan Baku, Cabang, Modifier, Kategori, Meja, Shift Log, Laporan) — **hanya toggle "export" yang ditampilkan di UI**; toggle "import" disembunyikan secara sengaja (komentar di kode: tombol Import di halaman-halaman terkait sudah dihapus dari UI/"dead", jadi permission ini tidak ada gunanya lagi) — tapi **tetap ada di struktur data & payload** kalau backend masih mengeceknya.

**Tab "Mobile Apps" disembunyikan** dari UI (komentar di kode): permission untuk app kasir mobile **belum benar-benar di-enforce di backend** (middleware `role.permission` belum dipasang di rute kasir), sehingga centang di tab itu tidak berdampak apa-apa secara fungsional. State `mobilePerms` tetap ada di kode dan dikirim di payload edit (`permissions_config.mobile`), tapi tidak bisa diedit dari UI sampai backend dibangun.

**Payload edit:** `PUT /v1/roles/{id}` body `{ permissions_config: { cms, mobile, custom } }` (name tidak bisa diubah lagi setelah create — ditampilkan readonly di form edit).

**Endpoint:**
| Aksi | Method | Endpoint | Body |
|---|---|---|---|
| List | GET | `/v1/roles` | — |
| Detail | GET | `/v1/roles/{id}` | — |
| Create | POST | `/v1/roles` | `{ name }` |
| Update | PUT | `/v1/roles/{id}` | `{ permissions_config }` |
| Delete | DELETE | `/v1/roles/{id}` | — |

**Expected response detail role:** `{ id, name, permissions_config: { cms: {...}, mobile: {...}, custom: {...} } }`

**Error handling (sama pola dengan modul lain):** 422 → ambil `Object.values(errors)[0]`, selain itu fallback `message`.

## Implikasi untuk keputusan tim

- Kalau ada laporan "user kasir mobile bisa akses fitur yang seharusnya dibatasi role-nya" — **ini bukan bug FE**, tapi karena backend memang belum enforce permission mobile. Perlu prioritaskan backend dulu sebelum unhide tab Mobile Apps.
- Kalau mau hapus total fitur import dari sistem, field `customActs[*].import` di payload bisa dihapus juga dari backend schema — saat ini masih dikirim FE walau tidak bisa diedit dari UI (selalu `false` kecuali ada data lama).
