# SPBU Manager — CLAUDE.md
> Source of truth untuk business logic, data flow, dan rules proyek SPBU Manager.
> Baca sebelum menulis kode apapun.
>
> Detail per layer: `frontend/CLAUDE.md` | `backend/CLAUDE.md`


## CodeGraph
CodeGraph MCP is active. Always use `codegraph_explore` as PRIMARY tool for codebase exploration before using Grep/Glob/Read. Use `codegraph_search` to find symbols, `codegraph_context` to build task context, `codegraph_callers`/`codegraph_callees` to trace call flow.

## Skills
- Backend (FastAPI, SQLAlchemy 2.x, Alembic, PostgreSQL) → `anthropic-skills:senior-backend`
- Frontend (React 18, TypeScript strict, Umi.js, Ant Design) → `anthropic-skills:senior-frontend`
- Visual planning, sitemap, wireframe → `anthropic-skills:michael-scott`
- Code audit, security review, refactor plan → `anthropic-skills:project-audit`

## Response Style (Caveman Mode)
Be concise. No filler, no hedging, no preamble, no trailing summaries. State conclusions first, reasoning second. Skip pleasantries. Lead with action or answer. Code over explanation unless asked.

---

## 1. Overview Proyek

**SPBU Manager** adalah sistem manajemen operasional multi-SPBU berbasis web.

### Tujuan Utama
- Digitalisasi laporan harian SPBU (penjualan, stok, expenses, penyetoran)
- Rekonsiliasi stok otomatis (losses/gain per shift & per hari)
- Tracking penebusan BBM ke Pertamina (DO → SO → Penerimaan)
- Deteksi anomali transaksi (quota BBM bersubsidi, double dip, losses berlebih)
- Manajemen kontrak sewa lahan tenant di area SPBU
- Laporan konsolidasi multi-SPBU untuk Super Admin

---

## 2. Tech Stack

| Layer | Technology |
|-------|------------|
| Frontend | Next.js 16 (App Router), TypeScript strict |
| UI Template | Tabler (https://tabler.io) — Bootstrap-based, bukan shadcn |
| State Management | TanStack Query (server state) + Zustand (UI state) |
| Form Validation | Zod + React Hook Form |
| Backend | FastAPI (Python 3.12+) |
| Database | PostgreSQL 16 |
| ORM | SQLAlchemy 2.x (async) + Alembic migrations |
| Auth | JWT (access + refresh token), bcrypt password hash |
| File Storage | Local filesystem (dev) / Google Drive (prod via `STORAGE_TYPE=gdrive`) |
| API Style | RESTful JSON, prefix `/api/v1/` |

### Prinsip Umum
- Backend: **repository pattern** — router → service → repository → model. Tidak ada raw query di router.
- Frontend: **server components by default**, client component hanya jika perlu interaktivitas.
- Selalu gunakan **enum** untuk status fields (bukan string bebas).
- Semua endpoint auth wajib punya `Depends(get_current_user)`.
- Gunakan **Pydantic v2** untuk semua schema.
- **Jangan pernah** simpan harga, volume, rupiah sebagai `float` — gunakan `Numeric(precision=15, scale=3)`.
- Next.js 16: middleware file harus bernama `proxy.ts`, fungsi harus bernama `proxy()`.
- **File upload**: selalu gunakan `UploadContext` + `save_upload()` dari `app/utils/file_upload.py`. Path: `{env}/{spbu_code}/{tipe}/{tahun}{bulan:02d}-{uuid8}-{filename}`. Backend memilih local vs Google Drive berdasarkan `settings.STORAGE_TYPE`.
  - `spbu_code` di `UploadContext` **harus** `nomor_pertamina` — gunakan `await get_spbu_code(db, spbu_id)` (dari `file_upload.py`), jangan `str(spbu_id)`.
  - Re-upload → hapus file lama via `delete_file(old_url)` setelah file baru tersimpan.
  - Delete record → hapus file terkait via `delete_file(url)` setelah DB commit.
  - Frontend display: selalu `getFileUrl(stored_url)` dari `lib/utils/file-url.ts` → routing ke `/api/v1/files/...`.

---

## 3. Roles & Permission

### Sistem Role: Custom & Flexible

Role **tidak fixed** — Super Admin dan SPBU Admin bisa membuat role baru dengan nama custom dan memilih permission secara granular **per modul per aksi**.

```
Hierarki kepemilikan role:
Super Admin
  └── bisa create/edit/delete role untuk semua SPBU
SPBU Admin
  └── bisa create/edit/delete role hanya untuk SPBU-nya sendiri
```

### Struktur Permission

Permission didefinisikan sebagai matriks: **Modul × Aksi**

**Daftar Modul:**
`dashboard` | `penjualan` | `stock` | `penebusan` | `penerimaan` | `expenses` | `penyetoran` | `rekonsiliasi` | `laporan` | `analytics` | `anomali` | `products` | `tangki` | `spbu_settings` | `contracts` | `users` | `spbu_management` | `operators` | `jadwal` | `absensi` | `housekeeping`

**Daftar Aksi:** `view` | `create` | `edit` | `delete` | `export` | `approve`

Tidak semua aksi relevan untuk semua modul. `approve` hanya di `penjualan`, `penyetoran`, `rekonsiliasi`.

### Implementasi di Backend

```python
# Super Admin (is_superadmin=True) bypass semua permission check
def has_permission(user, spbu_id, modul, aksi) -> bool:
    if user.is_superadmin:
        return True
    assignment = get_assignment(user.id, spbu_id)
    if not assignment:
        return False
    return role_has_permission(assignment.role_id, modul, aksi)
```

### Opsi B — Admin Auto-Approve
User dengan `penjualan:approve` (atau `stock:approve`) → backend langsung set `APPROVED` saat create. Frontend tampilkan tombol **"Simpan & Approve"**.

### Role Bawaan (Seed Data)

| Nama Role | Deskripsi |
|-----------|-----------|
| SPBU Admin | Akses penuh semua modul kecuali spbu_management |
| Manager | View + export semua modul, tidak bisa create/edit/delete/approve |
| Operator | Create di penjualan, stock, expenses, penyetoran; view terbatas |
| Viewer | View-only semua modul tanpa export |

---

## 4. Master Data

### 4.1 SPBU
- Dikelola di level Super Admin
- Setiap SPBU punya konfigurasi independen: nama, nomor Pertamina, alamat, rekening bank, shift, island & nozzle, tangki, threshold

**Threshold per SPBU:**
| Field | Default | Keterangan |
|-------|---------|------------|
| `teller_discrepancy_threshold_pct` | 0.300% | % selisih manual vs digital teller. Min 1 L. Trigger anomali `METER_DISCREPANCY` |

> `losses_threshold_penerimaan_pct` dan `losses_threshold_penjualan_pct` sudah **dipindah ke master_produk** — lihat migration `m9c0d1e2f3a4`.

### 4.2 Shift
- Dinamis per SPBU (bisa 2, 3, atau lebih shift)
- **Shift overnight** (jam_selesai < jam_mulai) → sistem handle tanggal +1
- Stok awal Shift N = Stok akhir Shift N-1 (tidak boleh ada gap)

### 4.3 Island & Nozzle
```
SPBU → Island → Nozzle (1 Nozzle = 1 Produk = 1 Tangki Sumber)
```
- Nozzle **tidak punya nomor seri** — identifikasi cukup dengan nama (1A, 1B, dll)
- Setiap nozzle punya **dua teller**: manual & digital
- `primary_teller` per nozzle — default 'manual'. Volume penjualan dihitung dari primary_teller.

### 4.4 Tangki Pendam
- Setiap tangki punya **tabel kalibrasi** (dipstick → volume): CSV dua kolom `tinggi_cm`, `volume_liter`
- Konversi: **interpolasi linear** (`app/utils/kalibrasi.py`). Input sounding dalam **mm**.
- Di luar range kalibrasi → **reject**, tidak extrapolate
- Tabel kalibrasi mengikuti **fisik tangki** — tidak berubah meski produk berganti

### 4.5 Products (Master Produk)
- Master produk dikelola global (bukan per SPBU)
- Aktivasi produk per SPBU: di `/spbu/[id]/settings` tab Informasi Dasar
- Produk bersubsidi punya kuota harian per jenis kendaraan:
  - **Pertalite**: Roda 2 = 20 L, Roda 4 = 50 L, Roda 6+ = 200 L
  - **Biosolar**: Roda 4 = 50 L, Angkutan umum = 80 L, Roda 6+ = 200 L

---

## 5. Business Flow per Modul

> **PENTING:** Sebelum kerja di modul apapun, baca docs file-nya dulu.

| Modul | Docs |
|-------|------|
| Penjualan (laporan shift) | `docs/modul-penjualan.md` |
| Stock Adjustment (sounding) | `docs/modul-stock.md` |
| Penyetoran | `docs/modul-penyetoran.md` |
| Penebusan & Penerimaan BBM | `docs/modul-penebusan-penerimaan.md` |
| Expenses | `docs/modul-expenses.md` |
| Rekonsiliasi (Harian + E2E) | `docs/modul-rekonsiliasi.md` |
| Master data schema (users, spbu, produk, GA, dll) | `docs/schema-master.md` |

### Alur Laporan Harian (Core Flow)

```
Per shift, operator:
1. Stock Adjustment — sounding semua tangki (mm → volume via kalibrasi)
2. Input Penjualan — teller awal & akhir per nozzle + kas & pembayaran
3. Input Expenses — pengeluaran operasional
4. Penyetoran — auto-created dari penjualan, batch submit ke manager
```

Stok sounding shift N = stok awal shift N. Stok akhir shift N = stok awal shift N+1.

### Quick-ref status flow (semua modul operasional)

```
Draft → Submitted → Approved (= Locked)
           ↑             ↓
        Recall        Rejected → edit → Re-submit
                                            ↑
                           Unlock (by approver, wajib isi alasan)
```

→ Detail semua modul ada di `docs/modul-*.md`. Baca file yang relevan sebelum kerja.

---

## 6. Anomali Detection

❌ Belum diimplementasikan (model, migration, router, `utils/anomali.py` belum ada).

| Kode | Trigger |
|------|---------|
| `QUOTA_EXCEEDED` | Volume per plat per produk per hari > kuota |
| `DOUBLE_DIP` | Plat sama isi di 2 SPBU berbeda dalam 1 hari |
| `METER_DISCREPANCY` | Selisih manual vs digital > threshold (min 1 L) |
| `LOSSES_EXCEEDED` | Losses tangki > threshold penjualan/penerimaan |
| `NEGATIVE_STOCK` | Stok teoritis < 0 |

Status: `New → Investigating → Closed (Valid) / False Positive`

---

## 7. Contracts (Sewa Lahan)

- Nama modul di UI: **"Contracts"** (bukan "Sewa Lahan")
- Alert otomatis jika kontrak < 90 hari dari jatuh tempo
- Status: `Active → Expiring Soon → Expired`

---

## 8. Database Schema

→ Detail schema per modul ada di `docs/modul-*.md` dan `docs/schema-master.md`.

---

## 9. Business Rules Penting

1. **Stok tidak boleh negatif.** Validasi di backend sebelum simpan.
2. **Harga penjualan** = harga berlaku pada tanggal laporan shift, bukan harga current.
3. **Shift overnight** (jam_selesai < jam_mulai) → tanggal laporan = tanggal shift mulai.
4. **Teller akhir ≥ teller awal**, kecuali `flag_reset_teller = true`.
5. **Teller awal** = `teller_terakhir` dari shift sebelumnya (otomatis). Shift pertama nozzle = input manual.
6. **Volume penjualan** selalu dihitung dari `primary_teller` nozzle saat save.
7. **Digital sounding wajib** semua tangki aktif sebelum submit. Manual boleh partial.
8. **Volume final sounding** = manual jika ada, fallback ke digital.
9. **Kalibrasi interpolasi linear** — di luar range → reject. `app/utils/kalibrasi.py`.
10. **Threshold losses** berbasis persentase: Penerimaan 0.15%, Penjualan 0.5%, Teller discrepancy 0.3% (min 1 L).
11. **Approved = Locked (semua modul)**: Setelah Approved, record tidak bisa diedit, tidak bisa tambah/hapus lampiran/foto, tidak bisa didelete — berlaku untuk penjualan, stock adjustment, penerimaan, expenses, penyetoran. Hanya user dengan permission `approve` di modul tersebut yang bisa **Unlock** (kembali ke Draft) dengan wajib mengisi alasan. Unlock tercatat di `unlocked_by_id`, `unlocked_at`, `unlock_reason`.
12. **Recall**: Operator SUBMITTED → DRAFT selama admin belum act. Dicatat di `recalled_by_id/recalled_at`.
13. **Opsi B auto-approve**: user dengan `penjualan:approve` atau `stock:approve` → APPROVED saat create. `force_draft=true` query param bypass ini (dev-only, untuk role switcher).
14. **Audit log** wajib: create, update, delete, submit, recall, review, unlock, import CSV, pemindahan produk.
    - Model `AuditLog` + tabel `audit_log` ✅ sudah ada. Call `log_action()` dari `app/utils/audit.py`.
    - Per-record history: `GET /api/v1/spbus/{id}/laporan-shift/{id}/activity-log` → baca `audit_log` WHERE `modul+object_id`. Pattern sama untuk modul lain bila diperlukan.
    - Frontend: `<AuditTimeline auditEntries={...}>` — render full history jika prop ada; fallback ke field tunggal di record jika tidak.
15. **Timezone**: DB = UTC. Display = Asia/Jakarta.
16. **Penebusan tanpa SO**: status `WAITING_SO`. Penerimaan hanya bisa dicatat setelah SO ada.
17. **Rekonsiliasi** hanya bisa dijalankan jika semua shift hari itu sudah Submitted.
18. **Delete strategy**: Master data (users, roles, spbu, shifts, islands, nozzles, tangki, produk, harga, tenants, kontrak, expense_kategori) → selalu soft delete via `deleted_at`. Operational/transactional data → hard delete dengan orphan check (cek FK sebelum delete, error message jika ada data terkait). `environment_mode` config key dipertahankan untuk keperluan dev lain, bukan untuk mengontrol delete behavior.
19. **Maintenance mode**: Superadmin bypass via `/login?staff=1`. `MaintenancePoller` polling 30 detik untuk user yang sudah login.
20. **Tabel kalibrasi tangki** tidak berubah saat produk berganti.
21. **Custom roles**: Super Admin bisa create role global, SPBU Admin hanya untuk SPBU-nya sendiri.
22. **Permission check**: is_superadmin → bypass semua. Otherwise cek role_permissions per modul per aksi.
23. **Role flags**: `can_be_scheduled` — role dapat dijadwalkan (operator/OB). `can_login_web` — role boleh akses web dashboard. Keduanya bisa di-toggle per role dari Roles & Permissions.
24. **Operator mode** (General Affairs): user dengan `can_be_scheduled = true` → tampilan terbatas: tidak ada date/shift picker, otomatis tanggal hari ini & shift aktif berdasarkan jam saat ini. Pending approvals disembunyikan. Berlaku di Attendance dan Housekeeping.
25. **PostgreSQL native enums**: Penambahan nilai ke enum (misal `modulenum`) **harus** via migration `ALTER TYPE ... ADD VALUE IF NOT EXISTS`. Jangan hanya update Python enum saja.
26. **Delete user**: Hanya Super Admin (`is_superadmin`) yang bisa hapus user. Tidak bisa hapus diri sendiri. Dev mode → hard delete, production → soft delete (`deleted_at`). Endpoint: `DELETE /users/{id}/delete`.
27. **User list scoping untuk SPBU Admin**: Ketika SPBU Admin membuka halaman Users, hanya tampilkan user dengan role `can_be_scheduled = true` yang ter-assign di SPBU tersebut (operator/OB). Manager/Viewer tidak ditampilkan.
28. **Kas penjualan**: Total kas (pecahan) + non-kas (kartu + QR + instansi) harus sama dengan total nilai penjualan shift. Validasi frontend (warning/error indicator), bukan hard-block.
    - **`kas_100k`..`kas_1k` menyimpan JUMLAH LEMBAR (bill count), bukan Rupiah.** Nilai Rupiah = `kas_100k×100000 + kas_50k×50000 + kas_20k×20000 + kas_10k×10000 + kas_5k×5000 + kas_2k×2000 + kas_1k×1000 + kas_logam`. `kas_logam` dan semua `pembayaran_*` sudah Rupiah langsung.
29. **BAST PDF**: Setiap modul yang punya approve (penjualan, stock, penerimaan, expenses, penyetoran) harus punya Berita Acara Serah Terima PDF setelah approved. Format: nama SPBU + tanggal + nama approver + timestamp sistem. ✅ Backend endpoint ada di semua modul (`GET /{id}/bast-pdf`). ✅ Frontend download button tersedia di semua modul.
30. **Penyetoran auto-create**: `_upsert_penyetoran()` dipanggil setiap kali `laporan_shift` di-save (create atau update). Hanya update amounts jika penyetoran masih DRAFT. Jika penyetoran sudah submitted/approved → amounts tidak diubah.

---

## 10. Status Implementasi

### ✅ Backend — Semua router sudah implemented
auth, spbu, products, users, roles, system, laporan-shift, stock-adjustment, penebusan, penerimaan, expenses, penyetoran, end-to-end, general-affairs (operators, jadwal, absensi, housekeeping).

### ✅ Frontend — Semua halaman operasional sudah done
penjualan, stock, penebusan, penerimaan, expenses, penyetoran, rekonsiliasi (E2E tab), track, products, spbu (list+create+settings), users+roles, login, maintenance.

**Approval UI sudah done di semua modul:**
- Penjualan: submit/recall/review/unlock + Download BA PDF
- Stock: submit/recall/review/unlock + Download BAST PDF
- Expenses: submit/recall/review/unlock + Download BAST PDF
- Penerimaan: submit/review/unlock + Download BAST PDF (no recall)
- Penyetoran: per-shift list + batch create/submit/approve + Download BAST PDF

### ✅ General Affairs — Semua halaman sudah done
- **Operators** — master data operator/OB
- **Jadwal** — jadwal shift per tanggal (grid view)
- **Attendance** (`/general-affairs/absensi`) — upload foto absensi per shift. Operator mode: auto tanggal+shift, no picker, no approvals.
- **Housekeeping** (`/general-affairs/housekeeping`) — deskripsi area + foto sebelum/sesudah per shift. Operator mode: sama seperti Attendance.

### ✅ PUMP-kun AI Chat Widget
Backend: `POST /api/v1/assistant` → `app/assistant/` (providers.py, service.py, tools.py)
Primary LLM: Google Gemini API. Fallback: Groq.

### ❌ TODO — Belum diimplementasikan

**Frontend pages:** `/dashboard`, `/laporan`, `/analytics`, `/anomali`

**Backend features:**
- Rekonsiliasi Harian — tab UI placeholder, model/router belum ada
- Pemindahan produk antar tangki — model, migration, router belum ada
- Anomali detection — `utils/anomali.py`, model, migration, router belum ada
- Audit log — ✅ model & tabel sudah ada (`audit_log`). Endpoint per-record di penjualan (`/laporan-shift/{id}/activity-log`). Modul lain belum ada endpoint activity-log-nya.
- CSV POS import (`penjualan_transaksi`)
- `tangki_produk_history`

---

## 11. Deployment (Production)

- **URL:** `https://spbu.goteku.com`
- **Server:** `root@103.41.206.254` (SSH via `~/.ssh/me_goteku_deploy`)
- **App root:** `/var/www/html/spbu.com`

```bash
# Deploy
git push origin main
# Di server:
cd /var/www/html/spbu.com && git pull origin main
cd frontend && NEXT_PUBLIC_API_URL=https://spbu.goteku.com npm run build
pm2 restart spbu-frontend && pm2 restart spbu-backend
```

| PM2 Name | Port |
|----------|------|
| `spbu-frontend` | 8007 |
| `spbu-backend` | **8006** (port 8000 dipakai project lain) |

**Nginx:** `/etc/nginx/sites-available/spbu.goteku.com` — htpasswd kecuali `/api/*` dan `/maintenance`

Detail backend conventions & migration history → `backend/CLAUDE.md`
Detail frontend conventions & URL structure → `frontend/CLAUDE.md`

---

*Versi: 2.1 — April 2026*
