From 699d43fbd43f62ffc63e1f9294233bf0eda40905 Mon Sep 17 00:00:00 2001 From: kang Date: Wed, 20 May 2026 16:24:22 +0800 Subject: [PATCH] =?UTF-8?q?feat(admin):=20=E6=96=B0=E5=A2=9E=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E6=B3=A8=E5=8D=95=E5=88=97=E8=A1=A8=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Ticket/AdminTicketItemIndexController.php | 139 ++++++++++++++++++ .../Requests/Admin/TicketItemListRequest.php | 38 +++++ ...01_add_admin_ticket_items_api_resource.php | 71 +++++++++ routes/api.php | 1 + routes/api/v1/admin/ticket.php | 13 ++ 5 files changed, 262 insertions(+) create mode 100644 app/Http/Controllers/Api/V1/Admin/Ticket/AdminTicketItemIndexController.php create mode 100644 app/Http/Requests/Admin/TicketItemListRequest.php create mode 100644 database/migrations/2026_05_20_000001_add_admin_ticket_items_api_resource.php create mode 100644 routes/api/v1/admin/ticket.php diff --git a/app/Http/Controllers/Api/V1/Admin/Ticket/AdminTicketItemIndexController.php b/app/Http/Controllers/Api/V1/Admin/Ticket/AdminTicketItemIndexController.php new file mode 100644 index 0000000..68dae3d --- /dev/null +++ b/app/Http/Controllers/Api/V1/Admin/Ticket/AdminTicketItemIndexController.php @@ -0,0 +1,139 @@ +validated(); + + $perPage = $this->perPage($request, 'per_page', 20, 100); + $page = $this->page($request); + + $query = TicketItem::query() + ->with([ + 'draw:id,draw_no,business_date', + 'order:id,order_no,currency_code,created_at', + 'player:id,site_code,site_player_id,username,nickname', + ]) + ->orderByDesc('ticket_items.id'); + + if (! empty($validated['player_id'])) { + $query->where('ticket_items.player_id', (int) $validated['player_id']); + } elseif (! empty($validated['player_account'])) { + $term = '%'.addcslashes(trim((string) $validated['player_account']), '%_\\').'%'; + $query->whereHas('player', function ($q) use ($term): void { + $q->where('site_player_id', 'like', $term) + ->orWhere('username', 'like', $term) + ->orWhere('nickname', 'like', $term); + }); + } + + $drawNo = $validated['draw_no'] ?? null; + if (is_string($drawNo) && trim($drawNo) !== '') { + $query->whereHas('draw', fn ($q) => $q->where('draw_no', trim($drawNo))); + } + + $statusInput = $validated['status'] ?? []; + if (is_string($statusInput)) { + $statusInput = [$statusInput]; + } + $statusValues = is_array($statusInput) + ? array_values(array_filter(array_map( + fn ($status) => is_string($status) ? trim($status) : '', + $statusInput, + ))) + : []; + if ($statusValues !== []) { + $query->whereIn('ticket_items.status', $statusValues); + } + + $number = trim((string) ($validated['number'] ?? '')); + if ($number !== '') { + $query->where(function ($q) use ($number): void { + $q->where('ticket_items.original_number', 'like', '%'.$number.'%') + ->orWhere('ticket_items.normalized_number', 'like', '%'.$number.'%') + ->orWhere('ticket_items.ticket_no', 'like', '%'.$number.'%') + ->orWhereHas('order', fn ($order) => $order->where('order_no', 'like', '%'.$number.'%')); + }); + } + + $startDate = $validated['start_date'] ?? null; + if (is_string($startDate) && $startDate !== '') { + $query->whereHas('order', fn ($q) => $q->whereDate('created_at', '>=', $startDate)); + } + + $endDate = $validated['end_date'] ?? null; + if (is_string($endDate) && $endDate !== '') { + $query->whereHas('order', fn ($q) => $q->whereDate('created_at', '<=', $endDate)); + } + + $paginator = $query->paginate(perPage: $perPage, page: $page, columns: ['*']); + + $items = collect($paginator->items())->map(function (TicketItem $row): array { + $totalBet = (int) $row->total_bet_amount; + $actualDeduct = (int) $row->actual_deduct_amount; + $winAmount = (int) $row->win_amount; + $jackpotWin = (int) $row->jackpot_win_amount; + + return [ + 'id' => $row->id, + 'ticket_no' => $row->ticket_no, + 'player_id' => $row->player_id, + 'site_code' => $row->player?->site_code, + 'site_player_id' => $row->player?->site_player_id, + 'username' => $row->player?->username, + 'nickname' => $row->player?->nickname, + 'order_no' => $row->order?->order_no, + 'draw_no' => $row->draw?->draw_no, + 'currency_code' => $row->order?->currency_code, + 'play_code' => $row->play_code, + 'original_number' => $row->original_number, + 'total_bet_amount' => $totalBet, + 'total_bet_amount_formatted' => CurrencyFormatter::fromMinor($totalBet), + 'actual_deduct_amount' => $actualDeduct, + 'actual_deduct_amount_formatted' => CurrencyFormatter::fromMinor($actualDeduct), + 'status' => $row->status, + 'fail_reason_code' => $row->fail_reason_code, + 'fail_reason_text' => $row->fail_reason_text, + 'win_amount' => $winAmount, + 'win_amount_formatted' => CurrencyFormatter::fromMinor($winAmount), + 'jackpot_win_amount' => $jackpotWin, + 'jackpot_win_amount_formatted' => CurrencyFormatter::fromMinor($jackpotWin), + 'placed_at' => $row->order?->created_at?->toIso8601String(), + 'updated_at' => $row->updated_at?->toIso8601String(), + ]; + })->values()->all(); + + return ApiResponse::success([ + 'items' => $items, + 'total' => $paginator->total(), + 'page' => $paginator->currentPage(), + 'per_page' => $paginator->perPage(), + 'last_page' => $paginator->lastPage(), + ]); + } +} diff --git a/app/Http/Requests/Admin/TicketItemListRequest.php b/app/Http/Requests/Admin/TicketItemListRequest.php new file mode 100644 index 0000000..eaa60fa --- /dev/null +++ b/app/Http/Requests/Admin/TicketItemListRequest.php @@ -0,0 +1,38 @@ +> + */ + public function rules(): array + { + return [ + 'page' => ['sometimes', 'integer', 'min:1'], + 'per_page' => ['sometimes', 'integer', 'min:1', 'max:100'], + 'size' => ['sometimes', 'integer', 'min:1', 'max:100'], + 'player_id' => ['sometimes', 'nullable', 'integer', 'min:1'], + 'player_account' => ['sometimes', 'nullable', 'string', 'max:128'], + 'draw_no' => ['sometimes', 'nullable', 'string', 'max:32'], + 'status' => ['sometimes'], + 'status.*' => ['string', 'max:32'], + 'number' => ['sometimes', 'nullable', 'string', 'max:64'], + 'start_date' => ['sometimes', 'nullable', 'date_format:Y-m-d'], + 'end_date' => ['sometimes', 'nullable', 'date_format:Y-m-d'], + ]; + } +} diff --git a/database/migrations/2026_05_20_000001_add_admin_ticket_items_api_resource.php b/database/migrations/2026_05_20_000001_add_admin_ticket_items_api_resource.php new file mode 100644 index 0000000..a22f34f --- /dev/null +++ b/database/migrations/2026_05_20_000001_add_admin_ticket_items_api_resource.php @@ -0,0 +1,71 @@ +where('code', 'admin.tickets.index') + ->value('id'); + + if ($resourceId === null) { + $resourceId = DB::table('admin_api_resources')->insertGetId([ + 'code' => 'admin.tickets.index', + 'module_code' => 'ticket', + 'name' => '后台注单列表', + 'http_method' => 'GET', + 'uri_pattern' => '/api/v1/admin/tickets', + 'route_name' => 'api.v1.admin.tickets.index', + 'auth_mode' => 'permission_required', + 'is_audit_required' => false, + 'status' => 1, + 'meta_json' => null, + 'created_at' => $now, + 'updated_at' => $now, + ]); + } + + $menuActionId = DB::table('admin_menu_actions') + ->where('permission_code', 'service.tickets.view') + ->value('id'); + + if ($menuActionId !== null) { + $exists = DB::table('admin_api_resource_bindings') + ->where('api_resource_id', $resourceId) + ->where('menu_action_id', $menuActionId) + ->exists(); + + if (! $exists) { + DB::table('admin_api_resource_bindings')->insert([ + 'api_resource_id' => $resourceId, + 'menu_action_id' => $menuActionId, + 'created_at' => $now, + 'updated_at' => $now, + ]); + } + } + } + + public function down(): void + { + $resourceId = DB::table('admin_api_resources') + ->where('code', 'admin.tickets.index') + ->value('id'); + + if ($resourceId !== null) { + DB::table('admin_api_resource_bindings') + ->where('api_resource_id', $resourceId) + ->delete(); + + DB::table('admin_api_resources') + ->where('id', $resourceId) + ->delete(); + } + } +}; diff --git a/routes/api.php b/routes/api.php index 0efaa8f..c00eaa3 100644 --- a/routes/api.php +++ b/routes/api.php @@ -26,6 +26,7 @@ Route::prefix('v1')->group(function (): void { require __DIR__.'/api/v1/admin/core.php'; require __DIR__.'/api/v1/admin/wallet.php'; require __DIR__.'/api/v1/admin/player.php'; + require __DIR__.'/api/v1/admin/ticket.php'; require __DIR__.'/api/v1/admin/draw.php'; require __DIR__.'/api/v1/admin/jackpot.php'; require __DIR__.'/api/v1/admin/config.php'; diff --git a/routes/api/v1/admin/ticket.php b/routes/api/v1/admin/ticket.php new file mode 100644 index 0000000..99117de --- /dev/null +++ b/routes/api/v1/admin/ticket.php @@ -0,0 +1,13 @@ +group(function (): void { + Route::get('tickets', AdminTicketItemIndexController::class) + ->name('api.v1.admin.tickets.index'); + });