# Modul Settings (Pengaturan)

Mencakup: Info Toko, Metode Pembayaran, Pajak & Biaya Tambahan, Printer. File: [settingsService.js](../../src/services/settingsService.js), [paymentService.js](../../src/services/paymentService.js), [waService.js](../../src/services/waService.js), [SettingsStore.jsx](../../src/pages/settings/SettingsStore.jsx), [SettingsPayment.jsx](../../src/pages/settings/SettingsPayment.jsx), [SettingsTax.jsx](../../src/pages/settings/SettingsTax.jsx), [TaxForm.jsx](../../src/pages/settings/TaxForm.jsx), [SettingsPrinter.jsx](../../src/pages/settings/SettingsPrinter.jsx)

## Konsep penting: 3 sumber data berbeda digabung jadi 1 halaman (Info Toko)

`SettingsStore.jsx` itu satu halaman tapi menulis ke **3 resource backend berbeda** sekaligus saat klik Simpan:
1. **Branch** (cabang aktif) — nama, kontak, alamat, logo, PIN void → `PUT/POST /v1/branches/{id}`
2. **Tenant** — jenis usaha & data pemilik (level akun, bukan per cabang) → `PUT/POST /v1/tenant`
3. **Settings** — header/footer struk, tipe pembulatan (level tenant juga) → `PUT /v1/settings`

Ketiganya dikirim sebagai **3 mutation independen** (bukan 1 payload gabungan) — kalau salah satu gagal (mis. update tenant gagal validasi), yang lain bisa tetap sukses. Tidak ada rollback gabungan; tiap mutation punya toast error sendiri.

### Validasi sebelum submit
1. `branch_name` kosong → toast "Nama cabang wajib diisi"
2. Kalau fitur Void diaktifkan (`voidEnabled`) **dan** cabang belum punya PIN void (`!branchData?.has_void_pin`) **dan** `void_pin` kosong → toast wajib isi PIN saat pertama kali aktifkan

### Logo
- Dikompresi di browser sebelum upload (`compressImage`: max width 600px, kualitas JPEG 0.8) — mengurangi ukuran upload tanpa perlu kompresi di backend.
- Maks ukuran file asli 10MB (dicek sebelum kompresi).

### Fitur Void Transaksi
Toggle `voidEnabled` mengontrol apakah kasir bisa void transaksi dengan PIN. Kalau dimatikan → kirim `disable_void_pin: true` (atau form-data `disable_void_pin=1`) ke endpoint branch, bukan menghapus PIN secara terpisah.

### Pembulatan Total Transaksi
`rounding_type`: `none` / `round_100` / `round_500` / `round_1000` — disimpan di level **Settings** (tenant), bukan branch (beda dari `rounding_type` branch di Master Data yang punya pilihan berbeda `round_up/round_down/round_near/none`! **Dua field rounding_type yang independen di backend** — satu di tabel branch, satu di tenant settings. Jangan tertukar saat debugging.

### Konfigurasi Struk (header/footer)
**Section ini di-comment-out / disembunyikan dari UI** saat ini (lihat blok komentar besar di kode) — field `receipt_header`/`receipt_footer` tetap ada di state & tetap dikirim ke `updateSettings`, tapi user tidak bisa mengedit dari form. Kemungkinan ditunda karena alasan desain/prioritas.

### WhatsApp Reporter — integrasi 2 backend berbeda

Fitur unik: laporan otomatis via WhatsApp chat. Melibatkan **Laravel** (kelola API key) **dan service Node.js terpisah** (`express_wa`, default `http://localhost:3001`, env `VITE_WA_SERVICE_URL`) yang menangani sesi WhatsApp (mirip whatsapp-web.js).

**State machine:** `no_key` → generate key → `disconnected` → connect → `connecting` (tampil QR untuk di-scan) → `connected`.

| Step | Endpoint | Catatan |
|---|---|---|
| Cek status API key | GET | `/v1/settings/wa` (Laravel) |
| Generate API key | POST | `/v1/settings/wa` (Laravel) |
| Hapus API key | DELETE | `/v1/settings/wa` (Laravel) |
| Ambil API key untuk connect | GET | `/v1/settings/wa/key` (Laravel, dipanggil internal sebelum connect) |
| Mulai sesi WA | POST | `{WA_SERVICE_URL}/sessions/{tenantId}/connect` body `{api_key}` |
| Cek status sesi | GET | `{WA_SERVICE_URL}/sessions/{tenantId}/status` — **di-poll tiap 3 detik** selama belum `connected` (`refetchInterval` TanStack Query, berhenti otomatis setelah connected) |
| Putuskan sesi | DELETE | `{WA_SERVICE_URL}/sessions/{tenantId}` |

**Catatan operasional:** kalau WA Service (Node.js) down, tombol "Hubungkan WhatsApp" akan gagal dengan toast "Gagal memulai koneksi WA. Pastikan WA Service berjalan." — ini bukan masalah di Laravel, cek service Node terpisah dulu.

---

## Metode Pembayaran — `SettingsPayment.jsx`

5 kategori metode pembayaran, masing-masing dengan toggle aktif/nonaktif level section + level item:

| Kategori | `type` backend | Aksi |
|---|---|---|
| Tunai | `cash` | toggle aktif saja (auto-create kalau belum ada record `{ name: 'Tunai', type: 'cash' }`); + "Nominal Cepat" (maks 6 preset nominal pembayaran tunai cepat, disimpan di **Settings** bukan payment-methods: `tunai_nominals` array) |
| Debit | `debit` | tambah bank dari daftar preset (`BANK_OPTIONS`) atau ketik manual |
| Kredit | `credit` | sama seperti Debit |
| Contactless | `contactless` | sama seperti Debit |
| QRIS | `qris` | tambah/edit via modal: pilih provider (Xendit/Cashlez/Dava), simpan kredensial (`api_key`, `base_url`, `merchant_name`) sebagai object `qris_credentials` |

**Toggle level section** ("aktifkan semua Debit" dsb): loop `Promise.all` update tiap item dalam kategori itu ke status yang sama (lawan dari status mayoritas saat ini) — bukan endpoint bulk khusus.

**Endpoint:**
| Aksi | Method | Endpoint |
|---|---|---|
| List metode | GET | `/v1/payment-methods` |
| Tambah bank/QRIS | POST | `/v1/payment-methods` body `{ name, type, bank_name? / qris_provider?+qris_credentials?, is_active }` |
| Update (toggle/edit) | PUT | `/v1/payment-methods/{id}` |
| Hapus | DELETE | `/v1/payment-methods/{id}` |
| Simpan nominal cepat tunai | PUT | `/v1/settings` body `{ settings: { tunai_nominals: [...] } }` |

**Perhatian keamanan:** `api_key` provider QRIS disimpan & dikirim sebagai plain text field di `qris_credentials` — tampil di form sebagai input biasa (bukan password-masked secara konsisten, font monospace tapi visible). Pastikan backend meng-enkripsi kredensial ini saat disimpan, dan endpoint GET tidak mengembalikan api_key mentah ke FE kalau tidak perlu ditampilkan ulang (cek implementasi `getTax`/`index` di backend kalau ini jadi concern).

---

## Pajak & Biaya Tambahan — `SettingsTax.jsx` + `TaxForm.jsx`

> **Koreksi memori sebelumnya:** modul ini **sudah terhubung ke backend asli** (CRUD penuh `/v1/taxes`) — bukan dummy.

**Konsep `is_system`:** ada 2 kategori pajak/biaya:
- **Pajak Sistem** (`is_system: true`) — preset bawaan (mis. PPN), **tidak bisa diganti nama atau dihapus**. Hanya `value`, `direction`, `calculation` yang bisa diedit. Toggle aktif/nonaktif langsung dari list (tanpa masuk form) via `SystemToggle` (PUT langsung).
- **Biaya Tambahan Kustom** (`is_system: false`) — buatan tenant sendiri, full CRUD termasuk nama, urutan (`sort_order`), keterangan (`notes`).

**Field form:** `name*` (disabled kalau `is_system`), `value*` (persentase 0-100), `direction` (`add` tambah ke total / `subtract` kurangi dari total — toggle visual 2 tombol), `calculation*`:
- `before_discount` — dihitung sebelum diskon diterapkan
- `after_discount` — dihitung setelah diskon
- `after_taxes` — cascade, dihitung setelah pajak/biaya lain juga diterapkan (urutan penting kalau ada multiple tax)

`sort_order` & `notes` hanya untuk pajak kustom (sistem tidak punya field ini di form).

**Endpoint:**
| Aksi | Method | Endpoint |
|---|---|---|
| List | GET | `/v1/taxes` |
| Detail | GET | `/v1/taxes/{id}` |
| Create | POST | `/v1/taxes` body `{ name, value, direction, calculation, sort_order, notes }` |
| Update | PUT | `/v1/taxes/{id}` body sama, kecuali kalau `is_system` hanya `{ value, direction, calculation }` (name/sort_order/notes tidak dikirim) |
| Delete | DELETE | `/v1/taxes/{id}` (hanya untuk non-sistem, tombol hapus disembunyikan untuk sistem) |

---

## Printer — `SettingsPrinter.jsx`

**Belum diimplementasikan** — halaman ini hanya stub placeholder ("Implement sesuai desain preview."). Service `paymentService.js` sudah punya fungsi siap pakai (`printers()`, `storePrinter()`, `updatePrinter()`, `destroyPrinter()` ke endpoint `/v1/printers`) tapi **tidak ada UI yang memanggilnya**. Kalau tim butuh fitur ini, backend kemungkinan sudah siap (lihat dummy data `DUMMY_PRINTERS` di service untuk gambaran bentuk data: `name, type, branch_id, ip_address, port, is_active`) — kerjanya hanya bangun UI form CRUD-nya.
