startDate = $this->option('start'); $this->endDate = $this->option('end'); $configId = $this->option('config-id'); if ($configId) { $config = CourseTypeDataOverviewConfig::where('status', true)->find($configId); if (!$config) { $this->error("未找到 status=1 的 config id={$configId}"); return 1; } $this->startDate = $config->start_date; $this->endDate = $config->end_date ?: date('Y-m-d', strtotime('+10 year')); $this->info("使用 config#{$configId} 的日期: {$this->startDate} ~ {$this->endDate}"); } else { $this->info("使用参数日期: {$this->startDate} ~ {$this->endDate}"); } // 1) home-v2 逻辑:按 yearConfigs 的统计方式(只取 is_chart=1 & is_history=0 的 CourseType + 其他) $homeV2 = $this->computeHomeV2($this->startDate, $this->endDate); // 2) courses-home 逻辑:courseTypesSum 的课程数量、以及涉及的 Course / HistoryCourse $coursesHome = $this->computeCoursesHome($this->startDate, $this->endDate); $this->printComparison($homeV2, $coursesHome); $this->printDetailDiff($homeV2, $coursesHome); return 0; } /** * home-v2 的 yearConfigs 统计(单一段 start~end) * 返回:期数、去重人数、涉及的 course_ids、history_course_ids */ protected function computeHomeV2(string $start, string $end): array { $courseIds = []; $historyIds = []; $periodsTotal = 0; $signsUniqueSum = 0; $allCourseTypes = CourseType::where('is_chart', 1)->where('is_history', 0)->orderBy('sort')->get(); foreach ($allCourseTypes as $ct) { // 历史:typeDetail name like,且仅 type 为 is_history=0(is_history=1 的由下方与 courses-home 口径一致的块统计) $history = HistoryCourse::whereHas('typeDetail', function ($q) use ($ct) { $q->where('name', 'like', '%' . $ct->name . '%')->where('is_history', 0); })->where(function ($q) use ($start, $end) { $q->whereBetween('start_time', [$start, $end]) ->orWhereBetween('end_time', [$start, $end]); })->get(); foreach ($history as $h) { $historyIds[] = $h->id; } $historyPeriods = $history->count(); // 现在:Course type=$ct->id, is_chart=1 $courses = Course::where('type', $ct->id)->where('is_chart', 1) ->where(function ($q) use ($start, $end) { $q->whereBetween('start_date', [$start, $end]) ->orWhereBetween('end_date', [$start, $end]); })->get(); foreach ($courses as $c) { $courseIds[] = $c->id; } $nowPeriods = $courses->count(); $periodsTotal += $historyPeriods + $nowPeriods; $signsUniqueSum += $history->sum('course_type_signs_pass_unique') + CourseSign::courseSignsTotalByUnique($start, $end, 1, $courses->pluck('id'), false, false); } // 其他 is_chart=0 $other = CourseType::getOtherStatistics($start, $end); $oIds = CourseType::getOtherCourseIds($start, $end); $otherH = HistoryCourse::whereIn('type', CourseType::where('is_chart', 0)->where('is_history', 0)->pluck('id')) ->where(function ($q) use ($start, $end) { $q->whereBetween('start_time', [$start, $end])->orWhereBetween('end_time', [$start, $end]); })->get(); foreach ($otherH as $h) { $historyIds[] = $h->id; } foreach (Course::whereIn('id', $oIds)->get() as $c) { $courseIds[] = $c->id; } $periodsTotal += $other->course_periods_total; $signsUniqueSum += $other->course_signs_total; // getOtherStatistics 内部已含 history,此处仅补 course ids(getOtherCourseIds 与 getOtherStatistics 的 Course 条件一致) // 与 courses-home 口径一致:附加 is_history=1 的 HistoryCourse(whereHas calendar is_count_people=1,type=体系 id) $courseTypesHistory = CourseType::where('is_history', 1)->get(); foreach ($courseTypesHistory as $hc) { $historyList = 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) ->get(); foreach ($historyList as $h) { $historyIds[] = $h->id; } $periodsTotal += $historyList->count(); $signsUniqueSum += $historyList->sum('course_type_signs_pass_unique'); } return [ 'periods_total' => $periodsTotal, 'signs_unique_sum' => $signsUniqueSum, 'course_ids' => array_values(array_unique($courseIds)), 'history_ids' => array_values(array_unique($historyIds)), ]; } /** * courses-home 的 courseTypesSum:按行展开的课程数量、去重(按类型聚合的那列会重复,这里只算 课程数量/期数) * 返回:课程数量(行数)、涉及的 course_ids、history_course_ids */ protected function computeCoursesHome(string $start, string $end): array { $courseIds = []; $historyIds = []; $courseTypes = CourseType::whereIn('id', CourseType::pluck('id')->toArray())->get(); // 第一段:非历史 CourseType 下的 Course foreach ($courseTypes as $ct) { $courses2 = Course::where('type', $ct->id) ->where(function ($q) use ($start, $end) { if ($start && $end) { $q->whereBetween('start_date', [$start, $end]) ->orWhereBetween('end_date', [$start, $end]); } }) ->where('is_chart', 1) ->orderBy('start_date') ->get(); foreach ($courses2 as $c) { $courseIds[] = $c->id; } } // 第二段:is_history=1 的 HistoryCourse(与 courses-home 一致:whereHas calendar is_count_people=1) $courseTypesHistory = CourseType::where('is_history', 1)->whereIn('id', CourseType::pluck('id')->toArray())->get(); foreach ($courseTypesHistory as $hc) { $courses3 = HistoryCourse::whereHas('calendar', function ($q) { $q->where('is_count_people', 1); }) ->where(function ($q) use ($start, $end) { $q->whereBetween('start_time', [$start, $end]) ->orWhereBetween('end_time', [$start, $end]); }) ->where('type', $hc->id) ->get(); foreach ($courses3 as $h) { $historyIds[] = $h->id; } } // 期数 = courseTypesSum 行数:每个 Course 一行,每个 HistoryCourse 一行 $periodsTotal = count($courseIds) + count($historyIds); // 去重人数:用与 home-v2 同口径的「所有在 courseTypesSum 里的 course_id + history 对应类型」来算较复杂, // 这里仅用 courses-home 实际用到的 Course 的 id 做 courseSignsTotalByUnique,历史用 sum(course_type_signs_pass_unique) $allCids = array_values(array_unique($courseIds)); $signsFromCourse = $allCids ? (int) CourseSign::courseSignsTotalByUnique($start, $end, 1, $allCids, false, false) : 0; $signsFromHistory = 0; foreach ($courseTypesHistory as $hc) { $list = 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) ->get(); $signsFromHistory += $list->sum('course_type_signs_pass_unique'); } $signsUniqueSum = $signsFromCourse + $signsFromHistory; return [ 'periods_total' => $periodsTotal, 'signs_unique_sum' => $signsUniqueSum, 'course_ids' => array_values(array_unique($courseIds)), 'history_ids' => array_values(array_unique($historyIds)), ]; } protected function printComparison(array $homeV2, array $coursesHome): void { $this->line(''); $this->line('========== 汇总对比 =========='); $this->table( ['指标', 'home-v2 (yearConfigs)', 'courses-home (courseTypesSum)', '差异 (courses-home - home-v2)'], [ [ '期数/课程数量', $homeV2['periods_total'], $coursesHome['periods_total'], $coursesHome['periods_total'] - $homeV2['periods_total'], ], [ '去重培养人数', $homeV2['signs_unique_sum'], $coursesHome['signs_unique_sum'], $coursesHome['signs_unique_sum'] - $homeV2['signs_unique_sum'], ], [ 'Course 数', count($homeV2['course_ids']), count($coursesHome['course_ids']), count($coursesHome['course_ids']) - count($homeV2['course_ids']), ], [ 'HistoryCourse 数', count($homeV2['history_ids']), count($coursesHome['history_ids']), count($coursesHome['history_ids']) - count($homeV2['history_ids']), ], ] ); } protected function printDetailDiff(array $homeV2, array $coursesHome): void { $this->line(''); $this->line('========== 明细差异(导致 courses-home 多出 期数 的条目) =========='); $h2C = array_flip($homeV2['course_ids']); $chC = array_flip($coursesHome['course_ids']); $onlyInChC = array_keys(array_diff_key($chC, $h2C)); $onlyInH2C = array_keys(array_diff_key($h2C, $chC)); $h2H = array_flip($homeV2['history_ids']); $chH = array_flip($coursesHome['history_ids']); $onlyInChH = array_keys(array_diff_key($chH, $h2H)); $onlyInH2H = array_keys(array_diff_key($h2H, $chH)); if (!empty($onlyInChC)) { $this->line('【仅 courses-home 有的 Course(会多算期数)】'); $rows = Course::whereIn('id', $onlyInChC)->get(['id', 'name', 'type', 'start_date', 'end_date', 'is_chart']); $this->table( ['id', 'name', 'type', 'start_date', 'end_date', 'is_chart'], $rows->map(fn($r) => [$r->id, $r->name, $r->type, $r->start_date, $r->end_date, $r->is_chart])->toArray() ); $ctIds = $rows->pluck('type')->unique(); $cts = CourseType::whereIn('id', $ctIds)->get(['id', 'name', 'is_chart', 'is_history']); $this->line('对应 CourseType: ' . $cts->map(fn($t) => "id={$t->id} name={$t->name} is_chart={$t->is_chart} is_history={$t->is_history}")->implode('; ')); } if (!empty($onlyInChH)) { $this->line('【仅 courses-home 有的 HistoryCourse(会多算期数)】'); $rows = HistoryCourse::whereIn('id', $onlyInChH)->get(['id', 'course_name', 'type', 'start_time', 'end_time', 'calendar_id']); $this->table( ['id', 'course_name', 'type', 'start_time', 'end_time', 'calendar_id'], $rows->map(fn($r) => [$r->id, $r->course_name, $r->type, $r->start_time, $r->end_time, $r->calendar_id])->toArray() ); $ctIds = $rows->pluck('type')->unique(); $cts = CourseType::whereIn('id', $ctIds)->get(['id', 'name', 'is_chart', 'is_history']); $this->line('对应 CourseType: ' . $cts->map(fn($t) => "id={$t->id} name={$t->name} is_chart={$t->is_chart} is_history={$t->is_history}")->implode('; ')); } if (!empty($onlyInH2C)) { $this->line('【仅 home-v2 有的 Course】'); $this->line(implode(', ', $onlyInH2C)); } if (!empty($onlyInH2H)) { $this->line('【仅 home-v2 有的 HistoryCourse】'); $this->line(implode(', ', $onlyInH2H)); } if (empty($onlyInChC) && empty($onlyInChH) && empty($onlyInH2C) && empty($onlyInH2H)) { $this->line('(无 ID 集合差异;若期数仍不一致,可能是同一条目在两个口径下 算/不算 的规则不同,例如 is_chart、is_count_people、日期区间、type 与 name 匹配等)'); } } }