2025-12-29 23:32:05 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Models;
|
|
|
|
|
|
2025-12-31 02:34:30 +01:00
|
|
|
use App\Enums\BucketAllocationTypeEnum;
|
2026-03-19 21:14:36 +01:00
|
|
|
use App\Enums\BucketTypeEnum;
|
2026-03-19 20:34:47 +01:00
|
|
|
use App\Models\Traits\HasUuid;
|
2025-12-29 23:32:05 +01:00
|
|
|
use Database\Factories\BucketFactory;
|
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
|
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
|
|
|
|
|
|
|
|
/**
|
2025-12-31 01:56:50 +01:00
|
|
|
* @property int $id
|
2026-03-19 20:34:47 +01:00
|
|
|
* @property string $uuid
|
2025-12-29 23:32:05 +01:00
|
|
|
* @property int $scenario_id
|
2026-03-19 21:14:36 +01:00
|
|
|
* @property BucketTypeEnum $type
|
2025-12-29 23:32:05 +01:00
|
|
|
* @property Scenario $scenario
|
|
|
|
|
* @property string $name
|
|
|
|
|
* @property int $priority
|
2025-12-31 02:34:30 +01:00
|
|
|
* @property BucketAllocationTypeEnum $allocation_type
|
2026-03-21 16:54:11 +01:00
|
|
|
* @property int $starting_amount
|
|
|
|
|
* @property int|null $allocation_value
|
2026-03-20 00:25:44 +01:00
|
|
|
* @property float $buffer_multiplier
|
2025-12-31 02:34:30 +01:00
|
|
|
*
|
|
|
|
|
* @method static BucketFactory factory()
|
2025-12-29 23:32:05 +01:00
|
|
|
*/
|
|
|
|
|
class Bucket extends Model
|
|
|
|
|
{
|
|
|
|
|
/** @use HasFactory<BucketFactory> */
|
2026-03-19 20:34:47 +01:00
|
|
|
use HasFactory, HasUuid;
|
2025-12-29 23:32:05 +01:00
|
|
|
|
|
|
|
|
protected $fillable = [
|
|
|
|
|
'scenario_id',
|
|
|
|
|
'name',
|
2026-03-19 21:14:36 +01:00
|
|
|
'type',
|
2025-12-29 23:32:05 +01:00
|
|
|
'priority',
|
|
|
|
|
'sort_order',
|
|
|
|
|
'allocation_type',
|
|
|
|
|
'allocation_value',
|
2026-03-20 00:25:44 +01:00
|
|
|
'buffer_multiplier',
|
2025-12-31 01:48:13 +01:00
|
|
|
'starting_amount',
|
2025-12-29 23:32:05 +01:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
protected $casts = [
|
2026-03-19 21:14:36 +01:00
|
|
|
'type' => BucketTypeEnum::class,
|
2025-12-29 23:32:05 +01:00
|
|
|
'priority' => 'integer',
|
|
|
|
|
'sort_order' => 'integer',
|
2026-03-21 16:54:11 +01:00
|
|
|
'allocation_value' => 'integer',
|
2026-03-20 00:25:44 +01:00
|
|
|
'buffer_multiplier' => 'decimal:2',
|
2025-12-31 01:48:13 +01:00
|
|
|
'starting_amount' => 'integer',
|
2025-12-31 02:34:30 +01:00
|
|
|
'allocation_type' => BucketAllocationTypeEnum::class,
|
2025-12-29 23:32:05 +01:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
public function scenario(): BelongsTo
|
|
|
|
|
{
|
|
|
|
|
return $this->belongsTo(Scenario::class);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the draws for the bucket.
|
|
|
|
|
*/
|
|
|
|
|
public function draws(): HasMany
|
|
|
|
|
{
|
|
|
|
|
return $this->hasMany(Draw::class);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the outflows for the bucket.
|
|
|
|
|
*/
|
|
|
|
|
public function outflows(): HasMany
|
|
|
|
|
{
|
|
|
|
|
return $this->hasMany(Outflow::class);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Scope to get buckets ordered by priority.
|
|
|
|
|
*/
|
|
|
|
|
public function scopeOrderedByPriority($query)
|
|
|
|
|
{
|
|
|
|
|
return $query->orderBy('priority');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Scope to get buckets ordered by sort order for UI display.
|
|
|
|
|
*/
|
|
|
|
|
public function scopeOrderedBySortOrder($query)
|
|
|
|
|
{
|
|
|
|
|
return $query->orderBy('sort_order')->orderBy('priority');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the current balance of the bucket.
|
2025-12-31 01:48:13 +01:00
|
|
|
* Calculates starting amount plus total draws (money allocated to bucket) minus total outflows (money spent from bucket).
|
2025-12-29 23:32:05 +01:00
|
|
|
*/
|
2025-12-31 02:34:30 +01:00
|
|
|
public function getCurrentBalance(): int
|
2025-12-29 23:32:05 +01:00
|
|
|
{
|
2025-12-31 02:34:30 +01:00
|
|
|
$totalDraws = $this->draws()->sum('amount');
|
|
|
|
|
$totalOutflows = $this->outflows()->sum('amount');
|
2025-12-31 01:48:13 +01:00
|
|
|
|
2025-12-31 02:34:30 +01:00
|
|
|
return $this->starting_amount + $totalDraws - $totalOutflows;
|
2025-12-29 23:32:05 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-20 00:25:44 +01:00
|
|
|
/**
|
2026-03-21 16:54:11 +01:00
|
|
|
* Get the effective capacity including buffer, in cents.
|
2026-03-20 00:25:44 +01:00
|
|
|
* Formula: allocation_value * (1 + buffer_multiplier)
|
|
|
|
|
*/
|
2026-03-21 16:54:11 +01:00
|
|
|
public function getEffectiveCapacity(): int
|
2026-03-20 00:25:44 +01:00
|
|
|
{
|
|
|
|
|
if ($this->allocation_type !== BucketAllocationTypeEnum::FIXED_LIMIT) {
|
2026-03-21 16:54:11 +01:00
|
|
|
return PHP_INT_MAX;
|
2026-03-20 00:25:44 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-21 16:54:11 +01:00
|
|
|
$base = $this->allocation_value ?? 0;
|
2026-03-20 00:25:44 +01:00
|
|
|
|
2026-03-21 16:54:11 +01:00
|
|
|
return (int) round($base * (1 + (float) $this->buffer_multiplier));
|
2026-03-20 00:25:44 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-29 23:32:05 +01:00
|
|
|
/**
|
|
|
|
|
* Check if the bucket can accept more money (for fixed_limit buckets).
|
|
|
|
|
*/
|
|
|
|
|
public function hasAvailableSpace(): bool
|
|
|
|
|
{
|
2025-12-31 02:34:30 +01:00
|
|
|
if ($this->allocation_type !== BucketAllocationTypeEnum::FIXED_LIMIT) {
|
2025-12-29 23:32:05 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 00:25:44 +01:00
|
|
|
return $this->getCurrentBalance() < $this->getEffectiveCapacity();
|
2025-12-29 23:32:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-03-21 16:54:11 +01:00
|
|
|
* Get available space in cents for fixed_limit buckets.
|
2025-12-29 23:32:05 +01:00
|
|
|
*/
|
2026-03-21 16:54:11 +01:00
|
|
|
public function getAvailableSpace(): int
|
2025-12-29 23:32:05 +01:00
|
|
|
{
|
2025-12-31 02:34:30 +01:00
|
|
|
if ($this->allocation_type !== BucketAllocationTypeEnum::FIXED_LIMIT) {
|
2026-03-21 16:54:11 +01:00
|
|
|
return PHP_INT_MAX;
|
2025-12-29 23:32:05 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-20 00:25:44 +01:00
|
|
|
return max(0, $this->getEffectiveCapacity() - $this->getCurrentBalance());
|
2025-12-29 23:32:05 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-21 17:26:45 +01:00
|
|
|
/**
|
|
|
|
|
* Whether this bucket has a finite capacity (fixed_limit only).
|
|
|
|
|
*/
|
|
|
|
|
public function hasFiniteCapacity(): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->allocation_type === BucketAllocationTypeEnum::FIXED_LIMIT;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-29 23:32:05 +01:00
|
|
|
/**
|
|
|
|
|
* Get display label for allocation type.
|
|
|
|
|
*/
|
|
|
|
|
public function getAllocationTypeLabel(): string
|
|
|
|
|
{
|
2025-12-31 01:33:44 +01:00
|
|
|
return $this->allocation_type->getLabel();
|
2025-12-29 23:32:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get allocation value validation rules based on type.
|
|
|
|
|
*/
|
2025-12-31 02:34:30 +01:00
|
|
|
public static function allocationValueRules(BucketAllocationTypeEnum $allocationType): array
|
2025-12-29 23:32:05 +01:00
|
|
|
{
|
2025-12-31 01:33:44 +01:00
|
|
|
return $allocationType->getAllocationValueRules();
|
2025-12-29 23:32:05 +01:00
|
|
|
}
|
|
|
|
|
}
|