'date', 'jam_masuk' => 'datetime', 'jam_keluar' => 'datetime', 'created_at' => 'datetime', 'updated_at' => 'datetime' ]; /** * Status absensi yang valid */ const STATUS_HADIR = 'hadir'; const STATUS_SAKIT = 'sakit'; const STATUS_IZIN = 'izin'; /** * Array status yang tersedia */ public static function getStatusOptions() { return [ self::STATUS_HADIR => 'Hadir', self::STATUS_SAKIT => 'Sakit', self::STATUS_IZIN => 'Izin' ]; } /** * Relasi ke model Teknisi * Satu absensi dimiliki oleh satu teknisi */ public function teknisi() { return $this->belongsTo(Teknisi::class, 'id_teknisi', 'id_teknisi'); } /** * Scope untuk filter berdasarkan tanggal */ public function scopeFilterByDate($query, $tanggal) { if ($tanggal) { return $query->whereDate('tanggal', $tanggal); } return $query; } /** * Scope untuk filter berdasarkan status */ public function scopeFilterByStatus($query, $status) { if ($status) { return $query->where('status', $status); } return $query; } /** * Scope untuk filter berdasarkan teknisi */ public function scopeFilterByTeknisi($query, $teknisiId) { if ($teknisiId) { return $query->where('id_teknisi', $teknisiId); } return $query; } /** * Scope untuk filter berdasarkan bulan dan tahun */ public function scopeFilterByMonth($query, $bulan, $tahun = null) { if (!$tahun) { $tahun = date('Y'); } return $query->whereMonth('tanggal', $bulan) ->whereYear('tanggal', $tahun); } /** * Scope untuk data absensi hari ini */ public function scopeToday($query) { return $query->whereDate('tanggal', Carbon::today()); } /** * Scope untuk data absensi minggu ini */ public function scopeThisWeek($query) { return $query->whereBetween('tanggal', [ Carbon::now()->startOfWeek(), Carbon::now()->endOfWeek() ]); } /** * Scope untuk data absensi bulan ini */ public function scopeThisMonth($query) { return $query->whereMonth('tanggal', Carbon::now()->month) ->whereYear('tanggal', Carbon::now()->year); } /** * Accessor untuk format tanggal Indonesia */ public function getTanggalFormattedAttribute() { return Carbon::parse($this->tanggal)->format('d/m/Y'); } /** * Accessor untuk format jam masuk */ public function getJamMasukFormattedAttribute() { return $this->jam_masuk ? Carbon::parse($this->jam_masuk)->format('H:i') : '-'; } /** * Accessor untuk format jam keluar */ public function getJamKeluarFormattedAttribute() { return $this->jam_keluar ? Carbon::parse($this->jam_keluar)->format('H:i') : '-'; } /** * Accessor untuk nama status dengan format title case */ public function getStatusFormattedAttribute() { return ucfirst($this->status); } /** * Accessor untuk URL foto absen masuk */ public function getFotoAbsenMasukUrlAttribute() { return $this->foto_absen_masuk ? asset('storage/' . $this->foto_absen_masuk) : null; } /** * Accessor untuk URL foto absen keluar */ public function getFotoAbsenKeluarUrlAttribute() { return $this->foto_absen_keluar ? asset('storage/' . $this->foto_absen_keluar) : null; } /** * Accessor untuk menghitung durasi kerja (dalam menit) */ public function getDurasiKerjaAttribute() { if ($this->jam_masuk && $this->jam_keluar) { $masuk = Carbon::parse($this->jam_masuk); $keluar = Carbon::parse($this->jam_keluar); return $keluar->diffInMinutes($masuk); } return 0; } /** * Accessor untuk durasi kerja dalam format jam:menit */ public function getDurasiKerjaFormattedAttribute() { $durasi = $this->durasi_kerja; if ($durasi > 0) { $jam = floor($durasi / 60); $menit = $durasi % 60; return sprintf('%02d:%02d', $jam, $menit); } return '00:00'; } /** * Accessor untuk menentukan apakah terlambat (asumsi jam masuk normal 08:00) */ /** * Accessor untuk kategori kerja (Kerja Biasa vs Lembur) */ public function getKategoriKerjaAttribute() { if ($this->jam_masuk) { $jamMasuk = Carbon::parse($this->jam_masuk); $start = Carbon::parse('07:00'); $end = Carbon::parse('18:00'); if ($jamMasuk->between($start, $end)) { return 'Kerja Biasa'; } return 'Kerja Urgent'; } return '-'; } /** * Label warna untuk kategori kerja */ public function getKategoriBadgeClassAttribute() { return $this->kategori_kerja === 'Kerja Biasa' ? 'badge-success' : 'badge-warning'; } /** * Logika terlambat ditiadakan sesuai permintaan user */ public function getIsTerlambatAttribute() { return false; } /** * Accessor untuk CSS class badge berdasarkan status */ public function getStatusBadgeClassAttribute() { $classes = [ self::STATUS_HADIR => 'badge-success', self::STATUS_SAKIT => 'badge-warning', self::STATUS_IZIN => 'badge-info' ]; return $classes[$this->status] ?? 'badge-secondary'; } /** * Method untuk mengecek apakah absensi sudah lengkap (ada jam masuk dan keluar) */ public function isComplete() { return !empty($this->jam_masuk) && !empty($this->jam_keluar); } /** * Method untuk mengecek apakah sudah absen masuk */ public function hasAbsenMasuk() { return !empty($this->jam_masuk); } /** * Method untuk mengecek apakah sudah absen keluar */ public function hasAbsenKeluar() { return !empty($this->jam_keluar); } /** * Static method untuk mendapatkan statistik absensi berdasarkan periode */ public static function getStatistik($startDate = null, $endDate = null) { $query = self::query(); if ($startDate && $endDate) { $query->whereBetween('tanggal', [$startDate, $endDate]); } return [ 'total' => $query->count(), 'hadir' => $query->where('status', self::STATUS_HADIR)->count(), 'sakit' => $query->where('status', self::STATUS_SAKIT)->count(), 'izin' => $query->where('status', self::STATUS_IZIN)->count(), ]; } /** * Boot method untuk event model */ protected static function boot() { parent::boot(); // Event ketika model akan dihapus static::deleting(function ($absensi) { // Hapus file foto jika ada if ($absensi->foto_absen_masuk && Storage::disk('public')->exists($absensi->foto_absen_masuk)) { Storage::disk('public')->delete($absensi->foto_absen_masuk); } if ($absensi->foto_absen_keluar && Storage::disk('public')->exists($absensi->foto_absen_keluar)) { Storage::disk('public')->delete($absensi->foto_absen_keluar); } }); } }