From 6bdf84974bd9ff5a725da99621f3e5a13b11da32 Mon Sep 17 00:00:00 2001 From: wangzhibiao Date: Thu, 24 Sep 2026 01:15:04 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=80=E8=87=B4=E6=80=A7?= =?UTF-8?q?=EF=BC=9A=E5=AE=8C=E6=88=90=E6=88=90=E7=BB=A9V2=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E6=97=B6=E9=97=AD=E7=8E=AF=E4=B8=8EAPI=E6=80=BB?= =?UTF-8?q?=E8=B4=A6=E5=AF=B9=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...oreV2RuntimeCompatibilityContractTest.java | 343 ++++++++++++++++++ .../ruoyi/train/domain/TrainScoreInfo.java | 2 +- .../train/mapper/TrainAnalysisMapper.java | 2 + .../train/mapper/TrainScoreInfoMapper.java | 16 + .../impl/TrainAnalysisServiceImpl.java | 15 +- .../impl/TrainScoreInfoServiceImpl.java | 102 +++++- .../mapper/train/TrainAnalysisMapper.xml | 8 + .../mapper/train/TrainScoreInfoMapper.xml | 190 +++++++--- .../bootstrap/000_seed_baseline_teacher.sql | 54 +++ docs/current/03-DATABASE-AND-TABLES.md | 20 +- docs/current/04-API-INVENTORY.md | 293 +++++++++------ docs/current/05-CLIENT-API-STATUS.md | 22 +- docs/current/CLEAN_DEMO_DATABASE_SPEC.md | 9 +- 13 files changed, 868 insertions(+), 208 deletions(-) create mode 100644 RuoYi-Vue/ruoyi-admin/src/test/java/com/ruoyi/web/controller/train/ScoreV2RuntimeCompatibilityContractTest.java create mode 100644 RuoYi-Vue/sql/train/v2/bootstrap/000_seed_baseline_teacher.sql diff --git a/RuoYi-Vue/ruoyi-admin/src/test/java/com/ruoyi/web/controller/train/ScoreV2RuntimeCompatibilityContractTest.java b/RuoYi-Vue/ruoyi-admin/src/test/java/com/ruoyi/web/controller/train/ScoreV2RuntimeCompatibilityContractTest.java new file mode 100644 index 000000000..22e28ff56 --- /dev/null +++ b/RuoYi-Vue/ruoyi-admin/src/test/java/com/ruoyi/web/controller/train/ScoreV2RuntimeCompatibilityContractTest.java @@ -0,0 +1,343 @@ +package com.ruoyi.web.controller.train; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Test; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; + +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.train.domain.TrainScoreInfo; +import com.ruoyi.train.domain.analysis.Student360Vo; +import com.ruoyi.train.mapper.TrainAnalysisMapper; +import com.ruoyi.train.mapper.TrainScoreInfoMapper; +import com.ruoyi.train.service.impl.TrainAnalysisServiceImpl; +import com.ruoyi.train.service.impl.TrainScoreInfoServiceImpl; + +/** + * P0-1 核心验证契约测试: + * 确保成绩模块 (CRUD) 及 学员画像 (Student-360) 彻底在 train_score_info 废弃缺失条件下, + * 基于 Canonical V2 Schema (train_score_item + train_student_score) 闭环工作。 + */ +public class ScoreV2RuntimeCompatibilityContractTest +{ + private Path findProjectFile(String relativePath) + { + for (Path path = Paths.get("").toAbsolutePath(); path != null; path = path.getParent()) + { + Path candidate = path.resolve(relativePath); + if (Files.isRegularFile(candidate)) { + return candidate; + } + Path candidateWithPrefix = path.resolve("RuoYi-Vue/" + relativePath); + if (Files.isRegularFile(candidateWithPrefix)) { + return candidateWithPrefix; + } + } + return null; + } + + @Test + public void testContract_StaticGate_NoTrainScoreInfoInRuntimeMapperXml() throws Exception + { + Path scoreMapperXml = findProjectFile("ruoyi-train/src/main/resources/mapper/train/TrainScoreInfoMapper.xml"); + assertNotNull("TrainScoreInfoMapper.xml 必须存在", scoreMapperXml); + String xmlContent = new String(Files.readAllBytes(scoreMapperXml), StandardCharsets.UTF_8).toLowerCase(); + + // 静态安全断言: 严禁出现任何 train_score_info + assertFalse("TrainScoreInfoMapper.xml 严禁包含废弃旧表 train_score_info", + xmlContent.contains("train_score_info")); + + // 必须基于 Canonical V2 数据库表构建 + assertTrue("必须查询/操作 train_student_score", xmlContent.contains("train_student_score")); + assertTrue("必须关联 train_score_item", xmlContent.contains("train_score_item")); + assertTrue("必须关联 train_enrollment", xmlContent.contains("train_enrollment")); + } + + @Test + public void testContract_StaticGate_AnalysisMapperUsesV2StudentScore() throws Exception + { + Path analysisMapperXml = findProjectFile("ruoyi-train/src/main/resources/mapper/train/TrainAnalysisMapper.xml"); + assertNotNull("TrainAnalysisMapper.xml 必须存在", analysisMapperXml); + String xmlContent = new String(Files.readAllBytes(analysisMapperXml), StandardCharsets.UTF_8).toLowerCase(); + + assertFalse("TrainAnalysisMapper.xml 严禁包含废弃旧表 train_score_info", + xmlContent.contains("train_score_info")); + assertTrue("TrainAnalysisMapper.xml 必须基于 train_student_score 统计成绩", + xmlContent.contains("countstudentscores")); + assertTrue("TrainAnalysisMapper.xml 中的 countStudentScores 必须关联 train_student_score", + xmlContent.contains("from train_student_score")); + } + + @Test + public void testContract_ScoreControllerEndpointsDeclared() throws Exception + { + Class clazz = TrainScoreInfoController.class; + assertNotNull(clazz); + + // GET list + Method listMethod = clazz.getMethod("list", TrainScoreInfo.class); + assertNotNull(listMethod); + assertTrue(listMethod.isAnnotationPresent(GetMapping.class)); + + // GET getInfo + Method getInfoMethod = clazz.getMethod("getInfo", Long.class); + assertNotNull(getInfoMethod); + assertTrue(getInfoMethod.isAnnotationPresent(GetMapping.class)); + + // POST add + Method addMethod = clazz.getMethod("add", TrainScoreInfo.class); + assertNotNull(addMethod); + assertTrue(addMethod.isAnnotationPresent(PostMapping.class)); + + // PUT edit + Method editMethod = clazz.getMethod("edit", TrainScoreInfo.class); + assertNotNull(editMethod); + assertTrue(editMethod.isAnnotationPresent(PutMapping.class)); + + // DELETE remove + Method removeMethod = clazz.getMethod("remove", Long[].class); + assertNotNull(removeMethod); + assertTrue(removeMethod.isAnnotationPresent(DeleteMapping.class)); + } + + @Test + public void testRuntime_ScoreCrudClosedUnderV2Schema() throws Exception + { + // 模拟 V2 数据库内存存储 (严禁访问 train_score_info) + final Map> studentScoreTable = new HashMap<>(); + final Map enrollmentTable = new HashMap<>(); + final Map scoreItemTable = new HashMap<>(); + final long[] idGen = new long[]{1000L}; + + // 预设一个已入学的档案与考核项 + enrollmentTable.put("PLAN01#STU001", 101L); + scoreItemTable.put("PLAN01#COURSE01", 201L); + + TrainScoreInfoMapper mockMapper = (TrainScoreInfoMapper) java.lang.reflect.Proxy.newProxyInstance( + TrainScoreInfoMapper.class.getClassLoader(), + new Class[]{TrainScoreInfoMapper.class}, + (proxy, method, args) -> { + String mname = method.getName(); + if ("selectTrainScoreInfoByScoreId".equals(mname)) { + Long scoreId = (Long) args[0]; + Map row = studentScoreTable.get(scoreId); + if (row == null) return null; + TrainScoreInfo info = new TrainScoreInfo(); + info.setScoreId(scoreId); + info.setPlanId((String) row.get("plan_id")); + info.setcId((String) row.get("c_id")); + info.setStudentId((String) row.get("student_id")); + info.setCourseScore((BigDecimal) row.get("score")); + info.setSourceType((Integer) row.get("source_type")); + return info; + } + if ("selectTrainScoreInfoList".equals(mname)) { + List list = new ArrayList<>(); + for (Map.Entry> e : studentScoreTable.entrySet()) { + TrainScoreInfo info = new TrainScoreInfo(); + info.setScoreId(e.getKey()); + info.setPlanId((String) e.getValue().get("plan_id")); + info.setStudentId((String) e.getValue().get("student_id")); + info.setCourseScore((BigDecimal) e.getValue().get("score")); + list.add(info); + } + return list; + } + if ("selectEnrollmentId".equals(mname)) { + String key = args[0] + "#" + args[1]; + return enrollmentTable.get(key); + } + if ("insertFallbackEnrollment".equals(mname)) { + String key = args[0] + "#" + args[1]; + enrollmentTable.put(key, ++idGen[0]); + return 1; + } + if ("selectPlanCourseId".equals(mname)) { + return 301L; + } + if ("selectScoreItemId".equals(mname)) { + String key = args[0] + "#" + (args[1] != null ? "COURSE01" : "NULL"); + return scoreItemTable.get(key); + } + if ("insertFallbackScoreItem".equals(mname)) { + String key = args[0] + "#" + (args[1] != null ? "COURSE01" : "NULL"); + scoreItemTable.put(key, ++idGen[0]); + return 1; + } + if ("selectStudentScoreIdByEnrollAndItem".equals(mname)) { + Long eId = (Long) args[0]; + Long sId = (Long) args[1]; + for (Map.Entry> entry : studentScoreTable.entrySet()) { + if (eId.equals(entry.getValue().get("enrollment_id")) && + sId.equals(entry.getValue().get("score_item_id"))) { + return entry.getKey(); + } + } + return null; + } + if ("insertStudentScoreRow".equals(mname)) { + @SuppressWarnings("unchecked") + Map params = (Map) args[0]; + long newId = ++idGen[0]; + params.put("scoreId", newId); + Map row = new HashMap<>(params); + row.put("plan_id", "PLAN01"); + row.put("student_id", "STU001"); + studentScoreTable.put(newId, row); + return 1; + } + if ("updateStudentScoreRow".equals(mname)) { + @SuppressWarnings("unchecked") + Map params = (Map) args[0]; + Long id = (Long) params.get("scoreId"); + Map row = studentScoreTable.get(id); + if (row != null) { + row.put("score", params.get("score")); + } + return 1; + } + if ("updateTrainScoreInfo".equals(mname)) { + TrainScoreInfo info = (TrainScoreInfo) args[0]; + Map row = studentScoreTable.get(info.getScoreId()); + if (row != null) { + row.put("score", info.getCourseScore()); + } + return 1; + } + if ("deleteTrainScoreInfoByScoreId".equals(mname)) { + Long id = (Long) args[0]; + studentScoreTable.remove(id); + return 1; + } + if ("deleteTrainScoreInfoByScoreIds".equals(mname)) { + Long[] ids = (Long[]) args[0]; + for (Long id : ids) { + studentScoreTable.remove(id); + } + return ids.length; + } + return null; + } + ); + + TrainScoreInfoServiceImpl service = new TrainScoreInfoServiceImpl(); + Field mapperField = TrainScoreInfoServiceImpl.class.getDeclaredField("trainScoreInfoMapper"); + mapperField.setAccessible(true); + mapperField.set(service, mockMapper); + + // 1. Create (新增成绩) + TrainScoreInfo newScore = new TrainScoreInfo(); + newScore.setPlanId("PLAN01"); + newScore.setcId("COURSE01"); + newScore.setStudentId("STU001"); + newScore.setCourseScore(new BigDecimal("95.50")); + newScore.setSourceType(1); + newScore.setSourceSystem("人工考务录入"); + + int inserted = service.insertTrainScoreInfo(newScore); + assertEquals(1, inserted); + assertNotNull("新增后必须回填 scoreId", newScore.getScoreId()); + Long createdId = newScore.getScoreId(); + + // 2. Detail (查询成绩详情) + TrainScoreInfo detail = service.selectTrainScoreInfoByScoreId(createdId); + assertNotNull(detail); + assertEquals(new BigDecimal("95.50"), detail.getCourseScore()); + assertEquals("PLAN01", detail.getPlanId()); + assertEquals("STU001", detail.getStudentId()); + + // 3. List (查询成绩列表) + List list = service.selectTrainScoreInfoList(new TrainScoreInfo()); + assertEquals(1, list.size()); + assertEquals(createdId, list.get(0).getScoreId()); + + // 4. Update (修改成绩) + detail.setCourseScore(new BigDecimal("98.00")); + int updated = service.updateTrainScoreInfo(detail); + assertEquals(1, updated); + TrainScoreInfo afterUpdate = service.selectTrainScoreInfoByScoreId(createdId); + assertEquals(new BigDecimal("98.00"), afterUpdate.getCourseScore()); + + // 5. Delete (删除成绩) + int deleted = service.deleteTrainScoreInfoByScoreId(createdId); + assertEquals(1, deleted); + assertTrue(service.selectTrainScoreInfoList(new TrainScoreInfo()).isEmpty()); + } + + @Test + public void testRuntime_Student360UnderV2Schema() throws Exception + { + final Long[] scoreCountHolder = new Long[]{0L}; + + TrainAnalysisMapper mockMapper = (TrainAnalysisMapper) java.lang.reflect.Proxy.newProxyInstance( + TrainAnalysisMapper.class.getClassLoader(), + new Class[]{TrainAnalysisMapper.class}, + (proxy, method, args) -> { + String mname = method.getName(); + if ("selectStudentBaseProfile".equals(mname)) { + Map base = new HashMap<>(); + base.put("student_name", "测试学员"); + base.put("gender", "0"); + base.put("user_binding_status", "NOT_BOUND"); + base.put("status", 1); + return base; + } + if ("selectStudentEnrollmentTimeline".equals(mname)) { + return Collections.emptyList(); + } + if ("selectStudentAttendanceStats".equals(mname)) { + return Collections.emptyList(); + } + if ("countStudentExpectedAttendance".equals(mname)) { + return 0L; + } + if ("selectStudentAccommodationAuth".equals(mname)) { + return Collections.emptyMap(); + } + if ("countStudentScores".equals(mname)) { + return scoreCountHolder[0]; + } + return null; + } + ); + + TrainAnalysisServiceImpl analysisService = new TrainAnalysisServiceImpl(); + Field mapperField = TrainAnalysisServiceImpl.class.getDeclaredField("analysisMapper"); + mapperField.setAccessible(true); + mapperField.set(analysisService, mockMapper); + + // 场景 A: 学员在 V2 train_student_score 中尚无成绩记录 + scoreCountHolder[0] = 0L; + Student360Vo voEmpty = analysisService.getStudent360("STU001"); + assertNotNull(voEmpty); + assertFalse("无成绩记录时 hasScoreData 必须为 false", Boolean.TRUE.equals(voEmpty.getHasScoreData())); + assertTrue(voEmpty.getScoreStatusDesc().contains("暂无成绩数据")); + + // 场景 B: 学员在 V2 train_student_score 中已录入 3 门考核成绩 + scoreCountHolder[0] = 3L; + Student360Vo voWithScore = analysisService.getStudent360("STU001"); + assertNotNull(voWithScore); + assertTrue("已录入成绩时 hasScoreData 必须为 true", Boolean.TRUE.equals(voWithScore.getHasScoreData())); + assertEquals("已登记 3 项考核成绩", voWithScore.getScoreStatusDesc()); + } +} diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainScoreInfo.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainScoreInfo.java index caef4829e..39f3903e6 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainScoreInfo.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainScoreInfo.java @@ -9,7 +9,7 @@ import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.core.domain.BaseEntity; /** - * 多类型成绩对象 train_score_info + * 多类型成绩兼容对象 (面向 V2 train_score_item 与 train_student_score 模型) * * @author ruoyi * @date 2026-09-08 diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainAnalysisMapper.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainAnalysisMapper.java index 0493cd4c1..2a62cb678 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainAnalysisMapper.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainAnalysisMapper.java @@ -69,4 +69,6 @@ public interface TrainAnalysisMapper Long countStudentExpectedAttendance(@Param("studentId") String studentId); Map selectStudentAccommodationAuth(@Param("studentId") String studentId); + + Long countStudentScores(@Param("studentId") String studentId); } diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainScoreInfoMapper.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainScoreInfoMapper.java index 4f471b3b0..df17d7a60 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainScoreInfoMapper.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainScoreInfoMapper.java @@ -58,4 +58,20 @@ public interface TrainScoreInfoMapper * @return 结果 */ public int deleteTrainScoreInfoByScoreIds(Long[] scoreIds); + + public Long selectEnrollmentId(@org.apache.ibatis.annotations.Param("planId") String planId, @org.apache.ibatis.annotations.Param("studentId") String studentId); + + public int insertFallbackEnrollment(@org.apache.ibatis.annotations.Param("planId") String planId, @org.apache.ibatis.annotations.Param("studentId") String studentId, @org.apache.ibatis.annotations.Param("createBy") String createBy); + + public Long selectPlanCourseId(@org.apache.ibatis.annotations.Param("planId") String planId, @org.apache.ibatis.annotations.Param("cId") String cId); + + public Long selectScoreItemId(@org.apache.ibatis.annotations.Param("planId") String planId, @org.apache.ibatis.annotations.Param("planCourseId") Long planCourseId); + + public int insertFallbackScoreItem(@org.apache.ibatis.annotations.Param("planId") String planId, @org.apache.ibatis.annotations.Param("planCourseId") Long planCourseId, @org.apache.ibatis.annotations.Param("itemName") String itemName, @org.apache.ibatis.annotations.Param("createBy") String createBy); + + public Long selectStudentScoreIdByEnrollAndItem(@org.apache.ibatis.annotations.Param("enrollmentId") Long enrollmentId, @org.apache.ibatis.annotations.Param("scoreItemId") Long scoreItemId); + + public int insertStudentScoreRow(java.util.Map params); + + public int updateStudentScoreRow(java.util.Map params); } diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainAnalysisServiceImpl.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainAnalysisServiceImpl.java index 3c42078e2..4c630929e 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainAnalysisServiceImpl.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainAnalysisServiceImpl.java @@ -411,9 +411,18 @@ public class TrainAnalysisServiceImpl implements ITrainAnalysisService vo.setAttendanceRate(0.0); } - // 4. 成绩状态 (严格不造假) - vo.setHasScoreData(false); - vo.setScoreStatusDesc("暂无成绩数据 (V2成绩表待考务录入)"); + // 4. 成绩状态 (基于 V2 train_student_score 真实聚合) + Long scoreCount = analysisMapper.countStudentScores(studentId); + if (scoreCount != null && scoreCount > 0) + { + vo.setHasScoreData(true); + vo.setScoreStatusDesc(String.format("已登记 %d 项考核成绩", scoreCount)); + } + else + { + vo.setHasScoreData(false); + vo.setScoreStatusDesc("暂无成绩数据 (V2成绩表待考务录入)"); + } // 5. 自主学习状态 (严格不造假) vo.setHasStudyData(false); diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainScoreInfoServiceImpl.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainScoreInfoServiceImpl.java index 0f20ca9bb..b9c4dad90 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainScoreInfoServiceImpl.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainScoreInfoServiceImpl.java @@ -1,17 +1,22 @@ package com.ruoyi.train.service.impl; +import java.math.BigDecimal; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.ruoyi.common.exception.ServiceException; import com.ruoyi.train.mapper.TrainScoreInfoMapper; import com.ruoyi.train.domain.TrainScoreInfo; import com.ruoyi.train.service.ITrainScoreInfoService; /** - * 多类型成绩Service业务层处理 + * 多类型成绩Service业务层处理 (基于 V2 表 train_student_score + train_score_item 兼容门面) * * @author ruoyi * @date 2026-09-08 @@ -33,12 +38,7 @@ public class TrainScoreInfoServiceImpl implements ITrainScoreInfoService @Override public TrainScoreInfo selectTrainScoreInfoByScoreId(Long scoreId) { - try { - return trainScoreInfoMapper.selectTrainScoreInfoByScoreId(scoreId); - } catch (Exception e) { - log.warn("Legacy train_score_info table query suppressed: {}", e.getMessage()); - return null; - } + return trainScoreInfoMapper.selectTrainScoreInfoByScoreId(scoreId); } /** @@ -50,24 +50,96 @@ public class TrainScoreInfoServiceImpl implements ITrainScoreInfoService @Override public List selectTrainScoreInfoList(TrainScoreInfo trainScoreInfo) { - try { - return trainScoreInfoMapper.selectTrainScoreInfoList(trainScoreInfo); - } catch (Exception e) { - log.warn("Legacy train_score_info table query suppressed: {}", e.getMessage()); - return Collections.emptyList(); - } + return trainScoreInfoMapper.selectTrainScoreInfoList(trainScoreInfo); } /** - * 新增多类型成绩 + * 新增多类型成绩 (V2 兼容门面: 自动桥接 train_enrollment 与 train_score_item 并写入 train_student_score) * * @param trainScoreInfo 多类型成绩 * @return 结果 */ + @Transactional(rollbackFor = Exception.class) @Override public int insertTrainScoreInfo(TrainScoreInfo trainScoreInfo) { - return trainScoreInfoMapper.insertTrainScoreInfo(trainScoreInfo); + String planId = trainScoreInfo.getPlanId(); + String studentId = trainScoreInfo.getStudentId(); + if (planId == null || planId.trim().isEmpty()) + { + throw new ServiceException("培训班ID不能为空"); + } + if (studentId == null || studentId.trim().isEmpty()) + { + throw new ServiceException("学员学号不能为空"); + } + + // 1. 查找或兜底生成参训档案 + Long enrollmentId = trainScoreInfoMapper.selectEnrollmentId(planId, studentId); + if (enrollmentId == null) + { + trainScoreInfoMapper.insertFallbackEnrollment(planId, studentId, trainScoreInfo.getCreateBy()); + enrollmentId = trainScoreInfoMapper.selectEnrollmentId(planId, studentId); + } + + // 2. 查找关联计划课程ID + String cId = trainScoreInfo.getcId(); + Long planCourseId = null; + if (cId != null && !cId.trim().isEmpty()) + { + planCourseId = trainScoreInfoMapper.selectPlanCourseId(planId, cId); + } + + // 3. 查找或兜底生成考核项 + Long scoreItemId = trainScoreInfoMapper.selectScoreItemId(planId, planCourseId); + if (scoreItemId == null) + { + String itemName = (cId != null && !cId.trim().isEmpty()) ? "课程综合考核" : "培训考核项"; + trainScoreInfoMapper.insertFallbackScoreItem(planId, planCourseId, itemName, trainScoreInfo.getCreateBy()); + scoreItemId = trainScoreInfoMapper.selectScoreItemId(planId, planCourseId); + } + + // 4. 解析分值 + BigDecimal score = trainScoreInfo.getCourseScore(); + if (score == null) score = trainScoreInfo.getExamScore(); + if (score == null) score = trainScoreInfo.getTrainScore(); + if (score == null) score = BigDecimal.ZERO; + if (score.compareTo(BigDecimal.ZERO) < 0) + { + throw new ServiceException("成绩不能为负数"); + } + + // 5. 插入或更新学员成绩明细 (train_student_score) + Long existingScoreId = trainScoreInfoMapper.selectStudentScoreIdByEnrollAndItem(enrollmentId, scoreItemId); + if (existingScoreId != null) + { + trainScoreInfo.setScoreId(existingScoreId); + Map params = new HashMap<>(); + params.put("scoreId", existingScoreId); + params.put("score", score); + params.put("sourceType", trainScoreInfo.getSourceType() != null ? trainScoreInfo.getSourceType() : 1); + params.put("sourceSystem", trainScoreInfo.getSourceSystem()); + params.put("updateBy", trainScoreInfo.getUpdateBy()); + params.put("remark", trainScoreInfo.getRemark()); + return trainScoreInfoMapper.updateStudentScoreRow(params); + } + else + { + Map params = new HashMap<>(); + params.put("enrollmentId", enrollmentId); + params.put("scoreItemId", scoreItemId); + params.put("score", score); + params.put("sourceType", trainScoreInfo.getSourceType() != null ? trainScoreInfo.getSourceType() : 1); + params.put("sourceSystem", trainScoreInfo.getSourceSystem()); + params.put("createBy", trainScoreInfo.getCreateBy()); + params.put("remark", trainScoreInfo.getRemark()); + int rows = trainScoreInfoMapper.insertStudentScoreRow(params); + if (params.get("scoreId") != null) + { + trainScoreInfo.setScoreId(((Number) params.get("scoreId")).longValue()); + } + return rows; + } } /** diff --git a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainAnalysisMapper.xml b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainAnalysisMapper.xml index 7a5b45b3f..e4f56fd72 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainAnalysisMapper.xml +++ b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainAnalysisMapper.xml @@ -384,4 +384,12 @@ LIMIT 1 + + + diff --git a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainScoreInfoMapper.xml b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainScoreInfoMapper.xml index 664436ef6..2f791a2c8 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainScoreInfoMapper.xml +++ b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainScoreInfoMapper.xml @@ -5,92 +5,176 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - - - + + + - - + + - - - + + + + + + + + - select score_id, plan_id, c_id, student_id, course_score, train_score, exam_score, source_type, source_system, import_time from train_score_info + select + ss.score_id, + si.plan_id, + coalesce(pc.c_id, '') as c_id, + e.student_id, + ss.score as course_score, + case when si.item_type = 2 then ss.score else null end as train_score, + case when si.item_type = 1 then ss.score else null end as exam_score, + cast(ss.source_type as integer) as source_type, + ss.source_system, + ss.import_time, + ss.create_by, + ss.create_time, + ss.update_by, + ss.update_time, + ss.remark + from train_student_score ss + join train_score_item si on ss.score_item_id = si.score_item_id + join train_enrollment e on ss.enrollment_id = e.enrollment_id + left join train_plan_course pc on si.plan_course_id = pc.plan_course_id - insert into train_score_info - - plan_id, - c_id, - student_id, - course_score, - train_score, - exam_score, - source_type, - source_system, - import_time, - - - #{planId}, - #{cId}, - #{studentId}, - #{courseScore}, - #{trainScore}, - #{examScore}, - #{sourceType}, - #{sourceSystem}, - #{importTime}, - + insert into train_student_score ( + enrollment_id, + score_item_id, + score, + source_type, + source_system, + import_time, + create_by, + create_time, + remark + ) + values ( + (select enrollment_id from train_enrollment where plan_id = #{planId} and student_id = #{studentId} limit 1), + coalesce( + (select si.score_item_id from train_score_item si + left join train_plan_course pc on si.plan_course_id = pc.plan_course_id + where si.plan_id = #{planId} and (pc.c_id = #{cId} or (#{cId} is null and si.plan_course_id is null)) limit 1), + (select score_item_id from train_score_item where plan_id = #{planId} limit 1) + ), + coalesce(#{courseScore}, #{examScore}, #{trainScore}, 0.00), + coalesce(#{sourceType}, 1), + #{sourceSystem}, + CURRENT_TIMESTAMP, + #{createBy}, + CURRENT_TIMESTAMP, + #{remark} + ) - update train_score_info + update train_student_score - plan_id = #{planId}, - c_id = #{cId}, - student_id = #{studentId}, - course_score = #{courseScore}, - train_score = #{trainScore}, - exam_score = #{examScore}, + score = #{courseScore}, + score = #{examScore}, + score = #{trainScore}, source_type = #{sourceType}, source_system = #{sourceSystem}, - import_time = #{importTime}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = CURRENT_TIMESTAMP, where score_id = #{scoreId} - delete from train_score_info where score_id = #{scoreId} + delete from train_student_score where score_id = #{scoreId} - - delete from train_score_info where score_id in + + delete from train_student_score where score_id in #{scoreId} + + + + + insert into train_enrollment (plan_id, student_id, source_type, enrollment_status, accommodation_required, join_time, create_by, create_time) + values (#{planId}, #{studentId}, 1, 1, 0, CURRENT_TIMESTAMP, #{createBy}, CURRENT_TIMESTAMP) + + + + + + + + insert into train_score_item (plan_id, plan_course_id, item_name, item_type, weight, full_score, sort_no, create_by, create_time) + values (#{planId}, #{planCourseId}, #{itemName}, 1, 100.00, 100.00, 0, #{createBy}, CURRENT_TIMESTAMP) + + + + + + insert into train_student_score (enrollment_id, score_item_id, score, source_type, source_system, import_time, create_by, create_time, remark) + values (#{enrollmentId}, #{scoreItemId}, #{score}, #{sourceType}, #{sourceSystem}, CURRENT_TIMESTAMP, #{createBy}, CURRENT_TIMESTAMP, #{remark}) + + + + update train_student_score + set score = #{score}, + source_type = #{sourceType}, + source_system = #{sourceSystem}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = CURRENT_TIMESTAMP + where score_id = #{scoreId} + \ No newline at end of file diff --git a/RuoYi-Vue/sql/train/v2/bootstrap/000_seed_baseline_teacher.sql b/RuoYi-Vue/sql/train/v2/bootstrap/000_seed_baseline_teacher.sql new file mode 100644 index 000000000..bc7aa9e4e --- /dev/null +++ b/RuoYi-Vue/sql/train/v2/bootstrap/000_seed_baseline_teacher.sql @@ -0,0 +1,54 @@ +-- ============================================================================== +-- RailwaySystem Clean Demo Bootstrap 补丁: 000_seed_baseline_teacher.sql +-- 适用数据库: KingbaseES V009R003C018 (schema: public) +-- 执行目标: 在全新库执行 000_phase05_exact_schema.sql 之后、000_preflight.sql 之前, +-- 确定性植入 1 条 Phase 0.5 基线教师占位记录,满足 preflight 与 008 迁移严格断言。 +-- 安全规范: 严禁包含真实个人信息,采用脱敏测试数据。 +-- ============================================================================== + +BEGIN; + +-- 确保幂等性:若已存在 T001 则先清理,确保行数精确为 1 +DELETE FROM train_teacher WHERE t_id = 'T001'; + +INSERT INTO train_teacher ( + t_id, + t_name, + t_gender, + t_birth, + t_department, + t_title, + t_phone, + t_email, + create_user, + create_time, + update_time, + remark +) VALUES ( + 'T001', + '基线教师', + '男', + '1985-01-01', + '机车运用培训部', + '高级讲师', + '13800000001', + 'teacher_t001@railway.local', + 'admin', + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP, + 'Phase 0.5 历史基线占位记录' +); + +-- 验证植入后精确为 1 条数据 +DO $$ +DECLARE + v_cnt INT; +BEGIN + SELECT count(*) INTO v_cnt FROM train_teacher; + IF v_cnt <> 1 THEN + RAISE EXCEPTION '>>> [000-SEED-FATAL] 基线教师数据植入异常: 期望精确 1 条,实际检测到 % 条', v_cnt; + END IF; + RAISE NOTICE '>>> [000-SEED-PASS] Phase 0.5 基线教师数据已成功植入并验证通过 (count=1)。'; +END $$; + +COMMIT; diff --git a/docs/current/03-DATABASE-AND-TABLES.md b/docs/current/03-DATABASE-AND-TABLES.md index bfc133bc3..66469438a 100644 --- a/docs/current/03-DATABASE-AND-TABLES.md +++ b/docs/current/03-DATABASE-AND-TABLES.md @@ -84,7 +84,8 @@ [STEP 2: 培训业务历史基线骨架构建] ├─ 2.1 RuoYi-Vue/sql/train/v2/rollback/000_phase05_exact_schema.sql │ (创建 14 张 Phase 0.5 历史基线表、外键、唯一约束与 update_time 触发器函数) - └─ 2.2 注入 1 条符合基线要求的初始教师记录 (为通过 000 预检与 008 数据平滑清洗) + └─ 2.2 RuoYi-Vue/sql/train/v2/bootstrap/000_seed_baseline_teacher.sql + (确定性注入 1 条基线教师记录 T001,满足 000 预检精确断言与 008 迁移清洗) [STEP 3: 培训业务 V2 结构演进迁移] ├─ 3.1 000_preflight.sql (严格只读核验 Phase 0.5 基线与 sys_* 行数) @@ -171,6 +172,21 @@ | 类别 | 表名 (TABLE_NAME) | 状态与职责说明 | | :--- | :--- | :--- | -| **历史过渡表** | `train_score_info` | 历史 Phase 0.5 单表成绩模型。V2 迁移脚本 `009_drop_legacy_score.sql` 已下线并在 `010_verify.sql` 中断言不存在;但在未完成完整成绩重构前,旧 Controller 仍保留只读兼容映射。 | +| **历史下线表** | `train_score_info` | 历史 Phase 0.5 单表成绩模型。状态为 **`LEGACY_DROPPED / NON_RUNTIME`**。已由 `009_drop_legacy_score.sql` 彻底删除,并由 `010_verify.sql` 强断言不存在。所有 Controller 与运行时 Mapper 已完成 V2 架构平滑切换,底层直接读写 `train_score_item` 与 `train_student_score`,代码库与运行时已实现 0 引用。 | | **系统底座表** | `sys_user` | 若依平台核心用户主档表,与 `train_teacher`、`train_student` 通过 `user_id` 建立强外键关联与 RBAC 权限绑定。 | | **平台管理表** | `sys_dept`、`sys_role`、`sys_menu`、`sys_dict_type`、`sys_dict_data`、`sys_config`、`sys_oper_log` 等 | 若依平台底层通用表结构,支持多部门组织架构、角色菜单授权与操作审计。 | + +--- + +### 3.4 数据库备份与恢复命令核验 (Kingbase Backup Command Verification) +- **环境识别**:KingbaseES V009R003C018(默认端口 4321,协议兼容 PostgreSQL 语法但系统字典与扩展类型具备独立实现)。 +- **备份工具核验**: + - macOS 本地开发机未安装本地 Kingbase/Postgres 二进制工具 (`sys_dump`/`pg_dump` not found); + - 生产及测试环境(宿主机 `192.168.139.218`)采用 KingbaseES 官方原生逻辑备份工具 **`sys_dump`**; + - 官方标准二进制逻辑归档备份命令: + `sys_dump -h 192.168.139.218 -p 4321 -U -d -F c -b -v -f ` + - 官方纯文本 SQL 逻辑导出命令: + `sys_dump -h 192.168.139.218 -p 4321 -U -d -f ` + - 对应恢复工具为 **`sys_restore`** 或 **`ksql`**; + - 严禁在未验证客户端直接假设 `pg_dump`,必须使用 KingbaseES 原生 `sys_dump` 确保对象权限、OID、Identity 自增列序列元数据完整备份。 + diff --git a/docs/current/04-API-INVENTORY.md b/docs/current/04-API-INVENTORY.md index 1429bb910..02768ddc3 100644 --- a/docs/current/04-API-INVENTORY.md +++ b/docs/current/04-API-INVENTORY.md @@ -3,146 +3,195 @@ > **最高原则**:**拒绝根据 URL 猜测表名与调用状态**! > 1. **`PRIMARY_TABLE` / `READ_TABLES` / `WRITE_TABLES`**:必须通过 `Controller → Service → Mapper → XML/SQL` 源码调用链完整确认,标记为 **`VERIFIED_FROM_SOURCE`**;严禁规则推导,严禁使用过时推测表名(如将物理表 `train_class_plan` 误写为 `train_plan`); > 2. **`USED_BY_UI`**:必须通过 `build_ui/src/api/**` 与 `build_ui/src/views/**` 实际调用证据确认,分为 **`VERIFIED_USED_BY_UI`**、**`DEFINED_IN_UI_API_ONLY`**、**`NOT_FOUND_IN_UI`**; -> 3. **`IMPLEMENTATION_STATUS`**:Controller 存在证明端点已定义,标注为 **`ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)`**,受外部硬件阻断的标注为 **`ENDPOINT_DEFINED (BLOCKED_EXTERNAL)`**。 +> 3. **`IMPLEMENTATION_STATUS`**:Controller 存在证明端点已定义,标注为 **`ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)`**,受外部硬件阻断的标注为 **`ENDPOINT_DEFINED (BLOCKED_EXTERNAL)`**; +> 4. **`V2_SCORE_SCHEMA`**:成绩模块已完成底层 V2 架构平滑闭环,所有 `/train/score/**` 与 `/train/scoreinfo/**` 运行时底层均已切换至 `train_student_score` + `train_score_item`,彻底废弃并清零对历史下线表 `train_score_info` 的任何运行时 SQL 调用。 --- -## 1. 核心培训业务 API 账本 (`CUSTOM_TRAIN_BUSINESS_API` — 107 个端点源码穿透验证) +## 1. 核心培训业务 API 账本 (`CUSTOM_TRAIN_BUSINESS_API` — 148 个端点源码穿透验证) | 接口路径 (API) | 请求方式 (METHOD) | 领域模块 | 控制器方法 (CTRL_METHOD) | 服务层方法 (SERVICE_METHOD) | 映射层方法 (MAPPER_METHOD) | 映射XML语句 (XML_STMT) | 主实体表 (PRIMARY_TABLE) | 读操作表 (READ_TABLES) | 写操作表 (WRITE_TABLES) | 调用链状态 (LINEAGE_STATUS) | UI调用状态 (USED_BY_UI) | 实现状态 (STATUS) | -| :--- | :---: | :---: | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :---: | :---: | :---: | -| `/train/teacher/list` | `GET` | `Train` | `list` | `ITrainTeacherService.selectTrainTeacherList` | `TrainTeacherMapper.selectTrainTeacherList` | `selectTrainTeacherList` | `train_teacher` | `train_teacher` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/teacher/{tId}` | `GET` | `Train` | `getInfo` | `ITrainTeacherService.selectTrainTeacherByTId` | `TrainTeacherMapper.selectTrainTeacherByTId` | `selectTrainTeacherByTId` | `train_teacher` | `train_teacher` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/teacher` | `POST` | `Train` | `add` | `ITrainTeacherService.insertTrainTeacher` | `TrainTeacherMapper.insertTrainTeacher` | `insertTrainTeacher` | `train_teacher` | `NONE` | `train_teacher` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/teacher` | `PUT` | `Train` | `edit` | `ITrainTeacherService.updateTrainTeacher` | `TrainTeacherMapper.updateTrainTeacher` | `updateTrainTeacher` | `train_teacher` | `train_teacher` | `train_teacher` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/teacher/{tIds}` | `DELETE` | `Train` | `remove` | `ITrainTeacherService.deleteTrainTeacherByTIds` | `TrainTeacherMapper.deleteTrainTeacherByTIds` | `deleteTrainTeacherByTIds` | `train_teacher` | `train_teacher` | `train_teacher` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/teacher/export` | `POST` | `Train` | `export` | `ITrainTeacherService.selectTrainTeacherList` | `TrainTeacherMapper.selectTrainTeacherList` | `selectTrainTeacherList` | `train_teacher` | `train_teacher` | `NONE` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/me/teacher/plans` | `GET` | `Train` | `getMyPlans` | `ITrainingIdentityService.assertTeacherIdentity` | `TrainLessonMapper.selectPlansByTeacherId` | `selectPlansByTeacherId` | `train_class_plan` | `train_class_plan, train_plan_course, train_lesson` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/me/teacher` | `GET` | `Train` | `getTeacherProfile` | `ITrainingIdentityService.assertTeacherIdentity` | `TrainTeacherMapper.selectTrainTeacherByTId` | `selectTrainTeacherByTId` | `train_teacher` | `train_teacher` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/me/teacher/students` | `GET` | `Train` | `getMyClassStudents` | `TrainLessonMapper.countTeacherInPlan` | `TrainEnrollmentMapper.selectEnrollmentItemList` | `selectEnrollmentItemList` | `train_enrollment` | `train_enrollment, train_lesson, train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/me/teacher/lessons/{lessonId}/attendance` | `GET` | `Train` | `getLessonAttendance` | `TrainLessonMapper.selectTrainLessonById` | `TrainStudentCheckinMapper.selectCheckinsByLessonId` | `selectCheckinsByLessonId` | `train_student_checkin` | `train_lesson, train_student_checkin` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/student/list` | `GET` | `Train` | `list` | `ITrainStudentService.selectTrainStudentList` | `TrainStudentMapper.selectTrainStudentList` | `selectTrainStudentList` | `train_student` | `train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/student/{sId}` | `GET` | `Train` | `getInfo` | `ITrainStudentService.selectTrainStudentBySId` | `TrainStudentMapper.selectTrainStudentBySId` | `selectTrainStudentBySId` | `train_student` | `train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/student` | `POST` | `Train` | `add` | `ITrainStudentService.insertTrainStudent` | `TrainStudentMapper.insertTrainStudent` | `insertTrainStudent` | `train_student` | `NONE` | `train_student` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/student` | `PUT` | `Train` | `edit` | `ITrainStudentService.updateTrainStudent` | `TrainStudentMapper.updateTrainStudent` | `updateTrainStudent` | `train_student` | `train_student` | `train_student` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/student/{sIds}` | `DELETE` | `Train` | `remove` | `ITrainStudentService.deleteTrainStudentBySIds` | `TrainStudentMapper.deleteTrainStudentBySIds` | `deleteTrainStudentBySIds` | `train_student` | `train_student` | `train_student` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/student/export` | `POST` | `Train` | `export` | `ITrainStudentService.selectTrainStudentList` | `TrainStudentMapper.selectTrainStudentList` | `selectTrainStudentList` | `train_student` | `train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/student/importTemplate` | `GET` | `Train` | `importTemplate` | `ITrainStudentService.importTemplate` | `NONE` | `NONE` | `train_student` | `NONE` | `NONE` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/student/importData` | `POST` | `Train` | `importData` | `ITrainStudentService.importStudent` | `TrainStudentMapper.insertTrainStudent` | `insertTrainStudent` | `train_student` | `train_student` | `train_student` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/me/student/enrollments` | `GET` | `Train` | `getMyEnrollments` | `ITrainingIdentityService.assertStudentIdentity` | `TrainEnrollmentMapper.selectEnrollmentsByStudentId` | `selectEnrollmentsByStudentId` | `train_enrollment` | `train_enrollment, train_class_plan` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/me/student/timetable` | `GET` | `Train` | `getMyTimetable` | `ITrainingIdentityService.assertStudentIdentity` | `TrainLessonMapper.selectScheduleLessonsByStudentId` | `selectScheduleLessonsByStudentId` | `train_lesson` | `train_lesson, train_plan_course, train_course, train_classroom_info, train_training_site` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/me/student/report` | `GET` | `Train` | `getMyReport` | `ITrainingIdentityService.assertStudentIdentity` | `TrainClassReportMapper.selectLatestReportByStudentId` | `selectLatestReportByStudentId` | `train_class_report` | `train_class_report, train_enrollment, train_class_plan` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/me/student/attendance` | `GET` | `Train` | `getMyAttendance` | `ITrainingIdentityService.assertStudentIdentity` | `TrainStudentCheckinMapper.selectCheckinsByStudentId` | `selectCheckinsByStudentId` | `train_student_checkin` | `train_student_checkin, train_lesson, train_enrollment` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/me/student/duration` | `GET` | `Train` | `getMyDuration` | `ITrainingIdentityService.assertStudentIdentity` | `TrainStudyDurationMapper.selectDurationsByStudentId` | `selectDurationsByStudentId` | `train_study_duration` | `train_study_duration, train_course` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/me/student/portrait` | `GET` | `Train` | `getMyPortrait` | `ITrainingIdentityService.assertStudentIdentity` | `TrainStudentPortraitMapper.selectPortraitByStudentId` | `selectPortraitByStudentId` | `train_student_portrait` | `train_student_portrait, train_enrollment, train_class_plan, train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/course/list` | `GET` | `Train` | `list` | `ITrainCourseService.selectTrainCourseList` | `TrainCourseMapper.selectTrainCourseList` | `selectTrainCourseList` | `train_course` | `train_course` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/course/{cId}` | `GET` | `Train` | `getInfo` | `ITrainCourseService.selectTrainCourseByCId` | `TrainCourseMapper.selectTrainCourseByCId` | `selectTrainCourseByCId` | `train_course` | `train_course` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/course` | `POST` | `Train` | `add` | `ITrainCourseService.insertTrainCourse` | `TrainCourseMapper.insertTrainCourse` | `insertTrainCourse` | `train_course` | `NONE` | `train_course` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/course` | `PUT` | `Train` | `edit` | `ITrainCourseService.updateTrainCourse` | `TrainCourseMapper.updateTrainCourse` | `updateTrainCourse` | `train_course` | `train_course` | `train_course` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/course/{cIds}` | `DELETE` | `Train` | `remove` | `ITrainCourseService.deleteTrainCourseByCIds` | `TrainCourseMapper.deleteTrainCourseByCIds` | `deleteTrainCourseByCIds` | `train_course` | `train_course` | `train_course` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/classroom/list` | `GET` | `Train` | `list` | `ITrainClassroomInfoService.selectTrainClassroomInfoList` | `TrainClassroomInfoMapper.selectTrainClassroomInfoList` | `selectTrainClassroomInfoList` | `train_classroom_info` | `train_classroom_info` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/classroom/{classRoomId}` | `GET` | `Train` | `getInfo` | `ITrainClassroomInfoService.selectTrainClassroomInfoByClassRoomId` | `TrainClassroomInfoMapper.selectTrainClassroomInfoByClassRoomId` | `selectTrainClassroomInfoByClassRoomId` | `train_classroom_info` | `train_classroom_info` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/classroom` | `POST` | `Train` | `add` | `ITrainClassroomInfoService.insertTrainClassroomInfo` | `TrainClassroomInfoMapper.insertTrainClassroomInfo` | `insertTrainClassroomInfo` | `train_classroom_info` | `NONE` | `train_classroom_info` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/classroom` | `PUT` | `Train` | `edit` | `ITrainClassroomInfoService.updateTrainClassroomInfo` | `TrainClassroomInfoMapper.updateTrainClassroomInfo` | `updateTrainClassroomInfo` | `train_classroom_info` | `train_classroom_info` | `train_classroom_info` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/classroom/{classRoomIds}` | `DELETE` | `Train` | `remove` | `ITrainClassroomInfoService.deleteTrainClassroomInfoByClassRoomIds` | `TrainClassroomInfoMapper.deleteTrainClassroomInfoByClassRoomIds` | `deleteTrainClassroomInfoByClassRoomIds` | `train_classroom_info` | `train_classroom_info` | `train_classroom_info` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/training-site/list` | `GET` | `Train` | `list` | `ITrainTrainingSiteService.selectTrainTrainingSiteList` | `TrainTrainingSiteMapper.selectTrainTrainingSiteList` | `selectTrainTrainingSiteList` | `train_training_site` | `train_training_site` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/training-site/{siteId}` | `GET` | `Train` | `getInfo` | `ITrainTrainingSiteService.selectTrainTrainingSiteBySiteId` | `TrainTrainingSiteMapper.selectTrainTrainingSiteBySiteId` | `selectTrainTrainingSiteBySiteId` | `train_training_site` | `train_training_site` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/training-site` | `POST` | `Train` | `add` | `ITrainTrainingSiteService.insertTrainTrainingSite` | `TrainTrainingSiteMapper.insertTrainTrainingSite` | `insertTrainTrainingSite` | `train_training_site` | `NONE` | `train_training_site` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/training-site` | `PUT` | `Train` | `edit` | `ITrainTrainingSiteService.updateTrainTrainingSite` | `TrainTrainingSiteMapper.updateTrainTrainingSite` | `updateTrainTrainingSite` | `train_training_site` | `train_training_site` | `train_training_site` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/training-site/{siteIds}` | `DELETE` | `Train` | `remove` | `ITrainTrainingSiteService.deleteTrainTrainingSiteBySiteIds` | `TrainTrainingSiteMapper.deleteTrainTrainingSiteBySiteIds` | `deleteTrainTrainingSiteBySiteIds` | `train_training_site` | `train_training_site` | `train_training_site` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/plans` | `GET` | `Train` | `list` | `PlanAppService.selectPlanItemList` | `TrainClassPlanMapper.selectTrainClassPlanList` | `selectTrainClassPlanList` | `train_class_plan` | `train_class_plan, train_plan_course, train_enrollment` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/plans/{planId}` | `GET` | `Train` | `getInfo` | `ITrainClassPlanService.selectTrainClassPlanByPlanId` | `TrainClassPlanMapper.selectTrainClassPlanByPlanId` | `selectTrainClassPlanByPlanId` | `train_class_plan` | `train_class_plan` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/plans` | `POST` | `Train` | `add` | `PlanAppService.createPlan` | `TrainClassPlanMapper.insertTrainClassPlan` | `insertTrainClassPlan` | `train_class_plan` | `NONE` | `train_class_plan` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/plans/{planId}` | `PUT` | `Train` | `edit` | `PlanAppService.updatePlan` | `TrainClassPlanMapper.updateTrainClassPlan` | `updateTrainClassPlan` | `train_class_plan` | `train_class_plan` | `train_class_plan` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/plans/{planIds}` | `DELETE` | `Train` | `remove` | `ITrainClassPlanService.deleteTrainClassPlanByPlanIds` | `TrainClassPlanMapper.deleteTrainClassPlanByPlanIds` | `deleteTrainClassPlanByPlanIds` | `train_class_plan` | `train_class_plan` | `train_class_plan` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/plans/{planId}/publish` | `POST` | `Train` | `publish` | `PlanAppService.publishPlan` | `TrainClassPlanMapper.updateTrainClassPlan` | `updateTrainClassPlan` | `train_class_plan` | `train_class_plan` | `train_class_plan` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/plans/{planId}/start` | `POST` | `Train` | `start` | `PlanAppService.startPlan` | `TrainClassPlanMapper.updateTrainClassPlan` | `updateTrainClassPlan` | `train_class_plan` | `train_class_plan` | `train_class_plan` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/plans/{planId}/complete` | `POST` | `Train` | `complete` | `PlanAppService.completePlan` | `TrainClassPlanMapper.updateTrainClassPlan` | `updateTrainClassPlan` | `train_class_plan` | `train_class_plan, train_enrollment` | `train_class_plan, train_enrollment` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/plans/{planId}/courses` | `GET` | `Train` | `list` | `PlanAppService.listCourses` | `TrainPlanCourseMapper.selectCourseItemsByPlanId` | `selectCourseItemsByPlanId` | `train_plan_course` | `train_plan_course, train_course` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/plans/{planId}/courses` | `POST` | `Train` | `bind` | `PlanAppService.bindCourses` | `TrainPlanCourseMapper.insertTrainPlanCourse` | `insertTrainPlanCourse` | `train_plan_course` | `train_plan_course` | `train_plan_course` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/plans/{planId}/courses/{planCourseId}` | `DELETE` | `Train` | `unbind` | `PlanAppService.unbindCourse` | `TrainPlanCourseMapper.deleteTrainPlanCourseByPlanCourseId` | `deleteTrainPlanCourseByPlanCourseId` | `train_plan_course` | `train_plan_course` | `train_plan_course` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/plans/{planId}/enrollments` | `GET` | `Train` | `list` | `EnrollmentAppService.listEnrollments` | `TrainEnrollmentMapper.selectEnrollmentItemList` | `selectEnrollmentItemList` | `train_enrollment` | `train_enrollment, train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/plans/{planId}/enrollments` | `POST` | `Train` | `enroll` | `EnrollmentAppService.enrollStudent` | `TrainEnrollmentMapper.insertTrainEnrollment` | `insertTrainEnrollment` | `train_enrollment` | `train_enrollment` | `train_enrollment` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/plans/{planId}/enrollments/batch` | `POST` | `Train` | `batchEnroll` | `EnrollmentAppService.batchEnrollStudents` | `TrainEnrollmentMapper.insertTrainEnrollment` | `insertTrainEnrollment` | `train_enrollment` | `train_enrollment` | `train_enrollment` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/enrollments/{enrollmentId}` | `GET` | `Train` | `getInfo` | `EnrollmentAppService.getEnrollmentDetail` | `TrainEnrollmentMapper.selectEnrollmentItemById` | `selectEnrollmentItemById` | `train_enrollment` | `train_enrollment, train_student, train_class_plan` | `NONE` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/enrollments/{enrollmentId}/status` | `PUT` | `Train` | `updateStatus` | `EnrollmentAppService.updateStatus` | `TrainEnrollmentMapper.updateTrainEnrollment` | `updateTrainEnrollment` | `train_enrollment` | `train_enrollment` | `train_enrollment` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/enrollments/{enrollmentId}` | `DELETE` | `Train` | `remove` | `EnrollmentAppService.removeEnrollment` | `TrainEnrollmentMapper.deleteTrainEnrollmentByEnrollmentId` | `deleteTrainEnrollmentByEnrollmentId` | `train_enrollment` | `train_enrollment` | `train_enrollment` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/plans/{planId}/lessons` | `GET` | `Train` | `list` | `SchedulingAppService.listLessons` | `TrainLessonMapper.selectLessonItemsByPlanId` | `selectLessonItemsByPlanId` | `train_lesson` | `train_lesson, train_plan_course, train_course, train_teacher, train_classroom_info, train_training_site` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/plans/{planId}/lessons` | `POST` | `Train` | `schedule` | `SchedulingAppService.scheduleLesson` | `TrainLessonMapper.insertTrainLesson` | `insertTrainLesson` | `train_lesson` | `train_lesson` | `train_lesson` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/lessons/{lessonId}` | `GET` | `Train` | `getInfo` | `SchedulingAppService.getLessonDetail` | `TrainLessonMapper.selectLessonItemById` | `selectLessonItemById` | `train_lesson` | `train_lesson, train_plan_course, train_course, train_teacher, train_classroom_info, train_training_site` | `NONE` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/lessons/{lessonId}` | `PUT` | `Train` | `update` | `SchedulingAppService.updateLesson` | `TrainLessonMapper.updateTrainLesson` | `updateTrainLesson` | `train_lesson` | `train_lesson` | `train_lesson` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/lessons/{lessonId}` | `DELETE` | `Train` | `cancel` | `SchedulingAppService.cancelLesson` | `TrainLessonMapper.deleteTrainLessonByLessonId` | `deleteTrainLessonByLessonId` | `train_lesson` | `train_lesson` | `train_lesson` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/lessons/check-conflict` | `POST` | `Train` | `checkConflict` | `SchedulingAppService.checkConflict` | `TrainLessonMapper.countTeacherConflicts` | `countTeacherConflicts` | `train_lesson` | `train_lesson` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/lessons/scheduling-options` | `GET` | `Train` | `getOptions` | `SchedulingAppService.getSchedulingOptions` | `TrainClassroomInfoMapper.selectTrainClassroomInfoList` | `selectTrainClassroomInfoList` | `train_classroom_info` | `train_classroom_info, train_training_site, train_teacher` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/plans/{planId}/timetable` | `GET` | `Train` | `getTimetable` | `SchedulingAppService.getTimetable` | `TrainLessonMapper.selectScheduleLessonsByPlanId` | `selectScheduleLessonsByPlanId` | `train_lesson` | `train_lesson, train_plan_course, train_course, train_teacher, train_classroom_info, train_training_site` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/enrollments/{enrollmentId}/report` | `POST` | `Train` | `report` | `ITrainClassReportService.reportEnrollment` | `TrainClassReportMapper.insertTrainClassReport` | `insertTrainClassReport` | `train_class_report` | `train_enrollment, train_class_plan` | `train_class_report` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/report/list` | `GET` | `Train` | `list` | `ITrainClassReportService.selectTrainClassReportList` | `TrainClassReportMapper.selectTrainClassReportList` | `selectTrainClassReportList` | `train_class_report` | `train_class_report, train_enrollment, train_student, train_class_plan` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/checkin/list` | `GET` | `Train` | `list` | `ITrainStudentCheckinService.selectTrainStudentCheckinList` | `TrainStudentCheckinMapper.selectTrainStudentCheckinList` | `selectTrainStudentCheckinList` | `train_student_checkin` | `train_student_checkin, train_student, train_lesson` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/attendance/lessons/{lessonId}/checkins` | `GET` | `Train` | `getLessonRoster` | `AttendanceAppService.getLessonRoster` | `TrainStudentCheckinMapper.selectCheckinsByLessonId` | `selectCheckinsByLessonId` | `train_student_checkin` | `train_student_checkin, train_enrollment, train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/attendance/sign-in` | `POST` | `Train` | `signIn` | `AttendanceAppService.signIn` | `TrainStudentCheckinMapper.insertTrainStudentCheckin` | `insertTrainStudentCheckin` | `train_student_checkin` | `train_student_checkin` | `train_student_checkin` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/attendance/sign-out` | `POST` | `Train` | `signOut` | `AttendanceAppService.signOut` | `TrainStudentCheckinMapper.updateTrainStudentCheckin` | `updateTrainStudentCheckin` | `train_student_checkin` | `train_student_checkin` | `train_student_checkin` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/attendance/proxy-checkin` | `POST` | `Train` | `signIn` | `AttendanceAppService.signIn` | `TrainStudentCheckinMapper.insertTrainStudentCheckin` | `insertTrainStudentCheckin` | `train_student_checkin` | `train_student_checkin` | `train_student_checkin` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/attendance/mark-absent` | `POST` | `Train` | `markAbsence` | `AttendanceAppService.markAbsence` | `TrainStudentCheckinMapper.insertTrainStudentCheckin` | `insertTrainStudentCheckin` | `train_student_checkin` | `train_student_checkin` | `train_student_checkin` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/attendance/adjust` | `PUT` | `Train` | `adjust` | `AttendanceAppService.adjust` | `TrainStudentCheckinMapper.updateTrainStudentCheckin` | `updateTrainStudentCheckin` | `train_student_checkin` | `train_student_checkin` | `train_student_checkin` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/dormitory/list` | `GET` | `Train` | `list` | `ITrainDormitoryInfoService.selectTrainDormitoryInfoList` | `TrainDormitoryInfoMapper.selectTrainDormitoryInfoList` | `selectTrainDormitoryInfoList` | `train_dormitory_info` | `train_dormitory_info` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/dormitory/{dormId}` | `GET` | `Train` | `getInfo` | `ITrainDormitoryInfoService.selectTrainDormitoryInfoByDormId` | `TrainDormitoryInfoMapper.selectTrainDormitoryInfoByDormId` | `selectTrainDormitoryInfoByDormId` | `train_dormitory_info` | `train_dormitory_info` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/dormitory` | `POST` | `Train` | `add` | `ITrainDormitoryInfoService.insertTrainDormitoryInfo` | `TrainDormitoryInfoMapper.insertTrainDormitoryInfo` | `insertTrainDormitoryInfo` | `train_dormitory_info` | `NONE` | `train_dormitory_info` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/dormitory` | `PUT` | `Train` | `edit` | `ITrainDormitoryInfoService.updateTrainDormitoryInfo` | `TrainDormitoryInfoMapper.updateTrainDormitoryInfo` | `updateTrainDormitoryInfo` | `train_dormitory_info` | `train_dormitory_info` | `train_dormitory_info` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/dormitory/{dormIds}` | `DELETE` | `Train` | `remove` | `ITrainDormitoryInfoService.deleteTrainDormitoryInfoByDormIds` | `TrainDormitoryInfoMapper.deleteTrainDormitoryInfoByDormIds` | `deleteTrainDormitoryInfoByDormIds` | `train_dormitory_info` | `train_dormitory_info` | `train_dormitory_info` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/accommodation/list` | `GET` | `Train` | `list` | `ITrainAccommodationService.selectTrainAccommodationList` | `TrainAccommodationMapper.selectTrainAccommodationList` | `selectTrainAccommodationList` | `train_accommodation` | `train_accommodation, train_dormitory_info, train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/accommodation/{accommodationId}` | `GET` | `Train` | `getInfo` | `ITrainAccommodationService.selectTrainAccommodationByAccommodationId` | `TrainAccommodationMapper.selectTrainAccommodationByAccommodationId` | `selectTrainAccommodationByAccommodationId` | `train_accommodation` | `train_accommodation, train_dormitory_info, train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/accommodation/enrollment/{enrollmentId}` | `GET` | `Train` | `getByEnrollment` | `ITrainAccommodationService.selectTrainAccommodationByEnrollmentId` | `TrainAccommodationMapper.selectTrainAccommodationByEnrollmentId` | `selectTrainAccommodationByEnrollmentId` | `train_accommodation` | `train_accommodation, train_dormitory_info, train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/accommodation/enrollment/{enrollmentId}/history` | `GET` | `Train` | `getHistory` | `ITrainAccommodationService.selectAccommodationHistory` | `TrainAccommodationMapper.selectAccommodationHistory` | `selectAccommodationHistory` | `train_accommodation` | `train_accommodation, train_dormitory_info, train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/accommodation` | `POST` | `Train` | `add` | `ITrainAccommodationService.insertTrainAccommodation` | `TrainAccommodationMapper.insertTrainAccommodation` | `insertTrainAccommodation` | `train_accommodation` | `train_accommodation, train_dormitory_info` | `train_accommodation, train_dormitory_info` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/accommodation/{accommodationId}/checkin` | `POST` | `Train` | `checkin` | `ITrainAccommodationService.checkinAccommodation` | `TrainAccommodationMapper.updateTrainAccommodation` | `updateTrainAccommodation` | `train_accommodation` | `train_accommodation, train_dormitory_info` | `train_accommodation, train_dormitory_info` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/accommodation/{accommodationId}/checkout` | `POST` | `Train` | `checkout` | `ITrainAccommodationService.checkoutAccommodation` | `TrainAccommodationMapper.updateTrainAccommodation` | `updateTrainAccommodation` | `train_accommodation` | `train_accommodation, train_dormitory_info` | `train_accommodation, train_dormitory_info` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/accommodation/{accommodationId}/transfer` | `POST` | `Train` | `transfer` | `ITrainAccommodationService.transferAccommodation` | `TrainAccommodationMapper.updateTrainAccommodation` | `updateTrainAccommodation` | `train_accommodation` | `train_accommodation, train_dormitory_info` | `train_accommodation, train_dormitory_info` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/accommodation/{accommodationIds}` | `DELETE` | `Train` | `remove` | `ITrainAccommodationService.deleteTrainAccommodationByAccommodationIds` | `TrainAccommodationMapper.deleteTrainAccommodationByAccommodationIds` | `deleteTrainAccommodationByAccommodationIds` | `train_accommodation` | `train_accommodation` | `train_accommodation, train_dormitory_info` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/score/list` | `GET` | `Train` | `list` | `ITrainScoreInfoService.selectTrainScoreInfoList` | `TrainScoreInfoMapper.selectTrainScoreInfoList` | `selectTrainScoreInfoList` | `train_score_info` | `train_score_info, train_student, train_course` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/score/{scoreId}` | `GET` | `Train` | `getInfo` | `ITrainScoreInfoService.selectTrainScoreInfoByScoreId` | `TrainScoreInfoMapper.selectTrainScoreInfoByScoreId` | `selectTrainScoreInfoByScoreId` | `train_score_info` | `train_score_info, train_student, train_course` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/score` | `POST` | `Train` | `add` | `ITrainScoreInfoService.insertTrainScoreInfo` | `TrainScoreInfoMapper.insertTrainScoreInfo` | `insertTrainScoreInfo` | `train_score_info` | `NONE` | `train_score_info` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/score` | `PUT` | `Train` | `edit` | `ITrainScoreInfoService.updateTrainScoreInfo` | `TrainScoreInfoMapper.updateTrainScoreInfo` | `updateTrainScoreInfo` | `train_score_info` | `train_score_info` | `train_score_info` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/score/{scoreIds}` | `DELETE` | `Train` | `remove` | `ITrainScoreInfoService.deleteTrainScoreInfoByScoreIds` | `TrainScoreInfoMapper.deleteTrainScoreInfoByScoreIds` | `deleteTrainScoreInfoByScoreIds` | `train_score_info` | `train_score_info` | `train_score_info` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/analysis/dashboard/overview` | `GET` | `Train` | `getDashboardOverview` | `ITrainAnalysisService.getDashboardOverview` | `TrainAnalysisMapper.selectDashboardOverview` | `selectDashboardOverview` | `train_class_plan` | `train_class_plan, train_enrollment, train_lesson, train_student_checkin, train_accommodation` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/analysis/dashboard/charts` | `GET` | `Train` | `getDashboardCharts` | `ITrainAnalysisService.getDashboardCharts` | `TrainAnalysisMapper.selectDashboardCharts` | `selectDashboardCharts` | `train_class_plan` | `train_class_plan, train_enrollment, train_lesson, train_student_checkin, train_accommodation` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/analysis/attendance/summary` | `GET` | `Train` | `getAttendanceSummary` | `ITrainAnalysisService.getAttendanceSummary` | `TrainAnalysisMapper.selectAttendanceSummary` | `selectAttendanceSummary` | `train_student_checkin` | `train_student_checkin, train_lesson, train_class_plan` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/analysis/attendance/charts` | `GET` | `Train` | `getAttendanceCharts` | `ITrainAnalysisService.getAttendanceCharts` | `TrainAnalysisMapper.selectAttendanceCharts` | `selectAttendanceCharts` | `train_student_checkin` | `train_student_checkin, train_lesson, train_class_plan` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/analysis/portrait/student-360/{studentId}` | `GET` | `Train` | `getStudent360` | `ITrainAnalysisService.getStudent360` | `TrainAnalysisMapper.selectStudent360` | `selectStudent360` | `train_student` | `train_student, train_enrollment, train_student_checkin, train_score_info, train_study_duration` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/access/mapping/list` | `GET` | `Train` | `getDormMapping` | `IExternalResourceMappingService.getActiveRoomMapping` | `TrainExternalResourceMappingMapper.selectTrainExternalResourceMappingList` | `selectTrainExternalResourceMappingList` | `train_external_resource_mapping` | `train_external_resource_mapping, train_dormitory_info` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/access/auth/list` | `GET` | `Train` | `listAuth` | `ITrainDormAuthService.selectTrainDormAuthList` | `TrainDormAuthMapper.selectTrainDormAuthList` | `selectTrainDormAuthList` | `train_dorm_auth` | `train_dorm_auth, train_accommodation, train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/access/operation/list` | `GET` | `Train` | `getOperationHistory` | `IExternalOperationService.listOperationsByDoorAuthId` | `TrainExternalOperationMapper.selectTrainExternalOperationList` | `selectTrainExternalOperationList` | `train_external_operation` | `train_external_operation` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/access/capability` | `GET` | `Train` | `getCapabilities` | `N/A (配置与只读状态)` | `NONE` | `NONE` | `N/A (配置与常量)` | `NONE` | `NONE` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/access/issue` | `POST` | `Train` | `issueCredential` | `AccessAuthorizationOrchestrator.issueAuthorization` | `TrainDormAuthMapper.updateTrainDormAuth` | `updateTrainDormAuth` | `train_dorm_auth` | `train_dorm_auth, train_accommodation` | `train_dorm_auth, train_external_operation` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BLOCKED_EXTERNAL)` | -| `/train/access/revoke` | `POST` | `Train` | `revokeAuth` | `AccessAuthorizationOrchestrator.revokeAuthorization` | `TrainDormAuthMapper.updateTrainDormAuth` | `updateTrainDormAuth` | `train_dorm_auth` | `train_dorm_auth, train_accommodation` | `train_dorm_auth, train_external_operation` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BLOCKED_EXTERNAL)` | -| `/train/binding/teachers/bindable-users` | `GET` | `Train` | `getTeacherBindableUsers` | `TrainAccountBindingAdminService.selectBindableUsers` | `SysUserMapper.selectUserList` | `selectUserList` | `sys_user` | `sys_user, train_teacher, train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/binding/teachers/{teacherId}/bind` | `POST` | `Train` | `bindTeacherUser` | `TrainAccountBindingAdminService.bindTeacherAccount` | `TrainTeacherMapper.updateTrainTeacher` | `updateTrainTeacher` | `train_teacher` | `sys_user, train_teacher` | `train_teacher` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/binding/students/bindable-users` | `GET` | `Train` | `getStudentBindableUsers` | `TrainAccountBindingAdminService.selectBindableUsers` | `SysUserMapper.selectUserList` | `selectUserList` | `sys_user` | `sys_user, train_teacher, train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | -| `/train/binding/students/{studentId}/bind` | `POST` | `Train` | `bindStudentUser` | `TrainAccountBindingAdminService.bindStudentAccount` | `TrainStudentMapper.updateTrainStudent` | `updateTrainStudent` | `train_student` | `sys_user, train_student` | `train_student` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| :--- | :---: | :---: | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :---: | :---: | +| `/train/access/auth/accommodation/{accommodationId}` | `GET` | `Train` | `TrainAccessManagementController.getAuthByAccommodationId` | `TrainAccessManagementService.getAuthByAccommodationId` | `TrainDormAuthMapper.selectByAccommodationId` | `selectByAccommodationId` | `train_dorm_auth` | `train_dorm_auth` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/access/auth/list` | `GET` | `Train` | `TrainAccessManagementController.listAuth` | `TrainAccessManagementService.listAuth` | `TrainDormAuthMapper.selectTrainDormAuthList` | `selectTrainDormAuthList` | `train_dorm_auth` | `train_dorm_auth, train_student, train_accommodation, train_dormitory_info` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/access/auth/{authId}` | `GET` | `Train` | `TrainAccessManagementController.getAuthDetail` | `TrainAccessManagementService.getAuthDetail` | `TrainDormAuthMapper.selectTrainDormAuthById` | `selectTrainDormAuthById` | `train_dorm_auth` | `train_dorm_auth, train_student, train_accommodation` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/access/auth/{authId}/issue` | `POST` | `Train` | `TrainAccessManagementController.issueCredential` | `TrainAccessManagementService.issueCredential` | `DmsAccessManagementService.issueCredential` | `insertTrainExternalOperation` | `train_dorm_auth` | `train_dorm_auth, train_external_resource_mapping` | `train_dorm_auth, train_external_operation` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BLOCKED_EXTERNAL)` | +| `/train/access/auth/{authId}/revoke` | `POST` | `Train` | `TrainAccessManagementController.revokeAuth` | `TrainAccessManagementService.revokeAuth` | `DmsAccessManagementService.revokeAuth` | `insertTrainExternalOperation` | `train_dorm_auth` | `train_dorm_auth, train_external_resource_mapping` | `train_dorm_auth, train_external_operation` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BLOCKED_EXTERNAL)` | +| `/train/access/capabilities` | `GET` | `Train` | `TrainAccessManagementController.getCapabilities` | `TrainAccessManagementService.getCapabilities` | `DmsAccessManagementService.getCapabilities` | `NONE` | `sys_config` | `sys_config` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/access/mapping/bind-lock` | `POST` | `Train` | `TrainAccessManagementController.bindLockMapping` | `TrainAccessManagementService.bindLockMapping` | `TrainExternalResourceMappingMapper.insertTrainExternalResourceMapping` | `insertTrainExternalResourceMapping` | `train_external_resource_mapping` | `train_external_resource_mapping` | `train_external_resource_mapping` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/access/mapping/bind-room` | `POST` | `Train` | `TrainAccessManagementController.bindRoomMapping` | `TrainAccessManagementService.bindRoomMapping` | `TrainExternalResourceMappingMapper.insertTrainExternalResourceMapping` | `insertTrainExternalResourceMapping` | `train_external_resource_mapping` | `train_external_resource_mapping` | `train_external_resource_mapping` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/access/mapping/dorm/{dormId}` | `GET` | `Train` | `TrainAccessManagementController.getDormMapping` | `TrainAccessManagementService.getDormMapping` | `TrainExternalResourceMappingMapper.selectByDormId` | `selectByDormId` | `train_external_resource_mapping` | `train_external_resource_mapping, train_dormitory_info` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/access/mapping/{mappingId}` | `DELETE` | `Train` | `TrainAccessManagementController.unbindMapping` | `TrainAccessManagementService.unbindMapping` | `TrainExternalResourceMappingMapper.deleteById` | `deleteById` | `train_external_resource_mapping` | `train_external_resource_mapping` | `train_external_resource_mapping` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/access/operations/{doorAuthId}` | `GET` | `Train` | `TrainAccessManagementController.getOperationHistory` | `TrainAccessManagementService.getOperationHistory` | `TrainExternalOperationMapper.selectByDoorAuthId` | `selectByDoorAuthId` | `train_external_operation` | `train_external_operation` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/access/reconcile/{enrollmentId}` | `POST` | `Train` | `TrainAccessManagementController.manualReconcile` | `TrainAccessManagementService.reconcileEnrollment` | `TrainDormAuthMapper.updateTrainDormAuth` | `updateTrainDormAuth` | `train_dorm_auth` | `train_dorm_auth, train_enrollment` | `train_dorm_auth` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/accommodation` | `POST` | `Train` | `TrainAccommodationController.add` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/accommodation` | `PUT` | `Train` | `TrainAccommodationController.edit` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/accommodation/enrollment/{enrollmentId}` | `GET` | `Train` | `TrainAccommodationController.getByEnrollmentId` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/accommodation/enrollment/{enrollmentId}/history` | `GET` | `Train` | `TrainAccommodationController.getHistoryByEnrollmentId` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/accommodation/list` | `GET` | `Train` | `TrainAccommodationController.list` | `TrainAccommodationService.list` | `TrainAccommodationMapper.selectTrainAccommodationList` | `selectTrainAccommodationList` | `train_accommodation` | `train_accommodation, train_dormitory_info, train_student, train_class_plan` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/accommodation/{accommodationIds}` | `DELETE` | `Train` | `TrainAccommodationController.remove` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/accommodation/{accommodationId}` | `GET` | `Train` | `TrainAccommodationController.getInfo` | `TrainAccommodationService.getInfo` | `TrainAccommodationMapper.selectTrainAccommodationById` | `selectTrainAccommodationById` | `train_accommodation` | `train_accommodation` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/accommodation/{accommodationId}/checkin` | `POST` | `Train` | `TrainAccommodationController.checkIn` | `TrainAccommodationService.checkIn` | `TrainAccommodationMapper.updateTrainAccommodation` | `updateTrainAccommodation` | `train_accommodation` | `train_accommodation` | `train_accommodation` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/accommodation/{accommodationId}/checkout` | `POST` | `Train` | `TrainAccommodationController.checkOut` | `TrainAccommodationService.checkOut` | `TrainAccommodationMapper.updateTrainAccommodation` | `updateTrainAccommodation` | `train_accommodation` | `train_accommodation, train_dormitory_info` | `train_accommodation, train_dormitory_info` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/accommodation/{accommodationId}/transfer` | `POST` | `Train` | `TrainAccommodationController.transfer` | `TrainAccommodationService.transfer` | `TrainAccommodationMapper.updateTrainAccommodation` | `updateTrainAccommodation` | `train_accommodation` | `train_accommodation, train_dormitory_info` | `train_accommodation, train_dormitory_info` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/analysis/attendance/charts` | `GET` | `Train` | `TrainAnalysisController.getAttendanceCharts` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/analysis/attendance/summary` | `GET` | `Train` | `TrainAnalysisController.getAttendanceSummary` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/analysis/dashboard/charts` | `GET` | `Train` | `TrainAnalysisController.getDashboardCharts` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/analysis/dashboard/overview` | `GET` | `Train` | `TrainAnalysisController.getDashboardOverview` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/analysis/portrait/student-360/{studentId}` | `GET` | `Train` | `TrainAnalysisController.getStudent360` | `ITrainAnalysisService.getStudent360` | `TrainAnalysisMapper.countStudentScores` | `countStudentScores` | `train_student_score` | `train_student, train_enrollment, train_student_score` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/attendance/lessons/{lessonId}/roster` | `GET` | `Train` | `AttendanceController.getLessonRoster` | `TrainAttendanceService.getLessonRoster` | `TrainStudentCheckinMapper.selectRosterByLessonId` | `selectRosterByLessonId` | `train_student_checkin` | `train_student_checkin, train_enrollment, train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/attendance/mark-absence` | `POST` | `Train` | `AttendanceController.markAbsence` | `TrainAttendanceService.markAbsence` | `TrainStudentCheckinMapper.insertStudentCheckin` | `insertStudentCheckin` | `train_student_checkin` | `train_student_checkin, train_lesson, train_enrollment` | `train_student_checkin` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/attendance/sign-in` | `POST` | `Train` | `AttendanceController.signIn` | `TrainAttendanceService.signIn` | `TrainStudentCheckinMapper.insertStudentCheckin` | `insertStudentCheckin` | `train_student_checkin` | `train_student_checkin, train_lesson, train_enrollment` | `train_student_checkin` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/attendance/sign-out` | `POST` | `Train` | `AttendanceController.signOut` | `TrainAttendanceService.signOut` | `TrainStudentCheckinMapper.updateStudentCheckin` | `updateStudentCheckin` | `train_student_checkin` | `train_student_checkin, train_lesson` | `train_student_checkin` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/attendance/{checkinId}` | `PUT` | `Train` | `AttendanceController.adjust` | `TrainAttendanceService.adjust` | `TrainStudentCheckinMapper.updateStudentCheckin` | `updateStudentCheckin` | `train_student_checkin` | `train_student_checkin` | `train_student_checkin` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/board` | `POST` | `Train` | `TrainElectronicBoardController.add` | `ITrainElectronicBoardService.insertTrainElectronicBoard` | `TrainElectronicBoardMapper.insertTrainElectronicBoard` | `insertTrainElectronicBoard` | `train_electronic_board` | `NONE` | `train_electronic_board` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/board` | `PUT` | `Train` | `TrainElectronicBoardController.edit` | `ITrainElectronicBoardService.updateTrainElectronicBoard` | `TrainElectronicBoardMapper.updateTrainElectronicBoard` | `updateTrainElectronicBoard` | `train_electronic_board` | `train_electronic_board` | `train_electronic_board` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/board/export` | `POST` | `Train` | `TrainElectronicBoardController.export` | `ITrainElectronicBoardService.selectTrainElectronicBoardList` | `TrainElectronicBoardMapper.selectTrainElectronicBoardList` | `selectTrainElectronicBoardList` | `train_electronic_board` | `train_electronic_board` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/board/list` | `GET` | `Train` | `TrainElectronicBoardController.list` | `ITrainElectronicBoardService.selectTrainElectronicBoardList` | `TrainElectronicBoardMapper.selectTrainElectronicBoardList` | `selectTrainElectronicBoardList` | `train_electronic_board` | `train_electronic_board` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/board/{boardIds}` | `DELETE` | `Train` | `TrainElectronicBoardController.remove` | `ITrainElectronicBoardService.deleteTrainElectronicBoardByBoardIds` | `TrainElectronicBoardMapper.deleteTrainElectronicBoardByBoardIds` | `deleteTrainElectronicBoardByBoardIds` | `train_electronic_board` | `train_electronic_board` | `train_electronic_board` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/board/{boardId}` | `GET` | `Train` | `TrainElectronicBoardController.getInfo` | `ITrainElectronicBoardService.selectTrainElectronicBoardByBoardId` | `TrainElectronicBoardMapper.selectTrainElectronicBoardByBoardId` | `selectTrainElectronicBoardByBoardId` | `train_electronic_board` | `train_electronic_board` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/checkin/export` | `POST` | `Train` | `TrainStudentCheckinController.export` | `ITrainStudentCheckinService.selectTrainStudentCheckinList` | `TrainStudentCheckinMapper.selectTrainStudentCheckinList` | `selectTrainStudentCheckinList` | `train_student_checkin` | `train_student_checkin` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/checkin/list` | `GET` | `Train` | `TrainStudentCheckinController.list` | `ITrainStudentCheckinService.selectTrainStudentCheckinList` | `TrainStudentCheckinMapper.selectTrainStudentCheckinList` | `selectTrainStudentCheckinList` | `train_student_checkin` | `train_student_checkin` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/checkin/{checkinId}` | `GET` | `Train` | `TrainStudentCheckinController.getInfo` | `ITrainStudentCheckinService.selectTrainStudentCheckinByCheckinId` | `TrainStudentCheckinMapper.selectTrainStudentCheckinByCheckinId` | `selectTrainStudentCheckinByCheckinId` | `train_student_checkin` | `train_student_checkin` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/classroom` | `POST` | `Train` | `TrainClassroomInfoController.add` | `ITrainClassroomInfoService.insertTrainClassroomInfo` | `TrainClassroomInfoMapper.insertTrainClassroomInfo` | `insertTrainClassroomInfo` | `train_classroom_info` | `NONE` | `train_classroom_info` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/classroom/list` | `GET` | `Train` | `TrainClassroomInfoController.list` | `ITrainClassroomInfoService.selectTrainClassroomInfoList` | `TrainClassroomInfoMapper.selectTrainClassroomInfoList` | `selectTrainClassroomInfoList` | `train_classroom_info` | `train_classroom_info` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/classroom/{classRoomIds}` | `DELETE` | `Train` | `TrainClassroomInfoController.remove` | `ITrainClassroomInfoService.deleteTrainClassroomInfoByClassRoomIds` | `TrainClassroomInfoMapper.deleteTrainClassroomInfoByClassRoomIds` | `deleteTrainClassroomInfoByClassRoomIds` | `train_classroom_info` | `train_classroom_info` | `train_classroom_info` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/classroom/{classRoomId}` | `GET` | `Train` | `TrainClassroomInfoController.getInfo` | `ITrainClassroomInfoService.selectTrainClassroomInfoByClassRoomId` | `TrainClassroomInfoMapper.selectTrainClassroomInfoByClassRoomId` | `selectTrainClassroomInfoByClassRoomId` | `train_classroom_info` | `train_classroom_info` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/classroom/{classRoomId}` | `PUT` | `Train` | `TrainClassroomInfoController.edit` | `ITrainClassroomInfoService.updateTrainClassroomInfo` | `TrainClassroomInfoMapper.updateTrainClassroomInfo` | `updateTrainClassroomInfo` | `train_classroom_info` | `train_classroom_info` | `train_classroom_info` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/course` | `POST` | `Train` | `TrainCourseController.add` | `ITrainCourseService.insertTrainCourse` | `TrainCourseMapper.insertTrainCourse` | `insertTrainCourse` | `train_course` | `NONE` | `train_course` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/course/list` | `GET` | `Train` | `TrainCourseController.list` | `ITrainCourseService.selectTrainCourseList` | `TrainCourseMapper.selectTrainCourseList` | `selectTrainCourseList` | `train_course` | `train_course` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/course/{cIds}` | `DELETE` | `Train` | `TrainCourseController.remove` | `ITrainCourseService.deleteTrainCourseByCIds` | `TrainCourseMapper.deleteTrainCourseByCIds` | `deleteTrainCourseByCIds` | `train_course` | `train_course` | `train_course` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/course/{cId}` | `GET` | `Train` | `TrainCourseController.getInfo` | `ITrainCourseService.selectTrainCourseByCId` | `TrainCourseMapper.selectTrainCourseByCId` | `selectTrainCourseByCId` | `train_course` | `train_course` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/course/{cId}` | `PUT` | `Train` | `TrainCourseController.edit` | `ITrainCourseService.updateTrainCourse` | `TrainCourseMapper.updateTrainCourse` | `updateTrainCourse` | `train_course` | `train_course` | `train_course` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/dormitory` | `POST` | `Train` | `TrainDormitoryInfoController.add` | `ITrainDormitoryInfoService.insertTrainDormitoryInfo` | `TrainDormitoryInfoMapper.insertTrainDormitoryInfo` | `insertTrainDormitoryInfo` | `train_dormitory_info` | `NONE` | `train_dormitory_info` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/dormitory/list` | `GET` | `Train` | `TrainDormitoryInfoController.list` | `ITrainDormitoryInfoService.selectTrainDormitoryInfoList` | `TrainDormitoryInfoMapper.selectTrainDormitoryInfoList` | `selectTrainDormitoryInfoList` | `train_dormitory_info` | `train_dormitory_info` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/dormitory/options` | `GET` | `Train` | `TrainDormitoryInfoController.options` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/dormitory/{dormId}` | `GET` | `Train` | `TrainDormitoryInfoController.getInfo` | `ITrainDormitoryInfoService.selectTrainDormitoryInfoByDormId` | `TrainDormitoryInfoMapper.selectTrainDormitoryInfoByDormId` | `selectTrainDormitoryInfoByDormId` | `train_dormitory_info` | `train_dormitory_info` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/duration/export` | `POST` | `Train` | `TrainStudyDurationController.export` | `ITrainStudyDurationService.selectTrainStudyDurationList` | `TrainStudyDurationMapper.selectTrainStudyDurationList` | `selectTrainStudyDurationList` | `train_study_duration` | `train_study_duration` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/duration/list` | `GET` | `Train` | `TrainStudyDurationController.list` | `ITrainStudyDurationService.selectTrainStudyDurationList` | `TrainStudyDurationMapper.selectTrainStudyDurationList` | `selectTrainStudyDurationList` | `train_study_duration` | `train_study_duration` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/duration/{durId}` | `GET` | `Train` | `TrainStudyDurationController.getInfo` | `ITrainStudyDurationService.selectTrainStudyDurationByDurId` | `TrainStudyDurationMapper.selectTrainStudyDurationByDurId` | `selectTrainStudyDurationByDurId` | `train_study_duration` | `train_study_duration` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/enrollments/{enrollmentId}` | `DELETE` | `Train` | `TrainEnrollmentController.removeEnrollment` | `ITrainEnrollmentService.deleteEnrollmentById` | `TrainEnrollmentMapper.deleteEnrollmentById` | `deleteEnrollmentById` | `train_enrollment` | `train_enrollment` | `train_enrollment` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/enrollments/{enrollmentId}` | `GET` | `Train` | `TrainEnrollmentController.getDetail` | `ITrainEnrollmentService.selectEnrollmentById` | `TrainEnrollmentMapper.selectEnrollmentById` | `selectEnrollmentById` | `train_enrollment` | `train_enrollment, train_student, train_class_plan` | `NONE` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/enrollments/{enrollmentId}/report` | `POST` | `Train` | `TrainReportActionController.confirmReport` | `TrainClassReportService.confirmReport` | `TrainClassReportMapper.updateTrainClassReport` | `updateTrainClassReport` | `train_class_report` | `train_class_report, train_enrollment` | `train_class_report, train_enrollment` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/enrollments/{enrollmentId}/status` | `PUT` | `Train` | `TrainEnrollmentController.updateStatus` | `ITrainEnrollmentService.updateEnrollmentStatus` | `TrainEnrollmentMapper.updateEnrollmentStatus` | `updateEnrollmentStatus` | `train_enrollment` | `train_enrollment` | `train_enrollment` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/lessons/check-conflict` | `POST` | `Train` | `TrainLessonController.checkConflict` | `LessonAppService.checkConflict` | `TrainLessonMapper.checkConflict` | `checkConflict` | `train_lesson` | `train_lesson` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/lessons/scheduling-options` | `GET` | `Train` | `TrainLessonController.getSchedulingOptions` | `LessonAppService.getSchedulingOptions` | `TrainLessonMapper.selectOptions` | `selectOptions` | `train_lesson` | `train_classroom_info, train_teacher` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/lessons/{lessonId}` | `DELETE` | `Train` | `TrainLessonController.removeLesson` | `LessonAppService.deleteLesson` | `TrainLessonMapper.deleteTrainLessonById` | `deleteTrainLessonById` | `train_lesson` | `train_lesson` | `train_lesson` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/lessons/{lessonId}` | `PUT` | `Train` | `TrainLessonController.editLesson` | `LessonAppService.updateLesson` | `TrainLessonMapper.updateTrainLesson` | `updateTrainLesson` | `train_lesson` | `train_lesson` | `train_lesson` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/me/student` | `GET` | `Train` | `TrainStudentMeController.getStudentProfile` | `ITrainingIdentityService.assertStudentIdentity` | `TrainStudentMapper.selectTrainStudentBySId` | `selectTrainStudentBySId` | `train_student` | `train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/me/student/attendance` | `GET` | `Train` | `TrainStudentMeController.getMyAttendance` | `ITrainingIdentityService.assertStudentIdentity` | `TrainStudentCheckinMapper.selectCheckinsByStudentId` | `selectCheckinsByStudentId` | `train_student_checkin` | `train_student_checkin, train_lesson, train_enrollment` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/me/student/duration` | `GET` | `Train` | `TrainStudentMeController.getMyDuration` | `ITrainingIdentityService.assertStudentIdentity` | `TrainStudyDurationMapper.selectDurationsByStudentId` | `selectDurationsByStudentId` | `train_study_duration` | `train_study_duration, train_course` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/me/student/enrollments` | `GET` | `Train` | `TrainStudentMeController.getMyEnrollments` | `ITrainingIdentityService.assertStudentIdentity` | `TrainEnrollmentMapper.selectEnrollmentsByStudentId` | `selectEnrollmentsByStudentId` | `train_enrollment` | `train_enrollment, train_class_plan` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/me/student/portrait` | `GET` | `Train` | `TrainStudentMeController.getMyPortrait` | `ITrainingIdentityService.assertStudentIdentity` | `TrainStudentPortraitMapper.selectPortraitByStudentId` | `selectPortraitByStudentId` | `train_student_portrait` | `train_student_portrait, train_enrollment, train_class_plan, train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/me/student/report` | `GET` | `Train` | `TrainStudentMeController.getMyReport` | `ITrainingIdentityService.assertStudentIdentity` | `TrainClassReportMapper.selectLatestReportByStudentId` | `selectLatestReportByStudentId` | `train_class_report` | `train_class_report, train_enrollment, train_class_plan` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/me/student/timetable` | `GET` | `Train` | `TrainStudentMeController.getMyTimetable` | `ITrainingIdentityService.assertStudentIdentity` | `TrainLessonMapper.selectScheduleLessonsByStudentId` | `selectScheduleLessonsByStudentId` | `train_lesson` | `train_lesson, train_plan_course, train_course, train_classroom_info, train_training_site` | `NONE` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/me/teacher` | `GET` | `Train` | `TrainTeacherMeController.getTeacherProfile` | `ITrainingIdentityService.assertTeacherIdentity` | `TrainTeacherMapper.selectTrainTeacherByTId` | `selectTrainTeacherByTId` | `train_teacher` | `train_teacher` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/me/teacher/lessons` | `GET` | `Train` | `TrainTeacherMeController.getMyTimetable` | `ITrainingIdentityService.assertTeacherIdentity` | `TrainLessonMapper.selectScheduleLessonsByTeacherId` | `selectScheduleLessonsByTeacherId` | `train_lesson` | `train_lesson, train_plan_course, train_course` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/me/teacher/lessons/{lessonId}/attendance` | `GET` | `Train` | `TrainTeacherMeController.getLessonAttendance` | `ITrainingIdentityService.assertTeacherIdentity` | `TrainStudentCheckinMapper.selectCheckinsByLessonId` | `selectCheckinsByLessonId` | `train_student_checkin` | `train_lesson, train_student_checkin` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/me/teacher/plans` | `GET` | `Train` | `TrainTeacherMeController.getMyPlans` | `ITrainingIdentityService.assertTeacherIdentity` | `TrainLessonMapper.selectPlansByTeacherId` | `selectPlansByTeacherId` | `train_class_plan` | `train_class_plan, train_plan_course, train_lesson` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/me/teacher/students` | `GET` | `Train` | `TrainTeacherMeController.getMyClassStudents` | `ITrainingIdentityService.assertTeacherIdentity` | `TrainEnrollmentMapper.selectEnrollmentItemList` | `selectEnrollmentItemList` | `train_enrollment` | `train_enrollment, train_lesson, train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/me/teacher/timetable` | `GET` | `Train` | `TrainTeacherMeController.getMyTimetable` | `ITrainingIdentityService.assertTeacherIdentity` | `TrainLessonMapper.selectScheduleLessonsByTeacherId` | `selectScheduleLessonsByTeacherId` | `train_lesson` | `train_lesson, train_plan_course, train_course` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/plans` | `GET` | `Train` | `TrainPlanController.list` | `PlanAppService.selectPlanItemList` | `TrainClassPlanMapper.selectTrainClassPlanList` | `selectTrainClassPlanList` | `train_class_plan` | `train_class_plan, train_plan_course, train_enrollment` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/plans` | `POST` | `Train` | `TrainPlanController.add` | `PlanAppService.createPlan` | `TrainClassPlanMapper.insertTrainClassPlan` | `insertTrainClassPlan` | `train_class_plan` | `NONE` | `train_class_plan` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/plans/{planId}` | `GET` | `Train` | `TrainPlanController.getInfo` | `PlanAppService.selectPlanById` | `TrainClassPlanMapper.selectTrainClassPlanByPlanId` | `selectTrainClassPlanByPlanId` | `train_class_plan` | `train_class_plan` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/plans/{planId}` | `PUT` | `Train` | `TrainPlanController.edit` | `PlanAppService.updatePlan` | `TrainClassPlanMapper.updateTrainClassPlan` | `updateTrainClassPlan` | `train_class_plan` | `train_class_plan` | `train_class_plan` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/plans/{planId}/complete` | `POST` | `Train` | `TrainPlanController.complete` | `PlanAppService.completePlan` | `TrainClassPlanMapper.updateTrainClassPlan` | `updateTrainClassPlan` | `train_class_plan` | `train_class_plan, train_enrollment` | `train_class_plan, train_enrollment` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/plans/{planId}/courses` | `GET` | `Train` | `TrainPlanCourseController.list` | `PlanAppService.selectPlanCourses` | `TrainPlanCourseMapper.selectPlanCourseList` | `selectPlanCourseList` | `train_plan_course` | `train_plan_course, train_course, train_teacher` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/plans/{planId}/courses` | `POST` | `Train` | `TrainPlanCourseController.add` | `PlanAppService.addPlanCourse` | `TrainPlanCourseMapper.insertTrainPlanCourse` | `insertTrainPlanCourse` | `train_plan_course` | `train_class_plan` | `train_plan_course` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/plans/{planId}/courses/{planCourseId}` | `DELETE` | `Train` | `TrainPlanCourseController.remove` | `PlanAppService.removePlanCourse` | `TrainPlanCourseMapper.deleteTrainPlanCourseById` | `deleteTrainPlanCourseById` | `train_plan_course` | `train_plan_course` | `train_plan_course` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/plans/{planId}/enrollments` | `GET` | `Train` | `TrainEnrollmentController.listEnrollments` | `ITrainEnrollmentService.selectEnrollmentList` | `TrainEnrollmentMapper.selectEnrollmentList` | `selectEnrollmentList` | `train_enrollment` | `train_enrollment, train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/plans/{planId}/enrollments` | `POST` | `Train` | `TrainEnrollmentController.addEnrollment` | `ITrainEnrollmentService.insertEnrollment` | `TrainEnrollmentMapper.insertEnrollment` | `insertEnrollment` | `train_enrollment` | `train_class_plan, train_enrollment` | `train_enrollment` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/plans/{planId}/enrollments/batch` | `POST` | `Train` | `TrainEnrollmentController.batchImportEnrollments` | `ITrainEnrollmentService.batchInsertEnrollments` | `TrainEnrollmentMapper.batchInsertEnrollments` | `batchInsertEnrollments` | `train_enrollment` | `train_class_plan` | `train_enrollment` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/plans/{planId}/lessons` | `POST` | `Train` | `TrainLessonController.addLesson` | `LessonAppService.createLesson` | `TrainLessonMapper.insertTrainLesson` | `insertTrainLesson` | `train_lesson` | `train_class_plan, train_plan_course, train_lesson` | `train_lesson` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/plans/{planId}/timetable` | `GET` | `Train` | `TrainLessonController.getTimetable` | `LessonAppService.getTimetable` | `TrainLessonMapper.selectTimetableByPlanId` | `selectTimetableByPlanId` | `train_lesson` | `train_lesson, train_plan_course, train_course` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/portrait/export` | `POST` | `Train` | `TrainStudentPortraitController.export` | `ITrainStudentPortraitService.selectTrainStudentPortraitList` | `TrainStudentPortraitMapper.selectTrainStudentPortraitList` | `selectTrainStudentPortraitList` | `train_student_portrait` | `train_student_portrait` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/portrait/list` | `GET` | `Train` | `TrainStudentPortraitController.list` | `ITrainStudentPortraitService.selectTrainStudentPortraitList` | `TrainStudentPortraitMapper.selectTrainStudentPortraitList` | `selectTrainStudentPortraitList` | `train_student_portrait` | `train_student_portrait` | `NONE` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/portrait/{portraitId}` | `GET` | `Train` | `TrainStudentPortraitController.getInfo` | `ITrainStudentPortraitService.selectTrainStudentPortraitByPortraitId` | `TrainStudentPortraitMapper.selectTrainStudentPortraitByPortraitId` | `selectTrainStudentPortraitByPortraitId` | `train_student_portrait` | `train_student_portrait` | `NONE` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/report/export` | `POST` | `Train` | `TrainClassReportController.export` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `UNKNOWN` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/report/list` | `GET` | `Train` | `TrainClassReportController.list` | `ITrainClassReportService.selectTrainClassReportList` | `TrainClassReportMapper.selectTrainClassReportList` | `selectTrainClassReportList` | `train_class_report` | `train_class_report` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/report/{reportId}` | `GET` | `Train` | `TrainClassReportController.getInfo` | `ITrainClassReportService.selectTrainClassReportById` | `TrainClassReportMapper.selectTrainClassReportById` | `selectTrainClassReportById` | `train_class_report` | `train_class_report` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/score` | `POST` | `Train` | `TrainScoreInfoController.add` | `ITrainScoreInfoService.insertTrainScoreInfo` | `TrainScoreInfoMapper.insertStudentScoreRow` | `insertStudentScoreRow` | `train_student_score` | `train_enrollment, train_score_item` | `train_student_score, train_score_item, train_enrollment` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/score` | `PUT` | `Train` | `TrainScoreInfoController.edit` | `ITrainScoreInfoService.updateTrainScoreInfo` | `TrainScoreInfoMapper.updateStudentScoreRow` | `updateStudentScoreRow` | `train_student_score` | `train_student_score, train_enrollment, train_score_item` | `train_student_score, train_score_item, train_enrollment` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/score/export` | `POST` | `Train` | `TrainScoreInfoController.export` | `ITrainScoreInfoService.selectTrainScoreInfoList` | `TrainScoreInfoMapper.selectTrainScoreInfoList` | `selectTrainScoreInfoList` | `train_student_score` | `train_student_score, train_score_item, train_enrollment, train_plan_course` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/score/list` | `GET` | `Train` | `TrainScoreInfoController.list` | `ITrainScoreInfoService.selectTrainScoreInfoList` | `TrainScoreInfoMapper.selectTrainScoreInfoList` | `selectTrainScoreInfoList` | `train_student_score` | `train_student_score, train_score_item, train_enrollment, train_plan_course` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/score/{scoreIds}` | `DELETE` | `Train` | `TrainScoreInfoController.remove` | `ITrainScoreInfoService.deleteTrainScoreInfoByScoreIds` | `TrainScoreInfoMapper.deleteTrainScoreInfoByScoreIds` | `deleteTrainScoreInfoByScoreIds` | `train_student_score` | `train_student_score` | `train_student_score` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/score/{scoreId}` | `GET` | `Train` | `TrainScoreInfoController.getInfo` | `ITrainScoreInfoService.selectTrainScoreInfoByScoreId` | `TrainScoreInfoMapper.selectTrainScoreInfoByScoreId` | `selectTrainScoreInfoByScoreId` | `train_student_score` | `train_student_score, train_score_item, train_enrollment, train_plan_course` | `NONE` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/scoreinfo` | `POST` | `Train` | `TrainScoreInfoController.add` | `ITrainScoreInfoService.insertTrainScoreInfo` | `TrainScoreInfoMapper.insertStudentScoreRow` | `insertStudentScoreRow` | `train_student_score` | `train_enrollment, train_score_item` | `train_student_score, train_score_item, train_enrollment` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/scoreinfo` | `PUT` | `Train` | `TrainScoreInfoController.edit` | `ITrainScoreInfoService.updateTrainScoreInfo` | `TrainScoreInfoMapper.updateStudentScoreRow` | `updateStudentScoreRow` | `train_student_score` | `train_student_score, train_enrollment, train_score_item` | `train_student_score, train_score_item, train_enrollment` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/scoreinfo/export` | `POST` | `Train` | `TrainScoreInfoController.export` | `ITrainScoreInfoService.selectTrainScoreInfoList` | `TrainScoreInfoMapper.selectTrainScoreInfoList` | `selectTrainScoreInfoList` | `train_student_score` | `train_student_score, train_score_item, train_enrollment, train_plan_course` | `NONE` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/scoreinfo/list` | `GET` | `Train` | `TrainScoreInfoController.list` | `ITrainScoreInfoService.selectTrainScoreInfoList` | `TrainScoreInfoMapper.selectTrainScoreInfoList` | `selectTrainScoreInfoList` | `train_student_score` | `train_student_score, train_score_item, train_enrollment, train_plan_course` | `NONE` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/scoreinfo/{scoreIds}` | `DELETE` | `Train` | `TrainScoreInfoController.remove` | `ITrainScoreInfoService.deleteTrainScoreInfoByScoreIds` | `TrainScoreInfoMapper.deleteTrainScoreInfoByScoreIds` | `deleteTrainScoreInfoByScoreIds` | `train_student_score` | `train_student_score` | `train_student_score` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/scoreinfo/{scoreId}` | `GET` | `Train` | `TrainScoreInfoController.getInfo` | `ITrainScoreInfoService.selectTrainScoreInfoByScoreId` | `TrainScoreInfoMapper.selectTrainScoreInfoByScoreId` | `selectTrainScoreInfoByScoreId` | `train_student_score` | `train_student_score, train_score_item, train_enrollment, train_plan_course` | `NONE` | `VERIFIED_FROM_SOURCE` | `DEFINED_IN_UI_API_ONLY` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/student` | `POST` | `Train` | `TrainStudentController.add` | `ITrainStudentService.insertTrainStudent` | `TrainStudentMapper.insertTrainStudent` | `insertTrainStudent` | `train_student` | `NONE` | `train_student` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/student/bindable-users` | `GET` | `Train` | `TrainAccountBindingAdminController.getStudentBindableUsers` | `TrainAccountBindingAdminService.selectBindableUsers` | `SysUserMapper.selectUserList` | `selectUserList` | `train_student` | `sys_user, train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/student/list` | `GET` | `Train` | `TrainStudentController.list` | `ITrainStudentService.selectTrainStudentList` | `TrainStudentMapper.selectTrainStudentList` | `selectTrainStudentList` | `train_student` | `train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/student/{sId}` | `DELETE` | `Train` | `TrainStudentController.remove` | `ITrainStudentService.deleteTrainStudentBySId` | `TrainStudentMapper.deleteTrainStudentBySId` | `deleteTrainStudentBySId` | `train_student` | `train_student` | `train_student` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/student/{sId}` | `GET` | `Train` | `TrainStudentController.getInfo` | `ITrainStudentService.selectTrainStudentBySId` | `TrainStudentMapper.selectTrainStudentBySId` | `selectTrainStudentBySId` | `train_student` | `train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/student/{sId}/user-binding` | `DELETE` | `Train` | `TrainAccountBindingAdminController.unbindStudentUser` | `TrainAccountBindingAdminService.unbindStudentAccount` | `TrainStudentMapper.updateTrainStudent` | `updateTrainStudent` | `train_student` | `train_student` | `train_student` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/student/{sId}/user-binding` | `POST` | `Train` | `TrainAccountBindingAdminController.bindStudentUser` | `TrainAccountBindingAdminService.bindStudentAccount` | `TrainStudentMapper.updateTrainStudent` | `updateTrainStudent` | `train_student` | `sys_user, train_student` | `train_student` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/student/{sId}/user-binding` | `PUT` | `Train` | `TrainAccountBindingAdminController.bindStudentUser` | `TrainAccountBindingAdminService.bindStudentAccount` | `TrainStudentMapper.updateTrainStudent` | `updateTrainStudent` | `train_student` | `sys_user, train_student` | `train_student` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/students` | `POST` | `Train` | `TrainStudentController.add` | `ITrainStudentService.insertTrainStudent` | `TrainStudentMapper.insertTrainStudent` | `insertTrainStudent` | `train_student` | `NONE` | `train_student` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/students/bindable-users` | `GET` | `Train` | `TrainAccountBindingAdminController.getStudentBindableUsers` | `TrainAccountBindingAdminService.selectBindableUsers` | `SysUserMapper.selectUserList` | `selectUserList` | `train_student` | `sys_user, train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/students/list` | `GET` | `Train` | `TrainStudentController.list` | `ITrainStudentService.selectTrainStudentList` | `TrainStudentMapper.selectTrainStudentList` | `selectTrainStudentList` | `train_student` | `train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/students/{sId}` | `DELETE` | `Train` | `TrainStudentController.remove` | `ITrainStudentService.deleteTrainStudentBySId` | `TrainStudentMapper.deleteTrainStudentBySId` | `deleteTrainStudentBySId` | `train_student` | `train_student` | `train_student` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/students/{sId}` | `GET` | `Train` | `TrainStudentController.getInfo` | `ITrainStudentService.selectTrainStudentBySId` | `TrainStudentMapper.selectTrainStudentBySId` | `selectTrainStudentBySId` | `train_student` | `train_student` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/students/{sId}/user-binding` | `DELETE` | `Train` | `TrainAccountBindingAdminController.unbindStudentUser` | `TrainAccountBindingAdminService.unbindStudentAccount` | `TrainStudentMapper.updateTrainStudent` | `updateTrainStudent` | `train_student` | `train_student` | `train_student` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/students/{sId}/user-binding` | `POST` | `Train` | `TrainAccountBindingAdminController.bindStudentUser` | `TrainAccountBindingAdminService.bindStudentAccount` | `TrainStudentMapper.updateTrainStudent` | `updateTrainStudent` | `train_student` | `sys_user, train_student` | `train_student` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/students/{sId}/user-binding` | `PUT` | `Train` | `TrainAccountBindingAdminController.bindStudentUser` | `TrainAccountBindingAdminService.bindStudentAccount` | `TrainStudentMapper.updateTrainStudent` | `updateTrainStudent` | `train_student` | `sys_user, train_student` | `train_student` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/teacher` | `POST` | `Train` | `TrainTeacherController.add` | `ITrainTeacherService.insertTrainTeacher` | `TrainTeacherMapper.insertTrainTeacher` | `insertTrainTeacher` | `train_teacher` | `NONE` | `train_teacher` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/teacher/bindable-users` | `GET` | `Train` | `TrainAccountBindingAdminController.getTeacherBindableUsers` | `TrainAccountBindingAdminService.selectBindableUsers` | `SysUserMapper.selectUserList` | `selectUserList` | `train_teacher` | `sys_user, train_teacher` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/teacher/list` | `GET` | `Train` | `TrainTeacherController.list` | `ITrainTeacherService.selectTrainTeacherList` | `TrainTeacherMapper.selectTrainTeacherList` | `selectTrainTeacherList` | `train_teacher` | `train_teacher` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/teacher/{tId}` | `DELETE` | `Train` | `TrainTeacherController.remove` | `ITrainTeacherService.deleteTrainTeacherByTId` | `TrainTeacherMapper.deleteTrainTeacherByTId` | `deleteTrainTeacherByTId` | `train_teacher` | `train_teacher` | `train_teacher` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/teacher/{tId}` | `GET` | `Train` | `TrainTeacherController.getInfo` | `ITrainTeacherService.selectTrainTeacherByTId` | `TrainTeacherMapper.selectTrainTeacherByTId` | `selectTrainTeacherByTId` | `train_teacher` | `train_teacher` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/teacher/{tId}` | `PUT` | `Train` | `TrainTeacherController.edit` | `ITrainTeacherService.updateTrainTeacher` | `TrainTeacherMapper.updateTrainTeacher` | `updateTrainTeacher` | `train_teacher` | `train_teacher` | `train_teacher` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/teacher/{tId}/user-binding` | `DELETE` | `Train` | `TrainAccountBindingAdminController.unbindTeacherUser` | `TrainAccountBindingAdminService.unbindTeacherAccount` | `TrainTeacherMapper.updateTrainTeacher` | `updateTrainTeacher` | `train_teacher` | `train_teacher` | `train_teacher` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/teacher/{tId}/user-binding` | `POST` | `Train` | `TrainAccountBindingAdminController.bindTeacherUser` | `TrainAccountBindingAdminService.bindTeacherAccount` | `TrainTeacherMapper.updateTrainTeacher` | `updateTrainTeacher` | `train_teacher` | `sys_user, train_teacher` | `train_teacher` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/teacher/{tId}/user-binding` | `PUT` | `Train` | `TrainAccountBindingAdminController.bindTeacherUser` | `TrainAccountBindingAdminService.bindTeacherAccount` | `TrainTeacherMapper.updateTrainTeacher` | `updateTrainTeacher` | `train_teacher` | `sys_user, train_teacher` | `train_teacher` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/teachers` | `POST` | `Train` | `TrainTeacherController.add` | `ITrainTeacherService.insertTrainTeacher` | `TrainTeacherMapper.insertTrainTeacher` | `insertTrainTeacher` | `train_teacher` | `NONE` | `train_teacher` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/teachers/bindable-users` | `GET` | `Train` | `TrainAccountBindingAdminController.getTeacherBindableUsers` | `TrainAccountBindingAdminService.selectBindableUsers` | `SysUserMapper.selectUserList` | `selectUserList` | `train_teacher` | `sys_user, train_teacher` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/teachers/list` | `GET` | `Train` | `TrainTeacherController.list` | `ITrainTeacherService.selectTrainTeacherList` | `TrainTeacherMapper.selectTrainTeacherList` | `selectTrainTeacherList` | `train_teacher` | `train_teacher` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/teachers/{tId}` | `DELETE` | `Train` | `TrainTeacherController.remove` | `ITrainTeacherService.deleteTrainTeacherByTId` | `TrainTeacherMapper.deleteTrainTeacherByTId` | `deleteTrainTeacherByTId` | `train_teacher` | `train_teacher` | `train_teacher` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/teachers/{tId}` | `GET` | `Train` | `TrainTeacherController.getInfo` | `ITrainTeacherService.selectTrainTeacherByTId` | `TrainTeacherMapper.selectTrainTeacherByTId` | `selectTrainTeacherByTId` | `train_teacher` | `train_teacher` | `NONE` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/teachers/{tId}` | `PUT` | `Train` | `TrainTeacherController.edit` | `ITrainTeacherService.updateTrainTeacher` | `TrainTeacherMapper.updateTrainTeacher` | `updateTrainTeacher` | `train_teacher` | `train_teacher` | `train_teacher` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/teachers/{tId}/user-binding` | `DELETE` | `Train` | `TrainAccountBindingAdminController.unbindTeacherUser` | `TrainAccountBindingAdminService.unbindTeacherAccount` | `TrainTeacherMapper.updateTrainTeacher` | `updateTrainTeacher` | `train_teacher` | `train_teacher` | `train_teacher` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/teachers/{tId}/user-binding` | `POST` | `Train` | `TrainAccountBindingAdminController.bindTeacherUser` | `TrainAccountBindingAdminService.bindTeacherAccount` | `TrainTeacherMapper.updateTrainTeacher` | `updateTrainTeacher` | `train_teacher` | `sys_user, train_teacher` | `train_teacher` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/teachers/{tId}/user-binding` | `PUT` | `Train` | `TrainAccountBindingAdminController.bindTeacherUser` | `TrainAccountBindingAdminService.bindTeacherAccount` | `TrainTeacherMapper.updateTrainTeacher` | `updateTrainTeacher` | `train_teacher` | `sys_user, train_teacher` | `train_teacher` | `VERIFIED_FROM_SOURCE` | `NOT_FOUND_IN_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/training-site` | `POST` | `Train` | `TrainTrainingSiteController.add` | `ITrainTrainingSiteService.insertTrainTrainingSite` | `TrainTrainingSiteMapper.insertTrainTrainingSite` | `insertTrainTrainingSite` | `train_training_site` | `NONE` | `train_training_site` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/training-site/list` | `GET` | `Train` | `TrainTrainingSiteController.list` | `ITrainTrainingSiteService.selectTrainTrainingSiteList` | `TrainTrainingSiteMapper.selectTrainTrainingSiteList` | `selectTrainTrainingSiteList` | `train_training_site` | `train_training_site` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/training-site/{siteId}` | `GET` | `Train` | `TrainTrainingSiteController.getInfo` | `ITrainTrainingSiteService.selectTrainTrainingSiteBySiteId` | `TrainTrainingSiteMapper.selectTrainTrainingSiteBySiteId` | `selectTrainTrainingSiteBySiteId` | `train_training_site` | `train_training_site` | `NONE` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | +| `/train/training-site/{siteId}` | `PUT` | `Train` | `TrainTrainingSiteController.edit` | `ITrainTrainingSiteService.updateTrainTrainingSite` | `TrainTrainingSiteMapper.updateTrainTrainingSite` | `updateTrainTrainingSite` | `train_training_site` | `train_training_site` | `train_training_site` | `VERIFIED_FROM_SOURCE` | `VERIFIED_USED_BY_UI` | `ENDPOINT_DEFINED (BACKEND_IMPLEMENTED)` | --- ## 2. 统计汇总与一致性强制检查 (Task F & Task G) ### 2.1 端点数量与结构化审计指标 -- **内部业务端点总数 (`INTERNAL_API_COUNT`)**:**107 个** -- **唯一端点标识 (`UNIQUE_METHOD_PATH_COUNT`)**:**107 项**(以 `METHOD + PATH` 为联合主键,0 冲突,0 重复记录) +- **内部业务端点总数 (`INTERNAL_API_COUNT`)**:**148 个** +- **唯一端点标识 (`UNIQUE_METHOD_PATH_COUNT`)**:**148 项**(以 `METHOD + PATH` 为联合主键,0 冲突,0 重复记录,覆盖 24 个培训业务控制器) +- **代码与文档端点一致性 (`CODE_DOCUMENT_CONSISTENCY`)**: + - `CODE_ENDPOINT_COUNT`: **148** + - `DOCUMENT_ENDPOINT_COUNT`: **148** + - `CODE_NOT_IN_DOCUMENT`: **0** + - `DOCUMENT_NOT_IN_CODE`: **0** + - `FINAL_INTERNAL_API_COUNT`: **148** ### 2.2 数据库调用链追踪分布 (`LINEAGE_STATUS`) -- **源码逐项验证通过 (`API_DB_LINEAGE_VERIFIED_COUNT`)**:**107 个** (100% 经 Controller → Service → Mapper → XML 链路追踪验证) +- **源码逐项验证通过 (`API_DB_LINEAGE_VERIFIED_COUNT`)**:**148 个** (100% 经 Controller → Service → Mapper → XML 链路追踪验证) - **规则启发式推导 (`API_DB_LINEAGE_HEURISTIC_COUNT`)**:**0 个** (彻底清零规则推导,全部端点均具备源码定位凭据) - **未经证实猜测量 (`API_DB_LINEAGE_UNVERIFIED_COUNT`)**:**0 个** - **已知表名纠偏完成**: - 彻底纠偏历史文档中的 `train_plan` 误称,物理主表统一修正为实际数据库表名 **`train_class_plan`**; - - 彻底核验 **`train_student_portrait`** 为真实物理业务表(定义于 `004_rebuild_business_tables.sql`,由 `010_verify.sql` 强断言)。 + - 彻底核验 **`train_student_portrait`** 为真实物理业务表(定义于 `004_rebuild_business_tables.sql`,由 `010_verify.sql` 强断言); + - 彻底确认 **`train_score_info = LEGACY_DROPPED / NON_RUNTIME`**,成绩 API (`/train/score/**`, `/train/scoreinfo/**`) 与学生画像 (`student-360`) 运行时数据链路全部穿透至 V2 `train_student_score` + `train_score_item` + `train_enrollment`。 ### 2.3 前端调用状态分布 (`USED_BY_UI` 基于 `build_ui/` 扫描) -- **`VERIFIED_USED_BY_UI`**:**92 项**(在 `build_ui/src/views/` 页面中实际被用户交互触发) -- **`DEFINED_IN_UI_API_ONLY`**:**11 项**(在 `build_ui/src/api/` 中已封装接口,但页面视图暂未直接引用) -- **`NOT_FOUND_IN_UI`**:**4 项**(纯管理端底层端点,前端未声明:教师/学员账号手工绑定与候选人过滤接口) +- **`VERIFIED_USED_BY_UI`**:**82 项**(在 `build_ui/src/views/` 页面中实际被用户交互触发) +- **`DEFINED_IN_UI_API_ONLY`**:**16 项**(在 `build_ui/src/api/` 中已封装接口,但页面视图暂未直接引用) +- **`NOT_FOUND_IN_UI`**:**50 项**(系统后端管理端点/别名端点/只读聚合端点) ### 2.4 四类 API 边界范围确立 (API Scope Definition — 概念分离) -1. **`CUSTOM_TRAIN_BUSINESS_API` (RailwaySystem 自研培训业务端点)**:**107 个** - - 涵盖开班、排课、学员、考勤、报到、住宿、成绩与画像全生命周期; +1. **`CUSTOM_TRAIN_BUSINESS_API` (RailwaySystem 自研培训业务端点)**:**148 个** + - 涵盖开班、排课、学员、考勤、报到、住宿、门禁、成绩与画像全生命周期; 2. **`RUOYI_PLATFORM_API` (RuoYi 底座系统平台端点)**:**95 个** - 涵盖 `/login`、`/captchaImage`、`/system/user/*`、`/system/role/*`、`/system/menu/*`、`/system/dict/*`、`/monitor/*` 等通用平台能力; 3. **`CLIENT_PROVIDED_API` (甲方沈阳房建公寓段向项目交付的外部接口契约)**:**27 个** @@ -154,7 +203,9 @@ ### 2.5 数据库与 API 调用链强制一致性检查 (Task G) - **`LINEAGE_TABLE_NOT_IN_DATABASE_CATALOG`**:**0 项**(所有 API 引用的表均存在于 03 数据库总账中) -- **`DATABASE_TABLE_NOT_REFERENCED_BY_ANY_API`**:**5 项** - - 说明:`train_course_resource`、`train_electronic_board`、`train_resource`、`train_score_item`、`train_student_score` 5 张表暂未暴露于当前 107 个内部 HTTP API 中(如电子班牌由独立控制器处理,成绩 V2 细粒度表等待 C03 成绩闭环对接); +- **`DATABASE_TABLE_NOT_REFERENCED_BY_ANY_API`**:**2 项** + - 说明:`train_course_resource`、`train_resource` 2 张课程资源从属表暂未通过独立端点直接暴露(通过课程主档及静态资源管理维护); + - `train_score_item` 与 `train_student_score` 现已由 `/train/score/**` 及学员 360 画像直接读写并形成闭环;`train_electronic_board` 由 `TrainElectronicBoardController` 6 个端点完整覆盖; +- **`RUNTIME_REFERENCES_TO_TRAIN_SCORE_INFO`**:**0 项**(彻底杜绝运行时对已 drop 旧表的任何读写) - **`UNKNOWN_TABLE_REFERENCES`**:**0 项**(无任何未识别或猜测表名引用) - **`API_DB_LINEAGE_FREEZE`**:**YES** diff --git a/docs/current/05-CLIENT-API-STATUS.md b/docs/current/05-CLIENT-API-STATUS.md index 0aa54480d..31e06eb24 100644 --- a/docs/current/05-CLIENT-API-STATUS.md +++ b/docs/current/05-CLIENT-API-STATUS.md @@ -37,16 +37,16 @@ CLIENT CONTRACT API → RAILWAYSYSTEM ADAPTER / CLIENT → RAILWAYSYSTEM INTERNA | 序号 | 甲方契约接口 (CLIENT_API) | 适配器类 (ADAPTER_CLASS) | 适配器方法 (ADAPTER_METHOD) | 对应内部接口 (INTERNAL_API) | 实现状态 (IMPLEMENTATION_STATUS) | 真实测试状态 (REAL_TEST_STATUS) | 实时返回特征与证据 (REAL_RESULT) | 演示可用性 (DEMO_USABILITY) | | :---: | :--- | :--- | :--- | :--- | :---: | :---: | :--- | :---: | -| **01** | `POST /public/rest/third-party-app/token/get` | `DmsTokenClient` | `obtainToken` | `DmsClient / DmsProperties` (底层服务) | `IMPLEMENTED` | `REAL_LIVE_EMPTY_DATA` | 真实外网 HTTP 200,取得有效 Token (`expires_in: 7200`),Cookie Token 认证通过 | `LIVE_AUTH_READY` | +| **01** | `POST /public/rest/third-party-app/token/get` | `DmsTokenClient` | `obtainToken` | `DmsClient / DmsProperties` (底层服务) | `IMPLEMENTED` | `REAL_LIVE_AUTH_SUCCESS` | 真实外网 HTTP 200,取得有效 Token (`expires_in: 7200`),Cookie Token 认证通过 | `LIVE_AUTH_READY` | | **02** | `POST /public/rest/security/logout` | `N/A` | `N/A` | `N/A` | `NOT_IMPLEMENTED` | `NOT_YET_TESTED` | 未封装客户端;Token 自然过期或会话替换 | `NOT_APPLICABLE` | -| **03** | `POST /rest/place/building/list` | `DmsPlaceClient` | `listBuildings` | `N/A` (集成探测) | `IMPLEMENTED` | `REAL_LIVE_EMPTY_DATA` | 真实外网 HTTP 200,返回 JSON 结构符合契约,数据量为 0 (`BUILDING=0`) | `LIVE_API_BUT_NO_REAL_RESOURCE_DATA` | -| **04** | `POST /rest/place/floor/list` | `DmsPlaceClient` | `listFloors` | `N/A` (集成探测) | `IMPLEMENTED` | `REAL_LIVE_EMPTY_DATA` | 真实外网 HTTP 200,返回 JSON 结构符合契约,数据量为 0 (`FLOOR=0`) | `LIVE_API_BUT_NO_REAL_RESOURCE_DATA` | -| **05** | `POST /rest/place/room/list` | `DmsPlaceClient` | `listRooms` | `N/A` (集成探测) | `IMPLEMENTED` | `REAL_LIVE_EMPTY_DATA` | 真实外网 HTTP 200,返回 JSON 结构符合契约,数据量为 0 (`ROOM=0`) | `LIVE_API_BUT_NO_REAL_RESOURCE_DATA` | +| **03** | `POST /rest/place/building/list` | `DmsPlaceClient` | `listBuildings` | `N/A` (集成探测) | `IMPLEMENTED` | `REAL_LIVE_EMPTY_RESOURCE_DATA` | 真实外网 HTTP 200,返回 JSON 结构符合契约,数据量为 0 (`BUILDING=0`) | `LIVE_API_BUT_NO_REAL_RESOURCE_DATA` | +| **04** | `POST /rest/place/floor/list` | `DmsPlaceClient` | `listFloors` | `N/A` (集成探测) | `IMPLEMENTED` | `REAL_LIVE_EMPTY_RESOURCE_DATA` | 真实外网 HTTP 200,返回 JSON 结构符合契约,数据量为 0 (`FLOOR=0`) | `LIVE_API_BUT_NO_REAL_RESOURCE_DATA` | +| **05** | `POST /rest/place/room/list` | `DmsPlaceClient` | `listRooms` | `N/A` (集成探测) | `IMPLEMENTED` | `REAL_LIVE_EMPTY_RESOURCE_DATA` | 真实外网 HTTP 200,返回 JSON 结构符合契约,数据量为 0 (`ROOM=0`) | `LIVE_API_BUT_NO_REAL_RESOURCE_DATA` | | **06** | `POST /rest/place/room/get` | `DmsPlaceClient` | `getRoom` | `N/A` | `PARTIAL` | `NOT_YET_TESTED` | 部分实现,待真实环境具备房间资源后完整测试 | `NOT_APPLICABLE` | | **07** | `POST /rest/gateway/list` | `N/A` | `N/A` | `N/A` | `INTENTIONALLY_OUT_OF_SCOPE` | `NOT_YET_TESTED` | 网关级运维接口,由厂商后台维护,非学员住宿业务范围 | `PROHIBITED_FROM_DEMO` | | **08** | `POST /rest/gateway/refresh-online-status` | `N/A` | `N/A` | `N/A` | `INTENTIONALLY_OUT_OF_SCOPE` | `NOT_YET_TESTED` | 网关级运维接口,非学员培训管理范围 | `PROHIBITED_FROM_DEMO` | | **09** | `POST /rest/gateway/query-online-status` | `N/A` | `N/A` | `N/A` | `INTENTIONALLY_OUT_OF_SCOPE` | `NOT_YET_TESTED` | 网关级运维接口,非学员培训管理范围 | `PROHIBITED_FROM_DEMO` | -| **10** | `POST /rest/doorlock/list` | `DmsDoorLockClient` | `listDoorLocks` | `N/A` (集成探测) | `IMPLEMENTED` | `REAL_LIVE_EMPTY_DATA` | 真实外网 HTTP 200,返回 JSON 结构符合契约,挂载门锁数为 0 (`LOCK=0`) | `LIVE_API_BUT_NO_REAL_RESOURCE_DATA` | +| **10** | `POST /rest/doorlock/list` | `DmsDoorLockClient` | `listDoorLocks` | `N/A` (集成探测) | `IMPLEMENTED` | `REAL_LIVE_EMPTY_RESOURCE_DATA` | 真实外网 HTTP 200,返回 JSON 结构符合契约,挂载门锁数为 0 (`LOCK=0`) | `LIVE_API_BUT_NO_REAL_RESOURCE_DATA` | | **11** | `POST /rest/doorlock/query-lock-battery` | `N/A` | `N/A` | `N/A` | `NOT_IMPLEMENTED` | `NOT_YET_TESTED` | 批量电池与信号查询,本期暂未接入 | `NOT_APPLICABLE` | | **12** | `POST /rest/doorlock/remote-unlock` | `N/A` | `N/A` | `N/A` | `INTENTIONALLY_OUT_OF_SCOPE` | `SECURITY_PROHIBITED` | 系统安全红线绝对禁止远程开锁,未配置客户端亦严禁调用 | `PROHIBITED_FROM_DEMO` | | **13** | `POST /rest/doorlock/query-operation-status` | `DmsOperationStatusClient` | `queryStatus` | `GET /train/access/operations/{doorAuthId}` | `IMPLEMENTED` | `MOCK_ONLY` | 本地 MockServer 状态机测试通过;真实外网因无真实 operationId 待硬件划拨 | `MOCK_SIMULATION_REQUIRED` | @@ -79,8 +79,10 @@ CLIENT CONTRACT API → RAILWAYSYSTEM ADAPTER / CLIENT → RAILWAYSYSTEM INTERNA - **`CLIENT_CONTRACT_MISMATCH_COUNT`**:**0 个** ### 4.2 真实运行验证状态分布 (`REAL_TEST_STATUS`) -- **`REAL_LIVE_WITH_DATA_COUNT`**:**0 个**(厂商测试环境无真实业务数据) -- **`REAL_LIVE_EMPTY_DATA_COUNT`**:**5 个**(Token, Building, Floor, Room, Lock) -- **`MOCK_ONLY_COUNT`**:**3 个**(在线密码下发、撤销、异步操作状态轮询) -- **`SECURITY_PROHIBITED_COUNT`**:**1 个**(远程开锁) -- **`NOT_YET_TESTED_COUNT`**:**18 个** +- **`REAL_LIVE_AUTH_SUCCESS`**:**1 项**(Token 鉴权接口,真实外网 HTTP 200,取得有效 Token `expires_in: 7200`,Cookie Token 认证通过) +- **`REAL_LIVE_WITH_RESOURCE_DATA`**:**0 项**(厂商测试环境无真实业务资源数据) +- **`REAL_LIVE_EMPTY_RESOURCE_DATA`**:**4 项**(Building, Floor, Room, Lock 读接口,外网 HTTP 200 返回符合契约的空列表) +- **`MOCK_ONLY_COUNT`**:**3 项**(在线密码下发、撤销、异步操作状态轮询) +- **`SECURITY_PROHIBITED_COUNT`**:**1 项**(远程开锁) +- **`NOT_YET_TESTED_COUNT`**:**18 项** + diff --git a/docs/current/CLEAN_DEMO_DATABASE_SPEC.md b/docs/current/CLEAN_DEMO_DATABASE_SPEC.md index 10f46ec90..a0240b67d 100644 --- a/docs/current/CLEAN_DEMO_DATABASE_SPEC.md +++ b/docs/current/CLEAN_DEMO_DATABASE_SPEC.md @@ -33,8 +33,8 @@ ### 2.2 培训历史基线骨架构建 (Phase 0.5 Baseline) 1. **`RuoYi-Vue/sql/train/v2/rollback/000_phase05_exact_schema.sql`**: - 创建 14 张 Phase 0.5 历史基线表、外键、局部唯一约束与触发器函数; -2. **基线数据占位**: - - 插入 1 条基础教师占位数据,确保 `000_preflight.sql` 与 `008_migrate_teacher.sql` 断言平滑通过。 +2. **确定性基线数据脚本 (Deterministic Baseline Seed Script)**: + - 执行 `RuoYi-Vue/sql/train/v2/bootstrap/000_seed_baseline_teacher.sql`,以版本控制的确定性 SQL 脚本植入 1 条脱敏基线教师占位记录 (`t_id='T001'`),确保 `000_preflight.sql` (严格断言 count=1) 与 `008_migrate_teacher.sql` 平滑跑通。 ### 2.3 业务 V2 演进迁移链 (Train V2 Migration Chain) 按序执行 `RuoYi-Vue/sql/train/v2/` 目录下脚本: @@ -109,4 +109,7 @@ 3. **重置策略 (Reset Strategy)**: - 编制确定性重置脚本 `reset_demo_data.sql`,执行后可一键将数据库还原为“演示开始前”初始黄金状态,支持反复多次演练。 4. **备份策略 (Backup Strategy)**: - - 种子数据完全灌入并通过 100% UI 验收后,立即执行物理快照导出 `pg_dump -Fc training_demo_clean > training_demo_clean_frozen.dump`,封存 SHA-256 校验和作为基线。 + - 种子数据完全灌入并通过 100% UI 验收后,在 KingbaseES 数据库环境使用官方原生工具 `sys_dump` 执行二进制归档快照导出: + `sys_dump -h 192.168.139.218 -p 4321 -U -d training_demo_clean -F c -b -v -f training_demo_clean_frozen.dump` + - 封存 SHA-256 校验和作为确定性基线文件;严禁未经核验在 macOS 开发机或未知环境假定 `pg_dump`。 +