Absensi Foto GPS Berdasarkan Titik Poin Absen Menggunakan CodeIgniter 4, Leaflet, JS (Multi Company + Validasi Lengkap)
Di era kerja hybrid dan remote, sistem absensi manual sudah tidak lagi relevan. Perusahaan membutuhkan sistem absensi online berbasis GPS dan foto real-time untuk memastikan kehadiran benar-benar dilakukan di lokasi kerja yang ditentukan. Salah satu solusi profesional adalah absensi foto GPS berdasarkan titik poin radius menggunakan CodeIgniter 4, Leaflet.js, dan JavaScript.
Sistem ini tidak hanya mencatat kehadiran, tetapi juga melakukan validasi radius lokasi, pembatasan jam kerja, anti double absen, watermark foto otomatis, validasi EXIF, device binding, log IP dan user agent, hingga monitoring live map oleh admin.
Jika kamu ingin melihat implementasi dasar absensi online menggunakan GPS dan upload foto, silakan pelajari panduan lengkapnya di tutorial membuat absensi online pakai GPS dan foto di CodeIgniter 4 sebelum mengembangkan versi SaaS seperti yang dibahas di artikel ini.
Arsitektur Sistem Absensi GPS
Backend Menggunakan CodeIgniter 4
CodeIgniter 4 sangat cocok untuk membangun sistem absensi berbasis SaaS karena ringan, modular, dan mudah di-deploy ke shared hosting maupun VPS. Dengan struktur MVC yang rapi, validasi dapat dilakukan secara terpusat di controller dan filter middleware.
Untuk kebutuhan multi company, setiap tabel wajib memiliki kolom company_id agar data masing-masing perusahaan terisolasi dengan aman.
Frontend Map Menggunakan Leaflet.js
Leaflet digunakan untuk menampilkan peta lokasi kantor, titik koordinat karyawan, serta radius absensi dalam bentuk lingkaran. Saat karyawan membuka halaman absen, sistem akan mendeteksi posisi GPS menggunakan JavaScript Geolocation API, lalu menghitung jarak terhadap titik kantor.
Jika jarak berada di luar radius yang ditentukan, tombol absen otomatis dinonaktifkan. Dengan pendekatan ini, sistem lebih sulit dimanipulasi.
Cara Membuat Absensi Online Dengan Lokasi: Fitur Wajib Sistem Absensi Profesional
1. Multi Company (SaaS)
Sistem mendukung banyak perusahaan dalam satu aplikasi. Setiap data user, absensi, dan pengaturan disimpan berdasarkan company_id sehingga aman dan terpisah.
2. Role Admin dan Employee
Role admin memiliki akses ke dashboard monitoring, pengaturan radius, validasi jam kerja, export laporan, serta live map. Employee hanya dapat melakukan absensi dan melihat riwayat pribadi.
3. Validasi Jam Kerja
Jam masuk, jam pulang, dan toleransi keterlambatan disimpan di database. Sistem akan menolak absensi di luar jam yang diizinkan serta memberi status terlambat jika melewati batas toleransi.
4. Anti Double Absen
Sebelum menyimpan data, sistem melakukan pengecekan apakah user sudah melakukan absensi pada tanggal yang sama. Jika sudah, maka permintaan akan ditolak.
5. Validasi Radius GPS
Perhitungan jarak menggunakan rumus Haversine antara titik kantor dan koordinat user. Jika jarak melebihi radius yang ditentukan perusahaan, absensi gagal.
6. Watermark Foto Otomatis
Foto hasil capture kamera akan otomatis ditambahkan watermark berisi nama user, tanggal, jam, koordinat, dan nama perusahaan. Hal ini mencegah manipulasi atau penggunaan ulang foto lama.
7. Validasi EXIF
Sistem membaca metadata EXIF pada foto untuk memastikan waktu pengambilan sesuai dengan waktu server. Jika metadata tidak sesuai atau tidak ditemukan, sistem dapat menolak absensi.
Pembahasan teknis dasar upload foto dan pengambilan koordinat GPS juga sudah dijelaskan secara detail pada panduan lengkap sistem absensi foto GPS CodeIgniter 4 yang bisa kamu jadikan fondasi sebelum menambahkan fitur lanjutan seperti EXIF dan device binding.
8. Device Binding
Setiap login pertama akan menyimpan fingerprint perangkat seperti device ID, IP address, dan user agent. Jika login dari perangkat berbeda, sistem dapat meminta verifikasi tambahan.
9. Log IP dan User Agent
Setiap aktivitas absensi dicatat lengkap dengan IP address dan browser yang digunakan. Fitur ini sangat penting untuk audit dan keamanan.
10. Live Map Admin
Admin dapat melihat titik lokasi semua karyawan yang sudah melakukan absensi dalam satu dashboard peta. Marker berbeda warna dapat menunjukkan status hadir atau terlambat.
11. Export Excel
Laporan absensi dapat diekspor ke format Excel berdasarkan filter tanggal, karyawan, maupun perusahaan. Format ini memudahkan bagian HR melakukan rekap dan perhitungan.
Struktur Folder
Pada absensi foto gps menggunakan CI4 ini memiliki struktur folder sebagai berikut untuk memudahkan pengguna dalam memahami.app/ ├── Controllers/ │ ├── Auth.php │ ├── Attendance.php │ └── Admin/Dashboard.php │ ├── Models/ │ ├── UserModel.php │ ├── CompanyModel.php │ ├── AttendanceModel.php │ └── AttendancePointModel.php │ ├── Helpers/ │ ├── gps_helper.php │ └── image_helper.php │ └── Views/ ├── login.php ├── attendance_form.php └── admin/ ├── dashboard.php └── live_map.php {codeBox}
Related Tool Recommendations:
Struktur Database Lengkap
Tabel attendance minimal berisi kolom berikut:
CREATE TABLE companies ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(150) NOT NULL ); CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, company_id INT NOT NULL, name VARCHAR(150), email VARCHAR(150), password VARCHAR(255), role ENUM('admin','employee') DEFAULT 'employee', device_id VARCHAR(255) ); CREATE TABLE attendance_points ( id INT AUTO_INCREMENT PRIMARY KEY, company_id INT NOT NULL, name VARCHAR(150), latitude DECIMAL(10,8), longitude DECIMAL(11,8), radius INT DEFAULT 100 ); CREATE TABLE attendances ( id INT AUTO_INCREMENT PRIMARY KEY, company_id INT NOT NULL, user_id INT NOT NULL, attendance_point_id INT NOT NULL, type ENUM('masuk','pulang'), check_date DATE, latitude DECIMAL(10,8), longitude DECIMAL(11,8), distance DOUBLE, photo VARCHAR(255), exif_hash VARCHAR(255), ip_address VARCHAR(50), user_agent TEXT, device_id VARCHAR(255), is_fake_gps TINYINT(1) DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY unique_attendance (user_id,type,check_date) ); {codeBox}$ads={1}
Code Controllers CI4
Auth.php
namespace App\Controllers; use App\Models\UserModel; class Auth extends BaseController { public function login() { return view('login'); } public function doLogin() { $model = new UserModel(); $user = $model->where('email',$this->request->getPost('email'))->first(); if($user && password_verify($this->request->getPost('password'),$user['password'])) { session()->set($user); return redirect()->to('/'); } return redirect()->back()->with('error','Login gagal'); } public function logout() { session()->destroy(); return redirect()->to('/login'); } }
Attendance.php(user)
namespace App\Controllers; use App\Models\AttendanceModel; use App\Models\AttendancePointModel; class Attendance extends BaseController { public function index() { return view('attendance_form'); } private function validateTime($type) { $now = date('H:i:s'); if($type=='masuk' && ($now<'07:00:00'||$now>'09:00:00')) return false; if($type=='pulang' && ($now<'16:00:00'||$now>'18:00:00')) return false; return true; } public function save() { helper(['gps','image']); $model = new AttendanceModel(); $pointModel = new AttendancePointModel(); $type = $this->request->getPost('type'); if(!$this->validateTime($type)) return back()->with('error','Diluar jam absensi'); $today = date('Y-m-d'); if($model->where([ 'user_id'=>session('id'), 'type'=>$type, 'check_date'=>$today ])->first()) return back()->with('error','Sudah absen hari ini'); $point = $pointModel->find($this->request->getPost('point_id')); $lat = $this->request->getPost('latitude'); $lng = $this->request->getPost('longitude'); $distance = haversineDistance( $point['latitude'],$point['longitude'],$lat,$lng ); if($distance>$point['radius']) return back()->with('error','Diluar radius'); $file = $this->request->getFile('photo'); $newName = $file->getRandomName(); $file->move('uploads/',$newName); $path = 'uploads/'.$newName; $watermark = session('name')." | ".date('Y-m-d H:i:s'); addWatermark($path,$watermark); $exif = @exif_read_data($path); $exifHash = $exif?hash('sha256',json_encode($exif)):null; $deviceId = md5($this->request->getUserAgent()->getAgentString()); $model->save([ 'company_id'=>session('company_id'), 'user_id'=>session('id'), 'attendance_point_id'=>$point['id'], 'type'=>$type, 'check_date'=>$today, 'latitude'=>$lat, 'longitude'=>$lng, 'distance'=>$distance, 'photo'=>$newName, 'exif_hash'=>$exifHash, 'ip_address'=>$this->request->getIPAddress(), 'user_agent'=>$this->request->getUserAgent()->getAgentString(), 'device_id'=>$deviceId ]); return back()->with('success','Absen berhasil'); } }
Admin.php
namespace App\Controllers\Admin; use App\Controllers\BaseController; use App\Models\AttendanceModel; class Dashboard extends BaseController { public function index() { return view('admin/dashboard'); } public function live() { $model = new AttendanceModel(); $data = $model->select('attendances.*, users.name') ->join('users','users.id=attendances.user_id') ->where('attendances.company_id',session('company_id')) ->where('check_date',date('Y-m-d')) ->findAll(); return $this->response->setJSON($data); } public function export() { $model = new AttendanceModel(); $data = $model->where('company_id',session('company_id'))->findAll(); header("Content-Type: application/vnd.ms-excel"); header("Content-Disposition: attachment; filename=attendance.xls"); echo "Nama\tTanggal\tTipe\n"; foreach($data as $d) { echo $d['user_id']."\t".$d['check_date']."\t".$d['type']."\n"; } } } {codeBox}
Code Models CI4
AttendanceModel.php$ads={2}
namespace App\Models; use CodeIgniter\Model; class AttendanceModel extends Model { protected $table = 'attendances'; protected $allowedFields = [ 'company_id','user_id','attendance_point_id', 'type','check_date','latitude','longitude', 'distance','photo','exif_hash','ip_address', 'user_agent','device_id','is_fake_gps' ]; } {codeBox}
Code Helpers CI4
file gps_helper.php
function haversineDistance($lat1, $lon1, $lat2, $lon2) { $earthRadius = 6371000; $dLat = deg2rad($lat2 - $lat1); $dLon = deg2rad($lon2 - $lon1); $a = sin($dLat/2)*sin($dLat/2) + cos(deg2rad($lat1))*cos(deg2rad($lat2))* sin($dLon/2)*sin($dLon/2); $c = 2 * atan2(sqrt($a), sqrt(1-$a)); return $earthRadius * $c; }
file image_helper.php
function addWatermark($path, $text) { $img = imagecreatefromjpeg($path); $color = imagecolorallocate($img,255,0,0); imagestring($img,5,20,20,$text,$color); imagejpeg($img,$path,90); imagedestroy($img); } {codeBox}
Kesimpulan
Absensi foto GPS berdasarkan titik poin absen menggunakan CodeIgniter 4, Leaflet, dan JavaScript adalah solusi modern untuk perusahaan yang membutuhkan sistem kehadiran yang transparan, aman, dan sulit dimanipulasi. Dengan fitur multi company, validasi radius, watermark foto, EXIF validation, device binding, hingga live monitoring, sistem ini sudah siap dikembangkan menjadi produk SaaS komersial.
Dengan struktur dan validasi berlapis seperti ini, kamu tidak hanya membuat sistem absensi biasa, tetapi membangun platform attendance tracking profesional berbasis lokasi yang siap dipasarkan.
