diff --git a/app/Console/Commands/DiffHomeV2CoursesHome.php b/app/Console/Commands/DiffHomeV2CoursesHome.php index ca51ff7..ff002f7 100644 --- a/app/Console/Commands/DiffHomeV2CoursesHome.php +++ b/app/Console/Commands/DiffHomeV2CoursesHome.php @@ -3,231 +3,294 @@ namespace App\Console\Commands; use App\Models\Course; +use App\Models\CourseSign; use App\Models\CourseType; use App\Models\CourseTypeDataOverviewConfig; use App\Models\HistoryCourse; -use App\Models\CourseSign; use Illuminate\Console\Command; /** - * 对比 home-v2 的 yearConfigs 与 courses-home 的 courseTypesSum - * 在 2024-01-01 ~ 2027-01-01 时间段内,找出课程数量、去重培养人数差异的原因 + * 比对 home-v2 的 yearConfigs 与 courses-home 的 courseTypesSum, + * 在指定时间段内 期数、课程数量、去重培养人数 的差异,并输出导致差异的 Course / HistoryCourse 明细。 */ class DiffHomeV2CoursesHome extends Command { - protected $signature = 'diff:home-v2-courses-home {--start=2024-01-01} {--end=2027-01-01}'; - protected $description = '对比 home-v2 yearConfigs 与 courses-home courseTypesSum 在指定时间段内的期数、去重培养人数差异'; - - public function handle() - { - $start = $this->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(); - } + protected $signature = 'diff:home-v2-courses-home + {--start=2024-01-01 : 开始日期} + {--end=2027-01-01 : 结束日期} + {--config-id= : 可选,仅用该 yearConfig 的日期;不传则用 start/end}'; - $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}")); + protected $description = '比对 home-v2(yearConfigs) 与 courses-home(courseTypesSum) 的期数、课程数量、去重培养人数差异'; - // 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']}"); + protected $startDate; + protected $endDate; - // 3. 按 courses-home 逻辑统计 - $coursesHome = $this->computeCoursesHomeStyle($start, $end); - $this->info("【courses-home 逻辑】课程数量(行数): {$coursesHome['course_count']}, 去重培养人数: {$coursesHome['course_signs_unique_total']}"); + public function handle() + { + $this->startDate = $this->option('start'); + $this->endDate = $this->option('end'); + $configId = $this->option('config-id'); - // 4. 差异 - $diffCount = $coursesHome['course_count'] - $homeV2['course_periods_total']; - $this->info("\n--- 差异: 课程数量 courses-home 多 " . $diffCount . " ---"); + 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}"); + } - // 5. 定位多出来的课程来源 - $this->findExtraSources($start, $end, $homeV2, $coursesHome); + // 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 风格:is_chart=1 且 is_history=0 的 CourseType + 按 name like 的 HistoryCourse + 其他 + * home-v2 的 yearConfigs 统计(单一段 start~end) + * 返回:期数、去重人数、涉及的 course_ids、history_course_ids */ - protected function computeHomeV2Style(string $start, string $end): array + protected function computeHomeV2(string $start, string $end): array { - $allCourseTypes = CourseType::where('is_chart', 1)->where('is_history', 0)->orderBy('sort')->get(); - $coursePeriodsTotal = 0; - $courseSignsUniqueTotal = 0; + $courseIds = []; + $historyIds = []; + $periodsTotal = 0; + $signsUniqueSum = 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]); - }; + $allCourseTypes = CourseType::where('is_chart', 1)->where('is_history', 0)->orderBy('sort')->get(); 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(); + // 历史: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(); - $coursePeriodsTotal += $historyCourse->count() + $courses->count(); - $courseSignsUniqueTotal += $historyCourse->sum('course_type_signs_pass_unique') - + (int)CourseSign::courseSignsTotalByUnique($start, $end, 1, $courses->pluck('id'), false, false); + $periodsTotal += $historyPeriods + $nowPeriods; + $signsUniqueSum += $history->sum('course_type_signs_pass_unique') + + 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; + // 其他 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 条件一致) - return ['course_periods_total' => $coursePeriodsTotal, 'course_signs_unique_total' => $courseSignsUniqueTotal]; + // 与 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 风格:全部 CourseType(含 is_history=1)的 Course(is_chart=1) + is_history=1 的 HistoryCourse(calendar.is_count_people=1) + * courses-home 的 courseTypesSum:按行展开的课程数量、去重(按类型聚合的那列会重复,这里只算 课程数量/期数) + * 返回:课程数量(行数)、涉及的 course_ids、history_course_ids */ - protected function computeCoursesHomeStyle(string $start, string $end): array + protected function computeCoursesHome(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]); - } - }; + $courseIds = []; + $historyIds = []; + $courseTypes = CourseType::whereIn('id', CourseType::pluck('id')->toArray())->get(); - // 第一循环:所有 CourseType(含 is_history=1) - $courseTypes = CourseType::whereIn('id', $course_type_id)->get(); + // 第一段:非历史 CourseType 下的 Course foreach ($courseTypes as $ct) { - $courses2 = Course::where('type', $ct->id)->where($dateFilter)->where('is_chart', 1)->orderBy('start_date')->get(); + $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) { - $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; + $courseIds[] = $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; + // 第二段: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) { - $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); + $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; } } - $courseSignsUniqueTotal = (int)CourseSign::courseSignsTotalByUnique($start, $end, 1, $allCourseIdsForUnique ?: [], false, false) + $historySignsTotal; + // 期数 = 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 [ - 'course_count' => count($courseTypesSum), - 'course_signs_unique_total' => $courseSignsUniqueTotal, - 'rows' => $courseTypesSum, + 'periods_total' => $periodsTotal, + 'signs_unique_sum' => $signsUniqueSum, + 'course_ids' => array_values(array_unique($courseIds)), + 'history_ids' => array_values(array_unique($historyIds)), ]; } - /** - * 找出 courses-home 多出来的课程来源 - */ - protected function findExtraSources(string $start, string $end, array $homeV2, array $coursesHome): void + protected function printComparison(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); + $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('; ')); } - $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; - } - } + + 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('; ')); } - $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']}" - )); + if (!empty($onlyInH2C)) { + $this->line('【仅 home-v2 有的 Course】'); + $this->line(implode(', ', $onlyInH2C)); + } + if (!empty($onlyInH2H)) { + $this->line('【仅 home-v2 有的 HistoryCourse】'); + $this->line(implode(', ', $onlyInH2H)); } - // 单独列出: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}"); - } + if (empty($onlyInChC) && empty($onlyInChH) && empty($onlyInH2C) && empty($onlyInH2H)) { + $this->line('(无 ID 集合差异;若期数仍不一致,可能是同一条目在两个口径下 算/不算 的规则不同,例如 is_chart、is_count_people、日期区间、type 与 name 匹配等)'); } } } diff --git a/app/Console/Commands/DiffStudyCoverRencai.php b/app/Console/Commands/DiffStudyCoverRencai.php new file mode 100644 index 0000000..b1038a7 --- /dev/null +++ b/app/Console/Commands/DiffStudyCoverRencai.php @@ -0,0 +1,78 @@ +option('start'); + $end = $this->option('end'); + + $course_type_id = CourseType::pluck('id')->toArray(); + $courses = Course::whereIn('type', $course_type_id)->get(); + $course_ids = $courses->pluck('id'); + + $rencaiCount = CourseSign::rencai($start, $end, $course_ids); + + // 复现 study 的 User 数量:address=苏州, is_rencai=1, courses_start/end, is_chart=1, status=1 + // is_rencai 的「人才培训」已与 rencai 一致:仅在 2025、is_chart=1 的报名中认定 + $studyCount = User::query() + ->whereHas('courseSigns', function ($query) use ($start, $end) { + $query->where('status', 1)->whereHas('course', function ($q) use ($start, $end) { + $q->where('is_chart', 1)->where(function ($q2) use ($start, $end) { + $q2->whereBetween('start_date', [$start, $end]) + ->orWhereBetween('end_date', [$start, $end]); + }); + }); + }) + ->whereHas('company', function ($c) { + $c->where('company_address', 'like', '%苏州%') + ->orWhere('company_city', 'like', '%苏州%'); + }) + ->where(function ($q) use ($start, $end) { + $q->whereHas('courseSigns', function ($cs) use ($start, $end) { + $cs->where('status', 1)->whereHas('course', function ($c) use ($start, $end) { + $c->where('is_chart', 1) + ->where(function ($q2) use ($start, $end) { + $q2->whereBetween('start_date', [$start, $end]) + ->orWhereBetween('end_date', [$start, $end]); + }) + ->whereHas('typeDetail', function ($t) { + $t->where('name', '人才培训'); + }); + }); + })->orWhere('type', 'like', '%人才%')->orWhere('talent_tags', 'like', '%人才%'); + }) + ->count(); + + $diff = $studyCount - $rencaiCount; + $this->table( + ['指标', 'study (User 数)', 'cover_rencai_total (rencai)', '差异 study - rencai'], + [['苏州人才', $studyCount, $rencaiCount, $diff]] + ); + + if ($diff !== 0) { + $this->warn("两者相差 {$diff},请检查 study 的 is_rencai 与 CourseSign::rencai 的细微口径。"); + return 1; + } + $this->info('study 与 cover_rencai_total 一致。'); + return 0; + } +} diff --git a/app/Http/Controllers/Admin/OtherController.php b/app/Http/Controllers/Admin/OtherController.php index 2c7e97e..907b990 100755 --- a/app/Http/Controllers/Admin/OtherController.php +++ b/app/Http/Controllers/Admin/OtherController.php @@ -222,11 +222,14 @@ class OtherController extends CommonController return clone $item; }); + // 汇总本配置下全部 Course id,用于 course_signs_unique_total 与 courses-home 口径一致(courseSignsTotalByUnique) + $configCourseIds = collect(); + // 对每个 CourseType 进行统计 foreach ($courseTypes as $courseType) { - // 历史课程数据(添加时间范围限制) + // 历史课程数据(添加时间范围限制;仅统计 type 为 is_history=0 的,is_history=1 的由下方「与 courses-home 口径一致」块统计,避免重复) $historyCourse = HistoryCourse::whereHas('typeDetail', function ($query) use ($courseType) { - $query->where('name', 'like', '%' . $courseType->name . '%'); + $query->where('name', 'like', '%' . $courseType->name . '%')->where('is_history', 0); })->where(function ($query) use ($configStartDate, $configEndDate) { $query->whereBetween('start_time', [$configStartDate, $configEndDate]) ->orWhereBetween('end_time', [$configStartDate, $configEndDate]); @@ -237,6 +240,7 @@ class OtherController extends CommonController $query->whereBetween('start_date', [$configStartDate, $configEndDate]) ->orWhereBetween('end_date', [$configStartDate, $configEndDate]); })->get(); + $configCourseIds = $configCourseIds->merge($courses->pluck('id')); // 历史课程期数 $courseType->history_course_periods_total = $historyCourse->count(); // 现在课程期数(添加时间范围限制) @@ -253,18 +257,38 @@ class OtherController extends CommonController $courseType->course_signs_total = $courseType->history_course_signs_total + $courseType->now_course_signs_total; } + // 与 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', function ($query) { + $query->where('is_count_people', 1); + })->where(function ($query) use ($configStartDate, $configEndDate) { + $query->whereBetween('start_time', [$configStartDate, $configEndDate]) + ->orWhereBetween('end_time', [$configStartDate, $configEndDate]); + })->where('type', $hc->id)->get(); + $hc->history_course_periods_total = $historyList->count(); + $hc->now_course_periods_total = 0; + $hc->history_course_signs_total = $historyList->sum('course_type_signs_pass_unique'); + $hc->now_course_signs_total = 0; + $hc->course_periods_total = $hc->history_course_periods_total; + $hc->course_signs_total = $hc->history_course_signs_total; + $courseTypes->push(clone $hc); + } + // 统计 is_chart=0 的课程类型数据,组成"其他"统计项 $otherCourseType = CourseType::getOtherStatistics($configStartDate, $configEndDate); // 将"其他"添加到 courseTypes 集合中 $courseTypes->push($otherCourseType); + $configCourseIds = $configCourseIds->merge(CourseType::getOtherCourseIds($configStartDate, $configEndDate)); + // 将统计数据直接组合到配置对象中 $config->courseTypes = $courseTypes; // 总期数(包含"其他") $config->course_periods_total = $courseTypes->sum('course_periods_total'); - // 总去重人数(包含"其他"),与 courseTypes 各项 course_signs_total 之和保持一致 - $config->course_signs_unique_total = $courseTypes->sum('course_signs_total'); + // 总去重人数(与 courses-home 的 course_signs_pass_unique 口径一致:courseSignsTotalByUnique) + $config->course_signs_unique_total = CourseSign::courseSignsTotalByUnique($configStartDate, $configEndDate, 1, $configCourseIds->unique()->values(), false); } return $this->success(compact('list', 'suzhou', 'country', 'monthCourses', 'time_axis', 'article', 'yearConfigs')); @@ -377,7 +401,7 @@ class OtherController extends CommonController ]; } } - // 附加历史课程数据(与 course_signs_pass 口径一致:与 needHistory 的 HistoryCourse 条件一致,calendar+日期+type in course_type_id) + // 附加历史课程数据(与 courses-home 口径一致:统计所有 is_history=1 的 HistoryCourse) $courseTypesHistory = CourseType::where('is_history', 1)->whereIn('id', $course_type_id)->get(); foreach ($courseTypesHistory as $historyCourse) { $courses3 = HistoryCourse::whereHas('calendar', function ($query) { @@ -863,7 +887,7 @@ class OtherController extends CommonController ]; } } - // 附加历史课程数据(与 course_signs_pass 口径一致:与 needHistory 的 HistoryCourse 条件一致,calendar+日期+type in course_type_id) + // 附加历史课程数据 $courseTypesHistory = CourseType::where('is_history', 1)->whereIn('id', $course_type_id)->get(); foreach ($courseTypesHistory as $historyCourse) { $courses3 = HistoryCourse::whereHas('calendar', function ($query) { @@ -2032,7 +2056,6 @@ class OtherController extends CommonController 'company_position' => $user->company_position ?? '', 'education' => $user->education ?? '', 'geren_rongyu' => $user->type ?? '', - 'talent_tags' => $user->talent_tags ?? '', 'course_names' => $courseNamesStr, 'course_types' => $courseTypesStr, 'course_count' => $totalCourseCount, @@ -2047,7 +2070,6 @@ class OtherController extends CommonController 'company_position' => '职位', 'education' => '学历', 'geren_rongyu' => '个人荣誉', - 'talent_tags' => '人才标签', 'course_names' => '课程名称', 'course_types' => '课程体系', 'course_count' => '报名课程数', diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 38fd6ab..1c6cd7e 100755 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -436,11 +436,61 @@ class UserController extends BaseController } }); } - // 是否人才(与 CourseSign::rencai 条件一致):1=是时筛选。任一条即视为人才:报名过人才培训课程 或 type/talent_tags 含「人才」 + // 是否人才(与 CourseSign::rencai 口径一致):1=是时筛选。任一条即视为人才:报名过人才培训课程 或 type/talent_tags 含「人才」。 + // 人才培训:仅在本次查询的课程与时间范围内(courses_start/end、is_chart 等)认定,与 cover_rencai_total 一致。 if (isset($all['is_rencai']) && (int) $all['is_rencai'] === 1) { - $query->where(function ($q) { - $q->whereHas('courseSigns', function ($cs) { - $cs->where('status', 1)->whereHas('course', function ($c) { + $query->where(function ($q) use ($all) { + $q->whereHas('courseSigns', function ($cs) use ($all) { + $cs->where('status', 1); + if (isset($all['course_id'])) { + $cs->where('course_id', $all['course_id']); + } + if (isset($all['sign_start_date']) && isset($all['sign_end_date'])) { + $cs->whereBetween('created_at', [$all['sign_start_date'], $all['sign_end_date']]); + } elseif (isset($all['sign_start_date'])) { + $cs->where('created_at', '>=', $all['sign_start_date']); + } elseif (isset($all['sign_end_date'])) { + $cs->where('created_at', '<=', $all['sign_end_date']); + } + $cs->whereHas('course', function ($c) use ($all) { + if (isset($all['year'])) { + $c->where('year', $all['year']); + } + if (isset($all['is_fee'])) { + $c->where('is_fee', $all['is_fee']); + } + if (isset($all['course_type'])) { + $c->where('type', $all['course_type']); + } + if (isset($all['course_name'])) { + $c->where('name', 'like', '%' . $all['course_name'] . '%'); + } + if (!empty($all['courses_start_date']) && !empty($all['courses_end_date'])) { + $c->where(function ($q2) use ($all) { + $q2->whereBetween('start_date', [$all['courses_start_date'], $all['courses_end_date']]) + ->orWhereBetween('end_date', [$all['courses_start_date'], $all['courses_end_date']]); + }); + } else { + if (!empty($all['courses_start_date'])) { + $c->where('start_date', '>=', $all['courses_start_date']); + } + if (!empty($all['courses_end_date'])) { + $c->where('end_date', '<=', $all['courses_end_date']); + } + } + if (isset($all['courses_ing']) && $all['courses_ing'] == 1) { + $c->where(function ($q2) { + $q2->where('start_date', '<=', date('Y-m-d'))->where('end_date', '>=', date('Y-m-d')); + }); + } + if (isset($all['is_chart'])) { + $c->where('is_chart', $all['is_chart']); + } + if (!empty($all['from']) && strpos((string) $all['from'], '跟班学员') !== false) { + $c->whereHas('typeDetail', function ($t) { + $t->where('is_count_genban', 1); + }); + } $c->whereHas('typeDetail', function ($t) { $t->where('name', '人才培训'); });