Fix code style issues across 32 files (Pint)
This commit is contained in:
parent
54de5b6cc2
commit
67ab1f5f41
32 changed files with 119 additions and 131 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,4 +10,4 @@ public function execute(Scenario $scenario): void
|
|||
{
|
||||
$scenario->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ public function messages(): array
|
|||
'end_date.after_or_equal' => 'End date must be after or equal to start date.',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -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([
|
||||
|
|
|
|||
|
|
@ -21,4 +21,4 @@ public function toArray(Request $request): array
|
|||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<DrawFactory> */
|
||||
use HasFactory;
|
||||
use HasProjectionStatus;
|
||||
use HasAmount;
|
||||
|
||||
protected $fillable = [
|
||||
'bucket_id',
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@
|
|||
*/
|
||||
class Inflow extends Model
|
||||
{
|
||||
use HasProjectionStatus;
|
||||
use HasAmount;
|
||||
use HasProjectionStatus;
|
||||
|
||||
protected $fillable = [
|
||||
'stream_id',
|
||||
|
|
|
|||
|
|
@ -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<OutflowFactory> */
|
||||
use HasFactory;
|
||||
use HasProjectionStatus;
|
||||
use HasAmount;
|
||||
|
||||
protected $fillable = [
|
||||
'stream_id',
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
/**
|
||||
* @property int $id
|
||||
* @property Collection<Bucket> $buckets
|
||||
*
|
||||
* @method static create(array $data)
|
||||
*/
|
||||
class Scenario extends Model
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,4 +28,4 @@ public function getFormattedAmountAttribute(): string
|
|||
{
|
||||
return number_format($this->amount / 100, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,4 +13,4 @@ public function getAll(): Collection
|
|||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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<Draw> 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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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.');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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([
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue