163 lines
5.3 KiB
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];
|
|
}
|
|
} |