diff --git a/app/Models/Bucket.php b/app/Models/Bucket.php index 5c8bac4..08ee63d 100644 --- a/app/Models/Bucket.php +++ b/app/Models/Bucket.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Enums\BucketAllocationTypeEnum; +use App\Models\Traits\HasUuid; use Database\Factories\BucketFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -11,6 +12,7 @@ /** * @property int $id + * @property string $uuid * @property int $scenario_id * @property Scenario $scenario * @property string $name @@ -24,7 +26,7 @@ class Bucket extends Model { /** @use HasFactory */ - use HasFactory; + use HasFactory, HasUuid; protected $fillable = [ 'scenario_id', diff --git a/app/Models/Draw.php b/app/Models/Draw.php index a262d08..8226329 100644 --- a/app/Models/Draw.php +++ b/app/Models/Draw.php @@ -4,6 +4,7 @@ use App\Models\Traits\HasAmount; use App\Models\Traits\HasProjectionStatus; +use App\Models\Traits\HasUuid; use Carbon\Carbon; use Database\Factories\DrawFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -11,6 +12,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; /** + * @property string $uuid * @property Bucket $bucket * @property int $priority_order * @property float $amount @@ -27,6 +29,7 @@ class Draw extends Model /** @use HasFactory */ use HasFactory; use HasProjectionStatus; + use HasUuid; protected $fillable = [ 'bucket_id', diff --git a/app/Models/Inflow.php b/app/Models/Inflow.php index 821f0c9..6f77e88 100644 --- a/app/Models/Inflow.php +++ b/app/Models/Inflow.php @@ -4,12 +4,14 @@ use App\Models\Traits\HasAmount; use App\Models\Traits\HasProjectionStatus; +use App\Models\Traits\HasUuid; use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * @property int $id + * @property string $uuid * @property int $stream_id * @property Stream $stream * @property float $amount @@ -21,6 +23,7 @@ class Inflow extends Model { use HasAmount; use HasProjectionStatus; + use HasUuid; protected $fillable = [ 'stream_id', diff --git a/app/Models/Outflow.php b/app/Models/Outflow.php index 40b883c..c6def8f 100644 --- a/app/Models/Outflow.php +++ b/app/Models/Outflow.php @@ -4,6 +4,7 @@ use App\Models\Traits\HasAmount; use App\Models\Traits\HasProjectionStatus; +use App\Models\Traits\HasUuid; use Carbon\Carbon; use Database\Factories\OutflowFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -12,6 +13,7 @@ /** * @property int $id + * @property string $uuid * @property int $stream_id * @property Stream $stream * @property int $amount @@ -28,6 +30,7 @@ class Outflow extends Model /** @use HasFactory */ use HasFactory; use HasProjectionStatus; + use HasUuid; protected $fillable = [ 'stream_id', diff --git a/app/Models/Scenario.php b/app/Models/Scenario.php index ae59193..e40ba3f 100644 --- a/app/Models/Scenario.php +++ b/app/Models/Scenario.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Models\Traits\HasUuid; use Database\Factories\ScenarioFactory; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -10,6 +11,7 @@ /** * @property int $id + * @property string $uuid * @property Collection $buckets * * @method static create(array $data) @@ -17,7 +19,7 @@ class Scenario extends Model { /** @use HasFactory */ - use HasFactory; + use HasFactory, HasUuid; protected $fillable = [ 'name', diff --git a/app/Models/Stream.php b/app/Models/Stream.php index 4d8fab0..0a4a954 100644 --- a/app/Models/Stream.php +++ b/app/Models/Stream.php @@ -5,6 +5,7 @@ use App\Enums\StreamFrequencyEnum; use App\Enums\StreamTypeEnum; use App\Models\Traits\HasAmount; +use App\Models\Traits\HasUuid; use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -12,12 +13,13 @@ /** * @property int $amount + * @property string $uuid * @property StreamFrequencyEnum $frequency * @property Carbon $start_date */ class Stream extends Model { - use HasAmount, HasFactory; + use HasAmount, HasFactory, HasUuid; protected $fillable = [ 'scenario_id', diff --git a/app/Models/Traits/HasUuid.php b/app/Models/Traits/HasUuid.php new file mode 100644 index 0000000..b427ce8 --- /dev/null +++ b/app/Models/Traits/HasUuid.php @@ -0,0 +1,22 @@ +uuid)) { + $model->uuid = (string) Str::orderedUuid(); + } + }); + } + + public function getRouteKeyName(): string + { + return 'uuid'; + } +} diff --git a/database/migrations/2025_12_29_192203_create_scenarios_table.php b/database/migrations/2025_12_29_192203_create_scenarios_table.php index 2f1fdbe..f535fdf 100644 --- a/database/migrations/2025_12_29_192203_create_scenarios_table.php +++ b/database/migrations/2025_12_29_192203_create_scenarios_table.php @@ -10,6 +10,7 @@ public function up(): void { Schema::create('scenarios', function (Blueprint $table) { $table->id(); + $table->uuid('uuid')->unique(); $table->string('name'); $table->text('description')->nullable(); $table->timestamps(); diff --git a/database/migrations/2025_12_29_205724_create_buckets_table.php b/database/migrations/2025_12_29_205724_create_buckets_table.php index b1dd390..159c7f5 100644 --- a/database/migrations/2025_12_29_205724_create_buckets_table.php +++ b/database/migrations/2025_12_29_205724_create_buckets_table.php @@ -10,6 +10,7 @@ public function up(): void { Schema::create('buckets', function (Blueprint $table) { $table->id(); + $table->uuid('uuid')->unique(); $table->foreignId('scenario_id')->constrained()->onDelete('cascade'); $table->string('name'); $table->integer('priority')->comment('Lower number = higher priority, 1 = first'); diff --git a/database/migrations/2025_12_30_202750_create_streams_table.php b/database/migrations/2025_12_30_202750_create_streams_table.php index 95e8afc..dd3e185 100644 --- a/database/migrations/2025_12_30_202750_create_streams_table.php +++ b/database/migrations/2025_12_30_202750_create_streams_table.php @@ -12,6 +12,7 @@ public function up(): void { Schema::create('streams', function (Blueprint $table) { $table->id(); + $table->uuid('uuid')->unique(); $table->foreignId('scenario_id')->constrained()->cascadeOnDelete(); $table->foreignId('bucket_id')->nullable()->constrained()->nullOnDelete(); $table->string('name'); diff --git a/database/migrations/2025_12_30_234435_create_inflows_table.php b/database/migrations/2025_12_30_234435_create_inflows_table.php index 1a55692..e4e25b2 100644 --- a/database/migrations/2025_12_30_234435_create_inflows_table.php +++ b/database/migrations/2025_12_30_234435_create_inflows_table.php @@ -10,6 +10,7 @@ public function up(): void { Schema::create('inflows', function (Blueprint $table) { $table->id(); + $table->uuid('uuid')->unique(); $table->foreignId('stream_id')->nullable()->constrained()->onDelete('set null'); $table->unsignedBigInteger('amount'); $table->date('date'); diff --git a/database/migrations/2025_12_30_234514_create_outflows_table.php b/database/migrations/2025_12_30_234514_create_outflows_table.php index f9757b6..87dd217 100644 --- a/database/migrations/2025_12_30_234514_create_outflows_table.php +++ b/database/migrations/2025_12_30_234514_create_outflows_table.php @@ -10,6 +10,7 @@ public function up(): void { Schema::create('outflows', function (Blueprint $table) { $table->id(); + $table->uuid('uuid')->unique(); $table->foreignId('stream_id')->nullable()->constrained()->onDelete('set null'); $table->foreignId('bucket_id')->nullable()->constrained()->onDelete('set null'); $table->unsignedBigInteger('amount'); diff --git a/database/migrations/2025_12_30_234548_create_draws_table.php b/database/migrations/2025_12_30_234548_create_draws_table.php index 5835f47..0a44ed2 100644 --- a/database/migrations/2025_12_30_234548_create_draws_table.php +++ b/database/migrations/2025_12_30_234548_create_draws_table.php @@ -10,6 +10,7 @@ public function up(): void { Schema::create('draws', function (Blueprint $table) { $table->id(); + $table->uuid('uuid')->unique(); $table->foreignId('bucket_id')->constrained()->onDelete('cascade'); $table->unsignedBigInteger('amount'); $table->date('date'); diff --git a/tests/Unit/Traits/HasUuidTest.php b/tests/Unit/Traits/HasUuidTest.php new file mode 100644 index 0000000..24e9f04 --- /dev/null +++ b/tests/Unit/Traits/HasUuidTest.php @@ -0,0 +1,59 @@ +create(); + + $this->assertNotNull($scenario->uuid); + $this->assertTrue(Str::isUuid($scenario->uuid)); + } + + public function test_uuid_is_not_overwritten_when_provided(): void + { + $customUuid = (string) Str::uuid(); + + $scenario = Scenario::factory()->create(['uuid' => $customUuid]); + + $this->assertEquals($customUuid, $scenario->uuid); + } + + public function test_route_key_name_returns_uuid(): void + { + $scenario = Scenario::factory()->create(); + + $this->assertEquals('uuid', $scenario->getRouteKeyName()); + } + + public function test_each_model_gets_a_unique_uuid(): void + { + $a = Scenario::factory()->create(); + $b = Scenario::factory()->create(); + + $this->assertNotEquals($a->uuid, $b->uuid); + } + + public function test_all_uuid_models_have_correct_route_key_name(): void + { + $scenario = Scenario::factory()->create(); + $bucket = Bucket::factory()->create(['scenario_id' => $scenario->id]); + $stream = Stream::factory()->create(['scenario_id' => $scenario->id]); + + $this->assertEquals('uuid', $bucket->getRouteKeyName()); + $this->assertEquals('uuid', $stream->getRouteKeyName()); + $this->assertTrue(Str::isUuid($bucket->uuid)); + $this->assertTrue(Str::isUuid($stream->uuid)); + } +}