# Modul Perjalanan Dinas (Business Trip)

Modul ini menangani pencatatan absensi selama perjalanan dinas dengan flow multi-hari dan sistem klaim.

## Daftar Isi
- [Konsep Dasar](#konsep-dasar)
- [Arsitektur](#arsitektur)
- [Model & Database](#model--database)
- [Flow Perjalanan Dinas](#flow-perjalanan-dinas)
- [Tipe Absensi](#tipe-absensi)
- [Sistem Klaim](#sistem-klaim)
- [Lokasi Rekomendasi](#lokasi-rekomendasi)
- [Endpoint API](#endpoint-api)
- [Integrasi Mobile](#integrasi-mobile)

---

## Konsep Dasar

Perjalanan dinas adalah pencatatan absensi untuk karyawan yang sedang melakukan tugas di luar kantor utama. Sistem ini:

1. **Tidak memerlukan approval** untuk memulai perjalanan dinas
2. **Hanya mencatat absensi** selama perjalanan berlangsung
3. **Mendukung multi-hari** dengan tipe absensi berbeda per fase
4. **Sistem klaim** untuk keperluan reimbursement/laporan

---

## Arsitektur

```
┌─────────────────────────────────────────────────────────────┐
│                 BusinessTripController                       │
│  - create(), checkIn(), checkOut(), claim(), today()        │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                   BusinessTripService                        │
│  - create(), checkIn(), checkOut()                          │
│  - claim(), claimAll(), approveClaim(), rejectClaim()       │
│  - getRecommendedLocations(), getTodayStatus()              │
└─────────────────────────────────────────────────────────────┘
                              │
              ┌───────────────┼───────────────┐
              ▼               ▼               ▼
    ┌──────────────┐  ┌────────────────┐  ┌──────────┐
    │ BusinessTrip │  │BusinessTrip    │  │  Office  │
    │    Model     │  │Attendance Model│  │  Model   │
    └──────────────┘  └────────────────┘  └──────────┘
```

---

## Model & Database

### Tabel `business_trips`

| Kolom | Tipe | Deskripsi |
|-------|------|-----------|
| id | bigint | Primary key |
| user_id | bigint | FK ke users |
| company_id | bigint | FK ke companies |
| title | string | Judul/nama perjalanan (nullable) |
| description | text | Deskripsi perjalanan (nullable) |
| destination_city | string | Kota tujuan |
| destination_province | string | Provinsi tujuan |
| start_date | date | Tanggal mulai |
| end_date | date | Tanggal selesai |
| status | enum | 'ongoing', 'completed' |
| home_latitude | decimal | Koordinat rumah (untuk validasi pulang) |
| home_longitude | decimal | Koordinat rumah |

### Tabel `business_trip_attendances`

| Kolom | Tipe | Deskripsi |
|-------|------|-----------|
| id | bigint | Primary key |
| business_trip_id | bigint | FK ke business_trips |
| user_id | bigint | FK ke users |
| date | date | Tanggal absensi |
| type | enum | Tipe absensi (lihat bagian Tipe Absensi) |
| check_in_time | datetime | Waktu check-in |
| check_in_latitude | decimal | Koordinat check-in |
| check_in_longitude | decimal | Koordinat check-in |
| check_in_address | text | Alamat check-in |
| check_in_city | string | Kota check-in |
| check_in_province | string | Provinsi check-in |
| check_in_is_editable | boolean | Apakah lokasi bisa diedit |
| check_out_time | datetime | Waktu check-out |
| check_out_* | ... | Field checkout serupa check-in |
| reference_office_id | bigint | FK ke offices (BPKP/Pemda terdekat) |
| distance_from_reference | integer | Jarak dari kantor referensi (meter) |
| claim_status | enum | 'draft', 'claimed', 'approved', 'rejected', 'auto_approved' |
| claimed_at | datetime | Waktu klaim |
| approved_by | bigint | FK ke users (admin yang approve) |
| approved_at | datetime | Waktu approval |
| rejection_reason | text | Alasan penolakan |

---

## Flow Perjalanan Dinas

### Diagram Flow

```
HARI-1 (Keberangkatan)
┌─────────────────────────────────────────────────────────────┐
│  ① DEPARTURE (Keberangkatan)                                │
│     Check-in: Di rumah/kantor asal                          │
│     Check-out: Saat berangkat                               │
├─────────────────────────────────────────────────────────────┤
│  ② ARRIVAL (Kedatangan)                                     │
│     Check-in: Tiba di kota tujuan                           │
│     Check-out: Selesai aktivitas hari pertama               │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
HARI-2 s/d HARI-(N-1) (Harian)
┌─────────────────────────────────────────────────────────────┐
│  ③ DAILY (Presensi Harian)                                  │
│     Check-in: Mulai aktivitas di kota tujuan                │
│     Check-out: Selesai aktivitas                            │
│     * Validasi: Harus di kota yang sama dengan tujuan       │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
HARI-N (Kepulangan)
┌─────────────────────────────────────────────────────────────┐
│  ④ RETURN_DEPARTURE (Keberangkatan Pulang)                  │
│     Check-in: Di kota tujuan                                │
│     Check-out: Saat berangkat pulang                        │
├─────────────────────────────────────────────────────────────┤
│  ⑤ RETURN_ARRIVAL (Tiba di Rumah)                           │
│     Check-in: Tiba di rumah/kota asal                       │
│     Check-out: -                                            │
│     * Status trip berubah menjadi 'completed'               │
└─────────────────────────────────────────────────────────────┘
```

### Contoh Skenario 3 Hari

| Hari | Tanggal | Tipe | Aktivitas |
|------|---------|------|-----------|
| 1 | 2 Jan | departure | Check-in: 06:00 di Jakarta, Check-out: 06:30 berangkat |
| 1 | 2 Jan | arrival | Check-in: 10:00 tiba di Bandung, Check-out: 17:00 |
| 2 | 3 Jan | daily | Check-in: 08:00, Check-out: 17:00 di Bandung |
| 3 | 4 Jan | return_departure | Check-in: 08:00, Check-out: 12:00 berangkat pulang |
| 3 | 4 Jan | return_arrival | Check-in: 16:00 tiba di Jakarta |

---

## Tipe Absensi

| Type | Label | Kapan Digunakan |
|------|-------|-----------------|
| `departure` | Keberangkatan | Hari pertama, absensi pertama |
| `arrival` | Kedatangan | Hari pertama, setelah departure |
| `daily` | Presensi Harian | Hari ke-2 sampai H-1 sebelum pulang |
| `return_departure` | Keberangkatan Pulang | Hari terakhir, absensi pertama |
| `return_arrival` | Tiba di Rumah | Hari terakhir, setelah return_departure |

### Penentuan Tipe Otomatis

Sistem menentukan tipe absensi secara otomatis berdasarkan:

```php
private function determineAttendanceType(BusinessTrip $trip, string $action): string
{
    $isFirstDay = today()->equalTo($trip->start_date);
    $isLastDay = today()->equalTo($trip->end_date);

    if ($isFirstDay) {
        // Cek apakah departure sudah selesai
        return $hasDeparture ? 'arrival' : 'departure';
    }

    if ($isLastDay) {
        // Cek apakah return_departure sudah selesai
        return $hasReturnDeparture ? 'return_arrival' : 'return_departure';
    }

    return 'daily';
}
```

---

## Sistem Klaim

### Status Klaim

| Status | Deskripsi |
|--------|-----------|
| `draft` | Absensi tercatat, belum diklaim |
| `claimed` | User sudah klaim, menunggu approval admin |
| `approved` | Admin sudah menyetujui klaim |
| `rejected` | Admin menolak klaim (dengan alasan) |
| `auto_approved` | Otomatis disetujui (jika bypass aktif) |

### Flow Klaim

```
┌─────────────┐
│   DRAFT     │ ◄── Status awal setelah check-in
└──────┬──────┘
       │ User klik "Klaim"
       ▼
┌──────────────────────────────────────────────────────────┐
│  Cek company.bypass_claim_approval                       │
├────────────────────┬─────────────────────────────────────┤
│  true              │  false                              │
└────────┬───────────┴───────────────┬─────────────────────┘
         ▼                           ▼
┌─────────────────┐         ┌─────────────────┐
│  AUTO_APPROVED  │         │    CLAIMED      │ ◄── Menunggu admin
└─────────────────┘         └────────┬────────┘
                                     │
                    ┌────────────────┼────────────────┐
                    ▼                                 ▼
           ┌─────────────────┐               ┌─────────────────┐
           │    APPROVED     │               │    REJECTED     │
           └─────────────────┘               └─────────────────┘
```

### Kapan Klaim Dilakukan?

- **H-1 sebelum kepulangan**: User bisa klaim semua absensi sekaligus
- **Setelah trip selesai**: User bisa klaim di waktu kapan saja
- **Per absensi**: User bisa klaim satu per satu

---

## Lokasi Rekomendasi

### Jenis Kantor Referensi

| Type | Deskripsi | Cakupan |
|------|-----------|---------|
| `bpkp` | Kantor BPKP Perwakilan | Per Provinsi |
| `pemda` | Kantor Pemda | Per Kota/Kabupaten |

### Cara Kerja

1. User check-in dengan koordinat GPS
2. Sistem mencari kantor referensi terdekat berdasarkan:
   - BPKP di provinsi tujuan
   - Pemda di kota tujuan
3. Jarak dari kantor referensi dicatat untuk keperluan laporan
4. User bisa memilih kantor referensi secara manual

---

## Endpoint API

### Business Trip Management

| Method | Endpoint | Deskripsi |
|--------|----------|-----------|
| POST | `/api/business-trips` | Buat perjalanan dinas baru |
| GET | `/api/business-trips` | Daftar perjalanan dinas user |
| GET | `/api/business-trips/{id}` | Detail perjalanan dinas |
| GET | `/api/business-trips/{id}/today` | Status absensi hari ini |

### Check-in/Check-out

| Method | Endpoint | Deskripsi |
|--------|----------|-----------|
| POST | `/api/business-trips/{id}/check-in` | Check-in |
| POST | `/api/business-trips/{id}/check-out` | Check-out |

### Klaim

| Method | Endpoint | Deskripsi |
|--------|----------|-----------|
| POST | `/api/business-trips/{id}/claim` | Klaim semua absensi |
| POST | `/api/business-trip-attendances/{id}/claim` | Klaim satu absensi |

### Lokasi Rekomendasi

| Method | Endpoint | Deskripsi |
|--------|----------|-----------|
| GET | `/api/offices/recommendations` | Rekomendasi BPKP & Pemda |

---

### POST /api/business-trips

Membuat perjalanan dinas baru.

**Request:**
```json
{
  "title": "Audit Pemda Bandung",
  "description": "Pemeriksaan laporan keuangan",
  "destination_city": "Bandung",
  "destination_province": "Jawa Barat",
  "start_date": "2026-01-05",
  "end_date": "2026-01-07"
}
```

**Response:**
```json
{
  "success": true,
  "message": "Perjalanan dinas berhasil dibuat",
  "data": {
    "id": 1,
    "title": "Audit Pemda Bandung",
    "destination_city": "Bandung",
    "destination_province": "Jawa Barat",
    "start_date": "2026-01-05",
    "end_date": "2026-01-07",
    "status": "ongoing"
  }
}
```

### POST /api/business-trips/{id}/check-in

Check-in untuk perjalanan dinas.

**Request:**
```json
{
  "latitude": -6.9175,
  "longitude": 107.6191,
  "address": "Jl. Asia Afrika No. 81, Bandung",
  "city": "Bandung",
  "province": "Jawa Barat",
  "notes": "Catatan opsional",
  "is_editable": false,
  "reference_office_id": 2
}
```

**Response:**
```json
{
  "success": true,
  "message": "Check-in Kedatangan berhasil",
  "data": {
    "id": 1,
    "type": "arrival",
    "date": "2026-01-05",
    "check_in_time": "2026-01-05T10:00:00.000000Z",
    "reference_office": {
      "id": 2,
      "name": "BPKP Perwakilan Jawa Barat"
    },
    "distance_from_reference": 150
  }
}
```

### GET /api/offices/recommendations

Mendapatkan lokasi BPKP dan Pemda terdekat.

**Request:**
```http
GET /api/offices/recommendations?province=Jawa Barat&city=Bandung&latitude=-6.9175&longitude=107.6191
```

**Response:**
```json
{
  "success": true,
  "data": {
    "bpkp": [
      {
        "id": 2,
        "name": "BPKP Perwakilan Jawa Barat",
        "address": "Jl. Asia Afrika No. 81",
        "distance": 150
      }
    ],
    "pemda": [
      {
        "id": 5,
        "name": "Pemda Kota Bandung",
        "address": "Jl. Wastukancana No. 2",
        "distance": 500
      }
    ]
  }
}
```

---

## Integrasi Mobile

### Membuat Perjalanan Dinas

1. User masuk ke menu "Perjalanan Dinas"
2. Klik "Buat Baru"
3. Isi form: Judul, Deskripsi, Kota Tujuan, Provinsi, Tanggal
4. Submit → Perjalanan langsung aktif

### Check-in/Check-out Harian

1. Buka halaman "Perjalanan Dinas Aktif"
2. Sistem menampilkan tipe absensi yang diharapkan
3. Ambil lokasi GPS
4. (Opsional) Tampilkan peta dengan lokasi rekomendasi
5. Pilih kantor referensi atau biarkan otomatis
6. Submit check-in/check-out

### Proses Klaim

1. Setelah perjalanan selesai atau H-1 sebelum pulang
2. Buka detail perjalanan dinas
3. Lihat daftar absensi dengan status "draft"
4. Klik "Klaim Semua" atau klaim per item
5. Jika bypass aktif → langsung approved
6. Jika tidak → menunggu admin

### Tampilan Peta

```
┌─────────────────────────────────────────────────────────────┐
│                          PETA                                │
│                                                              │
│     📍 Lokasi Anda                                          │
│                                                              │
│     🏛️ BPKP Perwakilan Jawa Barat (150m)                   │
│     🏢 Pemda Kota Bandung (500m)                            │
│                                                              │
└─────────────────────────────────────────────────────────────┘
│  [Pilih sebagai referensi]  [Gunakan lokasi saya]          │
└─────────────────────────────────────────────────────────────┘
```
