fix: auto-violasi dari E-Jurnal via ViolationService + notifikasi realtime

Sync controller sekarang pake ViolationService::recordViolation()
biar notifikasi realtime (AppNotification + broadcast event)
dan pengecekan SP threshold tetap jalan untuk data dari E-Jurnal.

ViolationService: recordViolation() & generateSpLetter()
diubah jadi nullable userId supaya bisa dipanggil tanpa user
(0 = system-generated dari sync)
This commit is contained in:
Bro
2026-07-24 13:20:40 +07:00
parent 1ac4a0ef74
commit a5db795eda
2 changed files with 9 additions and 15 deletions
@@ -14,11 +14,10 @@ class AttendanceSyncController extends Controller
{ {
/** /**
* Receive attendance push from E-Jurnal. * Receive attendance push from E-Jurnal.
* Auto-generate violations for alpha students. * Auto-generate violations for alpha students (with notifications).
*/ */
public function sync(Request $request) public function sync(Request $request)
{ {
// Validate token
$expectedToken = Setting::getValue('ejurnal_sync_token', ''); $expectedToken = Setting::getValue('ejurnal_sync_token', '');
$providedToken = $request->input('token'); $providedToken = $request->input('token');
@@ -33,7 +32,6 @@ class AttendanceSyncController extends Controller
], 401); ], 401);
} }
// Validate payload structure
$request->validate([ $request->validate([
'attendances' => 'required|array|min:1|max:500', 'attendances' => 'required|array|min:1|max:500',
'attendances.*.source_student_id' => 'required|integer', 'attendances.*.source_student_id' => 'required|integer',
@@ -52,13 +50,11 @@ class AttendanceSyncController extends Controller
'violations' => 0, 'violations' => 0,
]; ];
// Lookup map: source_student_id -> local student id
$sourceIds = array_unique(array_column($attendances, 'source_student_id')); $sourceIds = array_unique(array_column($attendances, 'source_student_id'));
$studentMap = Student::whereIn('source_id', $sourceIds) $studentMap = Student::whereIn('source_id', $sourceIds)
->where('is_active', true) ->where('is_active', true)
->pluck('id', 'source_id'); ->pluck('id', 'source_id');
// Track alpha counts per student for auto-violation
$alphaCounts = []; $alphaCounts = [];
$alphaDate = null; $alphaDate = null;
@@ -73,7 +69,6 @@ class AttendanceSyncController extends Controller
$localStudentId = $studentMap[$sourceId]; $localStudentId = $studentMap[$sourceId];
// Map status from e-jurnal to aplikasi-ketertiban format
$statusMap = [ $statusMap = [
'present' => 'hadir', 'present' => 'hadir',
'sick' => 'sakit', 'sick' => 'sakit',
@@ -83,7 +78,6 @@ class AttendanceSyncController extends Controller
$localStatus = $statusMap[$item['status']] ?? $item['status']; $localStatus = $statusMap[$item['status']] ?? $item['status'];
try { try {
// Upsert: unique constraint on (student_id, date, lesson_hour)
$attendance = Attendance::updateOrCreate( $attendance = Attendance::updateOrCreate(
[ [
'student_id' => $localStudentId, 'student_id' => $localStudentId,
@@ -93,7 +87,7 @@ class AttendanceSyncController extends Controller
[ [
'status' => $localStatus, 'status' => $localStatus,
'notes' => $item['notes'] ?? null, 'notes' => $item['notes'] ?? null,
'recorded_by' => null, // system-generated 'recorded_by' => null,
] ]
); );
@@ -103,7 +97,6 @@ class AttendanceSyncController extends Controller
$results['updated']++; $results['updated']++;
} }
// Track alpha for auto-violation
if ($localStatus === 'alpha') { if ($localStatus === 'alpha') {
$alphaCounts[$localStudentId] = ($alphaCounts[$localStudentId] ?? 0) + 1; $alphaCounts[$localStudentId] = ($alphaCounts[$localStudentId] ?? 0) + 1;
$alphaDate = $item['date']; $alphaDate = $item['date'];
@@ -114,22 +107,23 @@ class AttendanceSyncController extends Controller
} }
} }
// Auto-generate violation for alpha students // Auto-generate violations via service (includes realtime notifications)
if (!empty($alphaCounts)) { if (!empty($alphaCounts)) {
$alphaType = ViolationType::where('slug', 'alpha')->first(); $alphaType = ViolationType::where('slug', 'alpha')->first();
if ($alphaType) { if ($alphaType) {
$violationService = app(\App\Services\ViolationService::class);
foreach ($alphaCounts as $studentId => $count) { foreach ($alphaCounts as $studentId => $count) {
try { try {
\App\Models\Violation::create([ $violationService->recordViolation([
'student_id' => $studentId, 'student_id' => $studentId,
'violation_type_id' => $alphaType->id, 'violation_type_id' => $alphaType->id,
'violation_date' => $alphaDate, 'violation_date' => $alphaDate,
'points' => max(1, (int) round(($alphaType->points / 10) * $count)), 'points' => max(1, (int) round(($alphaType->points / 10) * $count)),
'description' => "Alpha - Tidak hadir tanpa keterangan ({$count} jam pelajaran)", 'description' => "Alpha - Tidak hadir tanpa keterangan ({$count} jam pelajaran)",
'notes' => 'Dibuat otomatis dari sinkron E-Jurnal.', 'notes' => 'Dibuat otomatis dari sinkron E-Jurnal.',
'recorded_by' => null, 'evidences' => [],
]); ], null);
$results['violations']++; $results['violations']++;
} catch (\Exception $e) { } catch (\Exception $e) {
$results['errors'][] = "Violation creation failed for student={$studentId}: " . $e->getMessage(); $results['errors'][] = "Violation creation failed for student={$studentId}: " . $e->getMessage();
+2 -2
View File
@@ -14,7 +14,7 @@ use Illuminate\Support\Facades\DB;
class ViolationService class ViolationService
{ {
public function recordViolation(array $data, int $userId): Violation public function recordViolation(array $data, ?int $userId = null): Violation
{ {
$violation = DB::transaction(function () use ($data, $userId) { $violation = DB::transaction(function () use ($data, $userId) {
$violation = Violation::create([ $violation = Violation::create([
@@ -72,7 +72,7 @@ class ViolationService
return $violation; return $violation;
} }
protected function generateSpLetter(Student $student, SpThreshold $threshold, int $userId): SpLetter protected function generateSpLetter(Student $student, SpThreshold $threshold, ?int $userId = null): SpLetter
{ {
$schoolName = Setting::getValue('school_name', 'SMK'); $schoolName = Setting::getValue('school_name', 'SMK');
$letterCount = SpLetter::where('sp_threshold_id', $threshold->id)->count() + 1; $letterCount = SpLetter::where('sp_threshold_id', $threshold->id)->count() + 1;