feat: enhance agent management and validation logic

- Updated AGENTS.md to clarify agent account restrictions and permissions.
- Implemented checks in AgentNodeAdminUserStoreController and AgentNodeRoleStoreController to restrict admin user and role creation to the agent's own node.
- Enhanced validation in AdminPlayerStoreController and AdminPlayerUpdateController to enforce credit limit and rebate rate rules based on player funding mode.
- Refactored various request classes to utilize shared admin account field rules for consistency.
- Improved error handling in services related to credit allocation and rebate limits to ensure proper validation and messaging.
This commit is contained in:
2026-06-14 21:13:27 +08:00
parent 395e1c7400
commit 5b6d4cb74d
56 changed files with 1558 additions and 222 deletions

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api\V1\Admin\Player;
use App\Models\Player;
use App\Models\AdminSite;
use App\Models\AgentNode;
use App\Models\AdminUser;
use App\Lottery\ErrorCode;
use App\Support\ApiMessage;
use App\Support\ApiResponse;
@@ -92,7 +93,7 @@ final class AdminPlayerStoreController extends Controller
$agentNodeId = $admin->isSuperAdmin()
? $this->resolveAgentNodeIdForSuperAdmin($request->validated('agent_node_id'), $siteCode)
: $admin->primaryAgentNodeId();
: $this->resolveAgentNodeIdForNonSuperAdmin($admin, $request->validated('agent_node_id'), $siteCode);
if ($agentNodeId === null) {
return ApiMessage::errorResponse(
@@ -104,17 +105,10 @@ final class AdminPlayerStoreController extends Controller
);
}
if (! $admin->isSuperAdmin()) {
$agent = AdminAgentScope::primaryAgentNode($admin);
if ($agent === null || (int) $agentNodeId !== (int) $agent->id) {
return ApiMessage::errorResponse($request, 'admin.player_create_agent_forbidden', ErrorCode::AdminForbidden->value, null, 403);
}
}
$agent = AgentNode::query()->findOrFail($agentNodeId);
$rebateRate = 0.0;
$extraRebateRate = 0.0;
if ($request->has('rebate_rate')) {
if ($request->has('rebate_rate') || $request->has('extra_rebate_rate')) {
$rebateRate = (float) $request->input('rebate_rate', 0) / 100;
$extraRebateRate = (float) $request->input('extra_rebate_rate', 0) / 100;
$rebateLimitValidator->assertPlayerRebateWithinAgent(
@@ -124,6 +118,12 @@ final class AdminPlayerStoreController extends Controller
);
}
if (! $isNative && ($request->has('credit_limit') || $request->has('rebate_rate') || $request->has('extra_rebate_rate'))) {
throw ValidationException::withMessages([
'credit_limit' => ['wallet_player_prohibited'],
]);
}
$creditLimit = $request->has('credit_limit')
? (int) $request->input('credit_limit', 0)
: ($isNative ? 0 : 0);
@@ -201,6 +201,43 @@ final class AdminPlayerStoreController extends Controller
return $rootId !== null ? (int) $rootId : null;
}
private function resolveAgentNodeIdForNonSuperAdmin(AdminUser $admin, mixed $requested, string $siteCode): ?int
{
// Check if admin is a platform account (bound via admin_user_site_roles)
$accessibleSiteIds = $admin->accessibleAdminSiteIds();
if ($accessibleSiteIds !== null) {
// Platform account (site admin) can specify agent_node_id
if ($requested !== null && (int) $requested > 0) {
$agent = AgentNode::query()->find((int) $requested);
if ($agent !== null && in_array((int) $agent->admin_site_id, $accessibleSiteIds, true)) {
return (int) $requested;
}
}
// Default to root node of the site
$siteId = AdminSite::query()->where('code', $siteCode)->value('id');
if ($siteId !== null && in_array((int) $siteId, $accessibleSiteIds, true)) {
$rootId = AgentNode::query()
->where('admin_site_id', (int) $siteId)
->where('depth', 0)
->value('id');
return $rootId !== null ? (int) $rootId : null;
}
return null;
}
// Agent account (bound via agent node) - can only create under own node
$agent = AdminAgentScope::primaryAgentNode($admin);
if ($agent === null) {
return null;
}
if ($requested !== null && (int) $requested > 0 && (int) $requested !== (int) $agent->id) {
return null; // Agent account cannot create under other nodes
}
return (int) $agent->id;
}
private function generateNativeSitePlayerId(string $siteCode): string
{
$prefix = strtoupper(substr(preg_replace('/[^A-Za-z]/', '', $siteCode) ?: 'LP', 0, 2));