validateStudentFilters($request)) return $error; $keyword = trim((string) $request->input('keyword', '')); $status = $request->input('status'); $pageSize = min(max((int) $request->input('page_size', 20), 1), 100); $query = Company::query()->select('id', 'company_name', 'credit_code', 'updated_at')->with('qccAccount'); if ($keyword !== '') { $query->where(function ($subQuery) use ($keyword) { $subQuery->where('company_name', 'like', '%' . $keyword . '%') ->orWhere('credit_code', 'like', '%' . $keyword . '%'); }); } $this->applyStudentFilters($query, $request); $query->with(['users' => $this->studentRelation()]); if ($status === 'none') { $query->doesntHave('qccAccount'); } elseif (in_array($status, [CompanyQccAccount::STATUS_SELECTED, CompanyQccAccount::STATUS_OCCUPIED, CompanyQccAccount::STATUS_UNKNOWN], true)) { $query->whereHas('qccAccount', fn($relation) => $relation->where('status', $status)); } return $this->studentResponse($query->orderByDesc('id')->paginate($pageSize)); } public function index(Request $request) { if ($error = $this->validateStudentFilters($request)) return $error; $status = $request->input('status'); $pageSize = min(max((int) $request->input('page_size', 20), 1), 100); $query = CompanyQccAccount::query()->with(['company:id,company_name,credit_code', 'company.users' => $this->studentRelation()]); $query->whereHas('company', function ($company) use ($request) { $keyword = trim((string) $request->input('keyword', '')); if ($keyword !== '') $company->where(fn ($q) => $q->where('company_name', 'like', '%'.$keyword.'%')->orWhere('credit_code', 'like', '%'.$keyword.'%')); $this->applyStudentFilters($company, $request); }); if (in_array($status, [CompanyQccAccount::STATUS_SELECTED, CompanyQccAccount::STATUS_OCCUPIED, CompanyQccAccount::STATUS_UNKNOWN], true)) { $query->where('status', $status); } return $this->studentResponse($query->orderByDesc('id')->paginate($pageSize)); } private function studentResponse($page) { foreach ($page as $row) { $company = $row instanceof Company ? $row : $row->company; foreach ($company ? $company->users : [] as $user) { // Avoid unrelated appointment/company accessors and extra queries during serialization. $user->setAppends([]); foreach ($user->courseSigns as $sign) { $sign->setAppends([]); if ($sign->course) $sign->course->setAppends([]); } } } return $this->success($page); } private function validateStudentFilters(Request $request) { $validator = Validator::make($request->all(), [ 'keyword' => 'nullable|string|max:200', 'user_name' => 'nullable|string|max:100', 'is_schoolmate' => 'nullable|in:0,1', 'course_type_id' => 'nullable|integer|min:1', 'course_name' => 'nullable|string|max:200', ]); return $validator->fails() ? $this->fail([ResponseCode::ERROR_PARAMETER, $validator->errors()->first()]) : null; } private function studentRelation(): \Closure { return function ($users) { $users->select('id', 'company_id', 'no', 'name', 'username', 'is_schoolmate', 'company_position') ->whereHas('courseSigns', fn ($sign) => $sign->where('status', 1)) ->with(['courseSigns' => fn ($sign) => $sign->select('id', 'user_id', 'course_id', 'status') ->where('status', 1)->with('course:id,name,type,start_date')]); }; } private function applyStudentFilters($companies, Request $request): void { if (!$request->filled('user_name') && !$request->filled('is_schoolmate') && !$request->filled('course_type_id') && !$request->filled('course_name')) return; $companies->whereHas('users', function ($user) use ($request) { if ($request->filled('user_name')) { $name = trim($request->user_name); $user->where(fn ($q) => $q->where('name', 'like', '%'.$name.'%')->orWhere('username', 'like', '%'.$name.'%')); } if ($request->filled('is_schoolmate')) $user->where('is_schoolmate', $request->is_schoolmate); $user->whereHas('courseSigns', function ($sign) use ($request) { $sign->where('status', 1); if ($request->filled('course_type_id') || $request->filled('course_name')) { $sign->whereHas('course', function ($course) use ($request) { if ($request->filled('course_type_id')) $course->where('type', $request->course_type_id); if ($request->filled('course_name')) $course->where('name', 'like', '%'.trim($request->course_name).'%'); }); } }); }); } public function select(Request $request) { $validator = Validator::make($request->all(), ['company_ids' => 'required|array|min:1', 'company_ids.*' => 'integer|distinct']); if ($validator->fails()) { return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } $companyIds = $request->input('company_ids'); $companies = Company::query()->whereIn('id', $companyIds)->get()->keyBy('id'); foreach ($companyIds as $companyId) { $company = $companies->get($companyId); if (!$company || empty(trim((string) $company->credit_code))) { return $this->fail([ResponseCode::ERROR_BUSINESS, '存在不存在或缺少统一社会信用代码的企业']); } } try { DB::transaction(function () use ($companyIds) { $existingCompanyIds = CompanyQccAccount::query()->whereIn('company_id', $companyIds)->lockForUpdate()->pluck('company_id'); if ($existingCompanyIds->isNotEmpty()) { throw new RuntimeException('存在已纳入企业户标注的企业'); } foreach ($companyIds as $companyId) { CompanyQccAccount::query()->create([ 'company_id' => $companyId, 'status' => CompanyQccAccount::STATUS_SELECTED, 'selected_by' => $this->getUserId(), 'selected_at' => now(), ]); } }); } catch (RuntimeException|QueryException $exception) { return $this->fail([ResponseCode::ERROR_BUSINESS, $exception->getMessage()]); } $this->log('纳入企查查企业户候选池', implode(',', $companyIds)); return $this->success('已纳入候选池'); } public function cancel(Request $request) { $validator = Validator::make($request->all(), ['id' => 'required|integer']); if ($validator->fails()) { return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } try { $companyId = DB::transaction(function () use ($request) { $account = CompanyQccAccount::query()->whereKey($request->input('id'))->lockForUpdate()->first(); if (!$account) { throw new RuntimeException('企业户关系不存在'); } if ($account->status !== CompanyQccAccount::STATUS_SELECTED) { throw new RuntimeException('仅候选状态可以取消'); } $companyId = $account->company_id; $account->delete(); return $companyId; }); } catch (RuntimeException $exception) { return $this->fail([ResponseCode::ERROR_BUSINESS, $exception->getMessage()]); } $this->log('取消企查查企业户候选关系', (string) $companyId); return $this->success('已取消候选关系'); } public function unknownConfirm(Request $request) { $validator = Validator::make($request->all(), [ 'id' => 'required|integer', 'decision' => 'required|in:retry,occupied', 'evidence' => 'required|string|max:2000', ]); if ($validator->fails()) { return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } $decision = $request->input('decision'); $evidence = trim($request->input('evidence')); try { $account = DB::transaction(function () use ($request, $decision, $evidence) { $account = CompanyQccAccount::query()->whereKey($request->input('id'))->lockForUpdate()->first(); if (!$account || $account->status !== CompanyQccAccount::STATUS_UNKNOWN) { throw new RuntimeException('仅未知状态可以人工确认'); } if ($decision === 'occupied') { $account->update([ 'status' => CompanyQccAccount::STATUS_OCCUPIED, 'first_success_at' => now(), 'first_success_source' => 'manual-confirmation', 'first_success_ref' => $evidence, 'unknown_note' => $evidence, ]); } else { $account->update(['status' => CompanyQccAccount::STATUS_SELECTED, 'unknown_note' => $evidence]); } return $account->fresh(); }); } catch (RuntimeException $exception) { return $this->fail([ResponseCode::ERROR_BUSINESS, $exception->getMessage()]); } $this->log('人工确认企查查企业户未知状态:' . $decision, $evidence); return $this->success($account); } protected function log(string $name, string $remark = ''): void { $admin = $this->getUser(); if ($admin) { OperateLog::addLogs($admin, $name, $remark); } } }