diff --git a/app/Console/Commands/DiffHomeV2CoursesHome.php b/app/Console/Commands/DiffHomeV2CoursesHome.php deleted file mode 100644 index ff002f7..0000000 --- a/app/Console/Commands/DiffHomeV2CoursesHome.php +++ /dev/null @@ -1,296 +0,0 @@ -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 匹配等)'); - } - } -} diff --git a/app/Console/Commands/DiffStudyCoverRencai.php b/app/Console/Commands/DiffStudyCoverRencai.php deleted file mode 100644 index b1038a7..0000000 --- a/app/Console/Commands/DiffStudyCoverRencai.php +++ /dev/null @@ -1,78 +0,0 @@ -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/Console/Commands/DiffStudyHomeV2Country.php b/app/Console/Commands/DiffStudyHomeV2Country.php deleted file mode 100644 index d7056ab..0000000 --- a/app/Console/Commands/DiffStudyHomeV2Country.php +++ /dev/null @@ -1,137 +0,0 @@ -option('address'); - - // 1) study:is_schoolmate=1, address=上海, is_chart=1;courses_start/end、status 为空(不限制课程日期与报名状态) - $studyQuery = User::query() - ->where('is_schoolmate', 1) - ->whereHas('company', function ($c) use ($address) { - $c->where('company_address', 'like', '%' . $address . '%') - ->orWhere('company_city', 'like', '%' . $address . '%'); - }) - ->whereHas('courseSigns', function ($cs) { - $cs->whereHas('course', function ($c) { - $c->where('is_chart', 1); - }); - }); - $studyIds = $studyQuery->pluck('id')->toArray(); - - // 2) home-v2 上海市 当前逻辑:address like + is_schoolmate + whereHas courseSigns(course is_chart=1)(与 study 完全一致) - $term = preg_replace('/市$/', '', '上海市') ?: '上海市'; - $homeV2Query = User::query() - ->where('is_schoolmate', 1) - ->whereHas('company', function ($q) use ($term) { - $q->where(function ($q2) use ($term) { - $q2->where('company_address', 'like', '%' . $term . '%') - ->orWhere('company_city', 'like', '%' . $term . '%'); - }); - }) - ->whereHas('courseSigns', function ($cs) { - $cs->whereHas('course', function ($c) { - $c->where('is_chart', 1); - }); - }); - $homeV2Ids = $homeV2Query->pluck('id')->toArray(); - - // 3) 仅用 address 匹配 + is_schoolmate(即与 home-v2 同样的 address 条件,不含 courseSigns) - $addressOnlyQuery = User::query() - ->where('is_schoolmate', 1) - ->whereHas('company', function ($q) use ($address) { - $q->where('company_address', 'like', '%' . $address . '%') - ->orWhere('company_city', 'like', '%' . $address . '%'); - }); - $addressOnlyIds = $addressOnlyQuery->pluck('id')->toArray(); - - $this->line('======== 人数 ========'); - $this->table( - ['口径', '人数'], - [ - ['study (address+is_schoolmate+courseSigns is_chart=1)', count($studyIds)], - ['home-v2 上海市 (address like+is_schoolmate, term=' . $term . ')', count($homeV2Ids)], - ['仅 address+is_schoolmate (不含 courseSigns)', count($addressOnlyIds)], - ] - ); - - $onlyStudy = array_values(array_diff($studyIds, $homeV2Ids)); - $onlyHomeV2 = array_values(array_diff($homeV2Ids, $studyIds)); - - $this->line('study ids 与 home-v2 上海市 ids 是否完全一致: ' . (count($onlyStudy) === 0 && count($onlyHomeV2) === 0 ? '是' : '否')); - - if (!empty($onlyStudy)) { - $this->line(''); - $this->line('【仅 study 有、home-v2 没有】 count=' . count($onlyStudy)); - $rows = User::with('company')->whereIn('id', $onlyStudy)->get(['id', 'name', 'mobile', 'is_schoolmate', 'company_id']); - $out = []; - foreach ($rows as $u) { - $c = $u->company; - $out[] = [ - $u->id, - $u->name, - $u->mobile, - $u->company_id, - $c ? ($c->company_city ?? '') : '', - $c ? ($c->company_address ?? '') : '', - $c ? (strpos((string)$c->company_address, $address) !== false || strpos((string)$c->company_city, $address) !== false ? 'Y' : 'N') : 'N', - ]; - } - $this->table(['user_id', 'name', 'mobile', 'company_id', 'company_city', 'company_address', 'match_' . $address . '?'], $out); - } - - if (!empty($onlyHomeV2)) { - $this->line(''); - $this->line('【仅 home-v2 有、study 没有】 count=' . count($onlyHomeV2)); - $this->line(implode(', ', $onlyHomeV2)); - } - - // 4) 检查 $countryArea 中是否有 上海市,以及 approvedStudents 与 普通 company 的差别 - $this->line(''); - $this->line('======== 补充:$countryArea 与 approvedStudents ========'); - $countryArea = Company::approvedStudents()->groupBy('company_city')->whereNotNull('company_city')->get(['company_city']); - $shCity = $countryArea->firstWhere('company_city', '上海市'); - $this->line('countryArea 中是否存在 company_city=上海市: ' . ($shCity ? '是' : '否')); - $hasShanghai = $countryArea->contains(fn ($i) => $i->company_city === '上海' || strpos((string)$i->company_city, '上海') !== false); - $this->line('countryArea 中是否有含「上海」的 company_city: ' . ($hasShanghai ? '是' : '否')); - - // 5) 若 study 多 1 人:检查该人 company 是否 whereHas 能匹配 (company_address or company_city) like 上海 - if (count($onlyStudy) === 1) { - $u = User::with('company')->find($onlyStudy[0]); - if ($u && $u->company) { - $c = $u->company; - $a = (string)($c->company_address ?? ''); - $city = (string)($c->company_city ?? ''); - $like = 'like \'%' . $term . '%\''; - $this->line(''); - $this->line('该 User 的 company: company_city=' . $city . ' company_address=' . $a); - $this->line('company_address ' . $like . ' => ' . (strpos($a, $term) !== false ? 'true' : 'false')); - $this->line('company_city ' . $like . ' => ' . (strpos($city, $term) !== false ? 'true' : 'false')); - $this->line('company 是否 approvedStudents: ' . ($this->companyIsApprovedStudents($c->id) ? 'Y' : 'N')); - } - } - - return 0; - } - - private function companyIsApprovedStudents($companyId): bool - { - return Company::approvedStudents()->where('id', $companyId)->exists(); - } -} diff --git a/app/Console/Commands/UpdateUserNo.php b/app/Console/Commands/UpdateUserNo.php index 371ce75..b339ef8 100755 --- a/app/Console/Commands/UpdateUserNo.php +++ b/app/Console/Commands/UpdateUserNo.php @@ -85,23 +85,42 @@ class UpdateUserNo extends Command $no = $baseNo; $attempt = 0; $maxAttempts = 1000; // 防止无限循环 + $saved = false; + + // 尝试保存学号,如果遇到唯一性冲突则重新生成 + while (!$saved && $attempt < $maxAttempts) { + // 检查学号是否已存在,如果存在则顺位+1继续尝试 + if (User::where('no', $no)->where('id', '!=', $user->id)->exists()) { + $i++; + $no = $course->student_prefix . str_pad($i, 3, '0', STR_PAD_LEFT); + $attempt++; + continue; + } - // 检查学号是否已存在,如果存在则顺位+1继续尝试 - while (User::where('no', $no)->where('id', '!=', $user->id)->exists() && $attempt < $maxAttempts) { - $i++; - $no = $course->student_prefix . str_pad($i, 3, '0', STR_PAD_LEFT); - $attempt++; + // 尝试更新用户编号 + try { + $user->no = $no; + $user->save(); + $saved = true; + } catch (\Illuminate\Database\QueryException $e) { + // 捕获唯一性约束冲突异常 + if ($e->getCode() == 23000 && strpos($e->getMessage(), 'Duplicate entry') !== false) { + // 学号已被其他用户占用,重新生成 + $i++; + $no = $course->student_prefix . str_pad($i, 3, '0', STR_PAD_LEFT); + $attempt++; + continue; + } + // 其他异常直接抛出 + throw $e; + } } - if ($attempt >= $maxAttempts) { + if (!$saved) { $this->warn('课程: ' . ($course->name ?? $course->id) . ', 用户: ' . ($user->name ?? $user->id) . ' 无法生成唯一学号,跳过'); continue; } - // 更新用户编号 - $user->no = $no; - $user->save(); - if ($no != $baseNo) { $this->info('课程: ' . ($course->name ?? $course->id) . ', 原学号: ' . $baseNo . ' 已存在,使用: ' . $no . ', 用户: ' . ($user->name ?? $user->id)); } else {