access->resource('agent', $agent); throw new ApiException(50303, '佣金提现尚未开通,未创建提现申请', 503); } public function __construct(private readonly Access $access) { } private function enterpriseIds(array $agents) { return WaterEnterprise::query()->whereIn('agent_id', $agents)->select('id'); } public function dashboard(int $agent): array { $agentRow = $this->access->resource('agent', $agent); $agentIds = $this->access->agentIds($agent); $start = date('Y-m-01 00:00:00'); $now = date('Y-m-d H:i:s'); $orders = WaterOrder::query()->whereIn('enterprise_id', $this->enterpriseIds($agentIds)); $month = (int) (clone $orders)->whereBetween('created_at', [$start, $now])->sum('amount_cents'); $previousStart = date('Y-m-01 00:00:00', strtotime('first day of last month')); $previousEnd = date('Y-m-d H:i:s', strtotime($start) - 1); $previous = (int) (clone $orders)->whereBetween('created_at', [$previousStart, $previousEnd])->sum('amount_cents'); $trend = []; for ($i = 6;$i >= 0;$i--) { $day = date('Y-m-d', strtotime('-' . $i . ' days')); $trend[] = [ 'day' => $day, 'amount_cents' => (int) (clone $orders)->whereBetween('created_at', [$day . ' 00:00:00', min($day . ' 23:59:59', $now)])->sum('amount_cents'), ]; } $customers = WaterEnterprise::query()->whereIn('agent_id', $agentIds); $pendingCommission = (int) WaterCommission::query()->where('agent_id', $agent) ->where('status', 'pending')->sum('amount_cents'); return [ 'agent' => [ 'id' => (int) $agentRow->id, 'name' => $agentRow->name, 'region' => $agentRow->region, ], 'summary' => [ 'month_amount_cents' => $month, 'previous_month_amount_cents' => $previous, 'growth_percent' => $previous > 0 ? round(($month - $previous) * 100 / $previous, 2) : null, 'customer_count' => (clone $customers)->count(), 'new_customers' => (clone $customers)->whereBetween('created_at', [$start, $now])->count(), 'team_count' => count($agentIds) - 1, 'estimated_commission_cents' => $pendingCommission, 'withdrawable_cents' => 0, 'pending_commission_cents' => $pendingCommission, ], 'trend' => $trend, 'attribution' => 'current_enterprise_agent', 'capabilities' => [ 'commission' => true, 'withdrawal' => false, ], ]; } public function customers(int $agent, array $input): array { $agentIds = $this->access->agentIds($agent); $page = new Pagination($input); $keyword = Fields::text($input, 'keyword', 120, false); $filter = Fields::choice($input, 'filter', ['all', 'new', 'unpaid'], 'all'); $asOf = date('Y-m-d H:i:s'); $base = WaterEnterprise::query()->whereIn('agent_id', $agentIds); $paid = WaterOrder::query()->where('created_at', '<=', $asOf)->select('enterprise_id'); $summary = [ 'all' => (clone $base)->count(), 'new' => (clone $base)->whereBetween('created_at', [date('Y-m-01 00:00:00'), date('Y-m-d H:i:s')])->count(), 'unpaid' => (clone $base)->whereNotIn('id', $paid)->count(), ]; if ($filter === 'new') { $base->whereBetween('created_at', [date('Y-m-01 00:00:00'), date('Y-m-d H:i:s')]); } if ($filter === 'unpaid') { $base->whereNotIn('id', $paid); } if ($keyword !== '') { $base->where(fn ($query) => $query->where('name', 'like', '%' . $keyword . '%')->orWhere('contact', 'like', '%' . $keyword . '%')->orWhere('phone', 'like', '%' . $keyword . '%')); } $total = (clone $base)->count(); $paidOrders = static fn ($query) => $query->where('created_at', '<=', $asOf); $rows = $base ->withSum(['orders as purchased_amount' => $paidOrders], 'amount_cents') ->withMax(['orders as last_purchase_at' => $paidOrders], 'created_at') ->withExists(['orders as has_purchased' => $paidOrders]) ->withCount('devices') ->orderByDesc('id')->offset($page->offset())->limit($page->pageSize)->get()->map(function ($enterprise) { return [ 'id' => (int) $enterprise->id, 'name' => $enterprise->name, 'contact' => $enterprise->contact, 'phone' => WaterPresenter::phone($enterprise->phone), 'region' => $enterprise->region, 'created_at' => $enterprise->created_at, 'amount_cents' => (int) $enterprise->purchased_amount, 'last_purchase_at' => $enterprise->last_purchase_at, 'has_purchased' => (bool) $enterprise->has_purchased, 'device_count' => (int) $enterprise->devices_count, ]; })->all(); return $page->result($rows, $total) + ['summary' => $summary]; } public function orders(int $agent, array $input): array { $agentIds = $this->access->agentIds($agent); $page = new Pagination($input); $query = WaterOrder::query()->whereIn('enterprise_id', $this->enterpriseIds($agentIds))->where('created_at', '<=', date('Y-m-d H:i:s')); if (isset($input['enterprise_id'])) { $query->where('enterprise_id', Fields::id($input, 'enterprise_id')); } $total = (clone $query)->count(); return $page->result($query->orderByDesc('id')->offset($page->offset())->limit($page->pageSize) ->get([ 'id', 'enterprise_id', 'package_name', 'buckets', 'amount_cents', 'created_at', ])->toArray(), $total); } public function commissions(int $agent, array $input = []): array { $this->access->resource('agent', $agent); $page = new Pagination($input); $status = Fields::choice($input, 'status', ['all', 'pending', 'settled', 'adjustment', 'withdrawn'], 'all'); $source = Fields::choice($input, 'source', ['regular', 'demo'], 'regular'); if ($source === 'demo' && $agent === 7 && TestPhoneIdentityProvider::enabled()) { $fixture = WaterLedger::query()->where('enterprise_id', 49)->where('kind', 'demo_seed') ->where('reference', 'TEST-MINI-COMMISSIONS-7-20261007')->first(); if ($fixture) { $data = json_decode($fixture->note, true, 512, JSON_THROW_ON_ERROR); $query = WaterLedger::query()->where('enterprise_id', 49)->where('kind', 'demo_seed') ->where('reference', 'like', 'TEST-MINI-COMMISSIONS-7-20261007-ITEM-%'); if ($status !== 'all') { $query->where('note->status', $status); } $total = (clone $query)->count(); $items = $query->orderByDesc('id')->offset($page->offset())->limit($page->pageSize)->get() ->map(fn ($record) => json_decode($record->note, true, 512, JSON_THROW_ON_ERROR))->all(); return array_replace($data, $page->result($items, $total)); } } if ($source === 'demo') { return [ 'enabled' => false, 'reason' => '当前账号没有佣金演示数据。', 'withdrawable_cents' => null, 'pending_cents' => null, 'total_cents' => null, 'withdrawn_cents' => null, 'demo' => true, ] + $page->result([], 0); } $base = WaterCommission::query()->where('agent_id', $agent); $pending = (int) (clone $base)->where('status', 'pending')->sum('amount_cents'); $totalAmount = (int) (clone $base)->sum('amount_cents'); $list = clone $base; if ($status !== 'all') { $list->where('status', $status); } $total = (clone $list)->count(); $items = $list->orderByDesc('id')->offset($page->offset())->limit($page->pageSize) ->get()->map(static fn (WaterCommission $row) => [ 'id' => (int) $row->id, 'customer_name' => $row->enterprise_name, 'title' => '购水订单佣金(' . intdiv((int) $row->rate_basis_points, 100) . '%)', 'status' => $row->status, 'status_text' => match ($row->status) { 'pending' => '待结算', 'settled' => '已结算', 'adjustment' => '已冲减', 'withdrawn' => '已提现', default => $row->status, }, 'amount_cents' => (int) $row->amount_cents, 'created_at' => $row->created_at, ])->all(); return [ 'enabled' => true, 'reason' => WaterCommissionPolicy::message(), 'withdrawable_cents' => 0, 'pending_cents' => $pending, 'total_cents' => $totalAmount, 'withdrawn_cents' => 0, ] + $page->result($items, $total); } }