- 在多个控制器中更新权限检查逻辑,确保管理员能够更灵活地管理代理和玩家。 - 在 AdminPlayerStoreController 中引入对玩家创建能力的验证,确保只有具备相应权限的管理员能够创建玩家。 - 更新请求验证逻辑,新增 credit_limit、rebate_rate 和 extra_rebate_rate 字段,以支持更细粒度的玩家管理。 - 在 AgentNodeProfileController 中添加对父代理能力授予的验证,确保子代理的权限在父代理范围内。 - 引入 AgentProfileFieldRules 以简化代理资料更新请求的规则定义,提升代码复用性。
233 lines
8.1 KiB
PHP
233 lines
8.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services\AgentSettlement;
|
|
|
|
use App\Models\AgentNode;
|
|
use App\Models\Player;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
final class AgentPeriodAggregator
|
|
{
|
|
public function __construct(
|
|
private readonly ShareSettlementCalculator $calculator,
|
|
private readonly BetSettlementSnapshotBuilder $snapshotBuilder,
|
|
) {}
|
|
|
|
/**
|
|
* @return array{
|
|
* players: array<int, array<string, mixed>>,
|
|
* agent_edges: array<string, int>,
|
|
* agent_subtrees: array<int, array<string, mixed>>,
|
|
* platform_share_profit: int,
|
|
* }
|
|
*/
|
|
public function aggregate(int $adminSiteId, string $periodStart, string $periodEnd): array
|
|
{
|
|
$siteCode = (string) DB::table('admin_sites')->where('id', $adminSiteId)->value('code');
|
|
$codeToId = AgentNode::query()
|
|
->where('admin_site_id', $adminSiteId)
|
|
->pluck('id', 'code')
|
|
->mapWithKeys(fn ($id, $code): array => [(string) $code => (int) $id])
|
|
->all();
|
|
|
|
$rows = DB::table('share_ledger as sl')
|
|
->join('players as p', 'p.id', '=', 'sl.player_id')
|
|
->where('p.site_code', $siteCode)
|
|
->whereBetween('sl.settled_at', [$periodStart, $periodEnd])
|
|
->select([
|
|
'sl.player_id',
|
|
'sl.ticket_item_id',
|
|
'sl.agent_node_id',
|
|
'sl.share_snapshot',
|
|
'sl.game_win_loss',
|
|
'sl.basic_rebate',
|
|
])
|
|
->orderBy('sl.id')
|
|
->get();
|
|
|
|
$players = [];
|
|
$agentEdges = [];
|
|
$agentSubtrees = [];
|
|
$platformShareProfit = 0;
|
|
|
|
foreach ($rows as $row) {
|
|
$playerId = (int) $row->player_id;
|
|
$snapshot = $this->resolveSnapshotFromLedgerRow($row);
|
|
if ($snapshot === null) {
|
|
$player = Player::query()->find($playerId);
|
|
if ($player === null) {
|
|
continue;
|
|
}
|
|
$built = $this->snapshotBuilder->buildForPlayer($player);
|
|
$snapshot = [
|
|
'total_shares' => $built['total_shares'],
|
|
'chain_codes' => $built['chain_codes'],
|
|
];
|
|
}
|
|
|
|
$gameWinLoss = (int) $row->game_win_loss;
|
|
$basicRebate = (int) $row->basic_rebate;
|
|
$extraRebate = $this->extraRebateForTicketItem(
|
|
(int) $row->ticket_item_id,
|
|
$periodStart,
|
|
$periodEnd,
|
|
);
|
|
|
|
$extraByCode = [];
|
|
if ($extraRebate > 0 && $snapshot['chain_codes'] !== []) {
|
|
$extraByCode[$snapshot['chain_codes'][0]] = $extraRebate;
|
|
}
|
|
|
|
$result = $this->calculator->calculate(
|
|
sharedNetWinLoss: 0,
|
|
totalSharesByCode: $snapshot['total_shares'],
|
|
extraRebateByCode: $extraByCode,
|
|
gameWinLoss: $gameWinLoss,
|
|
basicRebate: $basicRebate,
|
|
chainFromPlayer: $snapshot['chain_codes'],
|
|
);
|
|
|
|
$net = (int) round($result->playerNetSettlement);
|
|
|
|
if (! isset($players[$playerId])) {
|
|
$players[$playerId] = [
|
|
'agent_node_id' => (int) $row->agent_node_id,
|
|
'game_win_loss' => 0,
|
|
'basic_rebate' => 0,
|
|
'extra_rebate' => 0,
|
|
'net_amount' => 0,
|
|
];
|
|
}
|
|
|
|
$players[$playerId]['game_win_loss'] += $gameWinLoss;
|
|
$players[$playerId]['basic_rebate'] += $basicRebate;
|
|
$players[$playerId]['extra_rebate'] += $extraRebate;
|
|
$players[$playerId]['net_amount'] += $net;
|
|
|
|
foreach ($result->tierSettlements as $edge => $amount) {
|
|
$agentEdges[$edge] = ($agentEdges[$edge] ?? 0) + (int) round($amount);
|
|
}
|
|
|
|
$platformShareProfit += (int) round($result->finalProfits['platform'] ?? 0);
|
|
|
|
$pathIds = $this->resolveAgentPathIds($row, $snapshot, $codeToId);
|
|
foreach ($pathIds as $agentId) {
|
|
if (! isset($agentSubtrees[$agentId])) {
|
|
$agentSubtrees[$agentId] = [
|
|
'gross_win_loss' => 0,
|
|
'basic_rebate' => 0,
|
|
'extra_rebate' => 0,
|
|
'share_profit' => 0,
|
|
'player_count' => 0,
|
|
'_players_seen' => [],
|
|
];
|
|
}
|
|
$agentSubtrees[$agentId]['gross_win_loss'] += $gameWinLoss;
|
|
$agentSubtrees[$agentId]['basic_rebate'] += $basicRebate;
|
|
$agentSubtrees[$agentId]['extra_rebate'] += $extraRebate;
|
|
if (! in_array($playerId, $agentSubtrees[$agentId]['_players_seen'], true)) {
|
|
$agentSubtrees[$agentId]['_players_seen'][] = $playerId;
|
|
$agentSubtrees[$agentId]['player_count']++;
|
|
}
|
|
}
|
|
|
|
foreach ($snapshot['chain_codes'] as $code) {
|
|
$agentId = $codeToId[$code] ?? 0;
|
|
if ($agentId <= 0) {
|
|
continue;
|
|
}
|
|
$profit = (int) round($result->finalProfits[$code] ?? 0);
|
|
$agentSubtrees[$agentId]['share_profit'] = ($agentSubtrees[$agentId]['share_profit'] ?? 0) + $profit;
|
|
}
|
|
}
|
|
|
|
foreach ($agentSubtrees as $id => $subtree) {
|
|
unset($agentSubtrees[$id]['_players_seen']);
|
|
}
|
|
|
|
return [
|
|
'players' => $players,
|
|
'agent_edges' => $agentEdges,
|
|
'agent_subtrees' => $agentSubtrees,
|
|
'platform_share_profit' => $platformShareProfit,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array{chain_codes: list<string>, total_shares: array<string, float>} $snapshot
|
|
* @param array<string, int> $codeToId
|
|
* @return list<int>
|
|
*/
|
|
private function resolveAgentPathIds(object $row, array $snapshot, array $codeToId): array
|
|
{
|
|
$raw = $row->share_snapshot ?? null;
|
|
if ($raw !== null && $raw !== '') {
|
|
$decoded = is_string($raw) ? json_decode($raw, true) : $raw;
|
|
if (is_array($decoded) && is_array($decoded['agent_path'] ?? null)) {
|
|
return array_values(array_map(intval(...), $decoded['agent_path']));
|
|
}
|
|
}
|
|
|
|
$ids = [];
|
|
foreach ($snapshot['chain_codes'] as $code) {
|
|
$id = $codeToId[$code] ?? 0;
|
|
if ($id > 0) {
|
|
$ids[] = $id;
|
|
}
|
|
}
|
|
|
|
return $ids;
|
|
}
|
|
|
|
public function siteIdForPeriod(int $periodId): int
|
|
{
|
|
return (int) DB::table('settlement_periods')->where('id', $periodId)->value('admin_site_id');
|
|
}
|
|
|
|
private function extraRebateForTicketItem(int $ticketItemId, string $periodStart, string $periodEnd): int
|
|
{
|
|
if ($ticketItemId <= 0) {
|
|
return 0;
|
|
}
|
|
|
|
return (int) DB::table('rebate_records')
|
|
->where('ticket_item_id', $ticketItemId)
|
|
->where('rebate_type', 'extra')
|
|
->whereIn('status', ['accrued', 'reversed'])
|
|
->whereBetween('created_at', [$periodStart, $periodEnd])
|
|
->sum('rebate_amount');
|
|
}
|
|
|
|
/**
|
|
* @return array{total_shares: array<string, float>, chain_codes: list<string>}|null
|
|
*/
|
|
private function resolveSnapshotFromLedgerRow(object $row): ?array
|
|
{
|
|
$raw = $row->share_snapshot ?? null;
|
|
if ($raw === null || $raw === '') {
|
|
return null;
|
|
}
|
|
|
|
$decoded = is_string($raw) ? json_decode($raw, true) : $raw;
|
|
if (! is_array($decoded)) {
|
|
return null;
|
|
}
|
|
|
|
$totalShares = $decoded['total_shares'] ?? null;
|
|
$chainCodes = $decoded['chain_codes'] ?? null;
|
|
if (! is_array($totalShares) || ! is_array($chainCodes) || $chainCodes === []) {
|
|
return null;
|
|
}
|
|
|
|
$shares = [];
|
|
foreach ($totalShares as $code => $rate) {
|
|
$shares[(string) $code] = (float) $rate;
|
|
}
|
|
|
|
return [
|
|
'total_shares' => $shares,
|
|
'chain_codes' => array_values(array_map(strval(...), $chainCodes)),
|
|
];
|
|
}
|
|
}
|