4 - Add bucket type parameter to CreateBucketAction and update CreateScenarioAction
This commit is contained in:
parent
fe5355d182
commit
da036ce97f
3 changed files with 148 additions and 44 deletions
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Actions;
|
||||
|
||||
use App\Enums\BucketAllocationTypeEnum;
|
||||
use App\Enums\BucketTypeEnum;
|
||||
use App\Models\Bucket;
|
||||
use App\Models\Scenario;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
|
@ -14,9 +15,13 @@ public function execute(
|
|||
Scenario $scenario,
|
||||
string $name,
|
||||
BucketAllocationTypeEnum $allocationType,
|
||||
BucketTypeEnum $type = BucketTypeEnum::NEED,
|
||||
?float $allocationValue = null,
|
||||
?int $priority = null
|
||||
?int $priority = null,
|
||||
): Bucket {
|
||||
// Validate type + allocation type constraints
|
||||
$this->validateTypeConstraints($type, $allocationType);
|
||||
|
||||
// Validate allocation value based on type
|
||||
$this->validateAllocationValue($allocationType, $allocationValue);
|
||||
|
||||
|
|
@ -25,7 +30,7 @@ public function execute(
|
|||
$allocationValue = null;
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($scenario, $name, $allocationType, $allocationValue, $priority) {
|
||||
return DB::transaction(function () use ($scenario, $name, $allocationType, $allocationValue, $priority, $type) {
|
||||
// Determine priority (append to end if not specified)
|
||||
if ($priority === null) {
|
||||
$maxPriority = $scenario->buckets()->max('priority') ?? 0;
|
||||
|
|
@ -53,6 +58,7 @@ public function execute(
|
|||
// Create the bucket
|
||||
return $scenario->buckets()->create([
|
||||
'name' => $name,
|
||||
'type' => $type,
|
||||
'priority' => $priority,
|
||||
'sort_order' => $priority, // Start with sort_order matching priority
|
||||
'allocation_type' => $allocationType,
|
||||
|
|
@ -61,6 +67,20 @@ public function execute(
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that bucket type and allocation type are compatible.
|
||||
*/
|
||||
private function validateTypeConstraints(BucketTypeEnum $type, BucketAllocationTypeEnum $allocationType): void
|
||||
{
|
||||
if ($type === BucketTypeEnum::OVERFLOW && $allocationType !== BucketAllocationTypeEnum::UNLIMITED) {
|
||||
throw new InvalidArgumentException('Overflow buckets must use unlimited allocation type');
|
||||
}
|
||||
|
||||
if ($type !== BucketTypeEnum::OVERFLOW && $allocationType === BucketAllocationTypeEnum::UNLIMITED) {
|
||||
throw new InvalidArgumentException('Only overflow buckets can use unlimited allocation type');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate allocation value based on allocation type.
|
||||
*/
|
||||
|
|
@ -99,29 +119,32 @@ public function createDefaultBuckets(Scenario $scenario): array
|
|||
{
|
||||
$buckets = [];
|
||||
|
||||
// Monthly Expenses - Fixed limit, priority 1
|
||||
// Monthly Expenses - Need, fixed limit, priority 1
|
||||
$buckets[] = $this->execute(
|
||||
$scenario,
|
||||
'Monthly Expenses',
|
||||
BucketAllocationTypeEnum::FIXED_LIMIT,
|
||||
BucketTypeEnum::NEED,
|
||||
0,
|
||||
1
|
||||
);
|
||||
|
||||
// Emergency Fund - Fixed limit, priority 2
|
||||
// Emergency Fund - Need, fixed limit, priority 2
|
||||
$buckets[] = $this->execute(
|
||||
$scenario,
|
||||
'Emergency Fund',
|
||||
BucketAllocationTypeEnum::FIXED_LIMIT,
|
||||
BucketTypeEnum::NEED,
|
||||
0,
|
||||
2
|
||||
);
|
||||
|
||||
// Investments - Unlimited, priority 3
|
||||
// Overflow - Overflow, unlimited, priority 3
|
||||
$buckets[] = $this->execute(
|
||||
$scenario,
|
||||
'Investments',
|
||||
'Overflow',
|
||||
BucketAllocationTypeEnum::UNLIMITED,
|
||||
BucketTypeEnum::OVERFLOW,
|
||||
null,
|
||||
3
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace App\Actions;
|
||||
|
||||
use App\Enums\BucketAllocationTypeEnum;
|
||||
use App\Models\Scenario;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
|
|
@ -24,8 +23,6 @@ public function execute(array $data): Scenario
|
|||
|
||||
private function createDefaultBuckets(Scenario $scenario): void
|
||||
{
|
||||
$this->createBucketAction->execute($scenario, 'Monthly Expenses', BucketAllocationTypeEnum::FIXED_LIMIT, 0, 1);
|
||||
$this->createBucketAction->execute($scenario, 'Emergency Fund', BucketAllocationTypeEnum::FIXED_LIMIT, 0, 2);
|
||||
$this->createBucketAction->execute($scenario, 'Investments', BucketAllocationTypeEnum::UNLIMITED, null, 3);
|
||||
$this->createBucketAction->createDefaultBuckets($scenario);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
use App\Actions\CreateBucketAction;
|
||||
use App\Enums\BucketAllocationTypeEnum;
|
||||
use App\Enums\BucketTypeEnum;
|
||||
use App\Models\Bucket;
|
||||
use App\Models\Scenario;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
|
@ -31,11 +32,12 @@ public function test_can_create_fixed_limit_bucket(): void
|
|||
$this->scenario,
|
||||
'Test Bucket',
|
||||
BucketAllocationTypeEnum::FIXED_LIMIT,
|
||||
1000.00
|
||||
allocationValue: 1000.00
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(Bucket::class, $bucket);
|
||||
$this->assertEquals('Test Bucket', $bucket->name);
|
||||
$this->assertEquals(BucketTypeEnum::NEED, $bucket->type);
|
||||
$this->assertEquals(BucketAllocationTypeEnum::FIXED_LIMIT, $bucket->allocation_type);
|
||||
$this->assertEquals(1000.00, $bucket->allocation_value);
|
||||
$this->assertEquals(1, $bucket->priority);
|
||||
|
|
@ -49,36 +51,117 @@ public function test_can_create_percentage_bucket(): void
|
|||
$this->scenario,
|
||||
'Percentage Bucket',
|
||||
BucketAllocationTypeEnum::PERCENTAGE,
|
||||
25.5
|
||||
allocationValue: 25.5
|
||||
);
|
||||
|
||||
$this->assertEquals(BucketAllocationTypeEnum::PERCENTAGE, $bucket->allocation_type);
|
||||
$this->assertEquals(25.5, $bucket->allocation_value);
|
||||
}
|
||||
|
||||
public function test_can_create_unlimited_bucket(): void
|
||||
public function test_can_create_unlimited_overflow_bucket(): void
|
||||
{
|
||||
$bucket = $this->action->execute(
|
||||
$this->scenario,
|
||||
'Unlimited Bucket',
|
||||
BucketAllocationTypeEnum::UNLIMITED
|
||||
'Overflow Bucket',
|
||||
BucketAllocationTypeEnum::UNLIMITED,
|
||||
BucketTypeEnum::OVERFLOW
|
||||
);
|
||||
|
||||
$this->assertEquals(BucketTypeEnum::OVERFLOW, $bucket->type);
|
||||
$this->assertEquals(BucketAllocationTypeEnum::UNLIMITED, $bucket->allocation_type);
|
||||
$this->assertNull($bucket->allocation_value);
|
||||
}
|
||||
|
||||
public function test_unlimited_overflow_bucket_ignores_allocation_value(): void
|
||||
{
|
||||
$bucket = $this->action->execute(
|
||||
$this->scenario,
|
||||
'Overflow Bucket',
|
||||
BucketAllocationTypeEnum::UNLIMITED,
|
||||
BucketTypeEnum::OVERFLOW,
|
||||
999.99
|
||||
);
|
||||
|
||||
$this->assertEquals(BucketAllocationTypeEnum::UNLIMITED, $bucket->allocation_type);
|
||||
$this->assertNull($bucket->allocation_value);
|
||||
}
|
||||
|
||||
public function test_unlimited_bucket_ignores_allocation_value(): void
|
||||
public function test_can_create_need_bucket(): void
|
||||
{
|
||||
$bucket = $this->action->execute(
|
||||
$this->scenario,
|
||||
'Unlimited Bucket',
|
||||
BucketAllocationTypeEnum::UNLIMITED,
|
||||
999.99 // This should be ignored and set to null
|
||||
'Need Bucket',
|
||||
BucketAllocationTypeEnum::FIXED_LIMIT,
|
||||
BucketTypeEnum::NEED,
|
||||
500
|
||||
);
|
||||
|
||||
$this->assertEquals(BucketAllocationTypeEnum::UNLIMITED, $bucket->allocation_type);
|
||||
$this->assertNull($bucket->allocation_value);
|
||||
$this->assertEquals(BucketTypeEnum::NEED, $bucket->type);
|
||||
}
|
||||
|
||||
public function test_can_create_want_bucket(): void
|
||||
{
|
||||
$bucket = $this->action->execute(
|
||||
$this->scenario,
|
||||
'Want Bucket',
|
||||
BucketAllocationTypeEnum::FIXED_LIMIT,
|
||||
BucketTypeEnum::WANT,
|
||||
500
|
||||
);
|
||||
|
||||
$this->assertEquals(BucketTypeEnum::WANT, $bucket->type);
|
||||
}
|
||||
|
||||
public function test_default_type_is_need(): void
|
||||
{
|
||||
$bucket = $this->action->execute(
|
||||
$this->scenario,
|
||||
'Default Type Bucket',
|
||||
BucketAllocationTypeEnum::FIXED_LIMIT,
|
||||
allocationValue: 100
|
||||
);
|
||||
|
||||
$this->assertEquals(BucketTypeEnum::NEED, $bucket->type);
|
||||
}
|
||||
|
||||
public function test_overflow_bucket_must_use_unlimited_allocation(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Overflow buckets must use unlimited allocation type');
|
||||
|
||||
$this->action->execute(
|
||||
$this->scenario,
|
||||
'Bad Overflow',
|
||||
BucketAllocationTypeEnum::FIXED_LIMIT,
|
||||
BucketTypeEnum::OVERFLOW,
|
||||
100
|
||||
);
|
||||
}
|
||||
|
||||
public function test_non_overflow_bucket_cannot_use_unlimited_allocation(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Only overflow buckets can use unlimited allocation type');
|
||||
|
||||
$this->action->execute(
|
||||
$this->scenario,
|
||||
'Bad Need Bucket',
|
||||
BucketAllocationTypeEnum::UNLIMITED,
|
||||
BucketTypeEnum::NEED
|
||||
);
|
||||
}
|
||||
|
||||
public function test_want_bucket_cannot_use_unlimited_allocation(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Only overflow buckets can use unlimited allocation type');
|
||||
|
||||
$this->action->execute(
|
||||
$this->scenario,
|
||||
'Bad Want Bucket',
|
||||
BucketAllocationTypeEnum::UNLIMITED,
|
||||
BucketTypeEnum::WANT
|
||||
);
|
||||
}
|
||||
|
||||
public function test_priority_auto_increments_when_not_specified(): void
|
||||
|
|
@ -87,14 +170,14 @@ public function test_priority_auto_increments_when_not_specified(): void
|
|||
$this->scenario,
|
||||
'First Bucket',
|
||||
BucketAllocationTypeEnum::FIXED_LIMIT,
|
||||
100
|
||||
allocationValue: 100
|
||||
);
|
||||
|
||||
$bucket2 = $this->action->execute(
|
||||
$this->scenario,
|
||||
'Second Bucket',
|
||||
BucketAllocationTypeEnum::FIXED_LIMIT,
|
||||
200
|
||||
allocationValue: 200
|
||||
);
|
||||
|
||||
$this->assertEquals(1, $bucket1->priority);
|
||||
|
|
@ -107,8 +190,8 @@ public function test_can_specify_custom_priority(): void
|
|||
$this->scenario,
|
||||
'Priority Bucket',
|
||||
BucketAllocationTypeEnum::FIXED_LIMIT,
|
||||
100,
|
||||
5
|
||||
allocationValue: 100,
|
||||
priority: 5
|
||||
);
|
||||
|
||||
$this->assertEquals(5, $bucket->priority);
|
||||
|
|
@ -117,12 +200,12 @@ public function test_can_specify_custom_priority(): void
|
|||
public function test_existing_priorities_are_shifted_when_inserting(): void
|
||||
{
|
||||
// Create initial buckets
|
||||
$bucket1 = $this->action->execute($this->scenario, 'Bucket 1', BucketAllocationTypeEnum::FIXED_LIMIT, 100, 1);
|
||||
$bucket2 = $this->action->execute($this->scenario, 'Bucket 2', BucketAllocationTypeEnum::FIXED_LIMIT, 200, 2);
|
||||
$bucket3 = $this->action->execute($this->scenario, 'Bucket 3', BucketAllocationTypeEnum::FIXED_LIMIT, 300, 3);
|
||||
$bucket1 = $this->action->execute($this->scenario, 'Bucket 1', BucketAllocationTypeEnum::FIXED_LIMIT, allocationValue: 100, priority: 1);
|
||||
$bucket2 = $this->action->execute($this->scenario, 'Bucket 2', BucketAllocationTypeEnum::FIXED_LIMIT, allocationValue: 200, priority: 2);
|
||||
$bucket3 = $this->action->execute($this->scenario, 'Bucket 3', BucketAllocationTypeEnum::FIXED_LIMIT, allocationValue: 300, priority: 3);
|
||||
|
||||
// Insert a bucket at priority 2
|
||||
$newBucket = $this->action->execute($this->scenario, 'New Bucket', BucketAllocationTypeEnum::FIXED_LIMIT, 150, 2);
|
||||
$newBucket = $this->action->execute($this->scenario, 'New Bucket', BucketAllocationTypeEnum::FIXED_LIMIT, allocationValue: 150, priority: 2);
|
||||
|
||||
// Refresh models from database
|
||||
$bucket1->refresh();
|
||||
|
|
@ -144,8 +227,7 @@ public function test_throws_exception_for_fixed_limit_without_allocation_value()
|
|||
$this->action->execute(
|
||||
$this->scenario,
|
||||
'Test Bucket',
|
||||
BucketAllocationTypeEnum::FIXED_LIMIT,
|
||||
null
|
||||
BucketAllocationTypeEnum::FIXED_LIMIT
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -158,7 +240,7 @@ public function test_throws_exception_for_negative_fixed_limit_value(): void
|
|||
$this->scenario,
|
||||
'Test Bucket',
|
||||
BucketAllocationTypeEnum::FIXED_LIMIT,
|
||||
-100
|
||||
allocationValue: -100
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -170,8 +252,7 @@ public function test_throws_exception_for_percentage_without_allocation_value():
|
|||
$this->action->execute(
|
||||
$this->scenario,
|
||||
'Test Bucket',
|
||||
BucketAllocationTypeEnum::PERCENTAGE,
|
||||
null
|
||||
BucketAllocationTypeEnum::PERCENTAGE
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -184,7 +265,7 @@ public function test_throws_exception_for_percentage_below_minimum(): void
|
|||
$this->scenario,
|
||||
'Test Bucket',
|
||||
BucketAllocationTypeEnum::PERCENTAGE,
|
||||
0.005
|
||||
allocationValue: 0.005
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -197,7 +278,7 @@ public function test_throws_exception_for_percentage_above_maximum(): void
|
|||
$this->scenario,
|
||||
'Test Bucket',
|
||||
BucketAllocationTypeEnum::PERCENTAGE,
|
||||
101
|
||||
allocationValue: 101
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -210,8 +291,8 @@ public function test_throws_exception_for_negative_priority(): void
|
|||
$this->scenario,
|
||||
'Test Bucket',
|
||||
BucketAllocationTypeEnum::FIXED_LIMIT,
|
||||
100,
|
||||
0
|
||||
allocationValue: 100,
|
||||
priority: 0
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -223,20 +304,23 @@ public function test_create_default_buckets(): void
|
|||
|
||||
$this->assertCount(3, $buckets);
|
||||
|
||||
// Monthly Expenses
|
||||
// Monthly Expenses - Need
|
||||
$this->assertEquals('Monthly Expenses', $buckets[0]->name);
|
||||
$this->assertEquals(BucketTypeEnum::NEED, $buckets[0]->type);
|
||||
$this->assertEquals(1, $buckets[0]->priority);
|
||||
$this->assertEquals(BucketAllocationTypeEnum::FIXED_LIMIT, $buckets[0]->allocation_type);
|
||||
$this->assertEquals(0, $buckets[0]->allocation_value);
|
||||
|
||||
// Emergency Fund
|
||||
// Emergency Fund - Need
|
||||
$this->assertEquals('Emergency Fund', $buckets[1]->name);
|
||||
$this->assertEquals(BucketTypeEnum::NEED, $buckets[1]->type);
|
||||
$this->assertEquals(2, $buckets[1]->priority);
|
||||
$this->assertEquals(BucketAllocationTypeEnum::FIXED_LIMIT, $buckets[1]->allocation_type);
|
||||
$this->assertEquals(0, $buckets[1]->allocation_value);
|
||||
|
||||
// Investments
|
||||
$this->assertEquals('Investments', $buckets[2]->name);
|
||||
// Overflow
|
||||
$this->assertEquals('Overflow', $buckets[2]->name);
|
||||
$this->assertEquals(BucketTypeEnum::OVERFLOW, $buckets[2]->type);
|
||||
$this->assertEquals(3, $buckets[2]->priority);
|
||||
$this->assertEquals(BucketAllocationTypeEnum::UNLIMITED, $buckets[2]->allocation_type);
|
||||
$this->assertNull($buckets[2]->allocation_value);
|
||||
|
|
@ -246,8 +330,8 @@ public function test_creates_buckets_in_database_transaction(): void
|
|||
{
|
||||
// This test ensures database consistency by creating multiple buckets
|
||||
// and verifying they all exist with correct priorities
|
||||
$this->action->execute($this->scenario, 'Bucket 1', BucketAllocationTypeEnum::FIXED_LIMIT, 100, 1);
|
||||
$this->action->execute($this->scenario, 'Bucket 2', BucketAllocationTypeEnum::FIXED_LIMIT, 200, 1); // Insert at priority 1
|
||||
$this->action->execute($this->scenario, 'Bucket 1', BucketAllocationTypeEnum::FIXED_LIMIT, allocationValue: 100, priority: 1);
|
||||
$this->action->execute($this->scenario, 'Bucket 2', BucketAllocationTypeEnum::FIXED_LIMIT, allocationValue: 200, priority: 1); // Insert at priority 1
|
||||
|
||||
// Both buckets should exist with correct priorities
|
||||
$buckets = $this->scenario->buckets()->orderBy('priority')->get();
|
||||
|
|
|
|||
Loading…
Reference in a new issue