174 lines
5.7 KiB
PHP
174 lines
5.7 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\AttendanceRecord;
|
|
use App\Models\DrawResult;
|
|
use App\Models\DrawSession;
|
|
use App\Models\Member;
|
|
use App\Models\Prize;
|
|
use App\Services\DrawService;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\View\View;use RuntimeException;
|
|
|
|
class DrawController extends Controller
|
|
{
|
|
public function __construct(private readonly DrawService $service)
|
|
{
|
|
}
|
|
|
|
public function index(): View
|
|
{
|
|
$session = DrawSession::active();
|
|
|
|
// Setiap item hadiah diberi Nombor Cabutan 1..N ikut susunan (ascending draw_order).
|
|
// Cabutan berjalan MENURUN: mula dari nombor terakhir, hadiah utama (#1) dicabut terakhir.
|
|
$prizes = Prize::query()->ordered()->get()->values()->map(function (Prize $p, int $i) {
|
|
return [
|
|
'id' => $p->id,
|
|
'seq' => $i + 1,
|
|
'nama_hadiah' => $p->nama_hadiah,
|
|
'draw_order' => $p->draw_order,
|
|
'kategori' => $p->kategori,
|
|
'status' => $p->status,
|
|
];
|
|
});
|
|
|
|
$totalPrizes = $prizes->count();
|
|
$attendance = AttendanceRecord::count();
|
|
|
|
// Nombor terakhir (nombor mula cabutan) = jumlah hadiah,
|
|
// tetapi jika kehadiran < jumlah hadiah, hadkan kepada jumlah kehadiran.
|
|
$startNumber = min($totalPrizes, $attendance);
|
|
|
|
$eligibleCount = $this->service->eligibleMembers($session)->count();
|
|
|
|
// Cabutan pending semasa (jika ada) untuk sambung semula
|
|
$pending = DrawResult::with(['member', 'prize'])
|
|
->where('status', DrawResult::STATUS_PENDING)
|
|
->latest('spun_at')
|
|
->first();
|
|
|
|
return view('draw.index', compact(
|
|
'session', 'prizes', 'totalPrizes', 'attendance', 'startNumber', 'eligibleCount', 'pending'
|
|
));
|
|
}
|
|
|
|
/** Senarai pool semasa untuk render roda */
|
|
public function pool(): JsonResponse
|
|
{
|
|
$session = DrawSession::active();
|
|
|
|
return response()->json([
|
|
'pool' => $this->poolPayload($session),
|
|
]);
|
|
}
|
|
|
|
public function spin(Request $request, DrawService $service): JsonResponse
|
|
{
|
|
$data = $request->validate([
|
|
'prize_id' => ['required', 'integer', 'exists:prizes,id'],
|
|
]);
|
|
|
|
$session = DrawSession::active();
|
|
$prize = Prize::findOrFail($data['prize_id']);
|
|
|
|
try {
|
|
$result = $service->spin($prize, $session, $request->user());
|
|
} catch (RuntimeException $e) {
|
|
return response()->json(['message' => $e->getMessage()], 422);
|
|
}
|
|
|
|
// Pool selepas spin (winner mesti ada dalamnya)
|
|
$pool = $this->poolPayload($session);
|
|
$winnerIndex = collect($pool)->search(fn ($p) => $p['id'] === $result->member_id);
|
|
|
|
return response()->json([
|
|
'result_id' => $result->id,
|
|
'winner_index' => $winnerIndex === false ? 0 : $winnerIndex,
|
|
'pool' => $pool,
|
|
'winner' => $this->memberPayload($result->member),
|
|
'prize' => $this->prizePayload($result->prize),
|
|
'attempt' => $result->attempt_number,
|
|
]);
|
|
}
|
|
|
|
public function confirm(Request $request, DrawService $service): JsonResponse
|
|
{
|
|
$data = $request->validate(['result_id' => ['required', 'integer', 'exists:draw_results,id']]);
|
|
$result = DrawResult::findOrFail($data['result_id']);
|
|
|
|
try {
|
|
$result = $service->confirm($result, $request->user());
|
|
} catch (RuntimeException $e) {
|
|
return response()->json(['message' => $e->getMessage()], 422);
|
|
}
|
|
|
|
return response()->json([
|
|
'status' => 'confirmed',
|
|
'message' => 'TAHNIAH! Pemenang telah disahkan.',
|
|
'winner' => $this->memberPayload($result->member),
|
|
'prize' => $this->prizePayload($result->prize),
|
|
]);
|
|
}
|
|
|
|
public function cancel(Request $request, DrawService $service): JsonResponse
|
|
{
|
|
$data = $request->validate([
|
|
'result_id' => ['required', 'integer', 'exists:draw_results,id'],
|
|
'sebab' => ['nullable', 'string', 'max:255'],
|
|
]);
|
|
$result = DrawResult::findOrFail($data['result_id']);
|
|
|
|
try {
|
|
$result = $service->cancel($result, $request->user(), $data['sebab'] ?? null);
|
|
} catch (RuntimeException $e) {
|
|
return response()->json(['message' => $e->getMessage()], 422);
|
|
}
|
|
|
|
return response()->json([
|
|
'status' => 'cancelled',
|
|
'message' => 'Cabutan dibatalkan. Hadiah boleh dicabut semula.',
|
|
'prize' => $this->prizePayload($result->prize),
|
|
]);
|
|
}
|
|
|
|
private function poolPayload(DrawSession $session): array
|
|
{
|
|
return $this->service->eligibleMembers($session)
|
|
->map(fn (Member $m) => [
|
|
'id' => $m->id,
|
|
'label' => $m->no_pekerja ?: $m->no_anggota ?: $m->nama,
|
|
'nama' => $m->nama,
|
|
])
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
private function memberPayload(Member $m): array
|
|
{
|
|
return [
|
|
'id' => $m->id,
|
|
'nama' => $m->nama,
|
|
'no_pekerja' => $m->no_pekerja,
|
|
'no_anggota' => $m->no_anggota,
|
|
'jabatan' => $m->jabatan,
|
|
'bahagian' => $m->bahagian,
|
|
'masked_kp' => $m->maskedKp(),
|
|
];
|
|
}
|
|
|
|
private function prizePayload(Prize $p): array
|
|
{
|
|
return [
|
|
'id' => $p->id,
|
|
'nama_hadiah' => $p->nama_hadiah,
|
|
'draw_order' => $p->draw_order,
|
|
'kategori' => $p->kategori,
|
|
'status' => $p->status,
|
|
'status_label' => $p->statusLabel(),
|
|
];
|
|
}
|
|
}
|