From b21c5d355be213f4b25e9acdd9c0b1dd7582301e Mon Sep 17 00:00:00 2001 From: cody <648753004@qq.com> Date: Mon, 19 Jan 2026 18:28:49 +0800 Subject: [PATCH] update --- .../Commands/DiffHomeV2CoursesHome.php | 233 ++++++++++++++++++ .../Controllers/Admin/OtherController.php | 26 +- 2 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 app/Console/Commands/DiffHomeV2CoursesHome.php diff --git a/app/Console/Commands/DiffHomeV2CoursesHome.php b/app/Console/Commands/DiffHomeV2CoursesHome.php new file mode 100644 index 0000000..ca51ff7 --- /dev/null +++ b/app/Console/Commands/DiffHomeV2CoursesHome.php @@ -0,0 +1,233 @@ +option('start'); + $end = $this->option('end'); + $this->info("=== 对比时间段: {$start} ~ {$end} ===\n"); + + // 1. 找到覆盖该时间段的 yearConfig(或使用该时间段模拟) + $config = CourseTypeDataOverviewConfig::where('status', true) + ->where('start_date', '<=', $start) + ->where(function ($q) use ($end) { + $q->where('end_date', '>=', $end)->orWhereNull('end_date'); + }) + ->orderBy('sort') + ->first(); + + if (!$config) { + $config = CourseTypeDataOverviewConfig::where('status', true) + ->whereBetween('start_date', [$start, $end]) + ->orWhereBetween('end_date', [$start, $end]) + ->orderBy('sort') + ->first(); + } + + $configStart = $config ? $config->start_date : $start; + $configEnd = $config && $config->end_date ? $config->end_date : $end; + $this->info("yearConfig: " . ($config ? "id={$config->id} [{$configStart} ~ {$configEnd}]" : "无覆盖配置,使用 {$configStart} ~ {$configEnd}")); + + // 2. 按 home-v2 逻辑统计(仅用 2024-01-01~2027-01-01 做筛选,与 config 自身范围不一致时以 2024-2027 为准则需单独算) + // 为与 courses-home 可比,我们在这里用 start~end 作为统一日期范围 + $homeV2 = $this->computeHomeV2Style($start, $end); + $this->info("\n【home-v2 逻辑】期数: {$homeV2['course_periods_total']}, 去重培养人数: {$homeV2['course_signs_unique_total']}"); + + // 3. 按 courses-home 逻辑统计 + $coursesHome = $this->computeCoursesHomeStyle($start, $end); + $this->info("【courses-home 逻辑】课程数量(行数): {$coursesHome['course_count']}, 去重培养人数: {$coursesHome['course_signs_unique_total']}"); + + // 4. 差异 + $diffCount = $coursesHome['course_count'] - $homeV2['course_periods_total']; + $this->info("\n--- 差异: 课程数量 courses-home 多 " . $diffCount . " ---"); + + // 5. 定位多出来的课程来源 + $this->findExtraSources($start, $end, $homeV2, $coursesHome); + + return 0; + } + + /** + * home-v2 风格:is_chart=1 且 is_history=0 的 CourseType + 按 name like 的 HistoryCourse + 其他 + */ + protected function computeHomeV2Style(string $start, string $end): array + { + $allCourseTypes = CourseType::where('is_chart', 1)->where('is_history', 0)->orderBy('sort')->get(); + $coursePeriodsTotal = 0; + $courseSignsUniqueTotal = 0; + + $dateFilter = function ($q) use ($start, $end) { + $q->whereBetween('start_date', [$start, $end])->orWhereBetween('end_date', [$start, $end]); + }; + $historyDateFilter = function ($q) use ($start, $end) { + $q->whereBetween('start_time', [$start, $end])->orWhereBetween('end_time', [$start, $end]); + }; + + foreach ($allCourseTypes as $ct) { + $historyCourse = HistoryCourse::whereHas('typeDetail', fn($q) => $q->where('name', 'like', '%' . $ct->name . '%')) + ->where($historyDateFilter)->get(); + $courses = Course::where('type', $ct->id)->where('is_chart', 1)->where($dateFilter)->get(); + + $coursePeriodsTotal += $historyCourse->count() + $courses->count(); + $courseSignsUniqueTotal += $historyCourse->sum('course_type_signs_pass_unique') + + (int)CourseSign::courseSignsTotalByUnique($start, $end, 1, $courses->pluck('id'), false, false); + } + + $other = CourseType::getOtherStatistics($start, $end); + $coursePeriodsTotal += $other->course_periods_total; + $courseSignsUniqueTotal += $other->course_signs_total; + + return ['course_periods_total' => $coursePeriodsTotal, 'course_signs_unique_total' => $courseSignsUniqueTotal]; + } + + /** + * courses-home 风格:全部 CourseType(含 is_history=1)的 Course(is_chart=1) + is_history=1 的 HistoryCourse(calendar.is_count_people=1) + */ + protected function computeCoursesHomeStyle(string $start, string $end): array + { + $course_type_id = CourseType::pluck('id')->toArray(); + $courseTypesSum = []; + $allCourseIdsForUnique = []; + + $dateFilter = function ($q) use ($start, $end) { + if ($start && $end) { + $q->whereBetween('start_date', [$start, $end])->orWhereBetween('end_date', [$start, $end]); + } + }; + + // 第一循环:所有 CourseType(含 is_history=1) + $courseTypes = CourseType::whereIn('id', $course_type_id)->get(); + foreach ($courseTypes as $ct) { + $courses2 = Course::where('type', $ct->id)->where($dateFilter)->where('is_chart', 1)->orderBy('start_date')->get(); + foreach ($courses2 as $c) { + $courseTypesSum[] = ['source' => 'Course', 'course_type_id' => $ct->id, 'course_type_name' => $ct->name, 'is_history' => $ct->is_history ?? 0, 'course_id' => $c->id, 'course_name' => $c->name]; + $allCourseIdsForUnique[] = $c->id; + } + } + + // 第二循环:is_history=1 的 HistoryCourse(与 home-v2 一致:仅 typeDetail.name 能匹配某个 is_chart=1 且 is_history=0 的 name) + $chartHistoryTypeNames = CourseType::where('is_chart', 1)->where('is_history', 0)->pluck('name')->toArray(); + $courseTypesHistory = CourseType::where('is_history', 1)->whereIn('id', $course_type_id)->get(); + $historySignsTotal = 0; + foreach ($courseTypesHistory as $hc) { + $historyQ = HistoryCourse::whereHas('calendar', fn($q) => $q->where('is_count_people', 1)) + ->where(fn($q) => $q->whereBetween('start_time', [$start, $end])->orWhereBetween('end_time', [$start, $end])) + ->where('type', $hc->id); + if (!empty($chartHistoryTypeNames)) { + $historyQ->whereHas('typeDetail', function ($query) use ($chartHistoryTypeNames) { + $query->where(function ($q2) use ($chartHistoryTypeNames) { + foreach ($chartHistoryTypeNames as $n) { + $q2->orWhere('name', 'like', '%' . $n . '%'); + } + }); + }); + } else { + $historyQ->whereHas('typeDetail', fn($query) => $query->whereRaw('1=0')); + } + $courses3 = $historyQ->get(); + foreach ($courses3 as $c) { + $courseTypesSum[] = ['source' => 'HistoryCourse', 'course_type_id' => $hc->id, 'course_type_name' => $hc->name, 'is_history' => 1, 'history_course_id' => $c->id, 'course_name' => $c->course_name]; + $historySignsTotal += (int)($c->course_type_signs_pass_unique ?? 0); + } + } + + $courseSignsUniqueTotal = (int)CourseSign::courseSignsTotalByUnique($start, $end, 1, $allCourseIdsForUnique ?: [], false, false) + $historySignsTotal; + + return [ + 'course_count' => count($courseTypesSum), + 'course_signs_unique_total' => $courseSignsUniqueTotal, + 'rows' => $courseTypesSum, + ]; + } + + /** + * 找出 courses-home 多出来的课程来源 + */ + protected function findExtraSources(string $start, string $end, array $homeV2, array $coursesHome): void + { + $allCourseTypes = CourseType::where('is_chart', 1)->where('is_history', 0)->pluck('id')->toArray(); + $historyDateFilter = function ($q) use ($start, $end) { + $q->whereBetween('start_time', [$start, $end])->orWhereBetween('end_time', [$start, $end]); + }; + $dateFilter = function ($q) use ($start, $end) { + $q->whereBetween('start_date', [$start, $end])->orWhereBetween('end_date', [$start, $end]); + }; + + // home-v2 会计入的:1) Course: type in is_history=0, is_chart=1, 日期 2) HistoryCourse: typeDetail.name like (is_history=0 的 name), 日期 3) 其他 + $homeV2CourseIds = []; + foreach (CourseType::where('is_chart', 1)->where('is_history', 0)->get() as $ct) { + $ids = Course::where('type', $ct->id)->where('is_chart', 1)->where($dateFilter)->pluck('id')->toArray(); + $homeV2CourseIds = array_merge($homeV2CourseIds, $ids); + } + $otherCourseIds = Course::whereIn('type', CourseType::where('is_chart', 0)->where('is_history', 0)->pluck('id')) + ->where('is_chart', 1)->where($dateFilter)->pluck('id')->toArray(); + $homeV2CourseIds = array_unique(array_merge($homeV2CourseIds, $otherCourseIds)); + + $homeV2HistoryIds = []; + foreach (CourseType::where('is_chart', 1)->where('is_history', 0)->get() as $ct) { + $ids = HistoryCourse::whereHas('typeDetail', fn($q) => $q->where('name', 'like', '%' . $ct->name . '%')) + ->where($historyDateFilter)->pluck('id')->toArray(); + $homeV2HistoryIds = array_merge($homeV2HistoryIds, $ids); + } + $otherHistory = HistoryCourse::whereIn('type', CourseType::where('is_chart', 0)->where('is_history', 0)->pluck('id')) + ->where($historyDateFilter)->pluck('id')->toArray(); + $homeV2HistoryIds = array_unique(array_merge($homeV2HistoryIds, $otherHistory)); + + // courses-home 多出的:在 rows 里但不在 home-v2 的 + $extras = []; + foreach ($coursesHome['rows'] as $r) { + if ($r['source'] === 'Course') { + if (!in_array($r['course_id'], $homeV2CourseIds)) { + $extras[] = $r; + } + } else { + if (!in_array($r['history_course_id'], $homeV2HistoryIds)) { + $extras[] = $r; + } + } + } + + $this->info("\n【多出的课程/期(courses-home 有、home-v2 无)】共 " . count($extras) . " 条:"); + foreach ($extras as $e) { + $this->line(sprintf( + " - %s | type_id=%s is_history=%s | %s | %s", + $e['source'], + $e['course_type_id'], + $e['is_history'], + $e['course_name'] ?? '-', + isset($e['course_id']) ? "course_id={$e['course_id']}" : "history_id={$e['history_course_id']}" + )); + } + + // 单独列出:type 属于 is_history=1 的 Course(home-v2 不统计此类) + $isHistory1TypeIds = CourseType::where('is_history', 1)->pluck('id')->toArray(); + $courseFromHistory1Type = Course::whereIn('type', $isHistory1TypeIds) + ->where('is_chart', 1) + ->where($dateFilter) + ->get(); + if ($courseFromHistory1Type->isNotEmpty()) { + $this->info("\n【属于 is_history=1 课程类型的 Course(home-v2 不统计)】共 " . $courseFromHistory1Type->count() . " 条:"); + foreach ($courseFromHistory1Type as $c) { + $ct = CourseType::find($c->type); + $this->line(" - course_id={$c->id} type={$c->type} ({$ct->name}) | {$c->name} | {$c->start_date}~{$c->end_date}"); + } + } + } +} diff --git a/app/Http/Controllers/Admin/OtherController.php b/app/Http/Controllers/Admin/OtherController.php index 95a7cc7..8dba8d6 100755 --- a/app/Http/Controllers/Admin/OtherController.php +++ b/app/Http/Controllers/Admin/OtherController.php @@ -377,11 +377,22 @@ class OtherController extends CommonController ]; } } - // 附加历史课程数据 + // 附加历史课程数据(与 home-v2 yearConfigs 口径一致:仅统计 typeDetail.name 能匹配某个 is_chart=1 且 is_history=0 的 CourseType 的 HistoryCourse) + $chartHistoryTypeNames = CourseType::where('is_chart', 1)->where('is_history', 0)->pluck('name')->toArray(); $courseTypesHistory = CourseType::where('is_history', 1)->whereIn('id', $course_type_id)->get(); foreach ($courseTypesHistory as $historyCourse) { $courses3 = HistoryCourse::whereHas('calendar', function ($query) { $query->where('is_count_people', 1); + })->whereHas('typeDetail', function ($query) use ($chartHistoryTypeNames) { + if (empty($chartHistoryTypeNames)) { + $query->whereRaw('1=0'); + return; + } + $query->where(function ($q) use ($chartHistoryTypeNames) { + foreach ($chartHistoryTypeNames as $n) { + $q->orWhere('name', 'like', '%' . $n . '%'); + } + }); })->where(function ($query) use ($start_date, $end_date) { // 开始结束日期的筛选。or查询 $query->whereBetween('start_time', [$start_date, $end_date]) @@ -863,11 +874,22 @@ class OtherController extends CommonController ]; } } - // 附加历史课程数据 + // 附加历史课程数据(与 home-v2 yearConfigs 口径一致:仅统计 typeDetail.name 能匹配某个 is_chart=1 且 is_history=0 的 CourseType 的 HistoryCourse) + $chartHistoryTypeNames = CourseType::where('is_chart', 1)->where('is_history', 0)->pluck('name')->toArray(); $courseTypesHistory = CourseType::where('is_history', 1)->whereIn('id', $course_type_id)->get(); foreach ($courseTypesHistory as $historyCourse) { $courses3 = HistoryCourse::whereHas('calendar', function ($query) { $query->where('is_count_people', 1); + })->whereHas('typeDetail', function ($query) use ($chartHistoryTypeNames) { + if (empty($chartHistoryTypeNames)) { + $query->whereRaw('1=0'); + return; + } + $query->where(function ($q) use ($chartHistoryTypeNames) { + foreach ($chartHistoryTypeNames as $n) { + $q->orWhere('name', 'like', '%' . $n . '%'); + } + }); })->where(function ($query) use ($start_date, $end_date) { // 开始结束日期的筛选。or查询 $query->whereBetween('start_time', [$start_date, $end_date])