# Brief: Master Obat Database Refactor (V0.2) — ARSIP

**Tipe:** Spesifikasi implementasi (arsip)  
**Proyek:** Apotek Siaga — [siaga.app](https://github.com/ihdalhsn/siaga.app)  
**Versi brief:** 1.1 (arsip)  
**Tanggal asli:** 7 Juli 2026  
**Terakhir diselaraskan:** 27 Juli 2026  
**Status:** **Completed / superseded** — jangan dipakai sebagai acuan coding harga lagi

> **Sumber kebenaran rumus & alur harga saat ini:** [`pricing-dpb-rules.md`](pricing-dpb-rules.md)  
> **Konteks operasional:** [`Insight_Proses_Bisnis_Apotek_Siaga.md`](Insight_Proses_Bisnis_Apotek_Siaga.md)  
> Dokumen ini disimpan sebagai jejak keputusan V0.2a/b. Detail yang bertentangan dengan `pricing-dpb-rules.md` dianggap usang.

---

## Ringkasan eksekutif

Refactor V0.2 **sudah diimplementasikan**: identitas obat (`master_obat`) dipisah dari harga jual (`harga_jual_obat`), dengan jejak histori dan alur penentuan harga setelah DPB.

| Fase | Cakupan | Status |
|------|---------|--------|
| **V0.2a** | Pisah tabel harga jual + migrasi data + update kode | ✅ Selesai |
| **V0.2b** | `HargaModalService` + review harga pasca-DPB + UI | ✅ Selesai (alur & rumus berevolusi — lihat bawah) |
| **V0.3** | Import Excel harga bulanan | Masih di luar scope / pending |

**Evolusi setelah brief asli:**

- Modal agregat **bukan** lagi rata-rata tertimbang stok batch, melainkan **satu titik harga per PBF** (terbaru / max historis → mean atau max antar PBF).
- Setelah simpan DPB: redirect ke **detail DPB**; review utama di **Manajemen Harga** (tab antrian); shortcut `pembelian/{id}/konfirmasi-harga` tetap ada.
- Harga jual praktik: **global** (`cabang_id` null); DPB/stok tetap per cabang.
- OTC = ecer; REG/VIP = kemasan; margin VIP vs `harga_modal_satuan`.

---

## Konteks & acuan

| Dokumen | Peran sekarang |
|---------|----------------|
| [`pricing-dpb-rules.md`](pricing-dpb-rules.md) | **SSOT** istilah, rumus, alur DPB → harga |
| [`Insight_Proses_Bisnis_Apotek_Siaga.md`](Insight_Proses_Bisnis_Apotek_Siaga.md) | Insight operasional (titik PBF, antrian review) |
| `docs/database.md` / `docs/business-rules.md` | Skema & aturan — ikut SSOT pricing |
| `docs/pending-work.md` | Backlog (import Excel, dll.) |

### Prinsip desain (tetap berlaku)

1. Prototype operasional, bukan ERP lengkap.
2. `stok_batch_obat.harga_beli` / `harga_modal` per batch **tidak ditimpa** oleh agregat.
3. `detail_penjualan.harga_satuan` = snapshot transaksi.
4. Dictionary obat tidak dihapus.
5. Migration kompatibel MySQL + SQLite.
6. Bahasa Indonesia untuk enum/status & label UI.

---

## Temuan dari Excel harga (konteks V0.3, informatif)

File acuan historis: sheet **`HARGA `** (~2.200 baris; margin per obat `% UMUM` / `% REG` / `% VIP`).

**Implikasi yang sudah terpenuhi:** kolom `margin_otc`, `margin_reg`, `margin_vip` di `harga_jual_obat` siap untuk import V0.3 tanpa skema ulang.

---

## Baseline historis (sebelum V0.2) — jangan dibaca sebagai kondisi sekarang

Sebelum refactor, harga ada di kolom `master_obat.harga_otc/reg/vip`. Setelah V0.2a, kolom itu **sudah dihapus**; baca/tulis lewat `harga_jual_obat` + `HargaJualService`.

Entitas yang sejak awal sudah benar terpisah:

| Entitas | Fungsi |
|---------|--------|
| `stok_batch_obat.harga_beli` / `harga_modal` | Modal per batch dari DPB / migrasi |
| `detail_pembelian.*` | Snapshot baris faktur |
| `detail_penjualan.harga_satuan` | Snapshot harga jual transaksi |

---

## Skema yang dihasilkan (ringkas)

Relasi inti tetap seperti brief asli: `master_obat` → `harga_jual_obat` → `histori_harga_jual_obat` / `harga_modal_perhitungan`; DPB/stok per cabang.

### `harga_jual_obat` (praktik saat ini)

| Aspek | Ketentuan |
|-------|-----------|
| Scope | Default **global** (`cabang_id` null); kolom cabang tetap ada untuk opsi nanti |
| Aktif | Satu record aktif per obat global: `berlaku_selesai IS NULL` |
| Modal | `harga_modal_terhitung` = modal agregat **titik PBF**; `harga_modal_satuan` = dasar margin & OTC |
| Metode | `rata_rata` = Rata-rata PBF (titik terbaru); `tertinggi` = max titik max-historis per PBF |
| Sumber | `manual`, `dpb`, `sistem`, `excel_import`, … |

### `harga_modal_perhitungan`

Jejak batch berstok saat simpan harga dari jalur DPB (audit / FEFO) — **bukan** sumber bobot rumus harga jual.

Detail kolom lengkap: `docs/database.md` + `pricing-dpb-rules.md`.

---

## Fase V0.2a — Pisah tabel harga jual ✅

Sudah dikerjakan: migration tabel harga + histori, migrasi data dari master, drop kolom harga di `master_obat`, `HargaJualService`, update CRUD obat & penjualan, seeder, tests.

---

## Fase V0.2b — DPB → modal → penentuan harga jual ✅ (berevolusi)

### Alur bisnis (saat ini)

```
Admin input DPB
    → PembelianService::simpan() [stok batch + tagihan]
    → Redirect pembelian.show (detail DPB)
    → Review harga:
         • utama: Manajemen Harga (/manajemen-harga)
              tab dpb_hari_ini | modal_beda | cari
         • opsional: pembelian/{id}/konfirmasi-harga
    → HargaModalService::hitung(cabang_acuan, obat_id, metode)
    → Admin set OTC (ecer) / REG·VIP (kemasan)
    → HargaJualService::simpanHarga(..., sumber: 'dpb'|'manual')
```

### `HargaModalService` (rumus terkini)

```
Titik per PBF:
  rata_rata  → harga_modal DPB terbaru PBF itu
  tertinggi  → max harga_modal historis PBF itu

Modal agregat:
  rata_rata  → mean(titik PBF)
  tertinggi  → max(titik PBF)

harga_modal_satuan = (modal_agregat × 1.015) / isi_kemasan
```

- Fallback jika belum ada DPB: harga aktif / batch `AWAL-*`.
- Stok batch berstok = FEFO & jejak; **bukan** weighted average untuk harga jual.
- Spec lengkap: `pricing-dpb-rules.md` §4.3.

### UI

| Jalur | Path |
|-------|------|
| Antrian utama | `resources/views/manajemen-harga/index.blade.php` |
| Shortcut per faktur | `resources/views/pembelian/konfirmasi-harga.blade.php` |
| JS preview | `resources/js/konfirmasi-harga-page.js` |

---

## V0.3 — Import Excel (masih di luar scope)

Sketsa tabel `import_harga_bulan` / detail di brief asli tetap relevan sebagai catatan desain — belum diimplementasikan. Lihat `docs/pending-work.md`.

---

## Aturan bisnis setelah refactor (selaras kondisi sekarang)

### Master obat

- Hanya identitas + dictionary FK; **tidak** menyimpan harga jual/modal.
- `kode_obat`: auto-generate via `KodeObatGenerator` pada create aplikasi; seeder Excel mempertahankan kode file.

### Harga jual

- Aktif di `harga_jual_obat` (praktik global).
- Perubahan versi → `histori_harga_jual_obat` (kecuali mode koreksi salah ketik).
- `detail_penjualan.harga_satuan` tidak retroaktif.
- **OTC = ecer**; **REG/VIP = kemasan utuh**.

### Harga modal

- Agregat dari **titik PBF** di cabang acuan (bukan bobot stok).
- `stok_batch_obat.harga_*` tidak diubah oleh perhitungan modal jual.
- Satu obat dari >1 PBF → mean atau max antar titik PBF.

### Margin VIP 11%

```
margin_vip = (harga_vip_satuan − harga_modal_satuan) / harga_modal_satuan
warning jika margin_vip < 0.11
```

- Warning (bukan hard block) di Manajemen Harga / konfirmasi harga.
- Margin % per obat tersimpan di `margin_*` (siap import Excel).

### Penjualan

- Default harga dari `harga_jual_obat` aktif.
- Override manual di form tetap diizinkan.
- FEFO tidak berubah oleh refactor harga.

---

## Checklist implementasi (arsip — semua V0.2 selesai)

### Migrations / models / services

- [x] Tabel `harga_jual_obat`, `histori_harga_jual_obat`, `harga_modal_perhitungan`
- [x] Migrasi harga dari master + drop kolom harga di `master_obat`
- [x] Models + `HargaJualService` + `HargaModalService` (+ `HargaPerhitunganService`, `ManajemenHargaService`)

### Controllers / views / routes

- [x] Obat & penjualan memakai `harga_jual_obat`
- [x] Pembelian: redirect show; shortcut konfirmasi harga
- [x] Manajemen Harga (antrian tab)
- [x] Seeder & feature/unit tests harga/modal/DPB

### Dokumentasi

- [x] `pricing-dpb-rules.md` = SSOT
- [x] Insight proses bisnis diselaraskan (titik PBF + antrian)
- [ ] Import Excel V0.3 (masih pending)
- [ ] UI histori harga jual (backend ada; UI belum)

---

## Yang TIDAK boleh dilakukan (tetap relevan)

| Larangan | Alasan |
|----------|--------|
| Pakai brief ini sebagai SSOT rumus harga | Sudah diganti `pricing-dpb-rules.md` |
| Rata-ratakan / timpa `stok_batch_obat.harga_*` di DB | Snapshot batch harus tetap asli |
| Weighted-stok sebagai dasar harga jual | Diganti titik PBF |
| Hapus dictionary obat | Sudah benar |
| Retroaktif ubah `detail_penjualan.harga_satuan` | Audit |
| Hard-block VIP di bawah margin | Warning saja |
| Ubah logika FEFO / merge batch tanpa brief terpisah | Di luar scope harga |

---

## Kriteria penerimaan V0.2 (tercapai)

### V0.2a

- [x] `master_obat` tanpa kolom harga
- [x] Data harga di `harga_jual_obat`
- [x] CRUD / penjualan memakai service & relasi harga
- [x] Histori versi harga (backend)
- [x] Test suite terkait harga/DPB hijau

### V0.2b (versi terkini)

- [x] Setelah DPB: detail DPB + jalur review Manajemen Harga / shortcut konfirmasi
- [x] Modal dari titik PBF (`rata_rata` / `tertinggi`)
- [x] Warning margin VIP berbasis modal satuan
- [x] Jejak `harga_modal_perhitungan` pada jalur DPB (audit)
- [x] Docs pricing + insight diselaraskan

---

## Referensi kode (setelah refactor)

| Komponen | Path |
|----------|------|
| Model obat / harga | `app/Models/Obat.php`, `HargaJualObat.php` |
| Modal & antrian | `app/Services/HargaModalService.php`, `ManajemenHargaService.php` |
| Rumus unit | `app/Services/HargaPerhitunganService.php` |
| Simpan harga | `app/Services/HargaJualService.php` |
| DPB | `app/Services/PembelianService.php`, `PembelianController.php` |
| UI harga | `resources/views/manajemen-harga/`, `pembelian/konfirmasi-harga.blade.php` |

---

## Catatan arsip untuk reviewer

- Brief asli (7 Jul 2026) merencanakan weighted-stok + redirect wajib ke konfirmasi per faktur — itu **diganti** oleh titik PBF + Manajemen Harga (Juli 2026).
- `cabang_id` nullable tetap memungkinkan harga per cabang nanti; praktik V0 = global.
- V0.3 import Excel belum dikerjakan; sketsa di brief asli masih boleh jadi titik awal desain import.
