| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- <?php
- namespace App\Services\Agent;
- use App\Models\Agent\Agent;
- use App\Models\Agent\AgentCommission;
- use App\Models\Agent\AgentCommissionRule;
- use App\Models\Agent\AgentDailyStat;
- use Carbon\Carbon;
- use Illuminate\Support\Facades\DB;
- class AgentCommissionService
- {
- public function __construct(private AgentReportService $reports, private AgentWalletService $wallets)
- {
- }
- public function saveRule(Agent $agent, string $flowRate, string $profitRate, string $effectiveFrom, ?int $adminId): AgentCommissionRule
- {
- return $this->saveRates($agent, $flowRate, $profitRate, $effectiveFrom, $adminId);
- }
- public function saveFlowRate(Agent $agent, string $rate, string $effectiveFrom, ?int $adminId): AgentCommissionRule
- {
- return $this->saveRates($agent, $rate, null, $effectiveFrom, $adminId);
- }
- public function saveProfitRate(Agent $agent, string $rate, string $effectiveFrom, ?int $adminId): AgentCommissionRule
- {
- return $this->saveRates($agent, null, $rate, $effectiveFrom, $adminId);
- }
- public function settle(string $date, bool $credit = true): array
- {
- $day = Carbon::parse($date)->toDateString();
- $created = 0;
- $credited = 0;
- Agent::query()->where('status', Agent::STATUS_ENABLED)->orderBy('id')->chunkById(100, function ($agents) use ($day, $credit, &$created, &$credited) {
- foreach ($agents as $agent) {
- $descendantIds = $this->reports->visibleAgentIds($agent);
- $base = AgentDailyStat::query()->where('stat_date', $day)->whereIn('agent_id', $descendantIds)
- ->selectRaw('COALESCE(SUM(valid_bet_amount),0) valid_bet_amount, COALESCE(SUM(company_profit),0) company_profit, COALESCE(SUM(bet_count),0) bet_count')
- ->first();
- $rule = AgentCommissionRule::query()->where('agent_id', $agent->id)->where('status', 1)
- ->where('effective_from', '<=', $day)->orderByDesc('effective_from')->first();
- if (!$rule) {
- throw new \RuntimeException("代理 {$agent->id} 缺少 {$day} 可用的佣金比例记录");
- }
- $rates = [
- 'flow' => (string) $rule->flow_rate,
- 'profit' => (string) $rule->profit_rate,
- ];
- $bases = [
- 'flow' => (string) ($base->valid_bet_amount ?? 0),
- 'profit' => max('0', (string) ($base->company_profit ?? 0)),
- ];
- foreach (['flow', 'profit'] as $type) {
- $amount = bcdiv(bcmul($bases[$type], $rates[$type], 8), '100', 4);
- $commission = AgentCommission::query()->firstOrNew([
- 'settlement_date' => $day,
- 'agent_id' => $agent->id,
- 'type' => $type,
- ]);
- $isNew = !$commission->exists;
- if ($isNew || $commission->status === 'pending') {
- $commission->fill([
- 'base_amount' => $bases[$type], 'rate' => $rates[$type], 'amount' => $amount,
- 'bet_count' => (int) ($base->bet_count ?? 0), 'status' => 'pending',
- 'snapshot' => ['agent_ids' => $descendantIds],
- ]);
- $commission->save();
- }
- if ($isNew) {
- $created++;
- }
- if ($credit && $commission->status === 'pending' && bccomp((string) $commission->amount, '0', 4) > 0) {
- $this->wallets->adjust(
- (int) $agent->id,
- (string) $commission->amount,
- 'commission',
- $day . ($type === 'flow' ? '流水佣金' : '盈亏佣金'),
- 'commission:' . $commission->id,
- 'system',
- null,
- 'agent_commission',
- (int) $commission->id
- );
- $commission->status = 'credited';
- $commission->credited_at = now();
- $commission->save();
- $credited++;
- } elseif ($credit && $commission->status === 'pending') {
- $commission->status = 'credited';
- $commission->credited_at = now();
- $commission->save();
- }
- }
- AgentDailyStat::query()->where('stat_date', $day)->where('agent_id', $agent->id)->update([
- 'commission_amount' => AgentCommission::query()->where('settlement_date', $day)
- ->where('agent_id', $agent->id)->where('status', 'credited')->sum('amount'),
- ]);
- }
- });
- return compact('created', 'credited');
- }
- private function rateAt(Agent $agent, string $field, string $day): string
- {
- $rule = AgentCommissionRule::query()->where('agent_id', $agent->id)->where('status', 1)
- ->where('effective_from', '<=', $day)->orderByDesc('effective_from')->first();
- if ($rule) {
- return (string) $rule->{$field};
- }
- $agentField = $field === 'flow_rate' ? 'flow_commission_rate' : 'profit_commission_rate';
- return (string) $agent->{$agentField};
- }
- private function saveRates(Agent $agent, ?string $flowRate, ?string $profitRate, string $effectiveFrom, ?int $adminId): AgentCommissionRule
- {
- foreach (array_filter([$flowRate, $profitRate], fn($rate) => $rate !== null) as $rate) {
- if (bccomp((string) $rate, '0', 4) < 0 || bccomp((string) $rate, '100', 4) > 0) {
- throw new \InvalidArgumentException('佣金比例必须在0到100之间');
- }
- }
- $day = Carbon::parse($effectiveFrom)->toDateString();
- return DB::transaction(function () use ($agent, $flowRate, $profitRate, $day, $adminId) {
- $agent = Agent::query()->lockForUpdate()->findOrFail($agent->id);
- if (AgentCommission::query()->where('agent_id', $agent->id)->where('settlement_date', '>=', $day)->where('status', 'credited')->exists()) {
- throw new \RuntimeException('该日期佣金已入账,不能修改比例');
- }
- $existing = AgentCommissionRule::query()->where('agent_id', $agent->id)
- ->where('effective_from', $day)->lockForUpdate()->first();
- $flowRate = $flowRate ?? (string) ($existing?->flow_rate ?? $this->rateAt($agent, 'flow_rate', $day));
- $profitRate = $profitRate ?? (string) ($existing?->profit_rate ?? $this->rateAt($agent, 'profit_rate', $day));
- $parent = $agent->parent;
- if ($parent && (
- bccomp($flowRate, $this->rateAt($parent, 'flow_rate', $day), 4) > 0
- || bccomp($profitRate, $this->rateAt($parent, 'profit_rate', $day), 4) > 0
- )) {
- throw new \RuntimeException('下级代理佣金比例不能高于上级');
- }
- $children = Agent::query()->where('parent_id', $agent->id)->get();
- $maxFlow = $children->map(fn(Agent $child) => $this->rateAt($child, 'flow_rate', $day))->max() ?? 0;
- $maxProfit = $children->map(fn(Agent $child) => $this->rateAt($child, 'profit_rate', $day))->max() ?? 0;
- if (bccomp($flowRate, (string) $maxFlow, 4) < 0 || bccomp($profitRate, (string) $maxProfit, 4) < 0) {
- throw new \RuntimeException('代理佣金比例不能低于现有下级比例');
- }
- $agent->flow_commission_rate = $flowRate;
- $agent->profit_commission_rate = $profitRate;
- $agent->save();
- return AgentCommissionRule::query()->updateOrCreate(
- ['agent_id' => $agent->id, 'effective_from' => $day],
- ['flow_rate' => $flowRate, 'profit_rate' => $profitRate, 'status' => 1, 'created_by' => $adminId]
- );
- });
- }
- }
|