修复培训:修复Wave2浏览器验收阻断问题

This commit is contained in:
wangzhibiao committed 2026-09-14 16:48:18 +08:00
1 parent 20694559d7
commit 06c077c023
10 files changed
+255 -7

No files matched your search

@@ -6,6 +6,7 @@ import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* 排课冲突预检请求 DTO (UC-10)。
@@ -29,6 +30,7 @@ public class LessonConflictCheckDTO implements Serializable
/** 授课教师工号/标识 */
@NotBlank(message = "授课教师工号/标识不能为空")
@Size(max = 32, message = "教师工号长度不能超过32个字符")
@JsonProperty("tId")
private String tId;
/** 课次开始时间 */
@@ -83,11 +85,13 @@ public class LessonConflictCheckDTO implements Serializable
this.planCourseId = planCourseId;
}
@JsonProperty("tId")
public String getTId()
{
return tId;
}
@JsonProperty("tId")
public void setTId(String tId)
{
this.tId = tId;
@@ -6,6 +6,7 @@ import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* 新增课次请求 DTO (UC-07)。
@@ -23,6 +24,7 @@ public class LessonCreateDTO implements Serializable
/** 授课教师工号/标识 */
@NotBlank(message = "授课教师工号/标识不能为空")
@Size(max = 32, message = "教师工号长度不能超过32个字符")
@JsonProperty("tId")
private String tId;
/** 课次开始时间 */
@@ -61,11 +63,13 @@ public class LessonCreateDTO implements Serializable
this.planCourseId = planCourseId;
}
@JsonProperty("tId")
public String getTId()
{
return tId;
}
@JsonProperty("tId")
public void setTId(String tId)
{
this.tId = tId;
@@ -6,6 +6,7 @@ import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* 修改课次请求 DTO (UC-08)。
@@ -23,6 +24,7 @@ public class LessonUpdateDTO implements Serializable
/** 授课教师工号/标识 */
@NotBlank(message = "授课教师工号/标识不能为空")
@Size(max = 32, message = "教师工号长度不能超过32个字符")
@JsonProperty("tId")
private String tId;
/** 课次开始时间 */
@@ -61,11 +63,13 @@ public class LessonUpdateDTO implements Serializable
this.planCourseId = planCourseId;
}
@JsonProperty("tId")
public String getTId()
{
return tId;
}
@JsonProperty("tId")
public void setTId(String tId)
{
this.tId = tId;
@@ -3,6 +3,7 @@ package com.ruoyi.train.domain.vo;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* 本期已配置课程视图 VO。
@@ -18,18 +19,23 @@ public class PlanCourseItemVO implements Serializable
private String planId;
/** 课程ID */
@JsonProperty("cId")
private String cId;
/** 课程业务编码 */
@JsonProperty("cCode")
private String cCode;
/** 课程名称 */
@JsonProperty("cName")
private String cName;
/** 课程类型 */
@JsonProperty("cType")
private String cType;
/** 课程学时 */
@JsonProperty("cDuration")
private Integer cDuration;
/** 教学目标 */
@@ -65,51 +71,61 @@ public class PlanCourseItemVO implements Serializable
this.planId = planId;
}
@JsonProperty("cId")
public String getCId()
{
return cId;
}
@JsonProperty("cId")
public void setCId(String cId)
{
this.cId = cId;
}
@JsonProperty("cCode")
public String getCCode()
{
return cCode;
}
@JsonProperty("cCode")
public void setCCode(String cCode)
{
this.cCode = cCode;
}
@JsonProperty("cName")
public String getCName()
{
return cName;
}
@JsonProperty("cName")
public void setCName(String cName)
{
this.cName = cName;
}
@JsonProperty("cType")
public String getCType()
{
return cType;
}
@JsonProperty("cType")
public void setCType(String cType)
{
this.cType = cType;
}
@JsonProperty("cDuration")
public Integer getCDuration()
{
return cDuration;
}
@JsonProperty("cDuration")
public void setCDuration(Integer cDuration)
{
this.cDuration = cDuration;
@@ -3,6 +3,7 @@ package com.ruoyi.train.domain.vo;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* 计划课表课次明细项 VO (API Contract 1.2 / CG-02 / 回显表单必需)。
@@ -23,6 +24,7 @@ public class TimetableLessonItemVO implements Serializable
private String courseName;
/** 授课教师工号/标识 (回显表单必需) */
@JsonProperty("tId")
private String tId;
/** 授课教师姓名 */
@@ -90,11 +92,13 @@ public class TimetableLessonItemVO implements Serializable
this.courseName = courseName;
}
@JsonProperty("tId")
public String getTId()
{
return tId;
}
@JsonProperty("tId")
public void setTId(String tId)
{
this.tId = tId;
@@ -150,9 +150,9 @@ public class EnrollmentAppServiceImpl implements EnrollmentAppService
}
}
// 4. 校验学员档案有效性
// 4. 校验学员档案有效性 (严格要求 1:正常, 0:停用, null:无效)
TrainStudent student = studentMapper.selectTrainStudentBySId(dto.getStudentId());
if (student == null || "2".equals(student.getDelFlag()) || (student.getStatus() != null && student.getStatus() != 0))
if (student == null || "2".equals(student.getDelFlag()) || !Integer.valueOf(1).equals(student.getStatus()))
{
throw new ServiceException(StudentErrorCode.STUDENT_NOT_FOUND_MESSAGE, StudentErrorCode.STUDENT_NOT_FOUND);
}
@@ -281,7 +281,7 @@ public class EnrollmentAppServiceImpl implements EnrollmentAppService
student.setsName(StringUtils.isNotEmpty(dto.getRemark()) ? dto.getRemark() : "导入学员_" + dto.getStudentId());
student.setOrgName(dto.getOrgName());
student.setSourceType(2); // 2:Excel批量导入
student.setStatus(0);
student.setStatus(1); // 1:正常
student.setDelFlag("0");
student.setCreateBy(operator);
student.setCreateTime(DateUtils.getNowDate());
@@ -0,0 +1,72 @@
package com.ruoyi.train.domain.dto;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.train.domain.vo.TimetableLessonItemVO;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* 验证排课调度相关 DTO/VO 的 tId 属性在 Jackson 序列化/反序列化时的 Contract 绑定。
*/
public class SchedulingDtoJsonBindingTest
{
private ObjectMapper objectMapper;
@Before
public void setUp()
{
objectMapper = new ObjectMapper();
}
@Test
public void testLessonCreateDtoTIdBinding() throws Exception
{
String json = "{\"planCourseId\":101,\"tId\":\"T2026001\",\"startTime\":\"2026-09-15 09:00:00\",\"endTime\":\"2026-09-15 11:30:00\",\"venueType\":1,\"classRoomId\":\"CR-101\"}";
LessonCreateDTO dto = objectMapper.readValue(json, LessonCreateDTO.class);
assertNotNull("DTO should not be null", dto);
assertEquals("tId must be correctly bound from JSON 'tId'", "T2026001", dto.getTId());
}
@Test
public void testLessonUpdateDtoTIdBinding() throws Exception
{
String json = "{\"planCourseId\":102,\"tId\":\"T2026002\",\"startTime\":\"2026-09-15 14:00:00\",\"endTime\":\"2026-09-15 16:30:00\",\"venueType\":2,\"siteId\":\"SITE-01\"}";
LessonUpdateDTO dto = objectMapper.readValue(json, LessonUpdateDTO.class);
assertNotNull("DTO should not be null", dto);
assertEquals("tId must be correctly bound from JSON 'tId'", "T2026002", dto.getTId());
}
@Test
public void testLessonConflictCheckDtoTIdBinding() throws Exception
{
String json = "{\"planId\":\"PLAN-2026-001\",\"planCourseId\":101,\"tId\":\"T2026003\",\"startTime\":\"2026-09-15 09:00:00\",\"endTime\":\"2026-09-15 11:30:00\",\"venueType\":1,\"classRoomId\":\"CR-101\"}";
LessonConflictCheckDTO dto = objectMapper.readValue(json, LessonConflictCheckDTO.class);
assertNotNull("DTO should not be null", dto);
assertEquals("tId must be correctly bound from JSON 'tId'", "T2026003", dto.getTId());
}
@Test
public void testTimetableLessonItemVoTIdSerializationAndBinding() throws Exception
{
TimetableLessonItemVO vo = new TimetableLessonItemVO();
vo.setLessonId(1L);
vo.setPlanCourseId(101L);
vo.setCourseName("高速铁路信号基础");
vo.setTId("T2026004");
vo.setTeacherName("张讲师");
String json = objectMapper.writeValueAsString(vo);
JsonNode jsonNode = objectMapper.readTree(json);
assertTrue("Serialized JSON must contain field 'tId'", jsonNode.has("tId"));
assertEquals("tId field in JSON must match", "T2026004", jsonNode.get("tId").asText());
TimetableLessonItemVO deserialized = objectMapper.readValue(json, TimetableLessonItemVO.class);
assertEquals("Deserialized VO must preserve tId", "T2026004", deserialized.getTId());
}
}
@@ -0,0 +1,77 @@
package com.ruoyi.train.domain.vo;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* 验证 PlanCourseItemVO 字段命名及其在 Jackson 序列化与反序列化下的稳定性。
*/
public class PlanCourseItemVOTest
{
private ObjectMapper objectMapper;
@Before
public void setUp()
{
objectMapper = new ObjectMapper();
}
@Test
public void testPlanCourseItemVoSerialization() throws Exception
{
PlanCourseItemVO vo = new PlanCourseItemVO();
vo.setPlanCourseId(1001L);
vo.setPlanId("PLAN-2026-001");
vo.setCId("C-101");
vo.setCCode("TR-001");
vo.setCName("高速铁路信号基础");
vo.setCType("专业课");
vo.setCDuration(16);
vo.setSortNo(1);
vo.setRemark("重点必修");
String json = objectMapper.writeValueAsString(vo);
JsonNode node = objectMapper.readTree(json);
assertTrue("JSON must contain 'cId'", node.has("cId"));
assertEquals("C-101", node.get("cId").asText());
assertTrue("JSON must contain 'cCode'", node.has("cCode"));
assertEquals("TR-001", node.get("cCode").asText());
assertTrue("JSON must contain 'cName'", node.has("cName"));
assertEquals("高速铁路信号基础", node.get("cName").asText());
assertTrue("JSON must contain 'cType'", node.has("cType"));
assertEquals("专业课", node.get("cType").asText());
assertTrue("JSON must contain 'cDuration'", node.has("cDuration"));
assertEquals(16, node.get("cDuration").asInt());
assertTrue("JSON must contain 'sortNo'", node.has("sortNo"));
assertEquals(1, node.get("sortNo").asInt());
}
@Test
public void testPlanCourseItemVoDeserialization() throws Exception
{
String json = "{\"planCourseId\":1002,\"planId\":\"PLAN-2026-002\",\"cId\":\"C-102\",\"cCode\":\"TR-002\",\"cName\":\"列车运行控制系统\",\"cType\":\"实训课\",\"cDuration\":24,\"sortNo\":2}";
PlanCourseItemVO vo = objectMapper.readValue(json, PlanCourseItemVO.class);
assertNotNull(vo);
assertEquals(Long.valueOf(1002L), vo.getPlanCourseId());
assertEquals("PLAN-2026-002", vo.getPlanId());
assertEquals("C-102", vo.getCId());
assertEquals("TR-002", vo.getCCode());
assertEquals("列车运行控制系统", vo.getCName());
assertEquals("实训课", vo.getCType());
assertEquals(Integer.valueOf(24), vo.getCDuration());
assertEquals(Integer.valueOf(2), vo.getSortNo());
}
}
@@ -107,7 +107,7 @@ public class EnrollmentAppServiceImplTest
s1.setOrgName("沈阳电务段");
s1.setsPhone("13800000001");
s1.setIdCardLast4("1234");
s1.setStatus(0);
s1.setStatus(1);
s1.setDelFlag("0");
studentMapper.studentStore.put("S001", s1);
@@ -118,7 +118,7 @@ public class EnrollmentAppServiceImplTest
s2.setOrgName("大连电务段");
s2.setsPhone("13800000002");
s2.setIdCardLast4("5678");
s2.setStatus(0);
s2.setStatus(1);
s2.setDelFlag("0");
studentMapper.studentStore.put("S002", s2);
@@ -129,7 +129,7 @@ public class EnrollmentAppServiceImplTest
s3.setOrgName("锦州电务段");
s3.setsPhone("13800000003");
s3.setIdCardLast4("9012");
s3.setStatus(0);
s3.setStatus(1);
s3.setDelFlag("0");
studentMapper.studentStore.put("S003", s3);
}
@@ -260,6 +260,73 @@ public class EnrollmentAppServiceImplTest
() -> appService.addEnrollment("PLAN-001", dto, "admin"));
}
@Test
public void testAddNormalStudentEnrollmentSuccess()
{
// 1. 创建一个按正式流程得到的正常学员 (status=1)
TrainStudent normalStudent = new TrainStudent();
normalStudent.setsId("S_NORMAL_01");
normalStudent.setsName("赵六");
normalStudent.setStatus(1); // 正常
normalStudent.setDelFlag("0");
studentMapper.studentStore.put("S_NORMAL_01", normalStudent);
// 2. 添加到 Enrollment
EnrollmentAddDTO dto = new EnrollmentAddDTO();
dto.setStudentId("S_NORMAL_01");
dto.setSourceType(1);
dto.setAccommodationRequired(0);
Long id = appService.addEnrollment("PLAN-001", dto, "admin");
// 3. 必须成功
assertNotNull(id);
TrainEnrollment enrollment = enrollmentRepository.selectTrainEnrollmentById(id);
assertNotNull(enrollment);
assertEquals(Integer.valueOf(0), enrollment.getEnrollmentStatus()); // 待报到
}
@Test
public void testAddDisabledStudentEnrollmentRejected()
{
// 1. 创建一个停用学员 (status=0)
TrainStudent disabledStudent = new TrainStudent();
disabledStudent.setsId("S_DISABLED_01");
disabledStudent.setsName("钱七");
disabledStudent.setStatus(0); // 停用
disabledStudent.setDelFlag("0");
studentMapper.studentStore.put("S_DISABLED_01", disabledStudent);
// 2. 非正常/停用 Student 应按真实业务规则被拒绝
EnrollmentAddDTO dto = new EnrollmentAddDTO();
dto.setStudentId("S_DISABLED_01");
dto.setSourceType(1);
dto.setAccommodationRequired(0);
assertError(StudentErrorCode.STUDENT_NOT_FOUND,
() -> appService.addEnrollment("PLAN-001", dto, "admin"));
}
@Test
public void testAddNullStatusStudentEnrollmentRejected()
{
// 1. 创建一个 status 为 null 的学员
TrainStudent nullStatusStudent = new TrainStudent();
nullStatusStudent.setsId("S_NULL_STATUS_01");
nullStatusStudent.setsName("孙八");
nullStatusStudent.setStatus(null); // null 状态
nullStatusStudent.setDelFlag("0");
studentMapper.studentStore.put("S_NULL_STATUS_01", nullStatusStudent);
// 2. status 为 null 的 Student 应按严格规则被拒绝
EnrollmentAddDTO dto = new EnrollmentAddDTO();
dto.setStudentId("S_NULL_STATUS_01");
dto.setSourceType(1);
dto.setAccommodationRequired(0);
assertError(StudentErrorCode.STUDENT_NOT_FOUND,
() -> appService.addEnrollment("PLAN-001", dto, "admin"));
}
@Test
public void testListEnrollmentsAndDetailWithMasking()
{
@@ -186,7 +186,7 @@
<el-option
v-for="c in planCourseOptions"
:key="c.planCourseId"
:label="`${c.cName || c.courseName} (${c.cCode || '无编码'}) - ${c.cDuration || 0}学时`"
:label="`${c.cName || '未命名'} (${c.cCode || '无编码'}) - ${c.cDuration != null ? c.cDuration : 0}学时`"
:value="c.planCourseId"
/>
</el-select>