diff --git a/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolStudentController.java b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolStudentController.java index 43c6f01..aa4cf6b 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolStudentController.java +++ b/reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolStudentController.java @@ -7,7 +7,9 @@ import com.reading.platform.common.response.Result; import com.reading.platform.common.security.SecurityUtils; import com.reading.platform.dto.request.StudentCreateRequest; import com.reading.platform.dto.request.StudentUpdateRequest; +import com.reading.platform.dto.request.TransferStudentRequest; import com.reading.platform.dto.response.StudentResponse; +import com.reading.platform.dto.response.StudentTransferHistoryItemResponse; import com.reading.platform.entity.Student; import com.reading.platform.service.ClassService; import com.reading.platform.service.StudentService; @@ -18,6 +20,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Map; @Tag(name = "School - Student", description = "Student Management APIs for School") @RestController @@ -77,6 +80,17 @@ public class SchoolStudentController { return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize())); } + @Operation(summary = "Transfer student to another class") + @PostMapping("/{id}/transfer") + public Result> transferStudent( + @PathVariable Long id, + @Valid @RequestBody TransferStudentRequest request) { + Long tenantId = SecurityUtils.getCurrentTenantId(); + studentService.getStudentByIdWithTenantCheck(id, tenantId); + classService.assignStudentToClass(id, request.getToClassId(), tenantId); + return Result.success(Map.of("message", "调班成功")); + } + @Operation(summary = "Delete student") @DeleteMapping("/{id}") public Result deleteStudent(@PathVariable Long id) { @@ -85,4 +99,13 @@ public class SchoolStudentController { return Result.success(); } + @Operation(summary = "Get student class transfer history") + @GetMapping("/{id}/history") + public Result> getStudentClassHistory(@PathVariable Long id) { + Long tenantId = SecurityUtils.getCurrentTenantId(); + studentService.getStudentByIdWithTenantCheck(id, tenantId); + List history = classService.getStudentClassHistory(id, tenantId); + return Result.success(history); + } + } diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/request/TransferStudentRequest.java b/reading-platform-java/src/main/java/com/reading/platform/dto/request/TransferStudentRequest.java new file mode 100644 index 0000000..be625be --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/request/TransferStudentRequest.java @@ -0,0 +1,17 @@ +package com.reading.platform.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +@Schema(description = "学生调班请求") +public class TransferStudentRequest { + + @NotNull(message = "目标班级 ID 不能为空") + @Schema(description = "目标班级 ID", required = true) + private Long toClassId; + + @Schema(description = "调班原因") + private String reason; +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/dto/response/StudentTransferHistoryItemResponse.java b/reading-platform-java/src/main/java/com/reading/platform/dto/response/StudentTransferHistoryItemResponse.java new file mode 100644 index 0000000..1c085af --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/dto/response/StudentTransferHistoryItemResponse.java @@ -0,0 +1,53 @@ +package com.reading.platform.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 学生调班历史单条记录响应 + * 前端期望格式:fromClass、toClass 包含班级信息 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "学生调班历史单条记录") +public class StudentTransferHistoryItemResponse { + + @Schema(description = "记录 ID") + private Long id; + + @Schema(description = "调出班级(首次入园为 null)") + private ClassBasicInfo fromClass; + + @Schema(description = "调入班级") + private ClassBasicInfo toClass; + + @Schema(description = "调班原因") + private String reason; + + @Schema(description = "操作人 ID") + private Long operatedBy; + + @Schema(description = "创建时间") + private LocalDateTime createdAt; + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "班级基本信息") + public static class ClassBasicInfo { + @Schema(description = "班级 ID") + private Long id; + @Schema(description = "班级名称") + private String name; + @Schema(description = "年级") + private String grade; + } +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/ClassService.java b/reading-platform-java/src/main/java/com/reading/platform/service/ClassService.java index f1a4590..fcc96d8 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/ClassService.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/ClassService.java @@ -3,6 +3,7 @@ package com.reading.platform.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.reading.platform.dto.request.ClassCreateRequest; import com.reading.platform.dto.request.ClassUpdateRequest; +import com.reading.platform.dto.response.StudentTransferHistoryItemResponse; import com.reading.platform.entity.Clazz; import java.util.List; @@ -102,4 +103,9 @@ public interface ClassService extends com.baomidou.mybatisplus.extension.service */ Clazz getPrimaryClassByStudentId(Long studentId); + /** + * 获取学生调班历史(带租户验证) + */ + List getStudentClassHistory(Long studentId, Long tenantId); + } diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/impl/ClassServiceImpl.java b/reading-platform-java/src/main/java/com/reading/platform/service/impl/ClassServiceImpl.java index b0fd034..287efcf 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/impl/ClassServiceImpl.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/impl/ClassServiceImpl.java @@ -7,6 +7,7 @@ import com.reading.platform.common.enums.ErrorCode; import com.reading.platform.common.exception.BusinessException; import com.reading.platform.dto.request.ClassCreateRequest; import com.reading.platform.dto.request.ClassUpdateRequest; +import com.reading.platform.dto.response.StudentTransferHistoryItemResponse; import com.reading.platform.entity.ClassTeacher; import com.reading.platform.entity.Clazz; import com.reading.platform.entity.StudentClassHistory; @@ -22,6 +23,7 @@ import org.springframework.util.StringUtils; import java.time.LocalDate; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -337,4 +339,54 @@ public class ClassServiceImpl extends com.baomidou.mybatisplus.extension.service return clazzMapper.selectById(history.getClassId()); } + @Override + public List getStudentClassHistory(Long studentId, Long tenantId) { + log.debug("获取学生调班历史,学生 ID: {}, 租户 ID: {}", studentId, tenantId); + + List histories = studentClassHistoryMapper.selectList( + new LambdaQueryWrapper() + .eq(StudentClassHistory::getStudentId, studentId) + .orderByAsc(StudentClassHistory::getStartDate) + ); + + List result = new ArrayList<>(); + Clazz prevClass = null; + + for (StudentClassHistory h : histories) { + Clazz toClazz = getClassById(h.getClassId()); + if (toClazz == null || !tenantId.equals(toClazz.getTenantId())) { + log.warn("调班历史引用的班级不存在或无权访问,跳过。historyId: {}, classId: {}", h.getId(), h.getClassId()); + continue; + } + + StudentTransferHistoryItemResponse.ClassBasicInfo fromClassInfo = prevClass == null ? null + : StudentTransferHistoryItemResponse.ClassBasicInfo.builder() + .id(prevClass.getId()) + .name(prevClass.getName()) + .grade(prevClass.getGrade() != null ? prevClass.getGrade() : "") + .build(); + + StudentTransferHistoryItemResponse.ClassBasicInfo toClassInfo = + StudentTransferHistoryItemResponse.ClassBasicInfo.builder() + .id(toClazz.getId()) + .name(toClazz.getName()) + .grade(toClazz.getGrade() != null ? toClazz.getGrade() : "") + .build(); + + result.add(StudentTransferHistoryItemResponse.builder() + .id(h.getId()) + .fromClass(fromClassInfo) + .toClass(toClassInfo) + .reason(null) + .operatedBy(null) + .createdAt(h.getCreatedAt()) + .build()); + + prevClass = toClazz; + } + + Collections.reverse(result); + return result; + } + }