systemModel = new ScheduleOverviewSystem(); $this->courseModel = new ScheduleOverviewCourse(); $this->scheduleModel = new ScheduleOverviewSchedule(); $this->scheduleGroupModel = new ScheduleOverviewScheduleGroup(); $this->scheduleModuleModel = new ScheduleOverviewScheduleModule(); $this->locationModel = new ScheduleOverviewLocation(); $this->ownerModel = new ScheduleOverviewOwner(); } public function overview() { $year = request('year', date('Y')); return $this->success([ 'systems' => $this->getSystems($year), 'courses' => $this->getCourses($year), // 兼容前端矩阵/统计:展开带班组 modules 为“按月扁平记录” 'schedules' => $this->getSchedulesFlatFromGroups($year), ]); } public function systemIndex() { return $this->success($this->getSystems(request('year', date('Y')))); } public function systemShow() { $all = request()->all(); $validator = Validator::make($all, [ 'id' => 'required', ], [ 'id.required' => '体系ID必填', ]); if ($validator->fails()) { return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } $detail = $this->systemModel->with('courses')->find($all['id']); if (empty($detail)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '体系不存在']); } return $this->success($detail); } public function systemSave() { $all = request()->all(); $validator = Validator::make($all, [ 'year' => 'required|string|size:4', 'name' => 'required|string|max:255', 'sort' => 'nullable|integer', ], [ 'year.required' => '年份必填', 'name.required' => '体系名称必填', ]); if ($validator->fails()) { return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } if (!empty($all['id'])) { $model = $this->systemModel->find($all['id']); if (empty($model)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '体系不存在']); } } else { $model = new ScheduleOverviewSystem(); $model->admin_id = $this->getUserId(); $model->department_id = optional($this->getUser())->department_id; } DB::beginTransaction(); try { $model->year = $all['year']; $model->name = $all['name']; $model->sort = (int) ($all['sort'] ?? 0); $model->save(); DB::commit(); return $this->success($model); } catch (\Throwable $throwable) { DB::rollBack(); return $this->fail([ResponseCode::ERROR_INSIDE, $throwable->getMessage()]); } } public function systemDestroy() { $all = request()->all(); $validator = Validator::make($all, [ 'id' => 'required', ], [ 'id.required' => '体系ID必填', ]); if ($validator->fails()) { return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } $model = $this->systemModel->find($all['id']); if (empty($model)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '体系不存在']); } DB::beginTransaction(); try { $courseIds = $this->courseModel->where('system_id', $model->id)->pluck('id')->toArray(); $this->scheduleModel->where('system_id', $model->id)->delete(); if (!empty($courseIds)) { $this->scheduleModel->whereIn('course_id', $courseIds)->delete(); $this->courseModel->whereIn('id', $courseIds)->delete(); } $model->delete(); DB::commit(); return $this->success('删除成功'); } catch (\Throwable $throwable) { DB::rollBack(); return $this->fail([ResponseCode::ERROR_INSIDE, $throwable->getMessage()]); } } public function courseIndex() { return $this->success($this->getCourses(request('year', date('Y')))); } public function courseShow() { $all = request()->all(); $validator = Validator::make($all, [ 'id' => 'required', ], [ 'id.required' => '课程ID必填', ]); if ($validator->fails()) { return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } $detail = $this->courseModel->with(['system', 'courseType'])->find($all['id']); if (empty($detail)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '课程不存在']); } return $this->success($detail); } public function courseSave() { $all = request()->all(); $validator = Validator::make($all, [ 'system_id' => 'required|integer', 'course_type_id' => 'nullable|integer', 'name' => 'required|string|max:255', 'sort' => 'nullable|integer', ], [ 'system_id.required' => '所属体系必填', 'name.required' => '课程名称必填', ]); if ($validator->fails()) { return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } $system = $this->systemModel->find($all['system_id']); if (empty($system)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '所属体系不存在']); } $courseType = null; if (!empty($all['course_type_id'])) { $courseType = CourseType::query()->find($all['course_type_id']); if (empty($courseType)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '课程类别不存在']); } } if (!empty($all['id'])) { $model = $this->courseModel->find($all['id']); if (empty($model)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '课程不存在']); } } else { $model = new ScheduleOverviewCourse(); $model->admin_id = $this->getUserId(); $model->department_id = optional($this->getUser())->department_id; } DB::beginTransaction(); try { $originSystemId = $model->system_id; $model->system_id = (int) $all['system_id']; $model->course_type_id = !empty($all['course_type_id']) ? (int) $all['course_type_id'] : null; $model->name = $courseType ? $courseType->name : $all['name']; $model->sort = (int) ($all['sort'] ?? 0); $model->save(); if (!empty($originSystemId) && (int) $originSystemId !== (int) $model->system_id) { $this->scheduleModel->where('course_id', $model->id)->update([ 'system_id' => $model->system_id, ]); } DB::commit(); return $this->success($model->load(['system', 'courseType'])); } catch (\Throwable $throwable) { DB::rollBack(); return $this->fail([ResponseCode::ERROR_INSIDE, $throwable->getMessage()]); } } public function courseDestroy() { $all = request()->all(); $validator = Validator::make($all, [ 'id' => 'required', ], [ 'id.required' => '课程ID必填', ]); if ($validator->fails()) { return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } $model = $this->courseModel->find($all['id']); if (empty($model)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '课程不存在']); } DB::beginTransaction(); try { $this->scheduleModel->where('course_id', $model->id)->delete(); $model->delete(); DB::commit(); return $this->success('删除成功'); } catch (\Throwable $throwable) { DB::rollBack(); return $this->fail([ResponseCode::ERROR_INSIDE, $throwable->getMessage()]); } } public function scheduleIndex() { $year = request('year', date('Y')); $list = $this->scheduleGroupModel ->with(['system', 'course', 'modules.location', 'modules.owner']) ->where('year', $year) ->orderBy('system_id') ->orderBy('course_id') ->orderBy('id') ->get(); return $this->success($list); } public function scheduleShow() { $all = request()->all(); $validator = Validator::make($all, [ 'id' => 'required', ], [ 'id.required' => '带班ID必填', ]); if ($validator->fails()) { return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } $detail = $this->scheduleGroupModel->with(['system', 'course', 'modules.location', 'modules.owner'])->find($all['id']); if (empty($detail)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '带班不存在']); } return $this->success($detail); } public function scheduleSave() { $all = request()->all(); $rules = [ 'year' => 'required|string|size:4', 'system_id' => 'required|integer', 'course_id' => 'nullable|integer', 'title' => 'required|string|max:255', 'modules' => 'required|array|min:1', 'modules.*.name' => 'nullable|string|max:255', 'modules.*.month' => 'required|integer|between:1,12', 'modules.*.location_id' => 'required|integer', 'modules.*.owner_id' => 'required|integer', 'modules.*.count_text' => 'nullable|string|max:255', ]; $validator = Validator::make($all, $rules, [ 'year.required' => '年份必填', 'system_id.required' => '体系必填', 'title.required' => '课程名称必填', 'modules.required' => '模块/期数必填', 'modules.array' => '模块/期数格式不正确', 'modules.min' => '模块/期数至少需要一条数据', 'modules.*.month.required' => '月份必填', 'modules.*.month.integer' => '月份格式不正确', 'modules.*.month.between' => '月份范围需在1到12之间', 'modules.*.location_id.required' => '地点必填', 'modules.*.owner_id.required' => '负责人必填', ]); if ($validator->fails()) { return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } $system = $this->systemModel->find($all['system_id']); if (empty($system)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '体系不存在']); } $course = null; if (!empty($all['course_id'])) { $course = $this->courseModel->find($all['course_id']); if (empty($course)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '课程不存在']); } if ((int) $course->system_id !== (int) $system->id) { return $this->fail([ResponseCode::ERROR_BUSINESS, '课程不属于当前体系']); } } if ((string) $system->year !== (string) $all['year']) { return $this->fail([ResponseCode::ERROR_BUSINESS, '体系不属于当前年份']); } $modules = $all['modules'] ?? []; $locationIds = collect($modules)->pluck('location_id')->filter()->unique()->values()->toArray(); $ownerIds = collect($modules)->pluck('owner_id')->filter()->unique()->values()->toArray(); $locations = $this->locationModel->whereIn('id', $locationIds)->get()->keyBy('id'); $owners = $this->ownerModel->whereIn('id', $ownerIds)->get()->keyBy('id'); foreach ($locationIds as $locationId) { if (!$locations->has($locationId)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '地点不存在']); } } foreach ($ownerIds as $ownerId) { if (!$owners->has($ownerId)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '负责人不存在']); } } if (!empty($all['id'])) { $model = $this->scheduleGroupModel->find($all['id']); if (empty($model)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '带班不存在']); } } else { $model = new ScheduleOverviewScheduleGroup(); $model->admin_id = $this->getUserId(); $model->department_id = optional($this->getUser())->department_id; } DB::beginTransaction(); try { $model->year = $all['year']; $model->system_id = (int) $all['system_id']; $model->course_id = !empty($all['course_id']) ? (int) $all['course_id'] : null; $model->title = $all['title']; $model->save(); // 简化:保存时用“全量覆盖”的方式更新 modules $this->scheduleModuleModel->where('group_id', $model->id)->delete(); foreach ($modules as $index => $moduleData) { $module = new ScheduleOverviewScheduleModule(); $module->group_id = $model->id; $module->name = $moduleData['name'] ?? null; $module->month = (int) $moduleData['month']; $module->location_id = (int) $moduleData['location_id']; $module->owner_id = (int) $moduleData['owner_id']; $module->count_text = $moduleData['count_text'] ?? ''; $module->sort = (int) ($moduleData['sort'] ?? $index); $module->admin_id = $this->getUserId(); $module->department_id = optional($this->getUser())->department_id; $module->save(); } DB::commit(); return $this->success($model->load(['system', 'course', 'modules.location', 'modules.owner'])); } catch (\Throwable $throwable) { DB::rollBack(); return $this->fail([ResponseCode::ERROR_INSIDE, $throwable->getMessage()]); } } public function scheduleDestroy() { $all = request()->all(); $validator = Validator::make($all, [ 'id' => 'required', ], [ 'id.required' => '带班ID必填', ]); if ($validator->fails()) { return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } $model = $this->scheduleGroupModel->find($all['id']); if (empty($model)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '带班不存在']); } DB::beginTransaction(); try { $this->scheduleModuleModel->where('group_id', $model->id)->delete(); $model->delete(); DB::commit(); } catch (\Throwable $throwable) { DB::rollBack(); return $this->fail([ResponseCode::ERROR_INSIDE, $throwable->getMessage()]); } return $this->success('删除成功'); } public function locationIndex() { $list = $this->locationModel ->orderBy('sort') ->orderBy('id') ->get(); return $this->success($list); } public function locationSave() { $all = request()->all(); $validator = Validator::make($all, [ 'name' => 'required|string|max:255', 'sort' => 'nullable|integer', 'status' => 'nullable|integer|in:0,1', ], [ 'name.required' => '地点名称必填', ]); if ($validator->fails()) { return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } if (!empty($all['id'])) { $model = $this->locationModel->find($all['id']); if (empty($model)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '地点不存在']); } } else { $model = new ScheduleOverviewLocation(); $model->admin_id = $this->getUserId(); $model->department_id = optional($this->getUser())->department_id; } DB::beginTransaction(); try { $model->name = $all['name']; $model->sort = (int) ($all['sort'] ?? 0); $model->status = (int) ($all['status'] ?? 1); $model->save(); DB::commit(); return $this->success($model); } catch (\Throwable $throwable) { DB::rollBack(); return $this->fail([ResponseCode::ERROR_INSIDE, $throwable->getMessage()]); } } public function locationDestroy() { $all = request()->all(); $validator = Validator::make($all, [ 'id' => 'required', ], [ 'id.required' => '地点ID必填', ]); if ($validator->fails()) { return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } $model = $this->locationModel->find($all['id']); if (empty($model)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '地点不存在']); } $used = $this->scheduleModuleModel->where('location_id', $model->id)->exists(); if ($used) { return $this->fail([ResponseCode::ERROR_BUSINESS, '地点已被使用,无法删除']); } $model->delete(); return $this->success('删除成功'); } public function ownerIndex() { $list = $this->ownerModel ->orderBy('sort') ->orderBy('id') ->get(); return $this->success($list); } public function ownerSave() { $all = request()->all(); $validator = Validator::make($all, [ 'name' => 'required|string|max:255', 'sort' => 'nullable|integer', 'status' => 'nullable|integer|in:0,1', ], [ 'name.required' => '负责人名称必填', ]); if ($validator->fails()) { return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } if (!empty($all['id'])) { $model = $this->ownerModel->find($all['id']); if (empty($model)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '负责人不存在']); } } else { $model = new ScheduleOverviewOwner(); $model->admin_id = $this->getUserId(); $model->department_id = optional($this->getUser())->department_id; } DB::beginTransaction(); try { $model->name = $all['name']; $model->sort = (int) ($all['sort'] ?? 0); $model->status = (int) ($all['status'] ?? 1); $model->save(); DB::commit(); return $this->success($model); } catch (\Throwable $throwable) { DB::rollBack(); return $this->fail([ResponseCode::ERROR_INSIDE, $throwable->getMessage()]); } } public function ownerDestroy() { $all = request()->all(); $validator = Validator::make($all, [ 'id' => 'required', ], [ 'id.required' => '负责人ID必填', ]); if ($validator->fails()) { return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]); } $model = $this->ownerModel->find($all['id']); if (empty($model)) { return $this->fail([ResponseCode::ERROR_BUSINESS, '负责人不存在']); } $used = $this->scheduleModuleModel->where('owner_id', $model->id)->exists(); if ($used) { return $this->fail([ResponseCode::ERROR_BUSINESS, '负责人已被使用,无法删除']); } $model->delete(); return $this->success('删除成功'); } protected function getSystems($year) { return $this->systemModel ->where('year', $year) ->orderBy('sort') ->orderBy('id') ->get(); } protected function getCourses($year) { return $this->courseModel ->with(['system', 'courseType']) ->whereHas('system', function ($query) use ($year) { $query->where('year', $year); }) ->orderBy('sort') ->orderBy('id') ->get(); } protected function getSchedules($year) { return $this->scheduleModel ->with(['system', 'course']) ->where('year', $year) ->orderBy('month') ->orderBy('id') ->get(); } protected function getSchedulesFlatFromGroups($year) { $groups = $this->scheduleGroupModel ->with(['system', 'course', 'modules.location', 'modules.owner']) ->where('year', $year) ->orderBy('id') ->get(); $flat = []; foreach ($groups as $group) { foreach ($group->modules as $module) { $flat[] = [ 'id' => $module->id, 'group_id' => $group->id, 'year' => $group->year, 'system_id' => $group->system_id, 'course_id' => $group->course_id, 'month' => $module->month, 'module_name' => $module->name, 'location_id' => $module->location_id, 'owner_id' => $module->owner_id, 'title' => $group->title, 'owner' => optional($module->owner)->name ?? '', 'location' => optional($module->location)->name ?? '', 'count_text' => $module->count_text ?? '', 'system' => $group->system, 'course' => $group->course, ]; } } usort($flat, function ($a, $b) { $yearDiff = strcmp((string) ($a['year'] ?? ''), (string) ($b['year'] ?? '')); if ($yearDiff !== 0) { return $yearDiff; } $monthDiff = (int) ($a['month'] ?? 0) - (int) ($b['month'] ?? 0); if ($monthDiff !== 0) { return $monthDiff; } return (int) ($a['id'] ?? 0) - (int) ($b['id'] ?? 0); }); return $flat; } }