feat: enhance risk pool management and role validation

- Updated AGENTS.md to streamline information on risk pool operations and agent account restrictions.
- Introduced filtering options in AdminRiskPoolIndexController for active risk pools.
- Refactored AdminRiskPoolLockLogIndexController to support grouping by ticket or entry.
- Enhanced AdminRoleStoreController and AdminRoleUpdateController to prevent the use of reserved slugs for new roles.
- Improved error messaging for role creation and updates to clarify reserved role restrictions.
- Added new API routes for ticket item retrieval to improve admin functionalities.
This commit is contained in:
2026-06-15 17:21:53 +08:00
parent 5b6d4cb74d
commit 606ed6817e
14 changed files with 440 additions and 49 deletions

View File

@@ -0,0 +1,126 @@
<?php
namespace App\Services\Admin;
use App\Models\Draw;
use App\Models\RiskPoolLockLog;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
/**
* 风险占用流水查询:按号码明细或按注单聚合。
*/
final class AdminRiskPoolLockLogQueryService
{
/**
* @return LengthAwarePaginator<int, object>
*/
public function paginateEntries(Draw $draw, Request $request, int $page, int $perPage): LengthAwarePaginator
{
$action = trim((string) $request->query('action_type', ''));
$number = trim((string) $request->query('normalized_number', ''));
$ticketItemId = (int) $request->integer('ticket_item_id', 0);
$q = RiskPoolLockLog::query()
->where('draw_id', $draw->id)
->with(['ticketItem:id,ticket_no,play_code,player_id'])
->orderByDesc('created_at')
->orderByDesc('id');
if ($action !== '' && in_array($action, ['lock', 'release'], true)) {
$q->where('action_type', $action);
}
if ($number !== '' && preg_match('/^[0-9]{4}$/', $number) === 1) {
$q->where('normalized_number', $number);
}
if ($ticketItemId > 0) {
$q->where('ticket_item_id', $ticketItemId);
}
return $q->paginate($perPage, ['*'], 'page', $page);
}
/**
* @return LengthAwarePaginator<int, object>
*/
public function paginateByTicket(Draw $draw, Request $request, int $page, int $perPage): LengthAwarePaginator
{
$action = trim((string) $request->query('action_type', ''));
$number = trim((string) $request->query('normalized_number', ''));
$ticketItemId = (int) $request->integer('ticket_item_id', 0);
$bindings = ['draw_id' => $draw->id];
$filters = 'l.draw_id = :draw_id AND l.ticket_item_id IS NOT NULL';
if ($action !== '' && in_array($action, ['lock', 'release'], true)) {
$filters .= ' AND l.action_type = :action_type';
$bindings['action_type'] = $action;
}
if ($number !== '' && preg_match('/^[0-9]{4}$/', $number) === 1) {
$filters .= ' AND l.normalized_number = :normalized_number';
$bindings['normalized_number'] = $number;
}
if ($ticketItemId > 0) {
$filters .= ' AND l.ticket_item_id = :ticket_item_id';
$bindings['ticket_item_id'] = $ticketItemId;
}
$driver = DB::connection()->getDriverName();
$lockCountExpr = $driver === 'sqlite'
? "SUM(CASE WHEN l.action_type = 'lock' THEN 1 ELSE 0 END)"
: "COUNT(*) FILTER (WHERE l.action_type = 'lock')";
$releaseCountExpr = $driver === 'sqlite'
? "SUM(CASE WHEN l.action_type = 'release' THEN 1 ELSE 0 END)"
: "COUNT(*) FILTER (WHERE l.action_type = 'release')";
$lockAmountExpr = $driver === 'sqlite'
? "COALESCE(SUM(CASE WHEN l.action_type = 'lock' THEN l.amount ELSE 0 END), 0)"
: "COALESCE(SUM(l.amount) FILTER (WHERE l.action_type = 'lock'), 0)";
$releaseAmountExpr = $driver === 'sqlite'
? "COALESCE(SUM(CASE WHEN l.action_type = 'release' THEN l.amount ELSE 0 END), 0)"
: "COALESCE(SUM(l.amount) FILTER (WHERE l.action_type = 'release'), 0)";
$aggregateSql = <<<SQL
SELECT
l.ticket_item_id AS ticket_item_id,
ti.ticket_no AS ticket_no,
ti.play_code AS play_code,
ti.original_number AS original_number,
ti.combination_count AS combination_count,
ti.player_id AS player_id,
{$lockCountExpr} AS lock_entry_count,
{$releaseCountExpr} AS release_entry_count,
{$lockAmountExpr} AS total_lock_amount,
{$releaseAmountExpr} AS total_release_amount,
COUNT(DISTINCT l.normalized_number) AS number_count,
MAX(l.created_at) AS last_at
FROM risk_pool_lock_logs l
INNER JOIN ticket_items ti ON ti.id = l.ticket_item_id
WHERE {$filters}
GROUP BY l.ticket_item_id, ti.ticket_no, ti.play_code, ti.original_number, ti.combination_count, ti.player_id
SQL;
$countSql = "SELECT COUNT(*) AS aggregate_count FROM ({$aggregateSql}) AS grouped";
$total = (int) (DB::selectOne($countSql, $bindings)->aggregate_count ?? 0);
$lastPage = max(1, (int) ceil($total / $perPage));
$page = min(max($page, 1), $lastPage);
$offset = ($page - 1) * $perPage;
$items = DB::select(
"{$aggregateSql} ORDER BY last_at DESC, ticket_item_id DESC LIMIT {$perPage} OFFSET {$offset}",
$bindings,
);
return new LengthAwarePaginator(
$items,
$total,
$perPage,
$page,
['path' => $request->url(), 'query' => $request->query()],
);
}
}