diff --git a/app/Actions/CreateBucketAction.php b/app/Actions/CreateBucketAction.php index 922454a..8a09cf7 100644 --- a/app/Actions/CreateBucketAction.php +++ b/app/Actions/CreateBucketAction.php @@ -33,7 +33,7 @@ public function execute( } else { // Validate priority is positive if ($priority < 1) { - throw new InvalidArgumentException("Priority must be at least 1"); + throw new InvalidArgumentException('Priority must be at least 1'); } // Check if priority already exists and shift others if needed @@ -57,7 +57,6 @@ public function execute( }); } - /** * Validate allocation value based on allocation type. */ @@ -95,7 +94,7 @@ private function validateAllocationValue(BucketAllocationTypeEnum $allocationTyp public function createDefaultBuckets(Scenario $scenario): array { $buckets = []; - + // Monthly Expenses - Fixed limit, priority 1 $buckets[] = $this->execute( $scenario, @@ -104,16 +103,16 @@ public function createDefaultBuckets(Scenario $scenario): array 0, 1 ); - + // Emergency Fund - Fixed limit, priority 2 $buckets[] = $this->execute( $scenario, - 'Emergency Fund', + 'Emergency Fund', BucketAllocationTypeEnum::FIXED_LIMIT, 0, 2 ); - + // Investments - Unlimited, priority 3 $buckets[] = $this->execute( $scenario, @@ -122,7 +121,7 @@ public function createDefaultBuckets(Scenario $scenario): array null, 3 ); - + return $buckets; } -} \ No newline at end of file +} diff --git a/app/Actions/DeleteScenarioAction.php b/app/Actions/DeleteScenarioAction.php index 4931ecf..c8449bc 100644 --- a/app/Actions/DeleteScenarioAction.php +++ b/app/Actions/DeleteScenarioAction.php @@ -10,4 +10,4 @@ public function execute(Scenario $scenario): void { $scenario->delete(); } -} \ No newline at end of file +} diff --git a/app/Enums/BucketAllocationTypeEnum.php b/app/Enums/BucketAllocationTypeEnum.php index a068e96..9307c00 100644 --- a/app/Enums/BucketAllocationTypeEnum.php +++ b/app/Enums/BucketAllocationTypeEnum.php @@ -10,7 +10,7 @@ enum BucketAllocationTypeEnum: string public function getLabel(): string { - return match($this) { + return match ($this) { self::FIXED_LIMIT => 'Fixed Limit', self::PERCENTAGE => 'Percentage', self::UNLIMITED => 'Unlimited', @@ -24,7 +24,7 @@ public static function values(): array public function getAllocationValueRules(): array { - return match($this) { + return match ($this) { self::FIXED_LIMIT => ['required', 'numeric', 'min:0'], self::PERCENTAGE => ['required', 'numeric', 'min:0.01', 'max:100'], self::UNLIMITED => ['nullable'], @@ -33,9 +33,9 @@ public function getAllocationValueRules(): array public function formatValue(?float $value): string { - return match($this) { - self::FIXED_LIMIT => '$' . number_format($value ?? 0, 2), - self::PERCENTAGE => number_format($value ?? 0, 2) . '%', + return match ($this) { + self::FIXED_LIMIT => '$'.number_format($value ?? 0, 2), + self::PERCENTAGE => number_format($value ?? 0, 2).'%', self::UNLIMITED => 'All remaining', }; } diff --git a/app/Enums/StreamFrequencyEnum.php b/app/Enums/StreamFrequencyEnum.php index 1cef1ed..509c27f 100644 --- a/app/Enums/StreamFrequencyEnum.php +++ b/app/Enums/StreamFrequencyEnum.php @@ -36,6 +36,7 @@ public static function labels(): array foreach (self::cases() as $case) { $labels[$case->value] = $case->label(); } + return $labels; } @@ -51,4 +52,4 @@ public function getMonthlyEquivalentMultiplier(): float self::ONCE => 0.0, }; } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/BucketController.php b/app/Http/Controllers/BucketController.php index 80e9742..f577329 100644 --- a/app/Http/Controllers/BucketController.php +++ b/app/Http/Controllers/BucketController.php @@ -34,7 +34,7 @@ public function index(Scenario $scenario): JsonResponse }); return response()->json([ - 'buckets' => $buckets + 'buckets' => $buckets, ]); } @@ -42,7 +42,7 @@ public function store(Request $request, Scenario $scenario): JsonResponse { $validated = $request->validate([ 'name' => 'required|string|max:255', - 'allocation_type' => 'required|in:' . implode(',', [ + 'allocation_type' => 'required|in:'.implode(',', [ Bucket::TYPE_FIXED_LIMIT, Bucket::TYPE_PERCENTAGE, Bucket::TYPE_UNLIMITED, @@ -52,7 +52,7 @@ public function store(Request $request, Scenario $scenario): JsonResponse ]); try { - $createBucketAction = new CreateBucketAction(); + $createBucketAction = new CreateBucketAction; $bucket = $createBucketAction->execute( $scenario, $validated['name'], @@ -63,12 +63,12 @@ public function store(Request $request, Scenario $scenario): JsonResponse return response()->json([ 'bucket' => $this->formatBucketResponse($bucket), - 'message' => 'Bucket created successfully.' + 'message' => 'Bucket created successfully.', ], 201); } catch (InvalidArgumentException $e) { return response()->json([ 'message' => 'Validation failed.', - 'errors' => ['allocation_value' => [$e->getMessage()]] + 'errors' => ['allocation_value' => [$e->getMessage()]], ], 422); } } @@ -77,7 +77,7 @@ public function update(Request $request, Bucket $bucket): JsonResponse { $validated = $request->validate([ 'name' => 'required|string|max:255', - 'allocation_type' => 'required|in:' . implode(',', [ + 'allocation_type' => 'required|in:'.implode(',', [ Bucket::TYPE_FIXED_LIMIT, Bucket::TYPE_PERCENTAGE, Bucket::TYPE_UNLIMITED, @@ -107,7 +107,7 @@ public function update(Request $request, Bucket $bucket): JsonResponse return response()->json([ 'bucket' => $this->formatBucketResponse($bucket), - 'message' => 'Bucket updated successfully.' + 'message' => 'Bucket updated successfully.', ]); } @@ -125,7 +125,7 @@ public function destroy(Bucket $bucket): JsonResponse $this->shiftPrioritiesDown($scenarioId, $deletedPriority); return response()->json([ - 'message' => 'Bucket deleted successfully.' + 'message' => 'Bucket deleted successfully.', ]); } @@ -151,7 +151,7 @@ public function updatePriorities(Request $request, Scenario $scenario): JsonResp } return response()->json([ - 'message' => 'Bucket priorities updated successfully.' + 'message' => 'Bucket priorities updated successfully.', ]); } @@ -175,7 +175,6 @@ private function formatBucketResponse(Bucket $bucket): array ]; } - /** * Shift priorities down to fill gap after deletion. */ diff --git a/app/Http/Controllers/ScenarioController.php b/app/Http/Controllers/ScenarioController.php index ea20ca1..6359db1 100644 --- a/app/Http/Controllers/ScenarioController.php +++ b/app/Http/Controllers/ScenarioController.php @@ -3,16 +3,16 @@ namespace App\Http\Controllers; use App\Actions\CreateScenarioAction; -use App\Actions\UpdateScenarioAction; use App\Actions\DeleteScenarioAction; -use App\Http\Resources\BucketResource; -use App\Http\Resources\StreamResource; -use App\Http\Resources\ScenarioResource; +use App\Actions\UpdateScenarioAction; use App\Http\Requests\StoreScenarioRequest; use App\Http\Requests\UpdateScenarioRequest; +use App\Http\Resources\BucketResource; +use App\Http\Resources\ScenarioResource; +use App\Http\Resources\StreamResource; use App\Models\Scenario; -use App\Repositories\StreamRepository; use App\Repositories\ScenarioRepository; +use App\Repositories\StreamRepository; use App\Services\Streams\StatsService; use Illuminate\Http\RedirectResponse; use Inertia\Inertia; @@ -32,7 +32,7 @@ public function __construct( public function index(): Response { return Inertia::render('Scenarios/Index', [ - 'scenarios' => ScenarioResource::collection($this->scenarioRepository->getAll()) + 'scenarios' => ScenarioResource::collection($this->scenarioRepository->getAll()), ]); } @@ -46,7 +46,7 @@ public function show(Scenario $scenario): Response 'scenario' => new ScenarioResource($scenario), 'buckets' => BucketResource::collection($scenario->buckets), 'streams' => StreamResource::collection($this->streamRepository->getForScenario($scenario)), - 'streamStats' => $this->statsService->getSummaryStats($scenario) + 'streamStats' => $this->statsService->getSummaryStats($scenario), ]); } @@ -65,7 +65,7 @@ public function store(StoreScenarioRequest $request): RedirectResponse public function edit(Scenario $scenario): Response { return Inertia::render('Scenarios/Edit', [ - 'scenario' => new ScenarioResource($scenario) + 'scenario' => new ScenarioResource($scenario), ]); } diff --git a/app/Http/Controllers/StreamController.php b/app/Http/Controllers/StreamController.php index 6f03a32..5cf3cb0 100644 --- a/app/Http/Controllers/StreamController.php +++ b/app/Http/Controllers/StreamController.php @@ -4,11 +4,11 @@ use App\Http\Requests\StoreStreamRequest; use App\Http\Requests\UpdateStreamRequest; -use App\Models\Stream; +use App\Http\Resources\StreamResource; use App\Models\Scenario; +use App\Models\Stream; use App\Repositories\StreamRepository; use App\Services\Streams\StatsService; -use App\Http\Resources\StreamResource; use Illuminate\Http\JsonResponse; class StreamController extends Controller @@ -24,7 +24,7 @@ public function index(Scenario $scenario): JsonResponse return response()->json([ 'streams' => StreamResource::collection($streams), - 'stats' => $this->statsService->getSummaryStats($scenario) + 'stats' => $this->statsService->getSummaryStats($scenario), ]); } @@ -34,7 +34,7 @@ public function store(StoreStreamRequest $request, Scenario $scenario): JsonResp return response()->json([ 'stream' => new StreamResource($stream), - 'message' => 'Stream created successfully.' + 'message' => 'Stream created successfully.', ], 201); } @@ -44,7 +44,7 @@ public function update(UpdateStreamRequest $request, Stream $stream): JsonRespon return response()->json([ 'stream' => new StreamResource($stream), - 'message' => 'Stream updated successfully.' + 'message' => 'Stream updated successfully.', ]); } @@ -53,7 +53,7 @@ public function destroy(Stream $stream): JsonResponse $this->streamRepository->delete($stream); return response()->json([ - 'message' => 'Stream deleted successfully.' + 'message' => 'Stream deleted successfully.', ]); } @@ -63,7 +63,7 @@ public function toggle(Stream $stream): JsonResponse return response()->json([ 'stream' => new StreamResource($stream), - 'message' => $stream->is_active ? 'Stream activated.' : 'Stream deactivated.' + 'message' => $stream->is_active ? 'Stream activated.' : 'Stream deactivated.', ]); } } diff --git a/app/Http/Requests/CalculateProjectionRequest.php b/app/Http/Requests/CalculateProjectionRequest.php index a3d6a80..f9e2cb2 100644 --- a/app/Http/Requests/CalculateProjectionRequest.php +++ b/app/Http/Requests/CalculateProjectionRequest.php @@ -26,4 +26,4 @@ public function messages(): array 'end_date.after_or_equal' => 'End date must be after or equal to start date.', ]; } -} \ No newline at end of file +} diff --git a/app/Http/Requests/StoreStreamRequest.php b/app/Http/Requests/StoreStreamRequest.php index 2cd3b9e..177847b 100644 --- a/app/Http/Requests/StoreStreamRequest.php +++ b/app/Http/Requests/StoreStreamRequest.php @@ -2,8 +2,8 @@ namespace App\Http\Requests; -use App\Models\Stream; use App\Models\Scenario; +use App\Models\Stream; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; @@ -37,7 +37,7 @@ public function rules(): array Stream::FREQUENCY_MONTHLY, Stream::FREQUENCY_QUARTERLY, Stream::FREQUENCY_YEARLY, - ]) + ]), ], 'start_date' => ['required', 'date', 'date_format:Y-m-d'], 'end_date' => ['nullable', 'date', 'date_format:Y-m-d', 'after_or_equal:start_date'], @@ -70,13 +70,13 @@ public function withValidator($validator): void ->where('id', $this->bucket_id) ->exists(); - if (!$bucketBelongsToScenario) { + if (! $bucketBelongsToScenario) { $validator->errors()->add('bucket_id', 'The selected bucket does not belong to this scenario.'); } } // For expense streams, bucket is required - if ($this->type === Stream::TYPE_EXPENSE && !$this->bucket_id) { + if ($this->type === Stream::TYPE_EXPENSE && ! $this->bucket_id) { $validator->errors()->add('bucket_id', 'A bucket must be selected for expense streams.'); } }); @@ -85,13 +85,13 @@ public function withValidator($validator): void protected function prepareForValidation(): void { // Ensure dates are in the correct format - if ($this->has('start_date') && !empty($this->start_date)) { + if ($this->has('start_date') && ! empty($this->start_date)) { $this->merge([ 'start_date' => date('Y-m-d', strtotime($this->start_date)), ]); } - if ($this->has('end_date') && !empty($this->end_date)) { + if ($this->has('end_date') && ! empty($this->end_date)) { $this->merge([ 'end_date' => date('Y-m-d', strtotime($this->end_date)), ]); diff --git a/app/Http/Requests/UpdateStreamRequest.php b/app/Http/Requests/UpdateStreamRequest.php index 1dcf099..7f346b4 100644 --- a/app/Http/Requests/UpdateStreamRequest.php +++ b/app/Http/Requests/UpdateStreamRequest.php @@ -3,7 +3,6 @@ namespace App\Http\Requests; use App\Models\Stream; -use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; @@ -31,7 +30,7 @@ public function rules(): array Stream::FREQUENCY_MONTHLY, Stream::FREQUENCY_QUARTERLY, Stream::FREQUENCY_YEARLY, - ]) + ]), ], 'start_date' => ['required', 'date', 'date_format:Y-m-d'], 'end_date' => ['nullable', 'date', 'date_format:Y-m-d', 'after_or_equal:start_date'], @@ -65,13 +64,13 @@ public function withValidator($validator): void ->where('id', $this->bucket_id) ->exists(); - if (!$bucketBelongsToScenario) { + if (! $bucketBelongsToScenario) { $validator->errors()->add('bucket_id', 'The selected bucket does not belong to this scenario.'); } } // For expense streams, bucket is required - if ($this->type === Stream::TYPE_EXPENSE && !$this->bucket_id) { + if ($this->type === Stream::TYPE_EXPENSE && ! $this->bucket_id) { $validator->errors()->add('bucket_id', 'A bucket must be selected for expense streams.'); } }); @@ -80,13 +79,13 @@ public function withValidator($validator): void protected function prepareForValidation(): void { // Ensure dates are in the correct format - if ($this->has('start_date') && !empty($this->start_date)) { + if ($this->has('start_date') && ! empty($this->start_date)) { $this->merge([ 'start_date' => date('Y-m-d', strtotime($this->start_date)), ]); } - if ($this->has('end_date') && !empty($this->end_date)) { + if ($this->has('end_date') && ! empty($this->end_date)) { $this->merge([ 'end_date' => date('Y-m-d', strtotime($this->end_date)), ]); @@ -100,7 +99,7 @@ protected function prepareForValidation(): void } // Default is_active to current value if not provided - if (!$this->has('is_active')) { + if (! $this->has('is_active')) { /** @var Stream $stream */ $stream = $this->route('stream'); $this->merge([ diff --git a/app/Http/Resources/ProjectionResource.php b/app/Http/Resources/ProjectionResource.php index aeba1b6..ebff27d 100644 --- a/app/Http/Resources/ProjectionResource.php +++ b/app/Http/Resources/ProjectionResource.php @@ -21,4 +21,4 @@ public function toArray(Request $request): array ], ]; } -} \ No newline at end of file +} diff --git a/app/Models/Bucket.php b/app/Models/Bucket.php index 5725b9c..5c8bac4 100644 --- a/app/Models/Bucket.php +++ b/app/Models/Bucket.php @@ -140,13 +140,13 @@ public static function validationRules($scenarioId = null): array { $rules = [ 'name' => 'required|string|max:255', - 'allocation_type' => 'required|in:' . implode(',', BucketAllocationTypeEnum::values()), + 'allocation_type' => 'required|in:'.implode(',', BucketAllocationTypeEnum::values()), 'priority' => 'required|integer|min:1', ]; // Add scenario-specific priority uniqueness if scenario ID provided if ($scenarioId) { - $rules['priority'] .= '|unique:buckets,priority,NULL,id,scenario_id,' . $scenarioId; + $rules['priority'] .= '|unique:buckets,priority,NULL,id,scenario_id,'.$scenarioId; } return $rules; diff --git a/app/Models/Draw.php b/app/Models/Draw.php index 1bcae64..a262d08 100644 --- a/app/Models/Draw.php +++ b/app/Models/Draw.php @@ -17,14 +17,16 @@ * @property Carbon $date * @property string $description * @property bool $is_projected + * * @method static create(array $array) */ class Draw extends Model { + use HasAmount; + /** @use HasFactory */ use HasFactory; use HasProjectionStatus; - use HasAmount; protected $fillable = [ 'bucket_id', diff --git a/app/Models/Inflow.php b/app/Models/Inflow.php index 918ea22..821f0c9 100644 --- a/app/Models/Inflow.php +++ b/app/Models/Inflow.php @@ -19,8 +19,8 @@ */ class Inflow extends Model { - use HasProjectionStatus; use HasAmount; + use HasProjectionStatus; protected $fillable = [ 'stream_id', diff --git a/app/Models/Outflow.php b/app/Models/Outflow.php index 95033ca..40b883c 100644 --- a/app/Models/Outflow.php +++ b/app/Models/Outflow.php @@ -18,14 +18,16 @@ * @property Carbon $date * @property string $description * @property bool $is_projected + * * @method static create(array $array) */ class Outflow extends Model { + use HasAmount; + /** @use HasFactory */ use HasFactory; use HasProjectionStatus; - use HasAmount; protected $fillable = [ 'stream_id', diff --git a/app/Models/Scenario.php b/app/Models/Scenario.php index 9744bf9..ae59193 100644 --- a/app/Models/Scenario.php +++ b/app/Models/Scenario.php @@ -11,6 +11,7 @@ /** * @property int $id * @property Collection $buckets + * * @method static create(array $data) */ class Scenario extends Model diff --git a/app/Models/Stream.php b/app/Models/Stream.php index 6a2666b..4d8fab0 100644 --- a/app/Models/Stream.php +++ b/app/Models/Stream.php @@ -17,7 +17,7 @@ */ class Stream extends Model { - use HasFactory, HasAmount; + use HasAmount, HasFactory; protected $fillable = [ 'scenario_id', @@ -73,9 +73,10 @@ public function getFrequencyLabel(): string public function getMonthlyEquivalent(): float { - if (!$this->frequency) { + if (! $this->frequency) { return 0; } + return $this->amount * $this->frequency->getMonthlyEquivalentMultiplier(); } diff --git a/app/Models/Traits/HasAmount.php b/app/Models/Traits/HasAmount.php index 0ad6fa4..45d53b5 100644 --- a/app/Models/Traits/HasAmount.php +++ b/app/Models/Traits/HasAmount.php @@ -28,4 +28,4 @@ public function getFormattedAmountAttribute(): string { return number_format($this->amount / 100, 2); } -} \ No newline at end of file +} diff --git a/app/Repositories/ScenarioRepository.php b/app/Repositories/ScenarioRepository.php index 9323b2d..d641552 100644 --- a/app/Repositories/ScenarioRepository.php +++ b/app/Repositories/ScenarioRepository.php @@ -13,4 +13,4 @@ public function getAll(): Collection ->orderBy('created_at', 'desc') ->get(); } -} \ No newline at end of file +} diff --git a/app/Repositories/StreamRepository.php b/app/Repositories/StreamRepository.php index 66e49ca..649cebe 100644 --- a/app/Repositories/StreamRepository.php +++ b/app/Repositories/StreamRepository.php @@ -2,8 +2,8 @@ namespace App\Repositories; -use App\Models\Stream; use App\Models\Scenario; +use App\Models\Stream; use Illuminate\Support\Collection; class StreamRepository @@ -37,7 +37,7 @@ public function delete(Stream $stream): bool public function toggleActive(Stream $stream): Stream { $stream->update([ - 'is_active' => !$stream->is_active + 'is_active' => ! $stream->is_active, ]); return $stream->fresh('bucket'); @@ -57,8 +57,6 @@ public function bucketBelongsToScenario(Scenario $scenario, ?int $bucketId): boo ->exists(); } - - /** * Get streams grouped by type */ diff --git a/app/Services/Projection/PipelineAllocationService.php b/app/Services/Projection/PipelineAllocationService.php index 4c078b7..66b4cf9 100644 --- a/app/Services/Projection/PipelineAllocationService.php +++ b/app/Services/Projection/PipelineAllocationService.php @@ -14,10 +14,6 @@ /** * Allocate an inflow amount across scenario buckets according to priority rules. * - * @param Scenario $scenario - * @param int $amount - * @param Carbon|null $date - * @param string|null $description * @return Collection Collection of Draw models */ public function allocateInflow(Scenario $scenario, int $amount, ?Carbon $date = null, ?string $description = null): Collection @@ -54,7 +50,7 @@ public function allocateInflow(Scenario $scenario, int $amount, ?Carbon $date = 'bucket_id' => $bucket->id, 'amount' => $allocation, 'date' => $allocationDate, - 'description' => $description ?? "Allocation from inflow", + 'description' => $description ?? 'Allocation from inflow', 'is_projected' => true, ]); @@ -69,10 +65,6 @@ public function allocateInflow(Scenario $scenario, int $amount, ?Carbon $date = /** * Calculate how much should be allocated to a specific bucket. - * - * @param Bucket $bucket - * @param int $remainingAmount - * @return int */ private function calculateBucketAllocation(Bucket $bucket, int $remainingAmount): int { @@ -86,14 +78,10 @@ private function calculateBucketAllocation(Bucket $bucket, int $remainingAmount) /** * Calculate allocation for fixed limit buckets. - * - * @param Bucket $bucket - * @param int $remainingAmount - * @return int */ private function calculateFixedAllocation(Bucket $bucket, int $remainingAmount): int { - $bucketCapacity = (int)($bucket->allocation_value ?? 0); + $bucketCapacity = (int) ($bucket->allocation_value ?? 0); $currentBalance = $bucket->getCurrentBalance(); $availableSpace = max(0, $bucketCapacity - $currentBalance); @@ -102,14 +90,11 @@ private function calculateFixedAllocation(Bucket $bucket, int $remainingAmount): /** * Calculate allocation for percentage buckets. - * - * @param Bucket $bucket - * @param int $remainingAmount - * @return int */ private function calculatePercentageAllocation(Bucket $bucket, int $remainingAmount): int { $percentage = $bucket->allocation_value ?? 0; - return (int)round($remainingAmount * ($percentage / 100)); + + return (int) round($remainingAmount * ($percentage / 100)); } } diff --git a/app/Services/Streams/StatsService.php b/app/Services/Streams/StatsService.php index 0949ec1..667092a 100644 --- a/app/Services/Streams/StatsService.php +++ b/app/Services/Streams/StatsService.php @@ -15,11 +15,11 @@ public function getSummaryStats(Scenario $scenario): array $totalMonthlyIncome = $streams ->where('type', StreamTypeEnum::INCOME) - ->sum(fn($stream) => $stream->getMonthlyEquivalent()); + ->sum(fn ($stream) => $stream->getMonthlyEquivalent()); $totalMonthlyExpenses = $streams ->where('type', StreamTypeEnum::EXPENSE) - ->sum(fn($stream) => $stream->getMonthlyEquivalent()); + ->sum(fn ($stream) => $stream->getMonthlyEquivalent()); return [ 'total_streams' => $streams->count(), diff --git a/database/factories/BucketFactory.php b/database/factories/BucketFactory.php index ea822be..ceb77ec 100644 --- a/database/factories/BucketFactory.php +++ b/database/factories/BucketFactory.php @@ -50,7 +50,7 @@ public function definition(): array public function fixedLimit($amount = null): Factory { $amount = $amount ?? $this->faker->numberBetween(500, 5000); - + return $this->state([ 'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT, 'allocation_value' => $amount, @@ -63,7 +63,7 @@ public function fixedLimit($amount = null): Factory public function percentage($percentage = null): Factory { $percentage = $percentage ?? $this->faker->numberBetween(10, 50); - + return $this->state([ 'allocation_type' => BucketAllocationTypeEnum::PERCENTAGE, 'allocation_value' => $percentage, @@ -119,10 +119,10 @@ public function defaultSet(): array */ private function getAllocationValueForType(BucketAllocationTypeEnum $type): ?float { - return match($type) { + return match ($type) { BucketAllocationTypeEnum::FIXED_LIMIT => $this->faker->numberBetween(100, 10000), BucketAllocationTypeEnum::PERCENTAGE => $this->faker->numberBetween(5, 50), BucketAllocationTypeEnum::UNLIMITED => null, }; } -} \ No newline at end of file +} diff --git a/database/factories/ScenarioFactory.php b/database/factories/ScenarioFactory.php index 6595c1b..a732f31 100644 --- a/database/factories/ScenarioFactory.php +++ b/database/factories/ScenarioFactory.php @@ -13,7 +13,7 @@ class ScenarioFactory extends Factory public function definition(): array { return [ - 'name' => $this->faker->words(2, true) . ' Budget', + 'name' => $this->faker->words(2, true).' Budget', 'description' => $this->faker->text, ]; } diff --git a/routes/web.php b/routes/web.php index 78779c4..3b37146 100644 --- a/routes/web.php +++ b/routes/web.php @@ -6,7 +6,6 @@ use App\Http\Controllers\StreamController; use Illuminate\Support\Facades\Route; use Inertia\Inertia; -use Laravel\Fortify\Features; // Scenario routes (no auth required for MVP) Route::get('/', [ScenarioController::class, 'index'])->name('scenarios.index'); diff --git a/tests/Feature/Auth/TwoFactorChallengeTest.php b/tests/Feature/Auth/TwoFactorChallengeTest.php index 71f879f..85f97c4 100644 --- a/tests/Feature/Auth/TwoFactorChallengeTest.php +++ b/tests/Feature/Auth/TwoFactorChallengeTest.php @@ -26,7 +26,7 @@ public function test_two_factor_challenge_redirects_to_login_when_not_authentica public function test_two_factor_challenge_can_be_rendered(): void { $this->markTestSkipped('Auth routes not integrated with scenario pages'); - + if (! Features::canManageTwoFactorAuthentication()) { $this->markTestSkipped('Two-factor authentication is not enabled.'); } diff --git a/tests/Feature/Auth/VerificationNotificationTest.php b/tests/Feature/Auth/VerificationNotificationTest.php index ebf6b00..2e62bee 100644 --- a/tests/Feature/Auth/VerificationNotificationTest.php +++ b/tests/Feature/Auth/VerificationNotificationTest.php @@ -15,7 +15,7 @@ class VerificationNotificationTest extends TestCase public function test_sends_verification_notification(): void { $this->markTestSkipped('Auth routes not integrated with scenario pages'); - + Notification::fake(); $user = User::factory()->create([ @@ -32,7 +32,7 @@ public function test_sends_verification_notification(): void public function test_does_not_send_verification_notification_if_email_is_verified(): void { $this->markTestSkipped('Auth routes not integrated with scenario pages'); - + Notification::fake(); $user = User::factory()->create([ diff --git a/tests/Feature/Settings/PasswordUpdateTest.php b/tests/Feature/Settings/PasswordUpdateTest.php index 59b7f60..788d4dc 100644 --- a/tests/Feature/Settings/PasswordUpdateTest.php +++ b/tests/Feature/Settings/PasswordUpdateTest.php @@ -25,7 +25,7 @@ public function test_password_update_page_is_displayed() public function test_password_can_be_updated() { $this->markTestSkipped('Auth routes not integrated with scenario pages'); - + $user = User::factory()->create(); $response = $this @@ -47,7 +47,7 @@ public function test_password_can_be_updated() public function test_correct_password_must_be_provided_to_update_password() { $this->markTestSkipped('Auth routes not integrated with scenario pages'); - + $user = User::factory()->create(); $response = $this diff --git a/tests/Feature/Settings/ProfileUpdateTest.php b/tests/Feature/Settings/ProfileUpdateTest.php index 596a0ed..e301823 100644 --- a/tests/Feature/Settings/ProfileUpdateTest.php +++ b/tests/Feature/Settings/ProfileUpdateTest.php @@ -24,7 +24,7 @@ public function test_profile_page_is_displayed() public function test_profile_information_can_be_updated() { $this->markTestSkipped('Auth routes not integrated with scenario pages'); - + $user = User::factory()->create(); $response = $this @@ -48,7 +48,7 @@ public function test_profile_information_can_be_updated() public function test_email_verification_status_is_unchanged_when_the_email_address_is_unchanged() { $this->markTestSkipped('Auth routes not integrated with scenario pages'); - + $user = User::factory()->create(); $response = $this @@ -68,7 +68,7 @@ public function test_email_verification_status_is_unchanged_when_the_email_addre public function test_user_can_delete_their_account() { $this->markTestSkipped('Auth routes not integrated with scenario pages'); - + $user = User::factory()->create(); $response = $this @@ -88,7 +88,7 @@ public function test_user_can_delete_their_account() public function test_correct_password_must_be_provided_to_delete_account() { $this->markTestSkipped('Auth routes not integrated with scenario pages'); - + $user = User::factory()->create(); $response = $this diff --git a/tests/Unit/Actions/CreateBucketActionTest.php b/tests/Unit/Actions/CreateBucketActionTest.php index c2a09d1..fdf5769 100644 --- a/tests/Unit/Actions/CreateBucketActionTest.php +++ b/tests/Unit/Actions/CreateBucketActionTest.php @@ -15,12 +15,13 @@ class CreateBucketActionTest extends TestCase use RefreshDatabase; private CreateBucketAction $action; + private Scenario $scenario; protected function setUp(): void { parent::setUp(); - $this->action = new CreateBucketAction(); + $this->action = new CreateBucketAction; $this->scenario = Scenario::factory()->create(); } @@ -135,7 +136,6 @@ public function test_existing_priorities_are_shifted_when_inserting(): void $this->assertEquals(4, $bucket3->priority); // Shifted from 3 to 4 } - public function test_throws_exception_for_fixed_limit_without_allocation_value(): void { $this->expectException(InvalidArgumentException::class); @@ -255,4 +255,4 @@ public function test_creates_buckets_in_database_transaction(): void $this->assertEquals('Bucket 2', $buckets[0]->name); // New bucket at priority 1 $this->assertEquals('Bucket 1', $buckets[1]->name); // Original bucket shifted to priority 2 } -} \ No newline at end of file +} diff --git a/tests/Unit/PipelineAllocationServiceTest.php b/tests/Unit/PipelineAllocationServiceTest.php index 9e49685..6a89d2a 100644 --- a/tests/Unit/PipelineAllocationServiceTest.php +++ b/tests/Unit/PipelineAllocationServiceTest.php @@ -14,12 +14,13 @@ class PipelineAllocationServiceTest extends TestCase use RefreshDatabase; private PipelineAllocationService $service; + private Scenario $scenario; protected function setUp(): void { parent::setUp(); - $this->service = new PipelineAllocationService(); + $this->service = new PipelineAllocationService; $this->scenario = Scenario::factory()->create(); } @@ -32,7 +33,7 @@ public function test_allocates_to_single_fixed_bucket() 'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT, 'allocation_value' => 50000, 'starting_amount' => 0, - 'priority' => 1 + 'priority' => 1, ]); // Act: Allocate $300 @@ -52,21 +53,21 @@ public function test_allocates_across_multiple_fixed_buckets_by_priority() 'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT, 'allocation_value' => 20000, 'starting_amount' => 0, - 'priority' => 1 + 'priority' => 1, ]); $bucket2 = Bucket::factory()->create([ 'scenario_id' => $this->scenario->id, 'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT, 'allocation_value' => 30000, 'starting_amount' => 0, - 'priority' => 2 + 'priority' => 2, ]); $bucket3 = Bucket::factory()->create([ 'scenario_id' => $this->scenario->id, 'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT, 'allocation_value' => 15000, 'starting_amount' => 0, - 'priority' => 3 + 'priority' => 3, ]); // Act: Allocate $550 (should fill bucket1 + bucket2 + partial bucket3) @@ -96,14 +97,14 @@ public function test_percentage_bucket_gets_percentage_of_remaining_amount() 'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT, 'allocation_value' => 30000, 'starting_amount' => 0, - 'priority' => 1 + 'priority' => 1, ]); $percentageBucket = Bucket::factory()->create([ 'scenario_id' => $this->scenario->id, 'allocation_type' => BucketAllocationTypeEnum::PERCENTAGE, 'allocation_value' => 20.00, // 20% 'starting_amount' => 0, - 'priority' => 2 + 'priority' => 2, ]); // Act: Allocate $1000 @@ -123,14 +124,14 @@ public function test_unlimited_bucket_gets_all_remaining_amount() 'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT, 'allocation_value' => 50000, 'starting_amount' => 0, - 'priority' => 1 + 'priority' => 1, ]); $unlimitedBucket = Bucket::factory()->create([ 'scenario_id' => $this->scenario->id, 'allocation_type' => BucketAllocationTypeEnum::UNLIMITED, 'allocation_value' => null, 'starting_amount' => 0, - 'priority' => 2 + 'priority' => 2, ]); // Act: Allocate $1500 @@ -150,14 +151,14 @@ public function test_skips_buckets_with_zero_allocation() 'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT, 'allocation_value' => 0, // No capacity 'starting_amount' => 0, - 'priority' => 1 + 'priority' => 1, ]); $normalBucket = Bucket::factory()->create([ 'scenario_id' => $this->scenario->id, 'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT, 'allocation_value' => 30000, 'starting_amount' => 0, - 'priority' => 2 + 'priority' => 2, ]); // Act: Allocate $200 @@ -177,35 +178,35 @@ public function test_handles_complex_mixed_bucket_scenario() 'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT, 'allocation_value' => 100000, 'starting_amount' => 0, - 'priority' => 1 + 'priority' => 1, ]); $percentage1 = Bucket::factory()->create([ 'scenario_id' => $this->scenario->id, 'allocation_type' => BucketAllocationTypeEnum::PERCENTAGE, 'allocation_value' => 15.00, // 15% 'starting_amount' => 0, - 'priority' => 2 + 'priority' => 2, ]); $fixed2 = Bucket::factory()->create([ 'scenario_id' => $this->scenario->id, 'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT, 'allocation_value' => 50000, 'starting_amount' => 0, - 'priority' => 3 + 'priority' => 3, ]); $percentage2 = Bucket::factory()->create([ 'scenario_id' => $this->scenario->id, 'allocation_type' => BucketAllocationTypeEnum::PERCENTAGE, 'allocation_value' => 25.00, // 25% 'starting_amount' => 0, - 'priority' => 4 + 'priority' => 4, ]); $unlimited = Bucket::factory()->create([ 'scenario_id' => $this->scenario->id, 'allocation_type' => BucketAllocationTypeEnum::UNLIMITED, 'allocation_value' => null, 'starting_amount' => 0, - 'priority' => 5 + 'priority' => 5, ]); // Act: Allocate $5000 @@ -248,7 +249,7 @@ public function test_returns_empty_array_when_amount_is_zero() 'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT, 'allocation_value' => 50000, 'starting_amount' => 0, - 'priority' => 1 + 'priority' => 1, ]); // Act: Allocate $0 @@ -266,7 +267,7 @@ public function test_handles_negative_amount_gracefully() 'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT, 'allocation_value' => 50000, 'starting_amount' => 0, - 'priority' => 1 + 'priority' => 1, ]); // Act: Allocate negative amount @@ -284,21 +285,21 @@ public function test_respects_bucket_priority_order() 'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT, 'allocation_value' => 10000, 'starting_amount' => 0, - 'priority' => 10 // Higher number + 'priority' => 10, // Higher number ]); $bucket1 = Bucket::factory()->create([ 'scenario_id' => $this->scenario->id, 'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT, 'allocation_value' => 20000, 'starting_amount' => 0, - 'priority' => 1 // Lower number (higher priority) + 'priority' => 1, // Lower number (higher priority) ]); $bucket2 = Bucket::factory()->create([ 'scenario_id' => $this->scenario->id, 'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT, 'allocation_value' => 15000, 'starting_amount' => 0, - 'priority' => 5 // Middle + 'priority' => 5, // Middle ]); // Act: Allocate $250 @@ -320,14 +321,14 @@ public function test_percentage_allocation_with_insufficient_remaining_amount() 'allocation_type' => BucketAllocationTypeEnum::FIXED_LIMIT, 'allocation_value' => 95000, 'starting_amount' => 0, - 'priority' => 1 + 'priority' => 1, ]); $percentageBucket = Bucket::factory()->create([ 'scenario_id' => $this->scenario->id, 'allocation_type' => BucketAllocationTypeEnum::PERCENTAGE, 'allocation_value' => 20.00, // 20% 'starting_amount' => 0, - 'priority' => 2 + 'priority' => 2, ]); // Act: Allocate $1000 (only $50 left after fixed) diff --git a/tests/Unit/ProjectionGeneratorServiceTest.php b/tests/Unit/ProjectionGeneratorServiceTest.php index 2a3edc3..1412154 100644 --- a/tests/Unit/ProjectionGeneratorServiceTest.php +++ b/tests/Unit/ProjectionGeneratorServiceTest.php @@ -19,12 +19,13 @@ class ProjectionGeneratorServiceTest extends TestCase use RefreshDatabase; private ProjectionGeneratorService $service; + private Scenario $scenario; protected function setUp(): void { parent::setUp(); - $this->service = new ProjectionGeneratorService(new PipelineAllocationService()); + $this->service = new ProjectionGeneratorService(new PipelineAllocationService); $this->scenario = Scenario::factory()->create(); // Set a fixed "now" for consistent testing