105 lines
No EOL
4 KiB
PHP
105 lines
No EOL
4 KiB
PHP
<?php
|
|
|
|
namespace App\Actions;
|
|
|
|
use App\Models\Bucket;
|
|
use App\Models\Scenario;
|
|
use Illuminate\Support\Facades\DB;
|
|
use InvalidArgumentException;
|
|
|
|
class CreateBucketAction
|
|
{
|
|
public function execute(
|
|
Scenario $scenario,
|
|
string $name,
|
|
string $allocationType,
|
|
?float $allocationValue = null,
|
|
?int $priority = null
|
|
): Bucket {
|
|
// Validate allocation type
|
|
$validTypes = [Bucket::TYPE_FIXED_LIMIT, Bucket::TYPE_PERCENTAGE, Bucket::TYPE_UNLIMITED];
|
|
if (!in_array($allocationType, $validTypes)) {
|
|
throw new InvalidArgumentException("Invalid allocation type: {$allocationType}");
|
|
}
|
|
|
|
// Validate allocation value based on type
|
|
$this->validateAllocationValue($allocationType, $allocationValue);
|
|
|
|
// Set allocation_value to null for unlimited buckets
|
|
if ($allocationType === Bucket::TYPE_UNLIMITED) {
|
|
$allocationValue = null;
|
|
}
|
|
|
|
return DB::transaction(function () use ($scenario, $name, $allocationType, $allocationValue, $priority) {
|
|
// Determine priority (append to end if not specified)
|
|
if ($priority === null) {
|
|
$maxPriority = $scenario->buckets()->max('priority') ?? 0;
|
|
$priority = $maxPriority + 1;
|
|
} else {
|
|
// Validate priority is positive
|
|
if ($priority < 1) {
|
|
throw new InvalidArgumentException("Priority must be at least 1");
|
|
}
|
|
|
|
// Check if priority already exists and shift others if needed
|
|
$existingBucket = $scenario->buckets()->where('priority', $priority)->first();
|
|
if ($existingBucket) {
|
|
// Shift priorities to make room
|
|
$scenario->buckets()
|
|
->where('priority', '>=', $priority)
|
|
->increment('priority');
|
|
}
|
|
}
|
|
|
|
// Create the bucket
|
|
return $scenario->buckets()->create([
|
|
'name' => $name,
|
|
'priority' => $priority,
|
|
'sort_order' => $priority, // Start with sort_order matching priority
|
|
'allocation_type' => $allocationType,
|
|
'allocation_value' => $allocationValue,
|
|
]);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create default buckets for a new scenario.
|
|
*/
|
|
public function createDefaultBuckets(Scenario $scenario): void
|
|
{
|
|
$this->execute($scenario, 'Monthly Expenses', Bucket::TYPE_FIXED_LIMIT, 0, 1);
|
|
$this->execute($scenario, 'Emergency Fund', Bucket::TYPE_FIXED_LIMIT, 0, 2);
|
|
$this->execute($scenario, 'Investments', Bucket::TYPE_UNLIMITED, null, 3);
|
|
}
|
|
|
|
/**
|
|
* Validate allocation value based on allocation type.
|
|
*/
|
|
private function validateAllocationValue(string $allocationType, ?float $allocationValue): void
|
|
{
|
|
switch ($allocationType) {
|
|
case Bucket::TYPE_FIXED_LIMIT:
|
|
if ($allocationValue === null) {
|
|
throw new InvalidArgumentException('Fixed limit buckets require an allocation value');
|
|
}
|
|
if ($allocationValue < 0) {
|
|
throw new InvalidArgumentException('Fixed limit allocation value must be non-negative');
|
|
}
|
|
break;
|
|
|
|
case Bucket::TYPE_PERCENTAGE:
|
|
if ($allocationValue === null) {
|
|
throw new InvalidArgumentException('Percentage buckets require an allocation value');
|
|
}
|
|
if ($allocationValue < 0.01 || $allocationValue > 100) {
|
|
throw new InvalidArgumentException('Percentage allocation value must be between 0.01 and 100');
|
|
}
|
|
break;
|
|
|
|
case Bucket::TYPE_UNLIMITED:
|
|
// Unlimited buckets should not have an allocation value
|
|
// We'll set it to null in the main method regardless
|
|
break;
|
|
}
|
|
}
|
|
} |