# API Mobile LPK TIP — Dokumentasi (v1)

**Base URL:** `https://{host}/api/v1` · **Format:** JSON (`Accept: application/json`)
**Auth:** `Authorization: Bearer {token}` (Sanctum) — semua endpoint kecuali `auth/login`
**Status:** MVP BACKEND ✅ — Sprint M0–M5 (auth, dashboard, notifikasi, material/stok, scan QR, absensi, keamanan)

---

## 1. Format Respons

```json
{ "success": true, "data": {...}, "message": "OK" }
{ "success": false, "data": null, "message": "Pesan error", "errors": {...} }
```

- HTTP `200` sukses · `401` token invalid · `404` tidak ditemukan · `422` validasi
- Login dibatasi **10 percobaan/menit**

---

## 2. Auth

### POST `/auth/login`
```json
{ "username": "admin", "password": "123456" }
```
→ `200`: `{ "token": "1|...", "user": { id, name, username, nik, email, phone, employee_id } }`
- `username` = username **atau NIK** (dicocokkan via employees)
- `401`: username/password salah

### POST `/auth/logout` · GET `/me`

---

## 3. Dashboard

### GET `/dashboard/summary`
```json
{
  "greeting": "Selamat pagi", "date": "2026-08-14",
  "kpis": {
    "material_count": 5129,
    "stock_low_count": 12,
    "attendance_today": { "date", "is_present", "actual_in", "actual_out", "is_manual" },  // null jika belum absen
    "notifications_unread": 3
  },
  "stock_low": [ { "code", "name", "stock", "min_stock" } ]   // top 5
}
```

---

## 4. Notifikasi

| Method | Endpoint | Query |
|---|---|---|
| GET | `/notifications` | `unread_only=1`, `per_page` (default 15, maks 50) |

`data`: `{ current_page, per_page, total, data: [{ id, type, message, link, is_read, created_at }] }`

| Method | Endpoint |
|---|---|
| POST | `/notifications/{id}/read` |
| POST | `/notifications/read-all` |

---

## 5. Material & Stok

### GET `/materials`
Query: `search` (kode/nama), `type_id`, `group_id`, `per_page` (default 20, maks 100)
```json
"data": [{ "id", "code", "name", "spec", "type", "group", "unit", "unit_price", "min_stock", "stock_total" }]
```

### GET `/materials/{code}` — detail + stok per gudang
```json
"data": { "code", "name", "spec", "type", "group", "unit", "unit_price", "min_stock", "is_active",
          "stock_total": 25,
          "stocks": [ { "warehouse": "GUDANG PUSAT", "quantity": 20 }, ... ] }
```

### GET `/materials/{code}/movements`
Query: `limit` (default 20, maks 100)
```json
"data": { "code", "name", "movements": [ { "id", "type", "reference_no", "quantity", "balance_after", "warehouse", "note", "created_at" } ] }
```

### GET `/materials/qr-check?code=` — SCAN QR (Sprint M3)
Terima hasil scan kamera:
- format QR label: `LPK-TIP|CODE|NAMA` (kode di bagian ke-2)
- atau kode polos: `CODE`
→ respons sama dgn `GET /materials/{code}` (stok real-time)

**Alur Flutter:** kamera scan → `qr-check?code={raw}` → tampil detail (nama, spesifikasi, **stok per gudang**).

---

## 6. Absensi (Geotagging) — Sprint M4

### GET `/attendance/config`
Data: `employee` (id, name, nik, shift), `period` aktif, `locations` (peta radius utk maps), `today` (status check-in/out).

### POST `/attendance/check` (multipart/form-data)
Body: `type` (in|out), `latitude`, `longitude`, `accuracy`, `is_mocked`, `photo` (file selfie), `device_info`
→ `200`: `{ id, type, date, time, location, photo }` · `422` dengan pesan penolakan.

**Validasi server (anti fake location):**
1. `is_mocked=true` (emulator/GPS spoof) → **ditolak**
2. `accuracy > 100m` → ditolak ("Akurasi GPS terlalu rendah")
3. Jarak (haversine) ke lokasi terdekat > radius lokasi → ditolak ("di luar radius absensi")
4. Absen ganda per hari per tipe → ditolak

### GET `/attendance/history?month=YYYY-MM`
Riwayat: date, type, actual_in/out, is_present, is_mobile, is_manual, location, photo (URL).

**Master lokasi:** web → Master Data → **Lokasi Absensi** (nama, lat/long, radius meter).

### Keamanan (Sprint M5)
- Semua endpoint protected: `auth:sanctum` + `throttle:api` (**120/menit/user**)
- Login: **10 percobaan/menit** → `429`
- Audit: login/logout API tercatat di `user_logs` (History Login di web)
- Error non-JSON → selalu respons JSON (`404`, `429`, `500`)

---

## 7. Panduan Integrasi Flutter

1. **Login** simpan `token` (secure storage) → kirim `Authorization: Bearer`.
2. **401** → logout paksa & minta login ulang.
3. **Offline**: cache list material (sqflite); scan QR tetap online (stok real-time).
4. **Paginasi**: gunakan `current_page`/`per_page`/`total` utk infinite scroll.
5. **Error**: tampilkan `message` (Bahasa Indonesia) + `errors` utk validasi.

---

## 8. Status Sprint

| Sprint | API | Status |
|---|---|---|
| M0 | auth (login/logout/me) | ✅ |
| M1 | dashboard/summary, notifications | ✅ |
| M2 | materials, materials/{code}, movements | ✅ |
| M3 | qr-check (scan QR) + dokumen API ini | ✅ |
| M4 | attendance (geotagging) | ✅ config/check/history + anti fake location |
| M5 | keamanan & rilis | ✅ throttle 120/menit + login 10/menit, audit `user_logs`, JSON error |
