# Sprint — Enhancement Absensi (Registrasi Wajah & Verifikasi)

**Aplikasi:** LPK TIP v2 (Web Laravel + Mobile Flutter)
**Tanggal:** 2026-08-14 · **Status:** Perencanaan (draft sprint A1–A5)
**Dasar:** alur absensi geotagging yang sudah berjalan → ditambah **registrasi wajah & verifikasi biometrik**

---

## 1. Ringkasan & Tujuan

Menambah lapisan keamanan absensi mobile:
1. **Admin** mendaftarkan karyawan untuk absensi mobile → mengirim **tautan registrasi via email**.
2. **Karyawan** membuka tautan → **registrasi dengan 3 foto wajah** (tampak depan, samping kiri/kanan, senyum).
3. Saat **absen**, selain validasi lokasi (sudah ada), sistem **verifikasi wajah** apakah wajah yang absen sesuai dengan foto terdaftar.

---

## 2. Alur (Use Case)

### Skenario A — Pendaftaran oleh Admin *(KEPUTUSAN: TANPA EMAIL/UNDANGAN — registrasi langsung dari aplikasi)*

> **Perubahan:** Undangan via email dihapus. Registrasi wajah dilakukan langsung dari **aplikasi mobile** (login → Absen → "Registrasi Wajah Sekarang"). Menu Karyawan hanya menampilkan status **Terdaftar / Belum**.
```
[WEB] SDM & Relasi → Karyawan → aksi "Daftarkan Absensi Mobile"
  → pilih karyawan (dari daftar yg sudah didaftarkan admin)
  → sistem generate token registrasi (acak, kedaluwarsa 48 jam)
  → kirim email ke alamat karyawan (SMTP trocon-noreply@wiyaga.com)
     berisi tautan: https://trocon-lpk.aplikasigo.com/absen/registrasi/{token}
  → status registrasi tampil di daftar (Menunggu / Terdaftar / Kedaluwarsa)
```

### Skenario B — Registrasi Karyawan (3 foto) — dari APLIKASI
```
[APK] login → Absen → "Registrasi Wajah Sekarang"
  → kamera 3 posisi/mimik (Depan, Samping, Senyum) — LIVE
  → kirim ke API /attendance/face-register → tersimpan
  → (fallback web /absen/registrasi/{token} tetap ada utk browser)
```

  → halaman registrasi: data karyawan (nama/NIK) + instruksi
  → unggah 3 foto: TAMPAK DEPAN · TAMPak SAMPING · SENYUM
  → sistem simpan foto + ekstrak/verifikasi (proses embedding wajah)
  → selesai → status "Terdaftar" + notifikasi ke admin
```

### Skenario C — Absen dengan Verifikasi Wajah
```
[MOBILE] tab Absen → Masuk/Keluar
  → (1) lokasi GPS + radius (sudah ada)
  → (2) foto selfie (sudah ada) → dikirim ke server
  → (3) SERVER: bandingkan wajah selfie vs foto terdaftar karyawan
        → cocok (skor ≥ ambang) → absen diterima
        → tidak cocok / wajah tak terdeteksi → absen ditolak (pesan jelas)
  → riwayat absensi menampilkan hasil verifikasi (Cocok / Tidak cocok / Belum terdaftar)
```

---

## 3. Desain Teknis

### 3.1 Tabel baru (migrasi)
```sql
attendance_registrations
  id, employee_id (FK), email_to, token (unique), status
  -- status: pending | registered | expired | revoked
  expires_at, registered_at, created_by, timestamps

-- foto wajah terdaftar
attendance_faces
  id, attendance_registration_id (FK), employee_id (FK),
  angle ('front' | 'side' | 'smile'), photo_path,
  embedding JSON (vektor 128d) NULL,  -- hasil ekstraksi wajah
  created_at
```

### 3.2 Email (SMTP)
| Konfigurasi Laravel | Nilai |
|---|---|
| `MAIL_MAILER` | `smtp` |
| `MAIL_HOST` | `mail.wiyaga.com` |
| `MAIL_PORT` | `465` |
| `MAIL_ENCRYPTION` | `ssl` |
| `MAIL_USERNAME` | `trocon-noreply@wiyaga.com` |
| `MAIL_PASSWORD` | *(password email akun)* |
| `MAIL_FROM_ADDRESS` | `trocon-noreply@wiyaga.com` |
| `MAIL_FROM_NAME` | `LPK TIP — No Reply` |

- Kirim email via Mailable + template (logo TIP, nama karyawan, tombol tautan).
- Token: `Str::random(32)` / hashed, kedaluwarsa 48 jam; satu karyawan → satu token aktif (token lama di-revoke saat kirim ulang).

### 3.3 Verifikasi Wajah — Rekomendasi
| Opsi | Teknologi | Kelebihan | Kekurangan |
|---|---|---|---|
| **① Python service (disarankan)** | `FastAPI` + `face_recognition` (dlib) / `insightface` | Akurat, self-host gratis, embedding 128D | Perlu service kecil di server (atau server terpisah) |
| ② PHP library | `face-recognition` PHP wrapper (FFI) | Satu bahasa | Kurang matang, butuh libnative dlib |
| ③ Cloud (Azure Face / AWS Rekognition) | API berbayar | Akurasi tinggi, mudah | Biaya per deteksi, data wajah ke cloud |
| ④ OpenCV dasar | PHP GD tidak cukup | — | Tidak direkomendasikan utk identifikasi |

**Rekomendasi:** buat **micro-service Python** (mis. `/face/embed` & `/face/verify`) yang dipanggil Laravel via HTTP:
```
POST /face/embed   (foto registrasi ×3) → simpan embedding 128D per foto
POST /face/verify  (foto selfie)        → skor 0..1 vs embedding terdaftar
```
- Ambang skor disetel (mis. ≥ 0.6) & bisa dikonfigurasi.
- Jika service tidak tersedia → fallback: simpan foto tanpa verifikasi (mode "belum terverifikasi") + flag ke admin.

### 3.4 Lokasi Absensi Berbasis Proyek (per-proyek)

**Aturan:** karyawan yang **dipasangkan ke proyek** (`employee_projects`) hanya dapat absen jika berada **di dalam radius proyek tsb** — bukan lokasi absensi umum.

#### Perubahan data (migrasi)
```sql
ALTER TABLE projects
  ADD latitude        DECIMAL(10,7) NULL,      -- titik lokasi proyek
  ADD longitude       DECIMAL(10,7) NULL,
  ADD absen_radius_m  INT UNSIGNED NULL DEFAULT 100,  -- toleransi radius absen per proyek
  ADD is_active_absen TINYINT(1) DEFAULT 1;           -- proyek aktif utk absen
```

#### Master Proyek (Web)
- Form Proyek ditambah field: **Latitude**, **Longitude**, **Radius Absensi (meter)**, **Aktif Absen**.
- (Koordinat diambil dari Google Maps — klik kanan → salin).

#### Logika validasi saat absen (server)
```
1. Ambil proyek karyawan (employee_projects aktif).
2. Jika karyawan punya proyek aktif-utk-absen:
     → hitung jarak GPS ke titik proyek (haversine)
     → valid HANYA jika jarak ≤ projects.absen_radius_m
     → jika di luar radius proyek → TOLAK (pesan: "Anda di luar radius proyek {nama}")
3. Jika karyawan TIDAK punya proyek:
     → fallback ke lokasi absensi umum (attendance_locations) — seperti sekarang.
```

#### API
- `GET /attendance/config` → kirim proyek karyawan: `{ project: { id, name, latitude, longitude, radius_m } }` — peta mobile menggambar **radius proyek** (bukan/atau di samping lokasi umum).
- `POST /attendance/check` → validasi radius proyek (prioritas) sebelum selfie/verifikasi wajah.

#### UI Mobile
- Jika karyawan dipasangkan proyek → peta menampilkan **titik proyek + lingkaran radius**; pesan jelas bila di luar radius.

### 3.5 Integrasi API absensi (sudah ada)
- `POST /api/v1/attendance/check` — tambah validasi:
  1. karyawan **harus terdaftar** (attendance_registrations.status = registered)
  2. foto selfie → `/face/verify` vs embedding terdaftar
  3. hasil disimpan di kolom baru `attendances.face_verified` (boolean/null) + `face_score`
- `GET /attendance/config` — tambah `face_registered` utk UI mobile (disable tombol jika belum terdaftar / tampil pesan "Hubungi admin untuk registrasi wajah")

### 3.6 Halaman Web
- **SDM & Relasi → Karyawan**: aksi **"Daftarkan Absensi Mobile"** (kirim email), status registrasi, tombol **Kirim Ulang / Cabut**.
- **Log Absensi**: kolom **Verifikasi Wajah** (badge Cocok/Tidak/Belum terdaftar) + bisa lihat foto selfie & foto terdaftar berdampingan.

---

## 4. Roadmap Sprint

| Sprint | Lingkup | Deliverable |
|---|---|---|
| **A1** | Master registrasi + email | Tabel `attendance_registrations`, aksi admin (pilih karyawan → kirim email tautan), Mailable SMTP wiyaga.com, status & kirim ulang/cabut |
| **A2** | Halaman registrasi karyawan | Route publik `/absen/registrasi/{token}` (validasi token & kedaluwarsa), form unggah **3 foto** (depan/samping/senyum), simpan `attendance_faces` |
| **A3** | Embedding wajah | Service Python `/face/embed` & `/face/verify`, pipeline registrasi → embedding 128D, ambang skor |
| **A4** | Verifikasi saat absen | API check → **radius proyek (per-proyek)** + wajib terdaftar + `/face/verify`; kolom `face_verified`/`face_score`; UI mobile (status terdaftar, peta radius proyek, pesan bila belum) |
| **A5** | Log & polish | Log Absensi + badge verifikasi, foto berdampingan, notifikasi admin, UAT & test |

**Estimasi:** ± 2–3 minggu (A1–A5).

---

## 5. Keamanan & Privasi
- Token registrasi **sekali pakai + kedaluwarsa 48 jam**; simpan hash token.
- Foto wajah disimpan di storage privat (bukan public) — hanya diakses via auth.
- Embedding wajah (angka) lebih aman disimpan daripada gambar mentah; gambar hanya utk audit.
- Log semua aksi (siapa kirim tautan, siapa registrasi, hasil verifikasi).
- Rate limit endpoint verifikasi (mis. 5 percobaan/menit per karyawan).

---

## 6. Kriteria Diterima (DoD)
- [ ] Admin kirim email tautan → karyawan menerima (SMTP wiyaga.com terkonfigurasi)
- [ ] Tautan kedaluwarsa & sekali pakai; kirim ulang mengganti token
- [ ] Registrasi berhasil menyimpan 3 foto + embedding wajah
- [ ] Absen dengan wajah cocok → diterima; tidak cocok / belum terdaftar → ditolak dgn pesan jelas
- [ ] Absen hanya valid dalam **radius proyek** (lat/long + `absen_radius_m` per proyek) utk karyawan ber-proyek; fallback lokasi umum utk tanpa proyek
- [ ] Log Absensi menampilkan hasil verifikasi wajah
- [ ] Test otomatis (email, token, registrasi, verifikasi, API) ≥ 6 kasus
- [ ] Dokumentasi API & panduan pengguna

---

## 7. Catatan Infrastruktur
- **SMTP**: `mail.wiyaga.com:465 (SSL)` — pastikan port 465 terbuka dari server; uji kirim 1 email terlebih dahulu.
- **Face service**: Python + dlib/insightface (±1 GB) — rekomendasi server terpisah atau VPS kecil; fallback matikan verifikasi bila layanan down.
- **Storage**: `storage/app/private/faces/...` utk foto registrasi & selfie verifikasi.
- **Koordinat proyek**: isi lat/long + radius utk tiap proyek aktif absen; tanpa isi → proyek tidak dipakai utk validasi radius (fallback lokasi umum).
