MIF_E31230356/app/Services/BadgeService.php

163 lines
5.3 KiB
PHP

<?php
namespace App\Services;
use App\Models\Badge;
use App\Models\SiswaBadge;
use App\Models\PesertaChallenge;
use App\Models\PengumpulanTugas;
use App\Models\Leaderboard;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
class BadgeService
{
/**
* Syarat (kolom `syarat` di tabel badges) yang dikenali sistem.
*
* CHALLENGE
* challenge_1 → selesaikan 1 challenge
* challenge_3 → selesaikan 3 challenge
*
* TUGAS
* tugas_1 → kumpulkan 1 tugas tepat waktu (tanggal_submit ≤ deadline)
* tugas_3 → kumpulkan 3 tugas tepat waktu (tanggal_submit ≤ deadline)
*
* LEADERBOARD (dicek & dicabut secara real-time)
* leaderboard_top5 → masuk top 5 leaderboard kelas aktif
* leaderboard_top1 → raih peringkat 1 leaderboard kelas aktif
*/
// -------------------------------------------------------------------------
// ENTRY POINTS
// -------------------------------------------------------------------------
/**
* Dipanggil setelah siswa submit challenge.
*/
public function checkChallengeBadges(int $idSiswa): void
{
$jumlahSelesai = PesertaChallenge::where('id_siswa', $idSiswa)
->where('status', 'selesai')
->count();
$this->grantIfEligible($idSiswa, 'challenge_1', $jumlahSelesai >= 1);
$this->grantIfEligible($idSiswa, 'challenge_3', $jumlahSelesai >= 3);
}
/**
* Dipanggil setelah siswa submit tugas.
* Badge hanya diberikan jika tugas dikumpulkan TEPAT WAKTU
* (tanggal_submit ≤ deadline tugas).
*/
public function checkTugasBadges(int $idSiswa): void
{
// Join pengumpulan_tugas dengan tugas untuk membandingkan
// tanggal_submit dengan deadline secara langsung di DB.
$jumlahTepatWaktu = DB::table('pengumpulan_tugas')
->join('tugas', 'pengumpulan_tugas.id_tugas', '=', 'tugas.id_tugas')
->where('pengumpulan_tugas.id_siswa', $idSiswa)
->whereNotNull('pengumpulan_tugas.lampiran_tugas')
->whereColumn('pengumpulan_tugas.tanggal_submit', '<=', 'tugas.deadline')
->count();
$this->grantIfEligible($idSiswa, 'tugas_1', $jumlahTepatWaktu >= 1);
$this->grantIfEligible($idSiswa, 'tugas_3', $jumlahTepatWaktu >= 3);
}
/**
* Dipanggil dari endpoint JSON leaderboard (polling real-time).
* Badge leaderboard DICABUT jika siswa tidak lagi memenuhi syarat,
* dan DIBERIKAN KEMBALI jika kembali memenuhi syarat.
*/
public function checkLeaderboardBadges(int $idSiswa, int $idKelas): void
{
[$semester, $tahunAjaran] = $this->semesterAktif();
$lb = Leaderboard::where('id_siswa', $idSiswa)
->where('id_kelas', $idKelas)
->where('semester', $semester)
->where('tahun_ajaran', $tahunAjaran)
->first();
$ranking = $lb?->ranking ?? 0;
$isTop5 = $ranking >= 1 && $ranking <= 5;
$isTop1 = $ranking === 1;
$this->grantOrRevoke($idSiswa, 'leaderboard_top5', $isTop5);
$this->grantOrRevoke($idSiswa, 'leaderboard_top1', $isTop1);
}
// -------------------------------------------------------------------------
// PRIVATE HELPERS
// -------------------------------------------------------------------------
/**
* Berikan badge jika eligible. Tidak melakukan apa-apa jika sudah punya.
* Digunakan untuk badge challenge & tugas (tidak pernah dicabut).
*/
private function grantIfEligible(int $idSiswa, string $syarat, bool $eligible): void
{
if (!$eligible) {
return;
}
$badge = Badge::where('syarat', $syarat)->first();
if (!$badge) {
return;
}
$sudahPunya = SiswaBadge::where('id_siswa', $idSiswa)
->where('id_badge', $badge->id_badge)
->exists();
if (!$sudahPunya) {
SiswaBadge::create([
'id_siswa' => $idSiswa,
'id_badge' => $badge->id_badge,
'tanggal_diberikan' => Carbon::now(),
]);
}
}
/**
* Berikan badge jika eligible, CABUT jika tidak.
* Digunakan khusus untuk badge leaderboard (real-time).
*/
private function grantOrRevoke(int $idSiswa, string $syarat, bool $eligible): void
{
$badge = Badge::where('syarat', $syarat)->first();
if (!$badge) {
return;
}
$record = SiswaBadge::where('id_siswa', $idSiswa)
->where('id_badge', $badge->id_badge)
->first();
if ($eligible && !$record) {
SiswaBadge::create([
'id_siswa' => $idSiswa,
'id_badge' => $badge->id_badge,
'tanggal_diberikan' => Carbon::now(),
]);
} elseif (!$eligible && $record) {
$record->delete();
}
}
/**
* Mengembalikan [semester, tahun_ajaran] berdasarkan tanggal sekarang.
*/
private function semesterAktif(): array
{
$now = Carbon::now();
$semester = $now->month >= 7 ? '1' : '2';
$tahunAjaran = $now->month >= 7
? $now->year . '/' . ($now->year + 1)
: ($now->year - 1) . '/' . $now->year;
return [$semester, $tahunAjaran];
}
}