# Architecture

## Pola arsitektur

Aplikasi mengikuti pola **MVC Laravel klasik** dengan **service layer** untuk logika bisnis transaksional.

```
HTTP Request
    ↓
Route (routes/web.php) — middleware auth
    ↓
Controller — validasi request, mapping data, return view/redirect
    ↓
Service — DB::transaction(), aturan bisnis, orkestrasi multi-model
    ↓
Eloquent Model — persistensi, relasi
    ↓
MySQL / SQLite
```

### Prinsip pemisahan tanggung jawab

| Lapisan | Tanggung jawab | Tidak boleh |
|---------|----------------|-------------|
| **Controller** | Validasi inline, siapkan data view, redirect + flash | Logika bisnis kompleks, transaksi DB |
| **Service** | Orkestrasi transaksi, aturan FEFO, kas, tagihan | Render HTML |
| **Model** | Relasi, cast, accessor, query sederhana | Orkestrasi multi-entitas |
| **Blade** | Presentasi, komponen UI | Query database langsung |

### Yang tidak digunakan (sengaja, V0)

- Form Request classes
- Policies / Gates (otorisasi)
- Events / Listeners
- Queued Jobs
- API routes / Sanctum
- Repository pattern

Laravel auto-resolve dependency injection untuk constructor `Service` di controller.

---

## Diagram alur domain

### Alur pembelian (inbound)

```mermaid
flowchart TD
    A[POST /pembelian] --> B[PembelianController::store]
    B --> C[PembelianService::simpan]
    C --> D[Buat pembelian + detail_pembelian]
    D --> E[StokService::tambahStokBatch]
    E --> F[StokService::catatMutasi jenis Pembelian]
    F --> G[Buat tagihan_pbf jatuh tempo +30 hari]
```

### Alur penjualan (outbound)

```mermaid
flowchart TD
    A[POST /penjualan] --> B[PenjualanController::store]
    B --> C[PenjualanService::simpan]
    C --> D{Cek stok total}
    D -->|cukup| E[Buat penjualan + detail]
    E --> F[StokService::kurangiStokFEFO]
    F --> G[Catat mutasi_stok jenis Penjualan]
    G --> H[Simpan detail_penjualan_batch]
    H --> I{Ada sumber_kas?}
    I -->|ya| J[KasService::catatMasuk]
    D -->|tidak| K[RuntimeException → error form]
```

### Alur pembayaran tagihan PBF

```mermaid
flowchart TD
    A[PUT /tagihan/{id}] --> B[TagihanPbfController::update]
    B --> C[KasService::updateTagihanPembayaran]
    C --> D[Update jumlah_bayar + status]
    D --> E{Ada sumber_dana?}
    E -->|ya| F[KasService::catatKeluar kategori Pembayaran PBF]
```

---

## Service layer

### `StokService`

Pusat logika inventori:

| Method | Fungsi |
|--------|--------|
| `getStokTotal()` | Agregat jumlah dari semua batch |
| `getStokAgregat()` | Group per cabang+obat dengan status Aman/Menipis/Habis |
| `getStokMenipis()` / `getStokHabis()` | Filter status |
| `getMendekatiExpired($hari=90)` | Batch yang akan expired dalam N hari |
| `kurangiStokFEFO()` | Kurangi stok dengan `lockForUpdate`, urut `tanggal_expired` |
| `tambahStokBatch()` | Insert atau merge batch identik |
| `catatMutasi()` | Tulis audit `mutasi_stok` |
| `koreksiStok()` | Adjust batch + mutasi jenis Koreksi |
| `simpanDetailPenjualanBatch()` | Jejak batch yang dipakai penjualan |

### `PembelianService`

- `simpan($header, $detailItems)` — satu transaksi DB
- Auto-hitung `total_faktur` dari subtotal detail
- Buat `tagihan_pbf` dengan `tanggal_jatuh_tempo = tanggal_faktur + 30 hari`

### `PenjualanService`

- `simpan($header, $detailItems)` — satu transaksi DB
- Validasi stok sebelum commit
- Catat kas masuk jika `sumber_kas` dan `total > 0` (`sumber_kas` dari CASH→laci / TRANSFER→BCA; `cabang_id` ikut penjualan)

### `KasService`

- Kolom `mutasi_kas.saldo` = running balance **global per `sumber_kas`** (cara tulis baris tetap)
- UI **Kas Tunai** memakai neto per cabang (`getSaldoNetoPerCabang` / `getSaldoKasTunaiCabang`); **BCA** tetap `getSaldoTerakhir`
- `cabang_id` nullable: jejak cabang + filter laci tunai (penjualan/manual; PBF dari BCA = null)
- `getSaldoTerakhir($sumberKas)` — saldo dari mutasi terakhir per sumber (global)
- `getSaldoNetoPerCabang($sumberKas, $cabangId)` — `SUM(pemasukan) - SUM(pengeluaran)` untuk laci cabang
- `getSaldoKasTunaiCabang($cabangId)` — neto Kas Medis + Kas OTC cabang itu
- `catatMasuk()` / `catatKeluar(..., ?int $cabangId = null)` — append baris `mutasi_kas`
- `updateTagihanPembayaran(..., ?int $cabangId = null)` — update tagihan + optional catat keluar kas
- `getRingkasanBulanIni()` — sum pemasukan/pengeluaran bulan berjalan (masih global)
- UI kas: lihat [`BRIEF_Kas_Tunai_BCA.md`](BRIEF_Kas_Tunai_BCA.md) (`rekening=tunai|bca|omzet`)

---

## Routing & middleware

File: `routes/web.php`

| Grup | Middleware | Route pattern |
|------|------------|---------------|
| Guest | `guest` | `GET/POST /login` |
| Auth | `auth` | Semua route aplikasi |
| Logout | `auth` | `POST /logout` |

Resource routes menggunakan konvensi Laravel. Beberapa resource dibatasi method:

- `pembelian`, `penjualan`: hanya `index, create, store, show` (tidak ada edit/delete)
- `tagihan`: hanya `index, edit, update`
- `kas`: hanya `index, create, store`

Route stok custom (bukan resource):

- `GET stok/obat` → agregat stok
- `GET stok/batch` → stok per batch
- `GET/POST stok/batch/{id}/koreksi` → koreksi
- `GET stok/mutasi` → riwayat mutasi

---

## Autentikasi

- Session driver: `database` (produksi), `array` (testing)
- Satu user admin dari seeder
- Role minimal: `owner` (boleh switch cabang) dan `admin_cabang` (terkunci ke satu cabang)
- Session `cabang_aktif_id` via middleware `cabang.aktif` / `CabangContext`
- `LoginController` handle create/store/destroy; set cabang aktif setelah login
- Permission modul penuh (Spatie/policies) belum ada

---

## Frontend architecture

```
layouts/app.blade.php          ← shell authenticated
layouts/guest.blade.php        ← login page
components/*.blade.php         ← design system (x-button, x-card, dll.)
resources/css/app.css          ← Tailwind 4 + siaga-* classes
resources/js/app.js            ← sidebar mobile + searchable select init
resources/js/searchable-select.js ← dropdown pencarian obat/PBF
Alpine.js (CDN)                ← dynamic rows di pembelian/penjualan create
```

Vite bundle: `app.css` + `app.js`. Alpine dimuat terpisah via `@push('scripts')` di halaman transaksi.

---

## Database & environment

| Environment | DB | Catatan |
|-------------|-----|---------|
| Local dev | MySQL (docker) atau SQLite | Migration harus kompatibel keduanya |
| PHPUnit | SQLite `:memory:` | `phpunit.xml` env |
| Produksi (rencana) | MySQL 8.4 | `docker-compose.yml` tersedia |

---

## Testing architecture

```
tests/Feature/     ← integration tests (HTTP + DB)
tests/Unit/        ← placeholder (hanya assertTrue)
```

Gunakan `RefreshDatabase` trait. Test bermakna: auth smoke, cabang CRUD, pembelian flow.
