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)); } }