feat: 增强代理结算和账单管理功能

- 在多个控制器中引入 SettlementPartyEnrichment 服务,以优化代理结算和账单的处理逻辑。
- 更新 AgentSettlementBillIndexController 和 AgentSettlementBillShowController,支持根据账单 ID 和关键字进行查询。
- 在 AgentSettlementPeriodCloseController 中添加对站点管理权限的验证,确保只有具备相应权限的管理员能够关闭账期。
- 在 AgentSettlementPeriodIndexController 中更新账期数据的返回格式,提升数据的完整性和可用性。
- 引入对相对占成比例的支持,增强代理资料的管理能力,确保数据一致性。
This commit is contained in:
2026-06-05 18:00:56 +08:00
parent a44679665d
commit 2d32f006c5
63 changed files with 4893 additions and 288 deletions

View File

@@ -3,8 +3,10 @@
namespace App\Http\Controllers\Api\V1\Admin\AgentSettlement;
use App\Http\Controllers\Controller;
use App\Services\AgentSettlement\SettlementPartyEnrichment;
use App\Support\AdminAgentSettlementScope;
use App\Support\ApiResponse;
use App\Support\PaginationTrait;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
@@ -12,6 +14,12 @@ use Illuminate\Support\Facades\DB;
final class AgentSettlementBillIndexController extends Controller
{
use PaginationTrait;
public function __construct(
private readonly SettlementPartyEnrichment $partyEnrichment,
) {}
public function __invoke(Request $request): JsonResponse
{
$admin = $request->lotteryAdmin();
@@ -38,6 +46,11 @@ final class AgentSettlementBillIndexController extends Controller
$query->where('sp.admin_site_id', $adminSiteId);
}
$billId = (int) $request->query('bill_id', 0);
if ($billId > 0) {
$query->where('sb.id', $billId);
}
$billType = (string) $request->query('bill_type', '');
if ($billType !== '') {
$query->where('sb.bill_type', $billType);
@@ -54,16 +67,78 @@ final class AgentSettlementBillIndexController extends Controller
default => null,
};
$keyword = trim((string) $request->query('keyword', ''));
if ($keyword !== '') {
$this->applyKeywordFilter($query, $keyword);
}
AdminAgentSettlementScope::applyToBillsQuery($query, $admin, 'sb');
$meta = $this->paginationMeta($request, defaultPerPage: 20, maxPerPage: 100);
$paginator = $query->paginate($meta['per_page'], ['sb.*', 'sp.period_start', 'sp.period_end', 'sp.admin_site_id'], 'page', $meta['page']);
/** @var Collection<int, object> $items */
$items = $query->limit(200)->get();
$items = collect($paginator->items());
return ApiResponse::success([
'items' => $this->enrichBillRows($items),
'total' => $paginator->total(),
'page' => $paginator->currentPage(),
'per_page' => $paginator->perPage(),
]);
}
private function applyKeywordFilter(\Illuminate\Database\Query\Builder $query, string $keyword): void
{
$like = '%'.addcslashes($keyword, '%_\\').'%';
$query->where(function (\Illuminate\Database\Query\Builder $outer) use ($keyword, $like): void {
if (ctype_digit($keyword)) {
$outer->orWhere('sb.id', (int) $keyword)
->orWhere('sb.owner_id', (int) $keyword);
}
$outer->orWhere(function (\Illuminate\Database\Query\Builder $player) use ($like): void {
$player->where('sb.owner_type', 'player')
->whereExists(function (\Illuminate\Database\Query\Builder $exists) use ($like): void {
$exists->selectRaw('1')
->from('players as p')
->whereColumn('p.id', 'sb.owner_id')
->where(function (\Illuminate\Database\Query\Builder $match) use ($like): void {
$match->where('p.username', 'like', $like)
->orWhere('p.site_player_id', 'like', $like);
});
});
});
$outer->orWhere(function (\Illuminate\Database\Query\Builder $agent) use ($like): void {
$agent->where('sb.owner_type', 'agent')
->whereExists(function (\Illuminate\Database\Query\Builder $exists) use ($like): void {
$exists->selectRaw('1')
->from('agent_nodes as a')
->whereColumn('a.id', 'sb.owner_id')
->where(function (\Illuminate\Database\Query\Builder $match) use ($like): void {
$match->where('a.name', 'like', $like)
->orWhere('a.code', 'like', $like);
});
});
});
$outer->orWhere(function (\Illuminate\Database\Query\Builder $counter) use ($like): void {
$counter->where('sb.counterparty_type', 'agent')
->whereExists(function (\Illuminate\Database\Query\Builder $exists) use ($like): void {
$exists->selectRaw('1')
->from('agent_nodes as a')
->whereColumn('a.id', 'sb.counterparty_id')
->where(function (\Illuminate\Database\Query\Builder $match) use ($like): void {
$match->where('a.name', 'like', $like)
->orWhere('a.code', 'like', $like);
});
});
});
});
}
/**
* @param Collection<int, object> $items
* @return list<array<string, mixed>>
@@ -90,34 +165,57 @@ final class AgentSettlementBillIndexController extends Controller
$players = $playerIds !== []
? DB::table('players')
->whereIn('id', array_unique($playerIds))
->select(['id', 'username', 'site_player_id', 'funding_mode', 'auth_source'])
->select(['id', 'username', 'site_player_id', 'agent_node_id', 'funding_mode', 'auth_source'])
->get()
->keyBy('id')
: collect();
$agents = $agentIds !== []
? DB::table('agent_nodes')->whereIn('id', array_unique($agentIds))->get()->keyBy('id')
: collect();
foreach ($players as $player) {
$aid = (int) ($player->agent_node_id ?? 0);
if ($aid > 0) {
$agentIds[] = $aid;
}
}
$agents = $this->partyEnrichment->loadAgents($agentIds);
$out = [];
foreach ($items as $row) {
$item = (array) $row;
$item['owner_label'] = $this->resolvePartyLabel(
(string) $row->owner_type,
(int) $row->owner_id,
$players,
$agents,
);
$item['counterparty_label'] = $this->resolvePartyLabel(
(string) $row->counterparty_type,
(int) $row->counterparty_id,
$players,
$agents,
);
if ((string) $row->owner_type === 'player') {
$ownerType = (string) $row->owner_type;
$counterType = (string) $row->counterparty_type;
$counterId = (int) $row->counterparty_id;
$item['owner_label'] = $this->legacyOwnerLabel($ownerType, (int) $row->owner_id, $players, $agents);
$item['counterparty_label'] = $this->partyEnrichment->formatCounterpartyLabel($counterType, $counterId, $agents);
$item['player_username'] = null;
$item['player_site_player_id'] = null;
$item['player_id_display'] = null;
$item['direct_agent_label'] = null;
$item['superior_agent_label'] = null;
$item['owner_party_label'] = null;
if ($ownerType === 'player') {
$player = $players->get((int) $row->owner_id);
$item['player_username'] = $this->partyEnrichment->formatPlayerUsername($player);
$item['player_site_player_id'] = $this->partyEnrichment->formatPlayerSiteId($player);
$item['player_id_display'] = (int) $row->owner_id;
$item['owner_funding_mode'] = $player !== null ? (string) ($player->funding_mode ?? '') : null;
$item['owner_auth_source'] = $player !== null ? $player->auth_source : null;
$directId = $counterType === 'agent' ? $counterId : (int) ($player->agent_node_id ?? 0);
$line = $this->partyEnrichment->agentLineLabels($directId > 0 ? $directId : null, $agents);
$item['direct_agent_label'] = $line['direct_agent_label'];
$item['superior_agent_label'] = $line['parent_agent_label'];
} elseif ($ownerType === 'agent') {
$ownerAgentId = (int) $row->owner_id;
$item['owner_party_label'] = $this->partyEnrichment->formatAgent($agents->get($ownerAgentId), $ownerAgentId);
$item['superior_agent_label'] = $counterType === 'platform'
? 'platform'
: $this->partyEnrichment->formatCounterpartyLabel($counterType, $counterId, $agents);
}
$out[] = $item;
}
@@ -128,30 +226,22 @@ final class AgentSettlementBillIndexController extends Controller
* @param Collection<int, object> $players
* @param Collection<int, object> $agents
*/
private function resolvePartyLabel(
private function legacyOwnerLabel(
string $type,
int $id,
Collection $players,
Collection $agents,
): string {
if ($type === 'platform' || $id <= 0) {
return 'platform';
}
if ($type === 'player') {
$player = $players->get($id);
return $player !== null
? (string) ($player->username ?: $player->site_player_id)
? (string) ($player->username ?: $player->site_player_id ?: "player#{$id}")
: "player#{$id}";
}
if ($type === 'agent') {
$agent = $agents->get($id);
return $agent !== null
? (string) ($agent->name ?: $agent->code)
: "agent#{$id}";
return $this->partyEnrichment->formatAgent($agents->get($id), $id);
}
return "{$type}#{$id}";