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
@@ -0,0 +1,77 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\ParentStudent;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class ParentVerificationController extends Controller
{
public function index(): View
{
$pending = ParentStudent::query()
->where('status', 'pending')
->with(['user:id,name,username,phone,created_at', 'student:id,nisn,full_name,class_name,department_name'])
->orderByDesc('created_at')
->get();
$recent = ParentStudent::query()
->where('status', '!=', 'pending')
->with(['user:id,name,username', 'student:id,nisn,full_name,class_name'])
->orderByDesc('updated_at')
->limit(20)
->get();
return view('parents.verification', compact('pending', 'recent'));
}
public function approve(Request $request, int $linkId): RedirectResponse
{
$link = ParentStudent::findOrFail($linkId);
if ($link->status !== 'pending') {
return back()->with('error', 'Tautan sudah diproses sebelumnya.');
}
$link->update([
'status' => 'active',
'verified_at' => now(),
'verified_by' => $request->user()->id,
]);
// Isi data kontak wali ke siswa bila masih kosong
if ($link->student && empty($link->student->parent_phone)) {
$link->student->update([
'parent_name' => $link->user->name,
'parent_phone' => $link->user->phone ?: ($link->student->parent_phone),
]);
}
return back()->with('success', 'Akun wali "' . $link->user->name . '" untuk ' . $link->student->full_name . ' disetujui.');
}
public function reject(Request $request, int $linkId): RedirectResponse
{
$validated = $request->validate([
'reason' => ['nullable', 'string', 'max:255'],
]);
$link = ParentStudent::findOrFail($linkId);
if ($link->status !== 'pending') {
return back()->with('error', 'Tautan sudah diproses sebelumnya.');
}
$link->update([
'status' => 'rejected',
'verified_at' => now(),
'verified_by' => $request->user()->id,
'rejection_reason' => $validated['reason'] ?? null,
]);
return back()->with('success', 'Tautan ditolak.');
}
}