where('status', TransactionStatus::PENDING->value) ->whereNotNull('proof_image') ->count(); return response()->json(['error' => false, 'data' => ['count' => $count]]); } public function index(Request $request): JsonResponse { $query = Transaction::with('customer') ->where('type', 'manual') ->orderByDesc('created_at'); if ($request->filled('status')) { $query->where('status', $request->integer('status')); } if ($request->filled('filter')) { $search = $request->string('filter')->trim(); $query->where(function ($q) use ($search) { $q->where('public_code', 'like', "%{$search}%") ->orWhereHas('customer', fn($c) => $c ->where('fullname', 'like', "%{$search}%") ->orWhere('email', 'like', "%{$search}%")); }); } $perPage = $request->integer('per_page', 15); $paginated = $query->paginate($perPage); $items = $paginated->map(fn(Transaction $t) => [ 'id' => $t->id, 'uuid' => $t->uuid, 'public_code' => $t->public_code, 'trans' => $t->trans, 'amount' => $t->amount, 'type' => $t->type, 'status' => $t->status, 'status_label' => TransactionStatus::from((int) $t->status)->label(), 'proof_image' => $t->proof_image ? Storage::disk('public')->url($t->proof_image) : null, 'created_at' => $t->created_at?->toISOString(), 'customer' => $t->customer ? [ 'uuid' => $t->customer->uuid, 'fullname' => $t->customer->fullname, 'email' => $t->customer->email, 'phone' => $t->customer->phone, 'total' => $t->customer->total, 'total_pending' => $t->customer->total_pending, ] : null, ]); return response()->json([ 'error' => false, 'data' => [ 'items' => $items, 'total' => $paginated->total(), 'per_page' => $paginated->perPage(), 'current_page' => $paginated->currentPage(), 'last_page' => $paginated->lastPage(), ], ]); } public function approve(string $uuid): JsonResponse { $transaction = Transaction::with('customer') ->where('uuid', $uuid) ->where('type', 'manual') ->where('status', TransactionStatus::PENDING->value) ->first(); if (!$transaction) { return response()->json([ 'error' => true, 'message' => 'Giao dịch không tồn tại hoặc không thể duyệt', ], 404); } DB::beginTransaction(); try { $transaction->update(['status' => TransactionStatus::COMPLETED->value]); $customer = $transaction->customer; $customer->total += $transaction->amount; $customer->credit += (int) floor($transaction->amount / 100); $customer->total_pending = max(0, $customer->total_pending - $transaction->amount); $customer->save(); Log::create([ 'agent' => 'admin', 'client_ip' => request()->ip(), 'description' => 'Admin duyệt nạp tiền thủ công: ' . number_format($transaction->amount) . ' VNĐ (+' . floor($transaction->amount / 100) . ' credit)', 'customer_id' => $customer->id, ]); Notification::notifyCustomer($customer, [ 'type' => 'deposit_success', 'title' => 'Nạp tiền thành công', 'body' => 'Giao dịch ' . $transaction->public_code . ' đã được duyệt.', 'link' => '/lich-su-giao-dich/' . $transaction->id, 'data' => [ 'transaction_uuid' => $transaction->uuid, 'amount' => $transaction->amount, 'public_code' => $transaction->public_code, ], ]); DB::commit(); } catch (\Exception $e) { DB::rollBack(); FacadesLog::error('AdminTransactionController::approve error: ' . $e->getMessage()); return response()->json([ 'error' => true, 'message' => 'Có lỗi xảy ra, vui lòng thử lại', ], 500); } return response()->json([ 'error' => false, 'message' => 'Đã duyệt giao dịch thành công', ]); } public function reject(string $uuid): JsonResponse { $transaction = Transaction::with('customer') ->where('uuid', $uuid) ->where('type', 'manual') ->where('status', TransactionStatus::PENDING->value) ->first(); if (!$transaction) { return response()->json([ 'error' => true, 'message' => 'Giao dịch không tồn tại hoặc không thể từ chối', ], 404); } DB::beginTransaction(); try { $transaction->update(['status' => TransactionStatus::FAILED->value]); $customer = $transaction->customer; $customer->total_pending = max(0, $customer->total_pending - $transaction->amount); $customer->save(); Log::create([ 'agent' => 'admin', 'client_ip' => request()->ip(), 'description' => 'Admin từ chối nạp tiền thủ công: ' . number_format($transaction->amount) . ' VNĐ', 'customer_id' => $customer->id, ]); DB::commit(); } catch (\Exception $e) { DB::rollBack(); FacadesLog::error('AdminTransactionController::reject error: ' . $e->getMessage()); return response()->json([ 'error' => true, 'message' => 'Có lỗi xảy ra, vui lòng thử lại', ], 500); } return response()->json([ 'error' => false, 'message' => 'Đã từ chối giao dịch', ]); } }