From e158831fd32aeb1179f081848459e7b35a5c602e Mon Sep 17 00:00:00 2001 From: adonis Date: Tue, 18 Aug 2026 17:17:01 +0330 Subject: [PATCH] feat(server): add show_on_tv product flag and public /tv products API Co-authored-by: Cursor --- .../Controllers/Admin/ProductController.php | 43 +++++++++++++++++++ app/Models/Product.php | 25 +++++++++++ ...00001_add_show_on_tv_to_products_table.php | 43 +++++++++++++++++++ database/sql/add_product_show_on_tv.sql | 40 +++++++++++++++++ database/sql/deploy_safe_schema.sql | 25 +++++++++++ routes/customer.php | 1 + 6 files changed, 177 insertions(+) create mode 100644 database/migrations/2026_08_18_000001_add_show_on_tv_to_products_table.php create mode 100644 database/sql/add_product_show_on_tv.sql diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index b29d808..7a9b978 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -244,6 +244,43 @@ class ProductController extends SuperController return response()->json($products); } + /** + * تابلو /tv — بدون لاگین؛ فقط محصولات تیک‌خورده. + */ + public function listTvProducts() + { + if (! Product::hasShowOnTvColumn()) { + return response()->json([]); + } + + $products = Product::with(['file']) + ->where('active', 1) + ->where('show_on_tv', true) + ->get(); + + if (app(ConfigService::class)->isStore()) { + $categoryService = app(CategoryService::class); + $products = $categoryService->filterProductsByVisibleCategories($products); + $products = $categoryService->sortProductsForStore($products); + } + + $this->applyCustomerShow($products); + + $products = $products->filter(function (Product $product) { + return (bool) $product->show_on_tv; + }); + + if (Product::hasPriceOffsetColumn()) { + $products->each(function (Product $product) { + if ($product->reading_method) { + $product->makeHidden(['price_offset_percent']); + } + }); + } + + return response()->json($products->values()->all()); + } + public function ProductById($id) { if (! $this->isCustomerApiRequest()) { @@ -652,6 +689,12 @@ class ProductController extends SuperController $data['show_in_assets'] = !empty($data['show_in_assets']); } + if (!Product::hasShowOnTvColumn()) { + unset($data['show_on_tv']); + } elseif (array_key_exists('show_on_tv', $data)) { + $data['show_on_tv'] = !empty($data['show_on_tv']); + } + if (!Product::hasUseLastPriceIfZeroColumn()) { unset($data['use_last_price_if_zero']); } elseif (array_key_exists('use_last_price_if_zero', $data)) { diff --git a/app/Models/Product.php b/app/Models/Product.php index 3f1b3d3..d154416 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -19,6 +19,7 @@ class Product extends Model private static ?bool $priceColumnExists = null; private static ?bool $orderLimitColumnsExist = null; private static ?bool $showInAssetsColumnExists = null; + private static ?bool $showOnTvColumnExists = null; private static ?bool $useLastPriceIfZeroColumnExists = null; private static ?bool $directPriceColumnExists = null; private static ?bool $appPriceColumnExists = null; @@ -46,6 +47,9 @@ class Product extends Model if (!static::hasShowInAssetsColumn()) { unset($product->attributes['show_in_assets']); } + if (!static::hasShowOnTvColumn()) { + unset($product->attributes['show_on_tv']); + } if (!static::hasUseLastPriceIfZeroColumn()) { unset($product->attributes['use_last_price_if_zero']); } @@ -231,6 +235,24 @@ class Product extends Model return static::$showInAssetsColumnExists; } + public static function hasShowOnTvColumn(): bool + { + if (static::$showOnTvColumnExists !== null) { + return static::$showOnTvColumnExists; + } + + try { + static::$showOnTvColumnExists = Schema::hasColumn( + (new static)->getTable(), + 'show_on_tv' + ); + } catch (\Throwable) { + static::$showOnTvColumnExists = false; + } + + return static::$showOnTvColumnExists; + } + public static function hasUseLastPriceIfZeroColumn(): bool { if (static::$useLastPriceIfZeroColumnExists !== null) { @@ -413,6 +435,7 @@ class Product extends Model 'for_barayand', // Whether it is for Barayand (boolean). 'show', // Whether to show the record (boolean). 'show_in_assets', // Show balance on customer assets page cards + 'show_on_tv', // Show this product on customer /tv board 'trade_by_quantity', // UI معامله مقداری: ارزش کل + مقدار 'use_last_price_if_zero', // Keep last DB price when automatic provider returns zero 'open_sell', // Whether selling is open (boolean). @@ -448,6 +471,7 @@ class Product extends Model protected $attributes = [ 'show_in_assets' => false, + 'show_on_tv' => false, 'trade_by_quantity' => false, 'use_last_price_if_zero' => false, 'cash_fee_buy_is_percent' => false, @@ -461,6 +485,7 @@ class Product extends Model 'active' => 'boolean', // Cast to boolean. 'show' => 'boolean', // Cast to boolean. 'show_in_assets' => 'boolean', + 'show_on_tv' => 'boolean', 'trade_by_quantity' => 'boolean', 'use_last_price_if_zero' => 'boolean', 'override_trading_hours' => 'boolean', diff --git a/database/migrations/2026_08_18_000001_add_show_on_tv_to_products_table.php b/database/migrations/2026_08_18_000001_add_show_on_tv_to_products_table.php new file mode 100644 index 0000000..4031548 --- /dev/null +++ b/database/migrations/2026_08_18_000001_add_show_on_tv_to_products_table.php @@ -0,0 +1,43 @@ +boolean('show_on_tv')->default(false)->after('show_in_assets'); + } elseif (Schema::hasColumn('products', 'show')) { + $table->boolean('show_on_tv')->default(false)->after('show'); + } else { + $table->boolean('show_on_tv')->default(false); + } + }); + } else { + DB::statement( + 'ALTER TABLE `products` MODIFY COLUMN `show_on_tv` TINYINT(1) NOT NULL DEFAULT 0' + ); + } + } + + public function down(): void + { + if (!Schema::hasTable('products') || !Schema::hasColumn('products', 'show_on_tv')) { + return; + } + + Schema::table('products', function (Blueprint $table) { + $table->dropColumn('show_on_tv'); + }); + } +}; diff --git a/database/sql/add_product_show_on_tv.sql b/database/sql/add_product_show_on_tv.sql new file mode 100644 index 0000000..1ba8910 --- /dev/null +++ b/database/sql/add_product_show_on_tv.sql @@ -0,0 +1,40 @@ +-- نمایش محصول در مسیر /tv اپ مشتری +-- امن برای اجرای مجدد +-- USE دیتابیس_خودتان; + +SET @db = DATABASE(); + +SET @sql = IF( + (SELECT COUNT(*) FROM information_schema.TABLES + WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'products') = 0, + 'SELECT ''skip: products table not found'' AS info', + IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'products' AND COLUMN_NAME = 'show_on_tv') > 0, + 'SELECT ''skip: show_on_tv exists'' AS info', + IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'products' AND COLUMN_NAME = 'show_in_assets') > 0, + 'ALTER TABLE `products` ADD COLUMN `show_on_tv` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''نمایش در تابلو /tv'' AFTER `show_in_assets`', + IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'products' AND COLUMN_NAME = 'show') > 0, + 'ALTER TABLE `products` ADD COLUMN `show_on_tv` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''نمایش در تابلو /tv'' AFTER `show`', + 'ALTER TABLE `products` ADD COLUMN `show_on_tv` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''نمایش در تابلو /tv''' + ) + ) + ) +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @sql = IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'products' AND COLUMN_NAME = 'show_on_tv') > 0, + 'ALTER TABLE `products` MODIFY COLUMN `show_on_tv` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''نمایش در تابلو /tv''', + 'SELECT ''skip: show_on_tv modify'' AS info' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/database/sql/deploy_safe_schema.sql b/database/sql/deploy_safe_schema.sql index 97605ad..83f920a 100644 --- a/database/sql/deploy_safe_schema.sql +++ b/database/sql/deploy_safe_schema.sql @@ -43,6 +43,31 @@ DEALLOCATE PREPARE stmt; -- یک‌بار برای اصلاح سرورهایی که قبلاً DEFAULT 1 داشتند: -- UPDATE `products` SET `show_in_assets` = 0; +-- --------------------------------------------------------------------------- +-- products.show_on_tv (تابلو قیمت /tv) +-- --------------------------------------------------------------------------- +SET @sql = IF( + (SELECT COUNT(*) FROM information_schema.TABLES + WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'products') > 0 + AND (SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'products' AND COLUMN_NAME = 'show_on_tv') = 0, + IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'products' AND COLUMN_NAME = 'show_in_assets') > 0, + 'ALTER TABLE `products` ADD COLUMN `show_on_tv` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''نمایش در تابلو /tv'' AFTER `show_in_assets`', + IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'products' AND COLUMN_NAME = 'show') > 0, + 'ALTER TABLE `products` ADD COLUMN `show_on_tv` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''نمایش در تابلو /tv'' AFTER `show`', + 'ALTER TABLE `products` ADD COLUMN `show_on_tv` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''نمایش در تابلو /tv''' + ) + ), + 'SELECT ''skip products.show_on_tv'' AS info' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + -- --------------------------------------------------------------------------- -- products.use_last_price_if_zero (fallback وقتی provider صفر است) -- --------------------------------------------------------------------------- diff --git a/routes/customer.php b/routes/customer.php index 0b994d4..71f7073 100644 --- a/routes/customer.php +++ b/routes/customer.php @@ -40,6 +40,7 @@ $isStore = app(ConfigService::class)->isStore(); Route::middleware('validation')->group(function () { Route::get('/categories', [CategoryController::class, 'clientIndex']); + Route::get('/tv/products', [AdminProductController::class, 'listTvProducts']); }); // فروشگاه (قطره / کشکول) — مثل برنچ ghatre، بدون لاگین