PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]); } catch (PDOException $e) { die("DB Error"); } // Fungsi Helper untuk membuat UUID v4 (Native PHP 8.1+) function generate_uuid_v4(): string { $data = random_bytes(16); $data[6] = chr(ord($data[6]) & 0x0f | 0x40); $data[8] = chr(ord($data[8]) & 0x3f | 0x80); return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); } // Hanya proses jika ada HTTP POST //if ($_SERVER['REQUEST_METHOD'] === 'POST') { if(1 == 1) { $rawData = trim(file_get_contents("php://input")); if (!empty($rawData)) { // Ambil aturan absensi (Asumsi ID 1 adalah setting default) $stmtSettings = $pdo->query("SELECT * FROM attendance_settings LIMIT 1"); $settings = $stmtSettings->fetch(); if (!$settings) { die("OK"); // Hentikan jika belum ada setting, tapi tetap balas OK ke mesin } $lines = explode("\n", $rawData); foreach ($lines as $line) { $data = explode("\t", trim($line)); // Pastikan baris memiliki data minimal (User ID & Timestamp) if (count($data) < 2 || empty($data[0])) continue; $empNumber = $data[0]; $timestamp = $data[1]; // Format: YYYY-MM-DD HH:MM:SS $dateOnly = date('Y-m-d', strtotime($timestamp)); $timeOnly = date('H:i:s', strtotime($timestamp)); // -- LOGIKA PENENTUAN TYPE LOG (in/out) -- // Menentukan titik tengah (midpoint) antara work_start dan work_end $startStr = strtotime($dateOnly . ' ' . $settings['work_start']); $endStr = strtotime($dateOnly . ' ' . $settings['work_end']); $midpoint = $startStr + (($endStr - $startStr) / 2); $punchTime = strtotime($timestamp); $logType = ($punchTime < $midpoint) ? 'in' : 'out'; // ==================================================== // ALUR 1: SIMPAN KE attendance_logs (Status Pending) // ==================================================== $logUid = generate_uuid_v4(); $stmtInsertLog = $pdo->prepare(" INSERT INTO attendance_logs (uid, employee_number, timestamp, type, status, created_at, updated_at) VALUES (?, ?, ?, ?, 'pending', NOW(), NOW()) "); $stmtInsertLog->execute([$logUid, $empNumber, $timestamp, $logType]); $logId = $pdo->lastInsertId(); // ==================================================== // ALUR 2: PROSES KE attendances // ==================================================== try { $pdo->beginTransaction(); // Cari Employee ID $stmtEmp = $pdo->prepare("SELECT id FROM employees WHERE employee_number = ? AND status = 'active' LIMIT 1"); $stmtEmp->execute([$empNumber]); $employee = $stmtEmp->fetch(); if ($employee) { $employeeId = $employee['id']; // Cek apakah sudah ada record attendance hari ini $stmtCheck = $pdo->prepare("SELECT id, clock_in FROM attendances WHERE employee_id = ? AND attendance_date = ? LIMIT 1"); $stmtCheck->execute([$employeeId, $dateOnly]); $attendance = $stmtCheck->fetch(); if (!$attendance) { // KONDISI A: Absen Pertama Kali (Anggap Clock In) // Hitung Keterlambatan (Late Minutes) $lateMinutes = 0; if ($timeOnly > $settings['work_start']) { $inDateTime = new DateTime($timestamp); $startDateTime = new DateTime($dateOnly . ' ' . $settings['work_start']); $diff = $inDateTime->diff($startDateTime); $totalLate = ($diff->h * 60) + $diff->i; // Kurangi dengan toleransi keterlambatan $lateMinutes = max(0, $totalLate - $settings['late_tolerance_minutes']); } // Insert ke attendances $stmtInsertAtt = $pdo->prepare(" INSERT INTO attendances (uid, employee_id, attendance_date, clock_in, late_minutes, overtime_minutes, status, source, created_at, updated_at) VALUES (?, ?, ?, ?, ?, 0, 'present', 'api', NOW(), NOW()) "); $stmtInsertAtt->execute([ generate_uuid_v4(), $employeeId, $dateOnly, $timestamp, $lateMinutes ]); } else { // KONDISI B: Absen Selanjutnya (Anggap Clock Out) // Hitung Lembur (Overtime Minutes) $overtimeMinutes = 0; if ($timeOnly > $settings['work_end']) { $outDateTime = new DateTime($timestamp); $endDateTime = new DateTime($dateOnly . ' ' . $settings['work_end']); $diff = $outDateTime->diff($endDateTime); $overtimeMinutes = ($diff->h * 60) + $diff->i; // (Opsional) Batasi dengan overtime_max_hours jika diperlukan $maxOtMins = $settings['overtime_max_hours'] * 60; if ($overtimeMinutes > $maxOtMins) { $overtimeMinutes = $maxOtMins; } } // Update clock_out dan overtime_minutes pada record hari ini $stmtUpdateAtt = $pdo->prepare(" UPDATE attendances SET clock_out = ?, overtime_minutes = ?, updated_at = NOW() WHERE id = ? "); $stmtUpdateAtt->execute([$timestamp, $overtimeMinutes, $attendance['id']]); } // Sukses memproses log, ubah status log jadi processed $logStatus = 'processed'; } else { // Jika Karyawan tidak ditemukan/inactive, tandai failed $logStatus = 'failed'; } // Update status di table attendance_logs $stmtUpdateLog = $pdo->prepare("UPDATE attendance_logs SET status = ?, updated_at = NOW() WHERE id = ?"); $stmtUpdateLog->execute([$logStatus, $logId]); $pdo->commit(); } catch (Exception $e) { // Jika terjadi error saat memproses tabel relasi, batalkan semua perubahan di tabel relasi tersebut $pdo->rollBack(); // (Opsional) Anda bisa tambahkan log error ke file teks di sini } } } } // RESPONSE WAJIB KE MESIN header("Content-Type: text/plain"); echo "OK"; ?>