- 在 AgentNodeProfileController 中添加对父代理能力授予的验证,确保子代理的权限在父代理范围内。 - 更新多个请求类,统一代理资料字段的验证逻辑,提升代码复用性。 - 引入 AgentProfileFieldRules 以简化代理资料更新请求的规则定义。 - 在 AgentProfile 模型中设置主键为 agent_node_id,确保与代理节点的关联性。 - 更新错误信息,增加对授信额度和占成比例的验证,确保数据一致性。
197 lines
7.3 KiB
PHP
197 lines
7.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Agent;
|
|
|
|
use App\Models\AdminUser;
|
|
use App\Models\AgentNode;
|
|
use App\Models\AgentProfile;
|
|
use App\Support\AdminAgentScope;
|
|
use App\Support\AgentSettlementCycle;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
final class AgentProfileService
|
|
{
|
|
public function __construct(
|
|
private readonly ShareRateValidator $shareRateValidator,
|
|
private readonly CreditAllocationValidator $creditAllocationValidator,
|
|
private readonly RebateLimitValidator $rebateLimitValidator,
|
|
) {}
|
|
|
|
/**
|
|
* @param array<string, mixed> $payload
|
|
*/
|
|
public function upsertForNode(AgentNode $node, array $payload, ?AgentNode $parent = null): AgentProfile
|
|
{
|
|
$parent = $parent ?? ($node->parent_id !== null ? AgentNode::query()->find($node->parent_id) : null);
|
|
|
|
$totalShare = (float) ($payload['total_share_rate'] ?? 0);
|
|
$creditLimit = (int) ($payload['credit_limit'] ?? 0);
|
|
$rebateLimit = (float) ($payload['rebate_limit'] ?? 0);
|
|
$defaultRebate = (float) ($payload['default_player_rebate'] ?? 0);
|
|
|
|
if ($parent !== null) {
|
|
$this->shareRateValidator->assertChildWithinParent($parent, $totalShare);
|
|
}
|
|
|
|
return DB::transaction(function () use ($node, $payload, $parent, $totalShare, $creditLimit, $rebateLimit, $defaultRebate): AgentProfile {
|
|
$profile = AgentProfile::query()->firstOrNew(['agent_node_id' => $node->id]);
|
|
$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) {
|
|
$delta = $isNew ? $creditLimit : max(0, $creditLimit - $previousCredit);
|
|
if ($delta > 0) {
|
|
$this->creditAllocationValidator->assertAllocationWithinParent($parent, $delta);
|
|
}
|
|
}
|
|
|
|
if ($defaultRebate > $rebateLimit && $rebateLimit > 0) {
|
|
throw \Illuminate\Validation\ValidationException::withMessages([
|
|
'default_player_rebate' => ['exceeds_limit'],
|
|
]);
|
|
}
|
|
|
|
$profile->fill([
|
|
'total_share_rate' => $totalShare,
|
|
'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)),
|
|
]);
|
|
if (! $profile->exists) {
|
|
$profile->allocated_credit = 0;
|
|
$profile->used_credit = 0;
|
|
}
|
|
$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();
|
|
}
|
|
}
|
|
}
|
|
|
|
return $profile;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function present(AgentProfile $profile): array
|
|
{
|
|
$available = max(0, (int) $profile->credit_limit - (int) $profile->allocated_credit);
|
|
|
|
return [
|
|
'agent_node_id' => (int) $profile->agent_node_id,
|
|
'total_share_rate' => (float) $profile->total_share_rate,
|
|
'credit_limit' => (int) $profile->credit_limit,
|
|
'allocated_credit' => (int) $profile->allocated_credit,
|
|
'used_credit' => (int) $profile->used_credit,
|
|
'available_credit' => $available,
|
|
'rebate_limit' => (float) $profile->rebate_limit,
|
|
'default_player_rebate' => (float) $profile->default_player_rebate,
|
|
'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,
|
|
];
|
|
}
|
|
|
|
public function profileForNode(int $agentNodeId): ?AgentProfile
|
|
{
|
|
return AgentProfile::query()->where('agent_node_id', $agentNodeId)->first();
|
|
}
|
|
|
|
public function assertActorMayCreateChildAgent(AdminUser $admin): void
|
|
{
|
|
if ($admin->isSuperAdmin()) {
|
|
return;
|
|
}
|
|
|
|
$node = AdminAgentScope::primaryAgentNode($admin);
|
|
if ($node === null) {
|
|
return;
|
|
}
|
|
|
|
if (! $this->nodeMayCreateChildAgent($node->id)) {
|
|
throw ValidationException::withMessages([
|
|
'parent_id' => ['cannot_create_child_agent'],
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function assertActorMayCreatePlayer(AdminUser $admin): void
|
|
{
|
|
if ($admin->isSuperAdmin()) {
|
|
return;
|
|
}
|
|
|
|
$node = AdminAgentScope::primaryAgentNode($admin);
|
|
if ($node === null) {
|
|
return;
|
|
}
|
|
|
|
if (! $this->nodeMayCreatePlayer($node->id)) {
|
|
throw ValidationException::withMessages([
|
|
'site_code' => ['cannot_create_player'],
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $childPayload
|
|
*/
|
|
public function assertChildCapabilityGrantsWithinParent(AgentNode $parent, array $childPayload, AdminUser $actor): void
|
|
{
|
|
if ($actor->isSuperAdmin()) {
|
|
return;
|
|
}
|
|
|
|
$parentProfile = $this->profileForNode((int) $parent->id);
|
|
if ((bool) ($childPayload['can_create_child_agent'] ?? false)
|
|
&& ! ($parentProfile?->can_create_child_agent ?? false)) {
|
|
throw ValidationException::withMessages([
|
|
'can_create_child_agent' => ['parent_cannot_delegate'],
|
|
]);
|
|
}
|
|
|
|
if ((bool) ($childPayload['can_create_player'] ?? true)
|
|
&& ! ($parentProfile?->can_create_player ?? false)) {
|
|
throw ValidationException::withMessages([
|
|
'can_create_player' => ['parent_cannot_delegate'],
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function nodeMayCreateChildAgent(int $agentNodeId): bool
|
|
{
|
|
$profile = $this->profileForNode($agentNodeId);
|
|
|
|
return $profile === null || $profile->can_create_child_agent;
|
|
}
|
|
|
|
public function nodeMayCreatePlayer(int $agentNodeId): bool
|
|
{
|
|
$profile = $this->profileForNode($agentNodeId);
|
|
|
|
return $profile === null || $profile->can_create_player;
|
|
}
|
|
}
|