Allowance check
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API\v1\Admin\Allowance;
|
||||
|
||||
use App\AllowanceClaimCode;
|
||||
use App\AllowancePayout;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Util;
|
||||
use App\Result;
|
||||
use App\Voter;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class PayoutController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'voter_id' => 'required|integer',
|
||||
'method' => 'nullable|string|in:cash,bank',
|
||||
'amount_cents' => 'nullable|integer|min:0',
|
||||
'reference' => 'nullable|string|max:80',
|
||||
'note' => 'nullable|string|max:2000',
|
||||
'claim_code' => 'nullable|string|min:4|max:20',
|
||||
]);
|
||||
|
||||
$electionId = Util::getCurrentElection();
|
||||
$method = $request->input('method', 'cash');
|
||||
$voterId = (int) $request->input('voter_id');
|
||||
|
||||
$voter = Voter::where('election_id', $electionId)->where('id', $voterId)->first();
|
||||
if (!$voter) {
|
||||
return response()->json([
|
||||
'status' => 'failed',
|
||||
'message' => 'Voter not found for current election.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
// Enforce method by attendance type (paperless replacement for coupon rules)
|
||||
if ($method === 'cash' && (int) $voter->kehadiran !== 1) {
|
||||
return response()->json([
|
||||
'status' => 'failed',
|
||||
'message' => 'Cash payout only allowed for Fizikal attendance.',
|
||||
], 422);
|
||||
}
|
||||
if ($method === 'bank' && (int) $voter->kehadiran !== 2) {
|
||||
return response()->json([
|
||||
'status' => 'failed',
|
||||
'message' => 'Bank payout only allowed for Maya attendance.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
// Enforce "must have voted" (officer check)
|
||||
$hasVoted = Result::where('election_id', $electionId)->where('voter_id', $voterId)->exists();
|
||||
if (!$hasVoted) {
|
||||
return response()->json([
|
||||
'status' => 'failed',
|
||||
'message' => 'Voter has not voted yet.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
// Default amounts (can be overridden by passing amount_cents)
|
||||
$defaultAmount = $method === 'cash' ? 30000 : 15000;
|
||||
$amountCents = $request->filled('amount_cents') ? (int) $request->input('amount_cents') : $defaultAmount;
|
||||
|
||||
$now = Carbon::now();
|
||||
$adminId = $request->user() ? (int) $request->user()->id : null;
|
||||
|
||||
$claimCode = $request->input('claim_code');
|
||||
|
||||
// For fizikal cash payout, require claim code (paperless coupon).
|
||||
if ($method === 'cash' && (!$claimCode || trim($claimCode) === '')) {
|
||||
return response()->json([
|
||||
'status' => 'failed',
|
||||
'message' => 'Sila masukkan kod tuntutan (claim code) sebelum bayaran tunai dibuat.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$payout = DB::transaction(function () use ($electionId, $voterId, $method, $claimCode, $amountCents, $adminId, $now, $request) {
|
||||
// Consume claim code if provided
|
||||
if ($claimCode && trim($claimCode) !== '') {
|
||||
$row = AllowanceClaimCode::where('election_id', $electionId)
|
||||
->where('voter_id', $voterId)
|
||||
->where('method', $method)
|
||||
->lockForUpdate()
|
||||
->first();
|
||||
|
||||
if (!$row) {
|
||||
return response()->json([
|
||||
'status' => 'failed',
|
||||
'message' => 'Kod tuntutan tidak dijumpai.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
if ($row->used_at) {
|
||||
return response()->json([
|
||||
'status' => 'failed',
|
||||
'message' => 'Kod tuntutan telah digunakan.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
if ($row->expires_at && Carbon::now()->greaterThan($row->expires_at)) {
|
||||
return response()->json([
|
||||
'status' => 'failed',
|
||||
'message' => 'Kod tuntutan telah tamat tempoh.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
if (!Hash::check(trim((string) $claimCode), $row->code_hash)) {
|
||||
return response()->json([
|
||||
'status' => 'failed',
|
||||
'message' => 'Kod tuntutan tidak sah.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$row->used_at = Carbon::now();
|
||||
$row->used_by_admin_id = $adminId;
|
||||
$row->save();
|
||||
}
|
||||
|
||||
// Idempotent: if already exists (unique election_id+voter_id+method) we keep it as paid.
|
||||
return AllowancePayout::updateOrCreate(
|
||||
[
|
||||
'election_id' => $electionId,
|
||||
'voter_id' => $voterId,
|
||||
'method' => $method,
|
||||
],
|
||||
[
|
||||
'status' => 'paid',
|
||||
'amount_cents' => $amountCents,
|
||||
'currency' => 'MYR',
|
||||
'paid_by_admin_id' => $adminId,
|
||||
'paid_at' => $now,
|
||||
'reference' => $request->input('reference'),
|
||||
'note' => $request->input('note'),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
// In case the transaction returned a JSON response (validation failure), return it directly.
|
||||
if ($payout instanceof \Illuminate\Http\JsonResponse) {
|
||||
return $payout;
|
||||
}
|
||||
|
||||
activity()
|
||||
->performedOn($payout)
|
||||
->withProperties([
|
||||
'election_id' => $electionId,
|
||||
'voter_id' => $voterId,
|
||||
'voter_name' => $voter->name,
|
||||
'attendance' => (int) $voter->kehadiran,
|
||||
'method' => $method,
|
||||
'amount_cents' => (int) $payout->amount_cents,
|
||||
'currency' => $payout->currency,
|
||||
'paid_by_admin_id' => $adminId,
|
||||
'reference' => $payout->reference,
|
||||
'ip' => $request->ip(),
|
||||
'user_agent' => $request->userAgent(),
|
||||
])
|
||||
->log("allowance payout ({$method}): {$voter->name}");
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Allowance payout recorded.',
|
||||
'payout' => $payout,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API\v1\Admin\Allowance;
|
||||
|
||||
use App\AllowanceClaimCode;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Util;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class VerifyCodeController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'voter_id' => 'required|integer',
|
||||
'method' => 'nullable|string|in:cash,bank',
|
||||
'code' => 'required|string|min:4|max:20',
|
||||
]);
|
||||
|
||||
$electionId = Util::getCurrentElection();
|
||||
$method = $request->input('method', 'cash');
|
||||
$voterId = (int) $request->input('voter_id');
|
||||
$code = trim((string) $request->input('code'));
|
||||
|
||||
$row = AllowanceClaimCode::where('election_id', $electionId)
|
||||
->where('voter_id', $voterId)
|
||||
->where('method', $method)
|
||||
->first();
|
||||
|
||||
if (!$row) {
|
||||
return response()->json([
|
||||
'status' => 'failed',
|
||||
'message' => 'Kod tuntutan tidak dijumpai.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
if ($row->used_at) {
|
||||
return response()->json([
|
||||
'status' => 'failed',
|
||||
'message' => 'Kod tuntutan telah digunakan.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
if ($row->expires_at && Carbon::now()->greaterThan($row->expires_at)) {
|
||||
return response()->json([
|
||||
'status' => 'failed',
|
||||
'message' => 'Kod tuntutan telah tamat tempoh.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
if (!Hash::check($code, $row->code_hash)) {
|
||||
return response()->json([
|
||||
'status' => 'failed',
|
||||
'message' => 'Kod tuntutan tidak sah.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
// Mark used (consumed) here so it cannot be reused
|
||||
$row->used_at = Carbon::now();
|
||||
$row->used_by_admin_id = $request->user() ? (int) $request->user()->id : null;
|
||||
$row->save();
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Kod tuntutan disahkan.',
|
||||
'claim' => [
|
||||
'id' => $row->id,
|
||||
'voter_id' => $row->voter_id,
|
||||
'method' => $row->method,
|
||||
'expires_at' => $row->expires_at,
|
||||
'used_at' => $row->used_at,
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API\v1\Admin\Allowance;
|
||||
|
||||
use App\AllowancePayout;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class VoidController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request, $id)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'note' => 'nullable|string|max:2000',
|
||||
]);
|
||||
|
||||
$payout = AllowancePayout::find($id);
|
||||
if (!$payout) {
|
||||
return response()->json([
|
||||
'status' => 'failed',
|
||||
'message' => 'Payout not found.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$note = trim((string) $request->input('note', ''));
|
||||
if ($note !== '') {
|
||||
$payout->note = $payout->note ? ($payout->note . "\n\nVOID NOTE: " . $note) : ("VOID NOTE: " . $note);
|
||||
}
|
||||
|
||||
$payout->status = 'void';
|
||||
$payout->save();
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Payout voided.',
|
||||
'payout' => $payout,
|
||||
'voided_at' => Carbon::now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user