| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122 |
- <?php
- namespace App\Services\Agent;
- use App\Models\Agent\Agent;
- use App\Models\Agent\AgentApplication;
- use App\Models\User;
- use App\Services\OperationAuditService;
- use App\Services\SiteRealtimeService;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Validator;
- use Illuminate\Validation\Rule;
- class AgentApplicationService
- {
- public function listing(array $input): array
- {
- $data = Validator::make($input, [
- 'page' => ['nullable', 'integer', 'min:1'], 'limit' => ['nullable', 'integer', 'between:1,100'],
- 'status' => ['nullable', 'in:pending,approved,rejected'], 'username' => ['nullable', 'string', 'max:64'],
- 'member_id' => ['nullable', 'string', 'max:64'],
- ])->validate();
- $query = DB::table('agent_applications as a')->join('users as u', 'u.id', '=', 'a.user_id');
- if (!empty($data['status'])) $query->where('a.status', $data['status']);
- if (!empty($data['username'])) $query->where('a.username', 'like', '%' . $data['username'] . '%');
- if (!empty($data['member_id'])) $query->where('u.member_id', $data['member_id']);
- $total = (clone $query)->count();
- $rows = $query->select('a.id', 'a.user_id', 'u.member_id', 'u.account as member_account', 'a.username',
- 'a.status', 'a.agent_id', 'a.reject_reason', 'a.reviewed_by', 'a.reviewed_at', 'a.created_at')
- ->orderByDesc('a.id')->forPage((int) ($data['page'] ?? 1), (int) ($data['limit'] ?? 20))->get();
- return ['total' => $total, 'data' => $rows];
- }
- public function audit(array $input, AgentService $agents): array
- {
- $data = Validator::make($input, [
- 'id' => ['required', 'integer', 'min:1'], 'status' => ['required', 'in:approved,rejected'],
- 'remark' => ['required_if:status,rejected', 'nullable', 'string', 'max:500'],
- 'flow_commission_profile_id' => ['nullable', 'integer', Rule::exists('agent_flow_commission_profiles', 'id')->whereNull('owner_agent_id')],
- 'profit_commission_profile_id' => ['nullable', 'integer', Rule::exists('agent_profit_commission_profiles', 'id')->whereNull('owner_agent_id')],
- ])->validate();
- return DB::transaction(function () use ($data, $agents) {
- $found = AgentApplication::findOrFail($data['id']);
- $member = User::where('id', $found->user_id)->lockForUpdate()->firstOrFail();
- $application = AgentApplication::where('id', $found->id)->lockForUpdate()->firstOrFail();
- if ($application->status !== 'pending') {
- if ($application->status === $data['status']) return $application->toArray();
- throw new \RuntimeException('申请已审核,不能修改审核结果');
- }
- $this->assertMember($member, $data['status'] === 'rejected');
- $actor = OperationAuditService::actor();
- if (!$actor['operator_id']) throw new \RuntimeException('请使用后台管理员身份');
- $before = $application->toArray();
- if ($data['status'] === 'approved') {
- if (Agent::where('owner_user_id', $member->id)->exists()) throw new \RuntimeException('该会员已是代理');
- if (Agent::whereRaw('LOWER(username) = ?', [strtolower($application->username)])->exists()) throw new \RuntimeException('代理账号已被使用,请驳回后重新申请');
- $options = array_filter(array_intersect_key($data, array_flip(['flow_commission_profile_id', 'profit_commission_profile_id'])), fn ($value) => $value !== null);
- $agent = $agents->createFromApplication($application, $options + [
- 'real_name' => $member->first_name ?: '', 'language' => in_array($member->language, ['zh', 'en', 'vi'], true) ? $member->language : 'zh',
- ], (int) $actor['operator_id']);
- $this->bindMember($agent, (int) $member->id);
- $application->agent_id = $agent->id;
- }
- $application->status = $data['status'];
- $application->reject_reason = $data['status'] === 'rejected' ? trim((string) $data['remark']) : '';
- if ($data['status'] === 'rejected' && $application->reject_reason === '') throw new \RuntimeException('请填写驳回原因');
- $application->reviewed_by = $actor['operator_id'];
- $application->reviewed_at = now('Asia/Shanghai')->format('Y-m-d H:i:s');
- $application->password_hash = $application->withdraw_password_hash = null;
- $application->pending_user_id = $application->pending_username = null;
- $application->save();
- $this->notify($application, $member, $actor);
- OperationAuditService::record('agent_application', $application->id, $application->status, $before, $application);
- return $application->toArray();
- }, 3);
- }
- public function bindMember(Agent $agent, int $userId): Agent
- {
- return DB::transaction(function () use ($agent, $userId) {
- $member = User::where('id', $userId)->lockForUpdate()->firstOrFail();
- $this->assertMember($member);
- $agent = Agent::where('id', $agent->id)->lockForUpdate()->firstOrFail();
- if ($agent->owner_user_id !== null && (int) $agent->owner_user_id !== $userId) throw new \RuntimeException('代理已绑定其他会员');
- if (Agent::where('owner_user_id', $userId)->where('id', '<>', $agent->id)->exists()) throw new \RuntimeException('该会员已绑定其他代理账号');
- $agent->forceFill(['owner_user_id' => $userId])->save();
- return $agent->load(['flowCommissionProfile', 'profitCommissionProfile']);
- }, 3);
- }
- private function assertMember(User $member, bool $allowBanned = false): void
- {
- if ((int) $member->from !== 1 || !empty($member->delete_time) || (!$allowBanned && (int) $member->is_banned === 1)) {
- throw new \RuntimeException('会员状态不允许申请或绑定代理');
- }
- }
- private function notify(AgentApplication $application, User $member, array $actor): void
- {
- $approved = $application->status === 'approved';
- $account = htmlspecialchars($application->username, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
- $reason = htmlspecialchars($application->reject_reason, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
- $texts = [
- 'zh' => ['title' => $approved ? '代理申请已通过' : '代理申请未通过', 'content' => $approved ? "您的代理申请已通过,代理账号:{$account}。请使用申请时设置的密码登录代理后台。" : "您的代理申请未通过,原因:{$reason}。您可以重新申请。"],
- 'en' => ['title' => $approved ? 'Agent application approved' : 'Agent application rejected', 'content' => $approved ? "Your agent account {$account} is ready. Sign in to the agent portal with the password you set when applying." : "Your agent application was rejected. Reason: {$reason}. You may apply again."],
- 'vi' => ['title' => $approved ? 'Đơn đăng ký đại lý được duyệt' : 'Đơn đăng ký đại lý bị từ chối', 'content' => $approved ? "Tài khoản đại lý {$account} đã sẵn sàng. Đăng nhập bằng mật khẩu đã đặt khi đăng ký." : "Đơn đăng ký đại lý bị từ chối. Lý do: {$reason}. Bạn có thể đăng ký lại."],
- ];
- $now = now('Asia/Shanghai')->format('Y-m-d H:i:s');
- $language = in_array($member->language, ['zh', 'en', 'vi'], true) ? $member->language : 'zh';
- $messageId = DB::table('site_messages')->insertGetId([
- 'category' => 'internal', 'recipient_type' => 'specified_users', 'recipient_ids' => json_encode([$member->id]),
- 'status' => 'sent', 'send_at' => $now, 'sent_at' => $now, 'recipient_count' => 1, 'skipped_count' => 0,
- 'sender_id' => $actor['operator_id'], 'sender_name' => $actor['operator_name'],
- 'request_id' => 'agent-application:' . $application->id, 'request_hash' => hash('sha256', json_encode($texts)),
- 'created_at' => $now, 'updated_at' => $now,
- ]);
- foreach ($texts as $lang => $text) DB::table('site_message_translations')->insert($text + ['subtitle' => '', 'language' => $lang, 'message_id' => $messageId]);
- DB::table('site_message_recipients')->insert(['message_id' => $messageId, 'recipient_kind' => 'user', 'recipient_id' => $member->id, 'language' => $language, 'created_at' => $now]);
- OperationAuditService::record('site_message', $messageId, 'send', [], ['recipient_type' => 'specified_users', 'recipient_ids' => [$member->id], 'category' => 'internal', 'status' => 'sent']);
- SiteRealtimeService::changed('message', $messageId, 'send');
- }
- }
|