feat: 增强代理和玩家管理功能

- 在多个控制器中更新权限检查逻辑,确保管理员能够更灵活地管理代理和玩家。
- 在 AdminPlayerStoreController 中引入对玩家创建能力的验证,确保只有具备相应权限的管理员能够创建玩家。
- 更新请求验证逻辑,新增 credit_limit、rebate_rate 和 extra_rebate_rate 字段,以支持更细粒度的玩家管理。
- 在 AgentNodeProfileController 中添加对父代理能力授予的验证,确保子代理的权限在父代理范围内。
- 引入 AgentProfileFieldRules 以简化代理资料更新请求的规则定义,提升代码复用性。
This commit is contained in:
2026-06-04 18:00:50 +08:00
parent 96545f87f6
commit a44679665d
183 changed files with 10054 additions and 857 deletions

View File

@@ -6,6 +6,7 @@ use App\Models\AdminUser;
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;
@@ -16,6 +17,7 @@ final class AgentProfileService
private readonly ShareRateValidator $shareRateValidator,
private readonly CreditAllocationValidator $creditAllocationValidator,
private readonly RebateLimitValidator $rebateLimitValidator,
private readonly AgentCreditAllocatedSyncService $allocatedSync,
) {}
/**
@@ -39,10 +41,17 @@ final class AgentProfileService
$previousCredit = (int) $profile->credit_limit;
$isNew = ! $profile->exists;
if (! $isNew && $creditLimit < (int) $profile->allocated_credit) {
throw ValidationException::withMessages([
'credit_limit' => ['below_allocated'],
]);
if ($parent !== null && ! $isNew) {
$this->allocatedSync->syncForAgent($parent);
}
if (! $isNew) {
$this->allocatedSync->syncForAgent($node);
if ($creditLimit < (int) $profile->allocated_credit) {
throw ValidationException::withMessages([
'credit_limit' => ['below_allocated'],
]);
}
}
if ($parent !== null) {
@@ -53,7 +62,7 @@ final class AgentProfileService
}
if ($defaultRebate > $rebateLimit && $rebateLimit > 0) {
throw \Illuminate\Validation\ValidationException::withMessages([
throw ValidationException::withMessages([
'default_player_rebate' => ['exceeds_limit'],
]);
}
@@ -77,14 +86,7 @@ final class AgentProfileService
$profile->save();
if ($parent !== null) {
$parentProfile = AgentProfile::query()->where('agent_node_id', $parent->id)->first();
if ($parentProfile !== null) {
$creditDelta = $isNew ? $creditLimit : ($creditLimit - $previousCredit);
if ($creditDelta !== 0) {
$parentProfile->allocated_credit = max(0, (int) $parentProfile->allocated_credit + $creditDelta);
$parentProfile->save();
}
}
$this->allocatedSync->syncForAgent($parent);
}
return $profile;
@@ -96,6 +98,9 @@ final class AgentProfileService
*/
public function present(AgentProfile $profile): array
{
$this->allocatedSync->syncForAgentId((int) $profile->agent_node_id);
$profile->refresh();
$available = max(0, (int) $profile->credit_limit - (int) $profile->allocated_credit);
return [
@@ -119,6 +124,65 @@ final class AgentProfileService
return AgentProfile::query()->where('agent_node_id', $agentNodeId)->first();
}
/**
* @return array<string, mixed>|null
*/
public function parentCapsForNode(?AgentNode $parent): ?array
{
if ($parent === null) {
return null;
}
$this->allocatedSync->syncForAgent($parent);
$profile = $this->profileForNode((int) $parent->id);
if ($profile === null) {
return null;
}
return [
'agent_node_id' => (int) $parent->id,
'total_share_rate' => (float) $profile->total_share_rate,
'rebate_limit' => (float) $profile->rebate_limit,
'available_credit' => max(0, (int) $profile->credit_limit - (int) $profile->allocated_credit),
];
}
/** 玩家授信写入前:校验代理可下发是否足够(按当前库内已占用重算)。 */
public function assertMayIncreasePlayerCredit(AgentNode $agent, int $additionalCredit): void
{
if ($additionalCredit <= 0) {
return;
}
$this->assertAgentProfileExists($agent);
$this->allocatedSync->syncForAgent($agent);
$this->creditAllocationValidator->assertPlayerCreditDeltaWithinAgent($agent, $additionalCredit);
}
/** 玩家授信变更后:按直属玩家+直属下级代理重算已下发额度(无 profile 时跳过)。 */
public function refreshAllocatedCredit(AgentNode $agent): void
{
$this->allocatedSync->syncForAgent($agent);
}
public function adjustPlayerCreditAllocation(AgentNode $agent, int $previousLimit, int $newLimit, int $playerUsedCredit = 0): void
{
if ($newLimit < $playerUsedCredit) {
throw ValidationException::withMessages([
'credit_limit' => ['below_player_used'],
]);
}
$delta = $newLimit - $previousLimit;
$this->assertAgentProfileExists($agent);
$this->allocatedSync->syncForAgent($agent);
if ($delta > 0) {
$this->creditAllocationValidator->assertPlayerCreditDeltaWithinAgent($agent, $delta);
}
}
public function assertActorMayCreateChildAgent(AdminUser $admin): void
{
if ($admin->isSuperAdmin()) {
@@ -135,6 +199,12 @@ final class AgentProfileService
'parent_id' => ['cannot_create_child_agent'],
]);
}
if (AgentOverdueGuard::agentHasOverdueBills((int) $node->id)) {
throw ValidationException::withMessages([
'parent_id' => ['agent_overdue'],
]);
}
}
public function assertActorMayCreatePlayer(AdminUser $admin): void
@@ -153,6 +223,12 @@ final class AgentProfileService
'site_code' => ['cannot_create_player'],
]);
}
if (AgentOverdueGuard::agentHasOverdueBills((int) $node->id)) {
throw ValidationException::withMessages([
'site_code' => ['agent_overdue'],
]);
}
}
/**
@@ -193,4 +269,15 @@ final class AgentProfileService
return $profile === null || $profile->can_create_player;
}
private function assertAgentProfileExists(AgentNode $agent): void
{
if (AgentProfile::query()->where('agent_node_id', $agent->id)->exists()) {
return;
}
throw ValidationException::withMessages([
'credit_limit' => ['agent_profile_required'],
]);
}
}