Files
lotteryLaravel/app/Services/AgentSettlement/AgentPeriodAggregator.php
kang 2d32f006c5 feat: 增强代理结算和账单管理功能
- 在多个控制器中引入 SettlementPartyEnrichment 服务,以优化代理结算和账单的处理逻辑。
- 更新 AgentSettlementBillIndexController 和 AgentSettlementBillShowController,支持根据账单 ID 和关键字进行查询。
- 在 AgentSettlementPeriodCloseController 中添加对站点管理权限的验证,确保只有具备相应权限的管理员能够关闭账期。
- 在 AgentSettlementPeriodIndexController 中更新账期数据的返回格式,提升数据的完整性和可用性。
- 引入对相对占成比例的支持,增强代理资料的管理能力,确保数据一致性。
2026-06-05 18:00:56 +08:00

225 lines
7.8 KiB
PHP

<?php
namespace App\Services\AgentSettlement;
use App\Models\AgentNode;
use Illuminate\Support\Facades\DB;
final class AgentPeriodAggregator
{
public function __construct(
private readonly ShareSettlementCalculator $calculator,
) {}
/**
* @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) {
throw new \InvalidArgumentException(
'share_snapshot_missing:ticket_item_id='.(int) $row->ticket_item_id,
);
}
$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)),
];
}
}