khanegi setting and prices records
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\LivePriceTickService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class PriceTickCommand extends Command
|
||||
{
|
||||
protected $signature = 'prices:tick';
|
||||
|
||||
protected $description = 'Fetch live prices, sync products, cache last/list, log snapshot (every 10s)';
|
||||
|
||||
public function handle(LivePriceTickService $tickService): int
|
||||
{
|
||||
$tickService->tick();
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\PriceSnapshotService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class PrunePriceSnapshotsCommand extends Command
|
||||
{
|
||||
protected $signature = 'prices:prune';
|
||||
|
||||
protected $description = 'Downsample and prune price history (daily at midnight)';
|
||||
|
||||
public function handle(PriceSnapshotService $snapshots): int
|
||||
{
|
||||
$stats = $snapshots->pruneHistory();
|
||||
$this->info(json_encode($stats, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,18 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\ProductPriceSyncService;
|
||||
use App\Services\LivePriceTickService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SyncProductPrices extends Command
|
||||
{
|
||||
protected $signature = 'product:sync-prices';
|
||||
|
||||
protected $description = 'Fetch zaarbox price board + sync product DB (scheduler: every 30s)';
|
||||
protected $description = 'Alias for prices:tick (backward compatible cron)';
|
||||
|
||||
public function handle(ProductPriceSyncService $syncService): int
|
||||
public function handle(LivePriceTickService $tickService): int
|
||||
{
|
||||
// کران: GET https://price.zaarbox.ir/api/free (یا zaarbox_api_url) → کش + UPDATE محصولات
|
||||
$syncService->syncAll(true);
|
||||
$tickService->tick();
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
+13
-19
@@ -2,44 +2,38 @@
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Console\Commands\Price;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
protected $commands = [
|
||||
Price::class,
|
||||
Commands\Price::class,
|
||||
];
|
||||
/**
|
||||
* Define the application's command schedule.
|
||||
*
|
||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
||||
* @return void
|
||||
*/
|
||||
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
// همگامسازی قیمت محصولات متغیر (reading_method) — هر ۳۰ ثانیه
|
||||
// تیک قیمت — هر ۱۰ ثانیه (۶ بار در دقیقه)
|
||||
$schedule->call(function () {
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
\Illuminate\Support\Facades\Artisan::call('product:sync-prices');
|
||||
if ($i < 1) {
|
||||
sleep(30);
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
\Illuminate\Support\Facades\Artisan::call('prices:tick');
|
||||
if ($i < 5) {
|
||||
sleep(10);
|
||||
}
|
||||
}
|
||||
})->everyMinute()->name('sync_product_prices_30s')->withoutOverlapping();
|
||||
})->everyMinute()->name('prices_tick_10s')->withoutOverlapping();
|
||||
|
||||
$schedule->command('prices:refresh-zaarbox')
|
||||
->everyThirtyMinutes()
|
||||
->name('refresh_zaarbox_prices')
|
||||
->withoutOverlapping();
|
||||
|
||||
// پاکسازی و downsample تاریخچه — ساعت ۱۲ شب
|
||||
$schedule->command('prices:prune')
|
||||
->dailyAt('00:00')
|
||||
->name('prune_price_snapshots');
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the commands for the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function commands()
|
||||
{
|
||||
$this->load(__DIR__.'/Commands');
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Http\Controllers\Admin;
|
||||
use App\DB\Te\TableEngine;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\PriceSyncLog;
|
||||
use App\Services\ProductPriceSyncService;
|
||||
use App\Services\LivePriceTickService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PriceSyncLogController extends Controller
|
||||
@@ -29,14 +29,14 @@ class PriceSyncLogController extends Controller
|
||||
return response()->json($log);
|
||||
}
|
||||
|
||||
public function runSync(ProductPriceSyncService $syncService)
|
||||
public function runSync(LivePriceTickService $tickService)
|
||||
{
|
||||
$syncService->syncStorePrices(true);
|
||||
$tickService->tick();
|
||||
|
||||
$log = PriceSyncLog::with('items')->orderByDesc('id')->first();
|
||||
|
||||
return response()->json([
|
||||
'message' => $log ? 'قیمتها بهروزرسانی شد' : 'تغییری در قیمت محصولات نبود',
|
||||
'message' => $log ? 'قیمتها بهروزرسانی شد' : 'همگامسازی انجام شد',
|
||||
'data' => $log,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -175,15 +175,12 @@ class ProductController extends SuperController
|
||||
return response()->json($products);
|
||||
}
|
||||
|
||||
// آبشده خانگی — ادمین: sync از کش؛ کاربر: enrich بدون UPDATE
|
||||
if (! str_contains(request()->path(), '/customer/')) {
|
||||
app(ProductPriceSyncService::class)->syncAll(false);
|
||||
}
|
||||
|
||||
// آبشده خانگی — کران قیمت را آپدیت میکند؛ list فقط از DB میخواند
|
||||
$products = Product::with(['file'])->where('active', 1)->get();
|
||||
|
||||
if (str_contains(request()->path(), '/customer/')) {
|
||||
$displayPriceService = app(ProductDisplayPriceService::class);
|
||||
$displayPriceService->warmLivePriceCache();
|
||||
$displayPriceService->enrichForApi($products);
|
||||
$products->each(function (Product $product) use ($displayPriceService) {
|
||||
if (! $displayPriceService->hasCustomerDisplayPrice($product)) {
|
||||
|
||||
@@ -21,6 +21,7 @@ use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Morilog\Jalali\Jalalian;
|
||||
use App\Services\ConfigService;
|
||||
use App\Services\LivePriceCacheService;
|
||||
use App\Services\ProductDisplayPriceService;
|
||||
|
||||
|
||||
@@ -127,15 +128,37 @@ class ReportController extends Controller
|
||||
$payload['last_historical'] = app(ProductDisplayPriceService::class)->resolveHistoricalBasePrice();
|
||||
app(ProductDisplayPriceService::class)->cacheLivePricePayload($payload);
|
||||
|
||||
if (request()->boolean('force_refresh')) {
|
||||
return app(LivePriceCacheService::class)->put($payload);
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
public function lastPrice()
|
||||
public function lastPrice(Request $request)
|
||||
{
|
||||
$config = app(ConfigService::class);
|
||||
$cache = app(LivePriceCacheService::class);
|
||||
|
||||
if (! $request->boolean('force_refresh')) {
|
||||
$cached = $cache->get();
|
||||
if ($cached !== null) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$dbBuilt = $cache->buildFromDatabase();
|
||||
if (($dbBuilt['price'] ?? 0) > 0) {
|
||||
return $dbBuilt;
|
||||
}
|
||||
}
|
||||
|
||||
if ($config->isStore()) {
|
||||
return $this->storeLastPrice($config);
|
||||
return $this->appendUpPrices(array_merge([
|
||||
'closed' => false,
|
||||
'time' => now(),
|
||||
'main_product' => 0,
|
||||
'zaarbox' => 0,
|
||||
], $this->storeLastPrice($config)));
|
||||
}
|
||||
|
||||
$closed = false;
|
||||
@@ -361,24 +384,14 @@ class ReportController extends Controller
|
||||
}
|
||||
|
||||
if ($priceValue <= 0) {
|
||||
if (!$show_last_price) {
|
||||
return $this->appendUpPrices([
|
||||
'sell' => 0,
|
||||
'buy' => 0,
|
||||
'price_last' => 0,
|
||||
'price' => 0,
|
||||
'main_product' => $pr_price ?? 0,
|
||||
'montzar' => $montzar_price ?? 0,
|
||||
'zaarbox' => $zaarbox_price ?? 0,
|
||||
'closed' => true,
|
||||
'time' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
$lastPrice = Price::latest()->first();
|
||||
$storedPrice = $lastPrice ? (float) $lastPrice->price : 0.0;
|
||||
$useFallback = $request->boolean('force_refresh') || $show_last_price;
|
||||
|
||||
if ($storedPrice <= 0) {
|
||||
if ($storedPrice > 30_000_000 && $useFallback) {
|
||||
$priceValue = $storedPrice;
|
||||
$priceSource = 'database_last';
|
||||
} else {
|
||||
return $this->appendUpPrices([
|
||||
'sell' => 0,
|
||||
'buy' => 0,
|
||||
@@ -391,9 +404,6 @@ class ReportController extends Controller
|
||||
'time' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
$priceValue = $storedPrice;
|
||||
$priceSource = 'database_last';
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -406,9 +416,9 @@ class ReportController extends Controller
|
||||
$lastPrice = Price::latest()->first();
|
||||
|
||||
if (
|
||||
!$closed &&
|
||||
(!$lastPrice || (float) $lastPrice->price != (float) $priceToStore) &&
|
||||
$priceToStore > 1000000
|
||||
! $closed &&
|
||||
(! $lastPrice || (float) $lastPrice->price != (float) $priceToStore) &&
|
||||
$priceToStore > 30_000_000
|
||||
) {
|
||||
$price = new Price();
|
||||
$price->price = $priceToStore;
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Price;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class LivePriceCacheService
|
||||
{
|
||||
public const FULL_RESPONSE_KEY = 'live_price_full_response';
|
||||
|
||||
public const TTL_SECONDS = 15;
|
||||
|
||||
public function get(): ?array
|
||||
{
|
||||
$cached = Cache::get(self::FULL_RESPONSE_KEY);
|
||||
if (! is_array($cached) || ! isset($cached['body'], $cached['stored_at'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (now()->diffInSeconds($cached['stored_at']) > self::TTL_SECONDS) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $cached['body'];
|
||||
}
|
||||
|
||||
public function put(array $body): array
|
||||
{
|
||||
Cache::put(self::FULL_RESPONSE_KEY, [
|
||||
'body' => $body,
|
||||
'stored_at' => now(),
|
||||
], now()->addSeconds(self::TTL_SECONDS + 5));
|
||||
|
||||
app(ProductDisplayPriceService::class)->cacheLivePricePayload($body);
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/** پاسخ سریع بدون API خارجی — از آخرین رکورد prices + تنظیمات */
|
||||
public function buildFromDatabase(): array
|
||||
{
|
||||
$config = app(ConfigService::class);
|
||||
$display = app(ProductDisplayPriceService::class);
|
||||
$last = Price::orderByDesc('id')->first();
|
||||
$base = $last ? (int) $last->price : 0;
|
||||
|
||||
if ($base <= 0) {
|
||||
$payload = [
|
||||
'sell' => 0,
|
||||
'buy' => 0,
|
||||
'price_last' => 0,
|
||||
'price' => 0,
|
||||
'main_product' => 0,
|
||||
'montzar' => 0,
|
||||
'zaarbox' => 0,
|
||||
'closed' => true,
|
||||
'time' => now(),
|
||||
];
|
||||
} else {
|
||||
$diffBuy = (int) $config->displayBuySpreadRial();
|
||||
$diffSell = (int) $config->displaySellSpreadRial();
|
||||
$buy = (int) ceil($base + $diffBuy);
|
||||
$sell = max(0, (int) ceil($base - $diffSell));
|
||||
|
||||
$payload = [
|
||||
'sell' => $sell,
|
||||
'buy' => $buy,
|
||||
'price_last' => $buy,
|
||||
'price' => $base,
|
||||
'main_product' => 0,
|
||||
'montzar' => 0,
|
||||
'zaarbox' => 0,
|
||||
'closed' => false,
|
||||
'time' => now(),
|
||||
];
|
||||
}
|
||||
|
||||
$payload['up'] = $config->resolvedDailyBaseExtremes()['up'] > 0
|
||||
? [
|
||||
'sell' => max(0, (int) ceil($config->resolvedDailyBaseExtremes()['down'] - $config->displaySellSpreadRial())),
|
||||
'buy' => (int) ceil($config->resolvedDailyBaseExtremes()['up'] + $config->displayBuySpreadRial()),
|
||||
]
|
||||
: ['sell' => $payload['sell'] ?? 0, 'buy' => $payload['buy'] ?? 0];
|
||||
$payload['last_historical'] = $display->resolveHistoricalBasePrice();
|
||||
|
||||
return $payload;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Http\Controllers\Site\ReportController;
|
||||
use App\Models\Product;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class LivePriceTickService
|
||||
{
|
||||
public function tick(): void
|
||||
{
|
||||
$config = app(ConfigService::class);
|
||||
$sync = app(ProductPriceSyncService::class);
|
||||
|
||||
if ($config->isStore()) {
|
||||
$sync->syncStorePrices(true);
|
||||
$source = 'zaarbox';
|
||||
$sourceUrl = app(ZaarboxPriceService::class)->apiUrl();
|
||||
} else {
|
||||
$sync->syncAll(true);
|
||||
$source = $config->priceProvider() ?: 'legacy';
|
||||
$sourceUrl = $source === 'zaarbox'
|
||||
? app(ZaarboxPriceService::class)->apiUrl()
|
||||
: null;
|
||||
}
|
||||
|
||||
$liquidGold = (int) $config->getInt('montzar_live_price', 0);
|
||||
if ($liquidGold <= 0) {
|
||||
$liquidGold = app(ProductDisplayPriceService::class)->resolveHistoricalBasePrice();
|
||||
}
|
||||
|
||||
$activeProducts = Product::query()
|
||||
->where('active', 1)
|
||||
->whereNotNull('reading_method')
|
||||
->count();
|
||||
|
||||
app(PriceSyncLogService::class)->recordTick($source, $sourceUrl, $liquidGold, $activeProducts);
|
||||
|
||||
$request = Request::create('/api/price/last', 'GET', ['force_refresh' => '1']);
|
||||
$response = app(ReportController::class)->lastPrice($request);
|
||||
$payload = json_decode($response->getContent(), true);
|
||||
|
||||
if (is_array($payload)) {
|
||||
app(PriceSnapshotService::class)->recordTick($payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Price;
|
||||
use App\Models\PriceCandle;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class PriceSnapshotService
|
||||
{
|
||||
/** هر تیک کران — ذخیره در prices برای نمودار (هرگز صفر ثبت نمیشود) */
|
||||
public function recordTick(array $payload): void
|
||||
{
|
||||
$priceValue = (int) ($payload['price'] ?? 0);
|
||||
if ($priceValue <= 30_000_000) {
|
||||
$last = Price::orderByDesc('id')->first();
|
||||
if (! $last || (int) $last->price <= 30_000_000) {
|
||||
return;
|
||||
}
|
||||
$priceValue = (int) $last->price;
|
||||
}
|
||||
|
||||
Price::create([
|
||||
'price' => $priceValue,
|
||||
'date_at' => Carbon::now()->toDateString(),
|
||||
'time_at' => Carbon::now()->toTimeString(),
|
||||
'user_id' => null,
|
||||
]);
|
||||
|
||||
$this->touchCandle($priceValue);
|
||||
}
|
||||
|
||||
private function touchCandle(int $priceValue): void
|
||||
{
|
||||
$candle = PriceCandle::whereDate('date_at', date('Y-m-d'))->first();
|
||||
|
||||
if ($candle) {
|
||||
$candle->end = $priceValue;
|
||||
if ($priceValue < $candle->low) {
|
||||
$candle->low = $priceValue;
|
||||
} elseif ($priceValue > $candle->high) {
|
||||
$candle->high = $priceValue;
|
||||
}
|
||||
$candle->save();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
PriceCandle::create([
|
||||
'start' => $priceValue,
|
||||
'end' => $priceValue,
|
||||
'high' => $priceValue,
|
||||
'low' => $priceValue,
|
||||
'date_at' => date('Y-m-d'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* پاکسازی شبانه:
|
||||
* - قبل از ۳۱ روز: حذف
|
||||
* - ۷ تا ۳۱ روز: هر ۱۲ ساعت یک رکورد
|
||||
* - ۲۴ ساعت تا ۷ روز: هر ۳۰ دقیقه یک رکورد
|
||||
* - ۲۴ ساعت اخیر: نگهداری همه (هر ۱۰ ثانیه)
|
||||
*/
|
||||
public function pruneHistory(): array
|
||||
{
|
||||
$deletedOld = Price::query()
|
||||
->where('created_at', '<', now()->subDays(31))
|
||||
->delete();
|
||||
|
||||
$deleted7to31 = $this->downsampleRange(now()->subDays(31), now()->subDays(7), 12 * 3600);
|
||||
$deleted1to7 = $this->downsampleRange(now()->subDays(7), now()->subDay(), 30 * 60);
|
||||
|
||||
return [
|
||||
'deleted_older_than_31d' => $deletedOld,
|
||||
'downsampled_7_to_31d' => $deleted7to31,
|
||||
'downsampled_1_to_7d' => $deleted1to7,
|
||||
];
|
||||
}
|
||||
|
||||
private function downsampleRange(Carbon $from, Carbon $to, int $bucketSeconds): int
|
||||
{
|
||||
$rows = Price::query()
|
||||
->where('created_at', '>=', $from)
|
||||
->where('created_at', '<', $to)
|
||||
->orderBy('created_at')
|
||||
->get(['id', 'created_at']);
|
||||
|
||||
if ($rows->isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$keepIds = [];
|
||||
$lastBucket = null;
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$ts = $row->created_at?->timestamp ?? 0;
|
||||
$bucket = (int) floor($ts / $bucketSeconds);
|
||||
if ($bucket !== $lastBucket) {
|
||||
$keepIds[] = $row->id;
|
||||
$lastBucket = $bucket;
|
||||
} else {
|
||||
$keepIds[count($keepIds) - 1] = $row->id;
|
||||
}
|
||||
}
|
||||
|
||||
return Price::query()
|
||||
->where('created_at', '>=', $from)
|
||||
->where('created_at', '<', $to)
|
||||
->whereNotIn('id', $keepIds)
|
||||
->delete();
|
||||
}
|
||||
|
||||
/** @return array{DAILY: array<int, array{timestamp: int, value: int}>, WEEKLY: array, MONTHLY: array} */
|
||||
public function chartFromDatabase(): array
|
||||
{
|
||||
$daily = Price::query()
|
||||
->where('created_at', '>=', now()->subDay())
|
||||
->orderBy('created_at')
|
||||
->get(['price', 'created_at']);
|
||||
|
||||
$dailyPoints = $daily->map(function (Price $row) {
|
||||
$ts = ($row->created_at ?? now())->timestamp * 1000;
|
||||
|
||||
return ['timestamp' => $ts, 'value' => (int) $row->price];
|
||||
})->values()->all();
|
||||
|
||||
$weekRows = Price::query()
|
||||
->where('created_at', '>=', now()->subDays(7))
|
||||
->orderBy('created_at')
|
||||
->get(['id', 'price', 'created_at']);
|
||||
|
||||
$weeklyPoints = $this->bucketRows($weekRows, 30 * 60);
|
||||
|
||||
$monthRows = Price::query()
|
||||
->where('created_at', '>=', now()->subDays(31))
|
||||
->orderBy('created_at')
|
||||
->get(['id', 'price', 'created_at']);
|
||||
|
||||
$monthlyPoints = $this->bucketRows($monthRows, 12 * 3600);
|
||||
|
||||
return [
|
||||
'DAILY' => $dailyPoints,
|
||||
'WEEKLY' => $weeklyPoints,
|
||||
'MONTHLY' => $monthlyPoints,
|
||||
];
|
||||
}
|
||||
|
||||
/** @param \Illuminate\Support\Collection<int, Price> $rows */
|
||||
private function bucketRows($rows, int $bucketSeconds): array
|
||||
{
|
||||
$points = [];
|
||||
$lastBucket = null;
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$ts = $row->created_at?->timestamp ?? 0;
|
||||
$bucket = (int) floor($ts / $bucketSeconds);
|
||||
$point = [
|
||||
'timestamp' => $ts * 1000,
|
||||
'value' => (int) $row->price,
|
||||
];
|
||||
|
||||
if ($bucket !== $lastBucket) {
|
||||
$points[] = $point;
|
||||
$lastBucket = $bucket;
|
||||
} else {
|
||||
$points[count($points) - 1] = $point;
|
||||
}
|
||||
}
|
||||
|
||||
return $points;
|
||||
}
|
||||
}
|
||||
@@ -56,4 +56,20 @@ class PriceSyncLogService
|
||||
|
||||
return $log->load('items');
|
||||
}
|
||||
|
||||
/** هر تیک کران — حتی بدون تغییر قیمت محصول */
|
||||
public function recordTick(string $source, ?string $sourceUrl, int $liquidGoldPrice, int $productsSynced): ?PriceSyncLog
|
||||
{
|
||||
if (! $this->isAvailable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return PriceSyncLog::create([
|
||||
'source' => $source,
|
||||
'source_url' => $sourceUrl,
|
||||
'liquid_gold_price' => $liquidGoldPrice,
|
||||
'products_updated' => $productsSynced,
|
||||
'created_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ class ProductDisplayPriceService
|
||||
{
|
||||
public const LIVE_PRICE_CACHE_KEY = 'live_price_api_payload';
|
||||
|
||||
public const LIVE_PRICE_CACHE_SECONDS = 45;
|
||||
public const LIVE_PRICE_CACHE_SECONDS = 15;
|
||||
|
||||
public function usesAppPrice(Product $product): bool
|
||||
{
|
||||
@@ -262,6 +262,18 @@ class ProductDisplayPriceService
|
||||
/** @param array<int, array<string, mixed>> $feed */
|
||||
private function applyReferenceFeedPrice(Product $product, array $feed, ZaarboxPriceService $zaarbox): void
|
||||
{
|
||||
if (Product::hasStoredPriceColumns()) {
|
||||
$stored = (int) ($product->price ?? 0) + (int) ($product->price_profit ?? 0);
|
||||
if ($stored > 0) {
|
||||
$product->setAttribute('price', (int) ($product->price ?? 0));
|
||||
if (Schema::hasColumn($product->getTable(), 'price_profit')) {
|
||||
$product->setAttribute('price_profit', (int) ($product->price_profit ?? 0));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$row = $zaarbox->findFeedRow($feed, (string) $product->reading_method);
|
||||
if ($row === null) {
|
||||
$useLast = $this->productUsesLastPriceIfZero($product);
|
||||
|
||||
@@ -20,7 +20,7 @@ class ZaarboxPriceService
|
||||
private const CACHE_TTL_MINUTES = 35;
|
||||
|
||||
/** TTL کش فید — کران هر ۳۰ث refresh میکند؛ list فقط از همین میخواند */
|
||||
private const PRICE_LIST_CACHE_SECONDS = 45;
|
||||
private const PRICE_LIST_CACHE_SECONDS = 15;
|
||||
|
||||
private const DEFAULT_HISTORY_ID = 'ca3800a0-1f57-3528-b963-178f9168e04f';
|
||||
|
||||
@@ -90,6 +90,13 @@ class ZaarboxPriceService
|
||||
*/
|
||||
public function getAllPrices(): array
|
||||
{
|
||||
if ($this->config->getBool('chart_use_local_data', false)) {
|
||||
$local = app(PriceSnapshotService::class)->chartFromDatabase();
|
||||
if (! empty($local['DAILY'])) {
|
||||
return $local;
|
||||
}
|
||||
}
|
||||
|
||||
$cached = Cache::get($this->cacheKey());
|
||||
|
||||
if (is_array($cached) && isset($cached['prices'])) {
|
||||
|
||||
@@ -17,6 +17,8 @@ return [
|
||||
'zaarbox_price_key' => '',
|
||||
/** UUID نمودار Zaarbox — خالی = پیشفرض ca3800a0-... در ZaarboxPriceService */
|
||||
'chart_id' => '',
|
||||
/** false = نمودار از Zaarbox (chart_id) | true = داده ذخیرهشده خود سایت */
|
||||
'chart_use_local_data' => false,
|
||||
'register_jibit_verify' => false,
|
||||
'register_referral_sms_to_referrer' => false,
|
||||
'register_referral_bonus_message' => false,
|
||||
|
||||
@@ -89,8 +89,13 @@ WHERE r.`key` = 'referral_id'
|
||||
-- =============================================================================
|
||||
|
||||
-- قیمت zaarbox (اگر از قبل دارید value دست نخورده)
|
||||
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`)
|
||||
SELECT 'chart_id', '', 'UUID نمودار Zaarbox', 1, 1, 'خالی = ca3800a0-1f57-3528-b963-178f9168e04f'
|
||||
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
|
||||
SELECT 'chart_use_local_data', 'false', 'نمودار از داده خود سایت', 1, 1,
|
||||
'خاموش = Zaarbox (chart_id) | روشن = تاریخچه ذخیرهشده prices', 'switch'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'chart_use_local_data' LIMIT 1);
|
||||
|
||||
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
|
||||
SELECT 'chart_id', '', 'UUID نمودار Zaarbox', 1, 1, 'خالی = ca3800a0-1f57-3528-b963-178f9168e04f', 'text'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'chart_id' LIMIT 1);
|
||||
|
||||
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`)
|
||||
@@ -451,26 +456,26 @@ UPDATE `settings` SET `type` = 'longtext' WHERE `key` IN ('text_rule', 'text_abo
|
||||
|
||||
-- select (+ گزینهها در description)
|
||||
UPDATE `settings` SET `type` = 'select', `description` = 'منبع فعال قیمت|montzar,zaarbox,main_product' WHERE `key` = 'price_provider';
|
||||
UPDATE `settings` SET `type` = 'select', `description` = 'required = اجباری؛ خالی = اختیاری|required,', `can_edit` = 1 WHERE `key` = 'referral_id';
|
||||
UPDATE `settings` SET `type` = 'switch', `title_fa` = 'کد معرف اجباری', `description` = 'روشن = اجباری در ثبتنام | خاموش = اختیاری', `can_edit` = 1 WHERE `key` = 'referral_id';
|
||||
UPDATE `settings` SET `type` = 'select', `description` = 'sepal,' WHERE `key` = 'payping_gateway_name';
|
||||
UPDATE `settings` SET `type` = 'select', `description` = 'bale,telegram,whatsapp,rubika,eitaa,soroush' WHERE `key` = 'support_messenger';
|
||||
|
||||
-- switch
|
||||
UPDATE `settings` SET `type` = 'switch' WHERE `key` IN (
|
||||
'chart_use_local_data',
|
||||
'jibit_verification', 'register_jibit_verify', 'open_register', 'auto_active_customer',
|
||||
'close_price', 'montzar_price', 'main_product_price', 'show_last_price',
|
||||
'close_price', 'show_last_price',
|
||||
'send_to_tahesab', 'profit_gold_api_enabled', 'payping_round_toman', 'payping_strict_failed_withdraw',
|
||||
'has_page_earn_money', 'has_page_fund', 'has_page_permission', 'has_page_price_trigger',
|
||||
'has_page_physical_gold', 'has_chat', 'charge_notif', 'group_sms', 'display_montzar',
|
||||
'has_page_earn_money', 'charge_notif', 'group_sms',
|
||||
'montzar_closed', 'montzar_open_buy', 'montzar_open_sell',
|
||||
'product_update_price_on_list', 'product_auto_close_open_on_list', 'payping_fin_sync_enabled',
|
||||
'fee_free', 'external_order_api_enabled', 'is_store'
|
||||
'payping_fin_sync_enabled', 'external_order_api_enabled', 'is_store',
|
||||
'register_referral_sms_to_referrer', 'register_referral_bonus_message', 'price_from_nasiryan'
|
||||
);
|
||||
|
||||
-- number
|
||||
UPDATE `settings` SET `type` = 'number' WHERE `key` IN (
|
||||
'fee_single', 'buy_fee', 'sell_fee', 'fee_cash', 'fee_service_charge_percentage',
|
||||
'min_withdraw', 'min_first_buy', 'minimum_sell', 'referral_count_for_gift',
|
||||
'fee_single', 'buy_fee', 'sell_fee', 'fee_cash', 'fee_service_charge_percentage', 'fee_free',
|
||||
'min_withdraw', 'min_first_buy', 'minimum_sell',
|
||||
'montzar_product_id', 'balance_report_exclude_customer_id', 'gift_for_referrer',
|
||||
'gift_for_referral', 'up_price', 'down_price', 'payping_fin_customer_id'
|
||||
);
|
||||
@@ -580,10 +585,16 @@ WHERE NOT EXISTS (SELECT 1 FROM `const` WHERE `key` = 'liman' LIMIT 1);
|
||||
-- پاکسازی کلیدهای legacy / تست
|
||||
-- ---------------------------------------------------------------------------
|
||||
DELETE FROM `settings` WHERE `key` IN (
|
||||
'test', 'test2', 'test22', 'test3', 'test33', 'longtest', 'longtest2',
|
||||
'test', 'test2', 'test22', 'test3', 'test33', 'longtest', 'longtest2', 'test*',
|
||||
'is_requaire_referral', 'send_to_goldir', 'goldir_price', 'liman_token',
|
||||
'melted_product_id', 'display_montzar', 'tabanGohar_price', 'etehadie_api_url',
|
||||
'price_method', 'price_primary_source'
|
||||
'price_method', 'price_primary_source', 'main_product_price', 'montzar_price',
|
||||
'has_page_fund', 'has_page_permission', 'has_page_price_trigger', 'has_page_physical_gold',
|
||||
'has_chat', 'product_update_price_on_list', 'product_auto_close_open_on_list',
|
||||
'referral_count_for_gift', 'store_status',
|
||||
'name_en', 'bace_url', 'server_url', 'key', 'color_primary', 'color_secondary',
|
||||
'logo_register', 'logo_login', 'title_pwa', 'title_fav', 'text_rule', 'text_about',
|
||||
'payping_gateway_item'
|
||||
);
|
||||
|
||||
DELETE FROM `const` WHERE `key` = 'log' OR `key` LIKE 'goldir%';
|
||||
@@ -601,11 +612,48 @@ WHERE `key` = 'price_provider';
|
||||
UPDATE `settings` SET `value` = 'montzar'
|
||||
WHERE `key` = 'price_provider' AND LOWER(TRIM(`value`)) IN ('taban_gohar', 'etehadie');
|
||||
|
||||
UPDATE `settings` SET `description` = 'required = اجباری|required,', `title_fa` = 'کد معرف', `type` = 'select'
|
||||
UPDATE `settings` SET
|
||||
`type` = 'switch',
|
||||
`title_fa` = 'کد معرف اجباری',
|
||||
`description` = 'روشن = اجباری در ثبتنام | خاموش = اختیاری',
|
||||
`can_edit` = 1,
|
||||
`is_active` = 1
|
||||
WHERE `key` = 'referral_id';
|
||||
|
||||
UPDATE `settings` SET `description` = 'جایگزین: price_provider=main_product' WHERE `key` = 'main_product_price';
|
||||
UPDATE `settings` SET `description` = 'true یا price_provider=montzar' WHERE `key` = 'montzar_price';
|
||||
UPDATE `settings` SET `value` = 'required'
|
||||
WHERE `key` = 'referral_id'
|
||||
AND LOWER(TRIM(COALESCE(`value`, ''))) IN ('required', 'true', '1', 'yes');
|
||||
|
||||
UPDATE `settings` SET `value` = ''
|
||||
WHERE `key` = 'referral_id'
|
||||
AND LOWER(TRIM(COALESCE(`value`, ''))) NOT IN ('required');
|
||||
|
||||
UPDATE `settings` SET `type` = 'switch', `value` = 'false', `is_active` = 1, `can_edit` = 1
|
||||
WHERE `key` IN ('register_referral_sms_to_referrer', 'register_referral_bonus_message')
|
||||
AND (`type` IS NULL OR TRIM(`type`) = '' OR `type` = 'text')
|
||||
AND TRIM(COALESCE(`value`, '')) = '';
|
||||
|
||||
UPDATE `settings` SET `type` = 'switch', `is_active` = 1
|
||||
WHERE `key` IN ('register_referral_sms_to_referrer', 'register_referral_bonus_message');
|
||||
|
||||
UPDATE `settings` SET `value` = 'false'
|
||||
WHERE `key` IN ('register_referral_sms_to_referrer', 'register_referral_bonus_message')
|
||||
AND TRIM(COALESCE(`value`, '')) = '';
|
||||
|
||||
UPDATE `settings` SET
|
||||
`type` = 'switch',
|
||||
`title_fa` = 'نمودار از داده خود سایت',
|
||||
`description` = 'خاموش = Zaarbox (chart_id) | روشن = تاریخچه prices',
|
||||
`is_active` = 1,
|
||||
`can_edit` = 1
|
||||
WHERE `key` = 'chart_use_local_data';
|
||||
|
||||
UPDATE `settings` SET `type` = 'number', `title_fa` = 'تعداد سفارش اول بدون کارمزد', `description` = 'عدد — ۰ یعنی غیرفعال'
|
||||
WHERE `key` = 'fee_free';
|
||||
|
||||
-- همه تنظیمات قابل ویرایش همیشه فعال
|
||||
UPDATE `settings` SET `is_active` = 1 WHERE `can_edit` = 1 AND (`is_active` IS NULL OR `is_active` = 0);
|
||||
|
||||
UPDATE `settings` SET `description` = 'تعداد سفارش اول بدون کارمزد' WHERE `key` = 'fee_free';
|
||||
UPDATE `settings` SET `description` = 'id یا reading_method محصول آبشده' WHERE `key` = 'main_product';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user