where('agent_id', $agent->id)->where('status', AgentEquityOrder::STATUS_PENDING)->first(); $held = bcadd((string) $agent->equity_percent, '0', 4); $remaining = bcsub((string) $setting->equity_max_percent, $held, 4); return [ 'enabled' => (int) $setting->equity_enabled, 'price_per_percent' => (string) $setting->equity_price_per_percent, 'min_percent' => (string) $setting->equity_min_percent, 'max_percent' => (string) $setting->equity_max_percent, 'equity_percent' => $held, 'remaining_percent' => bccomp($remaining, '0', 4) > 0 ? $remaining : '0.0000', 'balance' => (string) $agent->balance, 'pending_order' => $pending, ]; } public function order(Agent $agent, string $percent): AgentEquityOrder { $order = DB::transaction(function () use ($agent, $percent) { $agent = Agent::query()->lockForUpdate()->findOrFail($agent->id); if (!$agent->isEquity()) throw new \RuntimeException('只有入股代理可以购买股份'); $setting = AgentSetting::current(); if (!(int) $setting->equity_enabled || bccomp((string) $setting->equity_price_per_percent, '0', 4) <= 0) { throw new \RuntimeException('入股暂未开放'); } if (bccomp($percent, (string) $setting->equity_min_percent, 4) < 0 || bccomp($percent, (string) $setting->equity_max_percent, 4) > 0) { throw new \InvalidArgumentException("购买比例须在 {$setting->equity_min_percent}% 至 {$setting->equity_max_percent}% 之间"); } if (AgentEquityOrder::query()->where('agent_id', $agent->id)->where('status', AgentEquityOrder::STATUS_PENDING)->exists()) { throw new \RuntimeException('已有待付款的入股订单,请先充值或取消'); } if (bccomp(bcadd((string) $agent->equity_percent, $percent, 4), (string) $setting->equity_max_percent, 4) > 0) { throw new \RuntimeException("累计持股不能超过 {$setting->equity_max_percent}%"); } return AgentEquityOrder::query()->create([ 'order_no' => 'AE' . now()->format('YmdHis') . strtoupper(Str::random(6)), 'agent_id' => $agent->id, 'percent' => bcadd($percent, '0', 4), 'price_per_percent' => (string) $setting->equity_price_per_percent, 'amount' => bcmul($percent, (string) $setting->equity_price_per_percent, 4), 'status' => AgentEquityOrder::STATUS_PENDING, ]); }); $this->fulfillPending((int) $agent->id); return $order->fresh(); } public function cancel(Agent $agent, int $orderId): AgentEquityOrder { return $this->cancelOrder(fn ($query) => $query->where('agent_id', $agent->id), $orderId, null, ''); } /** 总后台取消待付款订单(如代理长期不充值),需填写原因。 */ public function adminCancel(int $orderId, int $adminId, string $remark): AgentEquityOrder { $remark = trim($remark); if ($remark === '') throw new \InvalidArgumentException('请填写取消原因'); return $this->cancelOrder(fn ($query) => $query, $orderId, $adminId, $remark); } private function cancelOrder(callable $scope, int $orderId, ?int $adminId, string $remark): AgentEquityOrder { return DB::transaction(function () use ($scope, $orderId, $adminId, $remark) { $order = $scope(AgentEquityOrder::query())->lockForUpdate()->findOrFail($orderId); if ($order->status !== AgentEquityOrder::STATUS_PENDING) throw new \RuntimeException('只有待付款订单可以取消'); $order->status = AgentEquityOrder::STATUS_CANCELLED; $order->cancelled_by = $adminId; $order->cancel_remark = $remark; $order->save(); return $order; }); } /** * 总后台:全部代理的入股购买记录。summary 按当前筛选条件汇总: * 已开通的比例和金额、待付款笔数和金额。 */ public function paginate(array $params): array { $page = max(1, (int) ($params['page'] ?? 1)); $limit = min(100, max(1, (int) ($params['limit'] ?? 20))); $query = AgentEquityOrder::query(); if (!empty($params['agent_id'])) $query->where('agent_id', (int) $params['agent_id']); if (!empty($params['username'])) { $query->whereIn('agent_id', Agent::query()->where('username', 'like', '%' . $params['username'] . '%')->select('id')); } if (!empty($params['order_no'])) $query->where('order_no', $params['order_no']); if (!empty($params['status'])) $query->where('status', $params['status']); $dateColumn = ($params['date_type'] ?? '') === 'completed_at' ? 'completed_at' : 'created_at'; if (!empty($params['start_date'])) $query->where($dateColumn, '>=', $params['start_date'] . ' 00:00:00'); if (!empty($params['end_date'])) $query->where($dateColumn, '<=', $params['end_date'] . ' 23:59:59'); $sums = (clone $query)->selectRaw( 'COALESCE(SUM(CASE WHEN status = ? THEN percent ELSE 0 END), 0) AS completed_percent, ' . 'COALESCE(SUM(CASE WHEN status = ? THEN amount ELSE 0 END), 0) AS completed_amount, ' . 'COALESCE(SUM(CASE WHEN status = ? THEN 1 ELSE 0 END), 0) AS pending_count, ' . 'COALESCE(SUM(CASE WHEN status = ? THEN amount ELSE 0 END), 0) AS pending_amount', [AgentEquityOrder::STATUS_COMPLETED, AgentEquityOrder::STATUS_COMPLETED, AgentEquityOrder::STATUS_PENDING, AgentEquityOrder::STATUS_PENDING] )->first(); $total = (clone $query)->count(); $list = $query->with('agent:id,username,real_name,type,equity_percent,balance')->orderByDesc('id')->forPage($page, $limit)->get(); $summary = [ 'completed_percent' => bcadd((string) $sums->completed_percent, '0', 4), 'completed_amount' => bcadd((string) $sums->completed_amount, '0', 4), 'pending_count' => (int) $sums->pending_count, 'pending_amount' => bcadd((string) $sums->pending_amount, '0', 4), // 全平台入股代理当前持股合计(不受筛选影响) 'total_equity_percent' => bcadd((string) Agent::query()->where('type', Agent::TYPE_EQUITY)->sum('equity_percent'), '0', 4), ]; return compact('total', 'page', 'limit', 'list', 'summary'); } /** 余额足够时完成待付款订单(下单后、充值到账后调用)。 */ public function fulfillPending(int $agentId): int { return DB::transaction(function () use ($agentId) { $agent = Agent::query()->lockForUpdate()->findOrFail($agentId); if (!$agent->isEquity()) return 0; $done = 0; $orders = AgentEquityOrder::query()->where('agent_id', $agentId)->where('status', AgentEquityOrder::STATUS_PENDING) ->orderBy('id')->lockForUpdate()->get(); foreach ($orders as $order) { if (bccomp((string) $agent->balance, (string) $order->amount, 4) < 0) break; $this->wallets->adjust($agentId, (string) $order->amount, 'equity_purchase', '购买股份 ' . $order->percent . '%', 'equity-order:' . $order->id, 'agent', $agentId, 'agent_equity_order', (int) $order->id); $agent = Agent::query()->lockForUpdate()->findOrFail($agentId); $agent->equity_percent = bcadd((string) $agent->equity_percent, (string) $order->percent, 4); $agent->save(); $order->status = AgentEquityOrder::STATUS_COMPLETED; $order->completed_at = now(); $order->save(); ++$done; } return $done; }); } }