From e70fcef96266458033cf62ead09df89cb722fabc Mon Sep 17 00:00:00 2001 From: adonis Date: Sat, 13 Jun 2026 12:42:42 +0330 Subject: [PATCH] store 6 --- .../Admin/PriceSyncLogController.php | 48 +++++++++++++++ app/Models/PriceSyncLog.php | 32 ++++++++++ app/Models/PriceSyncLogItem.php | 40 +++++++++++++ app/Services/PriceSyncLogService.php | 59 +++++++++++++++++++ app/Services/ProductPriceService.php | 21 ++++++- app/Services/ProductPriceSyncService.php | 19 ++++-- ...2_000002_create_price_sync_logs_tables.php | 45 ++++++++++++++ routes/admin.php | 7 +++ 8 files changed, 264 insertions(+), 7 deletions(-) create mode 100644 app/Http/Controllers/Admin/PriceSyncLogController.php create mode 100644 app/Models/PriceSyncLog.php create mode 100644 app/Models/PriceSyncLogItem.php create mode 100644 app/Services/PriceSyncLogService.php create mode 100644 database/migrations/2026_06_12_000002_create_price_sync_logs_tables.php diff --git a/app/Http/Controllers/Admin/PriceSyncLogController.php b/app/Http/Controllers/Admin/PriceSyncLogController.php new file mode 100644 index 0000000..d9e1507 --- /dev/null +++ b/app/Http/Controllers/Admin/PriceSyncLogController.php @@ -0,0 +1,48 @@ +orderByDesc('id'); + + TableEngine::create($records, $request->all()) + ->initSearch(['source', 'source_url']) + ->select(['*']) + ->export($records); + + return response()->json($records); + } + + public function show($id) + { + $log = PriceSyncLog::with('items')->findOrFail($id); + + return response()->json($log); + } + + public function runSync(ProductPriceSyncService $syncService, ConfigService $configService) + { + if (! $configService->isStore()) { + return response()->json(['message' => 'فقط در حالت فروشگاه'], 422); + } + + $syncService->syncStorePrices(true); + + $log = PriceSyncLog::with('items')->orderByDesc('id')->first(); + + return response()->json([ + 'message' => $log ? 'قیمت‌ها به‌روزرسانی شد' : 'تغییری در قیمت محصولات نبود', + 'data' => $log, + ]); + } +} diff --git a/app/Models/PriceSyncLog.php b/app/Models/PriceSyncLog.php new file mode 100644 index 0000000..45f5c54 --- /dev/null +++ b/app/Models/PriceSyncLog.php @@ -0,0 +1,32 @@ + 'integer', + 'products_updated' => 'integer', + 'created_at' => 'datetime', + ]; + + public function items(): HasMany + { + return $this->hasMany(PriceSyncLogItem::class, 'log_id'); + } +} diff --git a/app/Models/PriceSyncLogItem.php b/app/Models/PriceSyncLogItem.php new file mode 100644 index 0000000..a95c3ae --- /dev/null +++ b/app/Models/PriceSyncLogItem.php @@ -0,0 +1,40 @@ + 'integer', + 'base_price' => 'integer', + 'base_profit' => 'integer', + 'offset_percent' => 'float', + 'stored_price' => 'integer', + 'previous_price' => 'integer', + ]; + + public function log(): BelongsTo + { + return $this->belongsTo(PriceSyncLog::class, 'log_id'); + } +} diff --git a/app/Services/PriceSyncLogService.php b/app/Services/PriceSyncLogService.php new file mode 100644 index 0000000..09ec0c4 --- /dev/null +++ b/app/Services/PriceSyncLogService.php @@ -0,0 +1,59 @@ +> $items + */ + public function record(string $source, ?string $sourceUrl, int $liquidGoldPrice, array $items): ?PriceSyncLog + { + if (! $this->isAvailable() || $items === []) { + return null; + } + + $changedItems = array_values(array_filter( + $items, + static fn (array $item) => (bool) ($item['changed'] ?? false) + )); + + if ($changedItems === []) { + return null; + } + + $log = PriceSyncLog::create([ + 'source' => $source, + 'source_url' => $sourceUrl, + 'liquid_gold_price' => $liquidGoldPrice, + 'products_updated' => count($changedItems), + 'created_at' => now(), + ]); + + foreach ($changedItems as $item) { + PriceSyncLogItem::create([ + 'log_id' => $log->id, + 'product_id' => $item['product_id'] ?? null, + 'product_title' => $item['product_title'] ?? null, + 'reading_method' => $item['reading_method'] ?? null, + 'source_name' => $item['source_name'] ?? null, + 'base_price' => (int) ($item['base_price'] ?? 0), + 'base_profit' => (int) ($item['base_profit'] ?? 0), + 'offset_percent' => (float) ($item['offset_percent'] ?? 0), + 'stored_price' => (int) ($item['new_price'] ?? 0), + 'previous_price' => (int) ($item['old_price'] ?? 0), + ]); + } + + return $log->load('items'); + } +} diff --git a/app/Services/ProductPriceService.php b/app/Services/ProductPriceService.php index 34a8c06..ea536d1 100644 --- a/app/Services/ProductPriceService.php +++ b/app/Services/ProductPriceService.php @@ -26,21 +26,23 @@ class ProductPriceService : 0.0; } - public static function syncFromReference(Product $product, int $basePrice, int $baseProfit = 0): void + /** @return array|null */ + public static function syncFromReference(Product $product, int $basePrice, int $baseProfit = 0, ?string $sourceName = null): ?array { if (!Product::hasStoredPriceColumns()) { - return; + return null; } $offsetPercent = self::offsetPercent($product); $adjustedPrice = self::adjustedTotal($basePrice, $baseProfit, $offsetPercent); + $oldPrice = (int) ($product->price ?? 0); if ( $adjustedPrice <= 0 && Product::hasUseLastPriceIfZeroColumn() && ($product->use_last_price_if_zero ?? false) ) { - return; + return null; } $data = ['price' => $adjustedPrice]; @@ -50,6 +52,19 @@ class ProductPriceService } Product::where('id', $product->id)->update($data); + + return [ + 'product_id' => $product->id, + 'product_title' => $product->title, + 'reading_method' => $product->reading_method, + 'source_name' => $sourceName ?? $product->reading_method, + 'base_price' => $basePrice, + 'base_profit' => $baseProfit, + 'offset_percent' => $offsetPercent, + 'old_price' => $oldPrice, + 'new_price' => $adjustedPrice, + 'changed' => $oldPrice !== $adjustedPrice, + ]; } /** وقتی فقط درصد در ادمین عوض می‌شود (بدون sync جدید) */ diff --git a/app/Services/ProductPriceSyncService.php b/app/Services/ProductPriceSyncService.php index 3f82b81..6e7a603 100644 --- a/app/Services/ProductPriceSyncService.php +++ b/app/Services/ProductPriceSyncService.php @@ -29,8 +29,12 @@ class ProductPriceSyncService */ public function syncStorePrices(bool $forceRefresh = true): bool { - $feed = $this->zaarboxPriceService->cachedPriceList($forceRefresh) - ?? $this->cachedLegacyPriceList($forceRefresh); + $zaarboxFeed = $this->zaarboxPriceService->cachedPriceList($forceRefresh); + $feed = $zaarboxFeed ?? $this->cachedLegacyPriceList($forceRefresh); + $source = $zaarboxFeed !== null ? 'zaarbox' : 'legacy'; + $sourceUrl = $source === 'zaarbox' + ? $this->zaarboxPriceService->apiUrl() + : 'http://212.23.201.173:8381/price_list'; if ($feed === null) { return false; @@ -43,6 +47,7 @@ class ProductPriceSyncService } $synced = false; + $logItems = []; foreach ($products as $product) { $row = $this->zaarboxPriceService->findFeedRow($feed, (string) $product->reading_method); @@ -53,9 +58,13 @@ class ProductPriceSyncService $row = $this->zaarboxPriceService->normalizeFeedRow($row); $basePrice = (int) ($row['price'] ?? 0); $baseProfit = (int) ($row['profit'] ?? 0); + $sourceName = trim((string) ($row['name'] ?? $product->reading_method)); - ProductPriceService::syncFromReference($product, $basePrice, $baseProfit); - $synced = true; + $result = ProductPriceService::syncFromReference($product, $basePrice, $baseProfit, $sourceName); + if ($result !== null) { + $logItems[] = $result; + $synced = true; + } if ((int) ($product->bubble ?? 0) === 1 && $liquidGoldPrice > 0 && $basePrice > 0) { $p = Product::find($product->id); @@ -66,6 +75,8 @@ class ProductPriceSyncService } } + app(PriceSyncLogService::class)->record($source, $sourceUrl, $liquidGoldPrice, $logItems); + return $synced; } diff --git a/database/migrations/2026_06_12_000002_create_price_sync_logs_tables.php b/database/migrations/2026_06_12_000002_create_price_sync_logs_tables.php new file mode 100644 index 0000000..5bb4e3b --- /dev/null +++ b/database/migrations/2026_06_12_000002_create_price_sync_logs_tables.php @@ -0,0 +1,45 @@ +id(); + $table->string('source', 32); + $table->string('source_url', 512)->nullable(); + $table->unsignedBigInteger('liquid_gold_price')->default(0); + $table->unsignedSmallInteger('products_updated')->default(0); + $table->timestamp('created_at')->nullable(); + }); + } + + if (! Schema::hasTable('price_sync_log_items')) { + Schema::create('price_sync_log_items', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('log_id'); + $table->unsignedBigInteger('product_id')->nullable(); + $table->string('product_title', 255)->nullable(); + $table->string('reading_method', 128)->nullable(); + $table->string('source_name', 255)->nullable(); + $table->unsignedBigInteger('base_price')->default(0); + $table->unsignedBigInteger('base_profit')->default(0); + $table->decimal('offset_percent', 8, 2)->default(0); + $table->unsignedBigInteger('stored_price')->default(0); + $table->unsignedBigInteger('previous_price')->default(0); + $table->index('log_id'); + }); + } + } + + public function down(): void + { + Schema::dropIfExists('price_sync_log_items'); + Schema::dropIfExists('price_sync_logs'); + } +}; diff --git a/routes/admin.php b/routes/admin.php index 159ab27..bdafd07 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -20,6 +20,7 @@ use App\Http\Controllers\Site\OfferCodesController; use App\Http\Controllers\Admin\SettingsController; use \App\Http\Controllers\Admin\GiftCardController; use App\Http\Controllers\Admin\CategoryController; +use App\Http\Controllers\Admin\PriceSyncLogController; use App\Services\ConfigService; @@ -112,6 +113,12 @@ Route::middleware('auth:user')->group(function (){ Route::post('price/table', [PriceController::class, 'table']); Route::post('prices', [PriceController::class, 'priceData']); Route::post('prices/chart', [PriceController::class, 'chartPrices']); + + if (app(ConfigService::class)->isStore()) { + Route::post('price-sync-logs/table', [PriceSyncLogController::class, 'table']); + Route::get('price-sync-logs/{id}', [PriceSyncLogController::class, 'show']); + Route::post('price-sync/run', [PriceSyncLogController::class, 'runSync']); + } Route::get('zaarbox/products', [PriceController::class, 'zaarboxProducts']);