341 lines
8.4 KiB
PHP
341 lines
8.4 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
class Absensi extends Model
|
|
{
|
|
use HasFactory;
|
|
|
|
/**
|
|
* Nama tabel di database
|
|
*/
|
|
protected $table = 'absensis'; // PERBAIKAN: Ganti 'absensi' menjadi 'absensis'
|
|
|
|
/**
|
|
* Primary key tabel
|
|
*/
|
|
protected $primaryKey = 'id_absensi';
|
|
|
|
/**
|
|
* Field yang dapat diisi secara mass assignment
|
|
*/
|
|
protected $fillable = [
|
|
'id_teknisi',
|
|
'tanggal',
|
|
'jam_masuk',
|
|
'jam_keluar',
|
|
'foto_absen_masuk',
|
|
'foto_absen_keluar',
|
|
'latitude',
|
|
'longitude',
|
|
'status',
|
|
'keterangan'
|
|
];
|
|
|
|
/**
|
|
* Field yang akan di-cast ke tipe data tertentu
|
|
*/
|
|
protected $casts = [
|
|
'tanggal' => '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);
|
|
}
|
|
});
|
|
}
|
|
} |