findOrFail($teacher); $items = TeacherFollowRecord::query() ->with(['adminUser', 'followMethodItem', 'urgencyItem']) ->where('teacher_id', $teacher) ->orderByDesc('followed_at') ->orderByDesc('id') ->get() ->map(fn (TeacherFollowRecord $r) => $this->serialize($r)); return $this->ok(['items' => $items]); } public function store(Request $request, int $teacher): JsonResponse { $model = Teacher::query()->with('starLevelItem')->findOrFail($teacher); $this->gridScope->assertTeacherAccessible($this->gridScope->userFromRequest($request), $model); $methodTypeId = DictType::query()->where('code', 'follow_method')->where('status', 1)->value('id'); $urgencyTypeId = DictType::query()->where('code', 'follow_urgency')->where('status', 1)->value('id'); $statusTypeId = DictType::query()->where('code', 'teacher_status')->where('status', 1)->value('id'); $data = $request->validate([ 'subject' => ['required', 'string', 'max:255'], 'content' => ['nullable', 'string'], 'followed_at' => ['required', 'date'], 'follow_method_dict_item_id' => $this->dictItemRules($methodTypeId, 'required'), 'urgency_dict_item_id' => $this->dictItemRules($urgencyTypeId, 'required'), 'admin_user_id' => ['nullable', 'integer', 'exists:admin_users,id'], 'next_follow_subject' => ['required', 'string', 'max:255'], 'next_follow_date' => ['nullable', 'date'], 'result' => ['nullable', 'string', 'max:64'], ]); $nextFollowDate = $data['next_follow_date'] ?? $this->followPlan->nextFollowDateFromStar($model->starLevelItem)?->toDateString(); if (! $nextFollowDate) { return $this->fail('请填写下次跟进日期,或先为老师设置有效星级', 422); } $adminId = $data['admin_user_id'] ?? $request->user()?->id; $record = TeacherFollowRecord::query()->create([ 'teacher_id' => $model->id, 'admin_user_id' => $adminId, 'subject' => $data['subject'], 'content' => $data['content'] ?? null, 'follow_method_dict_item_id' => $data['follow_method_dict_item_id'], 'urgency_dict_item_id' => $data['urgency_dict_item_id'], 'next_follow_subject' => $data['next_follow_subject'], 'next_follow_date' => $nextFollowDate, 'followed_at' => $data['followed_at'], 'result' => $data['result'] ?? null, ]); $model->next_follow_subject = $data['next_follow_subject']; $model->next_follow_date = $nextFollowDate; if (($data['result'] ?? '') === 'become_partner' && $statusTypeId) { $partnerItem = DictItem::query() ->where('dict_type_id', $statusTypeId) ->where('value', 'partner') ->where('status', 1) ->first(); if ($partnerItem) { $model->status_dict_item_id = $partnerItem->id; $model->is_partner = true; $model->converted_at = now(); } } $model->save(); return $this->ok(['id' => $record->id], '已保存跟进'); } /** * @return array */ protected function dictItemRules(?int $dictTypeId, string $presence): array { if (! $dictTypeId) { return ['required', 'integer']; } $exists = Rule::exists('dict_items', 'id')->where( fn ($q) => $q->where('dict_type_id', $dictTypeId)->where('status', 1) ); return ['required', 'integer', $exists]; } /** * @return array */ protected function serialize(TeacherFollowRecord $r): array { return [ 'id' => $r->id, 'subject' => $r->subject, 'content' => $r->content, 'followed_at' => $r->followed_at?->toDateString(), 'follow_method_item' => $r->followMethodItem ? [ 'id' => $r->followMethodItem->id, 'label' => $r->followMethodItem->label, ] : null, 'urgency_item' => $r->urgencyItem ? [ 'id' => $r->urgencyItem->id, 'label' => $r->urgencyItem->label, 'value' => $r->urgencyItem->value, ] : null, 'next_follow_subject' => $r->next_follow_subject, 'next_follow_date' => $r->next_follow_date?->toDateString(), 'operator_name' => $r->adminUser?->real_name ?: $r->adminUser?->username, 'result' => $r->result, 'created_at' => $r->created_at?->toIso8601String(), ]; } }