diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentClassChangeController.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentClassChangeController.java new file mode 100644 index 00000000..3ff89dc6 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentClassChangeController.java @@ -0,0 +1,104 @@ +package org.dromara.xg.controller; + +import java.util.List; + +import lombok.RequiredArgsConstructor; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.web.core.BaseController; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.xg.domain.vo.XgStudentClassChangeVo; +import org.dromara.xg.domain.bo.XgStudentClassChangeBo; +import org.dromara.xg.service.IXgStudentClassChangeService; +import org.dromara.common.mybatis.core.page.TableDataInfo; + +/** + * 学生换班级 + * + * @author AI Assistant + * @date 2025-01-25 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/studentMsg/studentClassChange") +public class XgStudentClassChangeController extends BaseController { + + private final IXgStudentClassChangeService xgStudentClassChangeService; + + /** + * 查询学生换班级列表 + */ + // @SaCheckPermission("studentMsg:studentClassChange:list") + @GetMapping("/list") + public TableDataInfo list(XgStudentClassChangeBo bo, PageQuery pageQuery) { + return xgStudentClassChangeService.queryPageList(bo, pageQuery); + } + + /** + * 导出学生换班级列表 + */ + // @SaCheckPermission("studentMsg:studentClassChange:export") + @Log(title = "学生换班级", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(XgStudentClassChangeBo bo, HttpServletResponse response) { + List list = xgStudentClassChangeService.queryList(bo); + ExcelUtil.exportExcel(list, "学生换班级", XgStudentClassChangeVo.class, response); + } + + /** + * 获取学生换班级详细信息 + * + * @param id 主键 + */ + // @SaCheckPermission("studentMsg:studentClassChange:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) { + return R.ok(xgStudentClassChangeService.queryById(id)); + } + + /** + * 新增学生换班级 + */ + // @SaCheckPermission("studentMsg:studentClassChange:add") + @Log(title = "学生换班级", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody XgStudentClassChangeBo bo) { + return toAjax(xgStudentClassChangeService.insertByBo(bo)); + } + + /** + * 修改学生换班级 + */ + // @SaCheckPermission("studentMsg:studentClassChange:edit") + @Log(title = "学生换班级", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody XgStudentClassChangeBo bo) { + return toAjax(xgStudentClassChangeService.updateByBo(bo)); + } + + /** + * 删除学生换班级 + * + * @param ids 主键串 + */ + // @SaCheckPermission("studentMsg:studentClassChange:remove") + @Log(title = "学生换班级", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) { + return toAjax(xgStudentClassChangeService.deleteWithValidByIds(List.of(ids), true)); + } + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgStudentClassChange.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgStudentClassChange.java new file mode 100644 index 00000000..16e795dd --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgStudentClassChange.java @@ -0,0 +1,146 @@ +package org.dromara.xg.domain; + +import org.dromara.common.tenant.core.TenantEntity; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.Date; +import java.io.Serial; + +/** + * 学生换班级对象 xg_student_class_change + * + * @author AI Assistant + * @date 2025-01-25 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("xg_student_class_change") +public class XgStudentClassChange extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID(雪花ID) + */ + @TableId(value = "id") + private Long id; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic(value = "0", delval = "1") + private Long deleted; + + /** + * 学生ID,关联xg_student_msg.id + */ + private Long studentId; + + /** + * 学号 + */ + private String studentNo; + + /** + * 学生姓名 + */ + private String studentName; + + /** + * 原学院ID + */ + private Long originalCollegeId; + + /** + * 原学院名称 + */ + private String originalCollegeName; + + /** + * 原专业ID + */ + private Long originalMajorId; + + /** + * 原专业名称 + */ + private String originalMajorName; + + /** + * 原班级ID + */ + private Long originalClassId; + + /** + * 原班级代码 + */ + private String originalClassCode; + + /** + * 原班级名称 + */ + private String originalClassName; + + /** + * 新学院ID + */ + private Long newCollegeId; + + /** + * 新学院名称 + */ + private String newCollegeName; + + /** + * 新专业ID + */ + private Long newMajorId; + + /** + * 新专业名称 + */ + private String newMajorName; + + /** + * 新班级ID + */ + private Long newClassId; + + /** + * 新班级代码 + */ + private String newClassCode; + + /** + * 新班级名称 + */ + private String newClassName; + + /** + * 换班级原因 + */ + private String changeReason; + + /** + * 换班级类型:1-主动申请,2-学校调整,3-专业调整,4-其他 + */ + private String changeType; + + /** + * 换班级时间 + */ + private Date changeTime; + + /** + * 生效时间 + */ + private Date effectiveTime; + + /** + * 处理状态:0-未处理,1-已处理 + */ + private String status; + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgStudentClassChangeBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgStudentClassChangeBo.java new file mode 100644 index 00000000..bed0e739 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgStudentClassChangeBo.java @@ -0,0 +1,157 @@ +package org.dromara.xg.domain.bo; + +import org.dromara.xg.domain.XgStudentClassChange; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 学生换班级业务对象 xg_student_class_change + * + * @author AI Assistant + * @date 2025-01-25 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = XgStudentClassChange.class, reverseConvertGenerate = false) +public class XgStudentClassChangeBo extends BaseEntity { + + /** + * 主键ID(雪花ID) + */ + @NotNull(message = "主键ID不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 学生ID,关联xg_student_msg.id + */ + @NotNull(message = "学生ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long studentId; + + /** + * 学号 + */ + private String studentNo; + + /** + * 学生姓名 + */ + private String studentName; + + /** + * 原学院ID + */ + private Long originalCollegeId; + + /** + * 原学院名称 + */ + private String originalCollegeName; + + /** + * 原专业ID + */ + private Long originalMajorId; + + /** + * 原专业名称 + */ + private String originalMajorName; + + /** + * 原班级ID + */ + private Long originalClassId; + + /** + * 原班级代码 + */ + private String originalClassCode; + + /** + * 原班级名称 + */ + private String originalClassName; + + /** + * 新学院ID + */ + @NotNull(message = "新学院不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long newCollegeId; + + /** + * 新学院名称 + */ + private String newCollegeName; + + /** + * 新专业ID + */ + @NotNull(message = "新专业不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long newMajorId; + + /** + * 新专业名称 + */ + private String newMajorName; + + /** + * 新班级ID + */ + @NotNull(message = "新班级不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long newClassId; + + /** + * 新班级代码 + */ + private String newClassCode; + + /** + * 新班级名称 + */ + private String newClassName; + + /** + * 换班级原因 + */ + @NotBlank(message = "换班级原因不能为空", groups = { AddGroup.class, EditGroup.class }) + private String changeReason; + + /** + * 换班级类型:1-主动申请,2-学校调整,3-专业调整,4-其他 + */ + private String changeType; + + /** + * 换班级时间 + */ + private Date changeTime; + + /** + * 生效时间 + */ + @NotNull(message = "生效时间不能为空", groups = { AddGroup.class, EditGroup.class }) + private Date effectiveTime; + + /** + * 处理状态:0-未处理,1-已处理 + */ + private String status; + + /** + * 创建部门 + */ + private Long createDept; + + /** + * 备注信息 + */ + private String remark; + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgStudentClassChangeVo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgStudentClassChangeVo.java new file mode 100644 index 00000000..7dc5264e --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgStudentClassChangeVo.java @@ -0,0 +1,122 @@ +package org.dromara.xg.domain.vo; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; +import org.dromara.xg.domain.XgStudentClassChange; +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.convert.ExcelDictConvert; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 学生换班级视图对象 xg_student_class_change + * + * @author AI Assistant + * @date 2025-01-25 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = XgStudentClassChange.class) +public class XgStudentClassChangeVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID(雪花ID) + */ + @ExcelProperty(value = "主键ID") + private Long id; + + /** + * 学生ID,关联xg_student_msg.id + */ + @ExcelProperty(value = "学生ID") + private Long studentId; + + /** + * 学号 + */ + @ExcelProperty(value = "学号") + private String studentNo; + + /** + * 学生姓名 + */ + @ExcelProperty(value = "学生姓名") + private String studentName; + + /** + * 原学院名称 + */ + @ExcelProperty(value = "原学院名称") + private String originalCollegeName; + + /** + * 原专业名称 + */ + @ExcelProperty(value = "原专业名称") + private String originalMajorName; + + /** + * 原班级名称 + */ + @ExcelProperty(value = "原班级名称") + private String originalClassName; + + /** + * 新学院名称 + */ + @ExcelProperty(value = "新学院名称") + private String newCollegeName; + + /** + * 新专业名称 + */ + @ExcelProperty(value = "新专业名称") + private String newMajorName; + + /** + * 新班级名称 + */ + @ExcelProperty(value = "新班级名称") + private String newClassName; + + /** + * 换班级原因 + */ + @ExcelProperty(value = "换班级原因") + private String changeReason; + + /** + * 生效时间 + */ + @ExcelProperty(value = "生效时间") + @JsonFormat(pattern = "yyyy-MM-dd") + private Date effectiveTime; + + /** + * 处理状态:0-未处理,1-已处理 + */ + @ExcelProperty(value = "处理状态") + private String status; + + /** + * 创建者 + */ + @ExcelProperty(value = "创建者") + private Long createBy; + + /** + * 创建时间 + */ + @ExcelProperty(value = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgStudentClassChangeMapper.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgStudentClassChangeMapper.java new file mode 100644 index 00000000..26f4c3bb --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgStudentClassChangeMapper.java @@ -0,0 +1,15 @@ +package org.dromara.xg.mapper; + +import org.dromara.xg.domain.XgStudentClassChange; +import org.dromara.xg.domain.vo.XgStudentClassChangeVo; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; + +/** + * 学生换班级Mapper接口 + * + * @author AI Assistant + * @date 2025-01-25 + */ +public interface XgStudentClassChangeMapper extends BaseMapperPlus { + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgStudentClassChangeService.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgStudentClassChangeService.java new file mode 100644 index 00000000..ba458224 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgStudentClassChangeService.java @@ -0,0 +1,69 @@ +package org.dromara.xg.service; + +import org.dromara.xg.domain.vo.XgStudentClassChangeVo; +import org.dromara.xg.domain.bo.XgStudentClassChangeBo; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * 学生换班级Service接口 + * + * @author AI Assistant + * @date 2025-01-25 + */ +public interface IXgStudentClassChangeService { + + /** + * 查询学生换班级 + * + * @param id 主键 + * @return 学生换班级 + */ + XgStudentClassChangeVo queryById(Long id); + + /** + * 分页查询学生换班级列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 学生换班级分页列表 + */ + TableDataInfo queryPageList(XgStudentClassChangeBo bo, PageQuery pageQuery); + + /** + * 查询符合条件的学生换班级列表 + * + * @param bo 查询条件 + * @return 学生换班级列表 + */ + List queryList(XgStudentClassChangeBo bo); + + /** + * 新增学生换班级 + * + * @param bo 学生换班级 + * @return 是否新增成功 + */ + Boolean insertByBo(XgStudentClassChangeBo bo); + + /** + * 修改学生换班级 + * + * @param bo 学生换班级 + * @return 是否修改成功 + */ + Boolean updateByBo(XgStudentClassChangeBo bo); + + /** + * 校验并批量删除学生换班级信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentClassChangeServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentClassChangeServiceImpl.java new file mode 100644 index 00000000..c680974d --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentClassChangeServiceImpl.java @@ -0,0 +1,126 @@ +package org.dromara.xg.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.xg.domain.XgStudentClassChange; +import org.dromara.xg.domain.bo.XgStudentClassChangeBo; +import org.dromara.xg.domain.vo.XgStudentClassChangeVo; +import org.dromara.xg.mapper.XgStudentClassChangeMapper; +import org.dromara.xg.service.IXgStudentClassChangeService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 学生换班级Service业务层处理 + * + * @author AI Assistant + * @date 2025-01-25 + */ +@RequiredArgsConstructor +@Service +public class XgStudentClassChangeServiceImpl implements IXgStudentClassChangeService { + + private final XgStudentClassChangeMapper baseMapper; + + /** + * 查询学生换班级 + */ + @Override + public XgStudentClassChangeVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 分页查询学生换班级列表 + */ + @Override + public TableDataInfo queryPageList(XgStudentClassChangeBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询符合条件的学生换班级列表 + */ + @Override + public List queryList(XgStudentClassChangeBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(XgStudentClassChangeBo bo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getStudentId() != null, XgStudentClassChange::getStudentId, bo.getStudentId()); + lqw.like(StringUtils.isNotBlank(bo.getStudentNo()), XgStudentClassChange::getStudentNo, bo.getStudentNo()); + lqw.like(StringUtils.isNotBlank(bo.getStudentName()), XgStudentClassChange::getStudentName, bo.getStudentName()); + lqw.eq(StringUtils.isNotBlank(bo.getStatus()), XgStudentClassChange::getStatus, bo.getStatus()); + lqw.orderByDesc(XgStudentClassChange::getCreateTime); + return lqw; + } + + /** + * 新增学生换班级 + */ + @Override + public Boolean insertByBo(XgStudentClassChangeBo bo) { + // 设置换班级时间为当前时间(如果未设置) + if (bo.getChangeTime() == null) { + bo.setChangeTime(new java.util.Date()); + } + // 设置状态为未处理 + if (StringUtils.isBlank(bo.getStatus())) { + bo.setStatus("0"); + } + XgStudentClassChange add = BeanUtil.toBean(bo, XgStudentClassChange.class); + validEntityBeforeSave(add); + return baseMapper.insert(add) > 0; + } + + /** + * 修改学生换班级 + */ + @Override + public Boolean updateByBo(XgStudentClassChangeBo bo) { + XgStudentClassChange update = BeanUtil.toBean(bo, XgStudentClassChange.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(XgStudentClassChange entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除学生换班级 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + // 检查是否包含已处理的记录 + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.in(XgStudentClassChange::getId, ids); + lqw.eq(XgStudentClassChange::getStatus, "1"); // 已处理状态 + if (baseMapper.selectCount(lqw) > 0) { + throw new ServiceException("已处理的换班级记录不能删除"); + } + } + return baseMapper.deleteByIds(ids) > 0; + } +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/studentClassChange/XgStudentClassChangeMapper.xml b/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/studentClassChange/XgStudentClassChangeMapper.xml new file mode 100644 index 00000000..54c45ade --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/studentClassChange/XgStudentClassChangeMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/docs/开发文档/API/API文档总览.md b/docs/开发文档/API/API文档总览.md index 6c917dae..61d90832 100644 --- a/docs/开发文档/API/API文档总览.md +++ b/docs/开发文档/API/API文档总览.md @@ -15,6 +15,7 @@ - [宿舍管理API](./宿舍管理API.md) - [宿舍房间管理API](./宿舍房间管理API.md) - **新增床位详细信息查询接口**(2025-01-27) - [宿舍床位分配管理API](./宿舍床位分配管理API.md) - **重要修复:床位分配不完整问题**(2025-01-27) +- [宿舍申请管理API](./宿舍申请管理API.md) - **新增**:支持学生调宿和退宿申请管理(2025-01-15) - [宿舍管理员信息API](./宿舍管理员信息API.md) - [宿舍管理员授权API](./宿舍管理员授权API.md) - [学生管理员授权关系API](./学生管理员授权关系API.md) diff --git a/docs/开发文档/API/宿舍申请管理API.md b/docs/开发文档/API/宿舍申请管理API.md new file mode 100644 index 00000000..9e12aa1a --- /dev/null +++ b/docs/开发文档/API/宿舍申请管理API.md @@ -0,0 +1,693 @@ +# 宿舍申请管理 API 接口文档 + +### 模块概述 + +**模块名称**: 宿舍申请管理 / Dormitory Application Management + +**基础路径**: `/dormApplication/dormApplication` + +**版本**: v1.0 + +**负责人**: zlwu + +**更新时间**: 2025-01-15 + +### 模块功能 + +宿舍申请管理模块主要处理学生的宿舍调宿和退宿申请,包括: + +- 学生宿舍调宿申请(更换床位) +- 学生宿舍退宿申请(释放床位) +- 工作流审批流程 +- 宿舍管理员权限控制 +- 学生信息自动更新 + +### 权限说明 + +- **权限前缀**: `dormApplication:dormApplication:` +- **权限列表**: + - `dormApplication:dormApplication:list` - 查询权限 + - `dormApplication:dormApplication:add` - 新增权限 + - `dormApplication:dormApplication:edit` - 编辑权限 + - `dormApplication:dormApplication:remove` - 删除权限 + - `dormApplication:dormApplication:query` - 查看详情权限 + - `dormApplication:dormApplication:export` - 导出权限 + +--- + +## 📊 接口概览 + +### 接口分类 + +#### 基础 CRUD 接口 + +| 接口名称 | HTTP 方法 | 路径 | 权限要求 | 功能描述 | +| -------------- | --------- | --------------------- | ----------------- | ---------------------- | +| 查询申请列表 | GET | `/dormApplication/dormApplication/list` | `dormApplication:dormApplication:list` | 分页查询宿舍申请列表 | +| 查询申请详情 | GET | `/dormApplication/dormApplication/{id}` | `dormApplication:dormApplication:query` | 根据 ID 查询申请详情 | +| 新增申请 | POST | `/dormApplication/dormApplication` | `dormApplication:dormApplication:add` | 创建新的宿舍申请 | +| 修改申请 | PUT | `/dormApplication/dormApplication` | `dormApplication:dormApplication:edit` | 更新申请信息 | +| 删除申请 | DELETE | `/dormApplication/dormApplication/{ids}` | `dormApplication:dormApplication:remove` | 批量删除申请 | +| 导出申请 | POST | `/dormApplication/dormApplication/export` | `dormApplication:dormApplication:export` | 导出申请列表 | + +#### 业务逻辑接口 + +| 接口名称 | HTTP 方法 | 路径 | 权限要求 | 功能描述 | +| ------------ | --------- | ------------------------- | -------- | ---------- | +| 获取学生选择器 | GET | `/dormApplication/dormApplication/studentOptions` | `dormApplication:dormApplication:list` | 获取学生选择器选项 | +| 获取学院审批人 | GET | `/dormApplication/dormApplication/getCollegeApprovers` | `dormApplication:dormApplication:list` | 获取二级学院审批人列表 | +| 获取学工处审批人 | GET | `/dormApplication/dormApplication/getStudentAffairsApprovers` | `dormApplication:dormApplication:list` | 获取学工处审批人列表 | + +### 通用响应格式 + +#### 成功响应 + +```json +{ + "code": 200, + "msg": "操作成功", + "data": { + // 响应数据 + } +} +``` + +#### 分页响应 + +```json +{ + "code": 200, + "msg": "查询成功", + "rows": [ + // 数据列表 + ], + "total": 100 +} +``` + +#### 错误响应 + +```json +{ + "code": 400001, + "msg": "参数错误", + "data": null +} +``` + +--- + +## 🔍 接口详细说明 + +### 1. 查询宿舍申请列表 + +#### 接口信息 + +**接口地址**: `GET /dormApplication/dormApplication/list` + +**接口描述**: 分页查询宿舍申请列表,支持多条件筛选和权限过滤 + +**权限要求**: `dormApplication:dormApplication:list` + +#### 请求参数 + +| 参数名 | 类型 | 必填 | 说明 | 示例值 | +| --------- | ------- | ---- | -------------------- | ------------ | +| pageNum | Integer | N | 页码,默认 1 | 1 | +| pageSize | Integer | N | 每页大小,默认 10 | 10 | +| applicationName | String | N | 申请名称 | "张三调宿申请" | +| applicationType | String | N | 申请类型:1-调宿,2-退宿 | "1" | +| applicationStatus | String | N | 审批状态 | "draft" | +| studentName | String | N | 学生姓名 | "张三" | +| studentNo | String | N | 学号 | "20240001" | +| collegeName | String | N | 学院名称 | "计算机学院" | +| majorName | String | N | 专业名称 | "软件工程" | +| className | String | N | 班级名称 | "软工2024班" | +| applyTime | String | N | 申请时间 | "2025-01-15" | + +#### 请求示例 + +```bash +curl -X GET "http://localhost:8080/dormApplication/dormApplication/list?pageNum=1&pageSize=10&applicationType=1" \ + -H "Authorization: Bearer {token}" \ + -H "Content-Type: application/json" +``` + +#### 响应示例 + +```json +{ + "code": 200, + "msg": "查询成功", + "rows": [ + { + "id": 1, + "applicationName": "张三调宿申请", + "applicationType": "1", + "applicationStatus": "draft", + "studentName": "张三", + "studentNo": "20240001", + "collegeName": "计算机学院", + "majorName": "软件工程", + "className": "软工2024班", + "applyTime": "2025-01-15 10:00:00", + "createBy": 1, + "createTime": "2025-01-15 10:00:00" + } + ], + "total": 1 +} +``` + +#### 错误码 + +| 错误码 | 说明 | 解决方案 | +| ------ | -------- | -------------------- | +| 401001 | 未登录 | 请先登录获取 token | +| 403001 | 无权限 | 请联系管理员分配权限 | +| 400001 | 参数错误 | 检查请求参数格式 | + +--- + +### 2. 查询申请详情 + +#### 接口信息 + +**接口地址**: `GET /dormApplication/dormApplication/{id}` + +**接口描述**: 根据 ID 查询宿舍申请的详细信息 + +**权限要求**: `dormApplication:dormApplication:query` + +#### 路径参数 + +| 参数名 | 类型 | 必填 | 说明 | 示例值 | +| ------ | ---- | ---- | -------- | ------ | +| id | Long | Y | 申请单ID | 1 | + +#### 请求示例 + +```bash +curl -X GET "http://localhost:8080/dormApplication/dormApplication/1" \ + -H "Authorization: Bearer {token}" \ + -H "Content-Type: application/json" +``` + +#### 响应示例 + +```json +{ + "code": 200, + "msg": "查询成功", + "data": { + "id": 1, + "applicationName": "张三调宿申请", + "applicationType": "1", + "applicationStatus": "draft", + "studentId": 1001, + "studentName": "张三", + "studentNo": "20240001", + "collegeId": 101, + "collegeName": "计算机学院", + "majorId": 201, + "majorName": "软件工程", + "classId": 301, + "className": "软工2024班", + "currentCampusId": 1, + "currentCampusName": "主校区", + "currentBuildingId": 11, + "currentBuildingName": "1号楼", + "currentRoomId": 111, + "currentRoomNo": "101", + "currentBedId": 1111, + "currentBedNo": "01", + "targetCampusId": 1, + "targetCampusName": "主校区", + "targetBuildingId": 12, + "targetBuildingName": "2号楼", + "targetRoomId": 121, + "targetRoomNo": "201", + "targetBedId": 1211, + "targetBedNo": "01", + "reasonDetail": "宿舍环境不好,希望调换", + "applyTime": "2025-01-15 10:00:00", + "remark": "备注信息" + } +} +``` + +#### 错误码 + +| 错误码 | 说明 | 解决方案 | +| ------ | ---------- | -------------------- | +| 404001 | 数据不存在 | 检查 ID 是否正确 | +| 401001 | 未登录 | 请先登录获取 token | +| 403001 | 无权限 | 请联系管理员分配权限 | + +--- + +### 3. 新增宿舍申请 + +#### 接口信息 + +**接口地址**: `POST /dormApplication/dormApplication` + +**接口描述**: 创建新的宿舍申请记录(调宿或退宿) + +**权限要求**: `dormApplication:dormApplication:add` + +#### 请求参数 + +| 参数名 | 类型 | 必填 | 说明 | 示例值 | +| -------- | ------- | ---- | ---------------------------- | ---------- | +| applicationName | String | Y | 申请名称 | "张三调宿申请" | +| applicationType | String | Y | 申请类型:1-调宿,2-退宿 | "1" | +| studentId | Long | Y | 申请学生ID | 1001 | +| studentName | String | Y | 学生姓名 | "张三" | +| studentNo | String | Y | 学号 | "20240001" | +| collegeId | Long | Y | 学院ID | 101 | +| majorId | Long | Y | 专业ID | 201 | +| classId | Long | Y | 班级ID | 301 | +| currentCampusId | Long | N | 当前校区ID(调宿必填) | 1 | +| currentBuildingId | Long | N | 当前楼栋ID(调宿必填) | 11 | +| currentRoomId | Long | N | 当前房间ID(调宿必填) | 111 | +| currentBedId | Long | Y | 当前床位ID | 1111 | +| targetCampusId | Long | N | 目标校区ID(调宿必填) | 1 | +| targetBuildingId | Long | N | 目标楼栋ID(调宿必填) | 12 | +| targetRoomId | Long | N | 目标房间ID(调宿必填) | 121 | +| targetBedId | Long | N | 目标床位ID(调宿必填) | 1211 | +| reasonDetail | String | Y | 详细原因说明 | "宿舍环境不好" | +| applyTime | String | Y | 申请时间 | "2025-01-15 10:00:00" | +| attachmentOssIds | String | N | 附件OSS ID列表 | "oss1,oss2" | +| remark | String | N | 备注信息 | "备注内容" | + +#### 请求体 + +```json +{ + "applicationName": "张三调宿申请", + "applicationType": "1", + "studentId": 1001, + "studentName": "张三", + "studentNo": "20240001", + "collegeId": 101, + "majorId": 201, + "classId": 301, + "currentCampusId": 1, + "currentBuildingId": 11, + "currentRoomId": 111, + "currentBedId": 1111, + "targetCampusId": 1, + "targetBuildingId": 12, + "targetRoomId": 121, + "targetBedId": 1211, + "reasonDetail": "宿舍环境不好,希望调换到更好的宿舍", + "applyTime": "2025-01-15 10:00:00", + "remark": "备注信息" +} +``` + +#### 请求示例 + +```bash +curl -X POST "http://localhost:8080/dormApplication/dormApplication" \ + -H "Authorization: Bearer {token}" \ + -H "Content-Type: application/json" \ + -d '{ + "applicationName": "张三调宿申请", + "applicationType": "1", + "studentId": 1001, + "studentName": "张三", + "studentNo": "20240001", + "collegeId": 101, + "majorId": 201, + "classId": 301, + "currentCampusId": 1, + "currentBuildingId": 11, + "currentRoomId": 111, + "currentBedId": 1111, + "targetCampusId": 1, + "targetBuildingId": 12, + "targetRoomId": 121, + "targetBedId": 1211, + "reasonDetail": "宿舍环境不好,希望调换到更好的宿舍", + "applyTime": "2025-01-15 10:00:00" + }' +``` + +#### 响应示例 + +```json +{ + "code": 200, + "msg": "新增成功", + "data": { + "id": 3 + } +} +``` + +#### 错误码 + +| 错误码 | 说明 | 解决方案 | +| ------ | ---------- | -------------------- | +| 400001 | 参数错误 | 检查必填参数是否缺失 | +| 400002 | 数据已存在 | 检查唯一字段是否重复 | +| 401001 | 未登录 | 请先登录获取 token | +| 403001 | 无权限 | 请联系管理员分配权限 | + +#### 参数验证规则 + +- `applicationName`: 长度 2-100 字符,不能为空 +- `applicationType`: 只能为 "1" 或 "2" +- `studentId`: 必须为正整数 +- `studentName`: 长度 2-50 字符,不能为空 +- `studentNo`: 长度 6-20 字符,不能为空 +- `reasonDetail`: 长度 5-500 字符,不能为空 +- `applyTime`: 必须为有效的时间格式 + +--- + +### 4. 修改宿舍申请 + +#### 接口信息 + +**接口地址**: `PUT /dormApplication/dormApplication` + +**接口描述**: 更新宿舍申请信息(仅限草稿状态的申请) + +**权限要求**: `dormApplication:dormApplication:edit` + +#### 请求参数 + +| 参数名 | 类型 | 必填 | 说明 | 示例值 | +| -------- | ------- | ---- | ---------------------------- | ---------- | +| id | Long | Y | 申请单ID | 1 | +| applicationName | String | Y | 申请名称 | "张三调宿申请" | +| applicationType | String | Y | 申请类型:1-调宿,2-退宿 | "1" | +| studentId | Long | Y | 申请学生ID | 1001 | +| reasonDetail | String | Y | 详细原因说明 | "宿舍环境不好" | +| targetBedId | Long | N | 目标床位ID(调宿时必填) | 1211 | +| remark | String | N | 备注信息 | "修改后的备注" | + +#### 请求体 + +```json +{ + "id": 1, + "applicationName": "张三调宿申请(修改)", + "applicationType": "1", + "studentId": 1001, + "reasonDetail": "宿舍环境不好,希望调换到更好的宿舍(修改原因)", + "targetBedId": 1211, + "remark": "修改后的备注" +} +``` + +#### 请求示例 + +```bash +curl -X PUT "http://localhost:8080/dormApplication/dormApplication" \ + -H "Authorization: Bearer {token}" \ + -H "Content-Type: application/json" \ + -d '{ + "id": 1, + "applicationName": "张三调宿申请(修改)", + "reasonDetail": "宿舍环境不好,希望调换到更好的宿舍(修改原因)", + "targetBedId": 1211 + }' +``` + +#### 响应示例 + +```json +{ + "code": 200, + "msg": "修改成功", + "data": null +} +``` + +#### 错误码 + +| 错误码 | 说明 | 解决方案 | +| ------ | ---------- | -------------------- | +| 404001 | 数据不存在 | 检查 ID 是否正确 | +| 400001 | 参数错误 | 检查参数格式是否正确 | +| 403002 | 状态不允许 | 只能修改草稿状态的申请 | +| 401001 | 未登录 | 请先登录获取 token | +| 403001 | 无权限 | 请联系管理员分配权限 | + +--- + +### 5. 删除宿舍申请 + +#### 接口信息 + +**接口地址**: `DELETE /dormApplication/dormApplication/{ids}` + +**接口描述**: 批量删除宿舍申请(仅限草稿状态的申请) + +**权限要求**: `dormApplication:dormApplication:remove` + +#### 路径参数 + +| 参数名 | 类型 | 必填 | 说明 | 示例值 | +| ------ | ------ | ---- | ----------------------------- | ------- | +| ids | String | Y | 申请单ID 列表,多个用逗号分隔 | "1,2,3" | + +#### 请求示例 + +```bash +curl -X DELETE "http://localhost:8080/dormApplication/dormApplication/1,2,3" \ + -H "Authorization: Bearer {token}" \ + -H "Content-Type: application/json" +``` + +#### 响应示例 + +```json +{ + "code": 200, + "msg": "删除成功", + "data": null +} +``` + +#### 错误码 + +| 错误码 | 说明 | 解决方案 | +| ------ | ---------- | -------------------- | +| 400001 | 参数错误 | 检查 ID 格式是否正确 | +| 403003 | 状态不允许 | 只能删除草稿状态的申请 | +| 401001 | 未登录 | 请先登录获取 token | +| 403001 | 无权限 | 请联系管理员分配权限 | + +--- + +## 🎯 业务逻辑接口 + +### 1. 获取学生选择器选项 + +#### 接口信息 + +**接口地址**: `GET /dormApplication/dormApplication/studentOptions` + +**接口描述**: 获取学生选择器选项列表,支持按学号、姓名检索 + +**权限要求**: `dormApplication:dormApplication:list` + +#### 请求参数 + +| 参数名 | 类型 | 必填 | 说明 | 示例值 | +| -------- | ------- | ---- | -------------------- | ------------ | +| pageNum | Integer | N | 页码,默认 1 | 1 | +| pageSize | Integer | N | 每页大小,默认 10 | 10 | +| studentName | String | N | 学生姓名 | "张三" | +| studentNo | String | N | 学号 | "20240001" | + +#### 请求示例 + +```bash +curl -X GET "http://localhost:8080/dormApplication/dormApplication/studentOptions?pageNum=1&pageSize=10&studentName=张三" \ + -H "Authorization: Bearer {token}" \ + -H "Content-Type: application/json" +``` + +#### 响应示例 + +```json +{ + "code": 200, + "msg": "查询成功", + "rows": [ + { + "studentId": 1001, + "studentName": "张三", + "studentNo": "20240001", + "collegeName": "计算机学院", + "majorName": "软件工程", + "className": "软工2024班" + } + ], + "total": 1 +} +``` + +--- + +### 2. 获取二级学院审批人列表 + +#### 接口信息 + +**接口地址**: `GET /dormApplication/dormApplication/getCollegeApprovers` + +**接口描述**: 根据学院ID获取二级学院审批人列表 + +**权限要求**: `dormApplication:dormApplication:list` + +#### 请求参数 + +| 参数名 | 类型 | 必填 | 说明 | 示例值 | +| -------- | ------- | ---- | -------------------- | ------------ | +| collegeId | Long | Y | 学院ID | 101 | + +#### 请求示例 + +```bash +curl -X GET "http://localhost:8080/dormApplication/dormApplication/getCollegeApprovers?collegeId=101" \ + -H "Authorization: Bearer {token}" \ + -H "Content-Type: application/json" +``` + +#### 响应示例 + +```json +{ + "code": 200, + "msg": "查询成功", + "data": [ + { + "userId": 201, + "userName": "lisi", + "nickName": "李四", + "deptId": 101 + } + ] +} +``` + +--- + +### 3. 获取学工处审批人列表 + +#### 接口信息 + +**接口地址**: `GET /dormApplication/dormApplication/getStudentAffairsApprovers` + +**接口描述**: 获取学工处审批人列表 + +**权限要求**: `dormApplication:dormApplication:list` + +#### 请求示例 + +```bash +curl -X GET "http://localhost:8080/dormApplication/dormApplication/getStudentAffairsApprovers" \ + -H "Authorization: Bearer {token}" \ + -H "Content-Type: application/json" +``` + +#### 响应示例 + +```json +{ + "code": 200, + "msg": "查询成功", + "data": [ + { + "userId": 301, + "userName": "wangwu", + "nickName": "王五", + "deptId": 999 + } + ] +} +``` + +--- + +## 📋 数据字典 + +### 申请类型枚举 + +| 值 | 名称 | 说明 | +| --- | ---- | -------- | +| 1 | 调宿申请 | 学生申请更换床位 | +| 2 | 退宿申请 | 学生申请退宿 | + +### 申请状态枚举 + +| 值 | 名称 | 说明 | +| ------ | ---- | -------- | +| draft | 草稿 | 申请已创建,待提交 | +| waiting | 待审核 | 申请已提交,等待审批 | +| finish | 已完成 | 审批通过,操作已执行 | +| cancel | 已撤销 | 申请被撤销 | +| back | 已退回 | 审批退回 | +| invalid | 已作废 | 申请已作废 | +| termination | 已终止 | 流程已终止 | + +### 学生状态枚举 + +| 值 | 名称 | 说明 | +| --- | ---- | -------- | +| 1 | 在校生 | 正常在校学生 | +| 2 | 休学 | 学生休学状态 | +| 3 | 退学 | 学生退学状态 | +| 4 | 毕业 | 学生已毕业 | + +--- + +## 🔄 版本历史 + +### v1.0.0 - 2025-01-15 + +- ✅ 初始版本发布 +- ✅ 完成宿舍申请基础 CRUD 接口 +- ✅ 完成学生选择器接口 +- ✅ 完成审批人获取接口 +- ✅ 集成工作流审批流程 +- ✅ 实现调宿和退宿业务逻辑 + +--- + +## 📖 相关文档 + +- **数据库设计**:[数据库设计文档](../设计文档/database/) +- **业务需求**:[业务需求文档](../需求文档/) +- **工作流文档**:[工作流开发规范](../../.cursor/cursorrules-workflow.md) +- **退宿逻辑说明**:[退宿逻辑详细说明](./退宿逻辑详细说明.md) +- **API基础规范**:[API文档基础规范](./API文档基础规范.md) + +--- + +## 📞 联系方式 + +**开发团队**:校园管理系统开发团队 + +**负责人**:zlwu + +**邮箱**:dev@hzdyjs.edu.cn + +**文档维护**:实时更新,如有问题请及时反馈 + +--- + +**📌 重要提醒**: + +1. 所有接口都需要先登录获取 token +2. 请求头必须包含 `Authorization: Bearer {token}` +3. 参数验证失败会返回详细的错误信息 +4. 生产环境请使用 HTTPS 协议 +5. 申请状态为非草稿状态时,只能查看不能修改或删除 +6. 退宿和调宿操作通过工作流审批完成后自动执行,无需手动调用业务接口 diff --git a/docs/开发文档/退宿逻辑详细说明.md b/docs/开发文档/退宿逻辑详细说明.md new file mode 100644 index 00000000..e88809a6 --- /dev/null +++ b/docs/开发文档/退宿逻辑详细说明.md @@ -0,0 +1,175 @@ +# 学生退宿逻辑详细说明 + +## 概述 + +本文档详细描述了学生宿舍管理系统中退宿操作的具体实现逻辑,包括前端界面、后端服务调用、数据库操作等各个层面的详细说明。 + +## 业务流程 + +### 1. 前端界面操作 + +**文件位置**: `web/src/views/xg/xsjcxx/xssssq/index.vue` + +学生可以通过前端界面提交退宿申请: + +- **申请类型**: `applicationType = "2"` (退宿申请) +- **申请状态**: 初始状态为 `draft` (草稿) +- **关键字段**: + - `currentBedId`: 当前床位ID + - `studentId`: 学生ID + - `classId`: 班级ID + +### 2. 审批流程 + +退宿申请需要通过工作流审批,流程代码:`hzdyjs-tsts` + +审批完成后,当 `applicationStatus = "finish"` 时,触发实际的退宿操作。 + +## 核心退宿逻辑 + +### 方法调用链 + +```java +// 1. 工作流监听器触发 +@EventListener(condition = "#processEvent.flowCode=='hzdyjs-tsts'") +public void processHandler(ProcessEvent processEvent) { + // 当审批完成时,执行调宿/退宿操作 + if ("finish".equals(processEvent.getStatus())) { + executeApplicationOperation(xgDormApplication); + } +} + +// 2. 执行申请操作 +private void executeApplicationOperation(XgDormApplication application) { + if ("2".equals(application.getApplicationType())) { + // 退宿申请 - 调用退宿接口 + List bedIds = List.of(application.getCurrentBedId()); + Boolean stayawayResult = xgDormBedStudentService.classTeacherStayawaySelectedBeds( + bedIds, // 床位ID列表 + application.getClassId() // 班级ID + ); + } +} + +// 3. 实际退宿实现 +@Override +@Transactional(rollbackFor = Exception.class) +public Boolean classTeacherStayawaySelectedBeds(List bedIds, Long classId) +``` + +### 详细SQL操作 + +**文件位置**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java` + +退宿操作涉及三个表的更新,按顺序执行: + +#### 1. 更新学生基本信息表 (`xg_student_msg`) + +```sql +-- 清理学生宿舍信息 +UPDATE xg_student_msg +SET + dormitory_building = NULL, -- 清空宿舍楼信息 + dormitory_room = NULL, -- 清空宿舍房间信息 + ext_field1 = NULL, -- 清空床位ID(存储在扩展字段1中) + update_time = NOW(), -- 更新时间 + update_by = ? -- 操作人ID +WHERE + ext_field1 IN (?) -- 床位ID列表(字符串形式) + AND deleted = 0 -- 未删除 + AND student_status = '1'; -- 在校生状态 +``` + +**说明**: +- `ext_field1` 字段存储床位ID +- 只处理在校生 (`student_status = '1'`) +- 将宿舍相关信息全部清空 + +#### 2. 更新床位学生分配表 (`xg_dorm_bed_student`) + +```sql +-- 标记床位为退宿状态 +UPDATE xg_dorm_bed_student +SET + status = '1', -- 状态:1-退宿 + checkout_time = NOW(), -- 退宿时间 + update_time = NOW(), -- 更新时间 + update_by = ? -- 操作人ID +WHERE + bed_id IN (?) -- 床位ID列表 + AND deleted = '0' -- 未删除 + AND status = '0'; -- 当前状态:0-正常入住 +``` + +**说明**: +- 状态变更:`0`(正常入住) → `1`(退宿) +- 记录退宿时间 `checkout_time` +- 只处理当前正常入住的床位 + +#### 3. 更新床位状态表 (`xg_dorm_bed`) + +```sql +-- 将床位状态改为闲置 +UPDATE xg_dorm_bed +SET + bed_status = '0' -- 床位状态:0-闲置 +WHERE + id IN (?) -- 床位ID列表 + AND deleted = '0'; -- 未删除 +``` + +**说明**: +- 床位状态变更为闲置,供其他学生使用 +- 不更新时间字段,只修改状态 + +## 数据状态说明 + +### 床位状态 (`bed_status`) +- `0`: 闲置 - 可分配给新学生 +- `1`: 已分配 - 已有学生入住 + +### 床位学生分配状态 (`status`) +- `0`: 正常入住 - 学生正在使用该床位 +- `1`: 退宿 - 学生已退宿,床位释放 + +### 学生状态 (`student_status`) +- `1`: 在校生 - 正常在校学生 +- `2`: 休学 +- `3`: 退学 +- `4`: 毕业 + +## 事务处理 + +所有退宿操作在一个事务中执行: + +```java +@Transactional(rollbackFor = Exception.class) +public Boolean classTeacherStayawaySelectedBeds(List bedIds, Long classId) +``` + +如果任何一步操作失败,整个退宿操作会回滚,确保数据一致性。 + +## 异常处理 + +- 床位ID列表为空时抛出 `IllegalArgumentException` +- SQL执行异常时抛出 `RuntimeException` +- 记录详细的日志信息用于问题排查 + +## 适用场景 + +此退宿逻辑适用于以下场景: + +1. **学生主动退宿申请** - 通过前端界面提交退宿申请,经审批后执行 +2. **学生退学处理** - 学生退学时自动释放床位 +3. **学生毕业处理** - 学生毕业时自动释放床位 +4. **学生休学处理** - 学生休学时释放床位 + +## 后续使用建议 + +在实现学生退学、毕业、休学脚本时,可以参考此退宿逻辑的SQL操作顺序: + +1. 首先更新 `xg_student_msg` 表清空宿舍信息 +2. 然后更新 `xg_dorm_bed_student` 表标记退宿状态 +3. 最后更新 `xg_dorm_bed` 表释放床位 + +**注意**: 不同场景可能需要根据具体业务逻辑调整条件判断。 diff --git a/web/src/api/xg/xsjcxx/studentClassChange/index.ts b/web/src/api/xg/xsjcxx/studentClassChange/index.ts new file mode 100644 index 00000000..67132569 --- /dev/null +++ b/web/src/api/xg/xsjcxx/studentClassChange/index.ts @@ -0,0 +1,68 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { XgStudentClassChangeVo, XgStudentClassChangeQuery, XgStudentClassChangeBo, R, TableDataInfo } from './types'; + +/** + * 查询学生换班级列表 + */ +export function listStudentClassChange(query?: XgStudentClassChangeQuery): AxiosPromise>> { + return request({ + url: '/studentMsg/studentClassChange/list', + method: 'get', + params: query + }); +} + +/** + * 查询学生换班级详细 + */ +export function getStudentClassChange(id: string | number): AxiosPromise> { + return request({ + url: `/studentMsg/studentClassChange/${id}`, + method: 'get' + }); +} + +/** + * 新增学生换班级 + */ +export function addStudentClassChange(data: XgStudentClassChangeBo): AxiosPromise> { + return request({ + url: '/studentMsg/studentClassChange', + method: 'post', + data: data + }); +} + +/** + * 修改学生换班级 + */ +export function updateStudentClassChange(data: XgStudentClassChangeBo): AxiosPromise> { + return request({ + url: '/studentMsg/studentClassChange', + method: 'put', + data: data + }); +} + +/** + * 删除学生换班级 + */ +export function delStudentClassChange(ids: string | number | Array): AxiosPromise> { + return request({ + url: `/studentMsg/studentClassChange/${ids}`, + method: 'delete' + }); +} + +/** + * 导出学生换班级 + */ +export function exportStudentClassChange(query?: XgStudentClassChangeQuery): AxiosPromise { + return request({ + url: '/studentMsg/studentClassChange/export', + method: 'post', + params: query, + responseType: 'blob' + }); +} diff --git a/web/src/api/xg/xsjcxx/studentClassChange/types.ts b/web/src/api/xg/xsjcxx/studentClassChange/types.ts new file mode 100644 index 00000000..d163a78d --- /dev/null +++ b/web/src/api/xg/xsjcxx/studentClassChange/types.ts @@ -0,0 +1,54 @@ +export interface XgStudentClassChangeVo { + id?: number; + studentId?: number; + studentNo?: string; + studentName?: string; + originalCollegeName?: string; + originalMajorName?: string; + originalClassName?: string; + newCollegeName?: string; + newMajorName?: string; + newClassName?: string; + changeReason?: string; + effectiveTime?: Date; + status?: string; + createBy?: number; + createTime?: Date; +} + +export interface XgStudentClassChangeQuery { + pageNum?: number; + pageSize?: number; + studentNo?: string; + studentName?: string; + status?: string; + params?: any; +} + +export interface XgStudentClassChangeBo { + id?: number; + studentId?: number; + studentNo?: string; + studentName?: string; + originalCollegeId?: number; + originalCollegeName?: string; + originalMajorId?: number; + originalMajorName?: string; + originalClassId?: number; + originalClassCode?: string; + originalClassName?: string; + newCollegeId?: number; + newCollegeName?: string; + newMajorId?: number; + newMajorName?: string; + newClassId?: number; + newClassCode?: string; + newClassName?: string; + changeReason?: string; + changeType?: string; + changeTime?: Date; + effectiveTime?: Date; + status?: string; + createDept?: number; + remark?: string; +} diff --git a/web/src/views/xg/xsjcxx/studentClassChange/index.vue b/web/src/views/xg/xsjcxx/studentClassChange/index.vue new file mode 100644 index 00000000..8026dcac --- /dev/null +++ b/web/src/views/xg/xsjcxx/studentClassChange/index.vue @@ -0,0 +1,328 @@ + + + + + diff --git a/web/src/views/xg/xsjcxx/studentMsg/studentMsg/index.vue b/web/src/views/xg/xsjcxx/studentMsg/studentMsg/index.vue index f61040f9..1b5a49cd 100644 --- a/web/src/views/xg/xsjcxx/studentMsg/studentMsg/index.vue +++ b/web/src/views/xg/xsjcxx/studentMsg/studentMsg/index.vue @@ -127,6 +127,9 @@ 毕业 + + 换班级 + @@ -915,6 +918,117 @@ + + + + + + +
+
+
+ 学号: + {{ changeClassForm.studentNo }} +
+
+ 姓名: + {{ changeClassForm.studentName }} +
+
+ 性别: + {{ changeClassForm.gender === '0' ? '男' : changeClassForm.gender === '1' ? '女' : '未知' }} +
+
+
+
+ 原学院: + {{ changeClassForm.originalCollegeName }} +
+
+ 原专业: + {{ changeClassForm.originalMajorName }} +
+
+ 原班级: + {{ changeClassForm.originalClassName }} +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ @@ -1739,6 +1853,7 @@ const deptOptions = ref([]); const rawDeptOptions = ref([]); const majorOptions = ref([]); const classOptions = ref([]); +const collegeOptions = ref([]); // 学院选项 const regionOptions = ref([]); // 行政区划选项 const dormBuildingOptions = ref([]); // 宿舍楼栋选项 const currentSelectedNode = ref(null); // 当前选中的部门节点 @@ -1749,6 +1864,29 @@ const classStatusMap = new Map(); let classStatusLoaded = false; let classStatusLoading = false; +// 换班级相关 +const changeClassDialog = reactive({ + visible: false, + title: '换班级申请' +}); +const changeClassForm = reactive({ + studentId: '', + studentNo: '', + studentName: '', + gender: '', + originalCollegeId: '', + originalCollegeName: '', + originalMajorId: '', + originalMajorName: '', + originalClassId: '', + originalClassName: '', + newCollegeId: '', + newMajorId: '', + newClassId: '', + effectiveTime: '', + changeReason: '' +}); + // 计算属性:判断是否显示新增按钮(只有班级节点才显示) const showAddButton = computed(() => { if (!currentSelectedNode.value) { @@ -1776,6 +1914,7 @@ const deptTreeRef = ref(); const queryFormRef = ref(); const studentFormRef = ref(); const statusChangeFormRef = ref(); +const changeClassFormRef = ref(); const uploadRef = ref(); const initPassword = ref('Hzdyjs123456!@#'); @@ -1956,6 +2095,15 @@ const statusChangeRules = reactive({ reason: [{ required: true, message: '异动原因不能为空', trigger: 'blur' }] }); +// 换班级表单验证规则 +const changeClassRules = reactive({ + newCollegeId: [{ required: true, message: '请选择新学院', trigger: 'change' }], + newMajorId: [{ required: true, message: '请选择新专业', trigger: 'change' }], + newClassId: [{ required: true, message: '请选择新班级', trigger: 'change' }], + effectiveTime: [{ required: true, message: '请选择生效时间', trigger: 'blur' }], + changeReason: [{ required: true, message: '换班级原因不能为空', trigger: 'blur' }] +}); + // 异动类型选项通过字典表获取:xg_stu_status const queryParams = reactive({ @@ -2990,6 +3138,190 @@ const submitStatusChange = async () => { }); }; +/** 打开换班级弹窗 */ +const openChangeClassModal = async () => { + if (ids.value.length !== 1) { + ElMessage.warning('请选择一个学生进行换班级操作'); + return; + } + + const studentId = ids.value[0]; + const selectedStudent = studentList.value.find((student) => student.id === studentId); + + if (!selectedStudent) { + ElMessage.error('未找到选中的学生信息'); + return; + } + + try { + // 获取学生详细信息 + const res = await service.get(`/studentMsg/studentMsg/${studentId}`); + if (res && res.code === 200) { + const studentData = res.data; + + // 填充学生基本信息 + changeClassForm.studentId = studentData.id; + changeClassForm.studentNo = studentData.studentNo; + changeClassForm.studentName = studentData.name; + changeClassForm.gender = studentData.gender; + + // 填充原班级信息 + changeClassForm.originalCollegeId = studentData.collegeId; + changeClassForm.originalCollegeName = studentData.collegeName; + changeClassForm.originalMajorId = studentData.majorId; + changeClassForm.originalMajorName = studentData.majorName; + changeClassForm.originalClassId = studentData.classId; + changeClassForm.originalClassName = studentData.className; + + // 重置新班级选择 + changeClassForm.newCollegeId = ''; + changeClassForm.newMajorId = ''; + changeClassForm.newClassId = ''; + changeClassForm.effectiveTime = ''; + changeClassForm.changeReason = ''; + + // 获取学院选项 + await loadCollegeOptions(); + + changeClassDialog.visible = true; + } else { + ElMessage.error(res?.msg || '获取学生信息失败'); + } + } catch (error) { + console.error('获取学生信息失败:', error); + ElMessage.error('获取学生信息失败,请重试'); + } +}; + +/** 加载学院选项 */ +const loadCollegeOptions = async () => { + try { + const res = await service.get('/system/dept/list', { + params: { + parentId: 101, // 二级学院节点 + deptCategory: 'xy-' // 学院标识 + } + }); + if (res && res.code === 200) { + collegeOptions.value = (res.data || []).map((dept: any) => ({ + id: dept.deptId, + label: dept.deptName + })); + } + } catch (error) { + console.error('获取学院列表失败:', error); + collegeOptions.value = []; + } +}; + +/** 学院选择变更 */ +const handleCollegeChange = async (collegeId: string) => { + changeClassForm.newMajorId = ''; + changeClassForm.newClassId = ''; + majorOptions.value = []; + classOptions.value = []; + + if (collegeId) { + try { + const res = await service.get('/system/dept/list', { + params: { + parentId: collegeId, + deptCategory: 'zy-' // 专业标识 + } + }); + if (res && res.code === 200) { + majorOptions.value = (res.data || []).map((dept: any) => ({ + id: dept.deptId, + label: dept.deptName + })); + } + } catch (error) { + console.error('获取专业列表失败:', error); + majorOptions.value = []; + } + } +}; + +/** 换班级专业选择变更 */ +const handleChangeClassMajorChange = async (majorId: string) => { + changeClassForm.newClassId = ''; + classOptions.value = []; + + if (majorId) { + try { + const res = await service.get('/system/dept/list', { + params: { + parentId: majorId, + deptCategory: 'bj-' // 班级标识 + } + }); + if (res && res.code === 200) { + classOptions.value = (res.data || []).map((dept: any) => ({ + id: dept.deptId, + label: dept.deptName + })); + } + } catch (error) { + console.error('获取班级列表失败:', error); + classOptions.value = []; + } + } +}; + +/** 提交换班级申请 */ +const submitChangeClass = async () => { + const formRef = changeClassFormRef.value; + if (!formRef) return; + + await formRef.validate(async (valid: boolean) => { + if (valid) { + try { + // 检查是否选择了不同的班级 + if (changeClassForm.newClassId === changeClassForm.originalClassId) { + ElMessage.warning('请选择不同的班级进行换班'); + return; + } + + const submitData = { + studentId: changeClassForm.studentId, + studentNo: changeClassForm.studentNo, + studentName: changeClassForm.studentName, + originalCollegeId: changeClassForm.originalCollegeId, + originalCollegeName: changeClassForm.originalCollegeName, + originalMajorId: changeClassForm.originalMajorId, + originalMajorName: changeClassForm.originalMajorName, + originalClassId: changeClassForm.originalClassId, + originalClassCode: '', // 可以根据需要获取 + originalClassName: changeClassForm.originalClassName, + newCollegeId: changeClassForm.newCollegeId, + newCollegeName: collegeOptions.value.find((c) => c.id === changeClassForm.newCollegeId)?.label || '', + newMajorId: changeClassForm.newMajorId, + newMajorName: majorOptions.value.find((m) => m.id === changeClassForm.newMajorId)?.label || '', + newClassId: changeClassForm.newClassId, + newClassName: classOptions.value.find((c) => c.id === changeClassForm.newClassId)?.label || '', + changeReason: changeClassForm.changeReason, + changeType: '1', // 主动申请 + changeTime: proxy?.parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}'), + effectiveTime: changeClassForm.effectiveTime, + status: '0' // 未处理状态 + }; + + const res = await service.post('/studentMsg/studentClassChange', submitData); + if (res && res.code === 200) { + ElMessage.success('换班级申请提交成功'); + changeClassDialog.visible = false; + await getList(); // 刷新列表 + } else { + ElMessage.error(res?.msg || '提交失败'); + } + } catch (error) { + console.error('提交换班级申请失败:', error); + ElMessage.error('提交失败,请重试'); + } + } + }); +}; + /** 导入按钮操作 */ const handleImport = () => { if (!showAddButton.value) { @@ -3780,4 +4112,36 @@ onMounted(() => { margin-bottom: 0; margin-right: 0; } + +/* 学生信息网格样式 */ +.student-info-grid { + display: flex; + flex-direction: column; + gap: 16px; +} + +.info-row { + display: flex; + gap: 24px; + flex-wrap: wrap; +} + +.info-item { + display: flex; + align-items: center; + min-width: 120px; + flex: 1; +} + +.info-label { + font-weight: 500; + color: #606266; + margin-right: 8px; + white-space: nowrap; +} + +.info-value { + color: #303133; + font-weight: 400; +} diff --git a/学生换班级/README.md b/学生换班级/README.md new file mode 100644 index 00000000..5ea83a73 --- /dev/null +++ b/学生换班级/README.md @@ -0,0 +1,95 @@ +# 学生换班级模块 + +## 📁 文件夹说明 + +本文件夹包含学生换班级业务模块的相关设计文档和数据库脚本。 + +## 📋 文件清单 + +### 🗄️ 数据库设计 +- **[学生换班级记录表.sql](学生换班级记录表.sql)** - 学生换班级记录表的完整建表SQL脚本 +- **[学生换班级记录表设计说明.md](学生换班级记录表设计说明.md)** - 详细的表结构设计说明文档 + +## 🎯 业务概述 + +学生换班级记录表用于记录学校所有学生换班级的操作历史,包括: +- 学生主动申请换班级 +- 学校根据教学需要调整班级 +- 学生转专业导致的班级变化 +- 其他特殊情况的换班级操作 + +## 🔑 核心功能 + +### 1. 完整的换班级记录 +- 记录学生的原班级和新班级信息 +- 保存换班级的原因和类型 +- 记录换班级的时间和生效时间 +- 状态管理:未处理/已处理 + +### 2. 前端界面支持 +- 学生信息管理页面集成换班级功能 +- 单选学生进行换班级操作 +- 三级级联选择新班级(学院→专业→班级) +- 实时展示学生原班级信息 + +### 3. 数据追溯和审计 +- 完整的操作历史记录 +- 支持数据追溯和审计 +- 提供统计分析基础数据 + +## 📊 数据结构特点 + +### 遵循项目规范 +- ✅ 包含所有强制性基础字段 +- ✅ 使用雪花ID作为主键 +- ✅ 支持多租户数据隔离 +- ✅ 逻辑删除,避免物理删除 +- ✅ 新增状态字段:0-未处理,1-已处理 + +### 关联关系 +- 学生信息:关联 `xg_student_msg` 表 +- 班级信息:关联 `xg_class_msg` 表 +- 专业信息:关联 `xg_major_msg` 表 +- 学院信息:关联 `sys_dept` 表(dept_category区分层级) + +### 索引优化 +- 基础索引:租户、删除标志、创建时间 +- 业务索引:学生ID、班级ID、时间等 +- 唯一约束:防止重复记录 + +## 🔄 业务流程 + +``` +学生申请 → 提交申请 → 辅导员审批 → 院系领导审批 → 教务处审批 → 执行换班级 → 生效 +``` + +## 📈 统计分析支持 + +支持多种维度的统计分析: +- 按时间统计换班级趋势 +- 按原因分析换班级分布 +- 按班级统计换班级情况 +- 按审批状态统计处理情况 + +## 🚀 使用建议 + +### 开发注意事项 +1. **数据一致性**: 换班级操作必须确保相关数据的一致性 +2. **业务规则**: 严格遵守学校的换班级管理规定 +3. **审批流程**: 必须经过完整的审批流程 +4. **文档同步**: 代码变更及时更新相关文档 + +### 性能优化 +1. **分页查询**: 大数据量查询必须分页 +2. **索引使用**: 充分利用设计的索引 +3. **缓存策略**: 对常用数据进行适当缓存 + +## 📞 技术支持 + +如有疑问或需要技术支持,请联系项目技术团队。 + +--- + +**创建时间**: 2025-01-25 +**维护人员**: AI Assistant +**版本**: v1.0.0 diff --git a/学生换班级/学生换班级记录表.sql b/学生换班级/学生换班级记录表.sql new file mode 100644 index 00000000..cf5d97d3 --- /dev/null +++ b/学生换班级/学生换班级记录表.sql @@ -0,0 +1,80 @@ +-- ========================================== +-- 数据库变更脚本 +-- 版本:V1.0.0 +-- 功能:新增学生换班级记录表 +-- 作者:AI Assistant +-- 时间:2025-01-25 +-- ========================================== + +-- ---------------------------- +-- Table structure for xg_student_class_change +-- ---------------------------- +DROP TABLE IF EXISTS `xg_student_class_change`; +CREATE TABLE `xg_student_class_change` ( + -- 基础字段(必须) + `id` bigint(20) NOT NULL COMMENT '主键ID(雪花ID)', + `tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号', + `create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门', + `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `deleted` tinyint(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', + + -- 学生基本信息 + `student_id` bigint(20) NOT NULL COMMENT '学生ID,关联xg_student_msg.id', + `student_no` varchar(32) NOT NULL COMMENT '学号', + `student_name` varchar(50) NOT NULL COMMENT '学生姓名', + + -- 原班级信息 + `original_college_id` bigint(20) DEFAULT NULL COMMENT '原学院ID ', + `original_college_name` varchar(100) DEFAULT NULL COMMENT '原学院名称', + `original_major_id` bigint(20) DEFAULT NULL COMMENT '原专业ID ', + `original_major_name` varchar(100) DEFAULT NULL COMMENT '原专业名称', + `original_class_id` bigint(20) DEFAULT NULL COMMENT '原班级ID ', + `original_class_code` varchar(32) DEFAULT NULL COMMENT '原班级代码', + `original_class_name` varchar(100) DEFAULT NULL COMMENT '原班级名称', + + -- 新班级信息 + `new_college_id` bigint(20) DEFAULT NULL COMMENT '新学院ID ', + `new_college_name` varchar(100) DEFAULT NULL COMMENT '新学院名称', + `new_major_id` bigint(20) DEFAULT NULL COMMENT '新专业ID ', + `new_major_name` varchar(100) DEFAULT NULL COMMENT '新专业名称', + `new_class_id` bigint(20) DEFAULT NULL COMMENT '新班级ID ', + `new_class_code` varchar(32) DEFAULT NULL COMMENT '新班级代码', + `new_class_name` varchar(100) DEFAULT NULL COMMENT '新班级名称', + + -- 换班级业务信息 + `change_reason` varchar(500) DEFAULT NULL COMMENT '换班级原因', + `change_type` char(1) DEFAULT '1' COMMENT '换班级类型:1-主动申请,2-学校调整,3-专业调整,4-其他', + `change_time` datetime DEFAULT NULL COMMENT '换班级时间', + `effective_time` datetime DEFAULT NULL COMMENT '生效时间', + `status` char(1) DEFAULT '0' COMMENT '处理状态:0-未处理,1-已处理', + + -- 扩展字段 + `remark` varchar(500) DEFAULT NULL COMMENT '备注', + + -- 主键和索引 + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `uk_student_change_time` (`student_id`, `change_time`, `tenant_id`, `deleted`), + KEY `idx_tenant_id` (`tenant_id`), + KEY `idx_deleted` (`deleted`), + KEY `idx_student_id` (`student_id`), + KEY `idx_student_no` (`student_no`), + KEY `idx_original_class_id` (`original_class_id`), + KEY `idx_new_class_id` (`new_class_id`), + KEY `idx_change_time` (`change_time`), + KEY `idx_effective_time` (`effective_time`), + KEY `idx_create_time` (`create_time`), + KEY `idx_tenant_deleted` (`tenant_id`, `deleted`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生换班级记录表'; + + +-- 建表完成提示 +-- 注意事项: +-- 1. 该表遵循项目数据库规范,包含所有强制性基础字段 +-- 2. 使用雪花ID作为主键,支持多租户数据隔离 +-- 3. 支持逻辑删除,避免物理删除数据 +-- 4. 记录学生换班级的完整历史信息,包括审批流程 +-- 5. 关联字段不使用外键约束,通过应用层逻辑维护数据一致性 +-- 6. 索引设计遵循性能优化原则 diff --git a/学生换班级/学生换班级记录表设计说明.md b/学生换班级/学生换班级记录表设计说明.md new file mode 100644 index 00000000..361321a6 --- /dev/null +++ b/学生换班级/学生换班级记录表设计说明.md @@ -0,0 +1,298 @@ +# 学生换班级记录表设计说明 + +## 📋 文档说明 + +**版本**: v1.0.0 +**制定时间**: 2025-01-25 +**适用范围**: 学生换班级业务模块 +**重要性**: ⚠️ **核心业务表** - 记录所有学生换班级操作的历史数据 + +--- + +## 🎯 设计目标 + +### 1. 业务需求 + +- **记录完整历史**: 记录学生换班级的完整过程和历史数据 +- **支持审批流程**: 包含换班级的申请、审批、执行全流程 +- **数据追溯**: 提供完整的换班级操作审计和追溯能力 +- **业务统计**: 支持换班级数据的统计分析和报表生成 + +### 2. 技术要求 + +- **数据完整性**: 遵循项目数据库设计规范 +- **性能优化**: 合理的索引设计,支持高效查询 +- **扩展性**: 预留扩展字段,支持未来功能需求 +- **安全性**: 多租户隔离,完整的审计追踪 + +--- + +## 🗄️ 表结构设计 + +### 表名:`xg_student_class_change` + +### 字段分类说明 + +#### 🔴 强制基础字段(必须) + +| 字段名 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| `id` | bigint(20) | - | 主键ID(雪花ID) | +| `tenant_id` | varchar(20) | '000000' | 租户编号 | +| `create_dept` | bigint(20) | NULL | 创建部门 | +| `create_by` | bigint(20) | NULL | 创建者 | +| `create_time` | datetime | NULL | 创建时间 | +| `update_by` | bigint(20) | NULL | 更新者 | +| `update_time` | datetime | NULL | 更新时间 | +| `deleted` | tinyint(1) | 0 | 删除标志(0存在 1删除) | + +#### 👤 学生基本信息 + +| 字段名 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| `student_id` | bigint(20) | - | 学生ID,关联xg_student_msg.id | +| `student_no` | varchar(32) | - | 学号 | +| `student_name` | varchar(50) | - | 学生姓名 | + +#### 📚 原班级信息 + +| 字段名 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| `original_college_id` | bigint(20) | NULL | 原学院ID,关联sys_dept.dept_id | +| `original_college_name` | varchar(100) | NULL | 原学院名称 | +| `original_major_id` | bigint(20) | NULL | 原专业ID,关联xg_major_msg.id | +| `original_major_name` | varchar(100) | NULL | 原专业名称 | +| `original_class_id` | bigint(20) | NULL | 原班级ID,关联xg_class_msg.id | +| `original_class_code` | varchar(32) | NULL | 原班级代码 | +| `original_class_name` | varchar(100) | NULL | 原班级名称 | + +#### 📚 新班级信息 + +| 字段名 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| `new_college_id` | bigint(20) | NULL | 新学院ID,关联sys_dept.dept_id | +| `new_college_name` | varchar(100) | NULL | 新学院名称 | +| `new_major_id` | bigint(20) | NULL | 新专业ID,关联xg_major_msg.id | +| `new_major_name` | varchar(100) | NULL | 新专业名称 | +| `new_class_id` | bigint(20) | NULL | 新班级ID,关联xg_class_msg.id | +| `new_class_code` | varchar(32) | NULL | 新班级代码 | +| `new_class_name` | varchar(100) | NULL | 新班级名称 | + +#### 🔄 换班级业务信息 + +| 字段名 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| `change_reason` | varchar(500) | NULL | 换班级原因 | +| `change_type` | char(1) | '1' | 换班级类型:1-主动申请,2-学校调整,3-专业调整,4-其他 | +| `change_time` | datetime | NULL | 换班级时间 | +| `effective_time` | datetime | NULL | 生效时间 | +| `status` | char(1) | '0' | 处理状态:0-未处理,1-已处理 | + +#### ✅ 审批流程信息 + +| 字段名 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| `approval_status` | char(1) | '0' | 审批状态:0-待审批,1-审批通过,2-审批拒绝,3-已撤销 | +| `approval_by` | bigint(20) | NULL | 审批人ID | +| `approval_time` | datetime | NULL | 审批时间 | +| `approval_remarks` | varchar(500) | NULL | 审批备注 | + +#### 📎 附件信息 + +| 字段名 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| `attachment_ids` | text | NULL | 附件文件ID(逗号分隔) | + +#### 📝 扩展字段 + +| 字段名 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| `remark` | varchar(500) | NULL | 备注 | + +--- + +## 🔍 索引设计 + +### 基础索引(必须) + +```sql +KEY `idx_tenant_id` (`tenant_id`), +KEY `idx_deleted` (`deleted`), +KEY `idx_create_time` (`create_time`), +KEY `idx_tenant_deleted` (`tenant_id`, `deleted`) +``` + +### 业务索引 + +```sql +KEY `idx_student_id` (`student_id`), +KEY `idx_student_no` (`student_no`), +KEY `idx_original_class_id` (`original_class_id`), +KEY `idx_new_class_id` (`new_class_id`), +KEY `idx_change_time` (`change_time`), +KEY `idx_effective_time` (`effective_time`), +KEY `idx_approval_status` (`approval_status`) +``` + +### 唯一约束 + +```sql +UNIQUE KEY `uk_student_change_time` (`student_id`, `change_time`, `tenant_id`, `deleted`) +``` + +--- + +## 🔄 业务流程 + +### 1. 换班级申请流程 + +``` +学生申请 → 提交申请 → 管理员处理 → 执行换班级 +``` + +### 2. 数据状态流转 + +``` +未处理(0) → 已处理(1) → 执行换班级 → 生效 +``` + +### 3. 换班级类型说明 + +| 类型代码 | 类型名称 | 说明 | +|----------|----------|------| +| 1 | 主动申请 | 学生主动提出换班级申请 | +| 2 | 学校调整 | 学校根据教学需要调整班级 | +| 3 | 专业调整 | 学生转专业导致班级变化 | +| 4 | 其他 | 其他特殊情况 | + +--- + +## 📊 数据关联关系 + +### 关联表说明 + +- **xg_student_msg**: 学生信息表(通过student_id关联) +- **xg_class_msg**: 班级信息表(通过original_class_id、new_class_id关联) +- **xg_major_msg**: 专业信息表(通过original_major_id、new_major_id关联) +- **sys_dept**: 部门信息表(通过original_college_id、new_college_id关联) +- **sys_user**: 用户信息表(通过create_by、update_by、approval_by关联) + +### 注意事项 + +⚠️ **不使用数据库外键约束**:所有关联关系通过应用层逻辑维护 + +--- + +## 📈 统计分析支持 + +### 支持的统计维度 + +1. **按时间统计**: 换班级数量趋势分析 +2. **按原因统计**: 换班级原因分布统计 +3. **按班级统计**: 各班级换班级情况统计 +4. **按审批状态**: 换班级申请处理情况统计 +5. **按操作人**: 换班级操作人员工作量统计 + +### 示例统计SQL + +```sql +-- 按月份统计换班级数量 +SELECT + DATE_FORMAT(change_time, '%Y-%m') AS month, + COUNT(*) AS change_count +FROM xg_student_class_change +WHERE deleted = 0 AND approval_status = '1' +GROUP BY DATE_FORMAT(change_time, '%Y-%m') +ORDER BY month; + +-- 按原因统计换班级分布 +SELECT + change_type, + COUNT(*) AS count +FROM xg_student_class_change +WHERE deleted = 0 AND approval_status = '1' +GROUP BY change_type; +``` + +--- + +## 🔒 安全考虑 + +### 数据安全 + +1. **多租户隔离**: 通过tenant_id实现数据隔离 +2. **权限控制**: 基于角色的数据访问控制 +3. **操作审计**: 完整的操作日志记录 +4. **数据加密**: 敏感信息加密存储 + +### 业务安全 + +1. **审批流程**: 多级审批确保操作合规 +2. **数据校验**: 换班级前进行业务规则校验 +3. **操作限制**: 防止重复换班级和无效操作 +4. **回滚机制**: 支持换班级操作的回滚 + +--- + +## 🚀 使用说明 + +### 数据插入规范 + +```sql +INSERT INTO xg_student_class_change ( + id, tenant_id, create_by, create_time, + student_id, student_no, student_name, + original_class_id, original_class_name, + new_class_id, new_class_name, + change_reason, change_type, change_time, + approval_status +) VALUES ( + 雪花ID, '000000', 操作人ID, NOW(), + 学生ID, '学号', '学生姓名', + 原班级ID, '原班级名称', + 新班级ID, '新班级名称', + '换班级原因', '1', NOW(), + '0' -- 待审批状态 +); +``` + +### 查询优化建议 + +1. **分页查询**: 大数据量时必须分页 +2. **索引使用**: 充分利用业务索引提升查询性能 +3. **关联查询**: 注意避免复杂的多表关联查询 +4. **缓存策略**: 对常用数据进行适当缓存 + +--- + +## 📞 维护说明 + +### 定期维护任务 + +1. **数据清理**: 定期清理已删除的历史数据 +2. **索引优化**: 定期检查和优化索引性能 +3. **统计更新**: 定期更新统计数据 +4. **备份策略**: 定期备份重要数据 + +### 扩展规划 + +1. **字段扩展**: 预留扩展字段用于未来需求 +2. **流程优化**: 支持更复杂的审批流程 +3. **自动化**: 支持自动换班级处理 +4. **集成**: 与教务系统深度集成 + +--- + +## ⚠️ 重要提醒 + +1. **数据一致性**: 换班级操作必须确保数据的一致性 +2. **业务规则**: 严格遵守学校的换班级管理规定 +3. **审批流程**: 必须经过完整的审批流程 +4. **文档同步**: 代码变更必须同步更新相关文档 +5. **测试验证**: 新功能必须经过充分的测试验证 + +--- + +**最后更新**: 2025-01-25 +**维护人员**: AI Assistant +**联系方式**: 项目技术团队 diff --git a/退学、休学、复学、毕业/01-核心状态修改.md b/退学、休学、复学、毕业/01-核心状态修改.md index bda84af9..0c6e7043 100644 --- a/退学、休学、复学、毕业/01-核心状态修改.md +++ b/退学、休学、复学、毕业/01-核心状态修改.md @@ -2,7 +2,7 @@ ## 📋 概述 -本分文档详细说明学生退学、休学、毕业处理中的核心状态修改内容,包括学生学籍状态和用户登录状态的变更逻辑。 +本分文档详细说明学生退学、休学、毕业、复学处理中的核心状态修改内容,包括学生学籍状态和用户登录状态的变更逻辑。 ## 🎯 核心状态修改 @@ -24,16 +24,16 @@ | `3` | 复学 | 从休学状态恢复 | info | | `4` | 退学 | 终止学籍 | danger | | `5` | 毕业 | 正常毕业 | success | - -**重要说明**: -- 数据库字段 `student_status` 为 char(1) 类型 -- 字典类型为 `xg_student_status` -- 状态值与数据库字段值直接对应,无需转换 +| `6` | 不在籍 | - | - | +| `7` | 结业 | - | - | +| `8` | 停学 | - | - | +| `9` | 分流 | - | - | #### 🔄 处理逻辑 - **休学**: `student_status = '2'`(休学) - **退学**: `student_status = '4'`(退学) - **毕业**: `student_status = '5'`(毕业) +- **复学**: `student_status = '3'`(复学) #### 📝 前端界面实现 **文件位置**: `web/src/views/xg/xsjcxx/stuPersonalMsg/index.vue` @@ -53,7 +53,7 @@ -- 参数说明: -- @student_id: 学生ID (主键) -- @student_no: 学生学号 --- @change_type: 变更类型 (XIU_XUE-休学, TUI_XUE-退学, BI_YE-毕业) +-- @change_type: 变更类型 (XIU_XUE-休学, TUI_XUE-退学, FU_XUE-复学, BI_YE-毕业) -- @operator_id: 操作员ID -- 休学处理 @@ -66,7 +66,7 @@ WHERE id = @student_id -- 退学处理 UPDATE xg_student_msg -SET student_status = '3', +SET student_status = '4', update_by = @operator_id, update_time = NOW() WHERE id = @student_id @@ -74,7 +74,15 @@ WHERE id = @student_id -- 毕业处理 UPDATE xg_student_msg -SET student_status = '4', +SET student_status = '5', + update_by = @operator_id, + update_time = NOW() +WHERE id = @student_id + AND deleted = 0; + +-- 复学处理 +UPDATE xg_student_msg +SET student_status = '3', update_by = @operator_id, update_time = NOW() WHERE id = @student_id @@ -96,9 +104,10 @@ WHERE id = @student_id | `1` | 停用 | 账号已被禁用 | #### 🔄 处理逻辑 -- **休学**: 可选择保留账号(`status` 保持 `0`)或禁用账号(`status = '1'`) -- **退学**: 必须禁用账号(`status = '1'`) -- **毕业**: 必须禁用账号(`status = '1'`) +- **休学**: 禁用账号(`status = '1'`) +- **退学**: 禁用账号(`status = '1'`) +- **毕业**: 禁用账号(`status = '1'`) +- **复学**: 启用账号(`status = '0'`) #### 📝 后端实体类 **文件位置**: `backend/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysUser.java` @@ -113,29 +122,34 @@ WHERE id = @student_id #### 💾 SQL处理脚本 ```sql -- 参数说明: +-- @student_id: 学生ID (对应sys_user.user_id) -- @student_no: 学生学号 (对应sys_user.user_name) --- @change_type: 变更类型 (XIU_XUE-休学, TUI_XUE-退学, BI_YE-毕业) +-- @change_type: 变更类型 (XIU_XUE-休学, TUI_XUE-退学, FU_XUE-复学, BI_YE-毕业) -- @operator_id: 操作员ID --- 退学/毕业:禁用用户账号 +-- 1. 备份原始数据到his表 +INSERT INTO sys_user_his SELECT * FROM sys_user WHERE user_id = @student_id; + +-- 2. 处理用户账号状态 +-- 退学/休学/毕业:禁用用户账号 UPDATE sys_user SET status = '1', update_by = @operator_id, - update_time = NOW(), - remark = CONCAT(COALESCE(remark, ''), ' | 学生状态变更-', @change_type, '-', NOW()) -WHERE user_name = @student_no + update_time = NOW() +WHERE user_id = @student_id AND account_type = '2' -- 学生账户类型 - AND del_flag = '0'; + AND del_flag = '0' + AND @change_type IN ('XIU_XUE', 'TUI_XUE', 'BI_YE'); --- 休学:可选保留账号(根据业务需求决定是否执行) --- UPDATE sys_user --- SET status = '1', -- 如需禁用则取消注释 --- update_by = @operator_id, --- update_time = NOW(), --- remark = CONCAT(COALESCE(remark, ''), ' | 学生休学-', NOW()) --- WHERE user_name = @student_no --- AND account_type = '2' --- AND del_flag = '0'; +-- 复学:启用用户账号 +UPDATE sys_user +SET status = '0', + update_by = @operator_id, + update_time = NOW() +WHERE user_id = @student_id + AND account_type = '2' -- 学生账户类型 + AND del_flag = '0' + AND @change_type = 'FU_XUE'; ``` ### 3. 学生状态变更记录 @@ -168,8 +182,8 @@ WHERE user_name = @student_no ```java // 变更类型枚举 public enum ChangeType { - TUI_XUE("退学"), XIU_XUE("休学"), + TUI_XUE("退学"), FU_XUE("复学"), BI_YE("毕业"); } @@ -183,10 +197,11 @@ public enum ChangeType { ``` 1. 创建状态变更记录 (xg_student_status_change) 2. 验证学生当前状态和业务规则 -3. 更新学生学籍状态 (xg_student_msg.student_status) -4. 根据变更类型处理用户登录状态 (sys_user.status) -5. 记录操作日志和审计信息 -6. 触发后续业务处理 (组织关系、经济资助等) +3. 备份原始数据到his表 +4. 更新学生学籍状态 (xg_student_msg.student_status) +5. 根据变更类型处理用户登录状态 (sys_user.status) +6. 记录操作日志和审计信息 +7. 触发后续业务处理 (组织关系、退宿处理等) ``` #### ⚡ 事务处理要求 @@ -197,11 +212,15 @@ START TRANSACTION; -- 步骤1: 创建变更记录 INSERT INTO xg_student_status_change (...) VALUES (...); --- 步骤2: 更新学生状态 +-- 步骤2: 备份原始数据到his表 +INSERT INTO xg_student_msg_his SELECT * FROM xg_student_msg WHERE id = ?; +INSERT INTO sys_user_his SELECT * FROM sys_user WHERE user_id = ?; + +-- 步骤3: 更新学生状态 UPDATE xg_student_msg SET student_status = ?, update_time = NOW() WHERE id = ?; --- 步骤3: 更新用户状态 (如需要) -UPDATE sys_user SET status = ?, update_time = NOW() WHERE user_name = ?; +-- 步骤4: 更新用户状态 +UPDATE sys_user SET status = ?, update_time = NOW() WHERE user_id = ?; -- 提交事务 COMMIT; @@ -230,17 +249,18 @@ WHERE user_name = @student_no AND del_flag = '0'; #### 📊 状态变更矩阵 -| 当前状态 | 可变更状态 | 说明 | -|----------|------------|------| -| 在读(1) | 休学(2)、退学(4)、毕业(5) | 正常学籍状态下的变更 | -| 休学(2) | 复学(3)、退学(4)、毕业(5) | 休学状态下的恢复或终止 | -| 复学(3) | 休学(2)、退学(4)、毕业(5) | 复学后的再次变更 | -| 其他状态 | 需特殊审批 | 异常状态需要额外处理 | +| 当前状态 | 可变更状态 | 账号处理 | 说明 | +|----------|------------|----------|------| +| 在读(1) | 休学(2)、退学(4)、毕业(5) | 禁用账号 | 正常学籍状态下的变更 | +| 休学(2) | 复学(3)、退学(4)、毕业(5) | 禁用账号 | 休学状态下的恢复或终止 | +| 复学(3) | 休学(2)、退学(4)、毕业(5) | 禁用账号 | 复学后的再次变更 | +| 其他状态 | 需特殊审批 | 根据情况 | 异常状态需要额外处理 | #### ⚠️ 业务约束 - **休学限制**: 同一学年内休学不能超过规定次数 - **退学限制**: 退学后不能恢复为正常学籍 - **毕业限制**: 毕业状态为最终状态,不允许变更 +- **复学限制**: 只有休学状态才能复学 - **时间约束**: 状态变更需要满足学年学期要求 ### 4. 异常处理机制 @@ -315,13 +335,14 @@ public class XgStudentStatusChangeServiceImpl implements IXgStudentStatusChangeS ```vue