# Modul Absensi (Attendance)

Modul ini menangani proses absensi harian pengguna dengan validasi GPS/lokasi.

## Daftar Isi
- [Arsitektur](#arsitektur)
- [Model & Database](#model--database)
- [Endpoint API](#endpoint-api)
- [Flow Absensi Harian](#flow-absensi-harian)
- [Validasi Lokasi](#validasi-lokasi)
- [Integrasi Mobile](#integrasi-mobile)

---

## Arsitektur

```
┌─────────────────────────────────────────────────────────────┐
│                    AttendanceController                      │
│  - checkIn(), checkOut(), today(), history()                │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    AttendanceService                         │
│  - checkIn(), checkOut(), getTodayStatus(), getHistory()    │
│  - findNearestOffice()                                       │
└─────────────────────────────────────────────────────────────┘
                              │
              ┌───────────────┼───────────────┐
              ▼               ▼               ▼
        ┌──────────┐    ┌──────────┐    ┌──────────┐
        │ Attendance│    │  Office  │    │ GeoHelper│
        │  Model    │    │  Model   │    │  Helper  │
        └──────────┘    └──────────┘    └──────────┘
```

---

## Model & Database

### Tabel `attendances`

| Kolom | Tipe | Deskripsi |
|-------|------|-----------|
| id | bigint | Primary key |
| user_id | bigint | FK ke users |
| company_id | bigint | FK ke companies |
| office_id | bigint | FK ke offices (nullable) |
| date | date | Tanggal absensi |
| type | enum | 'regular', 'remote', 'business_trip' |
| status | enum | 'present', 'late', 'early_leave', 'absent' |
| check_in_time | datetime | Waktu check-in |
| check_in_latitude | decimal(10,8) | Latitude check-in |
| check_in_longitude | decimal(11,8) | Longitude check-in |
| check_in_distance | integer | Jarak dari kantor (meter) |
| check_in_address | text | Alamat check-in |
| check_out_time | datetime | Waktu check-out |
| check_out_latitude | decimal(10,8) | Latitude check-out |
| check_out_longitude | decimal(11,8) | Longitude check-out |
| check_out_distance | integer | Jarak dari kantor (meter) |
| check_out_address | text | Alamat check-out |
| work_duration | integer | Durasi kerja (menit) |
| overtime_duration | integer | Durasi lembur (menit) |

### Tabel `offices`

| Kolom | Tipe | Deskripsi |
|-------|------|-----------|
| id | bigint | Primary key |
| company_id | bigint | FK ke companies (nullable untuk BPKP/Pemda) |
| name | string | Nama kantor |
| code | string | Kode kantor |
| address | text | Alamat lengkap |
| city | string | Kota/Kabupaten |
| province | string | Provinsi |
| latitude | decimal(10,8) | Latitude kantor |
| longitude | decimal(11,8) | Longitude kantor |
| radius | integer | Radius validasi (meter), default 100 |
| type | enum | 'headquarters', 'branch', 'bpkp', 'pemda' |
| is_active | boolean | Status aktif |

---

## Endpoint API

| Method | Endpoint | Deskripsi |
|--------|----------|-----------|
| GET | `/api/attendance/today` | Status absensi hari ini |
| POST | `/api/attendance/check-in` | Melakukan check-in |
| POST | `/api/attendance/check-out` | Melakukan check-out |
| GET | `/api/attendance/history` | Riwayat absensi |

### GET /api/attendance/today

Mendapatkan status absensi pengguna untuk hari ini.

**Request:**
```http
GET /api/attendance/today
Authorization: Bearer {token}
```

**Response:**
```json
{
  "success": true,
  "data": {
    "has_checked_in": true,
    "has_checked_out": false,
    "attendance": {
      "id": 1,
      "date": "2026-01-02",
      "type": "regular",
      "status": "present",
      "check_in_time": "2026-01-02T08:00:00.000000Z",
      "check_in_latitude": -6.2088,
      "check_in_longitude": 106.8456,
      "check_in_address": "Jl. Sudirman No. 1, Jakarta",
      "check_in_distance": 50,
      "check_out_time": null,
      "office": {
        "id": 1,
        "name": "Kantor Pusat Jakarta",
        "radius": 100
      }
    }
  }
}
```

### POST /api/attendance/check-in

Melakukan check-in absensi harian.

**Request:**
```http
POST /api/attendance/check-in
Authorization: Bearer {token}
Content-Type: application/json

{
  "latitude": -6.2088,
  "longitude": 106.8456,
  "address": "Jl. Sudirman No. 1, Jakarta Pusat",
  "notes": "Catatan opsional",
  "type": "regular"
}
```

**Response Sukses:**
```json
{
  "success": true,
  "message": "Check-in berhasil",
  "data": {
    "id": 1,
    "date": "2026-01-02",
    "check_in_time": "2026-01-02T08:00:00.000000Z",
    "status": "present"
  }
}
```

**Response Gagal (Di luar radius):**
```json
{
  "success": false,
  "message": "Anda berada di luar radius kantor. Jarak: 250 meter",
  "data": {
    "distance": 250,
    "required_radius": 100
  }
}
```

### POST /api/attendance/check-out

Melakukan check-out absensi harian.

**Request:**
```http
POST /api/attendance/check-out
Authorization: Bearer {token}
Content-Type: application/json

{
  "latitude": -6.2088,
  "longitude": 106.8456,
  "address": "Jl. Sudirman No. 1, Jakarta Pusat",
  "notes": "Catatan opsional"
}
```

**Response:**
```json
{
  "success": true,
  "message": "Check-out berhasil",
  "data": {
    "id": 1,
    "check_out_time": "2026-01-02T17:00:00.000000Z",
    "work_duration": 540,
    "overtime_duration": 0
  }
}
```

---

## Flow Absensi Harian

```
┌─────────────┐    ┌─────────────────┐    ┌────────────────┐
│  User buka  │───▶│ GET /today      │───▶│ Tampilkan      │
│  halaman    │    │ Cek status      │    │ status & tombol│
└─────────────┘    └─────────────────┘    └────────────────┘
                                                   │
                   ┌───────────────────────────────┘
                   ▼
         ┌─────────────────┐
         │ Belum check-in? │
         └────────┬────────┘
                  │ Ya
                  ▼
┌─────────────────────────────────────────────────────────────┐
│                      PROSES CHECK-IN                         │
├─────────────────────────────────────────────────────────────┤
│ 1. Ambil lokasi GPS dari device                             │
│ 2. Kirim ke POST /check-in                                  │
│ 3. Server validasi jarak dari kantor terdekat               │
│ 4. Jika dalam radius → Check-in berhasil                    │
│ 5. Jika di luar radius → Tolak dengan pesan error           │
│ 6. Tentukan status: present/late berdasarkan jam masuk      │
└─────────────────────────────────────────────────────────────┘
                  │
                  ▼
         ┌─────────────────┐
         │ Sudah check-in? │
         └────────┬────────┘
                  │ Ya
                  ▼
┌─────────────────────────────────────────────────────────────┐
│                      PROSES CHECK-OUT                        │
├─────────────────────────────────────────────────────────────┤
│ 1. Ambil lokasi GPS dari device                             │
│ 2. Kirim ke POST /check-out                                 │
│ 3. Hitung durasi kerja                                      │
│ 4. Hitung overtime jika lewat jam pulang                    │
│ 5. Update status jika early_leave                           │
└─────────────────────────────────────────────────────────────┘
```

---

## Validasi Lokasi

### Algoritma Haversine

Sistem menggunakan formula Haversine untuk menghitung jarak antara koordinat user dengan koordinat kantor.

```php
// GeoHelper::calculateDistance()
public static function calculateDistance(
    float $lat1, float $lon1,
    float $lat2, float $lon2
): float {
    $earthRadius = 6371000; // meter

    $latDiff = deg2rad($lat2 - $lat1);
    $lonDiff = deg2rad($lon2 - $lon1);

    $a = sin($latDiff / 2) * sin($latDiff / 2) +
         cos(deg2rad($lat1)) * cos(deg2rad($lat2)) *
         sin($lonDiff / 2) * sin($lonDiff / 2);

    $c = 2 * atan2(sqrt($a), sqrt(1 - $a));

    return $earthRadius * $c;
}
```

### Penentuan Status

| Kondisi | Status |
|---------|--------|
| Check-in sebelum jam masuk | `present` |
| Check-in setelah jam masuk | `late` |
| Check-out sebelum jam pulang | `early_leave` |
| Tidak ada check-in | `absent` |

---

## Integrasi Mobile

### Langkah Check-in

1. **Buka halaman absensi** → Panggil `GET /api/attendance/today`
2. **Cek status** → Jika belum check-in, tampilkan tombol Check-in
3. **Ambil lokasi** → Gunakan GPS device untuk mendapatkan koordinat
4. **Validasi lokal** (opsional) → Cek apakah dalam radius sebelum kirim
5. **Kirim request** → `POST /api/attendance/check-in`
6. **Handle response** → Tampilkan pesan sukses/error

### Langkah Check-out

1. **Cek status** → Jika sudah check-in tapi belum check-out
2. **Ambil lokasi** → Gunakan GPS device
3. **Kirim request** → `POST /api/attendance/check-out`
4. **Tampilkan ringkasan** → Durasi kerja, overtime, dll

### Pengaturan Lokasi Editable

Jika `company.allow_editable_location = true`:
- Tampilkan peta interaktif
- User bisa menggeser marker
- Koordinat yang dikirim adalah posisi marker

Jika `company.allow_editable_location = false`:
- Lokasi diambil langsung dari GPS
- User tidak bisa mengubah koordinat
