feat: add AgentNodeIndexController for node listing and remove settlement_cycle field from AgentProfile logic
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Admin\Agent;
|
||||
|
||||
use App\Support\ApiResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Support\AdminAgentScope;
|
||||
use App\Support\AgentNodePresenter;
|
||||
use App\Models\AgentNode;
|
||||
|
||||
final class AgentNodeIndexController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
$admin = $request->lotteryAdmin();
|
||||
abort_if($admin === null, 401);
|
||||
|
||||
$query = AgentNode::query()->orderBy('admin_site_id')->orderBy('path');
|
||||
|
||||
if (! $admin->isSuperAdmin()) {
|
||||
$actor = AdminAgentScope::primaryAgentNode($admin);
|
||||
if ($actor === null) {
|
||||
$query->whereRaw('0 = 1');
|
||||
} else {
|
||||
$query
|
||||
->where('admin_site_id', (int) $actor->admin_site_id)
|
||||
->where('path', 'like', $actor->path.'%');
|
||||
}
|
||||
}
|
||||
|
||||
return ApiResponse::success([
|
||||
'admin_site_id' => null,
|
||||
'items' => AgentNodePresenter::list($query->get()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,12 @@ final class AdminIntegrationSiteStoreController extends Controller
|
||||
AdminIntegrationSitePresenter::detail($site),
|
||||
$result['secrets'],
|
||||
);
|
||||
$payload['admin_user'] = [
|
||||
'id' => (int) $result['admin_user']->id,
|
||||
'username' => (string) $result['admin_user']->username,
|
||||
'nickname' => (string) $result['admin_user']->name,
|
||||
'email' => $result['admin_user']->email,
|
||||
];
|
||||
|
||||
AuditLogger::recordForAdmin(
|
||||
$admin,
|
||||
|
||||
@@ -3,23 +3,25 @@
|
||||
namespace App\Http\Controllers\Api\V1\Admin\Player;
|
||||
|
||||
use App\Models\Player;
|
||||
use App\Models\AdminSite;
|
||||
use App\Models\AgentNode;
|
||||
use App\Lottery\ErrorCode;
|
||||
use App\Support\ApiMessage;
|
||||
use App\Support\ApiResponse;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Support\AdminAgentScope;
|
||||
use App\Support\AdminSiteScope;
|
||||
use App\Support\AdminAgentScope;
|
||||
use App\Support\PlayerAuthSource;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Support\PlayerFundingMode;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Support\PlayerApiPresenter;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Admin\AdminPlayerStoreRequest;
|
||||
use App\Models\AgentNode;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use App\Services\Agent\AgentProfileService;
|
||||
use App\Services\Agent\RebateLimitValidator;
|
||||
use App\Services\Player\PlayerCreditService;
|
||||
use App\Support\PlayerAuthSource;
|
||||
use App\Support\PlayerFundingMode;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use App\Http\Requests\Admin\AdminPlayerStoreRequest;
|
||||
|
||||
/** POST /api/v1/admin/players */
|
||||
final class AdminPlayerStoreController extends Controller
|
||||
@@ -35,7 +37,7 @@ final class AdminPlayerStoreController extends Controller
|
||||
|
||||
try {
|
||||
$agentProfileService->assertActorMayCreatePlayer($admin);
|
||||
} catch (\Illuminate\Validation\ValidationException $e) {
|
||||
} catch (ValidationException $e) {
|
||||
return ApiMessage::errorResponse(
|
||||
$request,
|
||||
'admin.player_create_capability_forbidden',
|
||||
@@ -110,11 +112,15 @@ final class AdminPlayerStoreController extends Controller
|
||||
}
|
||||
|
||||
$agent = AgentNode::query()->findOrFail($agentNodeId);
|
||||
$rebateRate = 0.0;
|
||||
$extraRebateRate = 0.0;
|
||||
if ($request->has('rebate_rate')) {
|
||||
$rebateRate = (float) $request->input('rebate_rate', 0) / 100;
|
||||
$extraRebateRate = (float) $request->input('extra_rebate_rate', 0) / 100;
|
||||
$rebateLimitValidator->assertPlayerRebateWithinAgent(
|
||||
$agent,
|
||||
(float) $request->input('rebate_rate', 0),
|
||||
(float) $request->input('extra_rebate_rate', 0),
|
||||
$rebateRate,
|
||||
$extraRebateRate,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -122,7 +128,7 @@ final class AdminPlayerStoreController extends Controller
|
||||
? (int) $request->input('credit_limit', 0)
|
||||
: ($isNative ? 0 : 0);
|
||||
|
||||
$player = \Illuminate\Support\Facades\DB::transaction(function () use (
|
||||
$player = DB::transaction(function () use (
|
||||
$agent,
|
||||
$agentProfileService,
|
||||
$playerCreditService,
|
||||
@@ -132,6 +138,8 @@ final class AdminPlayerStoreController extends Controller
|
||||
$agentNodeId,
|
||||
$sitePlayerId,
|
||||
$creditLimit,
|
||||
$rebateRate,
|
||||
$extraRebateRate,
|
||||
): Player {
|
||||
$agentProfileService->assertMayIncreasePlayerCredit($agent, $creditLimit);
|
||||
|
||||
@@ -157,12 +165,12 @@ final class AdminPlayerStoreController extends Controller
|
||||
$agentProfileService->refreshAllocatedCredit($agent);
|
||||
|
||||
if ($request->has('rebate_rate')) {
|
||||
\Illuminate\Support\Facades\DB::table('player_rebate_profiles')->insert([
|
||||
DB::table('player_rebate_profiles')->insert([
|
||||
'player_id' => $player->id,
|
||||
'game_type' => '*',
|
||||
'inherit_from_agent' => false,
|
||||
'rebate_rate' => (float) $request->input('rebate_rate', 0),
|
||||
'extra_rebate_rate' => (float) $request->input('extra_rebate_rate', 0),
|
||||
'rebate_rate' => $rebateRate,
|
||||
'extra_rebate_rate' => $extraRebateRate,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
@@ -180,12 +188,12 @@ final class AdminPlayerStoreController extends Controller
|
||||
return (int) $requested;
|
||||
}
|
||||
|
||||
$siteId = \App\Models\AdminSite::query()->where('code', $siteCode)->value('id');
|
||||
$siteId = AdminSite::query()->where('code', $siteCode)->value('id');
|
||||
if ($siteId === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$rootId = \App\Models\AgentNode::query()
|
||||
$rootId = AgentNode::query()
|
||||
->where('admin_site_id', (int) $siteId)
|
||||
->where('depth', 0)
|
||||
->value('id');
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Admin\Player;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Admin\AdminPlayerUpdateRequest;
|
||||
use App\Models\AgentNode;
|
||||
use App\Models\Player;
|
||||
use App\Models\AgentNode;
|
||||
use App\Support\ApiResponse;
|
||||
use App\Support\AdminSiteScope;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Support\PlayerApiPresenter;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Agent\AgentProfileService;
|
||||
use App\Services\Agent\RebateLimitValidator;
|
||||
use App\Services\Player\PlayerCreditService;
|
||||
use App\Services\Player\PlayerRebateProfileService;
|
||||
use App\Support\AdminSiteScope;
|
||||
use App\Support\ApiResponse;
|
||||
use App\Support\PlayerApiPresenter;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Http\Requests\Admin\AdminPlayerUpdateRequest;
|
||||
|
||||
/** PUT /api/v1/admin/players/{player} */
|
||||
final class AdminPlayerUpdateController extends Controller
|
||||
@@ -44,11 +44,15 @@ final class AdminPlayerUpdateController extends Controller
|
||||
? AgentNode::query()->find((int) $player->agent_node_id)
|
||||
: null;
|
||||
|
||||
$rebateRate = 0.0;
|
||||
$extraRebateRate = 0.0;
|
||||
if ($agent !== null && $request->has('rebate_rate')) {
|
||||
$rebateRate = (float) $request->input('rebate_rate', 0) / 100;
|
||||
$extraRebateRate = (float) $request->input('extra_rebate_rate', 0) / 100;
|
||||
$rebateLimitValidator->assertPlayerRebateWithinAgent(
|
||||
$agent,
|
||||
(float) $request->input('rebate_rate', 0),
|
||||
(float) $request->input('extra_rebate_rate', 0),
|
||||
$rebateRate,
|
||||
$extraRebateRate,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -68,8 +72,8 @@ final class AdminPlayerUpdateController extends Controller
|
||||
['player_id' => $player->id, 'game_type' => '*'],
|
||||
[
|
||||
'inherit_from_agent' => false,
|
||||
'rebate_rate' => (float) $request->input('rebate_rate', 0),
|
||||
'extra_rebate_rate' => (float) $request->input('extra_rebate_rate', 0),
|
||||
'rebate_rate' => $rebateRate,
|
||||
'extra_rebate_rate' => $extraRebateRate,
|
||||
'updated_at' => now(),
|
||||
'created_at' => now(),
|
||||
],
|
||||
|
||||
@@ -30,6 +30,11 @@ final class AdminIntegrationSiteStoreRequest extends ApiFormRequest
|
||||
'iframe_allowed_origins.*' => ['string', 'max:512'],
|
||||
'lottery_h5_base_url' => ['nullable', 'string', 'max:512'],
|
||||
'notes' => ['nullable', 'string', 'max:5000'],
|
||||
'admin_account' => ['required', 'array'],
|
||||
'admin_account.username' => ['required', 'string', 'max:64', Rule::unique('admin_users', 'username')],
|
||||
'admin_account.nickname' => ['required', 'string', 'max:64'],
|
||||
'admin_account.password' => ['required', 'string', 'min:8', 'max:255'],
|
||||
'admin_account.email' => ['nullable', 'email', 'max:255'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ final class AdminPlayerStoreRequest extends ApiFormRequest
|
||||
'status' => ['sometimes', 'integer', 'in:0,1,2'],
|
||||
'agent_node_id' => ['sometimes', 'nullable', 'integer', 'min:1'],
|
||||
'credit_limit' => ['sometimes', 'integer', 'min:0'],
|
||||
'rebate_rate' => ['sometimes', 'numeric', 'min:0', 'max:1'],
|
||||
'extra_rebate_rate' => ['sometimes', 'numeric', 'min:0', 'max:1'],
|
||||
'rebate_rate' => ['sometimes', 'numeric', 'min:0', 'max:100'],
|
||||
'extra_rebate_rate' => ['sometimes', 'numeric', 'min:0', 'max:100'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -25,12 +25,12 @@ final class AdminPlayerUpdateRequest extends ApiFormRequest
|
||||
'default_currency' => ['sometimes', 'string', 'max:16', Rule::exists('currencies', 'code')],
|
||||
'status' => ['sometimes', 'integer', Rule::in([0, 1, 2])],
|
||||
'credit_limit' => ['sometimes', 'integer', 'min:0'],
|
||||
'rebate_rate' => ['sometimes', 'numeric', 'min:0', 'max:1'],
|
||||
'extra_rebate_rate' => ['sometimes', 'numeric', 'min:0', 'max:1'],
|
||||
'rebate_rate' => ['sometimes', 'numeric', 'min:0', 'max:100'],
|
||||
'extra_rebate_rate' => ['sometimes', 'numeric', 'min:0', 'max:100'],
|
||||
'rebate_profiles' => ['sometimes', 'array'],
|
||||
'rebate_profiles.*.game_type' => ['required_with:rebate_profiles', 'string', 'max:32'],
|
||||
'rebate_profiles.*.rebate_rate' => ['sometimes', 'numeric', 'min:0', 'max:1'],
|
||||
'rebate_profiles.*.extra_rebate_rate' => ['sometimes', 'numeric', 'min:0', 'max:1'],
|
||||
'rebate_profiles.*.rebate_rate' => ['sometimes', 'numeric', 'min:0', 'max:100'],
|
||||
'rebate_profiles.*.extra_rebate_rate' => ['sometimes', 'numeric', 'min:0', 'max:100'],
|
||||
'rebate_profiles.*.inherit_from_agent' => ['sometimes', 'boolean'],
|
||||
'risk_tags' => ['sometimes', 'array'],
|
||||
'risk_tags.*' => ['string', 'max:64'],
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
namespace App\Http\Requests\Admin\Concerns;
|
||||
|
||||
use App\Support\AgentSettlementCycle;
|
||||
|
||||
trait AgentProfileFieldRules
|
||||
{
|
||||
/** @return array<string, mixed> */
|
||||
@@ -15,7 +13,6 @@ trait AgentProfileFieldRules
|
||||
'credit_limit' => ['sometimes', 'integer', 'min:0'],
|
||||
'rebate_limit' => ['sometimes', 'numeric', 'min:0', 'max:100'],
|
||||
'default_player_rebate' => ['sometimes', 'numeric', 'min:0', 'max:100'],
|
||||
'settlement_cycle' => ['sometimes', 'string', 'in:'.implode(',', AgentSettlementCycle::VALUES)],
|
||||
'can_grant_extra_rebate' => ['sometimes', 'boolean'],
|
||||
'can_create_child_agent' => ['sometimes', 'boolean'],
|
||||
'can_create_player' => ['sometimes', 'boolean'],
|
||||
@@ -26,12 +23,6 @@ trait AgentProfileFieldRules
|
||||
|
||||
protected function prepareAgentProfileFieldsForValidation(): void
|
||||
{
|
||||
if (! $this->has('settlement_cycle')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->merge([
|
||||
'settlement_cycle' => AgentSettlementCycle::normalize($this->input('settlement_cycle')),
|
||||
]);
|
||||
// 预处理字段(如需要)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ final class AgentProfile extends Model
|
||||
'used_credit',
|
||||
'rebate_limit',
|
||||
'default_player_rebate',
|
||||
'settlement_cycle',
|
||||
'can_grant_extra_rebate',
|
||||
'can_create_child_agent',
|
||||
'can_create_player',
|
||||
|
||||
@@ -77,7 +77,6 @@ final class AgentDashboardOverviewBuilder
|
||||
(int) ($profile?->credit_limit ?? 0) - (int) ($profile?->allocated_credit ?? 0),
|
||||
),
|
||||
'total_share_rate' => (float) ($profile?->total_share_rate ?? 0),
|
||||
'settlement_cycle' => (string) ($profile?->settlement_cycle ?? 'weekly'),
|
||||
'can_create_child_agent' => (bool) ($profile?->can_create_child_agent ?? false),
|
||||
'can_create_player' => (bool) ($profile?->can_create_player ?? false),
|
||||
'direct_child_count' => $directChildCount,
|
||||
|
||||
@@ -30,7 +30,6 @@ final class AgentNodeService
|
||||
* credit_limit?: int,
|
||||
* rebate_limit?: float|int,
|
||||
* default_player_rebate?: float|int,
|
||||
* settlement_cycle?: string,
|
||||
* can_grant_extra_rebate?: bool,
|
||||
* can_create_child_agent?: bool,
|
||||
* can_create_player?: bool
|
||||
@@ -125,7 +124,6 @@ final class AgentNodeService
|
||||
'credit_limit' => (int) ($payload['credit_limit'] ?? 0),
|
||||
'rebate_limit' => (float) ($payload['rebate_limit'] ?? 0),
|
||||
'default_player_rebate' => (float) ($payload['default_player_rebate'] ?? 0),
|
||||
'settlement_cycle' => (string) ($payload['settlement_cycle'] ?? 'weekly'),
|
||||
'can_grant_extra_rebate' => (bool) ($payload['can_grant_extra_rebate'] ?? false),
|
||||
'can_create_child_agent' => (bool) ($payload['can_create_child_agent'] ?? false),
|
||||
'can_create_player' => (bool) ($payload['can_create_player'] ?? true),
|
||||
|
||||
@@ -7,7 +7,6 @@ use App\Models\AgentNode;
|
||||
use App\Models\AgentProfile;
|
||||
use App\Support\AdminAgentScope;
|
||||
use App\Support\AgentOverdueGuard;
|
||||
use App\Support\AgentSettlementCycle;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
@@ -86,9 +85,6 @@ final class AgentProfileService
|
||||
'credit_limit' => $creditLimit,
|
||||
'rebate_limit' => $rebateLimit,
|
||||
'default_player_rebate' => $defaultRebate,
|
||||
'settlement_cycle' => AgentSettlementCycle::normalize(
|
||||
$payload['settlement_cycle'] ?? $profile->settlement_cycle ?? 'weekly',
|
||||
),
|
||||
'can_grant_extra_rebate' => (bool) ($payload['can_grant_extra_rebate'] ?? $profile->can_grant_extra_rebate ?? false),
|
||||
'can_create_child_agent' => (bool) ($payload['can_create_child_agent'] ?? ($isNew ? false : $profile->can_create_child_agent)),
|
||||
'can_create_player' => (bool) ($payload['can_create_player'] ?? ($isNew ? true : $profile->can_create_player ?? true)),
|
||||
@@ -126,7 +122,6 @@ final class AgentProfileService
|
||||
'available_credit' => $available,
|
||||
'rebate_limit' => round((float) $profile->rebate_limit * 100, 4),
|
||||
'default_player_rebate' => round((float) $profile->default_player_rebate * 100, 4),
|
||||
'settlement_cycle' => AgentSettlementCycle::normalize($profile->settlement_cycle),
|
||||
'can_grant_extra_rebate' => (bool) $profile->can_grant_extra_rebate,
|
||||
'can_create_child_agent' => (bool) $profile->can_create_child_agent,
|
||||
'can_create_player' => (bool) $profile->can_create_player,
|
||||
|
||||
@@ -105,7 +105,6 @@ final class AgentSiteProvisioningService
|
||||
'credit_limit' => (int) ($payload['credit_limit'] ?? $defaults['credit_limit'] ?? 0),
|
||||
'rebate_limit' => (float) ($payload['rebate_limit'] ?? $defaults['rebate_limit'] ?? 0),
|
||||
'default_player_rebate' => (float) ($payload['default_player_rebate'] ?? $defaults['default_player_rebate'] ?? 0),
|
||||
'settlement_cycle' => (string) ($payload['settlement_cycle'] ?? $defaults['settlement_cycle'] ?? 'weekly'),
|
||||
'can_grant_extra_rebate' => (bool) ($payload['can_grant_extra_rebate'] ?? true),
|
||||
'can_create_child_agent' => (bool) ($payload['can_create_child_agent'] ?? true),
|
||||
'can_create_player' => (bool) ($payload['can_create_player'] ?? true),
|
||||
|
||||
@@ -15,6 +15,7 @@ final class RebateLimitValidator
|
||||
return;
|
||||
}
|
||||
|
||||
// Both $rebateRate and $profile->rebate_limit are ratios (0-1)
|
||||
$limit = (float) $profile->rebate_limit;
|
||||
if ($rebateRate > $limit) {
|
||||
throw ValidationException::withMessages([
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace App\Services\AgentSettlement;
|
||||
|
||||
use App\Models\Player;
|
||||
use App\Models\AgentNode;
|
||||
use App\Models\AgentProfile;
|
||||
use App\Models\Player;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
final class BetSettlementSnapshotBuilder
|
||||
|
||||
@@ -3,25 +3,45 @@
|
||||
namespace App\Services\Integration;
|
||||
|
||||
use App\Models\AdminSite;
|
||||
use App\Models\AdminRole;
|
||||
use App\Models\AdminUser;
|
||||
use App\Support\AdminPermissionInheritance;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
final class IntegrationSiteService
|
||||
{
|
||||
/** @var list<string> */
|
||||
private const SITE_ADMIN_PERMISSION_SLUGS = [
|
||||
'prd.agent.manage',
|
||||
'prd.agent.profile.manage',
|
||||
'prd.agent.user.manage',
|
||||
'prd.agent.role.manage',
|
||||
'prd.users.manage',
|
||||
'prd.tickets.view',
|
||||
'prd.report.view',
|
||||
'prd.settlement.agent.view',
|
||||
'prd.settlement.agent.manage',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly PartnerSiteConfigResolver $configResolver,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @return array{site: AdminSite, secrets: array{sso_jwt_secret: string, wallet_api_key: string}}
|
||||
* @return array{site: AdminSite, secrets: array{sso_jwt_secret: string, wallet_api_key: string}, admin_user: AdminUser}
|
||||
*/
|
||||
public function create(array $data): array
|
||||
{
|
||||
$secrets = $this->generateSecrets();
|
||||
|
||||
$site = DB::transaction(function () use ($data, $secrets): AdminSite {
|
||||
return AdminSite::query()->create([
|
||||
['site' => $site, 'admin_user' => $adminUser] = DB::transaction(function () use ($data, $secrets): array {
|
||||
/** @var array{username: string, nickname: string, password: string, email?: string|null} $adminAccount */
|
||||
$adminAccount = $data['admin_account'];
|
||||
|
||||
$site = AdminSite::query()->create([
|
||||
'code' => (string) $data['code'],
|
||||
'name' => (string) $data['name'],
|
||||
'currency_code' => (string) ($data['currency_code'] ?? 'NPR'),
|
||||
@@ -38,11 +58,23 @@ final class IntegrationSiteService
|
||||
'sso_jwt_secret_encrypted' => encrypt($secrets['sso_jwt_secret']),
|
||||
'wallet_api_key_encrypted' => encrypt($secrets['wallet_api_key']),
|
||||
]);
|
||||
|
||||
$role = $this->createSiteAdminRole($site);
|
||||
$adminUser = $this->createSiteAdminUser($site, $role, $adminAccount);
|
||||
|
||||
return [
|
||||
'site' => $site,
|
||||
'admin_user' => $adminUser,
|
||||
];
|
||||
});
|
||||
|
||||
$this->configResolver->forgetCache((string) $site->code);
|
||||
|
||||
return ['site' => $site->fresh(), 'secrets' => $secrets];
|
||||
return [
|
||||
'site' => $site->fresh(),
|
||||
'secrets' => $secrets,
|
||||
'admin_user' => $adminUser->fresh(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,4 +146,53 @@ final class IntegrationSiteService
|
||||
|
||||
return $trimmed === '' ? null : $trimmed;
|
||||
}
|
||||
|
||||
private function createSiteAdminRole(AdminSite $site): AdminRole
|
||||
{
|
||||
$slug = sprintf('site_admin_%s', (string) $site->code);
|
||||
$role = AdminRole::query()->create([
|
||||
'slug' => $slug,
|
||||
'name' => sprintf('%s 站点后台管理员', (string) $site->name),
|
||||
'description' => sprintf('自动创建:站点 %s (%s) 后台管理账号专用角色', (string) $site->name, (string) $site->code),
|
||||
'status' => 1,
|
||||
'is_system' => true,
|
||||
'sort_order' => 900,
|
||||
'scope_type' => AdminRole::SCOPE_SYSTEM,
|
||||
]);
|
||||
|
||||
$role->syncLegacyPermissionSlugs(
|
||||
AdminPermissionInheritance::expand(self::SITE_ADMIN_PERMISSION_SLUGS),
|
||||
);
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{username: string, nickname: string, password: string, email?: string|null} $adminAccount
|
||||
*/
|
||||
private function createSiteAdminUser(AdminSite $site, AdminRole $role, array $adminAccount): AdminUser
|
||||
{
|
||||
$username = trim((string) ($adminAccount['username'] ?? ''));
|
||||
$nickname = trim((string) ($adminAccount['nickname'] ?? ''));
|
||||
$password = (string) ($adminAccount['password'] ?? '');
|
||||
$email = $this->nullableTrim($adminAccount['email'] ?? null);
|
||||
|
||||
if ($username === '' || $nickname === '' || $password === '') {
|
||||
throw ValidationException::withMessages([
|
||||
'admin_account' => ['站点后台管理账号信息不完整。'],
|
||||
]);
|
||||
}
|
||||
|
||||
$user = AdminUser::query()->create([
|
||||
'username' => $username,
|
||||
'name' => $nickname,
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'status' => 0,
|
||||
]);
|
||||
|
||||
$user->syncSystemRoleSlugsForSite((int) $site->id, [(string) $role->slug]);
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
namespace App\Services\Player;
|
||||
|
||||
use App\Models\AgentNode;
|
||||
use App\Services\Agent\RebateLimitValidator;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Services\Agent\RebateLimitValidator;
|
||||
|
||||
final class PlayerRebateProfileService
|
||||
{
|
||||
@@ -25,8 +25,8 @@ final class PlayerRebateProfileService
|
||||
foreach ($profiles as $row) {
|
||||
$gameType = trim((string) ($row['game_type'] ?? '*')) ?: '*';
|
||||
$inherit = (bool) ($row['inherit_from_agent'] ?? false);
|
||||
$rebateRate = (float) ($row['rebate_rate'] ?? 0);
|
||||
$extraRate = (float) ($row['extra_rebate_rate'] ?? 0);
|
||||
$rebateRate = (float) ($row['rebate_rate'] ?? 0) / 100;
|
||||
$extraRate = (float) ($row['extra_rebate_rate'] ?? 0) / 100;
|
||||
|
||||
if (! $inherit) {
|
||||
$this->rebateLimitValidator->assertPlayerRebateWithinAgent($agent, $rebateRate, $extraRate);
|
||||
@@ -56,8 +56,8 @@ final class PlayerRebateProfileService
|
||||
->get()
|
||||
->map(static fn (object $row): array => [
|
||||
'game_type' => (string) $row->game_type,
|
||||
'rebate_rate' => (float) $row->rebate_rate,
|
||||
'extra_rebate_rate' => (float) $row->extra_rebate_rate,
|
||||
'rebate_rate' => round((float) $row->rebate_rate * 100, 4),
|
||||
'extra_rebate_rate' => round((float) $row->extra_rebate_rate * 100, 4),
|
||||
'inherit_from_agent' => (bool) $row->inherit_from_agent,
|
||||
])
|
||||
->all();
|
||||
|
||||
@@ -139,6 +139,7 @@ final class AdminAuthorizationRegistry
|
||||
* platform_only?: bool,
|
||||
* agent_hidden?: bool,
|
||||
* activeMatchPrefix?: string,
|
||||
* activeExact?: bool,
|
||||
* requiredAny?: list<string>
|
||||
* }>
|
||||
*/
|
||||
@@ -146,7 +147,8 @@ final class AdminAuthorizationRegistry
|
||||
{
|
||||
return [
|
||||
['segment' => 'dashboard', 'label' => 'Dashboard', 'href' => '/admin', 'nav_group' => 'overview', 'requiredAny' => ['prd.dashboard.view']],
|
||||
['segment' => 'agents', 'label' => 'Agent lines', 'href' => '/admin/agents', 'nav_group' => 'agent', 'activeMatchPrefix' => '/admin/agents', 'requiredAny' => array_values(array_unique(array_merge(['prd.agent.view', 'prd.agent.manage', 'prd.agent.role.view', 'prd.agent.role.manage', 'prd.agent.user.view', 'prd.agent.user.manage', 'prd.agent-line.provision', 'prd.agent.profile.manage'], AdminPermissionLanguage::requiredAnyPrdSlugs('integration-sites'))))],
|
||||
['segment' => 'agents', 'label' => 'Agent lines', 'href' => '/admin/agents', 'nav_group' => 'agent', 'activeExact' => true, 'requiredAny' => array_values(array_unique(array_merge(['prd.agent.view', 'prd.agent.manage', 'prd.agent.role.view', 'prd.agent.role.manage', 'prd.agent.user.view', 'prd.agent.user.manage', 'prd.agent-line.provision', 'prd.agent.profile.manage'], AdminPermissionLanguage::requiredAnyPrdSlugs('integration-sites'))))],
|
||||
['segment' => 'agent_list', 'label' => 'Agent list', 'href' => '/admin/agents/list', 'nav_group' => 'agent', 'activeMatchPrefix' => '/admin/agents/list', 'requiredAny' => ['prd.agent.view', 'prd.agent.manage', 'prd.agent.role.view', 'prd.agent.role.manage', 'prd.agent.user.view', 'prd.agent.user.manage', 'prd.agent.profile.manage']],
|
||||
['segment' => 'settlement_center', 'label' => 'Credit settlement', 'href' => '/admin/settlement-center', 'nav_group' => 'agent', 'activeMatchPrefix' => '/admin/settlement-center', 'requiredAny' => ['prd.settlement.agent.view', 'prd.settlement.agent.manage']],
|
||||
['segment' => 'draws', 'label' => 'Draws', 'href' => '/admin/draws', 'nav_group' => 'operations', 'requiredAny' => ['prd.draw_result.manage', 'prd.draw_result.view', 'prd.draw_reopen.manage']],
|
||||
['segment' => 'tickets', 'label' => 'Tickets', 'href' => '/admin/tickets', 'nav_group' => 'operations', 'requiredAny' => ['prd.tickets.view']],
|
||||
@@ -155,10 +157,10 @@ final class AdminAuthorizationRegistry
|
||||
['segment' => 'wallet', 'label' => 'Wallet', 'href' => '/admin/wallet/transactions', 'nav_group' => 'finance', 'activeMatchPrefix' => '/admin/wallet', 'requiredAny' => ['prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs', 'prd.wallet_adjust.manage'], 'agent_hidden' => true],
|
||||
['segment' => 'reconcile', 'label' => 'Reconcile', 'href' => '/admin/reconcile', 'nav_group' => 'finance', 'requiredAny' => ['prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs'], 'agent_hidden' => true],
|
||||
['segment' => 'reports', 'label' => 'Reports', 'href' => '/admin/reports', 'nav_group' => 'finance', 'requiredAny' => ['prd.report.view']],
|
||||
['segment' => 'rules_plays', 'label' => 'Play rules', 'href' => '/admin/rules/plays', 'nav_group' => 'rules', 'platform_only' => true, 'requiredAny' => ['prd.play_switch.manage', 'prd.odds.manage', 'prd.odds.view']],
|
||||
['segment' => 'rules_odds', 'label' => 'Odds & rebate', 'href' => '/admin/rules/odds', 'nav_group' => 'rules', 'platform_only' => true, 'requiredAny' => ['prd.odds.manage', 'prd.rebate.manage', 'prd.rebate.view']],
|
||||
['segment' => 'jackpot', 'label' => 'Jackpot', 'href' => '/admin/jackpot', 'nav_group' => 'rules', 'platform_only' => true, 'activeMatchPrefix' => '/admin/jackpot', 'requiredAny' => ['prd.jackpot.manage', 'prd.jackpot.view']],
|
||||
['segment' => 'risk_cap', 'label' => 'Risk cap rules', 'href' => '/admin/risk/cap', 'nav_group' => 'rules', 'platform_only' => true, 'activeMatchPrefix' => '/admin/risk/cap', 'requiredAny' => ['prd.risk_cap.manage', 'prd.risk_cap.view']],
|
||||
['segment' => 'rules_plays', 'label' => 'Play rules', 'href' => '/admin/rules/plays', 'nav_group' => 'platform', 'platform_only' => true, 'requiredAny' => ['prd.play_switch.manage', 'prd.odds.manage', 'prd.odds.view']],
|
||||
['segment' => 'rules_odds', 'label' => 'Odds & rebate', 'href' => '/admin/rules/odds', 'nav_group' => 'platform', 'platform_only' => true, 'requiredAny' => ['prd.odds.manage', 'prd.rebate.manage', 'prd.rebate.view']],
|
||||
['segment' => 'jackpot', 'label' => 'Jackpot', 'href' => '/admin/jackpot', 'nav_group' => 'platform', 'platform_only' => true, 'activeMatchPrefix' => '/admin/jackpot', 'requiredAny' => ['prd.jackpot.manage', 'prd.jackpot.view']],
|
||||
['segment' => 'risk_cap', 'label' => 'Risk cap rules', 'href' => '/admin/risk/cap', 'nav_group' => 'platform', 'platform_only' => true, 'activeMatchPrefix' => '/admin/risk/cap', 'requiredAny' => ['prd.risk_cap.manage', 'prd.risk_cap.view']],
|
||||
['segment' => 'currencies', 'label' => 'Currencies', 'href' => '/admin/currencies', 'nav_group' => 'platform', 'platform_only' => true, 'requiredAny' => ['prd.currency.manage']],
|
||||
['segment' => 'integration', 'label' => 'Integration sites', 'href' => '/admin/config/integration-sites', 'nav_group' => 'platform', 'platform_only' => true, 'activeMatchPrefix' => '/admin/config/integration-sites', 'requiredAny' => AdminPermissionLanguage::requiredAnyPrdSlugs('integration-sites')],
|
||||
['segment' => 'admin_users', 'label' => 'Admin Users', 'href' => '/admin/admin-users', 'nav_group' => 'platform', 'platform_only' => true, 'requiredAny' => ['prd.admin_user.manage']],
|
||||
@@ -279,6 +281,7 @@ final class AdminAuthorizationRegistry
|
||||
* platform_only?: bool,
|
||||
* agent_hidden?: bool,
|
||||
* activeMatchPrefix?: string,
|
||||
* activeExact?: bool,
|
||||
* requiredAny?: list<string>
|
||||
* }>
|
||||
*/
|
||||
@@ -297,6 +300,7 @@ final class AdminAuthorizationRegistry
|
||||
* platform_only?: bool,
|
||||
* agent_hidden?: bool,
|
||||
* activeMatchPrefix?: string,
|
||||
* activeExact?: bool,
|
||||
* requiredAny?: list<string>
|
||||
* }>
|
||||
*/
|
||||
@@ -420,6 +424,7 @@ final class AdminAuthorizationRegistry
|
||||
['code' => 'admin.agent-lines.store', 'module_code' => 'agent', 'name' => '开通代理线路', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/agent-lines', 'route_name' => 'api.v1.admin.agent-lines.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.line.provision'], 'legacy_permission_slugs' => ['prd.agent-line.provision', 'prd.agent.manage']],
|
||||
['code' => 'admin.agent-lines.show', 'module_code' => 'agent', 'name' => '代理线路详情', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-lines/{admin_site}', 'route_name' => 'api.v1.admin.agent-lines.show', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.line.provision', 'agent.node.view', 'agent.node.manage'], 'legacy_permission_slugs' => ['prd.agent-line.provision', 'prd.agent.view', 'prd.agent.manage']],
|
||||
['code' => 'admin.agent-nodes.tree', 'module_code' => 'agent', 'name' => '代理树', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-nodes/tree', 'route_name' => 'api.v1.admin.agent-nodes.tree', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.node.view', 'agent.node.manage', 'agent.role.view', 'agent.role.manage', 'agent.user.view', 'agent.user.manage']],
|
||||
['code' => 'admin.agent-nodes.index', 'module_code' => 'agent', 'name' => '代理列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-nodes', 'route_name' => 'api.v1.admin.agent-nodes.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.node.view', 'agent.node.manage', 'agent.role.view', 'agent.role.manage', 'agent.user.view', 'agent.user.manage']],
|
||||
['code' => 'admin.agent-nodes.store', 'module_code' => 'agent', 'name' => '创建下级代理', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/agent-nodes', 'route_name' => 'api.v1.admin.agent-nodes.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.node.manage']],
|
||||
['code' => 'admin.agent-nodes.show', 'module_code' => 'agent', 'name' => '代理节点详情', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}', 'route_name' => 'api.v1.admin.agent-nodes.show', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.node.view', 'agent.node.manage']],
|
||||
['code' => 'admin.agent-nodes.update', 'module_code' => 'agent', 'name' => '更新代理节点', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}', 'route_name' => 'api.v1.admin.agent-nodes.update', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.node.manage']],
|
||||
|
||||
@@ -36,9 +36,8 @@ final class AgentNodePresenter
|
||||
'allocated_credit' => (int) $profile->allocated_credit,
|
||||
'used_credit' => (int) $profile->used_credit,
|
||||
'available_credit' => max(0, (int) $profile->credit_limit - (int) $profile->allocated_credit),
|
||||
'rebate_limit' => (float) $profile->rebate_limit,
|
||||
'default_player_rebate' => (float) $profile->default_player_rebate,
|
||||
'settlement_cycle' => AgentSettlementCycle::normalize($profile->settlement_cycle),
|
||||
'rebate_limit' => round((float) $profile->rebate_limit * 100, 4),
|
||||
'default_player_rebate' => round((float) $profile->default_player_rebate * 100, 4),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -126,4 +125,26 @@ final class AgentNodePresenter
|
||||
|
||||
return $roots;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iterable<AgentNode> $nodes
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
public static function list(iterable $nodes): array
|
||||
{
|
||||
$nodeList = $nodes instanceof Collection ? $nodes : collect($nodes);
|
||||
$profiles = AgentProfile::query()
|
||||
->whereIn('agent_node_id', $nodeList->pluck('id'))
|
||||
->get()
|
||||
->keyBy('agent_node_id');
|
||||
|
||||
return $nodeList
|
||||
->map(function (AgentNode $node) use ($profiles): array {
|
||||
$profile = $profiles->get($node->id);
|
||||
|
||||
return self::item($node, $profile instanceof AgentProfile ? $profile : null);
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\AgentProfile;
|
||||
use App\Models\Player;
|
||||
use App\Models\AgentNode;
|
||||
use App\Models\AgentProfile;
|
||||
use App\Models\PlayerWallet;
|
||||
use App\Support\PlayerFundingMode;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/** 玩家 API 统一 JSON 形状(列表行 / 详情)。 */
|
||||
@@ -68,8 +68,8 @@ final class PlayerApiPresenter
|
||||
->get()
|
||||
->map(static fn (object $row): array => [
|
||||
'game_type' => (string) $row->game_type,
|
||||
'rebate_rate' => (float) $row->rebate_rate,
|
||||
'extra_rebate_rate' => (float) $row->extra_rebate_rate,
|
||||
'rebate_rate' => round((float) $row->rebate_rate * 100, 4),
|
||||
'extra_rebate_rate' => round((float) $row->extra_rebate_rate * 100, 4),
|
||||
'inherit_from_agent' => (bool) $row->inherit_from_agent,
|
||||
])
|
||||
->all(),
|
||||
@@ -79,7 +79,7 @@ final class PlayerApiPresenter
|
||||
/**
|
||||
* @return array{0: ?float, 1: bool} rebate rate (ratio) and whether inherited from agent
|
||||
*/
|
||||
private static function resolveListRebate(Player $player, ?\App\Models\AgentNode $agent): array
|
||||
private static function resolveListRebate(Player $player, ?AgentNode $agent): array
|
||||
{
|
||||
$row = DB::table('player_rebate_profiles')
|
||||
->where('player_id', $player->id)
|
||||
@@ -87,13 +87,13 @@ final class PlayerApiPresenter
|
||||
->first();
|
||||
|
||||
if ($row !== null && ! (bool) $row->inherit_from_agent) {
|
||||
return [(float) $row->rebate_rate, false];
|
||||
return [round((float) $row->rebate_rate * 100, 4), false];
|
||||
}
|
||||
|
||||
if ($agent !== null) {
|
||||
$profile = AgentProfile::query()->where('agent_node_id', $agent->id)->first();
|
||||
|
||||
return [(float) ($profile?->default_player_rebate ?? 0), true];
|
||||
return [round((float) ($profile?->default_player_rebate ?? 0) * 100, 4), true];
|
||||
}
|
||||
|
||||
return [null, false];
|
||||
|
||||
Reference in New Issue
Block a user