You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

234 lines
12 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Console\Commands;
use App\Models\Course;
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 时间段内,找出课程数量、去重培养人数差异的原因
*/
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();
}
$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-012027-01-01 做筛选,与 config 自身范围不一致时以 2024-2027 为准则需单独算)
// 为与 courses-home 可比,我们在这里用 startend 作为统一日期范围
$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 的 Coursehome-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 课程类型的 Coursehome-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}");
}
}
}
}