Initial commit: Struktur aplikasi ilycats

This commit is contained in:
Dwi F Sukmana
2026-02-08 15:16:35 +07:00
commit 0921901aed
235 changed files with 29016 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ActivityLog extends Model
{
protected $guarded = ['id'];
protected $casts = ['properties' => 'array'];
public function user()
{
return $this->belongsTo(User::class);
}
}
+182
View File
@@ -0,0 +1,182 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Storage;
use Carbon\Carbon;
class Cat extends Model
{
use SoftDeletes;
/**
* SINKRONISASI TOTAL: 22 Kolom Database
* Memasukkan berat_terakhir, lokasi, dan kolom identitas lainnya.
*/
protected $fillable = [
'nama_kucing', 'ras', 'registry', 'jenis_kelamin', 'tanggal_lahir',
'foto', 'berat_terakhir', 'lokasi', 'riwayat_sakit_terakhir',
'jadwal_vaksin', 'jadwal_obat_cacing', 'jadwal_obat_kutu',
'jadwal_grooming', 'induk_jantan_id', 'induk_betina_id',
'lokasi_kandang', 'pic_id', 'berat_badan',
'pedigree', 'microchip', 'status', 'kode_ems' // Kolom tambahan dari UI Anda
];
/**
* Casting Laravel 11/12 Style
*/
protected function casts(): array
{
return [
'tanggal_lahir' => 'date',
'jadwal_vaksin' => 'date',
'jadwal_obat_cacing' => 'date',
'jadwal_obat_kutu' => 'date',
'jadwal_grooming' => 'date',
'registry' => 'array',
'berat_badan' => 'decimal:2',
'berat_terakhir' => 'decimal:2',
];
}
/* -------------------------------------------------------------------------- */
/* RELATIONS */
/* -------------------------------------------------------------------------- */
public function treatments(): HasMany
{
return $this->hasMany(Treatment::class);
}
public function weightHistories(): HasMany
{
return $this->hasMany(WeightHistory::class)->latest();
}
public function sire(): BelongsTo
{
return $this->belongsTo(Cat::class, 'induk_jantan_id')->withDefault([
'nama_kucing' => 'Unknown Sire'
]);
}
public function dam(): BelongsTo
{
return $this->belongsTo(Cat::class, 'induk_betina_id')->withDefault([
'nama_kucing' => 'Unknown Dam'
]);
}
public function mattingsAsSire(): HasMany
{
return $this->hasMany(Matting::class, 'sire_id');
}
public function mattingsAsDam(): HasMany
{
return $this->hasMany(Matting::class, 'dam_id');
}
public function pic(): BelongsTo
{
return $this->belongsTo(User::class, 'pic_id')->withDefault([
'name' => 'System'
]);
}
/* -------------------------------------------------------------------------- */
/* ACCESSORS */
/* -------------------------------------------------------------------------- */
protected function genderIcon(): Attribute
{
return Attribute::make(
// Database menggunakan enum 'Jantan'/'Betina' (Case Sensitive)
get: fn () => strtolower($this->jenis_kelamin) === 'jantan'
? '<i class="fas fa-mars text-indigo-500"></i>'
: '<i class="fas fa-venus text-rose-500"></i>'
);
}
protected function fotoUrl(): Attribute
{
return Attribute::make(
get: fn () => $this->foto
? Storage::url($this->foto)
: asset('images/default-cat.png')
);
}
protected function age(): Attribute
{
return Attribute::make(
get: function () {
if (!$this->tanggal_lahir) return 'N/A';
$diff = $this->tanggal_lahir->diff(now());
if ($diff->y > 0) return "{$diff->y} Thn, {$diff->m} Bln";
if ($diff->m > 0) return "{$diff->m} Bln, {$diff->d} Hari";
return "{$diff->d} Hari";
}
);
}
// Usia presisi untuk laporan medis (Tahun, Bulan, Hari)
protected function usiaDetail(): Attribute
{
return Attribute::make(
get: function () {
if (!$this->tanggal_lahir) return 'Data N/A';
$diff = $this->tanggal_lahir->diff(now());
return "{$diff->y} Thn, {$diff->m} Bln, {$diff->d} Hari";
}
);
}
protected function registryLabel(): Attribute
{
return Attribute::make(
get: fn () => $this->registry ?? 'NON-PED'
);
}
// Badges untuk registry
protected function registryBadges(): Attribute
{
return Attribute::make(
get: function () {
// Jika tidak ada registry
if (empty($this->registry)) {
return '<span class="text-[9px] text-slate-400 font-bold italic tracking-widest">NON-PED</span>';
}
// Mapping warna untuk setiap federasi
$config = [
'ICA' => ['bg' => 'bg-blue-50', 'text' => 'text-blue-700', 'border' => 'border-blue-100'],
'CFA' => ['bg' => 'bg-rose-50', 'text' => 'text-rose-700', 'border' => 'border-rose-100'],
'WCF' => ['bg' => 'bg-emerald-50', 'text' => 'text-emerald-700', 'border' => 'border-emerald-100'],
'FFF' => ['bg' => 'bg-amber-50', 'text' => 'text-amber-700', 'border' => 'border-amber-100'],
];
$html = '<div class="flex flex-wrap gap-1">';
$registries = is_array($this->registry) ? $this->registry : [$this->registry];
foreach ($registries as $reg) {
$style = $config[$reg] ?? ['bg' => 'bg-slate-50', 'text' => 'text-slate-600', 'border' => 'border-slate-200'];
$html .= "<span class='px-2 py-0.5 {$style['bg']} {$style['text']} {$style['border']} border rounded-lg text-[8px] font-black uppercase tracking-tighter shadow-sm'>";
$html .= $reg;
$html .= "</span>";
}
$html .= '</div>';
return $html;
}
);
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
class Category extends Model
{
protected $fillable = ['name', 'keywords'];
protected static function booted()
{
static::creating(function ($category) {
// Otomatis membuat slug dari name sebelum data disimpan
if (empty($category->slug)) {
$category->slug = Str::slug($category->name);
}
});
}
public function medicines(): HasMany
{
return $this->hasMany(Medicine::class);
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class Food extends Model
{
protected $table = 'foods';
// Gunakan guarded atau fillable, pastikan 'slug' dan 'category_id' diizinkan
protected $guarded = ['id'];
/**
* Relasi ke model Category
*/
public function category()
{
return $this->belongsTo(Category::class, 'category_id');
}
/**
* Booting model events
*/
protected static function booted()
{
static::saving(function ($food) {
// 1. OTOMATIS GENERATE SLUG
// Hanya generate jika nama berubah atau slug masih kosong
if ($food->isDirty('nama_makanan') || empty($food->slug)) {
$food->slug = Str::slug($food->nama_makanan) . '-' . Str::random(5);
}
// 2. AUTO-CATEGORIZATION (SMART LOGIC)
// Hanya jalankan jika category_id belum diisi manual
if (empty($food->category_id)) {
$productName = strtolower($food->nama_makanan);
$categories = Category::whereNotNull('keywords')->get();
$found = false;
foreach ($categories as $cat) {
$keywordsArray = explode(',', strtolower($cat->keywords));
foreach ($keywordsArray as $keyword) {
$cleanKeyword = trim($keyword);
if (!empty($cleanKeyword) && str_contains($productName, $cleanKeyword)) {
$food->category_id = $cat->id;
$food->kategori = $cat->name;
$found = true;
break; // Keluar dari loop keyword
}
}
if ($found) break; // Keluar dari loop kategori
}
// 3. FALLBACK LOGIC
// Jika tidak ada keyword yang cocok, cari kategori default
if (!$found) {
$default = Category::where('name', 'Dry Food')
->orWhere('name', 'Umum')
->first();
if ($default) {
$food->category_id = $default->id;
$food->kategori = $default->name;
} else {
$food->kategori = 'Umum';
}
}
}
});
}
}
+112
View File
@@ -0,0 +1,112 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Facades\Storage;
class Kitten extends Model
{
// Mengizinkan semua field kecuali ID untuk diisi secara massal
protected $guarded = ['id'];
/**
* Casting Data: Memastikan tipe data sinkron antara DB dan PHP.
*/
protected function casts(): array
{
return [
'tgl_lahir' => 'date',
'is_pedigree' => 'boolean',
'registry' => 'array',
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
}
/**
* MODEL EVENTS
* Membersihkan file storage secara otomatis saat record dihapus.
*/
protected static function booted()
{
static::deleting(function ($kitten) {
// Hapus semua foto pedigree (slot 1-4) jika ada
for ($i = 1; $i <= 4; $i++) {
$field = "foto_ped_$i";
if ($kitten->$field) {
Storage::disk('public')->delete($kitten->$field);
}
}
});
}
/* -------------------------------------------------------------------------- */
/* RELATIONS */
/* -------------------------------------------------------------------------- */
/**
* Menghubungkan ke Master Profil Kucing (Patient Table)
*/
public function cat(): BelongsTo
{
return $this->belongsTo(Cat::class, 'cat_id');
}
/**
* Menghubungkan ke Event Perkawinan (Breeding Event)
*/
public function matting(): BelongsTo
{
return $this->belongsTo(Matting::class, 'matting_id');
}
/* -------------------------------------------------------------------------- */
/* ACCESSORS (UI Helper) */
/* -------------------------------------------------------------------------- */
/**
* Menghitung berapa banyak dokumen pedigree yang sudah diupload (0-4)
*/
protected function pedigreeDocsCount(): Attribute
{
return Attribute::make(
get: function () {
$count = 0;
for ($i = 1; $i <= 4; $i++) {
if ($this->{"foto_ped_$i"}) $count++;
}
return $count;
}
);
}
/**
* Badge Status Pedigree untuk Tampilan Tabel
*/
protected function pedigreeStatusBadge(): Attribute
{
return Attribute::make(
get: function () {
if ($this->is_pedigree) {
return '<span class="px-2 py-0.5 bg-indigo-50 text-indigo-600 rounded text-[9px] font-black uppercase border border-indigo-100 italic">Certificate OK</span>';
}
return '<span class="px-2 py-0.5 bg-slate-50 text-slate-400 rounded text-[9px] font-black uppercase border border-slate-100">Non-Ped</span>';
}
);
}
/**
* Mengambil URL Foto Pedigree Slot 1 (Thumbnail)
*/
protected function firstPedigreeUrl(): Attribute
{
return Attribute::make(
get: fn () => $this->foto_ped_1
? asset('storage/' . $this->foto_ped_1)
: asset('images/default-doc.png')
);
}
}
+137
View File
@@ -0,0 +1,137 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Facades\Log;
class Matting extends Model
{
protected $guarded = ['id'];
protected function casts(): array
{
return [
'tanggal_kawin' => 'date',
'tgl_kawin_awal' => 'date',
'tgl_kawin_akhir' => 'date',
'tgl_lahir_sire' => 'date',
'tgl_lahir_dam' => 'date',
'jumlah_anak_hidup' => 'integer',
];
}
/**
* SINKRONISASI OTOMATIS (The Engine)
* Saat data Matting disimpan, otomatis update data di tabel Cats.
*/
protected static function booted()
{
static::saved(function ($matting) {
// 1. Update data Sire (Bapak)
if ($matting->sire_id && $matting->sire) {
$matting->sire->update([
'pedigree' => $matting->no_pedigree_bapak,
'microchip' => $now_microchip_bapak ?? $matting->no_microchip_bapak,
'tanggal_lahir' => $matting->tgl_lahir_sire,
// Pastikan kolom di tabel 'mattings' adalah ems_bapak
'kode_ems' => $matting->ems_bapak,
]);
}
// 2. Update data Dam (Ibu)
if ($matting->dam_id && $matting->dam) {
$matting->dam->update([
'pedigree' => $matting->no_pedigree_ibu,
'microchip' => $matting->no_microchip_ibu,
'tanggal_lahir' => $matting->tgl_lahir_dam,
// Pastikan kolom di tabel 'mattings' adalah ems_ibu
'kode_ems' => $matting->ems_ibu,
]);
}
});
}
/* -------------------------------------------------------------------------- */
/* RELATIONS */
/* -------------------------------------------------------------------------- */
public function sire(): BelongsTo
{
return $this->belongsTo(Cat::class, 'sire_id');
}
public function dam(): BelongsTo
{
return $this->belongsTo(Cat::class, 'dam_id');
}
public function kittens(): HasMany
{
return $this->hasMany(Kitten::class, 'matting_id');
}
/* -------------------------------------------------------------------------- */
/* ACCESSORS (UI & Breeding Intelligence) */
/* -------------------------------------------------------------------------- */
/**
* EDD (Expected Delivery Date) - H+63
*/
protected function edd(): Attribute
{
return Attribute::make(
get: fn () => $this->tgl_kawin_awal ? $this->tgl_kawin_awal->addDays(63) : null
);
}
/**
* Deskripsi Sisa Hari (Countdown Text)
*/
protected function countdownText(): Attribute
{
return Attribute::make(
get: function () {
if (!$this->edd || $this->status !== 'proses') return null;
$days = (int) now()->startOfDay()->diffInDays($this->edd->startOfDay(), false);
if ($days > 0) return "$days Hari Lagi";
if ($days === 0) return "Prediksi Lahir Hari Ini";
return "Overdue " . abs($days) . " Hari";
}
);
}
/**
* Badge Status Modern
*/
protected function statusBadge(): Attribute
{
return Attribute::make(
get: function () {
$status = strtolower($this->status);
return match($status) {
'proses' => '<span class="px-3 py-1 bg-blue-50 text-blue-600 rounded-full text-[10px] font-black uppercase border border-blue-100">Monitoring</span>',
'berhasil' => '<span class="px-3 py-1 bg-emerald-50 text-emerald-600 rounded-full text-[10px] font-black uppercase border border-emerald-100">Lahir</span>',
'gagal' => '<span class="px-3 py-1 bg-rose-50 text-rose-600 rounded-full text-[10px] font-black uppercase border border-rose-100">Gagal</span>',
default => '<span class="px-3 py-1 bg-slate-50 text-slate-600 rounded-full text-[10px] font-black uppercase">'.$status.'</span>',
};
}
);
}
/**
* PIC (Point of Contact)
*/
public function pic(): BelongsTo
{
// Pastikan kolom di tabel 'mattings' adalah 'user_id'
// Jika kolomnya bernama 'pic_id', ganti 'user_id' menjadi 'pic_id'
return $this->belongsTo(User::class, 'user_id')->withDefault([
'name' => 'System / Admin'
]);
}
}
+80
View File
@@ -0,0 +1,80 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Storage;
class Medicine extends Model
{
/**
* Mass assignment untuk kolom-kolom inventori.
* Kolom 'kategori' tetap ada di fillable agar bisa diisi otomatis oleh sistem.
*/
protected $fillable = [
'nama_obat',
'foto_produk',
'kategori',
'qty',
'unit',
'expired_date'
];
/**
* Logic Otomatisasi & Integritas Data
*/
protected static function booted()
{
static::saving(function ($medicine) {
$name = strtolower($medicine->nama_obat);
// Ambil semua kategori dari database
$categories = \App\Models\Category::all();
foreach ($categories as $cat) {
// Pecah kata kunci berdasarkan koma
$keywords = explode(',', strtolower($cat->keywords));
foreach ($keywords as $keyword) {
if (str_contains($name, trim($keyword))) {
$medicine->category_id = $cat->id;
$medicine->kategori = $cat->name; // Sinkronisasi teks kategori
return;
}
}
}
$medicine->kategori = 'Umum';
});
}
/**
* Casting tipe data untuk presisi medis.
*/
protected $casts = [
'expired_date' => 'date', // Memperbaiki error isPast() di Blade
'qty' => 'float', // Mendukung dosis desimal seperti 2.5 ml
];
/**
* Accessor: Mempermudah pemanggilan foto produk dengan fallback image.
*/
public function getFotoUrlAttribute(): string
{
if ($this->foto_produk && Storage::disk('public')->exists($this->foto_produk)) {
return asset('storage/' . $this->foto_produk);
}
// Menggunakan UI Avatars sebagai placeholder jika foto tidak ada
return 'https://ui-avatars.com/api/?name=' . urlencode($this->nama_obat) . '&background=f1f5f9&color=64748b';
}
/**
* Relasi ke riwayat pengobatan kucing.
*/
public function treatments(): HasMany
{
return $this->hasMany(Treatment::class, 'medicine_id');
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
protected $fillable = ['name', 'slug'];
public function permissions()
{
return $this->hasMany(RolePermission::class);
}
public function users()
{
return $this->hasMany(User::class);
}
/**
* Tambahkan metode ini agar View dapat mengecek izin langsung dari objek Role
*/
public function hasPermission($moduleSlug, $action)
{
return $this->permissions()
->where('module_slug', $moduleSlug)
->where($action, 1) // Cek kolom can_read, can_create, dll
->exists();
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class RolePermission extends Model
{
protected $fillable = [
'role_id',
'module_slug',
'can_read',
'can_create',
'can_update',
'can_delete'
];
public function role()
{
return $this->belongsTo(Role::class);
}
}
+10
View File
@@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Setting extends Model
{
protected $fillable = ['key', 'value', 'group'];
}
+67
View File
@@ -0,0 +1,67 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
class Supply extends Model
{
protected $table = 'supplies';
protected $guarded = ['id'];
public function category(): BelongsTo
{
return $this->belongsTo(Category::class, 'category_id');
}
protected static function booted()
{
parent::booted();
static::saving(function ($supply) {
// 1. Sinkronisasi Slug (Mencegah error SQL sebelumnya)
if ($supply->isDirty('nama_perlengkapan') || empty($supply->slug)) {
$supply->slug = Str::slug($supply->nama_perlengkapan) . '-' . Str::random(5);
}
// 2. Auto-Categorization
if (empty($supply->category_id)) {
$name = strtolower($supply->nama_perlengkapan);
// Ambil kategori yang punya keywords
$categories = Category::whereNotNull('keywords')->get();
foreach ($categories as $cat) {
$keywords = explode(',', strtolower($cat->keywords));
foreach ($keywords as $keyword) {
$trimmed = trim($keyword);
// Cek apakah "pasir" atau keyword lain ada di nama produk
if (!empty($trimmed) && str_contains($name, $trimmed)) {
$supply->category_id = $cat->id;
// PENTING: Update kolom teks 'kategori' agar tampilan tidak 'Umum'
$supply->kategori = $cat->name;
return;
}
}
}
// 3. Fallback jika tidak ada yang cocok
// Cari kategori yang mengandung kata 'Pasir' atau 'Umum'
$default = Category::where('name', 'LIKE', '%Pasir%')
->orWhere('name', 'Umum')
->first();
if ($default) {
$supply->category_id = $default->id;
$supply->kategori = $default->name;
} else {
$supply->kategori = 'Umum';
}
}
});
}
}
+109
View File
@@ -0,0 +1,109 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Storage;
class Treatment extends Model
{
/**
* Kolom yang boleh diisi secara mass-assignment.
*/
protected $fillable = [
'cat_id',
'user_id',
'tanggal',
'keluhan',
'diagnosa',
'nama_obat',
'dosis',
'keterangan',
'foto_bukti',
'medicine_id',
'qty_used',
];
/**
* Casting data agar formatnya sesuai (Tanggal jadi object Carbon).
*/
protected $casts = [
'tanggal' => 'datetime',
'qty_used' => 'decimal:2',
];
/* -------------------------------------------------------------------------- */
/* RELASI (MENGATASI ERROR RELATION NOT FOUND) */
/* -------------------------------------------------------------------------- */
/**
* Relasi ke Kucing.
*/
public function cat(): BelongsTo
{
return $this->belongsTo(Cat::class, 'cat_id')->withDefault([
'nama_kucing' => 'Kucing Terhapus'
]);
}
/**
* Relasi utama ke User (PIC).
* Nama fungsi 'user' wajib ada karena dipanggil di TreatmentController baris 31.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id')->withDefault([
'name' => 'System / Admin'
]);
}
/**
* Alias untuk relasi user.
* Nama fungsi 'pic' wajib ada karena dipanggil di DashboardController.
*/
public function pic(): BelongsTo
{
return $this->user();
}
/**
* Relasi ke Inventori Obat.
*/
public function medicine(): BelongsTo
{
return $this->belongsTo(Medicine::class, 'medicine_id')->withDefault([
'nama_obat' => $this->nama_obat ?? 'Obat Manual'
]);
}
/* -------------------------------------------------------------------------- */
/* ACCESSORS */
/* -------------------------------------------------------------------------- */
/**
* Mengambil URL foto bukti.
* Cara akses di Blade: {{ $treatment->foto_url }}
*/
public function getFotoUrlAttribute(): string
{
if (!$this->foto_bukti) {
return 'https://ui-avatars.com/api/?name=No+Image&background=f1f5f9&color=cbd5e1';
}
return Storage::url($this->foto_bukti);
}
/* -------------------------------------------------------------------------- */
/* SCOPES */
/* -------------------------------------------------------------------------- */
/**
* Scope untuk mengurutkan data terbaru.
*/
public function scopeLatestFirst(Builder $query): void
{
$query->orderBy('tanggal', 'desc');
}
}
+77
View File
@@ -0,0 +1,77 @@
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use App\Traits\HasPermissions; // Tambahan: Import Trait Permission
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\Relations\BelongsTo; // Tambahan: Import untuk relasi
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable, HasPermissions; // Tambahan: Gunakan HasPermissions
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'email',
'password',
'role', // Kode existing Anda
'role_id', // Tambahan: ID untuk relasi tabel Role
'phone',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
/**
* Tambahan: Relasi ke tabel Roles
* Setiap user memiliki satu Role.
*/
public function role(): BelongsTo
{
return $this->belongsTo(Role::class);
}
public function hasAccess($module, $permission)
{
// Jika admin, berikan akses penuh secara otomatis
if ($this->role && $this->role->slug === 'admin') {
return true;
}
// Cek di tabel role_permissions melalui relasi role
return $this->role->permissions()
->where('module_slug', $module)
->where($permission, 1) // $permission berisi 'can_read', 'can_update', dll
->exists();
}
}
+10
View File
@@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class WeightHistory extends Model
{
//
}