diff --git a/app/Domains/Catalog/Requests/StoreBrandRequest.php b/app/Domains/Catalog/Requests/StoreBrandRequest.php index e3beb68..cb151c7 100644 --- a/app/Domains/Catalog/Requests/StoreBrandRequest.php +++ b/app/Domains/Catalog/Requests/StoreBrandRequest.php @@ -2,6 +2,7 @@ namespace App\Domains\Catalog\Requests; +use App\Domains\Catalog\Models\Brand; use App\Domains\Prop\Support\PropRules; use Illuminate\Foundation\Http\FormRequest; @@ -21,7 +22,7 @@ class StoreBrandRequest extends FormRequest 'tenant_codigo' => ['required', 'string', 'exists:tenants,codigo'], 'nombre' => ['required', 'string', 'max:255'], 'descripcion' => ['nullable', 'string'], - ...PropRules::sync(), + ...PropRules::sync(Brand::class), ]; } } diff --git a/app/Domains/Catalog/Requests/StoreCategoryRequest.php b/app/Domains/Catalog/Requests/StoreCategoryRequest.php index e65b9f5..08e75ee 100644 --- a/app/Domains/Catalog/Requests/StoreCategoryRequest.php +++ b/app/Domains/Catalog/Requests/StoreCategoryRequest.php @@ -2,6 +2,7 @@ namespace App\Domains\Catalog\Requests; +use App\Domains\Catalog\Models\Category; use App\Domains\Prop\Support\PropRules; use Illuminate\Foundation\Http\FormRequest; @@ -21,7 +22,7 @@ class StoreCategoryRequest extends FormRequest 'tenant_code' => ['required_with:categoria_id', 'string', 'exists:tenants,codigo'], 'categoria_id' => ['nullable', 'integer', 'exists:categorias,id'], 'nombre' => ['required', 'string', 'max:255'], - ...PropRules::sync(), + ...PropRules::sync(Category::class), ]; } } diff --git a/app/Domains/Catalog/Requests/StoreProductRequest.php b/app/Domains/Catalog/Requests/StoreProductRequest.php index 2cec566..781f75e 100644 --- a/app/Domains/Catalog/Requests/StoreProductRequest.php +++ b/app/Domains/Catalog/Requests/StoreProductRequest.php @@ -2,6 +2,7 @@ namespace App\Domains\Catalog\Requests; +use App\Domains\Catalog\Models\Product; use App\Domains\Prop\Support\PropRules; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; @@ -25,7 +26,7 @@ class StoreProductRequest extends FormRequest 'nombre' => ['required', 'string', 'max:255'], 'descripcion' => ['nullable', 'string'], 'precio' => ['required', 'numeric', 'min:0'], - ...PropRules::sync(), + ...PropRules::sync(Product::class), ]; } } diff --git a/app/Domains/Catalog/Requests/UpdateBrandRequest.php b/app/Domains/Catalog/Requests/UpdateBrandRequest.php index eaa7b6d..525c6ac 100644 --- a/app/Domains/Catalog/Requests/UpdateBrandRequest.php +++ b/app/Domains/Catalog/Requests/UpdateBrandRequest.php @@ -2,6 +2,7 @@ namespace App\Domains\Catalog\Requests; +use App\Domains\Catalog\Models\Brand; use App\Domains\Prop\Support\PropRules; use Illuminate\Foundation\Http\FormRequest; @@ -21,7 +22,7 @@ class UpdateBrandRequest extends FormRequest 'tenant_codigo' => ['required', 'string', 'exists:tenants,codigo'], 'nombre' => ['required', 'string', 'max:255'], 'descripcion' => ['nullable', 'string'], - ...PropRules::sync(), + ...PropRules::sync(Brand::class), ]; } } diff --git a/app/Domains/Catalog/Requests/UpdateCategoryRequest.php b/app/Domains/Catalog/Requests/UpdateCategoryRequest.php index e485a3e..e27f92e 100644 --- a/app/Domains/Catalog/Requests/UpdateCategoryRequest.php +++ b/app/Domains/Catalog/Requests/UpdateCategoryRequest.php @@ -31,7 +31,7 @@ class UpdateCategoryRequest extends FormRequest Rule::notIn([$category?->id]), ], 'nombre' => ['required', 'string', 'max:255'], - ...PropRules::sync(), + ...PropRules::sync(Category::class), ]; } } diff --git a/app/Domains/Catalog/Requests/UpdateProductRequest.php b/app/Domains/Catalog/Requests/UpdateProductRequest.php index 02479dd..ef516e0 100644 --- a/app/Domains/Catalog/Requests/UpdateProductRequest.php +++ b/app/Domains/Catalog/Requests/UpdateProductRequest.php @@ -34,7 +34,7 @@ class UpdateProductRequest extends FormRequest 'nombre' => ['required', 'string', 'max:255'], 'descripcion' => ['nullable', 'string'], 'precio' => ['required', 'numeric', 'min:0'], - ...PropRules::sync(), + ...PropRules::sync(Product::class), ]; } } diff --git a/app/Domains/Prop/Support/PropRules.php b/app/Domains/Prop/Support/PropRules.php index 48b89ff..f3c2d9d 100644 --- a/app/Domains/Prop/Support/PropRules.php +++ b/app/Domains/Prop/Support/PropRules.php @@ -2,16 +2,59 @@ namespace App\Domains\Prop\Support; +use App\Domains\Prop\Models\Prop; +use Closure; +use Illuminate\Database\Eloquent\Model; + class PropRules { /** * @return array */ - public static function sync(): array + public static function sync(string $modelClass): array { return [ - 'props' => ['sometimes', 'array'], + 'props' => [ + 'sometimes', + 'array', + static function (string $attribute, mixed $value, Closure $fail) use ($modelClass): void { + static::validatePropsForModel($attribute, $value, $fail, $modelClass); + }, + ], 'props.*' => ['nullable'], ]; } + + protected static function validatePropsForModel(string $attribute, mixed $value, Closure $fail, string $modelClass): void + { + if (! is_array($value) || $value === []) { + return; + } + + if (! is_subclass_of($modelClass, Model::class)) { + $fail("The {$attribute} field references an invalid propable model."); + + return; + } + + $propCodes = array_map('strval', array_keys($value)); + $existingPropCodes = Prop::query() + ->forModel($modelClass) + ->whereIn('codigo', $propCodes) + ->pluck('codigo') + ->all(); + + $missingPropCodes = array_values(array_diff($propCodes, $existingPropCodes)); + + if ($missingPropCodes === []) { + return; + } + + $fail(sprintf( + 'The selected %s are invalid for %s: %s.', + $attribute, + class_basename($modelClass), + implode(', ', $missingPropCodes), + )); + } } diff --git a/app/Domains/Tenant/Controllers/TenantController.php b/app/Domains/Tenant/Controllers/TenantController.php index 4901449..26f644c 100644 --- a/app/Domains/Tenant/Controllers/TenantController.php +++ b/app/Domains/Tenant/Controllers/TenantController.php @@ -21,7 +21,6 @@ class TenantController extends Controller { $validated = $request->validated(); $props = $validated['props'] ?? []; - unset($validated['props']); $tenant = Tenant::createWithProps($validated, $props); diff --git a/app/Domains/Tenant/Requests/StoreTenantRequest.php b/app/Domains/Tenant/Requests/StoreTenantRequest.php index 60d0623..59ab351 100644 --- a/app/Domains/Tenant/Requests/StoreTenantRequest.php +++ b/app/Domains/Tenant/Requests/StoreTenantRequest.php @@ -3,6 +3,7 @@ namespace App\Domains\Tenant\Requests; use App\Domains\Prop\Support\PropRules; +use App\Domains\Tenant\Models\Tenant; use App\Domains\Tenant\Support\TenantDomainNormalizer; use Closure; use Illuminate\Foundation\Http\FormRequest; @@ -50,7 +51,7 @@ class StoreTenantRequest extends FormRequest 'max:255', Rule::unique('tenants', 'dominio'), ], - ...PropRules::sync(), + ...PropRules::sync(Tenant::class), ]; } } diff --git a/app/Domains/Tenant/Requests/UpdateTenantRequest.php b/app/Domains/Tenant/Requests/UpdateTenantRequest.php index 7246ca2..94e5a2e 100644 --- a/app/Domains/Tenant/Requests/UpdateTenantRequest.php +++ b/app/Domains/Tenant/Requests/UpdateTenantRequest.php @@ -59,7 +59,7 @@ class UpdateTenantRequest extends FormRequest 'max:255', Rule::unique('tenants', 'dominio')->ignore($tenant?->id), ], - ...PropRules::sync(), + ...PropRules::sync(Tenant::class), ]; } } diff --git a/database/migrations/2026_06_24_000000_create_categorias_table.php b/database/migrations/2026_06_24_000000_create_categorias_table.php new file mode 100644 index 0000000..ccdeb4e --- /dev/null +++ b/database/migrations/2026_06_24_000000_create_categorias_table.php @@ -0,0 +1,42 @@ +id(); + $table->string('tenant_code')->nullable(); + $table->unsignedBigInteger('categoria_id')->nullable(); + $table->string('nombre'); + $table->timestamps(); + + $table->foreign('tenant_code') + ->references('codigo') + ->on('tenants') + ->cascadeOnUpdate() + ->restrictOnDelete(); + + $table->foreign('categoria_id') + ->references('id') + ->on('categorias') + ->cascadeOnUpdate() + ->restrictOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('categorias'); + } +}; diff --git a/database/migrations/2026_06_24_000100_create_brands_table.php b/database/migrations/2026_06_24_000100_create_brands_table.php new file mode 100644 index 0000000..2886bb3 --- /dev/null +++ b/database/migrations/2026_06_24_000100_create_brands_table.php @@ -0,0 +1,36 @@ +id(); + $table->string('tenant_codigo'); + $table->string('nombre'); + $table->text('descripcion')->nullable(); + $table->timestamps(); + + $table->foreign('tenant_codigo') + ->references('codigo') + ->on('tenants') + ->cascadeOnUpdate() + ->restrictOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('brands'); + } +};