feat: API wali murid (Sanctum) + verifikasi admin

- Pasang laravel/sanctum, routes/api.php (prefix api/v1, 10 endpoint)
- Migrasi: parent_name di students, parent_students (pivot wali↔anak),
  parent_devices (token FCM), permission parents-verify
- Auth wali: registrasi NISN+HP (auto-verify jika HP cocok data siswa,
  pending jika tidak) + login token Sanctum
- Endpoint: me, logout, students, link anak, violations, sp-letters,
  notifications, register device
- Halaman admin Verifikasi Wali (approve/reject) + menu sidebar
- Sync kesiswaan: tarik data orang tua (student_parents) ke students
- 8 feature test baru (ParentApiTest) — total suite 39 test hijau
This commit is contained in:
Novy San87
2026-08-05 18:58:29 +07:00
parent b06419624b
commit 3efce0f919
24 changed files with 1437 additions and 4 deletions
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ParentDevice extends Model
{
protected $fillable = [
'user_id', 'platform', 'fcm_token', 'device_name', 'last_active_at',
];
protected function casts(): array
{
return [
'last_active_at' => 'datetime',
];
}
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ParentStudent extends Model
{
protected $table = 'parent_students';
protected $fillable = [
'user_id', 'student_id', 'relation', 'status', 'verified_at', 'verified_by', 'rejection_reason',
];
protected function casts(): array
{
return [
'verified_at' => 'datetime',
];
}
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
public function student()
{
return $this->belongsTo(Student::class, 'student_id');
}
}
+6 -1
View File
@@ -10,7 +10,7 @@ class Student extends Model
protected $fillable = [
'source_id', 'nisn', 'student_number', 'full_name',
'gender', 'place_of_birth', 'date_of_birth', 'address',
'phone_number', 'parent_phone', 'email', 'class_name', 'class_level',
'phone_number', 'parent_phone', 'parent_name', 'email', 'class_name', 'class_level',
'department_code', 'department_name', 'academic_year_name',
'status', 'photo_path', 'class_id', 'is_active',
'metadata', 'synced_at',
@@ -41,6 +41,11 @@ class Student extends Model
return $this->belongsTo(Classes::class, 'class_id');
}
public function parentLinks()
{
return $this->hasMany(\App\Models\ParentStudent::class, 'student_id');
}
public function getTotalPointsAttribute(): int
{
return $this->violations()
+23 -2
View File
@@ -5,12 +5,13 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasFactory, Notifiable;
use HasApiTokens, HasFactory, Notifiable;
public const ROLES = ['admin', 'bk', 'wali_kelas', 'staff', 'other', 'kepala_sekolah', 'waka_kesiswaan', 'ketua_tim'];
public const ROLES = ['admin', 'bk', 'wali_kelas', 'staff', 'other', 'kepala_sekolah', 'waka_kesiswaan', 'ketua_tim', 'parent'];
protected $fillable = [
'name', 'username', 'email', 'phone', 'password', 'role', 'roles', 'is_active', 'active_session_token',
@@ -66,6 +67,7 @@ class User extends Authenticatable
public function isKepalaSekolah(): bool { return $this->hasRole('kepala_sekolah'); }
public function isWakaKesiswaan(): bool { return $this->hasRole('waka_kesiswaan'); }
public function isKetuaTim(): bool { return $this->hasRole('ketua_tim'); }
public function isParent(): bool { return $this->hasRole('parent'); }
/**
* Role dengan visibilitas data GLOBAL (semua kelas/siswa).
@@ -136,6 +138,25 @@ class User extends Authenticatable
return $this->hasMany(Violation::class, 'recorded_by');
}
/**
* Relasi akun wali murid → anak (pivot parent_students).
*/
public function parentStudents()
{
return $this->hasMany(\App\Models\ParentStudent::class, 'user_id');
}
/**
* Anak-anak (siswa) milik akun wali, hanya yang statusnya aktif.
*/
public function children()
{
return $this->belongsToMany(\App\Models\Student::class, 'parent_students', 'user_id', 'student_id')
->withPivot(['relation', 'status', 'verified_at'])
->withTimestamps()
->wherePivot('status', 'active');
}
public function appNotifications()
{
return $this->hasMany(AppNotification::class, 'user_id');