|
@@ -307,14 +307,31 @@ class DiagnosticChapterService
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 获取第一个未摸底的章节
|
|
|
|
|
- * 用于章节摸底流程
|
|
|
|
|
|
|
+ * 获取章节摸底目标章节
|
|
|
|
|
+ * - 传入 targetChapterIds: 仅在指定章节范围内按顺序找“有题知识点”章节(允许重复摸底)
|
|
|
|
|
+ * - 未传 targetChapterIds: 保持原逻辑,找第一个未摸底章节
|
|
|
*/
|
|
*/
|
|
|
- public function getFirstUndiagnosedChapter(int $textbookId, int $studentId): ?array
|
|
|
|
|
|
|
+ public function getFirstUndiagnosedChapter(int $textbookId, int $studentId, ?array $targetChapterIds = null): ?array
|
|
|
{
|
|
{
|
|
|
- $chapters = TextbookCatalog::query()
|
|
|
|
|
|
|
+ $query = TextbookCatalog::query()
|
|
|
->where('textbook_id', $textbookId)
|
|
->where('textbook_id', $textbookId)
|
|
|
- ->where('node_type', 'chapter')
|
|
|
|
|
|
|
+ ->where('node_type', 'chapter');
|
|
|
|
|
+
|
|
|
|
|
+ $targetChapterIds = is_array($targetChapterIds)
|
|
|
|
|
+ ? array_values(array_unique(array_filter(array_map('intval', $targetChapterIds), fn ($id) => $id > 0)))
|
|
|
|
|
+ : [];
|
|
|
|
|
+
|
|
|
|
|
+ // 兜底:允许传入 section/subsection 节点,自动映射到所属 chapter 节点
|
|
|
|
|
+ if (!empty($targetChapterIds)) {
|
|
|
|
|
+ $targetChapterIds = $this->normalizeToChapterIds($textbookId, $targetChapterIds);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $useTargetChapters = !empty($targetChapterIds);
|
|
|
|
|
+ if ($useTargetChapters) {
|
|
|
|
|
+ $query->whereIn('id', $targetChapterIds);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $chapters = $query
|
|
|
->orderBy('sort_order')
|
|
->orderBy('sort_order')
|
|
|
->orderBy('display_no')
|
|
->orderBy('display_no')
|
|
|
->orderBy('id')
|
|
->orderBy('id')
|
|
@@ -324,6 +341,42 @@ class DiagnosticChapterService
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 用户指定章节:按顺序找“有题知识点”的章节,不判断是否已摸底(允许重复摸底)
|
|
|
|
|
+ if ($useTargetChapters) {
|
|
|
|
|
+ foreach ($chapters as $chapter) {
|
|
|
|
|
+ $chapterData = $this->getChapterKnowledgePointsSimple($chapter->id);
|
|
|
|
|
+ $kpCodesWithQuestions = $this->filterKpCodesWithQuestions($chapterData['kp_codes']);
|
|
|
|
|
+
|
|
|
|
|
+ if (empty($kpCodesWithQuestions)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Log::info('DiagnosticChapterService: 指定章节摸底命中章节(允许重复)', [
|
|
|
|
|
+ 'textbook_id' => $textbookId,
|
|
|
|
|
+ 'student_id' => $studentId,
|
|
|
|
|
+ 'target_chapter_ids' => $targetChapterIds,
|
|
|
|
|
+ 'chapter_id' => $chapter->id,
|
|
|
|
|
+ 'chapter_name' => $chapter->name ?? '',
|
|
|
|
|
+ 'kp_count' => count($kpCodesWithQuestions),
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'chapter_id' => $chapter->id,
|
|
|
|
|
+ 'chapter_name' => $chapter->name ?? '',
|
|
|
|
|
+ 'section_ids' => $chapterData['section_ids'],
|
|
|
|
|
+ 'kp_codes' => $kpCodesWithQuestions,
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Log::warning('DiagnosticChapterService: 指定章节均无可用题目知识点', [
|
|
|
|
|
+ 'textbook_id' => $textbookId,
|
|
|
|
|
+ 'student_id' => $studentId,
|
|
|
|
|
+ 'target_chapter_ids' => $targetChapterIds,
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
foreach ($chapters as $chapter) {
|
|
foreach ($chapters as $chapter) {
|
|
|
// 检查是否已摸底
|
|
// 检查是否已摸底
|
|
|
if (!$this->hasChapterDiagnostic($studentId, $chapter->id)) {
|
|
if (!$this->hasChapterDiagnostic($studentId, $chapter->id)) {
|
|
@@ -374,6 +427,62 @@ class DiagnosticChapterService
|
|
|
];
|
|
];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 将传入节点ID(chapter/section/subsection)统一映射为所属 chapter ID 列表。
|
|
|
|
|
+ * 保持传入顺序去重,忽略不属于当前教材的节点。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array<int> $nodeIds
|
|
|
|
|
+ * @return array<int>
|
|
|
|
|
+ */
|
|
|
|
|
+ private function normalizeToChapterIds(int $textbookId, array $nodeIds): array
|
|
|
|
|
+ {
|
|
|
|
|
+ if (empty($nodeIds)) {
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $chapterIds = [];
|
|
|
|
|
+ $seen = [];
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($nodeIds as $nodeId) {
|
|
|
|
|
+ $currentId = (int) $nodeId;
|
|
|
|
|
+ if ($currentId <= 0) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $guard = 0;
|
|
|
|
|
+ while ($currentId > 0 && $guard++ < 10) {
|
|
|
|
|
+ $node = TextbookCatalog::query()
|
|
|
|
|
+ ->where('id', $currentId)
|
|
|
|
|
+ ->where('textbook_id', $textbookId)
|
|
|
|
|
+ ->first(['id', 'parent_id', 'node_type']);
|
|
|
|
|
+
|
|
|
|
|
+ if (!$node) {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ($node->node_type === 'chapter') {
|
|
|
|
|
+ if (!isset($seen[$node->id])) {
|
|
|
|
|
+ $seen[$node->id] = true;
|
|
|
|
|
+ $chapterIds[] = (int) $node->id;
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $currentId = (int) ($node->parent_id ?? 0);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (count($chapterIds) !== count($nodeIds)) {
|
|
|
|
|
+ Log::info('DiagnosticChapterService: 章节参数已自动映射到chapter节点', [
|
|
|
|
|
+ 'textbook_id' => $textbookId,
|
|
|
|
|
+ 'input_ids' => $nodeIds,
|
|
|
|
|
+ 'resolved_chapter_ids' => $chapterIds,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $chapterIds;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 获取当前应该学习的章节(第一个有未达标知识点的章节)
|
|
* 获取当前应该学习的章节(第一个有未达标知识点的章节)
|
|
|
* 用于智能组卷流程
|
|
* 用于智能组卷流程
|