1 - Add UUID support to all domain models

This commit is contained in:
myrmidex 2026-03-19 20:34:47 +01:00
parent bfa92f8f24
commit 367f255200
14 changed files with 105 additions and 3 deletions

View file

@ -3,6 +3,7 @@
namespace App\Models; namespace App\Models;
use App\Enums\BucketAllocationTypeEnum; use App\Enums\BucketAllocationTypeEnum;
use App\Models\Traits\HasUuid;
use Database\Factories\BucketFactory; use Database\Factories\BucketFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@ -11,6 +12,7 @@
/** /**
* @property int $id * @property int $id
* @property string $uuid
* @property int $scenario_id * @property int $scenario_id
* @property Scenario $scenario * @property Scenario $scenario
* @property string $name * @property string $name
@ -24,7 +26,7 @@
class Bucket extends Model class Bucket extends Model
{ {
/** @use HasFactory<BucketFactory> */ /** @use HasFactory<BucketFactory> */
use HasFactory; use HasFactory, HasUuid;
protected $fillable = [ protected $fillable = [
'scenario_id', 'scenario_id',

View file

@ -4,6 +4,7 @@
use App\Models\Traits\HasAmount; use App\Models\Traits\HasAmount;
use App\Models\Traits\HasProjectionStatus; use App\Models\Traits\HasProjectionStatus;
use App\Models\Traits\HasUuid;
use Carbon\Carbon; use Carbon\Carbon;
use Database\Factories\DrawFactory; use Database\Factories\DrawFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -11,6 +12,7 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
/** /**
* @property string $uuid
* @property Bucket $bucket * @property Bucket $bucket
* @property int $priority_order * @property int $priority_order
* @property float $amount * @property float $amount
@ -27,6 +29,7 @@ class Draw extends Model
/** @use HasFactory<DrawFactory> */ /** @use HasFactory<DrawFactory> */
use HasFactory; use HasFactory;
use HasProjectionStatus; use HasProjectionStatus;
use HasUuid;
protected $fillable = [ protected $fillable = [
'bucket_id', 'bucket_id',

View file

@ -4,12 +4,14 @@
use App\Models\Traits\HasAmount; use App\Models\Traits\HasAmount;
use App\Models\Traits\HasProjectionStatus; use App\Models\Traits\HasProjectionStatus;
use App\Models\Traits\HasUuid;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
/** /**
* @property int $id * @property int $id
* @property string $uuid
* @property int $stream_id * @property int $stream_id
* @property Stream $stream * @property Stream $stream
* @property float $amount * @property float $amount
@ -21,6 +23,7 @@ class Inflow extends Model
{ {
use HasAmount; use HasAmount;
use HasProjectionStatus; use HasProjectionStatus;
use HasUuid;
protected $fillable = [ protected $fillable = [
'stream_id', 'stream_id',

View file

@ -4,6 +4,7 @@
use App\Models\Traits\HasAmount; use App\Models\Traits\HasAmount;
use App\Models\Traits\HasProjectionStatus; use App\Models\Traits\HasProjectionStatus;
use App\Models\Traits\HasUuid;
use Carbon\Carbon; use Carbon\Carbon;
use Database\Factories\OutflowFactory; use Database\Factories\OutflowFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -12,6 +13,7 @@
/** /**
* @property int $id * @property int $id
* @property string $uuid
* @property int $stream_id * @property int $stream_id
* @property Stream $stream * @property Stream $stream
* @property int $amount * @property int $amount
@ -28,6 +30,7 @@ class Outflow extends Model
/** @use HasFactory<OutflowFactory> */ /** @use HasFactory<OutflowFactory> */
use HasFactory; use HasFactory;
use HasProjectionStatus; use HasProjectionStatus;
use HasUuid;
protected $fillable = [ protected $fillable = [
'stream_id', 'stream_id',

View file

@ -2,6 +2,7 @@
namespace App\Models; namespace App\Models;
use App\Models\Traits\HasUuid;
use Database\Factories\ScenarioFactory; use Database\Factories\ScenarioFactory;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -10,6 +11,7 @@
/** /**
* @property int $id * @property int $id
* @property string $uuid
* @property Collection<Bucket> $buckets * @property Collection<Bucket> $buckets
* *
* @method static create(array $data) * @method static create(array $data)
@ -17,7 +19,7 @@
class Scenario extends Model class Scenario extends Model
{ {
/** @use HasFactory<ScenarioFactory> */ /** @use HasFactory<ScenarioFactory> */
use HasFactory; use HasFactory, HasUuid;
protected $fillable = [ protected $fillable = [
'name', 'name',

View file

@ -5,6 +5,7 @@
use App\Enums\StreamFrequencyEnum; use App\Enums\StreamFrequencyEnum;
use App\Enums\StreamTypeEnum; use App\Enums\StreamTypeEnum;
use App\Models\Traits\HasAmount; use App\Models\Traits\HasAmount;
use App\Models\Traits\HasUuid;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@ -12,12 +13,13 @@
/** /**
* @property int $amount * @property int $amount
* @property string $uuid
* @property StreamFrequencyEnum $frequency * @property StreamFrequencyEnum $frequency
* @property Carbon $start_date * @property Carbon $start_date
*/ */
class Stream extends Model class Stream extends Model
{ {
use HasAmount, HasFactory; use HasAmount, HasFactory, HasUuid;
protected $fillable = [ protected $fillable = [
'scenario_id', 'scenario_id',

View file

@ -0,0 +1,22 @@
<?php
namespace App\Models\Traits;
use Illuminate\Support\Str;
trait HasUuid
{
public static function bootHasUuid(): void
{
static::creating(function ($model) {
if (empty($model->uuid)) {
$model->uuid = (string) Str::orderedUuid();
}
});
}
public function getRouteKeyName(): string
{
return 'uuid';
}
}

View file

@ -10,6 +10,7 @@ public function up(): void
{ {
Schema::create('scenarios', function (Blueprint $table) { Schema::create('scenarios', function (Blueprint $table) {
$table->id(); $table->id();
$table->uuid('uuid')->unique();
$table->string('name'); $table->string('name');
$table->text('description')->nullable(); $table->text('description')->nullable();
$table->timestamps(); $table->timestamps();

View file

@ -10,6 +10,7 @@ public function up(): void
{ {
Schema::create('buckets', function (Blueprint $table) { Schema::create('buckets', function (Blueprint $table) {
$table->id(); $table->id();
$table->uuid('uuid')->unique();
$table->foreignId('scenario_id')->constrained()->onDelete('cascade'); $table->foreignId('scenario_id')->constrained()->onDelete('cascade');
$table->string('name'); $table->string('name');
$table->integer('priority')->comment('Lower number = higher priority, 1 = first'); $table->integer('priority')->comment('Lower number = higher priority, 1 = first');

View file

@ -12,6 +12,7 @@ public function up(): void
{ {
Schema::create('streams', function (Blueprint $table) { Schema::create('streams', function (Blueprint $table) {
$table->id(); $table->id();
$table->uuid('uuid')->unique();
$table->foreignId('scenario_id')->constrained()->cascadeOnDelete(); $table->foreignId('scenario_id')->constrained()->cascadeOnDelete();
$table->foreignId('bucket_id')->nullable()->constrained()->nullOnDelete(); $table->foreignId('bucket_id')->nullable()->constrained()->nullOnDelete();
$table->string('name'); $table->string('name');

View file

@ -10,6 +10,7 @@ public function up(): void
{ {
Schema::create('inflows', function (Blueprint $table) { Schema::create('inflows', function (Blueprint $table) {
$table->id(); $table->id();
$table->uuid('uuid')->unique();
$table->foreignId('stream_id')->nullable()->constrained()->onDelete('set null'); $table->foreignId('stream_id')->nullable()->constrained()->onDelete('set null');
$table->unsignedBigInteger('amount'); $table->unsignedBigInteger('amount');
$table->date('date'); $table->date('date');

View file

@ -10,6 +10,7 @@ public function up(): void
{ {
Schema::create('outflows', function (Blueprint $table) { Schema::create('outflows', function (Blueprint $table) {
$table->id(); $table->id();
$table->uuid('uuid')->unique();
$table->foreignId('stream_id')->nullable()->constrained()->onDelete('set null'); $table->foreignId('stream_id')->nullable()->constrained()->onDelete('set null');
$table->foreignId('bucket_id')->nullable()->constrained()->onDelete('set null'); $table->foreignId('bucket_id')->nullable()->constrained()->onDelete('set null');
$table->unsignedBigInteger('amount'); $table->unsignedBigInteger('amount');

View file

@ -10,6 +10,7 @@ public function up(): void
{ {
Schema::create('draws', function (Blueprint $table) { Schema::create('draws', function (Blueprint $table) {
$table->id(); $table->id();
$table->uuid('uuid')->unique();
$table->foreignId('bucket_id')->constrained()->onDelete('cascade'); $table->foreignId('bucket_id')->constrained()->onDelete('cascade');
$table->unsignedBigInteger('amount'); $table->unsignedBigInteger('amount');
$table->date('date'); $table->date('date');

View file

@ -0,0 +1,59 @@
<?php
namespace Tests\Unit\Traits;
use App\Models\Bucket;
use App\Models\Scenario;
use App\Models\Stream;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Str;
use Tests\TestCase;
class HasUuidTest extends TestCase
{
use RefreshDatabase;
public function test_uuid_is_auto_generated_on_creation(): void
{
$scenario = Scenario::factory()->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));
}
}