140 lines
5.8 KiB
PHP
140 lines
5.8 KiB
PHP
<?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));
|
|
}
|
|
}
|