# Folder Structure

```
/workspace/
├── app/                          # Kode aplikasi PHP
│   ├── Console/Commands/
│   │   └── CleanupHargaJualGlobalCommand.php  # Promote harga cabang → global
│   ├── Http/Controllers/         # Controller per modul
│   │   ├── Auth/LoginController.php
│   │   ├── CabangAktifController.php
│   │   ├── Concerns/ResolvesCabangAktif.php
│   │   └── Middleware via bootstrap alias cabang.aktif
│   ├── Http/Middleware/
│   │   └── EnsureCabangAktif.php
│   ├── Models/                   # Eloquent models
│   │   └── Concerns/IsMasterDictionary.php
│   ├── Providers/AppServiceProvider.php
│   └── Services/                 # Logika bisnis transaksional
│       ├── CabangContext.php            # Session cabang aktif
│       ├── StokService.php
│       ├── PembelianService.php
│       ├── HargaPerhitunganService.php  # Rumus PPN/modal/margin/total tagihan
│       ├── HargaModalService.php        # Modal faktur + agregat batch
│       ├── HargaJualService.php
│       ├── ManajemenHargaService.php    # Antrian harga global (alfabet + cabang)
│       ├── PenjualanService.php
│       └── KasService.php
│
├── bootstrap/                    # Bootstrap Laravel
├── config/                       # Konfigurasi (database, session, dll.)
├── database/
│   ├── factories/UserFactory.php
│   ├── migrations/               # 20 migration files (urut timestamp)
│   └── seeders/                  # Data awal + dictionary obat
│
├── docs/                         # Dokumentasi (termasuk panduan agent ini)
├── public/                       # Web root (index.php, assets build)
│   └── images/brand/             # Logo & favicon
│
├── resources/
│   ├── css/app.css               # Tailwind 4 + design tokens siaga-*
│   ├── js/
│   │   ├── app.js                # Sidebar + init searchable select
│   │   ├── konfirmasi-harga-page.js  # Alpine penentuan harga (DPB + global)
│   │   └── searchable-select.js  # Komponen dropdown pencarian
│   └── views/
│       ├── layouts/              # app.blade.php, guest.blade.php
│       ├── components/           # Blade anonymous components (x-*)
│       │   └── konfirmasi-harga/ # Partial UI penentuan harga bersama
│       ├── auth/                 # login
│       ├── dashboard/
│       ├── cabang/, pbf/, obat/, pelanggan/   # Master CRUD
│       ├── pembelian/, penjualan/              # Transaksi
│       ├── manajemen-harga/      # Penentuan harga obat global
│       ├── stok/                 # obat, batch, koreksi, mutasi
│       ├── kas/                  # Cash flow
│       ├── tagihan/, rencana/    # Hutang PBF (+ histori bayar)
│       └── laporan/              # 6 laporan + index
│
├── routes/
│   ├── web.php                   # Semua route aplikasi
│   └── console.php
│
├── scripts/
│   └── generate-favicons.php     # Utility generate favicon
│
├── storage/                      # Logs, cache, sessions, uploads
├── tests/
│   ├── Feature/                  # Integration tests
│   └── Unit/
│
├── artisan                       # CLI Laravel
├── composer.json                 # PHP dependencies
├── package.json                  # Node dependencies (Vite, Tailwind)
├── vite.config.js
├── docker-compose.yml            # MySQL 8.4 untuk dev
├── phpunit.xml
└── README.md                     # Quick start singkat
```

---

## Detail per direktori penting

### `app/Http/Controllers/`

Satu controller per modul bisnis. Pola umum:

```php
public function __construct(private XxxService $service) {}

public function index(Request $request) {
    // filter + paginate + return view
}

public function store(Request $request) {
    $validated = $request->validate([...]);
    $this->service->simpan(...);
    return redirect()->route('...')->with('success', '...');
}
```

| Controller | View namespace | Service |
|------------|----------------|---------|
| `DashboardController` | `dashboard.*` | StokService, KasService |
| `CabangController` | `cabang.*` | — |
| `PbfController` | `pbf.*` | — |
| `ObatController` | `obat.*` | StokService (stok total di index) |
| `PelangganController` | `pelanggan.*` | — |
| `PembelianController` | `pembelian.*` | PembelianService |
| `ManajemenHargaObatController` | `manajemen-harga.*` | ManajemenHargaService, HargaJualService |
| `PenjualanController` | `penjualan.*` | PenjualanService |
| `TagihanPbfController` | `tagihan.*` (+ `tagihan.histori`) | KasService |
| `RencanaPembayaranController` | `rencana.*` | — |
| `StokController` | `stok.index` (tab obat/batch) | StokService |
| `StokObatController` | `stok.obat.index` (redirect → hub) | — |
| `StokBatchObatController` | `stok.batch.index` (redirect), `stok.batch.koreksi` | StokService |
| `MutasiStokController` | `stok.mutasi.index` | — |
| `MutasiKasController` | `kas.*` | KasService |
| `LaporanController` | `laporan.*` | StokService |

### `app/Models/`

Semua model master memakai `protected $table = 'master_*'` karena nama tabel Indonesia tidak mengikuti pluralisasi Laravel.

Model transaksi memakai nama tabel langsung: `pembelian`, `penjualan`, `stok_batch_obat`, dll.

Dictionary models (`GolonganObat`, `KategoriObat`, `BentukSediaan`, `SatuanObat`) memakai trait `IsMasterDictionary` dengan scope `active()` dan `ordered()`.

### `app/Services/`

**Hanya** service ini yang boleh mengorkestrasi transaksi multi-tabel. Jangan duplikasi logika FEFO atau kas di controller.

### `database/migrations/`

Urutan penting:

1. `0001_*` — users, cache, jobs (Laravel default)
2. `2026_06_30_000001`–`000014` — domain apotek
3. `2026_07_03_000001`–`000003` — dictionary obat + refactor FK + rename master tables

Migration `000003_rename_master_data_tables` menangani upgrade dari nama tabel lama (`cabang` → `master_cabang`).

### `database/seeders/`

| Seeder | Isi |
|--------|-----|
| `DatabaseSeeder` | User admin, 2 cabang; orchestrate seeder berikut |
| `PbfSeeder` | 23 PBF dari `database/data/pbf.json` |
| `PelangganSeeder` | 62 pelanggan dari `database/data/pelanggan.json` |
| `MasterObatDictionarySeeder` | Orchestrator dictionary obat |
| `GolonganObatSeeder` | Golongan obat |
| `KategoriObatSeeder` | Kategori obat |
| `SatuanObatSeeder` | Satuan/kemasan terkecil obat |
| `ObatSeeder` | ~2205 obat dari `database/data/obat.json` |
| `PembelianSeeder` | Sample DPB + stok/tagihan/harga jual dari `database/data/pembelian.json` |

**File data & generator:**

| File | Generator | Sumber Excel |
|------|-----------|--------------|
| `database/data/pbf.json` | `scripts/generate-pbf-pelanggan-data.py` | `seeder.xlsx` sheet PBF |
| `database/data/pelanggan.json` | `scripts/generate-pbf-pelanggan-data.py` | `seeder.xlsx` sheet PELANGGAN |
| `database/data/obat.json` | `scripts/generate-obat-data.py` | `seederObat.xlsx` |
| `database/data/pembelian.json` | Manual (sample DPB) | — |

### `resources/views/components/`

Komponen Blade anonymous — dipanggil sebagai `<x-nama>`:

| Komponen | Props utama |
|----------|-------------|
| `button` | `variant`, `size`, `href`, `type` |
| `card` | `title`, `hover` |
| `badge` | `variant` (success, warning, danger, info) |
| `table` | slot `head`, `body` |
| `table-scroll` | wrapper scroll horizontal |
| `form-field` | `label`, `name`, `type`, `options`, `value` |
| `form-group` | wrapper form |
| `alert` | flash success + validation errors |
| `page-header` | title, subtitle, action slot |
| `sidebar` | navigasi utama (array `$links`) |
| `navbar` | header + logout |
| `searchable-select` | dropdown pencarian statis |
| `searchable-select-alpine` | varian untuk baris dinamis Alpine |
| `favicon` | link rel icon |

### `resources/views/` per modul

Setiap modul master mengikuti pola:

```
{modul}/index.blade.php    ← tabel + filter + pagination
{modul}/create.blade.php   ← form create
{modul}/edit.blade.php     ← form edit
```

Modul transaksi:

```
pembelian/index, create, edit, show, _form, konfirmasi-harga
penjualan/index, create, show
```

### `tests/`

| File | Cakupan |
|------|---------|
| `Feature/ApotekSiagaTest.php` | Login, dashboard, cabang auth + create |
| `Feature/PembelianFlowTest.php` | POST DPB → stok_batch + tagihan (incl. PPN) |
| `Feature/PembelianEditDeleteTest.php` | Edit/hapus DPB + guard tagihan/stok |
| `Feature/TagihanPrioritasHistoriTest.php` | Sort JT+sisa, filter aktif, tulis histori bayar |
| `Feature/KonfirmasiHargaPembelianTest.php` | DPB → konfirmasi → harga jual |
| `Feature/HargaModalServiceTest.php` | Modal faktur + agregat |
| `Unit/HargaPerhitunganServiceTest.php` | PPN, markup, margin, total faktur |
| `Feature/ExampleTest.php` | Redirect guest ke login |

### `public/`

- `index.php` — entry point
- `build/` — output Vite setelah `npm run build`
- `images/brand/` — aset branding

### `scripts/`

Utility sekali pakai, bukan bagian runtime aplikasi.

| Script | Output | Sumber |
|--------|--------|--------|
| `generate-obat-data.py` | `database/data/obat.json` | `seederObat.xlsx` |
| `generate-pbf-pelanggan-data.py` | `pbf.json` / `pelanggan.json` | `seeder.xlsx` |
| `generate-migrasi-harga.py` | `database/data/migrasi/harga_obat.json` | `docs/migrasi/harga_obat.xlsx` |

**Migrasi harga + stok awal (untuk simulasi penjualan):**

```bash
python scripts/generate-migrasi-harga.py
php artisan migrasi:harga-obat
# opsi: --dry-run  |  --skip-stok  |  --cabang="Apotek Siaga 24"
```
