From a84915033931a09128353316c1c5fe6027716e36 Mon Sep 17 00:00:00 2001 From: adonis Date: Sun, 26 Jul 2026 21:17:57 +0330 Subject: [PATCH] fix bug of pargar 4 --- .../Controllers/Admin/PargarController.php | 9 ++ app/Services/PargarSyncService.php | 95 +++++++++++++++++++ routes/admin.php | 1 + 3 files changed, 105 insertions(+) diff --git a/app/Http/Controllers/Admin/PargarController.php b/app/Http/Controllers/Admin/PargarController.php index 3a32fc1..4bbe6cb 100644 --- a/app/Http/Controllers/Admin/PargarController.php +++ b/app/Http/Controllers/Admin/PargarController.php @@ -33,6 +33,15 @@ class PargarController extends Controller return response()->json(PargarSyncLog::with('customer')->findOrFail($id)); } + /** اسناد پنل که پرگار هنوز تحویل نگرفته — ماندهٔ این مشتری‌ها قفل است */ + public function stuckOutbound(Request $request) + { + $minutes = (int) $request->query('minutes', 15); + $minutes = max(0, min($minutes, 60 * 24 * 30)); + + return response()->json($this->sync()->stuckOutboundDocuments($minutes)); + } + public function customerSyncStatus(Request $request) { $panelTotal = Customer::whereNull('deleted_at')->count(); diff --git a/app/Services/PargarSyncService.php b/app/Services/PargarSyncService.php index 7ce0258..22b9f31 100644 --- a/app/Services/PargarSyncService.php +++ b/app/Services/PargarSyncService.php @@ -7,6 +7,7 @@ use App\Models\Order; use App\Models\PargarSyncLog; use App\Models\Product; use Illuminate\Database\QueryException; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Schema; @@ -405,6 +406,100 @@ class PargarSyncService ]; } + /** + * شناسه‌ای که سند با آن در GET /pargar/orders بیرون می‌رود. + */ + public function outboundExportId(PargarSyncLog $log): string + { + if ($log->type === self::TYPE_ORDER) { + return (string) ($log->order_id ?? $log->id); + } + + $withdrawId = (int) ($log->withdraw_id ?? 0); + + return $withdrawId > 0 ? 'W-'.$withdrawId : 'WLOG-'.$log->id; + } + + /** + * اسنادی که در نوبت GET /pargar/orders هستند و پرگار هنوز تأییدشان نکرده است. + * + * تا وقتی این‌ها معلق‌اند، ماندهٔ پرگارِ همان مشتری روی کیف اعمال نمی‌شود؛ پس باید + * در پنل دیده شوند، نه اینکه با شکایت مشتری کشف شوند. + * + * @return array{ + * count: int, + * customers: int, + * waited_minutes: int, + * oldest_at: string|null, + * last_confirmed_at: string|null, + * threshold_minutes: int, + * rows: list> + * } + */ + public function stuckOutboundDocuments(int $olderThanMinutes = 15, int $limit = 50): array + { + $threshold = now()->subMinutes(max(0, $olderThanMinutes)); + + $logs = PargarSyncLog::with('customer') + ->whereIn('type', [self::TYPE_ORDER, self::TYPE_WALLET_CASH, self::TYPE_WALLET_GOLD]) + ->where('sync_status', self::STATUS_PENDING) + ->where('created_at', '<=', $threshold) + ->where(function ($q) { + // سند کیف تا وقتی pending است در هر fetch فرستاده می‌شود؛ سفارش فقط + // وقتی هنوز در نوبت خروجی است (sts=2 و تأییدنشده). + $q->whereIn('type', [self::TYPE_WALLET_CASH, self::TYPE_WALLET_GOLD]) + ->orWhereExists(function ($sub) { + $sub->select(DB::raw(1)) + ->from('orders') + ->whereColumn('orders.id', 'pargar_sync_logs.order_id') + ->where('orders.sts', 2) + ->whereNull('orders.pargar_confirmed_at'); + }); + }) + ->orderBy('id') + ->get(); + + $oldest = $logs->first()?->created_at; + + return [ + 'count' => $logs->count(), + 'customers' => $logs->pluck('customer_id')->filter()->unique()->count(), + 'waited_minutes' => $oldest ? (int) $oldest->diffInMinutes(now()) : 0, + 'oldest_at' => $oldest?->toDateTimeString(), + 'last_confirmed_at' => $this->lastOutboundConfirmedAt(), + 'threshold_minutes' => max(0, $olderThanMinutes), + 'rows' => $logs->take($limit)->map(fn (PargarSyncLog $log) => [ + 'id' => (int) $log->id, + 'export_id' => $this->outboundExportId($log), + 'type' => (string) $log->type, + 'amount' => $log->amount === null ? null : (float) $log->amount, + 'desc' => (string) ($log->desc ?? ''), + 'order_id' => $log->order_id ? (int) $log->order_id : null, + 'withdraw_id' => $log->withdraw_id ? (int) $log->withdraw_id : null, + 'customer_id' => $log->customer_id ? (int) $log->customer_id : null, + 'customer_name' => $log->customer?->name, + 'customer_mobile' => $log->customer?->mobile, + 'pargar_account_id' => $log->customer?->pargar_account_id, + 'created_at' => $log->created_at?->toDateTimeString(), + 'waited_minutes' => $log->created_at ? (int) $log->created_at->diffInMinutes(now()) : 0, + ])->values()->all(), + ]; + } + + /** آخرین باری که پرگار سندی از پنل را تأیید کرده است */ + private function lastOutboundConfirmedAt(): ?string + { + $query = PargarSyncLog::query() + ->whereIn('type', [self::TYPE_ORDER, self::TYPE_WALLET_CASH, self::TYPE_WALLET_GOLD]) + ->where('sync_status', self::STATUS_SUCCESS); + + $confirmedAt = PargarSyncLog::hasConfirmedAtColumn() + ? $query->clone()->max('confirmed_at') + : null; + + return $confirmedAt ?: $query->max('updated_at'); + } + public function retry(PargarSyncLog $log): array { if (! $this->isFailed($log) && ! empty($log->pargar_ref)) { diff --git a/routes/admin.php b/routes/admin.php index e64c65f..5200f1d 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -214,6 +214,7 @@ Route::middleware('auth:user')->group(function (){ Route::post('pargar/table', [PargarController::class, 'table']); Route::get('pargar/customer-sync/status', [PargarController::class, 'customerSyncStatus']); + Route::get('pargar/stuck-outbound', [PargarController::class, 'stuckOutbound']); Route::post('pargar/customer-sync/panel-to-pargar', [PargarController::class, 'syncPanelToPargar']); Route::post('pargar/customer-sync/import-from-pargar', [PargarController::class, 'importFromPargar']); Route::post('pargar/customer-sync/rollback-import', [PargarController::class, 'rollbackImport']);