dispatchAccruedToPeriod($periodId, $periodStart, $periodEnd); $allocationCount = $this->buildAllocations($periodId, $rebateIds); return [ 'dispatched' => count($rebateIds), 'allocations' => $allocationCount, ]; } /** * @return list */ private function dispatchAccruedToPeriod(int $periodId, string $periodStart, string $periodEnd): array { $ids = DB::table('rebate_records as rr') ->join('ticket_items as ti', 'ti.id', '=', 'rr.ticket_item_id') ->join('share_ledger as sl', function ($join): void { $join->on('sl.ticket_item_id', '=', 'ti.id') ->whereNull('sl.reversal_of_id'); }) ->where('rr.status', 'accrued') ->whereBetween('sl.settled_at', [$periodStart, $periodEnd]) ->pluck('rr.id') ->map(fn ($id): int => (int) $id) ->all(); if ($ids === []) { return []; } DB::table('rebate_records') ->whereIn('id', $ids) ->update([ 'settlement_period_id' => $periodId, 'status' => 'in_bill', 'updated_at' => now(), ]); return $ids; } /** * @param list $rebateIds */ private function buildAllocations(int $periodId, array $rebateIds): int { if ($rebateIds === []) { return 0; } $playerBills = DB::table('settlement_bills') ->where('settlement_period_id', $periodId) ->where('bill_type', 'player') ->get() ->keyBy('owner_id'); $count = 0; $rebates = DB::table('rebate_records') ->whereIn('id', $rebateIds) ->where('status', 'in_bill') ->get(); foreach ($rebates as $rebate) { $playerId = (int) $rebate->player_id; $bill = $playerBills->get($playerId); $billId = $bill !== null ? (int) $bill->id : null; if ((string) $rebate->rebate_type === 'extra') { $count += $this->insertExtraAllocation($rebate, $billId); continue; } $count += $this->insertBasicShareAllocations($rebate, $billId); } return $count; } private function insertExtraAllocation(object $rebate, ?int $billId): int { $agentId = (int) ($rebate->owner_agent_id ?? 0); if ($agentId <= 0) { return 0; } DB::table('rebate_allocations')->insert([ 'rebate_record_id' => (int) $rebate->id, 'settlement_bill_id' => $billId, 'participant_type' => 'agent', 'participant_id' => $agentId, 'actual_share_rate' => 0, 'allocated_amount' => (int) $rebate->rebate_amount, 'allocation_rule' => 'owner', 'created_at' => now(), 'updated_at' => now(), ]); return 1; } private function insertBasicShareAllocations(object $rebate, ?int $billId): int { $ticketItemId = (int) ($rebate->ticket_item_id ?? 0); if ($ticketItemId <= 0) { return 0; } $ledger = DB::table('share_ledger') ->where('ticket_item_id', $ticketItemId) ->whereNull('reversal_of_id') ->orderByDesc('id') ->first(); if ($ledger === null) { return 0; } $snapshot = $this->decodeSnapshot($ledger->share_snapshot); if ($snapshot === null) { return 0; } $amount = (int) $rebate->rebate_amount; if ($amount <= 0) { return 0; } $shares = $snapshot['actual_shares']; $rows = []; $allocatedSum = 0; $participants = []; foreach ($shares as $code => $rate) { if ($code === 'platform') { $participants[] = ['type' => 'platform', 'id' => 0, 'rate' => (float) $rate]; continue; } $node = AgentNode::query()->where('code', (string) $code)->first(); if ($node === null) { continue; } $participants[] = ['type' => 'agent', 'id' => (int) $node->id, 'rate' => (float) $rate]; } foreach ($participants as $index => $p) { $isLast = $index === count($participants) - 1; $slice = $isLast ? $amount - $allocatedSum : (int) round($amount * ($p['rate'] / 100), 0, PHP_ROUND_HALF_UP); $allocatedSum += $slice; $rows[] = [ 'rebate_record_id' => (int) $rebate->id, 'settlement_bill_id' => $billId, 'participant_type' => (string) $p['type'], 'participant_id' => (int) $p['id'], 'actual_share_rate' => $p['rate'], 'allocated_amount' => $slice, 'allocation_rule' => 'share', 'created_at' => now(), 'updated_at' => now(), ]; } if ($rows !== []) { DB::table('rebate_allocations')->insert($rows); } return count($rows); } /** * @return array{actual_shares: array}|null */ private function decodeSnapshot(mixed $raw): ?array { if ($raw === null || $raw === '') { return null; } $decoded = is_string($raw) ? json_decode($raw, true) : $raw; if (! is_array($decoded)) { return null; } $actual = $decoded['actual_shares'] ?? null; if (! is_array($actual) || $actual === []) { return null; } $shares = []; foreach ($actual as $code => $rate) { $shares[(string) $code] = (float) $rate; } return ['actual_shares' => $shares]; } public function markRebatesSettledForBill(int $billId): void { $bill = DB::table('settlement_bills')->where('id', $billId)->first(); if ($bill === null || (string) $bill->bill_type !== 'player') { return; } DB::table('rebate_records') ->where('player_id', (int) $bill->owner_id) ->where('settlement_period_id', (int) $bill->settlement_period_id) ->where('status', 'in_bill') ->update([ 'status' => 'settled', 'updated_at' => now(), ]); } }