Add bucket starting amount
This commit is contained in:
parent
a07461e5a3
commit
217fd679e2
6 changed files with 50 additions and 9 deletions
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
31
app/Models/Traits/HasAmount.php
Normal file
31
app/Models/Traits/HasAmount.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue