4 - Update BucketController with bucket type validation and constraints

This commit is contained in:
myrmidex 2026-03-19 21:29:03 +01:00
parent da036ce97f
commit e5dc7b0e21

View file

@ -3,6 +3,8 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Actions\CreateBucketAction; use App\Actions\CreateBucketAction;
use App\Enums\BucketAllocationTypeEnum;
use App\Enums\BucketTypeEnum;
use App\Models\Bucket; use App\Models\Bucket;
use App\Models\Scenario; use App\Models\Scenario;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@ -17,21 +19,7 @@ public function index(Scenario $scenario): JsonResponse
$buckets = $scenario->buckets() $buckets = $scenario->buckets()
->orderedBySortOrder() ->orderedBySortOrder()
->get() ->get()
->map(function ($bucket) { ->map(fn ($bucket) => $this->formatBucketResponse($bucket));
return [
'id' => $bucket->uuid,
'name' => $bucket->name,
'priority' => $bucket->priority,
'sort_order' => $bucket->sort_order,
'allocation_type' => $bucket->allocation_type,
'allocation_value' => $bucket->allocation_value,
'allocation_type_label' => $bucket->getAllocationTypeLabel(),
'formatted_allocation_value' => $bucket->getFormattedAllocationValue(),
'current_balance' => $bucket->getCurrentBalance(),
'has_available_space' => $bucket->hasAvailableSpace(),
'available_space' => $bucket->getAvailableSpace(),
];
});
return response()->json([ return response()->json([
'buckets' => $buckets, 'buckets' => $buckets,
@ -42,22 +30,28 @@ public function store(Request $request, Scenario $scenario): JsonResponse
{ {
$validated = $request->validate([ $validated = $request->validate([
'name' => 'required|string|max:255', 'name' => 'required|string|max:255',
'allocation_type' => 'required|in:'.implode(',', [ 'type' => 'required|in:'.implode(',', BucketTypeEnum::values()),
Bucket::TYPE_FIXED_LIMIT, 'allocation_type' => 'required|in:'.implode(',', BucketAllocationTypeEnum::values()),
Bucket::TYPE_PERCENTAGE,
Bucket::TYPE_UNLIMITED,
]),
'allocation_value' => 'nullable|numeric', 'allocation_value' => 'nullable|numeric',
'priority' => 'nullable|integer|min:1', 'priority' => 'nullable|integer|min:1',
]); ]);
$type = BucketTypeEnum::from($validated['type']);
$allocationType = BucketAllocationTypeEnum::from($validated['allocation_type']);
$constraintError = $this->validateBucketTypeConstraints($type, $allocationType, $scenario);
if ($constraintError) {
return $constraintError;
}
try { try {
$createBucketAction = new CreateBucketAction; $createBucketAction = new CreateBucketAction;
$bucket = $createBucketAction->execute( $bucket = $createBucketAction->execute(
$scenario, $scenario,
$validated['name'], $validated['name'],
$validated['allocation_type'], $allocationType,
$validated['allocation_value'], $type,
$validated['allocation_value'] ?? null,
$validated['priority'] ?? null $validated['priority'] ?? null
); );
@ -77,23 +71,28 @@ public function update(Request $request, Bucket $bucket): JsonResponse
{ {
$validated = $request->validate([ $validated = $request->validate([
'name' => 'required|string|max:255', 'name' => 'required|string|max:255',
'allocation_type' => 'required|in:'.implode(',', [ 'type' => 'required|in:'.implode(',', BucketTypeEnum::values()),
Bucket::TYPE_FIXED_LIMIT, 'allocation_type' => 'required|in:'.implode(',', BucketAllocationTypeEnum::values()),
Bucket::TYPE_PERCENTAGE,
Bucket::TYPE_UNLIMITED,
]),
'allocation_value' => 'nullable|numeric', 'allocation_value' => 'nullable|numeric',
'priority' => 'nullable|integer|min:1', 'priority' => 'nullable|integer|min:1',
]); ]);
$type = BucketTypeEnum::from($validated['type']);
$allocationType = BucketAllocationTypeEnum::from($validated['allocation_type']);
$constraintError = $this->validateBucketTypeConstraints($type, $allocationType, $bucket->scenario, $bucket);
if ($constraintError) {
return $constraintError;
}
// Validate allocation_value based on allocation_type // Validate allocation_value based on allocation_type
$allocationValueRules = Bucket::allocationValueRules($validated['allocation_type']); $allocationValueRules = Bucket::allocationValueRules($allocationType);
$request->validate([ $request->validate([
'allocation_value' => $allocationValueRules, 'allocation_value' => $allocationValueRules,
]); ]);
// Set allocation_value to null for unlimited buckets // Set allocation_value to null for unlimited buckets
if ($validated['allocation_type'] === Bucket::TYPE_UNLIMITED) { if ($allocationType === BucketAllocationTypeEnum::UNLIMITED) {
$validated['allocation_value'] = null; $validated['allocation_value'] = null;
} }
@ -155,6 +154,47 @@ public function updatePriorities(Request $request, Scenario $scenario): JsonResp
]); ]);
} }
/**
* Validate bucket type constraints (type+allocation compatibility, one overflow per scenario).
*/
private function validateBucketTypeConstraints(
BucketTypeEnum $type,
BucketAllocationTypeEnum $allocationType,
Scenario $scenario,
?Bucket $excludeBucket = null
): ?JsonResponse {
if ($type === BucketTypeEnum::OVERFLOW && $allocationType !== BucketAllocationTypeEnum::UNLIMITED) {
return response()->json([
'message' => 'Validation failed.',
'errors' => ['allocation_type' => ['Overflow buckets must use unlimited allocation type.']],
], 422);
}
if ($type !== BucketTypeEnum::OVERFLOW && $allocationType === BucketAllocationTypeEnum::UNLIMITED) {
return response()->json([
'message' => 'Validation failed.',
'errors' => ['allocation_type' => ['Only overflow buckets can use unlimited allocation type.']],
], 422);
}
if ($type === BucketTypeEnum::OVERFLOW) {
$query = $scenario->buckets()->where('type', BucketTypeEnum::OVERFLOW->value);
if ($excludeBucket) {
$query->where('id', '!=', $excludeBucket->id);
}
if ($query->exists()) {
return response()->json([
'message' => 'Validation failed.',
'errors' => ['type' => ['A scenario can only have one overflow bucket.']],
], 422);
}
}
return null;
}
/** /**
* Format bucket data for JSON response. * Format bucket data for JSON response.
*/ */
@ -163,6 +203,8 @@ private function formatBucketResponse(Bucket $bucket): array
return [ return [
'id' => $bucket->uuid, 'id' => $bucket->uuid,
'name' => $bucket->name, 'name' => $bucket->name,
'type' => $bucket->type,
'type_label' => $bucket->type->getLabel(),
'priority' => $bucket->priority, 'priority' => $bucket->priority,
'sort_order' => $bucket->sort_order, 'sort_order' => $bucket->sort_order,
'allocation_type' => $bucket->allocation_type, 'allocation_type' => $bucket->allocation_type,