# Attendance Review V2 Detection Formula Engine

File diagram:
- `docs/flowcharts/attendance-review-v2-detection-formula-engine.drawio`

Fokus diagram:
- flow `detect -> queue job -> processDetectionJob -> reviewData -> calculationData`
- rumus status harian dari log mentah
- pembatasan jam berdasar shift planning, SPL approved, dan auto overtime
- rumus kalkulasi jam normal, jam lembur, break deduction, dan weighted overtime

Dasar implementasi:
- [AttendanceReview routes](D:\APP\LARAVEL_DEFAULT\AHSP\app\Modules\AttendanceReview\routes\web.php)
- [AttendanceReviewV2Controller.php](D:\APP\LARAVEL_DEFAULT\AHSP\app\Modules\AttendanceReview\Http\Controllers\AttendanceReviewV2Controller.php)
- [AttendanceReviewController.php](D:\APP\LARAVEL_DEFAULT\AHSP\app\Modules\AttendanceReview\Http\Controllers\AttendanceReviewController.php)
- [RunAttendanceDetectionJob.php](D:\APP\LARAVEL_DEFAULT\AHSP\app\Modules\AttendanceReview\Jobs\RunAttendanceDetectionJob.php)

## 1. Entry Point V2

Route V2 memakai controller turunan, tetapi engine utamanya tetap diwarisi dari controller induk:
- `POST /attendance-review-v2/detect` -> `runDetection()`
- queue job -> `RunAttendanceDetectionJob`
- job memanggil `processDetectionJob()`
- hasil dibaca kembali oleh:
  - `reviewData()` untuk matrix review
  - `calculationData()` untuk ringkasan jam dan OT

## 2. Input Detection

Detection engine membaca kombinasi data berikut:
- profil bulan aktif
- assignment karyawan aktif di profil bulan
- shift utama / shift alternatif rotasi mingguan
- custom shift schedule per tanggal bila ada
- attendance log mentah
- rounding rule global atau rule assignment
- kalender kerja: `workday`, `holiday`, `holiday_special`, weekend
- SPL approved untuk membuka window lembur sebelum / sesudah shift

## 3. Rumus Detection Harian

Per karyawan dan per tanggal:

1. Tentukan shift aktif:
- prioritas custom schedule tanggal itu
- kalau tidak ada, pakai assignment bulanan
- kalau mode rotate mingguan aktif, pilih primary / alt berdasarkan block rotasi

2. Bentuk planning:
- `shiftStart = tanggal + start_time`
- `shiftEnd = tanggal + end_time`
- kalau `shiftEnd <= shiftStart`, berarti cross-day dan `shiftEnd + 1 hari`

3. Bentuk window kandidat log:
- `windowStart = shiftStart - 6 jam`
- `windowEnd = shiftEnd + 8 jam`
- ambil log mentah karyawan yang jatuh di window itu

4. Klasifikasi jumlah log:

`count == 0`
- `status = no_log`
- `reason = Tidak ada log dalam window shift`
- `confidence = 0`

`count == 1`
- jika `singleLog <= shiftStart + 4 jam`
  - `status = missing_out`
  - `checkin = singleLog`
  - `suggestedOut = shiftEnd`
  - `confidence = 62.5`
- selain itu
  - `status = missing_in`
  - `checkout = singleLog`
  - `suggestedIn = shiftStart`
  - `confidence = 58.0`

`count >= 2`
- `checkin = firstLog`
- `checkout = lastLog`
- `startDiff = abs(shiftStart - checkin)` dalam menit
- `endDiff = abs(shiftEnd - checkout)` dalam menit
- `confidence = clamp(100 - ((startDiff + endDiff) / 4), 10, 100)`

Rule status:
- `startDiff <= 180 && endDiff <= 240` -> `normal`
- `startDiff > 240 && endDiff <= 240` -> `shift_transition`
- `startDiff <= 180 && endDiff > 300` -> `shift_transition`
- selain itu -> `unpaired`
  - `suggestedIn = shiftStart`
  - `suggestedOut = shiftEnd`

## 4. Rounding

Jika rounding aktif:
- target `checkin`, `checkout`, atau `both`
- method:
  - `floor`
  - `ceil`
  - `nearest`
- jika `discipline_mode = true`:
  - check-in dipaksa `ceil`
  - check-out dipaksa `floor`

Rumus umum:
- `totalMinute = hour * 60 + minute`
- `rounded = floor|ceil|round(totalMinute / interval) * interval`

## 5. Capping Oleh SPL Approved / Auto OT

Jika jam masuk lebih awal dari planning:
- bila `auto OT workday mode = full_actual`, check-in tetap pakai log aktual
- bila tidak ada SPL awal approved, check-in dicap ke `shiftStart`
- bila ada SPL awal approved, check-in minimal = `approved pre_start`

Jika jam pulang lebih akhir dari planning:
- bila `auto overtime without SPL = true`, checkout tetap pakai log aktual
- bila tidak ada SPL akhir approved, checkout dicap ke `shiftEnd`
- bila ada SPL akhir approved, checkout maksimal = `approved post_end`

Engine juga menambahkan trace ke reason:
- nomor dokumen SPL
- kode job bila ada
- tipe SPL
- jam window SPL

## 6. Simpan / Reset Approval

Sebelum save:
- kalau review lama sudah approved, engine bandingkan core field penting
- jika inti detection tidak berubah, approval dipertahankan
- jika inti berubah, `is_approved`, `approved_by`, `approved_at` di-reset

Field inti yang dibandingkan:
- status
- effective in / out
- planned shift
- suggested in / suggested out
- cross-day flag

## 7. Overlay Untuk Review Matrix

Saat `reviewData()` dipanggil, V2 menambahkan lapisan informasi:

Priority tag:
1. `leave_approved`
2. `overtime_*`
3. `forgetting_clock_*`

Effective status:
- jika leave approved -> `leave_approved`
- jika `status = no_log` tapi ada `overtime_approved` -> tampil `overtime_approved`
- selain itu pakai status review asli

Deteksi `Butuh SPL`:
- tidak ada SPL apapun pada tanggal itu
- bukan auto overtime
- `rawOut > plannedEnd`
- selisih menit >= `ATTENDANCE_MISSING_SPL_THRESHOLD_MINUTES`

Deteksi `Butuh Extend SPL`:
- ada SPL approved
- bukan auto overtime
- `rawOut > approved post_end`
- selisih menit >= `ATTENDANCE_EXTEND_SPL_THRESHOLD_MINUTES`

## 8. Rumus Calculation Data

Urutan day type:
1. kalau ada `workday_map` -> `workday`
2. kalau ada `holiday_special_map` -> `holiday_special`
3. kalau ada `holiday_map`:
   - label weekend -> `weekend`
   - selain itu -> `holiday`
4. kalau hari Sabtu/Minggu -> `weekend`
5. fallback -> `workday`

Planned minutes:
- ambil `work_minutes_per_day` dari shift planning bila ada
- kalau kosong, pakai selisih `planned_shift_end - planned_shift_start`
- fallback akhir = `480 menit`

Effective actual in/out:
- workday tetap mengikuti hasil cap SPL / auto OT
- holiday / weekend / holiday special memakai actual review

Rumus utama:
- `actualMinutes = diff(effectiveCheckin, effectiveCheckout)`
- `normalMinutes = workday ? min(actualMinutes, plannedMinutes) : 0`
- `overtimeMinutes = workday ? max(0, actualMinutes - plannedMinutes) : (splApproved ? actualMinutes : 0)`
- `overtimeHoursGross = overtimeMinutes / 60`
- `breakDeductionHours = rule break untuk dayType, kecuali NoRest-2`
- `overtimeHours = max(0, overtimeHoursGross - breakDeductionHours)`
- `weightedOvertimeHours = apply multiplier timeline`

Timeline multiplier:
- rules dibaca dari `profil_perkalian_jam_detail`
- slot dihitung per jam ke-1, jam ke-2, dst
- rule break `is_break = true` memotong jam lembur, kecuali mode skip break
- bila `holiday_special` tidak punya rule khusus, fallback engine otomatis:
  - `1 s/d 24 jam -> x4`

## 9. Output Engine

Output utama yang dipakai V2:
- attendance daily review row
- history perubahan review
- matrix review per tanggal
- summary jam aktual
- summary jam normal
- summary overtime bersih
- dynamic OT bucket per multiplier
- weighted overtime untuk payroll engine

## Catatan Implementasi

- `AttendanceReviewV2Controller` hanya menambah tampilan V2 dan helper simulasi rule SPL khusus.
- Rumus detection dan calculation inti tetap berjalan di [AttendanceReviewController.php](D:\APP\LARAVEL_DEFAULT\AHSP\app\Modules\AttendanceReview\Http\Controllers\AttendanceReviewController.php).
- Jadi flowchart ini memang menggambarkan engine yang dipakai halaman Attendance Review V2 saat ini, bukan engine terpisah.
