*/ public function closePeriod(int $periodId): array { AgentSettlementProductionGuard::assertProductionCloseAllowed(); $period = DB::table('settlement_periods')->where('id', $periodId)->first(); if ($period === null) { throw new \InvalidArgumentException('period_not_found'); } if ((string) $period->status === 'closed') { throw new \InvalidArgumentException('period_already_closed'); } $adminSiteId = (int) $period->admin_site_id; $aggregate = $this->aggregator->aggregate( $adminSiteId, (string) $period->period_start, (string) $period->period_end, ); if ($aggregate['players'] === []) { throw new \InvalidArgumentException('period_no_ledger_rows'); } $billIds = $this->billGenerator->generate($periodId, $adminSiteId, $aggregate); $roundingDiff = $this->platformRounding->apply($periodId, $aggregate); $rebateStats = $this->periodCloseRebate->dispatchAndAllocate( $periodId, (string) $period->period_start, (string) $period->period_end, ); $unsettled = $this->unsettledWarning->countForSite( $adminSiteId, (string) $period->period_start, (string) $period->period_end, ); DB::table('settlement_periods')->where('id', $periodId)->update([ 'status' => 'closed', 'updated_at' => now(), ]); DB::table('share_ledger') ->whereBetween('settled_at', [$period->period_start, $period->period_end]) ->update(['settlement_period_id' => $periodId]); $this->reconcileAllocatedCreditForSite($adminSiteId); return [ 'period_id' => $periodId, 'bill_ids' => $billIds, 'player_count' => count($aggregate['players']), 'agent_edges' => $aggregate['agent_edges'], 'rebate_dispatched' => $rebateStats['dispatched'], 'rebate_allocations' => $rebateStats['allocations'], 'unsettled_ticket_count' => $unsettled['count'], 'unsettled_ticket_sample' => $unsettled['ticket_item_ids'], 'platform_rounding_adjustment' => $roundingDiff, ]; } /** 关账后按真理源重算各代理「已下发额度」,避免与直属玩家/下级代理授信脱节。 */ private function reconcileAllocatedCreditForSite(int $adminSiteId): void { $nodes = AgentNode::query()->where('admin_site_id', $adminSiteId)->get(); foreach ($nodes as $node) { $this->allocatedSync->syncForAgent($node); } } }