This commit is contained in:
Saufi
2026-06-03 08:51:22 +08:00
commit a14d43fe34
347 changed files with 38197 additions and 0 deletions

View File

@@ -0,0 +1,139 @@
<?php
namespace App\Services\Admin\Management;
use App\Models\Election;
use App\Models\PusatMengundi;
use App\Models\User;
use App\Models\WheelchairAllocation;
use App\Models\WheelchairTransaction;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
class WheelchairManagementService
{
/**
* @param array{election_id: int, pusat_mengundi_id: int, allocated_quantity: int, notes?: string|null} $data
*/
public function upsertAllocation(User $actor, array $data): WheelchairAllocation
{
return DB::transaction(function () use ($actor, $data): WheelchairAllocation {
/** @var Election $election */
$election = Election::query()->findOrFail($data['election_id']);
/** @var PusatMengundi $pusat */
$pusat = PusatMengundi::query()->findOrFail($data['pusat_mengundi_id']);
if ((int) $pusat->election_id !== (int) $election->id) {
throw ValidationException::withMessages([
'pusat_mengundi_id' => 'Pusat Mengundi tidak berada dalam pilihanraya yang sama.',
]);
}
$allocation = WheelchairAllocation::query()->firstOrNew([
'election_id' => $election->id,
'pusat_mengundi_id' => $pusat->id,
]);
$outstanding = $allocation->exists ? $this->outstandingQuantity($allocation) : 0;
if ($data['allocated_quantity'] < $outstanding) {
throw ValidationException::withMessages([
'allocated_quantity' => 'Peruntukan tidak boleh kurang daripada jumlah kerusi roda yang masih belum dipulang.',
]);
}
$before = $allocation->exists ? $allocation->only(['allocated_quantity', 'notes']) : null;
$allocation->forceFill([
'allocated_quantity' => $data['allocated_quantity'],
'notes' => $data['notes'] ?? null,
])->save();
activity('wheelchairs')
->performedOn($allocation)
->causedBy($actor)
->withProperties([
'before' => $before,
'after' => $allocation->only(['allocated_quantity', 'notes']),
])
->log('wheelchair_allocation_upserted');
return $allocation;
});
}
/**
* @param array{transaction_type: string, quantity: int, taken_at?: string|null, taken_by_name?: string|null, returned_at?: string|null, return_condition?: string|null, notes?: string|null} $data
*/
public function recordTransaction(WheelchairAllocation $allocation, User $actor, array $data): WheelchairTransaction
{
return DB::transaction(function () use ($allocation, $actor, $data): WheelchairTransaction {
$allocation->refresh();
$type = $data['transaction_type'];
$quantity = $data['quantity'];
if ($type === 'taken' && $quantity > $this->availableQuantity($allocation)) {
throw ValidationException::withMessages([
'quantity' => 'Jumlah diambil melebihi baki peruntukan kerusi roda.',
]);
}
if ($type === 'returned' && $quantity > $this->outstandingQuantity($allocation)) {
throw ValidationException::withMessages([
'quantity' => 'Jumlah dipulang melebihi jumlah kerusi roda yang masih belum dipulang.',
]);
}
$transaction = WheelchairTransaction::query()->create([
'wheelchair_allocation_id' => $allocation->id,
'transaction_type' => $type,
'quantity' => $quantity,
'taken_at' => $type === 'taken' ? ($data['taken_at'] ?? now()) : null,
'taken_by_name' => $type === 'taken' ? ($data['taken_by_name'] ?? null) : null,
'returned_at' => $type === 'returned' ? ($data['returned_at'] ?? now()) : null,
'return_condition' => $type === 'returned' ? ($data['return_condition'] ?? null) : null,
'returned_by_name' => $type === 'returned' ? ($data['returned_by_name'] ?? null) : null,
'recorded_by_user_id' => $actor->id,
'notes' => $data['notes'] ?? null,
]);
activity('wheelchairs')
->performedOn($transaction)
->causedBy($actor)
->withProperties([
'allocation_id' => $allocation->id,
'transaction_type' => $type,
'quantity' => $quantity,
'available_after' => $this->availableQuantity($allocation->fresh()),
'outstanding_after' => $this->outstandingQuantity($allocation->fresh()),
])
->log('wheelchair_transaction_recorded');
return $transaction;
});
}
public function takenQuantity(WheelchairAllocation $allocation): int
{
return (int) $allocation->transactions()
->where('transaction_type', 'taken')
->sum('quantity');
}
public function returnedQuantity(WheelchairAllocation $allocation): int
{
return (int) $allocation->transactions()
->where('transaction_type', 'returned')
->sum('quantity');
}
public function outstandingQuantity(WheelchairAllocation $allocation): int
{
return max(0, $this->takenQuantity($allocation) - $this->returnedQuantity($allocation));
}
public function availableQuantity(WheelchairAllocation $allocation): int
{
return max(0, (int) $allocation->allocated_quantity - $this->outstandingQuantity($allocation));
}
}