MIF_E31230892/sim-pkpps/app/Models/Santri.php

532 lines
15 KiB
PHP

<?php
// app/Models/Santri.php - Model untuk Data Santri (DENGAN FOTO)
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Santri extends Model
{
use HasFactory;
/**
* Field yang boleh diisi massal (mass assignment)
*/
protected $fillable = [
'id_santri',
'nis',
'nama_lengkap',
'jenis_kelamin',
'status',
'alamat_santri',
'daerah_asal',
'nama_orang_tua',
'nomor_hp_ortu',
'rfid_uid',
'foto',
];
/**
* Cast attributes ke tipe data tertentu
*/
protected $casts = [
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
/**
* Generator ID Kustom (S001, S002, ...)
*/
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
if (empty($model->id_santri)) {
$last = Santri::orderBy('id', 'desc')->first();
$num = $last ? intval(substr($last->id_santri, 1)) + 1 : 1;
$model->id_santri = 'S' . str_pad($num, 3, '0', STR_PAD_LEFT);
}
});
}
/**
* Relasi: Santri memiliki banyak akun (santri_accounts)
*/
public function santriAccount()
{
return $this->hasMany(SantriAccount::class, 'id_santri', 'id_santri');
}
/**
* Relasi: Santri memiliki satu User Account (hasOne) - LEGACY
*/
public function user()
{
return $this->hasOne(SantriAccount::class, 'id_santri', 'id_santri')
->where('role', 'santri');
}
/**
* Relasi: Santri memiliki satu akun Wali (orang tua) - LEGACY
*/
public function waliUser()
{
return $this->hasOne(SantriAccount::class, 'id_santri', 'id_santri')
->where('role', 'wali');
}
/**
* Relasi: Santri memiliki banyak data kesehatan
*/
public function kesehatanSantri()
{
return $this->hasMany(KesehatanSantri::class, 'id_santri', 'id_santri');
}
/**
* Relasi: Kesehatan santri yang masih dirawat
*/
public function kesehatanAktif()
{
return $this->hasMany(KesehatanSantri::class, 'id_santri', 'id_santri')
->where('status', 'dirawat');
}
/**
* Relasi: Santri memiliki banyak data kepulangan
*/
public function kepulangan()
{
return $this->hasMany(Kepulangan::class, 'id_santri', 'id_santri');
}
/**
* Relasi: Kepulangan yang sedang aktif
*/
public function kepulanganAktif()
{
return $this->hasMany(Kepulangan::class, 'id_santri', 'id_santri')
->where('status', 'Disetujui')
->whereDate('tanggal_pulang', '<=', now())
->whereDate('tanggal_kembali', '>=', now());
}
/**
* Relasi: Santri memiliki banyak riwayat pelanggaran
*/
public function riwayatPelanggaran()
{
return $this->hasMany(RiwayatPelanggaran::class, 'id_santri', 'id_santri');
}
/**
* Relasi: Santri memiliki banyak pembayaran SPP
*/
public function pembayaranSpp()
{
return $this->hasMany(PembayaranSpp::class, 'id_santri', 'id_santri');
}
/**
* Relasi: SPP yang belum lunas
*/
public function sppBelumLunas()
{
return $this->hasMany(PembayaranSpp::class, 'id_santri', 'id_santri')
->where('status', 'Belum Lunas');
}
/**
* Relasi: SPP yang telat
*/
public function sppTelat()
{
return $this->hasMany(PembayaranSpp::class, 'id_santri', 'id_santri')
->where('status', 'Belum Lunas')
->where('batas_bayar', '<', now());
}
/**
* Relasi: Santri memiliki banyak transaksi uang saku
*/
public function uangSaku()
{
return $this->hasMany(UangSaku::class, 'id_santri', 'id_santri');
}
/**
* Relasi: Santri memiliki banyak absensi kegiatan (BARU)
*/
public function absensiKegiatans()
{
return $this->hasMany(AbsensiKegiatan::class, 'id_santri', 'id_santri');
}
/**
* Accessor: Nama kelompok kelas
*/
public function getKelompokNameAttribute()
{
return $this->kelasPrimary?->kelas?->kelompok?->nama_kelompok ?? '-';
}
/**
* Accessor untuk mendapatkan badge HTML status
*/
public function getStatusBadgeAttribute()
{
$badges = [
'Aktif' => '<span class="badge badge-success"><i class="fas fa-check-circle"></i> Aktif</span>',
'Lulus' => '<span class="badge badge-info"><i class="fas fa-graduation-cap"></i> Lulus</span>',
'Khatam' => '<span class="badge badge-primary"><i class="fas fa-award"></i> Khatam</span>',
'Tidak Aktif' => '<span class="badge badge-secondary"><i class="fas fa-times-circle"></i> Tidak Aktif</span>',
];
return $badges[$this->status] ?? $this->status;
}
/**
* Accessor: Total poin pelanggaran
*/
public function getTotalPoinPelanggaranAttribute()
{
return $this->riwayatPelanggaran()->sum('poin');
}
/**
* Accessor: Total tunggakan SPP
*/
public function getTotalTunggakanAttribute()
{
return $this->sppBelumLunas()->sum('nominal');
}
/**
* Accessor: Saldo uang saku terakhir
*/
public function getSaldoUangSakuAttribute()
{
$transaksiTerakhir = $this->uangSaku()
->orderBy('tanggal_transaksi', 'desc')
->orderBy('created_at', 'desc')
->first();
return $transaksiTerakhir ? $transaksiTerakhir->saldo_sesudah : 0;
}
/**
* Accessor: Total pemasukan uang saku
*/
public function getTotalPemasukanUangSakuAttribute()
{
return $this->uangSaku()->where('jenis_transaksi', 'pemasukan')->sum('nominal');
}
/**
* Accessor: Total pengeluaran uang saku
*/
public function getTotalPengeluaranUangSakuAttribute()
{
return $this->uangSaku()->where('jenis_transaksi', 'pengeluaran')->sum('nominal');
}
/**
* Accessor: Status RFID (BARU)
*/
public function getHasRfidAttribute()
{
return !empty($this->rfid_uid);
}
/**
* Accessor: Total kehadiran kegiatan (BARU)
*/
public function getTotalKehadiranAttribute()
{
return $this->absensiKegiatans()->where('status', 'Hadir')->count();
}
/**
* Accessor: URL Foto Santri (BARU)
*/
public function getFotoUrlAttribute()
{
if ($this->foto && file_exists(storage_path('app/public/' . $this->foto))) {
return asset('storage/' . $this->foto);
}
// Fallback ke gambar default berdasarkan jenis kelamin
if ($this->jenis_kelamin === 'Perempuan') {
return asset('images/default-female.png');
}
return asset('images/default-male.png');
}
/**
* Scope untuk filter santri aktif
*/
public function scopeAktif($query)
{
return $query->where('status', 'Aktif');
}
/**
* Scope untuk filter santri lulus
*/
public function scopeLulus($query)
{
return $query->where('status', 'Lulus');
}
/**
* Scope untuk filter santri tidak aktif
*/
public function scopeTidakAktif($query)
{
return $query->where('status', 'Tidak Aktif');
}
/**
* Scope untuk filter berdasarkan kelas (santri yang punya kelas ini)
*/
public function scopeKelas($query, $idKelas)
{
return $query->whereHas('kelasSantri', function($q) use ($idKelas) {
$q->where('id_kelas', $idKelas);
});
}
/**
* Scope untuk filter berdasarkan kelompok kelas
*/
public function scopeKelompok($query, $idKelompok)
{
return $query->whereHas('kelasSantri', function($q) use ($idKelompok) {
$q->whereHas('kelas', function($q2) use ($idKelompok) {
$q2->where('id_kelompok', $idKelompok);
});
});
}
/**
* Scope: Filter santri by kelas name (via relational system)
* Replaces old Santri::where('kelas', $name) queries
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $namaKelas - Nama kelas (e.g., 'PB', 'Lambatan', 'Cepatan')
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeKelasByName($query, $namaKelas)
{
return $query->whereHas('kelasSantri', function($q) use ($namaKelas) {
$q->whereHas('kelas', function($q2) use ($namaKelas) {
$q2->where('nama_kelas', $namaKelas);
});
});
}
/**
* Scope: Filter santri by PRIMARY kelas name only
* Used in dashboard/capaian where only primary class matters
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $namaKelas - Nama kelas (e.g., 'PB', 'Lambatan', 'SMA 12')
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePrimaryKelasByName($query, $namaKelas)
{
return $query->whereHas('kelasSantri', function($q) use ($namaKelas) {
$q->where('is_primary', true)
->whereHas('kelas', function($q2) use ($namaKelas) {
$q2->where('nama_kelas', $namaKelas);
});
});
}
/**
* Scope untuk search santri
*/
public function scopeSearch($query, $search)
{
return $query->where(function($q) use ($search) {
$q->where('nama_lengkap', 'like', "%{$search}%")
->orWhere('nis', 'like', "%{$search}%")
->orWhere('id_santri', 'like', "%{$search}%");
});
}
/**
* Relasi: Santri memiliki banyak capaian
*/
public function capaian()
{
return $this->hasMany(Capaian::class, 'id_santri', 'id_santri');
}
// ==========================================
// RELASI SISTEM KELAS BARU
// ==========================================
/**
* Relasi: Santri memiliki banyak record kelas (hasMany ke santri_kelas)
*/
public function kelasSantri()
{
return $this->hasMany(SantriKelas::class, 'id_santri', 'id_santri');
}
/**
* Relasi: Santri memiliki satu kelas primary (hasOne ke santri_kelas dengan is_primary = true)
*/
public function kelasPrimary()
{
return $this->hasOne(SantriKelas::class, 'id_santri', 'id_santri')
->where('is_primary', true)
->with('kelas');
}
/**
* Relasi: Santri belongs to many Kelas (many-to-many through santri_kelas)
*/
public function kelasMany()
{
return $this->belongsToMany(Kelas::class, 'santri_kelas', 'id_santri', 'id_kelas', 'id_santri', 'id')
->withPivot('tahun_ajaran', 'is_primary')
->withTimestamps();
}
/**
* Get rata-rata capaian per semester
*/
public function getRataRataCapaianAttribute()
{
return $this->capaian()->avg('persentase') ?? 0;
}
// ==========================================
// ACCESSOR SISTEM KELAS BARU
// ==========================================
/**
* Accessor: Get kelas name (primary atau pertama)
*
* @return string
*/
public function getKelasNameAttribute()
{
$primary = $this->kelasPrimary;
if ($primary && $primary->kelas) {
return $primary->kelas->nama_kelas;
}
// Fallback ke kelas pertama jika tidak ada primary
$first = $this->kelasSantri->first();
return $first && $first->kelas ? $first->kelas->nama_kelas : 'Belum Ada Kelas';
}
/**
* Accessor: Backward compatible kelas accessor (replaces dropped column)
* Returns primary kelas name for seamless migration from old system
*
* @return string
*/
public function getKelasAttribute()
{
return $this->kelas_name;
}
/**
* Accessor: Get semua kelas sebagai string (untuk display ringkas)
*
* @return string
*/
public function getKelasListStringAttribute()
{
$items = $this->kelasSantri
->filter(fn($sk) => $sk->kelas && $sk->kelas->kelompok)
->map(fn($sk) => $sk->kelas->kelompok->nama_kelompok . ': ' . $sk->kelas->nama_kelas);
return $items->isNotEmpty() ? $items->implode(', ') : 'Belum Ada Kelas';
}
/**
* Accessor: Get kelas ID dari sistem baru (primary class ID)
*
* @return int|null
*/
public function getPrimaryKelasIdAttribute()
{
$kelasPrimary = $this->kelasPrimary;
return $kelasPrimary ? $kelasPrimary->id_kelas : null;
}
// ==========================================
// HELPER METHODS SISTEM KELAS BARU
// ==========================================
/**
* Check apakah santri ada di kelas tertentu
*
* @param int $id_kelas
* @return bool
*/
public function hasKelas($id_kelas)
{
return $this->kelasMany()->where('kelas.id', $id_kelas)->exists();
}
/**
* Get all kelas santri untuk tahun ajaran tertentu
*
* @param string|null $tahun_ajaran - Format: 2024/2025, null untuk tahun ajaran saat ini
* @return \Illuminate\Database\Eloquent\Collection
*/
public function getKelasByTahun($tahun_ajaran = null)
{
if ($tahun_ajaran === null) {
$tahun_ajaran = SantriKelas::getCurrentAcademicYear();
}
return $this->kelasSantri()
->with('kelas.kelompok')
->where('tahun_ajaran', $tahun_ajaran)
->get();
}
/**
* Assign santri ke kelas baru
*
* @param int $id_kelas
* @param string|null $tahun_ajaran - Format: 2024/2025, null untuk tahun ajaran saat ini
* @param bool $is_primary - Set sebagai kelas utama
* @return \App\Models\SantriKelas
*/
public function assignKelas($id_kelas, $tahun_ajaran = null, $is_primary = false)
{
if ($tahun_ajaran === null) {
$tahun_ajaran = SantriKelas::getCurrentAcademicYear();
}
// Jika set as primary, unset kelas primary lainnya di tahun ajaran yang sama
if ($is_primary) {
$this->kelasSantri()
->where('tahun_ajaran', $tahun_ajaran)
->update(['is_primary' => false]);
}
// Create or update santri_kelas
return SantriKelas::updateOrCreate(
[
'id_santri' => $this->id_santri,
'id_kelas' => $id_kelas,
'tahun_ajaran' => $tahun_ajaran,
],
[
'is_primary' => $is_primary,
]
);
}
}