Refactor Prop model to use enum for data types; remove PropDataType model and update related requests and migrations
This commit is contained in:
@@ -2,11 +2,11 @@
|
||||
|
||||
namespace App\Domains\Prop\Models;
|
||||
|
||||
use App\Domains\Prop\Support\PropDataType;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
#[Fillable([
|
||||
@@ -14,12 +14,23 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
'codigo',
|
||||
'nombre',
|
||||
'is_required',
|
||||
'data_type_id',
|
||||
'data_type',
|
||||
])]
|
||||
class Prop extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'is_required' => 'boolean',
|
||||
'data_type' => PropDataType::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Builder<self> $query
|
||||
*/
|
||||
@@ -28,14 +39,6 @@ class Prop extends Model
|
||||
$query->where('propable_type', is_string($model) ? $model : $model::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo<PropDataType, $this>
|
||||
*/
|
||||
public function dataType(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(PropDataType::class, 'data_type_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany<PropOption, $this>
|
||||
*/
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Prop\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
#[Fillable([
|
||||
'name',
|
||||
'code',
|
||||
])]
|
||||
class PropDataType extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* @return HasMany<Prop, $this>
|
||||
*/
|
||||
public function props(): HasMany
|
||||
{
|
||||
return $this->hasMany(Prop::class, 'data_type_id');
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Domains\Prop\Requests;
|
||||
|
||||
use App\Domains\Prop\Support\PropDataType;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
@@ -21,7 +22,7 @@ class StorePropRequest extends FormRequest
|
||||
'codigo' => ['required', 'string', 'max:255', Rule::unique('props', 'codigo')],
|
||||
'nombre' => ['required', 'string', 'max:255'],
|
||||
'is_required' => ['sometimes', 'boolean'],
|
||||
'data_type_id' => ['required', 'integer', 'exists:props_data_types,id'],
|
||||
'data_type' => ['required', Rule::enum(PropDataType::class)],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Domains\Prop\Requests;
|
||||
|
||||
use App\Domains\Prop\Models\Prop;
|
||||
use App\Domains\Prop\Support\PropDataType;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
@@ -25,7 +26,7 @@ class UpdatePropRequest extends FormRequest
|
||||
'codigo' => ['required', 'string', 'max:255', Rule::unique('props', 'codigo')->ignore($prop?->id)],
|
||||
'nombre' => ['required', 'string', 'max:255'],
|
||||
'is_required' => ['sometimes', 'boolean'],
|
||||
'data_type_id' => ['required', 'integer', 'exists:props_data_types,id'],
|
||||
'data_type' => ['required', Rule::enum(PropDataType::class)],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
namespace App\Domains\Prop\Resources;
|
||||
|
||||
use App\Domains\Prop\Models\Prop;
|
||||
use App\Domains\Prop\Support\PropableModels;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/**
|
||||
* @mixin \App\Domains\Prop\Models\Prop
|
||||
* @mixin Prop
|
||||
*/
|
||||
class PropResource extends JsonResource
|
||||
{
|
||||
@@ -17,11 +19,11 @@ class PropResource extends JsonResource
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'propable_type' => $this->propable_type,
|
||||
'propable_type' => PropableModels::aliasFor($this->propable_type) ?? $this->propable_type,
|
||||
'codigo' => $this->codigo,
|
||||
'nombre' => $this->nombre,
|
||||
'is_required' => $this->is_required,
|
||||
'data_type_id' => $this->data_type_id,
|
||||
'data_type' => $this->data_type?->value,
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at,
|
||||
];
|
||||
|
||||
26
app/Domains/Prop/Support/PropDataType.php
Normal file
26
app/Domains/Prop/Support/PropDataType.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Prop\Support;
|
||||
|
||||
enum PropDataType: string
|
||||
{
|
||||
case String = 'string';
|
||||
case Integer = 'integer';
|
||||
case Decimal = 'decimal';
|
||||
case Boolean = 'boolean';
|
||||
case Date = 'date';
|
||||
case DateTime = 'datetime';
|
||||
case Json = 'json';
|
||||
case Select = 'select';
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function values(): array
|
||||
{
|
||||
return array_map(
|
||||
static fn (self $type): string => $type->value,
|
||||
self::cases(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -39,4 +39,9 @@ class PropableModels
|
||||
|
||||
return $modelClass;
|
||||
}
|
||||
|
||||
public static function aliasFor(string $modelClass): ?string
|
||||
{
|
||||
return array_search($modelClass, static::map(), true) ?: null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('props_data_types', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('code')->unique();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('props_data_types');
|
||||
}
|
||||
};
|
||||
@@ -17,7 +17,7 @@ return new class extends Migration
|
||||
$table->string('codigo')->unique();
|
||||
$table->string('nombre');
|
||||
$table->boolean('is_required')->default(false);
|
||||
$table->foreignId('data_type_id')->constrained('props_data_types')->cascadeOnUpdate()->restrictOnDelete();
|
||||
$table->string('data_type');
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('propable_type');
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Prop\Support\PropDataType;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasTable('props')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Schema::hasColumn('props', 'data_type_id')) {
|
||||
Schema::table('props', function (Blueprint $table) {
|
||||
$table->string('data_type')->nullable()->after('is_required');
|
||||
});
|
||||
|
||||
if (Schema::hasTable('props_data_types')) {
|
||||
$typeMap = DB::table('props_data_types')
|
||||
->pluck('code', 'id')
|
||||
->map(static fn (mixed $code): string => in_array($code, PropDataType::values(), true) ? (string) $code : PropDataType::String->value)
|
||||
->all();
|
||||
|
||||
DB::table('props')
|
||||
->select(['id', 'data_type_id'])
|
||||
->orderBy('id')
|
||||
->get()
|
||||
->each(function (object $prop) use ($typeMap): void {
|
||||
DB::table('props')
|
||||
->where('id', $prop->id)
|
||||
->update([
|
||||
'data_type' => $typeMap[$prop->data_type_id] ?? PropDataType::String->value,
|
||||
]);
|
||||
});
|
||||
} else {
|
||||
DB::table('props')->update([
|
||||
'data_type' => PropDataType::String->value,
|
||||
]);
|
||||
}
|
||||
|
||||
Schema::table('props', function (Blueprint $table) {
|
||||
$table->dropForeign(['data_type_id']);
|
||||
$table->dropColumn('data_type_id');
|
||||
});
|
||||
|
||||
Schema::table('props', function (Blueprint $table) {
|
||||
$table->string('data_type')->nullable(false)->change();
|
||||
});
|
||||
}
|
||||
|
||||
if (Schema::hasTable('props_data_types')) {
|
||||
Schema::drop('props_data_types');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
if (! Schema::hasTable('props_data_types')) {
|
||||
Schema::create('props_data_types', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('code')->unique();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
DB::table('props_data_types')->insert([
|
||||
[
|
||||
'id' => 1,
|
||||
'name' => 'String',
|
||||
'code' => PropDataType::String->value,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
if (Schema::hasTable('props') && Schema::hasColumn('props', 'data_type') && ! Schema::hasColumn('props', 'data_type_id')) {
|
||||
Schema::table('props', function (Blueprint $table) {
|
||||
$table->foreignId('data_type_id')->nullable()->after('is_required')->constrained('props_data_types')->cascadeOnUpdate()->restrictOnDelete();
|
||||
});
|
||||
|
||||
$stringTypeId = DB::table('props_data_types')
|
||||
->where('code', PropDataType::String->value)
|
||||
->value('id');
|
||||
|
||||
DB::table('props')->update([
|
||||
'data_type_id' => $stringTypeId,
|
||||
]);
|
||||
|
||||
Schema::table('props', function (Blueprint $table) {
|
||||
$table->foreignId('data_type_id')->nullable(false)->change();
|
||||
$table->dropColumn('data_type');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
249
docs/postman/tenant-prop-management.postman_collection.json
Normal file
249
docs/postman/tenant-prop-management.postman_collection.json
Normal file
@@ -0,0 +1,249 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "b0b0df2c-4e1e-4ef4-b6a8-1c5348d5b6f2",
|
||||
"name": "Shopit Back - Tenants and Props",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"variable": [
|
||||
{
|
||||
"key": "base_url",
|
||||
"value": "http://127.0.0.1:8000/api"
|
||||
},
|
||||
{
|
||||
"key": "tenant_id",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"key": "prop_id",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"key": "propable_type",
|
||||
"value": "tenant"
|
||||
}
|
||||
],
|
||||
"item": [
|
||||
{
|
||||
"name": "Tenants",
|
||||
"item": [
|
||||
{
|
||||
"name": "List Tenants",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/tenants",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"tenants"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Create Tenant",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"codigo\": \"tenant-demo\",\n \"nombre\": \"Tenant Demo\",\n \"dominio\": \"tenant-demo.local\",\n \"props\": {\n \"color_primario\": \"#ff6600\",\n \"pais\": \"AR\"\n }\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/tenants",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"tenants"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Show Tenant",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/tenants/{{tenant_id}}",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"tenants",
|
||||
"{{tenant_id}}"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Update Tenant",
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"codigo\": \"tenant-demo\",\n \"nombre\": \"Tenant Demo Updated\",\n \"dominio\": \"tenant-demo.example.com\",\n \"props\": {\n \"color_primario\": \"#0055ff\",\n \"pais\": \"UY\"\n }\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/tenants/{{tenant_id}}",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"tenants",
|
||||
"{{tenant_id}}"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Delete Tenant",
|
||||
"request": {
|
||||
"method": "DELETE",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/tenants/{{tenant_id}}",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"tenants",
|
||||
"{{tenant_id}}"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Props",
|
||||
"item": [
|
||||
{
|
||||
"name": "List Props By Model",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/prop-models/{{propable_type}}/props",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"prop-models",
|
||||
"{{propable_type}}",
|
||||
"props"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Create Prop For Model",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"codigo\": \"color_primario\",\n \"nombre\": \"Color primario\",\n \"is_required\": false,\n \"data_type\": \"string\"\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/prop-models/{{propable_type}}/props",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"prop-models",
|
||||
"{{propable_type}}",
|
||||
"props"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Show Prop",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/prop-models/{{propable_type}}/props/{{prop_id}}",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"prop-models",
|
||||
"{{propable_type}}",
|
||||
"props",
|
||||
"{{prop_id}}"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Update Prop",
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"codigo\": \"color_primario\",\n \"nombre\": \"Color primario actualizado\",\n \"is_required\": true,\n \"data_type\": \"string\"\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/prop-models/{{propable_type}}/props/{{prop_id}}",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"prop-models",
|
||||
"{{propable_type}}",
|
||||
"props",
|
||||
"{{prop_id}}"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Delete Prop",
|
||||
"request": {
|
||||
"method": "DELETE",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/prop-models/{{propable_type}}/props/{{prop_id}}",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"prop-models",
|
||||
"{{propable_type}}",
|
||||
"props",
|
||||
"{{prop_id}}"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
45
tests/Feature/Prop/PropControllerTest.php
Normal file
45
tests/Feature/Prop/PropControllerTest.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Prop;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class PropControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_it_creates_a_prop_using_enum_data_type(): void
|
||||
{
|
||||
$response = $this->postJson('/api/prop-models/tenant/props', [
|
||||
'codigo' => 'color_primario',
|
||||
'nombre' => 'Color primario',
|
||||
'is_required' => false,
|
||||
'data_type' => 'string',
|
||||
]);
|
||||
|
||||
$response
|
||||
->assertCreated()
|
||||
->assertJsonPath('propable_type', 'tenant')
|
||||
->assertJsonPath('data_type', 'string');
|
||||
|
||||
$this->assertDatabaseHas('props', [
|
||||
'codigo' => 'color_primario',
|
||||
'data_type' => 'string',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_rejects_unknown_enum_data_type(): void
|
||||
{
|
||||
$response = $this->postJson('/api/prop-models/tenant/props', [
|
||||
'codigo' => 'color_primario',
|
||||
'nombre' => 'Color primario',
|
||||
'is_required' => false,
|
||||
'data_type' => 'unsupported-type',
|
||||
]);
|
||||
|
||||
$response
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['data_type']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user