Add bucket starting amount

This commit is contained in:
myrmidex 2025-12-31 01:48:13 +01:00
parent a07461e5a3
commit 217fd679e2
6 changed files with 50 additions and 9 deletions

View file

@ -27,12 +27,15 @@ class Bucket extends Model
'sort_order', 'sort_order',
'allocation_type', 'allocation_type',
'allocation_value', 'allocation_value',
'starting_amount',
]; ];
protected $casts = [ protected $casts = [
'priority' => 'integer', 'priority' => 'integer',
'sort_order' => 'integer', 'sort_order' => 'integer',
'allocation_value' => 'decimal:2', 'allocation_value' => 'decimal:2',
'starting_amount' => 'integer',
'allocation_type' => BucketAllocationType::class,
]; ];
public function scenario(): BelongsTo public function scenario(): BelongsTo
@ -42,21 +45,17 @@ public function scenario(): BelongsTo
/** /**
* Get the draws for the bucket. * Get the draws for the bucket.
* (Will be implemented when Draw model is created)
*/ */
public function draws(): HasMany public function draws(): HasMany
{ {
// TODO: Implement when Draw model is created
return $this->hasMany(Draw::class); return $this->hasMany(Draw::class);
} }
/** /**
* Get the outflows for the bucket. * Get the outflows for the bucket.
* (Will be implemented when Outflow model is created)
*/ */
public function outflows(): HasMany public function outflows(): HasMany
{ {
// TODO: Implement when Outflow model is created
return $this->hasMany(Outflow::class); return $this->hasMany(Outflow::class);
} }
@ -78,12 +77,14 @@ public function scopeOrderedBySortOrder($query)
/** /**
* Get the current balance of the bucket. * Get the current balance of the bucket.
* For MVP, this will always return 0 as we don't have transactions yet. * Calculates starting amount plus total draws (money allocated to bucket) minus total outflows (money spent from bucket).
*/ */
public function getCurrentBalance(): float public function getCurrentBalance(): float
{ {
// TODO: Calculate from draws minus outflows when those features are implemented $totalDrawsCents = $this->draws()->sum('amount');
return 0.0; $totalOutflowsCents = $this->outflows()->sum('amount');
return ($this->starting_amount + $totalDrawsCents - $totalOutflowsCents) / 100;
} }
/** /**

View file

@ -2,11 +2,13 @@
namespace App\Models; namespace App\Models;
use App\Models\Traits\HasAmount;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Stream extends Model class Stream extends Model
{ {
use HasAmount;
const TYPE_INCOME = 'income'; const TYPE_INCOME = 'income';
const TYPE_EXPENSE = 'expense'; const TYPE_EXPENSE = 'expense';
@ -31,7 +33,7 @@ class Stream extends Model
]; ];
protected $casts = [ protected $casts = [
'amount' => 'decimal:2', 'amount' => 'integer',
'start_date' => 'date', 'start_date' => 'date',
'end_date' => 'date', 'end_date' => 'date',
'is_active' => 'boolean', 'is_active' => 'boolean',
@ -42,6 +44,7 @@ public function scenario(): BelongsTo
return $this->belongsTo(Scenario::class); return $this->belongsTo(Scenario::class);
} }
public function bucket(): BelongsTo public function bucket(): BelongsTo
{ {
return $this->belongsTo(Bucket::class); return $this->belongsTo(Bucket::class);

View file

@ -0,0 +1,31 @@
<?php
namespace App\Models\Traits;
trait HasAmount
{
/**
* Get amount in currency units (stored as minor units/cents).
*/
public function getAmountCurrencyAttribute(): float
{
return $this->amount / 100;
}
/**
* Set amount from currency units (stores as minor units/cents).
*/
public function setAmountCurrencyAttribute($value): void
{
$this->attributes['amount'] = round($value * 100);
}
/**
* Format amount for display with proper currency formatting.
* This can be extended later to support different currencies.
*/
public function getFormattedAmountAttribute(): string
{
return number_format($this->amount / 100, 2);
}
}

View file

@ -40,6 +40,7 @@ public function definition(): array
'sort_order' => $this->faker->numberBetween(0, 10), 'sort_order' => $this->faker->numberBetween(0, 10),
'allocation_type' => $allocationType, 'allocation_type' => $allocationType,
'allocation_value' => $this->getAllocationValueForType($allocationType), 'allocation_value' => $this->getAllocationValueForType($allocationType),
'starting_amount' => $this->faker->numberBetween(0, 100000), // $0 to $1000 in cents
]; ];
} }
@ -92,6 +93,7 @@ public function defaultSet(): array
'sort_order' => 1, 'sort_order' => 1,
'allocation_type' => BucketAllocationType::FIXED_LIMIT, 'allocation_type' => BucketAllocationType::FIXED_LIMIT,
'allocation_value' => 0, 'allocation_value' => 0,
'starting_amount' => 0,
]), ]),
$this->state([ $this->state([
'name' => 'Emergency Fund', 'name' => 'Emergency Fund',
@ -99,6 +101,7 @@ public function defaultSet(): array
'sort_order' => 2, 'sort_order' => 2,
'allocation_type' => BucketAllocationType::FIXED_LIMIT, 'allocation_type' => BucketAllocationType::FIXED_LIMIT,
'allocation_value' => 0, 'allocation_value' => 0,
'starting_amount' => 0,
]), ]),
$this->state([ $this->state([
'name' => 'Investments', 'name' => 'Investments',
@ -106,6 +109,7 @@ public function defaultSet(): array
'sort_order' => 3, 'sort_order' => 3,
'allocation_type' => BucketAllocationType::UNLIMITED, 'allocation_type' => BucketAllocationType::UNLIMITED,
'allocation_value' => null, 'allocation_value' => null,
'starting_amount' => 0,
]), ]),
]; ];
} }

View file

@ -17,6 +17,8 @@ public function up(): void
$table->enum('allocation_type', ['fixed_limit', 'percentage', 'unlimited']); $table->enum('allocation_type', ['fixed_limit', 'percentage', 'unlimited']);
$table->decimal('allocation_value', 10, 2)->nullable() $table->decimal('allocation_value', 10, 2)->nullable()
->comment('Limit amount for fixed_limit, percentage for percentage type, NULL for unlimited'); ->comment('Limit amount for fixed_limit, percentage for percentage type, NULL for unlimited');
$table->unsignedBigInteger('starting_amount')->default(0)
->comment('Initial amount in bucket in cents before any draws or outflows');
$table->timestamps(); $table->timestamps();
// Indexes for performance // Indexes for performance

View file

@ -14,7 +14,7 @@ public function up(): void
$table->foreignId('bucket_id')->nullable()->constrained()->nullOnDelete(); $table->foreignId('bucket_id')->nullable()->constrained()->nullOnDelete();
$table->string('name'); $table->string('name');
$table->boolean('is_active')->default(true); $table->boolean('is_active')->default(true);
$table->decimal('amount', 12, 2); $table->unsignedBigInteger('amount');
$table->enum('type', ['income', 'expense']); $table->enum('type', ['income', 'expense']);
$table->enum('frequency', ['once', 'weekly', 'biweekly', 'monthly', 'quarterly', 'yearly']); $table->enum('frequency', ['once', 'weekly', 'biweekly', 'monthly', 'quarterly', 'yearly']);
$table->date('start_date'); $table->date('start_date');