From 98e1fe969c0b12962ee182c69ceca8a6351f614b Mon Sep 17 00:00:00 2001 From: adonis Date: Fri, 12 Jun 2026 19:49:53 +0330 Subject: [PATCH] store --- .../Controllers/Admin/PriceController.php | 27 +++ .../Controllers/Admin/ProductController.php | 27 ++- .../Controllers/Site/ReportController.php | 188 ++++++++++++++++++ app/Services/ProductDisplayPriceService.php | 29 ++- app/Services/ProductPriceSyncService.php | 44 ++++ routes/admin.php | 4 +- 6 files changed, 310 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/Admin/PriceController.php b/app/Http/Controllers/Admin/PriceController.php index 18c10ed..16de69c 100644 --- a/app/Http/Controllers/Admin/PriceController.php +++ b/app/Http/Controllers/Admin/PriceController.php @@ -71,6 +71,33 @@ class PriceController extends Controller ]); } + /** لیست مرجع قیمت برای ادمین قدیمی (ghatreh_admin) — GET /admin/p */ + public function methodPrices(ZaarboxPriceService $zaarboxPriceService) + { + $prices = $zaarboxPriceService->cachedPriceList(false); + if ($prices === null) { + return response()->json([]); + } + + $items = collect($prices) + ->map(function ($row) use ($zaarboxPriceService) { + $row = is_array($row) ? $row : (array) $row; + $normalized = $zaarboxPriceService->normalizeFeedRow($row); + + return [ + 'id' => trim((string) ($row['id'] ?? $row['key'] ?? '')), + 'key' => trim((string) ($row['key'] ?? '')), + 'name' => trim((string) ($row['name'] ?? '')), + 'price' => (int) round((float) ($normalized['price'] ?? 0)), + 'profit' => (int) round((float) ($normalized['profit'] ?? 0)), + ]; + }) + ->filter(fn (array $row) => $row['name'] !== '') + ->values(); + + return response()->json($items); + } + public function table(Request $request){ $records = Price::with(['user']); TableEngine::create($records, $request->all()) diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index f912c26..6794219 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -14,8 +14,10 @@ use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; use Illuminate\Support\Facades\Http; use \App\Http\Controllers\Customer\FundController; use App\Http\Controllers\Site\ReportController; +use App\Services\ConfigService; use App\Services\ProductDisplayPriceService; use App\Services\ProductPriceService; +use App\Services\ProductPriceSyncService; use Illuminate\Support\Facades\Schema; @@ -151,9 +153,18 @@ class ProductController extends SuperController public function listProduct() { - // ادمین: sync اختیاری از کش — کاربر: فقط enrich از کش کران (بدون HTTP / بدون UPDATE) + if (app(ConfigService::class)->isStore()) { + app(ProductPriceSyncService::class)->syncStorePrices(true); + $this->auto_close_open(); + + return response()->json( + Product::with(['file'])->where('active', 1)->get() + ); + } + + // آبشده خانگی — ادمین: sync از کش؛ کاربر: enrich بدون UPDATE if (! str_contains(request()->path(), '/customer/')) { - app(\App\Services\ProductPriceSyncService::class)->syncAll(false); + app(ProductPriceSyncService::class)->syncAll(false); } $products = Product::with(['file'])->where('active', 1)->get(); @@ -162,7 +173,9 @@ class ProductController extends SuperController $displayPriceService = app(ProductDisplayPriceService::class); $displayPriceService->enrichForApi($products); $products->each(function (Product $product) use ($displayPriceService) { - $product->setAttribute('show', $displayPriceService->resolveCustomerShow($product)); + if (! $displayPriceService->hasCustomerDisplayPrice($product)) { + $product->setAttribute('show', false); + } }); } @@ -179,7 +192,7 @@ class ProductController extends SuperController public function ProductById($id) { - $this->getPrice(); + app(ProductPriceSyncService::class)->syncStorePrices(true); $this->auto_close_open(); $product = Product::with(['file']) ->where('id', $id) @@ -199,7 +212,11 @@ class ProductController extends SuperController public function getPrice() { - app(\App\Services\ProductPriceSyncService::class)->syncAll(true); + if (app(ConfigService::class)->isStore()) { + app(ProductPriceSyncService::class)->syncStorePrices(true); + } else { + app(ProductPriceSyncService::class)->syncAll(true); + } return response()->json(true); } diff --git a/app/Http/Controllers/Site/ReportController.php b/app/Http/Controllers/Site/ReportController.php index f61c3e4..fb5b7d2 100644 --- a/app/Http/Controllers/Site/ReportController.php +++ b/app/Http/Controllers/Site/ReportController.php @@ -134,6 +134,10 @@ class ReportController extends Controller { $config = app(ConfigService::class); + if ($config->isStore()) { + return $this->storeLastPrice($config); + } + $closed = false; $montzar_price = 0; @@ -499,6 +503,190 @@ class ReportController extends Controller ]); } + /** + * فروشگاه — منطق lastPrice قبل مرج (ghatreh): montzar_price / nasiryan / prices + */ + protected function storeLastPrice(ConfigService $config): array + { + $toBool = fn ($value) => filter_var($value, FILTER_VALIDATE_BOOLEAN); + + $difference = (float) config('fee.single'); + $talaIr = 0; + $nasiryan = 0; + $montzar = 0; + + $montzarExists = Setting::where('key', 'montzar_price')->exists(); + $montzarEnabled = $montzarExists && $toBool($config->Config('montzar_price')); + + if ($montzarEnabled) { + try { + $montzarProductId = (int) ($config->Config('montzar_product_id') ?: 25); + $montzarProducts = app(MontzarService::class)->productList(); + + if (isset($montzarProducts['message'])) { + throw new \Exception('Montzar message: ' . $montzarProducts['message']); + } + + $montzarProduct = collect($montzarProducts)->firstWhere('id', $montzarProductId); + if (! $montzarProduct || ! isset($montzarProduct['sub_agent'])) { + throw new \Exception("Montzar product not found. product_id={$montzarProductId}"); + } + + $buy = (float) ($montzarProduct['sub_agent']['rate_buy_gram_price'] ?? 0); + $sell = (float) ($montzarProduct['sub_agent']['rate_sell_gram_price'] ?? 0); + if ($buy <= 0 || $sell <= 0) { + throw new \Exception("Montzar invalid gram price. buy={$buy}, sell={$sell}"); + } + + $customPrice = [ + 'price' => ($buy + $sell) / 2, + 'diff' => ($buy - $sell) / 2, + ]; + + $config->UpdateConfig('montzar_live_price', (float) $customPrice['price']); + $config->UpdateConfig('montzar_diff', (float) $customPrice['diff']); + $config->UpdateConfig('montzar_mazaneh', ((float) $customPrice['price'] * 4.331802)); + $config->UpdateConfig('montzar_truly_buy_mazaneh', $montzarProduct['sub_agent']['rate_buy_price'] ?? 0); + $config->UpdateConfig('montzar_truly_sell_mazaneh', $montzarProduct['sub_agent']['rate_sell_price'] ?? 0); + $config->UpdateConfig( + 'montzar_last_product_log', + json_encode($montzarProduct, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) + ); + + $openBuy = $toBool($montzarProduct['open_buy'] ?? false); + $openSell = $toBool($montzarProduct['open_sell'] ?? false); + $config->UpdateConfig('montzar_open_buy', $openBuy ? 'true' : 'false'); + $config->UpdateConfig('montzar_open_sell', $openSell ? 'true' : 'false'); + + if ($openBuy || $openSell) { + $montzar = (int) $customPrice['price']; + } + + $config->UpdateConfig('montzar_closed', $montzar === 0 ? 'true' : 'false'); + } catch (\Throwable $e) { + $montzar = 0; + $config->UpdateConfig('montzar_closed', 'true'); + \Log::error('STORE MONZAR PRICE ERROR', ['message' => $e->getMessage()]); + } + } + + $nasiryanEnabled = $config->Config('price_from_nasiryan') == 'true'; + if ($nasiryanEnabled) { + try { + $nasiryanRes = Http::withoutVerifying() + ->timeout(2) + ->withToken(Cnt::where('key', 'nasiryan')->first()['value'] ?? '') + ->get('https://nasiryan.com/server/customer/product/list') + ->json(); + + if ((isset($nasiryanRes['message']) && $nasiryanRes['message'] == 'no authorised') || $nasiryanRes == null) { + $cnt = new Cnt(); + $cnt->nasiryan_login(); + $nasiryanRes = Http::withoutVerifying() + ->timeout(2) + ->withToken(Cnt::where('key', 'nasiryan')->first()['value'] ?? '') + ->get('https://nasiryan.com/server/customer/product/list') + ->json(); + } + + $lp = collect($nasiryanRes)->firstWhere('id', 1); + if (! $lp || ! isset($lp['sub_agent'])) { + throw new \Exception('Nasiryan product/sub_agent not found'); + } + + $buy = (float) $lp['sub_agent']['rate_buy_gram_price']; + $sell = (float) $lp['sub_agent']['rate_sell_gram_price']; + $customPrice = [ + 'price' => ($buy + $sell) / 2, + 'diff' => ($buy - $sell) / 2, + ]; + + Cnt::where('key', 'nasiryan_price')->update(['value' => (float) $customPrice['price']]); + Cnt::where('key', 'nasiryan_diff')->update(['value' => (float) $customPrice['diff']]); + Cnt::where('key', 'nasiryan_mazaneh')->update(['value' => ((float) $customPrice['price'] * 4.331802)]); + Cnt::where('key', 'nasiryan_truly_buy_mazaneh')->update(['value' => $lp['sub_agent']['rate_buy_price']]); + Cnt::where('key', 'nasiryan_truly_sell_mazaneh')->update(['value' => $lp['sub_agent']['rate_sell_price']]); + Cnt::where('key', 'log')->update(['value' => json_encode($nasiryanRes, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)]); + + $nasiryan = (int) $customPrice['price']; + Cnt::where('key', 'nasiryan_closed')->update(['value' => $nasiryan == 0 ? 'true' : 'false']); + } catch (\Throwable $e) { + $nasiryan = 0; + Cnt::where('key', 'nasiryan_closed')->update(['value' => 'true']); + } + } + + if ($montzarEnabled && $montzar !== 0) { + $priceValue = $montzar; + $priceSource = 'montzar'; + } elseif ($nasiryanEnabled && $nasiryan !== 0) { + $priceValue = $nasiryan; + $priceSource = 'nasiryan'; + } else { + $priceValue = $talaIr; + $priceSource = 'tala_ir'; + } + + $lastStored = Price::latest()->first(); + if ( + (! $lastStored || (float) $lastStored->price != (float) $priceValue) + && $priceValue > 30000000 + ) { + $price = new Price(); + $price->price = $priceValue; + $price->time_at = Carbon::now()->toTimeString(); + $price->date_at = Carbon::now()->toDateString(); + $price->save(); + + $this->updatePrise(); + + $candle = PriceCandle::whereDate('date_at', date('Y-m-d'))->first(); + if ($candle !== null) { + $candle->end = $priceValue; + if ($priceValue < $candle->low) { + $candle->low = $priceValue; + } elseif ($priceValue > $candle->high) { + $candle->high = $priceValue; + } + $candle->save(); + } else { + PriceCandle::create([ + 'start' => $priceValue, + 'end' => $priceValue, + 'high' => $priceValue, + 'low' => $priceValue, + 'date_at' => date('Y-m-d'), + ]); + } + } + + $price = Price::orderByDesc('id')->first(); + if (! $price) { + return []; + } + + if ($priceSource === 'montzar') { + $difference += (float) $config->Config('montzar_diff'); + } elseif ($priceSource === 'nasiryan') { + $difference += (float) (Cnt::where('key', 'nasiryan_diff')->first()['value'] ?? 0); + } + + $response = [ + 'sell' => (int) ceil($price->price - $difference), + 'buy' => (int) ceil($price->price + $difference), + 'price_last' => (int) ceil($price->price + $difference), + 'price' => $priceValue, + 'tala_ir' => $talaIr, + 'nasiryan' => $nasiryan, + ]; + + if ($montzarExists) { + $response['montzar'] = $montzar; + } + + return $response; + } + public function check() { $service = Http:: diff --git a/app/Services/ProductDisplayPriceService.php b/app/Services/ProductDisplayPriceService.php index f6c17d8..af46687 100644 --- a/app/Services/ProductDisplayPriceService.php +++ b/app/Services/ProductDisplayPriceService.php @@ -36,8 +36,20 @@ class ProductDisplayPriceService /** * پر کردن price روی مدل‌ها برای پاسخ JSON — بدون UPDATE دیتابیس */ + /** پر کردن کش مظنه اگر خالی باشد (قبل از enrich لیست مشتری) */ + public function warmLivePriceCache(): void + { + if (! is_array(Cache::get(self::LIVE_PRICE_CACHE_KEY))) { + $this->resolveFallbackPayload(); + } + } + public function enrichForApi(Collection|iterable $products): void { + if (app(ConfigService::class)->isStore()) { + return; + } + $collection = collect($products); $zaarbox = app(ZaarboxPriceService::class); $zaarboxFeed = $zaarbox->getCachedPriceListOnly(); @@ -88,10 +100,15 @@ class ProductDisplayPriceService ? $this->resolveLiveBuyPriceWithFallback() : $this->resolveStrictLiveBuyPrice(); - if ($unitBuy <= 0 && ! $useLast && app(ConfigService::class)->getBool('show_last_price', false)) { + if ($unitBuy <= 0 && app(ConfigService::class)->getBool('show_last_price', false)) { $unitBuy = $this->resolveHistoricalBasePrice(); } + $offset = ProductPriceService::offsetPercent($product); + if ($unitBuy > 0 && $offset != 0.0) { + $unitBuy = ProductPriceService::adjustedTotal($unitBuy, 0, $offset); + } + $this->applyPriceOrFallback($product, $unitBuy, $useLast); } @@ -247,6 +264,16 @@ class ProductDisplayPriceService { $row = $zaarbox->findFeedRow($feed, (string) $product->reading_method); if ($row === null) { + $useLast = $this->productUsesLastPriceIfZero($product); + $offset = ProductPriceService::offsetPercent($product); + $historical = app(ConfigService::class)->getBool('show_last_price', false) + ? $this->resolveHistoricalBasePrice() + : 0; + $computed = $historical > 0 + ? ProductPriceService::adjustedTotal($historical, 0, $offset) + : 0; + $this->applyPriceOrFallback($product, $computed, $useLast); + return; } diff --git a/app/Services/ProductPriceSyncService.php b/app/Services/ProductPriceSyncService.php index dcf9d9e..e6a43c3 100644 --- a/app/Services/ProductPriceSyncService.php +++ b/app/Services/ProductPriceSyncService.php @@ -23,6 +23,50 @@ class ProductPriceSyncService ) { } + /** + * فروشگاه (قطره / کشکول) — همان getPrice قدیمی ghatreh: + * فید legacy + تطبیق reading_method با name + ذخیره خام price/profit (بدون offset). + */ + public function syncStorePrices(bool $forceRefresh = true): bool + { + $RPrices = $this->cachedLegacyPriceList($forceRefresh); + if ($RPrices === null) { + return false; + } + + $products = Product::query()->whereNotNull('reading_method')->get(); + $liquidGoldPrice = (int) $this->configService->getInt('montzar_live_price', 0); + if ($liquidGoldPrice <= 0) { + $liquidGoldPrice = app(ProductDisplayPriceService::class)->resolveHistoricalBasePrice(); + } + + $synced = false; + + foreach ($products as $product) { + foreach ($RPrices as $RPrice) { + if ($product->reading_method != ($RPrice['name'] ?? null)) { + continue; + } + + Product::where('id', $product->id)->update([ + 'price' => (int) ($RPrice['price'] ?? 0), + 'price_profit' => (int) ($RPrice['profit'] ?? 0), + ]); + $synced = true; + + if ((int) ($product->bubble ?? 0) === 1 && $liquidGoldPrice > 0) { + $p = Product::find($product->id); + if ($p) { + $p->amount = (float) ($RPrice['price'] ?? 0) / $liquidGoldPrice; + $p->save(); + } + } + } + } + + return $synced; + } + /** @param bool $forceRefresh درخواست تازه از https://price.zaarbox.ir/api/free (کران) */ public function syncAll(bool $forceRefresh = false): bool { diff --git a/routes/admin.php b/routes/admin.php index fdf869d..5cbe3ae 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -25,9 +25,7 @@ use App\Services\ConfigService; Route::middleware('auth:user')->group(function (){ Route::middleware('validation')->group(function (){ - Route::get('/p', function () { - return Http::get('http://109.122.246.48:4949/api/free', [])->json(); - }); + Route::get('/p', [PriceController::class, 'methodPrices']); //upload file Route::post('/file',[FileController::class, 'store']);