diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index 6794219..2924ccb 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -155,11 +155,20 @@ class ProductController extends SuperController { if (app(ConfigService::class)->isStore()) { app(ProductPriceSyncService::class)->syncStorePrices(true); - $this->auto_close_open(); - return response()->json( - Product::with(['file'])->where('active', 1)->get() - ); + if (! str_contains(request()->path(), '/customer/')) { + $this->auto_close_open(); + } + + $products = Product::with(['file'])->where('active', 1)->get(); + + if (str_contains(request()->path(), '/customer/')) { + $products->each(function (Product $product) { + $product->setAttribute('show', $this->storeProductShowForCustomer($product)); + }); + } + + return response()->json($products); } // آبشده خانگی — ادمین: sync از کش؛ کاربر: enrich بدون UPDATE @@ -324,6 +333,25 @@ class ProductController extends SuperController } + /** فروشگاه — نمایش در اپ کاربر: فعال + قیمت ذخیره‌شده یا محصول وزنی */ + private function storeProductShowForCustomer(Product $product): bool + { + if (! $product->active) { + return false; + } + + $storedPrice = (int) ($product->price ?? 0) + (int) ($product->price_profit ?? 0); + if ($storedPrice > 0) { + return true; + } + + if ((int) ($product->direct_price ?? 0) === 0 && (float) ($product->amount ?? 0) > 0) { + return true; + } + + return false; + } + private function filterProductData(array $data): array { if (!Product::hasPriceOffsetColumn()) { diff --git a/app/Services/ProductPriceSyncService.php b/app/Services/ProductPriceSyncService.php index e6a43c3..3f82b81 100644 --- a/app/Services/ProductPriceSyncService.php +++ b/app/Services/ProductPriceSyncService.php @@ -24,13 +24,15 @@ class ProductPriceSyncService } /** - * فروشگاه (قطره / کشکول) — همان getPrice قدیمی ghatreh: - * فید legacy + تطبیق reading_method با name + ذخیره خام price/profit (بدون offset). + * فروشگاه (قطره / کشکول) — فید legacy + تطبیق reading_method با name + * + اعمال price_offset_percent روی قیمت ذخیره‌شده. */ public function syncStorePrices(bool $forceRefresh = true): bool { - $RPrices = $this->cachedLegacyPriceList($forceRefresh); - if ($RPrices === null) { + $feed = $this->zaarboxPriceService->cachedPriceList($forceRefresh) + ?? $this->cachedLegacyPriceList($forceRefresh); + + if ($feed === null) { return false; } @@ -43,23 +45,23 @@ class ProductPriceSyncService $synced = false; foreach ($products as $product) { - foreach ($RPrices as $RPrice) { - if ($product->reading_method != ($RPrice['name'] ?? null)) { - continue; - } + $row = $this->zaarboxPriceService->findFeedRow($feed, (string) $product->reading_method); + if ($row === null) { + continue; + } - Product::where('id', $product->id)->update([ - 'price' => (int) ($RPrice['price'] ?? 0), - 'price_profit' => (int) ($RPrice['profit'] ?? 0), - ]); - $synced = true; + $row = $this->zaarboxPriceService->normalizeFeedRow($row); + $basePrice = (int) ($row['price'] ?? 0); + $baseProfit = (int) ($row['profit'] ?? 0); - if ((int) ($product->bubble ?? 0) === 1 && $liquidGoldPrice > 0) { - $p = Product::find($product->id); - if ($p) { - $p->amount = (float) ($RPrice['price'] ?? 0) / $liquidGoldPrice; - $p->save(); - } + ProductPriceService::syncFromReference($product, $basePrice, $baseProfit); + $synced = true; + + if ((int) ($product->bubble ?? 0) === 1 && $liquidGoldPrice > 0 && $basePrice > 0) { + $p = Product::find($product->id); + if ($p) { + $p->amount = $basePrice / $liquidGoldPrice; + $p->save(); } } } diff --git a/database/sql/kashkol_products_pricing_bootstrap.sql b/database/sql/kashkol_products_pricing_bootstrap.sql new file mode 100644 index 0000000..70d9bb6 --- /dev/null +++ b/database/sql/kashkol_products_pricing_bootstrap.sql @@ -0,0 +1,190 @@ +-- ============================================================================= +-- kashkol_products_pricing_bootstrap.sql +-- ستون‌های قیمت‌گذاری محصولات + تنظیمات provider برای کشکول / قطره +-- +-- قبل از اجرا: +-- USE `kashkolgold`; +-- mysqldump بکاپ +-- +-- بعد از اجرا: +-- php artisan config:clear +-- php artisan cache:clear +-- ============================================================================= + +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; +SET @db = DATABASE(); + +START TRANSACTION; + +-- --------------------------------------------------------------------------- +-- products — ستون‌های قیمت و فروشگاه +-- --------------------------------------------------------------------------- + +-- price / price_profit (اگر DB خیلی قدیمی است) +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 = 'price') = 0, + 'ALTER TABLE `products` ADD COLUMN `price` BIGINT NOT NULL DEFAULT 0', + 'SELECT ''skip products.price'' AS info' +); +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 = 'price_profit') = 0, + IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'products' AND COLUMN_NAME = 'price') > 0, + 'ALTER TABLE `products` ADD COLUMN `price_profit` INT NULL DEFAULT NULL AFTER `price`', + 'ALTER TABLE `products` ADD COLUMN `price_profit` INT NULL DEFAULT NULL' + ), + 'SELECT ''skip products.price_profit'' AS info' +); +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 = 'price_offset_percent') = 0, + IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'products' AND COLUMN_NAME = 'price_profit') > 0, + 'ALTER TABLE `products` ADD COLUMN `price_offset_percent` DECIMAL(8,2) NOT NULL DEFAULT 0 COMMENT ''تعدیل درصد نسبت به قیمت مرجع'' AFTER `price_profit`', + 'ALTER TABLE `products` ADD COLUMN `price_offset_percent` DECIMAL(8,2) NOT NULL DEFAULT 0 COMMENT ''تعدیل درصد نسبت به قیمت مرجع''' + ), + 'SELECT ''skip products.price_offset_percent'' AS info' +); +PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; + +-- مرجع قیمت از provider (نام ردیف فید) +SET @sql = IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'products' AND COLUMN_NAME = 'reading_method') = 0, + 'ALTER TABLE `products` ADD COLUMN `reading_method` VARCHAR(255) NULL DEFAULT NULL COMMENT ''مرجع قیمت — name ردیف فید''', + 'SELECT ''skip products.reading_method'' AS info' +); +PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; + +-- fallback وقتی provider صفر است +SET @sql = IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'products' AND COLUMN_NAME = 'show_in_assets') = 0, + 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_in_assets` TINYINT(1) NOT NULL DEFAULT 0 AFTER `show`', + 'ALTER TABLE `products` ADD COLUMN `show_in_assets` TINYINT(1) NOT NULL DEFAULT 0' + ), + 'SELECT ''skip products.show_in_assets'' AS info' +); +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 = 'use_last_price_if_zero') = 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 `use_last_price_if_zero` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''استفاده از آخرین قیمت وقتی provider صفر است'' AFTER `show_in_assets`', + 'ALTER TABLE `products` ADD COLUMN `use_last_price_if_zero` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''استفاده از آخرین قیمت وقتی provider صفر است''' + ), + 'SELECT ''skip products.use_last_price_if_zero'' AS info' +); +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 = 'inventory') = 0, + 'ALTER TABLE `products` ADD COLUMN `inventory` INT NOT NULL DEFAULT 0 COMMENT ''تعداد موجودی''', + 'SELECT ''skip products.inventory'' AS info' +); +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 = 'description') = 0, + 'ALTER TABLE `products` ADD COLUMN `description` TEXT NULL', + 'SELECT ''skip products.description'' AS info' +); +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 = 'direct_price') = 0, + 'ALTER TABLE `products` ADD COLUMN `direct_price` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''1=قیمت ثابت، 0=محاسبه از مظنه''', + 'SELECT ''skip products.direct_price'' AS info' +); +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 = 'bubble') = 0, + 'ALTER TABLE `products` ADD COLUMN `bubble` TINYINT NOT NULL DEFAULT 0', + 'SELECT ''skip products.bubble'' AS info' +); +PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; + +-- --------------------------------------------------------------------------- +-- settings — فروشگاه + provider +-- --------------------------------------------------------------------------- + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'is_store', 'true', 'حالت فروشگاهی (قطره / کشکول)', 1, 1, 'true = روت‌های فروشگاه', 'switch' +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'is_store' LIMIT 1); + +UPDATE `settings` SET `value` = 'true' WHERE `key` = 'is_store'; + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'montzar_price', 'true', 'قیمت از همکار (montzar)', 1, 1, NULL, 'switch' +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'montzar_price' LIMIT 1); + +UPDATE `settings` SET `value` = 'true' WHERE `key` = 'montzar_price'; + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'show_last_price', 'true', 'نمایش آخرین قیمت DB اگر API قطع شد', 1, 1, NULL, 'switch' +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'show_last_price' LIMIT 1); + +UPDATE `settings` SET `value` = 'true' WHERE `key` = 'show_last_price'; + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'montzar_mobile', '', 'موبایل API همکار', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'montzar_mobile' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'montzar_password', '', 'رمز API همکار', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'montzar_password' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'montzar_product_id', '25', 'کد محصول همکار', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'montzar_product_id' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'montzar_base_url', 'https://limangold.ir', 'آدرس سرور همکار', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'montzar_base_url' LIMIT 1); + +INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`) +SELECT 'zaarbox_api_url', 'https://price.zaarbox.ir/api/free', 'آدرس API Zaarbox', 1, 1, NULL +WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'zaarbox_api_url' LIMIT 1); + +-- --------------------------------------------------------------------------- +-- prices — اگر خالی است یک رکورد اولیه (fallback) +-- --------------------------------------------------------------------------- +INSERT INTO `prices` (`price`, `date_at`, `time_at`, `created_at`, `updated_at`) +SELECT 85000000, CURDATE(), CURTIME(), NOW(), NOW() +FROM DUAL +WHERE NOT EXISTS (SELECT 1 FROM `prices` LIMIT 1); + +-- --------------------------------------------------------------------------- +-- migrations — ثبت migrationهای ستون (اگر migrate نشده) +-- --------------------------------------------------------------------------- +INSERT IGNORE INTO `migrations` (`migration`, `batch`) VALUES +('2026_06_02_000001_add_price_offset_percent_to_products_table', 1), +('2026_06_09_000001_add_show_in_assets_to_products_table', 1), +('2026_06_10_000001_add_use_last_price_if_zero_to_products_table', 1), +('2026_06_11_000001_add_store_tables_and_columns', 1); + +COMMIT; + +-- چک نهایی +SELECT COLUMN_NAME, COLUMN_TYPE, COLUMN_COMMENT +FROM information_schema.COLUMNS +WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'products' + AND COLUMN_NAME IN ( + 'price', 'price_profit', 'price_offset_percent', 'reading_method', + 'use_last_price_if_zero', 'inventory', 'direct_price', 'bubble' + ) +ORDER BY ORDINAL_POSITION; + +SELECT `key`, `value` FROM `settings` +WHERE `key` IN ('is_store', 'montzar_price', 'show_last_price', 'montzar_mobile', 'montzar_product_id') +ORDER BY `key`; + +SELECT id, price, date_at, time_at FROM `prices` ORDER BY id DESC LIMIT 3;