feat: 切换 schema dump 基线并增强返点结算与管理校验

This commit is contained in:
2026-06-08 17:41:41 +08:00
parent 2d32f006c5
commit 8d5d7f5b17
130 changed files with 5746 additions and 6723 deletions

View File

@@ -38,8 +38,8 @@ final class RiskCapItemsReplaceController extends Controller
'items' => ['required', 'array', 'min:1'],
'items.*.draw_id' => ['sometimes', 'nullable', 'integer', 'exists:draws,id'],
'items.*.normalized_number' => ['required', 'string', 'size:4', 'regex:/^[0-9]{4}$/'],
'items.*.cap_amount' => ['required', 'integer', 'min:0'],
'items.*.cap_type' => ['required', 'string', 'max:16'],
'items.*.cap_amount' => ['required', 'integer', 'min:1'],
'items.*.cap_type' => ['required', 'string', 'in:default,per_number'],
]);
$service->replaceItems($version, $data['items'], $admin);

View File

@@ -50,6 +50,7 @@ final class AdminCurrencyDestroyController extends Controller
$checks = [
'玩家默认币种' => DB::table('players')->where('default_currency', $code),
'玩家钱包' => DB::table('player_wallets')->where('currency_code', $code),
'站点默认币种' => DB::table('admin_sites')->where('currency_code', $code),
'转账单' => DB::table('transfer_orders')->where('currency_code', $code),
'注单' => DB::table('ticket_orders')->where('currency_code', $code),
'赔率配置' => DB::table('odds_items')->where('currency_code', $code),

View File

@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Api\V1\Admin\Jackpot;
use App\Models\AdminUser;
use App\Models\Draw;
use App\Models\JackpotPool;
use App\Support\ApiResponse;
use App\Support\ApiMessage;
@@ -40,11 +41,21 @@ final class AdminJackpotPoolManualBurstController extends Controller
}
$data = $request->validate([
'draw_id' => 'required|integer|exists:draws,id',
'draw_id' => ['required'],
]);
$drawId = $this->resolveDrawId(trim((string) $data['draw_id']));
if ($drawId === null) {
return ApiResponse::error(
trans('validation.exists', ['attribute' => 'draw_id'], $request->lotteryLocale()),
ErrorCode::ClientHttpError->value,
['draw_id' => [trans('validation.exists', ['attribute' => 'draw_id'], $request->lotteryLocale())]],
422,
);
}
try {
$payload = $this->service->execute($pool, (int) $data['draw_id']);
$payload = $this->service->execute($pool, $drawId);
} catch (\RuntimeException $e) {
return ApiResponse::error(
ApiMessage::get($request, 'jackpot_manual_burst_failed', [
@@ -58,4 +69,22 @@ final class AdminJackpotPoolManualBurstController extends Controller
return ApiResponse::success($payload);
}
private function resolveDrawId(string $drawRef): ?int
{
if ($drawRef === '') {
return null;
}
if (ctype_digit($drawRef)) {
$draw = Draw::query()->whereKey((int) $drawRef)->first();
if ($draw !== null) {
return (int) $draw->id;
}
}
$draw = Draw::query()->where('draw_no', $drawRef)->first();
return $draw !== null ? (int) $draw->id : null;
}
}

View File

@@ -11,7 +11,9 @@ use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller;
use App\Support\AdminSiteScope;
use App\Support\PlayerFundingMode;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
/** DELETE /api/v1/admin/players/{player} */
final class AdminPlayerDestroyController extends Controller
@@ -25,13 +27,36 @@ final class AdminPlayerDestroyController extends Controller
return $denied;
}
$hasWallets = Player::query()
->whereKey($player->getKey())
->whereHas('wallets', static fn (Builder $q) => $q->where('balance', '!=', 0))
if (PlayerFundingMode::usesCredit($player)) {
$creditRow = DB::table('player_credit_accounts')
->where('player_id', $player->getKey())
->first();
$usedCredit = (int) ($creditRow->used_credit ?? 0);
$frozenCredit = (int) ($creditRow->frozen_credit ?? 0);
if ($usedCredit !== 0 || $frozenCredit !== 0) {
return ApiMessage::errorResponse($request, 'admin.player_credit_in_use_blocks_delete', ErrorCode::ValidationFailed->value, null, 422);
}
} else {
$hasWallets = Player::query()
->whereKey($player->getKey())
->whereHas('wallets', static fn (Builder $q) => $q->where('balance', '!=', 0))
->exists();
if ($hasWallets) {
return ApiMessage::errorResponse($request, 'admin.player_wallet_balance_blocks_delete', ErrorCode::ValidationFailed->value, null, 422);
}
}
$hasUnpaidSettlementBills = DB::table('settlement_bills')
->where('owner_type', 'player')
->where('owner_id', $player->getKey())
->whereIn('status', ['confirmed', 'partial_paid', 'overdue'])
->where('unpaid_amount', '>', 0)
->exists();
if ($hasWallets) {
return ApiMessage::errorResponse($request, 'admin.player_wallet_balance_blocks_delete', ErrorCode::ValidationFailed->value, null, 422);
if ($hasUnpaidSettlementBills) {
return ApiMessage::errorResponse($request, 'admin.player_unpaid_settlement_blocks_delete', ErrorCode::ValidationFailed->value, null, 422);
}
$hasTickets = TicketOrder::query()
@@ -42,6 +67,7 @@ final class AdminPlayerDestroyController extends Controller
return ApiMessage::errorResponse($request, 'admin.player_has_tickets_blocks_delete', ErrorCode::ValidationFailed->value, null, 422);
}
DB::table('player_credit_accounts')->where('player_id', $player->getKey())->delete();
$player->wallets()->delete();
$player->delete();

View File

@@ -35,7 +35,7 @@ final class AdminPlayerIndexController extends Controller
if ($keyword !== '') {
$term = '%'.addcslashes($keyword, '%_\\').'%';
$q->where(static function ($sub) use ($term): void {
$q->where(static function ($sub) use ($term, $keyword): void {
$sub->where('site_player_id', 'like', $term)
->orWhere('username', 'like', $term)
->orWhere('nickname', 'like', $term);

View File

@@ -55,7 +55,7 @@ final class AdminPlayerStoreController extends Controller
if ($isNative) {
$sitePlayerId = $sitePlayerId !== ''
? $sitePlayerId
: 'native:'.Str::lower(Str::ulid());
: $this->generateNativeSitePlayerId($siteCode);
}
if ($sitePlayerId === '') {
@@ -192,4 +192,20 @@ final class AdminPlayerStoreController extends Controller
return $rootId !== null ? (int) $rootId : null;
}
private function generateNativeSitePlayerId(string $siteCode): string
{
$prefix = strtoupper(substr(preg_replace('/[^A-Za-z]/', '', $siteCode) ?: 'LP', 0, 2));
$prefix = str_pad($prefix, 2, 'P');
do {
$candidate = sprintf('%s%06d', $prefix, random_int(0, 999999));
$exists = Player::query()
->where('site_code', $siteCode)
->where('site_player_id', $candidate)
->exists();
} while ($exists);
return $candidate;
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Http\Controllers\Api\V1\Admin\Reconcile;
use App\Models\TransferOrder;
use App\Models\ReconcileJob;
use Illuminate\Http\Request;
use App\Models\ReconcileItem;
@@ -20,6 +21,19 @@ final class ReconcileItemIndexController extends Controller
->orderBy('id')
->paginate($p['perPage'], ['*'], 'page', $p['page']);
$transferNos = collect($paginator->items())
->map(fn (ReconcileItem $item) => $item->side_a_ref)
->filter(fn ($value) => is_string($value) && $value !== '')
->values()
->all();
$transferStatuses = $transferNos === []
? []
: TransferOrder::query()
->whereIn('transfer_no', $transferNos)
->pluck('status', 'transfer_no')
->all();
return AdminApiList::jsonWith($paginator, fn (ReconcileItem $r) => [
'id' => (int) $r->id,
'side_a_ref' => $r->side_a_ref,
@@ -27,6 +41,8 @@ final class ReconcileItemIndexController extends Controller
'difference_amount' => (int) $r->difference_amount,
'status' => $r->status,
'resolved_at' => $r->resolved_at?->toIso8601String(),
'is_resolved' => $r->resolved_at !== null || in_array($transferStatuses[$r->side_a_ref ?? ''] ?? null, ['success', 'reversed', 'manually_processed'], true),
'current_transfer_status' => $transferStatuses[$r->side_a_ref ?? ''] ?? null,
'created_at' => $r->created_at?->toIso8601String(),
], [
'job_id' => (int) $reconcile_job->id,

View File

@@ -14,10 +14,16 @@ final class ReportJobIndexController extends Controller
public function __invoke(Request $request): JsonResponse
{
$p = AdminApiList::readPaging($request);
$reportType = trim((string) $request->query('report_type', ''));
$paginator = ReportJob::query()
->orderByDesc('id')
->paginate($p['perPage'], ['*'], 'page', $p['page']);
$query = ReportJob::query()
->orderByDesc('id');
if ($reportType !== '') {
$query->where('report_type', $reportType);
}
$paginator = $query->paginate($p['perPage'], ['*'], 'page', $p['page']);
return AdminApiList::json($paginator, fn (ReportJob $j) => $this->row($j));
}

View File

@@ -46,7 +46,7 @@ final class AdminTicketItemIndexController extends Controller
->with([
'draw:id,draw_no,business_date',
'order:id,order_no,currency_code,created_at',
'player:id,site_code,site_player_id,username,nickname,agent_node_id',
'player:id,site_code,site_player_id,username,nickname,agent_node_id,funding_mode',
'player.agentNode:id,code,name',
])
->orderByDesc('ticket_items.id');
@@ -108,6 +108,8 @@ final class AdminTicketItemIndexController extends Controller
'site_player_id' => $row->player?->site_player_id,
'username' => $row->player?->username,
'nickname' => $row->player?->nickname,
'funding_mode' => $row->player?->funding_mode,
'uses_credit' => $row->player?->funding_mode === 'credit',
'order_no' => $row->order?->order_no,
'draw_no' => $row->draw?->draw_no,
'currency_code' => $row->order?->currency_code,

View File

@@ -67,7 +67,7 @@ final class WalletTransactionListController extends Controller
$query = WalletTxn::query()
->with([
'player:id,site_code,site_player_id,username,nickname,agent_node_id',
'player:id,site_code,site_player_id,username,nickname,agent_node_id,funding_mode,auth_source',
'player.agentNode:id,code,name',
])
->orderByDesc('id');