diff --git a/app/Http/Controllers/Admin/CategoryController.php b/app/Http/Controllers/Admin/CategoryController.php index de4dd61..cb8ebb8 100644 --- a/app/Http/Controllers/Admin/CategoryController.php +++ b/app/Http/Controllers/Admin/CategoryController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; use App\Models\Category; +use App\Services\CategoryService; use Illuminate\Http\Request; use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; @@ -17,12 +18,17 @@ class CategoryController extends Controller $query->where('active', (int) $request->active); } + if ($request->boolean('select_only')) { + $query->where('show_in_select', 1); + } + if ($request->filled('search')) { $query->where('name', 'like', '%' . $request->search . '%'); } $categories = $query - ->orderBy('id', 'asc') + ->orderBy('sort_order') + ->orderBy('id') ->get(); return response()->json($categories); @@ -40,26 +46,75 @@ class CategoryController extends Controller throw new UnprocessableEntityHttpException('این دسته‌بندی قبلا ثبت شده است'); } + $maxOrder = (int) Category::max('sort_order'); + $category = Category::create([ 'name' => $name, 'active' => $request->has('active') ? (int) $request->active : 1, + 'show_in_filter' => $request->has('show_in_filter') ? (int) $request->show_in_filter : 1, + 'show_in_select' => $request->has('show_in_select') ? (int) $request->show_in_select : 1, + 'sort_order' => $maxOrder + 1, ]); return response()->json($category); } + public function update(Request $request, $id) + { + $category = Category::findOrFail($id); + + if ($request->has('name')) { + $name = trim((string) $request->input('name')); + if ($name === '') { + throw new UnprocessableEntityHttpException('نام دسته‌بندی الزامی است'); + } + if (Category::where('name', $name)->where('id', '!=', $id)->exists()) { + throw new UnprocessableEntityHttpException('این نام قبلا ثبت شده است'); + } + $category->name = $name; + } + + foreach (['active', 'show_in_filter', 'show_in_select', 'sort_order'] as $field) { + if ($request->has($field)) { + $category->{$field} = $field === 'sort_order' + ? (int) $request->input($field) + : (int) $request->input($field); + } + } + + $category->save(); + + return response()->json($category); + } + + public function syncFromProducts(CategoryService $categoryService) + { + $created = $categoryService->syncFromProducts(); + + return response()->json([ + 'message' => $created > 0 + ? "{$created} دسته‌بندی از محصولات اضافه شد" + : 'دسته‌بندی جدیدی برای افزودن نبود', + 'created' => $created, + 'items' => Category::query()->orderBy('sort_order')->orderBy('id')->get(), + ]); + } + + /** تب فیلتر اپ کاربر */ public function clientIndex(Request $request) { $query = Category::query() - ->where('active', 1); + ->where('active', 1) + ->where('show_in_filter', 1); if ($request->filled('search')) { $query->where('name', 'like', '%' . $request->search . '%'); } $categories = $query - ->orderBy('id', 'asc') - ->get(); + ->orderBy('sort_order') + ->orderBy('id') + ->get(['id', 'name', 'sort_order']); return response()->json($categories); } @@ -73,12 +128,11 @@ class CategoryController extends Controller } $category = Category::findOrFail($id); - $category->active = $active; $category->save(); return response()->json([ - 'message' => $active ? 'دسته‌بندی فعال شد' : 'دسته‌بندی غیرفعال شد', + 'message' => $active ? 'نمایش محصولات این دسته فعال شد' : 'نمایش محصولات این دسته غیرفعال شد', 'data' => $category, ]); } diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index 2924ccb..dc4044a 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -18,6 +18,7 @@ use App\Services\ConfigService; use App\Services\ProductDisplayPriceService; use App\Services\ProductPriceService; use App\Services\ProductPriceSyncService; +use App\Services\CategoryService; use Illuminate\Support\Facades\Schema; @@ -161,6 +162,9 @@ class ProductController extends SuperController } $products = Product::with(['file'])->where('active', 1)->get(); + $categoryService = app(CategoryService::class); + $products = $categoryService->filterProductsByVisibleCategories($products); + $products = $categoryService->sortProductsForStore($products); if (str_contains(request()->path(), '/customer/')) { $products->each(function (Product $product) { diff --git a/app/Models/Category.php b/app/Models/Category.php index 1485247..d72c64a 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -11,6 +11,9 @@ class Category extends Model protected $fillable = [ 'name', 'active', + 'show_in_filter', + 'show_in_select', + 'sort_order', ]; const CREATED_AT = 'created_at'; @@ -18,5 +21,8 @@ class Category extends Model protected $casts = [ 'active' => 'boolean', + 'show_in_filter' => 'boolean', + 'show_in_select' => 'boolean', + 'sort_order' => 'integer', ]; } diff --git a/app/Services/CategoryService.php b/app/Services/CategoryService.php new file mode 100644 index 0000000..3f082c5 --- /dev/null +++ b/app/Services/CategoryService.php @@ -0,0 +1,90 @@ +where('active', 0) + ->pluck('name'); + } + + public function filterProductsByVisibleCategories(Collection $products): Collection + { + $hidden = $this->hiddenCategoryNames(); + if ($hidden->isEmpty()) { + return $products; + } + + return $products->filter(function ($product) use ($hidden) { + $group = trim((string) ($product->group_type ?? '')); + if ($group === '') { + return true; + } + + if (! Category::where('name', $group)->exists()) { + return true; + } + + return ! $hidden->contains($group); + })->values(); + } + + public function sortProductsForStore(Collection $products): Collection + { + return $products + ->sort(function ($a, $b) { + $aInStock = (int) ($a->inventory ?? 0) > 0 ? 0 : 1; + $bInStock = (int) ($b->inventory ?? 0) > 0 ? 0 : 1; + if ($aInStock !== $bInStock) { + return $aInStock <=> $bInStock; + } + + return ((int) ($b->inventory ?? 0)) <=> ((int) ($a->inventory ?? 0)); + }) + ->values(); + } + + public function syncFromProducts(): int + { + $names = Product::query() + ->whereNotNull('group_type') + ->where('group_type', '!=', '') + ->distinct() + ->orderBy('group_type') + ->pluck('group_type'); + + $created = 0; + $order = (int) Category::max('sort_order'); + + foreach ($names as $name) { + $name = trim((string) $name); + if ($name === '') { + continue; + } + + if (Category::where('name', $name)->exists()) { + continue; + } + + $order++; + Category::create([ + 'name' => $name, + 'active' => 1, + 'show_in_filter' => 1, + 'show_in_select' => 1, + 'sort_order' => $order, + ]); + $created++; + } + + return $created; + } +} diff --git a/database/migrations/2026_06_12_000001_add_category_display_flags.php b/database/migrations/2026_06_12_000001_add_category_display_flags.php new file mode 100644 index 0000000..e4bb007 --- /dev/null +++ b/database/migrations/2026_06_12_000001_add_category_display_flags.php @@ -0,0 +1,42 @@ +boolean('show_in_filter')->default(true)->after('active'); + } + if (! Schema::hasColumn('categories', 'show_in_select')) { + $table->boolean('show_in_select')->default(true)->after('show_in_filter'); + } + if (! Schema::hasColumn('categories', 'sort_order')) { + $table->unsignedSmallInteger('sort_order')->default(0)->after('show_in_select'); + } + }); + } + + public function down(): void + { + if (! Schema::hasTable('categories')) { + return; + } + + Schema::table('categories', function (Blueprint $table) { + foreach (['sort_order', 'show_in_select', 'show_in_filter'] as $column) { + if (Schema::hasColumn('categories', $column)) { + $table->dropColumn($column); + } + } + }); + } +}; diff --git a/routes/admin.php b/routes/admin.php index 5cbe3ae..159ab27 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -167,6 +167,8 @@ Route::middleware('auth:user')->group(function (){ if (app(ConfigService::class)->isStore()) { Route::get('categories', [CategoryController::class, 'index']); Route::post('categories', [CategoryController::class, 'store']); + Route::post('categories/sync-from-products', [CategoryController::class, 'syncFromProducts']); + Route::put('categories/{id}', [CategoryController::class, 'update']); Route::get('categories/{id}/active/{active}', [CategoryController::class, 'changeActive']); } diff --git a/routes/customer.php b/routes/customer.php index 7978b41..f68d9fa 100644 --- a/routes/customer.php +++ b/routes/customer.php @@ -18,6 +18,7 @@ use App\Http\Controllers\Admin\GiftCardController; use App\Services\ConfigService; use App\Http\Controllers\Customer\havaleController; use App\Http\Controllers\Customer\CartController; +use App\Http\Controllers\Admin\CategoryController; //----------gateway-verify @@ -32,6 +33,7 @@ if ($isStore) { Route::middleware('validation')->group(function () { Route::get('/product/list', [AdminProductController::class, 'listProduct']); Route::get('/product/list/{id}', [AdminProductController::class, 'ProductById']); + Route::get('/categories', [CategoryController::class, 'clientIndex']); Route::post('validateDiscount', [GiftCardController::class, 'validateDiscount']); }); }