新增培训:实现宿舍房间P0能力
This commit is contained in:
1 parent
4bd2f54856
commit
7a5878fe27
15 files changed
+2130
-328
No files matched your search
+69
-57
@@ -1,104 +1,116 @@
|
||||
package com.ruoyi.web.controller.train;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.train.domain.TrainDormitoryInfo;
|
||||
import com.ruoyi.train.service.ITrainDormitoryInfoService;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.train.domain.TrainDormitoryInfo;
|
||||
import com.ruoyi.train.domain.dto.DormitoryCreateDTO;
|
||||
import com.ruoyi.train.domain.vo.DormOptionsVO;
|
||||
import com.ruoyi.train.domain.vo.DormitoryDetailVO;
|
||||
import com.ruoyi.train.domain.vo.DormitoryItemVO;
|
||||
import com.ruoyi.train.service.ITrainDormitoryInfoService;
|
||||
|
||||
/**
|
||||
* 宿舍信息Controller
|
||||
*
|
||||
* 宿舍房间基础资源 Controller (ISSUE-01-05 / API Contract V1.0.2)
|
||||
*
|
||||
* 冻结公开物理 Endpoint 共且仅有 4 个正式 P0:
|
||||
* 1. GET /train/dormitory/list
|
||||
* 2. GET /train/dormitory/{dormId}
|
||||
* 3. POST /train/dormitory
|
||||
* 4. GET /train/dormitory/options
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-09-08
|
||||
* @date 2026-09-14
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping({"/train/dorminfo", "/train/dormitory"})
|
||||
@RequestMapping("/train/dormitory")
|
||||
public class TrainDormitoryInfoController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private ITrainDormitoryInfoService trainDormitoryInfoService;
|
||||
|
||||
/**
|
||||
* 查询宿舍信息列表
|
||||
* 分页查询宿舍房间列表 (附带容量、已住人数、剩余床位)
|
||||
* GET /train/dormitory/list
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:info:list')")
|
||||
@PreAuthorize("@ss.hasPermi('train:dormitory:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(TrainDormitoryInfo trainDormitoryInfo)
|
||||
public TableDataInfo list(TrainDormitoryInfo query)
|
||||
{
|
||||
startPage();
|
||||
List<TrainDormitoryInfo> list = trainDormitoryInfoService.selectTrainDormitoryInfoList(trainDormitoryInfo);
|
||||
return getDataTable(list);
|
||||
List<TrainDormitoryInfo> list = trainDormitoryInfoService.selectTrainDormitoryInfoList(query);
|
||||
List<DormitoryItemVO> voList = new ArrayList<>();
|
||||
if (list != null)
|
||||
{
|
||||
for (TrainDormitoryInfo entity : list)
|
||||
{
|
||||
DormitoryItemVO vo = new DormitoryItemVO();
|
||||
vo.setDormId(entity.getDormId());
|
||||
vo.setDormBuilding(entity.getDormBuilding());
|
||||
vo.setDormFloor(entity.getDormFloor());
|
||||
vo.setDormRoom(entity.getDormRoom());
|
||||
vo.setCapacity(entity.getCapacity());
|
||||
vo.setUsedCount(entity.getUsedCount());
|
||||
vo.setRemainingBeds(entity.getRemainingBeds());
|
||||
vo.setGenderType(entity.getGenderType());
|
||||
vo.setLockDeviceSn(entity.getLockDeviceSn());
|
||||
vo.setDormStatus(entity.getDormStatus());
|
||||
vo.setRemark(entity.getRemark());
|
||||
vo.setCreateTime(entity.getCreateTime());
|
||||
voList.add(vo);
|
||||
}
|
||||
}
|
||||
return getDataTable(voList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出宿舍信息列表
|
||||
* 获取指定房间详情及当前入住人员列表
|
||||
* GET /train/dormitory/{dormId}
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:info:export')")
|
||||
@Log(title = "宿舍信息", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(HttpServletResponse response, TrainDormitoryInfo trainDormitoryInfo)
|
||||
{
|
||||
List<TrainDormitoryInfo> list = trainDormitoryInfoService.selectTrainDormitoryInfoList(trainDormitoryInfo);
|
||||
ExcelUtil<TrainDormitoryInfo> util = new ExcelUtil<TrainDormitoryInfo>(TrainDormitoryInfo.class);
|
||||
util.exportExcel(response, list, "宿舍信息数据");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取宿舍信息详细信息
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:info:query')")
|
||||
@GetMapping(value = "/{dormId}")
|
||||
@PreAuthorize("@ss.hasPermi('train:dormitory:list')")
|
||||
@GetMapping("/{dormId}")
|
||||
public AjaxResult getInfo(@PathVariable("dormId") String dormId)
|
||||
{
|
||||
return success(trainDormitoryInfoService.selectTrainDormitoryInfoByDormId(dormId));
|
||||
DormitoryDetailVO detail = trainDormitoryInfoService.selectDormitoryDetailByDormId(dormId);
|
||||
return success(detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增宿舍信息
|
||||
* 新增宿舍房间 (同楼栋房号重复拦截抛 40006)
|
||||
* POST /train/dormitory
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:info:add')")
|
||||
@Log(title = "宿舍信息", businessType = BusinessType.INSERT)
|
||||
@PreAuthorize("@ss.hasPermi('train:dormitory:edit')")
|
||||
@Log(title = "宿舍房间管理", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody TrainDormitoryInfo trainDormitoryInfo)
|
||||
public AjaxResult add(@Validated @RequestBody DormitoryCreateDTO dto)
|
||||
{
|
||||
return toAjax(trainDormitoryInfoService.insertTrainDormitoryInfo(trainDormitoryInfo));
|
||||
trainDormitoryInfoService.insertTrainDormitory(dto, SecurityUtils.getUsername());
|
||||
return success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改宿舍信息
|
||||
* 获取楼栋、楼层级联下拉数据 (分房交互必需)
|
||||
* GET /train/dormitory/options
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:info:edit')")
|
||||
@Log(title = "宿舍信息", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody TrainDormitoryInfo trainDormitoryInfo)
|
||||
@PreAuthorize("@ss.hasPermi('train:dormitory:list')")
|
||||
@GetMapping("/options")
|
||||
public AjaxResult options()
|
||||
{
|
||||
return toAjax(trainDormitoryInfoService.updateTrainDormitoryInfo(trainDormitoryInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除宿舍信息
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:info:remove')")
|
||||
@Log(title = "宿舍信息", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{dormIds}")
|
||||
public AjaxResult remove(@PathVariable String[] dormIds)
|
||||
{
|
||||
return toAjax(trainDormitoryInfoService.deleteTrainDormitoryInfoByDormIds(dormIds));
|
||||
List<DormOptionsVO> tree = trainDormitoryInfoService.selectDormitoryTreeOptions();
|
||||
return success(tree);
|
||||
}
|
||||
}
|
||||
+85
-43
@@ -6,138 +6,180 @@ import com.ruoyi.common.annotation.Excel;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
|
||||
/**
|
||||
* 宿舍信息对象 train_dormitory_info
|
||||
*
|
||||
* 宿舍信息对象 train_dormitory_info (Database V2 / ISSUE-01-05)
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-09-08
|
||||
* @date 2026-09-14
|
||||
*/
|
||||
public class TrainDormitoryInfo extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 宿舍ID主键 */
|
||||
/** 宿舍ID主键 (32位UUID) */
|
||||
private String dormId;
|
||||
|
||||
/** 宿舍楼栋 */
|
||||
@Excel(name = "宿舍楼栋")
|
||||
private String dormBuilding;
|
||||
|
||||
/** 所在楼层 */
|
||||
@Excel(name = "所在楼层")
|
||||
private String dormFloor;
|
||||
|
||||
/** 宿舍房间号 */
|
||||
@Excel(name = "宿舍房间号")
|
||||
private String dormRoom;
|
||||
|
||||
/** 宿舍总床位 */
|
||||
@Excel(name = "宿舍总床位")
|
||||
private Long bedTotal;
|
||||
/** 核定床位容量 (正整数,无默认值,1~8) */
|
||||
@Excel(name = "核定床位容量")
|
||||
private Integer capacity;
|
||||
|
||||
/** 已占用床位 */
|
||||
@Excel(name = "已占用床位")
|
||||
private Long bedUsed;
|
||||
/** 适用性别 (0:不限, 1:男, 2:女) */
|
||||
@Excel(name = "适用性别", readConverterExp = "0=不限,1=男,2=女")
|
||||
private Integer genderType;
|
||||
|
||||
/** 宿舍人脸智能锁设备序列号,对接智能锁 */
|
||||
@Excel(name = "宿舍人脸智能锁设备序列号,对接智能锁")
|
||||
/** 智能门锁设备序列号 */
|
||||
@Excel(name = "智能门锁设备序列号")
|
||||
private String lockDeviceSn;
|
||||
|
||||
/** 0 禁用 1 可用 */
|
||||
@Excel(name = "0 禁用 1 可用")
|
||||
/** 房间状态 (0: 禁用, 1: 可用) */
|
||||
@Excel(name = "房间状态", readConverterExp = "0=禁用,1=可用")
|
||||
private Integer dormStatus;
|
||||
|
||||
/** 创建管理员账号 */
|
||||
@Excel(name = "创建管理员账号")
|
||||
private String createUser;
|
||||
/** 删除标志 (0: 存在, 2: 删除) */
|
||||
private String delFlag;
|
||||
|
||||
public void setDormId(String dormId)
|
||||
/** 已住人数 (实时统计自 train_accommodation,非持久化列) */
|
||||
private Integer usedCount;
|
||||
|
||||
/** 剩余床位 (capacity - usedCount,非持久化列) */
|
||||
private Integer remainingBeds;
|
||||
|
||||
public void setDormId(String dormId)
|
||||
{
|
||||
this.dormId = dormId;
|
||||
}
|
||||
|
||||
public String getDormId()
|
||||
public String getDormId()
|
||||
{
|
||||
return dormId;
|
||||
}
|
||||
|
||||
public void setDormBuilding(String dormBuilding)
|
||||
public void setDormBuilding(String dormBuilding)
|
||||
{
|
||||
this.dormBuilding = dormBuilding;
|
||||
}
|
||||
|
||||
public String getDormBuilding()
|
||||
public String getDormBuilding()
|
||||
{
|
||||
return dormBuilding;
|
||||
}
|
||||
|
||||
public void setDormRoom(String dormRoom)
|
||||
public void setDormFloor(String dormFloor)
|
||||
{
|
||||
this.dormFloor = dormFloor;
|
||||
}
|
||||
|
||||
public String getDormFloor()
|
||||
{
|
||||
return dormFloor;
|
||||
}
|
||||
|
||||
public void setDormRoom(String dormRoom)
|
||||
{
|
||||
this.dormRoom = dormRoom;
|
||||
}
|
||||
|
||||
public String getDormRoom()
|
||||
public String getDormRoom()
|
||||
{
|
||||
return dormRoom;
|
||||
}
|
||||
|
||||
public void setBedTotal(Long bedTotal)
|
||||
public void setCapacity(Integer capacity)
|
||||
{
|
||||
this.bedTotal = bedTotal;
|
||||
this.capacity = capacity;
|
||||
}
|
||||
|
||||
public Long getBedTotal()
|
||||
public Integer getCapacity()
|
||||
{
|
||||
return bedTotal;
|
||||
return capacity;
|
||||
}
|
||||
|
||||
public void setBedUsed(Long bedUsed)
|
||||
public void setGenderType(Integer genderType)
|
||||
{
|
||||
this.bedUsed = bedUsed;
|
||||
this.genderType = genderType;
|
||||
}
|
||||
|
||||
public Long getBedUsed()
|
||||
public Integer getGenderType()
|
||||
{
|
||||
return bedUsed;
|
||||
return genderType;
|
||||
}
|
||||
|
||||
public void setLockDeviceSn(String lockDeviceSn)
|
||||
public void setLockDeviceSn(String lockDeviceSn)
|
||||
{
|
||||
this.lockDeviceSn = lockDeviceSn;
|
||||
}
|
||||
|
||||
public String getLockDeviceSn()
|
||||
public String getLockDeviceSn()
|
||||
{
|
||||
return lockDeviceSn;
|
||||
}
|
||||
|
||||
public void setDormStatus(Integer dormStatus)
|
||||
public void setDormStatus(Integer dormStatus)
|
||||
{
|
||||
this.dormStatus = dormStatus;
|
||||
}
|
||||
|
||||
public Integer getDormStatus()
|
||||
public Integer getDormStatus()
|
||||
{
|
||||
return dormStatus;
|
||||
}
|
||||
|
||||
public void setCreateUser(String createUser)
|
||||
public void setDelFlag(String delFlag)
|
||||
{
|
||||
this.createUser = createUser;
|
||||
this.delFlag = delFlag;
|
||||
}
|
||||
|
||||
public String getCreateUser()
|
||||
public String getDelFlag()
|
||||
{
|
||||
return createUser;
|
||||
return delFlag;
|
||||
}
|
||||
|
||||
public Integer getUsedCount()
|
||||
{
|
||||
return usedCount;
|
||||
}
|
||||
|
||||
public void setUsedCount(Integer usedCount)
|
||||
{
|
||||
this.usedCount = usedCount;
|
||||
}
|
||||
|
||||
public Integer getRemainingBeds()
|
||||
{
|
||||
return remainingBeds;
|
||||
}
|
||||
|
||||
public void setRemainingBeds(Integer remainingBeds)
|
||||
{
|
||||
this.remainingBeds = remainingBeds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
||||
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("dormId", getDormId())
|
||||
.append("dormBuilding", getDormBuilding())
|
||||
.append("dormFloor", getDormFloor())
|
||||
.append("dormRoom", getDormRoom())
|
||||
.append("bedTotal", getBedTotal())
|
||||
.append("bedUsed", getBedUsed())
|
||||
.append("capacity", getCapacity())
|
||||
.append("genderType", getGenderType())
|
||||
.append("lockDeviceSn", getLockDeviceSn())
|
||||
.append("dormStatus", getDormStatus())
|
||||
.append("createUser", getCreateUser())
|
||||
.append("delFlag", getDelFlag())
|
||||
.append("createBy", getCreateBy())
|
||||
.append("createTime", getCreateTime())
|
||||
.append("updateBy", getUpdateBy())
|
||||
.append("updateTime", getUpdateTime())
|
||||
.append("remark", getRemark())
|
||||
.toString();
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
package com.ruoyi.train.domain.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
/**
|
||||
* 宿舍房间创建 DTO (ISSUE-01-05 / API Contract V1.0.2)
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class DormitoryCreateDTO implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 宿舍楼栋 */
|
||||
@NotBlank(message = "宿舍楼栋不能为空")
|
||||
@Size(max = 50, message = "宿舍楼栋长度不能超过50个字符")
|
||||
private String dormBuilding;
|
||||
|
||||
/** 所在楼层 */
|
||||
@NotBlank(message = "所在楼层不能为空")
|
||||
@Size(max = 20, message = "所在楼层长度不能超过20个字符")
|
||||
private String dormFloor;
|
||||
|
||||
/** 房间号 */
|
||||
@NotBlank(message = "房间号不能为空")
|
||||
@Size(max = 30, message = "房间号长度不能超过30个字符")
|
||||
private String dormRoom;
|
||||
|
||||
/** 核定床位容量 (1~8) */
|
||||
@NotNull(message = "床位容量不能为空")
|
||||
@Min(value = 1, message = "床位容量最小为1")
|
||||
@Max(value = 8, message = "床位容量最大为8")
|
||||
private Integer capacity;
|
||||
|
||||
/** 适用性别 (0:不限, 1:男, 2:女) */
|
||||
@NotNull(message = "适用性别不能为空")
|
||||
private Integer genderType;
|
||||
|
||||
/** 智能门锁SN */
|
||||
@Size(max = 64, message = "智能锁设备序列号长度不能超过64个字符")
|
||||
private String lockDeviceSn;
|
||||
|
||||
/** 备注 */
|
||||
@Size(max = 500, message = "备注长度不能超过500个字符")
|
||||
private String remark;
|
||||
|
||||
public String getDormBuilding()
|
||||
{
|
||||
return dormBuilding;
|
||||
}
|
||||
|
||||
public void setDormBuilding(String dormBuilding)
|
||||
{
|
||||
this.dormBuilding = dormBuilding;
|
||||
}
|
||||
|
||||
public String getDormFloor()
|
||||
{
|
||||
return dormFloor;
|
||||
}
|
||||
|
||||
public void setDormFloor(String dormFloor)
|
||||
{
|
||||
this.dormFloor = dormFloor;
|
||||
}
|
||||
|
||||
public String getDormRoom()
|
||||
{
|
||||
return dormRoom;
|
||||
}
|
||||
|
||||
public void setDormRoom(String dormRoom)
|
||||
{
|
||||
this.dormRoom = dormRoom;
|
||||
}
|
||||
|
||||
public Integer getCapacity()
|
||||
{
|
||||
return capacity;
|
||||
}
|
||||
|
||||
public void setCapacity(Integer capacity)
|
||||
{
|
||||
this.capacity = capacity;
|
||||
}
|
||||
|
||||
public Integer getGenderType()
|
||||
{
|
||||
return genderType;
|
||||
}
|
||||
|
||||
public void setGenderType(Integer genderType)
|
||||
{
|
||||
this.genderType = genderType;
|
||||
}
|
||||
|
||||
public String getLockDeviceSn()
|
||||
{
|
||||
return lockDeviceSn;
|
||||
}
|
||||
|
||||
public void setLockDeviceSn(String lockDeviceSn)
|
||||
{
|
||||
this.lockDeviceSn = lockDeviceSn;
|
||||
}
|
||||
|
||||
public String getRemark()
|
||||
{
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark)
|
||||
{
|
||||
this.remark = remark;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package com.ruoyi.train.domain.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 宿舍楼栋/楼层/房间级联下拉选项 VO (ISSUE-01-05 / API Contract V1.0.2)
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class DormOptionsVO implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 节点取值 (楼栋名 / 楼层名 / dormId) */
|
||||
private String value;
|
||||
|
||||
/** 节点显示名称 (楼栋名 / 楼层名 / 房间号) */
|
||||
private String label;
|
||||
|
||||
/** 节点类型 ("building", "floor", "room") */
|
||||
private String type;
|
||||
|
||||
/** 子节点集合 (无子节点时为空列表或 null) */
|
||||
private List<DormOptionsVO> children;
|
||||
|
||||
// 以下为房间叶子节点扩展属性 (分房交互便利使用)
|
||||
/** 房间ID */
|
||||
private String dormId;
|
||||
|
||||
/** 楼栋名称 */
|
||||
private String dormBuilding;
|
||||
|
||||
/** 所在楼层 */
|
||||
private String dormFloor;
|
||||
|
||||
/** 房间号 */
|
||||
private String dormRoom;
|
||||
|
||||
/** 核定床位容量 */
|
||||
private Integer capacity;
|
||||
|
||||
/** 已住人数 */
|
||||
private Integer usedCount;
|
||||
|
||||
/** 剩余床位 */
|
||||
private Integer remainingBeds;
|
||||
|
||||
/** 适用性别 (0:不限, 1:男, 2:女) */
|
||||
private Integer genderType;
|
||||
|
||||
/** 智能锁序列号 */
|
||||
private String lockDeviceSn;
|
||||
|
||||
public DormOptionsVO()
|
||||
{
|
||||
}
|
||||
|
||||
public DormOptionsVO(String value, String label, String type)
|
||||
{
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
this.type = type;
|
||||
this.children = new ArrayList<>();
|
||||
}
|
||||
|
||||
public String getValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label)
|
||||
{
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String getType()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public List<DormOptionsVO> getChildren()
|
||||
{
|
||||
return children;
|
||||
}
|
||||
|
||||
public void setChildren(List<DormOptionsVO> children)
|
||||
{
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
public String getDormId()
|
||||
{
|
||||
return dormId;
|
||||
}
|
||||
|
||||
public void setDormId(String dormId)
|
||||
{
|
||||
this.dormId = dormId;
|
||||
}
|
||||
|
||||
public String getDormBuilding()
|
||||
{
|
||||
return dormBuilding;
|
||||
}
|
||||
|
||||
public void setDormBuilding(String dormBuilding)
|
||||
{
|
||||
this.dormBuilding = dormBuilding;
|
||||
}
|
||||
|
||||
public String getDormFloor()
|
||||
{
|
||||
return dormFloor;
|
||||
}
|
||||
|
||||
public void setDormFloor(String dormFloor)
|
||||
{
|
||||
this.dormFloor = dormFloor;
|
||||
}
|
||||
|
||||
public String getDormRoom()
|
||||
{
|
||||
return dormRoom;
|
||||
}
|
||||
|
||||
public void setDormRoom(String dormRoom)
|
||||
{
|
||||
this.dormRoom = dormRoom;
|
||||
}
|
||||
|
||||
public Integer getCapacity()
|
||||
{
|
||||
return capacity;
|
||||
}
|
||||
|
||||
public void setCapacity(Integer capacity)
|
||||
{
|
||||
this.capacity = capacity;
|
||||
}
|
||||
|
||||
public Integer getUsedCount()
|
||||
{
|
||||
return usedCount;
|
||||
}
|
||||
|
||||
public void setUsedCount(Integer usedCount)
|
||||
{
|
||||
this.usedCount = usedCount;
|
||||
}
|
||||
|
||||
public Integer getRemainingBeds()
|
||||
{
|
||||
return remainingBeds;
|
||||
}
|
||||
|
||||
public void setRemainingBeds(Integer remainingBeds)
|
||||
{
|
||||
this.remainingBeds = remainingBeds;
|
||||
}
|
||||
|
||||
public Integer getGenderType()
|
||||
{
|
||||
return genderType;
|
||||
}
|
||||
|
||||
public void setGenderType(Integer genderType)
|
||||
{
|
||||
this.genderType = genderType;
|
||||
}
|
||||
|
||||
public String getLockDeviceSn()
|
||||
{
|
||||
return lockDeviceSn;
|
||||
}
|
||||
|
||||
public void setLockDeviceSn(String lockDeviceSn)
|
||||
{
|
||||
this.lockDeviceSn = lockDeviceSn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package com.ruoyi.train.domain.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
/**
|
||||
* 宿舍房间详情 VO (ISSUE-01-05 / API Contract V1.0.2)
|
||||
* 包含房间基础属性与当前入住人员列表
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class DormitoryDetailVO implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 宿舍ID主键 */
|
||||
private String dormId;
|
||||
|
||||
/** 宿舍楼栋 */
|
||||
private String dormBuilding;
|
||||
|
||||
/** 所在楼层 */
|
||||
private String dormFloor;
|
||||
|
||||
/** 房间号 */
|
||||
private String dormRoom;
|
||||
|
||||
/** 核定床位容量 */
|
||||
private Integer capacity;
|
||||
|
||||
/** 已住人数 (实时统计自 train_accommodation) */
|
||||
private Integer usedCount;
|
||||
|
||||
/** 剩余床位 (capacity - usedCount) */
|
||||
private Integer remainingBeds;
|
||||
|
||||
/** 适用性别 (0:不限, 1:男, 2:女) */
|
||||
private Integer genderType;
|
||||
|
||||
/** 智能门锁SN */
|
||||
private String lockDeviceSn;
|
||||
|
||||
/** 房间状态 (0: 禁用, 1: 可用) */
|
||||
private Integer dormStatus;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 创建时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
/** 当前入住人员列表 (无入住人员时返回空列表 []) */
|
||||
private List<DormitoryOccupantVO> occupants = new ArrayList<>();
|
||||
|
||||
public String getDormId()
|
||||
{
|
||||
return dormId;
|
||||
}
|
||||
|
||||
public void setDormId(String dormId)
|
||||
{
|
||||
this.dormId = dormId;
|
||||
}
|
||||
|
||||
public String getDormBuilding()
|
||||
{
|
||||
return dormBuilding;
|
||||
}
|
||||
|
||||
public void setDormBuilding(String dormBuilding)
|
||||
{
|
||||
this.dormBuilding = dormBuilding;
|
||||
}
|
||||
|
||||
public String getDormFloor()
|
||||
{
|
||||
return dormFloor;
|
||||
}
|
||||
|
||||
public void setDormFloor(String dormFloor)
|
||||
{
|
||||
this.dormFloor = dormFloor;
|
||||
}
|
||||
|
||||
public String getDormRoom()
|
||||
{
|
||||
return dormRoom;
|
||||
}
|
||||
|
||||
public void setDormRoom(String dormRoom)
|
||||
{
|
||||
this.dormRoom = dormRoom;
|
||||
}
|
||||
|
||||
public Integer getCapacity()
|
||||
{
|
||||
return capacity;
|
||||
}
|
||||
|
||||
public void setCapacity(Integer capacity)
|
||||
{
|
||||
this.capacity = capacity;
|
||||
}
|
||||
|
||||
public Integer getUsedCount()
|
||||
{
|
||||
return usedCount;
|
||||
}
|
||||
|
||||
public void setUsedCount(Integer usedCount)
|
||||
{
|
||||
this.usedCount = usedCount;
|
||||
}
|
||||
|
||||
public Integer getRemainingBeds()
|
||||
{
|
||||
return remainingBeds;
|
||||
}
|
||||
|
||||
public void setRemainingBeds(Integer remainingBeds)
|
||||
{
|
||||
this.remainingBeds = remainingBeds;
|
||||
}
|
||||
|
||||
public Integer getGenderType()
|
||||
{
|
||||
return genderType;
|
||||
}
|
||||
|
||||
public void setGenderType(Integer genderType)
|
||||
{
|
||||
this.genderType = genderType;
|
||||
}
|
||||
|
||||
public String getLockDeviceSn()
|
||||
{
|
||||
return lockDeviceSn;
|
||||
}
|
||||
|
||||
public void setLockDeviceSn(String lockDeviceSn)
|
||||
{
|
||||
this.lockDeviceSn = lockDeviceSn;
|
||||
}
|
||||
|
||||
public Integer getDormStatus()
|
||||
{
|
||||
return dormStatus;
|
||||
}
|
||||
|
||||
public void setDormStatus(Integer dormStatus)
|
||||
{
|
||||
this.dormStatus = dormStatus;
|
||||
}
|
||||
|
||||
public String getRemark()
|
||||
{
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark)
|
||||
{
|
||||
this.remark = remark;
|
||||
}
|
||||
|
||||
public Date getCreateTime()
|
||||
{
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(Date createTime)
|
||||
{
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
public List<DormitoryOccupantVO> getOccupants()
|
||||
{
|
||||
return occupants;
|
||||
}
|
||||
|
||||
public void setOccupants(List<DormitoryOccupantVO> occupants)
|
||||
{
|
||||
this.occupants = occupants != null ? occupants : new ArrayList<>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.ruoyi.train.domain.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
/**
|
||||
* 宿舍房间列表项 VO (ISSUE-01-05 / API Contract V1.0.2)
|
||||
* 包含容量、已住人数、剩余床位
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class DormitoryItemVO implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 宿舍ID主键 */
|
||||
private String dormId;
|
||||
|
||||
/** 宿舍楼栋 */
|
||||
private String dormBuilding;
|
||||
|
||||
/** 所在楼层 */
|
||||
private String dormFloor;
|
||||
|
||||
/** 房间号 */
|
||||
private String dormRoom;
|
||||
|
||||
/** 核定床位容量 */
|
||||
private Integer capacity;
|
||||
|
||||
/** 已住人数 (实时统计自 train_accommodation) */
|
||||
private Integer usedCount;
|
||||
|
||||
/** 剩余床位 (capacity - usedCount) */
|
||||
private Integer remainingBeds;
|
||||
|
||||
/** 适用性别 (0:不限, 1:男, 2:女) */
|
||||
private Integer genderType;
|
||||
|
||||
/** 智能门锁SN */
|
||||
private String lockDeviceSn;
|
||||
|
||||
/** 房间状态 (0: 禁用, 1: 可用) */
|
||||
private Integer dormStatus;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 创建时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
public String getDormId()
|
||||
{
|
||||
return dormId;
|
||||
}
|
||||
|
||||
public void setDormId(String dormId)
|
||||
{
|
||||
this.dormId = dormId;
|
||||
}
|
||||
|
||||
public String getDormBuilding()
|
||||
{
|
||||
return dormBuilding;
|
||||
}
|
||||
|
||||
public void setDormBuilding(String dormBuilding)
|
||||
{
|
||||
this.dormBuilding = dormBuilding;
|
||||
}
|
||||
|
||||
public String getDormFloor()
|
||||
{
|
||||
return dormFloor;
|
||||
}
|
||||
|
||||
public void setDormFloor(String dormFloor)
|
||||
{
|
||||
this.dormFloor = dormFloor;
|
||||
}
|
||||
|
||||
public String getDormRoom()
|
||||
{
|
||||
return dormRoom;
|
||||
}
|
||||
|
||||
public void setDormRoom(String dormRoom)
|
||||
{
|
||||
this.dormRoom = dormRoom;
|
||||
}
|
||||
|
||||
public Integer getCapacity()
|
||||
{
|
||||
return capacity;
|
||||
}
|
||||
|
||||
public void setCapacity(Integer capacity)
|
||||
{
|
||||
this.capacity = capacity;
|
||||
}
|
||||
|
||||
public Integer getUsedCount()
|
||||
{
|
||||
return usedCount;
|
||||
}
|
||||
|
||||
public void setUsedCount(Integer usedCount)
|
||||
{
|
||||
this.usedCount = usedCount;
|
||||
}
|
||||
|
||||
public Integer getRemainingBeds()
|
||||
{
|
||||
return remainingBeds;
|
||||
}
|
||||
|
||||
public void setRemainingBeds(Integer remainingBeds)
|
||||
{
|
||||
this.remainingBeds = remainingBeds;
|
||||
}
|
||||
|
||||
public Integer getGenderType()
|
||||
{
|
||||
return genderType;
|
||||
}
|
||||
|
||||
public void setGenderType(Integer genderType)
|
||||
{
|
||||
this.genderType = genderType;
|
||||
}
|
||||
|
||||
public String getLockDeviceSn()
|
||||
{
|
||||
return lockDeviceSn;
|
||||
}
|
||||
|
||||
public void setLockDeviceSn(String lockDeviceSn)
|
||||
{
|
||||
this.lockDeviceSn = lockDeviceSn;
|
||||
}
|
||||
|
||||
public Integer getDormStatus()
|
||||
{
|
||||
return dormStatus;
|
||||
}
|
||||
|
||||
public void setDormStatus(Integer dormStatus)
|
||||
{
|
||||
this.dormStatus = dormStatus;
|
||||
}
|
||||
|
||||
public String getRemark()
|
||||
{
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark)
|
||||
{
|
||||
this.remark = remark;
|
||||
}
|
||||
|
||||
public Date getCreateTime()
|
||||
{
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(Date createTime)
|
||||
{
|
||||
this.createTime = createTime;
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
package com.ruoyi.train.domain.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
/**
|
||||
* 房间当前入住学员 VO (ISSUE-01-05 / API Contract V1.0.2)
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class DormitoryOccupantVO implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 参训档案ID */
|
||||
private Long enrollmentId;
|
||||
|
||||
/** 学员ID */
|
||||
private String studentId;
|
||||
|
||||
/** 学员姓名 */
|
||||
private String studentName;
|
||||
|
||||
/** 学员性别 */
|
||||
private String gender;
|
||||
|
||||
/** 所属单位/部门 */
|
||||
private String orgName;
|
||||
|
||||
/** 分配床位号 (可选) */
|
||||
private String bedNo;
|
||||
|
||||
/** 分配时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date assignedTime;
|
||||
|
||||
/** 住宿状态 (0:预分配, 1:已入住) */
|
||||
private Integer accommodationStatus;
|
||||
|
||||
public Long getEnrollmentId()
|
||||
{
|
||||
return enrollmentId;
|
||||
}
|
||||
|
||||
public void setEnrollmentId(Long enrollmentId)
|
||||
{
|
||||
this.enrollmentId = enrollmentId;
|
||||
}
|
||||
|
||||
public String getStudentId()
|
||||
{
|
||||
return studentId;
|
||||
}
|
||||
|
||||
public void setStudentId(String studentId)
|
||||
{
|
||||
this.studentId = studentId;
|
||||
}
|
||||
|
||||
public String getStudentName()
|
||||
{
|
||||
return studentName;
|
||||
}
|
||||
|
||||
public void setStudentName(String studentName)
|
||||
{
|
||||
this.studentName = studentName;
|
||||
}
|
||||
|
||||
public String getGender()
|
||||
{
|
||||
return gender;
|
||||
}
|
||||
|
||||
public void setGender(String gender)
|
||||
{
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
public String getOrgName()
|
||||
{
|
||||
return orgName;
|
||||
}
|
||||
|
||||
public void setOrgName(String orgName)
|
||||
{
|
||||
this.orgName = orgName;
|
||||
}
|
||||
|
||||
public String getBedNo()
|
||||
{
|
||||
return bedNo;
|
||||
}
|
||||
|
||||
public void setBedNo(String bedNo)
|
||||
{
|
||||
this.bedNo = bedNo;
|
||||
}
|
||||
|
||||
public Date getAssignedTime()
|
||||
{
|
||||
return assignedTime;
|
||||
}
|
||||
|
||||
public void setAssignedTime(Date assignedTime)
|
||||
{
|
||||
this.assignedTime = assignedTime;
|
||||
}
|
||||
|
||||
public Integer getAccommodationStatus()
|
||||
{
|
||||
return accommodationStatus;
|
||||
}
|
||||
|
||||
public void setAccommodationStatus(Integer accommodationStatus)
|
||||
{
|
||||
this.accommodationStatus = accommodationStatus;
|
||||
}
|
||||
}
|
||||
+40
-13
@@ -1,35 +1,62 @@
|
||||
package com.ruoyi.train.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.ruoyi.train.domain.TrainDormitoryInfo;
|
||||
import com.ruoyi.train.domain.vo.DormitoryOccupantVO;
|
||||
|
||||
/**
|
||||
* 宿舍信息Mapper接口
|
||||
*
|
||||
* 宿舍信息Mapper接口 (ISSUE-01-05 / API Contract V1.0.2)
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-09-08
|
||||
* @date 2026-09-14
|
||||
*/
|
||||
public interface TrainDormitoryInfoMapper
|
||||
public interface TrainDormitoryInfoMapper
|
||||
{
|
||||
/**
|
||||
* 查询宿舍信息
|
||||
*
|
||||
*
|
||||
* @param dormId 宿舍信息主键
|
||||
* @return 宿舍信息
|
||||
*/
|
||||
public TrainDormitoryInfo selectTrainDormitoryInfoByDormId(String dormId);
|
||||
|
||||
/**
|
||||
* 查询宿舍信息列表
|
||||
*
|
||||
* 查询宿舍信息列表 (包含实时聚合的 used_count 与 remaining_beds)
|
||||
*
|
||||
* @param trainDormitoryInfo 宿舍信息
|
||||
* @return 宿舍信息集合
|
||||
*/
|
||||
public List<TrainDormitoryInfo> selectTrainDormitoryInfoList(TrainDormitoryInfo trainDormitoryInfo);
|
||||
|
||||
/**
|
||||
* 检查同楼栋下房间号是否唯一
|
||||
*
|
||||
* @param dormBuilding 楼栋名称
|
||||
* @param dormRoom 房间号
|
||||
* @param dormId 排除的宿舍ID (新增时传 null)
|
||||
* @return 匹配的宿舍实体 (若无返回 null)
|
||||
*/
|
||||
public TrainDormitoryInfo checkBuildingRoomUnique(@Param("dormBuilding") String dormBuilding, @Param("dormRoom") String dormRoom, @Param("dormId") String dormId);
|
||||
|
||||
/**
|
||||
* 查询指定宿舍当前有效入住的学员列表
|
||||
*
|
||||
* @param dormId 宿舍ID
|
||||
* @return 入住学员列表
|
||||
*/
|
||||
public List<DormitoryOccupantVO> selectOccupantsByDormId(@Param("dormId") String dormId);
|
||||
|
||||
/**
|
||||
* 查询所有未删除且可用的宿舍列表 (用于生成级联树)
|
||||
*
|
||||
* @return 宿舍列表
|
||||
*/
|
||||
public List<TrainDormitoryInfo> selectAllAvailableDormitories();
|
||||
|
||||
/**
|
||||
* 新增宿舍信息
|
||||
*
|
||||
*
|
||||
* @param trainDormitoryInfo 宿舍信息
|
||||
* @return 结果
|
||||
*/
|
||||
@@ -37,23 +64,23 @@ public interface TrainDormitoryInfoMapper
|
||||
|
||||
/**
|
||||
* 修改宿舍信息
|
||||
*
|
||||
*
|
||||
* @param trainDormitoryInfo 宿舍信息
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateTrainDormitoryInfo(TrainDormitoryInfo trainDormitoryInfo);
|
||||
|
||||
/**
|
||||
* 删除宿舍信息
|
||||
*
|
||||
* 软删除宿舍信息
|
||||
*
|
||||
* @param dormId 宿舍信息主键
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteTrainDormitoryInfoByDormId(String dormId);
|
||||
|
||||
/**
|
||||
* 批量删除宿舍信息
|
||||
*
|
||||
* 批量软删除宿舍信息
|
||||
*
|
||||
* @param dormIds 需要删除的数据主键集合
|
||||
* @return 结果
|
||||
*/
|
||||
|
||||
+50
-13
@@ -2,34 +2,64 @@ package com.ruoyi.train.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.train.domain.TrainDormitoryInfo;
|
||||
import com.ruoyi.train.domain.dto.DormitoryCreateDTO;
|
||||
import com.ruoyi.train.domain.vo.DormOptionsVO;
|
||||
import com.ruoyi.train.domain.vo.DormitoryDetailVO;
|
||||
|
||||
/**
|
||||
* 宿舍信息Service接口
|
||||
*
|
||||
* 宿舍信息Service接口 (ISSUE-01-05 / API Contract V1.0.2)
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-09-08
|
||||
* @date 2026-09-14
|
||||
*/
|
||||
public interface ITrainDormitoryInfoService
|
||||
public interface ITrainDormitoryInfoService
|
||||
{
|
||||
/**
|
||||
* 查询宿舍信息
|
||||
*
|
||||
* 查询宿舍信息 (基础Domain)
|
||||
*
|
||||
* @param dormId 宿舍信息主键
|
||||
* @return 宿舍信息
|
||||
*/
|
||||
public TrainDormitoryInfo selectTrainDormitoryInfoByDormId(String dormId);
|
||||
|
||||
/**
|
||||
* 查询宿舍房间详情 (包含基础属性与当前入住人员列表)
|
||||
*
|
||||
* @param dormId 宿舍信息主键
|
||||
* @return 宿舍详情VO
|
||||
*/
|
||||
public DormitoryDetailVO selectDormitoryDetailByDormId(String dormId);
|
||||
|
||||
/**
|
||||
* 查询宿舍信息列表
|
||||
*
|
||||
*
|
||||
* @param trainDormitoryInfo 宿舍信息
|
||||
* @return 宿舍信息集合
|
||||
*/
|
||||
public List<TrainDormitoryInfo> selectTrainDormitoryInfoList(TrainDormitoryInfo trainDormitoryInfo);
|
||||
|
||||
/**
|
||||
* 新增宿舍信息
|
||||
*
|
||||
* 校验同楼栋下房间号是否唯一
|
||||
*
|
||||
* @param dormBuilding 楼栋名称
|
||||
* @param dormRoom 房间号
|
||||
* @param dormId 排除的宿舍ID (新增时传 null)
|
||||
* @return true 唯一 (无冲突), false 不唯一 (已存在)
|
||||
*/
|
||||
public boolean checkBuildingRoomUnique(String dormBuilding, String dormRoom, String dormId);
|
||||
|
||||
/**
|
||||
* 新增宿舍房间 (带严格校验与查重防重)
|
||||
*
|
||||
* @param dto 宿舍创建DTO
|
||||
* @param operator 操作人
|
||||
* @return 结果
|
||||
*/
|
||||
public int insertTrainDormitory(DormitoryCreateDTO dto, String operator);
|
||||
|
||||
/**
|
||||
* 新增宿舍信息 (兼容底层Entity)
|
||||
*
|
||||
* @param trainDormitoryInfo 宿舍信息
|
||||
* @return 结果
|
||||
*/
|
||||
@@ -37,7 +67,7 @@ public interface ITrainDormitoryInfoService
|
||||
|
||||
/**
|
||||
* 修改宿舍信息
|
||||
*
|
||||
*
|
||||
* @param trainDormitoryInfo 宿舍信息
|
||||
* @return 结果
|
||||
*/
|
||||
@@ -45,17 +75,24 @@ public interface ITrainDormitoryInfoService
|
||||
|
||||
/**
|
||||
* 批量删除宿舍信息
|
||||
*
|
||||
*
|
||||
* @param dormIds 需要删除的宿舍信息主键集合
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteTrainDormitoryInfoByDormIds(String[] dormIds);
|
||||
|
||||
/**
|
||||
* 删除宿舍信息信息
|
||||
*
|
||||
* 删除宿舍信息
|
||||
*
|
||||
* @param dormId 宿舍信息主键
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteTrainDormitoryInfoByDormId(String dormId);
|
||||
|
||||
/**
|
||||
* 查询楼栋 -> 楼层 -> 房间结构化级联选项树
|
||||
*
|
||||
* @return 级联树集合
|
||||
*/
|
||||
public List<DormOptionsVO> selectDormitoryTreeOptions();
|
||||
}
|
||||
+216
-16
@@ -1,28 +1,39 @@
|
||||
package com.ruoyi.train.service.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import java.util.Map;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.train.mapper.TrainDormitoryInfoMapper;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.uuid.IdUtils;
|
||||
import com.ruoyi.train.domain.TrainDormitoryInfo;
|
||||
import com.ruoyi.train.domain.dto.DormitoryCreateDTO;
|
||||
import com.ruoyi.train.domain.vo.DormOptionsVO;
|
||||
import com.ruoyi.train.domain.vo.DormitoryDetailVO;
|
||||
import com.ruoyi.train.domain.vo.DormitoryOccupantVO;
|
||||
import com.ruoyi.train.mapper.TrainDormitoryInfoMapper;
|
||||
import com.ruoyi.train.service.ITrainDormitoryInfoService;
|
||||
|
||||
/**
|
||||
* 宿舍信息Service业务层处理
|
||||
*
|
||||
* 宿舍信息Service业务层处理 (ISSUE-01-05 / API Contract V1.0.2)
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-09-08
|
||||
* @date 2026-09-14
|
||||
*/
|
||||
@Service
|
||||
public class TrainDormitoryInfoServiceImpl implements ITrainDormitoryInfoService
|
||||
public class TrainDormitoryInfoServiceImpl implements ITrainDormitoryInfoService
|
||||
{
|
||||
@Autowired
|
||||
private TrainDormitoryInfoMapper trainDormitoryInfoMapper;
|
||||
|
||||
/**
|
||||
* 查询宿舍信息
|
||||
*
|
||||
* 查询宿舍信息 (基础Domain)
|
||||
*
|
||||
* @param dormId 宿舍信息主键
|
||||
* @return 宿舍信息
|
||||
*/
|
||||
@@ -33,8 +44,44 @@ public class TrainDormitoryInfoServiceImpl implements ITrainDormitoryInfoService
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询宿舍信息列表
|
||||
*
|
||||
* 查询宿舍房间详情 (包含基础属性与当前入住人员列表)
|
||||
*
|
||||
* @param dormId 宿舍信息主键
|
||||
* @return 宿舍详情VO
|
||||
*/
|
||||
@Override
|
||||
public DormitoryDetailVO selectDormitoryDetailByDormId(String dormId)
|
||||
{
|
||||
TrainDormitoryInfo dorm = trainDormitoryInfoMapper.selectTrainDormitoryInfoByDormId(dormId);
|
||||
if (dorm == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
DormitoryDetailVO detailVO = new DormitoryDetailVO();
|
||||
detailVO.setDormId(dorm.getDormId());
|
||||
detailVO.setDormBuilding(dorm.getDormBuilding());
|
||||
detailVO.setDormFloor(dorm.getDormFloor());
|
||||
detailVO.setDormRoom(dorm.getDormRoom());
|
||||
detailVO.setCapacity(dorm.getCapacity());
|
||||
detailVO.setUsedCount(dorm.getUsedCount());
|
||||
detailVO.setRemainingBeds(dorm.getRemainingBeds());
|
||||
detailVO.setGenderType(dorm.getGenderType());
|
||||
detailVO.setLockDeviceSn(dorm.getLockDeviceSn());
|
||||
detailVO.setDormStatus(dorm.getDormStatus());
|
||||
detailVO.setRemark(dorm.getRemark());
|
||||
detailVO.setCreateTime(dorm.getCreateTime());
|
||||
|
||||
// 查询当前有效入住学员列表
|
||||
List<DormitoryOccupantVO> occupants = trainDormitoryInfoMapper.selectOccupantsByDormId(dormId);
|
||||
detailVO.setOccupants(occupants != null ? occupants : new ArrayList<>());
|
||||
|
||||
return detailVO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询宿舍信息列表 (包含实时聚合的 used_count 与 remaining_beds)
|
||||
*
|
||||
* @param trainDormitoryInfo 宿舍信息
|
||||
* @return 宿舍信息
|
||||
*/
|
||||
@@ -45,21 +92,103 @@ public class TrainDormitoryInfoServiceImpl implements ITrainDormitoryInfoService
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增宿舍信息
|
||||
*
|
||||
* 校验同楼栋下房间号是否唯一
|
||||
*
|
||||
* @param dormBuilding 楼栋名称
|
||||
* @param dormRoom 房间号
|
||||
* @param dormId 排除的宿舍ID (新增时传 null)
|
||||
* @return true 唯一 (无冲突), false 不唯一 (已存在)
|
||||
*/
|
||||
@Override
|
||||
public boolean checkBuildingRoomUnique(String dormBuilding, String dormRoom, String dormId)
|
||||
{
|
||||
if (StringUtils.isEmpty(dormBuilding) || StringUtils.isEmpty(dormRoom))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
TrainDormitoryInfo info = trainDormitoryInfoMapper.checkBuildingRoomUnique(dormBuilding, dormRoom, dormId);
|
||||
return info == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增宿舍房间 (带严格校验与查重防重)
|
||||
*
|
||||
* @param dto 宿舍创建DTO
|
||||
* @param operator 操作人
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int insertTrainDormitory(DormitoryCreateDTO dto, String operator)
|
||||
{
|
||||
// 1. 容量校验:必须在 1~8 之间
|
||||
if (dto.getCapacity() == null || dto.getCapacity() < 1 || dto.getCapacity() > 8)
|
||||
{
|
||||
throw new ServiceException("房间床位容量必须在1至8之间");
|
||||
}
|
||||
|
||||
// 2. 适用性别校验:0:不限, 1:男, 2:女
|
||||
if (dto.getGenderType() == null || dto.getGenderType() < 0 || dto.getGenderType() > 2)
|
||||
{
|
||||
throw new ServiceException("适用性别取值不合法");
|
||||
}
|
||||
|
||||
String building = dto.getDormBuilding().trim();
|
||||
String room = dto.getDormRoom().trim();
|
||||
|
||||
// 3. 同楼栋下房间号查重 (抛出错误码 40006)
|
||||
if (!checkBuildingRoomUnique(building, room, null))
|
||||
{
|
||||
throw new ServiceException("同一楼栋下该宿舍房号已存在", 40006);
|
||||
}
|
||||
|
||||
// 4. 构建实体
|
||||
TrainDormitoryInfo info = new TrainDormitoryInfo();
|
||||
info.setDormId(IdUtils.fastSimpleUUID());
|
||||
info.setDormBuilding(building);
|
||||
info.setDormFloor(dto.getDormFloor().trim());
|
||||
info.setDormRoom(room);
|
||||
info.setCapacity(dto.getCapacity());
|
||||
info.setGenderType(dto.getGenderType());
|
||||
info.setLockDeviceSn(StringUtils.trimToNull(dto.getLockDeviceSn()));
|
||||
info.setDormStatus(1); // 默认可用
|
||||
info.setDelFlag("0");
|
||||
info.setCreateBy(operator);
|
||||
info.setCreateTime(DateUtils.getNowDate());
|
||||
try
|
||||
{
|
||||
return trainDormitoryInfoMapper.insertTrainDormitoryInfo(info);
|
||||
}
|
||||
catch (DuplicateKeyException e)
|
||||
{
|
||||
throw new ServiceException("同一楼栋下该宿舍房号已存在", 40006);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增宿舍信息 (兼容底层Entity)
|
||||
*
|
||||
* @param trainDormitoryInfo 宿舍信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int insertTrainDormitoryInfo(TrainDormitoryInfo trainDormitoryInfo)
|
||||
{
|
||||
if (StringUtils.isEmpty(trainDormitoryInfo.getDormId()))
|
||||
{
|
||||
trainDormitoryInfo.setDormId(IdUtils.fastSimpleUUID());
|
||||
}
|
||||
if (trainDormitoryInfo.getDormStatus() == null)
|
||||
{
|
||||
trainDormitoryInfo.setDormStatus(1);
|
||||
}
|
||||
trainDormitoryInfo.setDelFlag("0");
|
||||
trainDormitoryInfo.setCreateTime(DateUtils.getNowDate());
|
||||
return trainDormitoryInfoMapper.insertTrainDormitoryInfo(trainDormitoryInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改宿舍信息
|
||||
*
|
||||
*
|
||||
* @param trainDormitoryInfo 宿舍信息
|
||||
* @return 结果
|
||||
*/
|
||||
@@ -72,7 +201,7 @@ public class TrainDormitoryInfoServiceImpl implements ITrainDormitoryInfoService
|
||||
|
||||
/**
|
||||
* 批量删除宿舍信息
|
||||
*
|
||||
*
|
||||
* @param dormIds 需要删除的宿舍信息主键
|
||||
* @return 结果
|
||||
*/
|
||||
@@ -83,8 +212,8 @@ public class TrainDormitoryInfoServiceImpl implements ITrainDormitoryInfoService
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除宿舍信息信息
|
||||
*
|
||||
* 删除宿舍信息
|
||||
*
|
||||
* @param dormId 宿舍信息主键
|
||||
* @return 结果
|
||||
*/
|
||||
@@ -93,4 +222,75 @@ public class TrainDormitoryInfoServiceImpl implements ITrainDormitoryInfoService
|
||||
{
|
||||
return trainDormitoryInfoMapper.deleteTrainDormitoryInfoByDormId(dormId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询楼栋 -> 楼层 -> 房间结构化级联选项树
|
||||
*
|
||||
* @return 级联树集合
|
||||
*/
|
||||
@Override
|
||||
public List<DormOptionsVO> selectDormitoryTreeOptions()
|
||||
{
|
||||
List<TrainDormitoryInfo> allDorms = trainDormitoryInfoMapper.selectAllAvailableDormitories();
|
||||
if (allDorms == null || allDorms.isEmpty())
|
||||
{
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// 使用 LinkedHashMap 保持有序分组:Building -> Floor -> List<Dormitory>
|
||||
Map<String, Map<String, List<TrainDormitoryInfo>>> buildingMap = new LinkedHashMap<>();
|
||||
|
||||
for (TrainDormitoryInfo dorm : allDorms)
|
||||
{
|
||||
String building = dorm.getDormBuilding();
|
||||
String floor = dorm.getDormFloor();
|
||||
|
||||
buildingMap
|
||||
.computeIfAbsent(building, k -> new LinkedHashMap<>())
|
||||
.computeIfAbsent(floor, k -> new ArrayList<>())
|
||||
.add(dorm);
|
||||
}
|
||||
|
||||
List<DormOptionsVO> resultTree = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, Map<String, List<TrainDormitoryInfo>>> bEntry : buildingMap.entrySet())
|
||||
{
|
||||
String buildingName = bEntry.getKey();
|
||||
DormOptionsVO buildingNode = new DormOptionsVO(buildingName, buildingName, "building");
|
||||
|
||||
List<DormOptionsVO> floorNodeList = new ArrayList<>();
|
||||
for (Map.Entry<String, List<TrainDormitoryInfo>> fEntry : bEntry.getValue().entrySet())
|
||||
{
|
||||
String floorName = fEntry.getKey();
|
||||
String floorLabel = floorName.endsWith("层") || floorName.endsWith("楼") ? floorName : floorName + "层";
|
||||
DormOptionsVO floorNode = new DormOptionsVO(floorName, floorLabel, "floor");
|
||||
|
||||
List<DormOptionsVO> roomNodeList = new ArrayList<>();
|
||||
for (TrainDormitoryInfo dorm : fEntry.getValue())
|
||||
{
|
||||
String roomLabel = dorm.getDormRoom().endsWith("室") ? dorm.getDormRoom() : dorm.getDormRoom() + "室";
|
||||
DormOptionsVO roomNode = new DormOptionsVO(dorm.getDormId(), roomLabel, "room");
|
||||
roomNode.setDormId(dorm.getDormId());
|
||||
roomNode.setDormBuilding(dorm.getDormBuilding());
|
||||
roomNode.setDormFloor(dorm.getDormFloor());
|
||||
roomNode.setDormRoom(dorm.getDormRoom());
|
||||
roomNode.setCapacity(dorm.getCapacity());
|
||||
roomNode.setUsedCount(dorm.getUsedCount());
|
||||
roomNode.setRemainingBeds(dorm.getRemainingBeds());
|
||||
roomNode.setGenderType(dorm.getGenderType());
|
||||
roomNode.setLockDeviceSn(dorm.getLockDeviceSn());
|
||||
// 房间节点为叶子节点,children 置为 null 便于前端 Cascader 渲染
|
||||
roomNode.setChildren(null);
|
||||
|
||||
roomNodeList.add(roomNode);
|
||||
}
|
||||
floorNode.setChildren(roomNodeList);
|
||||
floorNodeList.add(floorNode);
|
||||
}
|
||||
buildingNode.setChildren(floorNodeList);
|
||||
resultTree.add(buildingNode);
|
||||
}
|
||||
|
||||
return resultTree;
|
||||
}
|
||||
}
|
||||
+127
-48
@@ -3,98 +3,177 @@
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.train.mapper.TrainDormitoryInfoMapper">
|
||||
|
||||
|
||||
<resultMap type="TrainDormitoryInfo" id="TrainDormitoryInfoResult">
|
||||
<result property="dormId" column="dorm_id" />
|
||||
<result property="dormBuilding" column="dorm_building" />
|
||||
<result property="dormRoom" column="dorm_room" />
|
||||
<result property="bedTotal" column="bed_total" />
|
||||
<result property="bedUsed" column="bed_used" />
|
||||
<result property="lockDeviceSn" column="lock_device_sn" />
|
||||
<result property="dormStatus" column="dorm_status" />
|
||||
<result property="createUser" column="create_user" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="dormId" column="dorm_id" />
|
||||
<result property="dormBuilding" column="dorm_building" />
|
||||
<result property="dormFloor" column="dorm_floor" />
|
||||
<result property="dormRoom" column="dorm_room" />
|
||||
<result property="capacity" column="capacity" />
|
||||
<result property="genderType" column="gender_type" />
|
||||
<result property="lockDeviceSn" column="lock_device_sn" />
|
||||
<result property="dormStatus" column="dorm_status" />
|
||||
<result property="delFlag" column="del_flag" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="usedCount" column="used_count" />
|
||||
<result property="remainingBeds" column="remaining_beds" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectTrainDormitoryInfoVo">
|
||||
select dorm_id, dorm_building, dorm_room, bed_total, bed_used, lock_device_sn, dorm_status, create_user, create_time, update_time, remark from train_dormitory_info
|
||||
SELECT
|
||||
d.dorm_id,
|
||||
d.dorm_building,
|
||||
d.dorm_floor,
|
||||
d.dorm_room,
|
||||
d.capacity,
|
||||
d.gender_type,
|
||||
d.lock_device_sn,
|
||||
d.dorm_status,
|
||||
d.del_flag,
|
||||
d.create_by,
|
||||
d.create_time,
|
||||
d.update_by,
|
||||
d.update_time,
|
||||
d.remark,
|
||||
COALESCE(acc.used_count, 0) AS used_count,
|
||||
(d.capacity - COALESCE(acc.used_count, 0)) AS remaining_beds
|
||||
FROM train_dormitory_info d
|
||||
LEFT JOIN (
|
||||
SELECT dorm_id, COUNT(1) AS used_count
|
||||
FROM train_accommodation
|
||||
WHERE accommodation_status IN (0, 1)
|
||||
GROUP BY dorm_id
|
||||
) acc ON d.dorm_id = acc.dorm_id
|
||||
</sql>
|
||||
|
||||
<select id="selectTrainDormitoryInfoList" parameterType="TrainDormitoryInfo" resultMap="TrainDormitoryInfoResult">
|
||||
<include refid="selectTrainDormitoryInfoVo"/>
|
||||
<where>
|
||||
<if test="dormBuilding != null and dormBuilding != ''"> and dorm_building = #{dormBuilding}</if>
|
||||
<if test="dormRoom != null and dormRoom != ''"> and dorm_room = #{dormRoom}</if>
|
||||
<if test="bedTotal != null "> and bed_total = #{bedTotal}</if>
|
||||
<if test="bedUsed != null "> and bed_used = #{bedUsed}</if>
|
||||
<if test="lockDeviceSn != null and lockDeviceSn != ''"> and lock_device_sn = #{lockDeviceSn}</if>
|
||||
<if test="dormStatus != null "> and dorm_status = #{dormStatus}</if>
|
||||
<if test="createUser != null and createUser != ''"> and create_user = #{createUser}</if>
|
||||
</where>
|
||||
WHERE d.del_flag = '0'
|
||||
<if test="dormBuilding != null and dormBuilding != ''">
|
||||
AND d.dorm_building LIKE concat('%', #{dormBuilding}, '%')
|
||||
</if>
|
||||
<if test="dormFloor != null and dormFloor != ''">
|
||||
AND d.dorm_floor = #{dormFloor}
|
||||
</if>
|
||||
<if test="dormRoom != null and dormRoom != ''">
|
||||
AND d.dorm_room LIKE concat('%', #{dormRoom}, '%')
|
||||
</if>
|
||||
<if test="genderType != null">
|
||||
AND d.gender_type = #{genderType}
|
||||
</if>
|
||||
<if test="dormStatus != null">
|
||||
AND d.dorm_status = #{dormStatus}
|
||||
</if>
|
||||
ORDER BY d.dorm_building ASC, d.dorm_floor ASC, d.dorm_room ASC
|
||||
</select>
|
||||
|
||||
|
||||
<select id="selectTrainDormitoryInfoByDormId" parameterType="String" resultMap="TrainDormitoryInfoResult">
|
||||
<include refid="selectTrainDormitoryInfoVo"/>
|
||||
where dorm_id = #{dormId}
|
||||
WHERE d.dorm_id = #{dormId} AND d.del_flag = '0'
|
||||
</select>
|
||||
|
||||
<select id="checkBuildingRoomUnique" resultMap="TrainDormitoryInfoResult">
|
||||
SELECT dorm_id, dorm_building, dorm_room
|
||||
FROM train_dormitory_info
|
||||
WHERE dorm_building = #{dormBuilding}
|
||||
AND dorm_room = #{dormRoom}
|
||||
AND del_flag = '0'
|
||||
<if test="dormId != null and dormId != ''">
|
||||
AND dorm_id != #{dormId}
|
||||
</if>
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<select id="selectAllAvailableDormitories" resultMap="TrainDormitoryInfoResult">
|
||||
<include refid="selectTrainDormitoryInfoVo"/>
|
||||
WHERE d.del_flag = '0' AND d.dorm_status = 1
|
||||
ORDER BY d.dorm_building ASC, d.dorm_floor ASC, d.dorm_room ASC
|
||||
</select>
|
||||
|
||||
<select id="selectOccupantsByDormId" resultType="com.ruoyi.train.domain.vo.DormitoryOccupantVO">
|
||||
SELECT
|
||||
a.enrollment_id AS enrollmentId,
|
||||
e.student_id AS studentId,
|
||||
s.s_name AS studentName,
|
||||
s.s_gender AS gender,
|
||||
s.org_name AS orgName,
|
||||
a.bed_no AS bedNo,
|
||||
a.assigned_time AS assignedTime,
|
||||
a.accommodation_status AS accommodationStatus
|
||||
FROM train_accommodation a
|
||||
LEFT JOIN train_enrollment e ON a.enrollment_id = e.enrollment_id
|
||||
LEFT JOIN train_student s ON e.student_id = s.s_id
|
||||
WHERE a.dorm_id = #{dormId}
|
||||
AND a.accommodation_status IN (0, 1)
|
||||
ORDER BY a.assigned_time ASC
|
||||
</select>
|
||||
|
||||
<insert id="insertTrainDormitoryInfo" parameterType="TrainDormitoryInfo">
|
||||
insert into train_dormitory_info
|
||||
INSERT INTO train_dormitory_info
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="dormId != null">dorm_id,</if>
|
||||
<if test="dormBuilding != null">dorm_building,</if>
|
||||
<if test="dormBuilding != null and dormBuilding != ''">dorm_building,</if>
|
||||
<if test="dormFloor != null and dormFloor != ''">dorm_floor,</if>
|
||||
<if test="dormRoom != null and dormRoom != ''">dorm_room,</if>
|
||||
<if test="bedTotal != null">bed_total,</if>
|
||||
<if test="bedUsed != null">bed_used,</if>
|
||||
<if test="capacity != null">capacity,</if>
|
||||
<if test="genderType != null">gender_type,</if>
|
||||
<if test="lockDeviceSn != null">lock_device_sn,</if>
|
||||
<if test="dormStatus != null">dorm_status,</if>
|
||||
<if test="createUser != null and createUser != ''">create_user,</if>
|
||||
<if test="delFlag != null and delFlag != ''">del_flag,</if>
|
||||
<if test="createBy != null and createBy != ''">create_by,</if>
|
||||
<if test="createTime != null">create_time,</if>
|
||||
<if test="updateBy != null and updateBy != ''">update_by,</if>
|
||||
<if test="updateTime != null">update_time,</if>
|
||||
<if test="remark != null">remark,</if>
|
||||
</trim>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="dormId != null">#{dormId},</if>
|
||||
<if test="dormBuilding != null">#{dormBuilding},</if>
|
||||
<if test="dormBuilding != null and dormBuilding != ''">#{dormBuilding},</if>
|
||||
<if test="dormFloor != null and dormFloor != ''">#{dormFloor},</if>
|
||||
<if test="dormRoom != null and dormRoom != ''">#{dormRoom},</if>
|
||||
<if test="bedTotal != null">#{bedTotal},</if>
|
||||
<if test="bedUsed != null">#{bedUsed},</if>
|
||||
<if test="capacity != null">#{capacity},</if>
|
||||
<if test="genderType != null">#{genderType},</if>
|
||||
<if test="lockDeviceSn != null">#{lockDeviceSn},</if>
|
||||
<if test="dormStatus != null">#{dormStatus},</if>
|
||||
<if test="createUser != null and createUser != ''">#{createUser},</if>
|
||||
<if test="delFlag != null and delFlag != ''">#{delFlag},</if>
|
||||
<if test="createBy != null and createBy != ''">#{createBy},</if>
|
||||
<if test="createTime != null">#{createTime},</if>
|
||||
<if test="updateBy != null and updateBy != ''">#{updateBy},</if>
|
||||
<if test="updateTime != null">#{updateTime},</if>
|
||||
<if test="remark != null">#{remark},</if>
|
||||
</trim>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<update id="updateTrainDormitoryInfo" parameterType="TrainDormitoryInfo">
|
||||
update train_dormitory_info
|
||||
UPDATE train_dormitory_info
|
||||
<trim prefix="SET" suffixOverrides=",">
|
||||
<if test="dormBuilding != null">dorm_building = #{dormBuilding},</if>
|
||||
<if test="dormBuilding != null and dormBuilding != ''">dorm_building = #{dormBuilding},</if>
|
||||
<if test="dormFloor != null and dormFloor != ''">dorm_floor = #{dormFloor},</if>
|
||||
<if test="dormRoom != null and dormRoom != ''">dorm_room = #{dormRoom},</if>
|
||||
<if test="bedTotal != null">bed_total = #{bedTotal},</if>
|
||||
<if test="bedUsed != null">bed_used = #{bedUsed},</if>
|
||||
<if test="capacity != null">capacity = #{capacity},</if>
|
||||
<if test="genderType != null">gender_type = #{genderType},</if>
|
||||
<if test="lockDeviceSn != null">lock_device_sn = #{lockDeviceSn},</if>
|
||||
<if test="dormStatus != null">dorm_status = #{dormStatus},</if>
|
||||
<if test="createUser != null and createUser != ''">create_user = #{createUser},</if>
|
||||
<if test="createTime != null">create_time = #{createTime},</if>
|
||||
<if test="delFlag != null and delFlag != ''">del_flag = #{delFlag},</if>
|
||||
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
|
||||
<if test="updateTime != null">update_time = #{updateTime},</if>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
</trim>
|
||||
where dorm_id = #{dormId}
|
||||
WHERE dorm_id = #{dormId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteTrainDormitoryInfoByDormId" parameterType="String">
|
||||
delete from train_dormitory_info where dorm_id = #{dormId}
|
||||
</delete>
|
||||
<update id="deleteTrainDormitoryInfoByDormId" parameterType="String">
|
||||
UPDATE train_dormitory_info SET del_flag = '2' WHERE dorm_id = #{dormId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteTrainDormitoryInfoByDormIds" parameterType="String">
|
||||
delete from train_dormitory_info where dorm_id in
|
||||
<update id="deleteTrainDormitoryInfoByDormIds" parameterType="String">
|
||||
UPDATE train_dormitory_info SET del_flag = '2' WHERE dorm_id IN
|
||||
<foreach item="dormId" collection="array" open="(" separator="," close=")">
|
||||
#{dormId}
|
||||
</foreach>
|
||||
</delete>
|
||||
</update>
|
||||
</mapper>
|
||||
+426
@@ -0,0 +1,426 @@
|
||||
package com.ruoyi.train.service.impl;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.train.domain.TrainDormitoryInfo;
|
||||
import com.ruoyi.train.domain.dto.DormitoryCreateDTO;
|
||||
import com.ruoyi.train.domain.vo.DormOptionsVO;
|
||||
import com.ruoyi.train.domain.vo.DormitoryDetailVO;
|
||||
import com.ruoyi.train.domain.vo.DormitoryOccupantVO;
|
||||
import com.ruoyi.train.mapper.TrainDormitoryInfoMapper;
|
||||
|
||||
/**
|
||||
* ISSUE-01-05 宿舍房间基础资源单元测试 (DormitoryApiTest / ServiceTest)
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class DormitoryServiceTest
|
||||
{
|
||||
private TrainDormitoryInfoServiceImpl dormitoryService;
|
||||
private MockDormitoryMapper mockMapper;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
dormitoryService = new TrainDormitoryInfoServiceImpl();
|
||||
mockMapper = new MockDormitoryMapper();
|
||||
Field field = TrainDormitoryInfoServiceImpl.class.getDeclaredField("trainDormitoryInfoMapper");
|
||||
field.setAccessible(true);
|
||||
field.set(dormitoryService, mockMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* AC-01 & testAdd: 正常录入与同楼栋重复房号拦截抛 40006
|
||||
*/
|
||||
@Test
|
||||
public void testAddAndDuplicateInterception()
|
||||
{
|
||||
DormitoryCreateDTO dto = new DormitoryCreateDTO();
|
||||
dto.setDormBuilding("A栋");
|
||||
dto.setDormFloor("1");
|
||||
dto.setDormRoom("101");
|
||||
dto.setCapacity(4);
|
||||
dto.setGenderType(1); // 男
|
||||
dto.setLockDeviceSn("LOCK-SN-101");
|
||||
dto.setRemark("标准4人间");
|
||||
|
||||
// 1. 首次新增成功
|
||||
int result = dormitoryService.insertTrainDormitory(dto, "admin");
|
||||
assertEquals(1, result);
|
||||
|
||||
TrainDormitoryInfo saved = mockMapper.findByBuildingAndRoom("A栋", "101");
|
||||
assertNotNull(saved);
|
||||
assertEquals("A栋", saved.getDormBuilding());
|
||||
assertEquals("101", saved.getDormRoom());
|
||||
assertEquals(Integer.valueOf(4), saved.getCapacity());
|
||||
assertEquals(Integer.valueOf(1), saved.getGenderType());
|
||||
assertEquals("LOCK-SN-101", saved.getLockDeviceSn());
|
||||
assertEquals("admin", saved.getCreateBy());
|
||||
|
||||
// 2. 再次提交相同楼栋与房号 A栋-101,必须拦截并抛出错误码 40006 (DORM_ROOM_DUPLICATED)
|
||||
try
|
||||
{
|
||||
dormitoryService.insertTrainDormitory(dto, "admin");
|
||||
fail("同楼栋房号重复未拦截");
|
||||
}
|
||||
catch (ServiceException e)
|
||||
{
|
||||
assertEquals(Integer.valueOf(40006), e.getCode());
|
||||
assertTrue(e.getMessage().contains("同一楼栋下该宿舍房号已存在"));
|
||||
}
|
||||
|
||||
// 3. 提交不同楼栋同房号 B栋-101,允许新增成功
|
||||
DormitoryCreateDTO dtoB = new DormitoryCreateDTO();
|
||||
dtoB.setDormBuilding("B栋");
|
||||
dtoB.setDormFloor("1");
|
||||
dtoB.setDormRoom("101");
|
||||
dtoB.setCapacity(4);
|
||||
dtoB.setGenderType(2); // 女
|
||||
int resultB = dormitoryService.insertTrainDormitory(dtoB, "admin");
|
||||
assertEquals(1, resultB);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证高并发底层唯一约束冲突 DuplicateKeyException 是否安全转换为 40006 业务异常
|
||||
*/
|
||||
@Test
|
||||
public void testConcurrentInsertDuplicateKeyConversion()
|
||||
{
|
||||
DormitoryCreateDTO dto = new DormitoryCreateDTO();
|
||||
dto.setDormBuilding("D栋");
|
||||
dto.setDormFloor("1");
|
||||
dto.setDormRoom("101");
|
||||
dto.setCapacity(4);
|
||||
dto.setGenderType(1);
|
||||
|
||||
// 模拟高并发下绕过前置查重、底层数据库抛出唯一约束冲突异常
|
||||
mockMapper.setSimulateDuplicateKey(true);
|
||||
|
||||
try
|
||||
{
|
||||
dormitoryService.insertTrainDormitory(dto, "admin");
|
||||
fail("底层唯一键冲突未转换为 40006 业务异常");
|
||||
}
|
||||
catch (ServiceException e)
|
||||
{
|
||||
assertEquals(Integer.valueOf(40006), e.getCode());
|
||||
assertTrue(e.getMessage().contains("同一楼栋下该宿舍房号已存在"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 容量与性别边界校验
|
||||
*/
|
||||
@Test
|
||||
public void testCapacityAndGenderValidation()
|
||||
{
|
||||
DormitoryCreateDTO dto = new DormitoryCreateDTO();
|
||||
dto.setDormBuilding("A栋");
|
||||
dto.setDormFloor("2");
|
||||
dto.setDormRoom("201");
|
||||
dto.setGenderType(1);
|
||||
|
||||
// 容量为0
|
||||
dto.setCapacity(0);
|
||||
try
|
||||
{
|
||||
dormitoryService.insertTrainDormitory(dto, "admin");
|
||||
fail("容量为0未拦截");
|
||||
}
|
||||
catch (ServiceException e)
|
||||
{
|
||||
assertTrue(e.getMessage().contains("必须在1至8之间"));
|
||||
}
|
||||
|
||||
// 容量为9
|
||||
dto.setCapacity(9);
|
||||
try
|
||||
{
|
||||
dormitoryService.insertTrainDormitory(dto, "admin");
|
||||
fail("容量为9未拦截");
|
||||
}
|
||||
catch (ServiceException e)
|
||||
{
|
||||
assertTrue(e.getMessage().contains("必须在1至8之间"));
|
||||
}
|
||||
|
||||
// 性别非法
|
||||
dto.setCapacity(4);
|
||||
dto.setGenderType(5);
|
||||
try
|
||||
{
|
||||
dormitoryService.insertTrainDormitory(dto, "admin");
|
||||
fail("非法性别未拦截");
|
||||
}
|
||||
catch (ServiceException e)
|
||||
{
|
||||
assertTrue(e.getMessage().contains("适用性别取值不合法"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* testList: 分页返回房间、核定床位、已住与剩余
|
||||
*/
|
||||
@Test
|
||||
public void testList()
|
||||
{
|
||||
// 准备一间房间,capacity = 4,已有 3 人入住
|
||||
TrainDormitoryInfo dorm = new TrainDormitoryInfo();
|
||||
dorm.setDormId("dorm-001");
|
||||
dorm.setDormBuilding("A栋");
|
||||
dorm.setDormFloor("1");
|
||||
dorm.setDormRoom("102");
|
||||
dorm.setCapacity(4);
|
||||
dorm.setGenderType(1);
|
||||
dorm.setUsedCount(3);
|
||||
dorm.setRemainingBeds(1);
|
||||
dorm.setDormStatus(1);
|
||||
dorm.setDelFlag("0");
|
||||
mockMapper.save(dorm);
|
||||
|
||||
List<TrainDormitoryInfo> list = dormitoryService.selectTrainDormitoryInfoList(new TrainDormitoryInfo());
|
||||
assertNotNull(list);
|
||||
assertEquals(1, list.size());
|
||||
TrainDormitoryInfo item = list.get(0);
|
||||
assertEquals(Integer.valueOf(4), item.getCapacity());
|
||||
assertEquals(Integer.valueOf(3), item.getUsedCount());
|
||||
assertEquals(Integer.valueOf(1), item.getRemainingBeds());
|
||||
}
|
||||
|
||||
/**
|
||||
* testGet: 回显房间当前入住学员列表
|
||||
*/
|
||||
@Test
|
||||
public void testGet()
|
||||
{
|
||||
TrainDormitoryInfo dorm = new TrainDormitoryInfo();
|
||||
dorm.setDormId("dorm-detail-01");
|
||||
dorm.setDormBuilding("C栋");
|
||||
dorm.setDormFloor("3");
|
||||
dorm.setDormRoom("301");
|
||||
dorm.setCapacity(4);
|
||||
dorm.setGenderType(1);
|
||||
dorm.setUsedCount(1);
|
||||
dorm.setRemainingBeds(3);
|
||||
dorm.setDormStatus(1);
|
||||
dorm.setDelFlag("0");
|
||||
mockMapper.save(dorm);
|
||||
|
||||
DormitoryOccupantVO occupant = new DormitoryOccupantVO();
|
||||
occupant.setEnrollmentId(1001L);
|
||||
occupant.setStudentId("stu-01");
|
||||
occupant.setStudentName("张三");
|
||||
occupant.setGender("0"); // 男
|
||||
occupant.setOrgName("上海机务段");
|
||||
occupant.setBedNo("1号床");
|
||||
occupant.setAssignedTime(new Date());
|
||||
occupant.setAccommodationStatus(1);
|
||||
mockMapper.addOccupant("dorm-detail-01", occupant);
|
||||
|
||||
DormitoryDetailVO detail = dormitoryService.selectDormitoryDetailByDormId("dorm-detail-01");
|
||||
assertNotNull(detail);
|
||||
assertEquals("C栋", detail.getDormBuilding());
|
||||
assertEquals("301", detail.getDormRoom());
|
||||
assertEquals(1, detail.getOccupants().size());
|
||||
assertEquals("张三", detail.getOccupants().get(0).getStudentName());
|
||||
assertEquals("1号床", detail.getOccupants().get(0).getBedNo());
|
||||
}
|
||||
|
||||
/**
|
||||
* AC-02 & testOptions: 结构化返回楼栋-楼层-房间级联树
|
||||
*/
|
||||
@Test
|
||||
public void testOptions()
|
||||
{
|
||||
// 构造 A栋1层101, A栋1层102, A栋2层201, B栋1层101
|
||||
createDormEntity("d1", "A栋", "1", "101", 4, 1);
|
||||
createDormEntity("d2", "A栋", "1", "102", 4, 1);
|
||||
createDormEntity("d3", "A栋", "2", "201", 4, 1);
|
||||
createDormEntity("d4", "B栋", "1", "101", 2, 2);
|
||||
|
||||
List<DormOptionsVO> tree = dormitoryService.selectDormitoryTreeOptions();
|
||||
assertNotNull(tree);
|
||||
// 两个楼栋:A栋与B栋
|
||||
assertEquals(2, tree.size());
|
||||
|
||||
DormOptionsVO buildingA = tree.get(0);
|
||||
assertEquals("A栋", buildingA.getLabel());
|
||||
assertEquals("building", buildingA.getType());
|
||||
assertNotNull(buildingA.getChildren());
|
||||
assertEquals(2, buildingA.getChildren().size()); // 1层、2层
|
||||
|
||||
DormOptionsVO floor1 = buildingA.getChildren().get(0);
|
||||
assertEquals("1", floor1.getValue());
|
||||
assertEquals("1层", floor1.getLabel());
|
||||
assertEquals("floor", floor1.getType());
|
||||
assertNotNull(floor1.getChildren());
|
||||
assertEquals(2, floor1.getChildren().size()); // 101室、102室
|
||||
|
||||
DormOptionsVO room101 = floor1.getChildren().get(0);
|
||||
assertEquals("d1", room101.getValue());
|
||||
assertEquals("101室", room101.getLabel());
|
||||
assertEquals("room", room101.getType());
|
||||
assertNull(room101.getChildren()); // 叶子节点无 children
|
||||
}
|
||||
|
||||
private void createDormEntity(String id, String building, String floor, String room, int capacity, int gender)
|
||||
{
|
||||
TrainDormitoryInfo dorm = new TrainDormitoryInfo();
|
||||
dorm.setDormId(id);
|
||||
dorm.setDormBuilding(building);
|
||||
dorm.setDormFloor(floor);
|
||||
dorm.setDormRoom(room);
|
||||
dorm.setCapacity(capacity);
|
||||
dorm.setGenderType(gender);
|
||||
dorm.setUsedCount(0);
|
||||
dorm.setRemainingBeds(capacity);
|
||||
dorm.setDormStatus(1);
|
||||
dorm.setDelFlag("0");
|
||||
mockMapper.save(dorm);
|
||||
}
|
||||
|
||||
/**
|
||||
* 内存 Mock Mapper 实现
|
||||
*/
|
||||
private static class MockDormitoryMapper implements TrainDormitoryInfoMapper
|
||||
{
|
||||
private final Map<String, TrainDormitoryInfo> store = new java.util.LinkedHashMap<>();
|
||||
private final Map<String, List<DormitoryOccupantVO>> occupantsMap = new HashMap<>();
|
||||
private boolean simulateDuplicateKey = false;
|
||||
|
||||
public void setSimulateDuplicateKey(boolean simulateDuplicateKey)
|
||||
{
|
||||
this.simulateDuplicateKey = simulateDuplicateKey;
|
||||
}
|
||||
|
||||
public void save(TrainDormitoryInfo dorm)
|
||||
{
|
||||
store.put(dorm.getDormId(), dorm);
|
||||
}
|
||||
|
||||
public void addOccupant(String dormId, DormitoryOccupantVO occupant)
|
||||
{
|
||||
occupantsMap.computeIfAbsent(dormId, k -> new ArrayList<>()).add(occupant);
|
||||
}
|
||||
|
||||
public TrainDormitoryInfo findByBuildingAndRoom(String building, String room)
|
||||
{
|
||||
for (TrainDormitoryInfo d : store.values())
|
||||
{
|
||||
if ("0".equals(d.getDelFlag()) && building.equals(d.getDormBuilding()) && room.equals(d.getDormRoom()))
|
||||
{
|
||||
return d;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrainDormitoryInfo selectTrainDormitoryInfoByDormId(String dormId)
|
||||
{
|
||||
return store.get(dormId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TrainDormitoryInfo> selectTrainDormitoryInfoList(TrainDormitoryInfo query)
|
||||
{
|
||||
List<TrainDormitoryInfo> list = new ArrayList<>();
|
||||
for (TrainDormitoryInfo d : store.values())
|
||||
{
|
||||
if ("0".equals(d.getDelFlag()))
|
||||
{
|
||||
list.add(d);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrainDormitoryInfo checkBuildingRoomUnique(String dormBuilding, String dormRoom, String dormId)
|
||||
{
|
||||
for (TrainDormitoryInfo d : store.values())
|
||||
{
|
||||
if ("0".equals(d.getDelFlag()) && dormBuilding.equals(d.getDormBuilding()) && dormRoom.equals(d.getDormRoom()))
|
||||
{
|
||||
if (dormId == null || !dormId.equals(d.getDormId()))
|
||||
{
|
||||
return d;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DormitoryOccupantVO> selectOccupantsByDormId(String dormId)
|
||||
{
|
||||
return occupantsMap.getOrDefault(dormId, new ArrayList<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TrainDormitoryInfo> selectAllAvailableDormitories()
|
||||
{
|
||||
List<TrainDormitoryInfo> list = new ArrayList<>();
|
||||
for (TrainDormitoryInfo d : store.values())
|
||||
{
|
||||
if ("0".equals(d.getDelFlag()) && Integer.valueOf(1).equals(d.getDormStatus()))
|
||||
{
|
||||
list.add(d);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertTrainDormitoryInfo(TrainDormitoryInfo trainDormitoryInfo)
|
||||
{
|
||||
if (simulateDuplicateKey)
|
||||
{
|
||||
throw new org.springframework.dao.DuplicateKeyException("Mock unique constraint violation (dorm_building, dorm_room)");
|
||||
}
|
||||
store.put(trainDormitoryInfo.getDormId(), trainDormitoryInfo);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateTrainDormitoryInfo(TrainDormitoryInfo trainDormitoryInfo)
|
||||
{
|
||||
store.put(trainDormitoryInfo.getDormId(), trainDormitoryInfo);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteTrainDormitoryInfoByDormId(String dormId)
|
||||
{
|
||||
TrainDormitoryInfo d = store.get(dormId);
|
||||
if (d != null)
|
||||
{
|
||||
d.setDelFlag("2");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteTrainDormitoryInfoByDormIds(String[] dormIds)
|
||||
{
|
||||
for (String id : dormIds)
|
||||
{
|
||||
deleteTrainDormitoryInfoByDormId(id);
|
||||
}
|
||||
return dormIds.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +1,35 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询宿舍信息列表
|
||||
// 分页查询宿舍房间列表 (P0)
|
||||
export function listDormitory(query) {
|
||||
return request({
|
||||
url: '/train/dorminfo/list',
|
||||
url: '/train/dormitory/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询宿舍信息详细
|
||||
// 查询宿舍详细信息及当前入住人员列表 (P0)
|
||||
export function getDormitory(dormId) {
|
||||
return request({
|
||||
url: '/train/dorminfo/' + dormId,
|
||||
url: '/train/dormitory/' + dormId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增宿舍信息
|
||||
// 新增宿舍房间 (P0)
|
||||
export function addDormitory(data) {
|
||||
return request({
|
||||
url: '/train/dorminfo',
|
||||
url: '/train/dormitory',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改宿舍信息
|
||||
export function updateDormitory(data) {
|
||||
// 获取楼栋、楼层级联下拉数据 (P0)
|
||||
export function getDormitoryOptions() {
|
||||
return request({
|
||||
url: '/train/dorminfo',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除宿舍信息
|
||||
export function delDormitory(dormId) {
|
||||
return request({
|
||||
url: '/train/dorminfo/' + dormId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 导出宿舍信息
|
||||
export function exportDormitory(query) {
|
||||
return request({
|
||||
url: '/train/dorminfo/export',
|
||||
method: 'post',
|
||||
params: query
|
||||
url: '/train/dormitory/options',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form v-show="showSearch" ref="queryForm" :model="queryParams" size="small" :inline="true" label-width="72px">
|
||||
<el-form-item label="宿舍楼栋" prop="dormBuilding">
|
||||
<el-input
|
||||
v-model="queryParams.dormBuilding"
|
||||
@@ -9,6 +9,14 @@
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="所在楼层" prop="dormFloor">
|
||||
<el-input
|
||||
v-model="queryParams.dormFloor"
|
||||
placeholder="请输入楼层"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="房间号" prop="dormRoom">
|
||||
<el-input
|
||||
v-model="queryParams.dormRoom"
|
||||
@@ -17,7 +25,14 @@
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="dormStatus">
|
||||
<el-form-item label="适用性别" prop="genderType">
|
||||
<el-select v-model="queryParams.genderType" placeholder="适用性别" clearable>
|
||||
<el-option label="不限" :value="0" />
|
||||
<el-option label="男" :value="1" />
|
||||
<el-option label="女" :value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="宿舍状态" prop="dormStatus">
|
||||
<el-select v-model="queryParams.dormStatus" placeholder="宿舍状态" clearable>
|
||||
<el-option label="可用" :value="1" />
|
||||
<el-option label="禁用" :value="0" />
|
||||
@@ -32,66 +47,60 @@
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
v-hasPermi="['train:dormitory:edit']"
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="handleAdd"
|
||||
>新增</el-button>
|
||||
>新增房间</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
:disabled="single"
|
||||
@click="handleUpdate"
|
||||
>修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
<right-toolbar :show-search.sync="showSearch" @queryTable="getList" />
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="dormitoryList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="宿舍ID" align="center" prop="dormId" />
|
||||
<el-table v-loading="loading" :data="dormitoryList">
|
||||
<el-table-column label="宿舍楼栋" align="center" prop="dormBuilding" />
|
||||
<el-table-column label="楼层" align="center" prop="dormFloor" width="80" />
|
||||
<el-table-column label="房间号" align="center" prop="dormRoom" />
|
||||
<el-table-column label="总床位" align="center" prop="bedTotal" />
|
||||
<el-table-column label="已占用床位" align="center" prop="bedUsed" />
|
||||
<el-table-column label="核定床位" align="center" prop="capacity" width="90" />
|
||||
<el-table-column label="已住人数" align="center" width="90">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.usedCount > 0 ? 'warning' : 'info'">
|
||||
{{ scope.row.usedCount != null ? scope.row.usedCount : 0 }} 人
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="剩余床位" align="center" width="90">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.remainingBeds > 0 ? 'success' : 'danger'">
|
||||
{{ scope.row.remainingBeds != null ? scope.row.remainingBeds : scope.row.capacity }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="适用性别" align="center" width="90">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.genderType === 1">男</span>
|
||||
<span v-else-if="scope.row.genderType === 2">女</span>
|
||||
<span v-else>不限</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="智能锁序列号" align="center" prop="lockDeviceSn" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="状态" align="center" prop="dormStatus">
|
||||
<el-table-column label="状态" align="center" width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.dormStatus === 1 ? 'success' : 'danger'">
|
||||
{{ scope.row.dormStatus === 1 ? '可用' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建管理员" align="center" prop="createUser" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
v-hasPermi="['train:dormitory:list']"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
>删除</el-button>
|
||||
icon="el-icon-view"
|
||||
@click="handleDetail(scope.row)"
|
||||
>详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -104,32 +113,33 @@
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 添加或修改宿舍信息对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||
<!-- 添加宿舍信息对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="550px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="110px">
|
||||
<el-form-item label="宿舍楼栋" prop="dormBuilding">
|
||||
<el-input v-model="form.dormBuilding" placeholder="请输入宿舍楼栋" />
|
||||
<el-input v-model="form.dormBuilding" placeholder="请输入宿舍楼栋,例如:A栋" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所在楼层" prop="dormFloor">
|
||||
<el-input v-model="form.dormFloor" placeholder="请输入所在楼层,例如:3" />
|
||||
</el-form-item>
|
||||
<el-form-item label="房间号" prop="dormRoom">
|
||||
<el-input v-model="form.dormRoom" placeholder="请输入房间号" />
|
||||
<el-input v-model="form.dormRoom" placeholder="请输入房间号,例如:301" />
|
||||
</el-form-item>
|
||||
<el-form-item label="总床位数" prop="bedTotal">
|
||||
<el-input-number v-model="form.bedTotal" :min="1" :max="20" placeholder="总床位" />
|
||||
<el-form-item label="核定床位容量" prop="capacity">
|
||||
<el-input-number v-model="form.capacity" :min="1" :max="8" placeholder="床位容量" />
|
||||
</el-form-item>
|
||||
<el-form-item label="已占床位" prop="bedUsed">
|
||||
<el-input-number v-model="form.bedUsed" :min="0" :max="form.bedTotal || 20" placeholder="已占床位" />
|
||||
</el-form-item>
|
||||
<el-form-item label="智能锁SN" prop="lockDeviceSn">
|
||||
<el-input v-model="form.lockDeviceSn" placeholder="请输入人脸/智能锁序列号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="宿舍状态" prop="dormStatus">
|
||||
<el-radio-group v-model="form.dormStatus">
|
||||
<el-radio :label="1">可用</el-radio>
|
||||
<el-radio :label="0">禁用</el-radio>
|
||||
<el-form-item label="适用性别" prop="genderType">
|
||||
<el-radio-group v-model="form.genderType">
|
||||
<el-radio :label="0">不限</el-radio>
|
||||
<el-radio :label="1">男</el-radio>
|
||||
<el-radio :label="2">女</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="管理员账号" prop="createUser">
|
||||
<el-input v-model="form.createUser" placeholder="请输入创建管理员账号" />
|
||||
<el-form-item label="智能锁SN" prop="lockDeviceSn">
|
||||
<el-input v-model="form.lockDeviceSn" placeholder="请输入人脸/智能门锁序列号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注内容" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
@@ -137,42 +147,102 @@
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 宿舍详情与当前入住人员对话框 -->
|
||||
<el-dialog title="宿舍详情与入住人员" :visible.sync="detailOpen" width="750px" append-to-body>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="宿舍楼栋">{{ detailData.dormBuilding }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所在楼层">{{ detailData.dormFloor }}</el-descriptions-item>
|
||||
<el-descriptions-item label="房间号">{{ detailData.dormRoom }}</el-descriptions-item>
|
||||
<el-descriptions-item label="核定床位容量">{{ detailData.capacity }} 人</el-descriptions-item>
|
||||
<el-descriptions-item label="已住人数">{{ detailData.usedCount || 0 }} 人</el-descriptions-item>
|
||||
<el-descriptions-item label="剩余可用床位">{{ detailData.remainingBeds != null ? detailData.remainingBeds : detailData.capacity }}</el-descriptions-item>
|
||||
<el-descriptions-item label="适用性别">
|
||||
<span v-if="detailData.genderType === 1">男</span>
|
||||
<span v-else-if="detailData.genderType === 2">女</span>
|
||||
<span v-else>不限</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="智能门锁SN">{{ detailData.lockDeviceSn || '未绑定' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="detailData.dormStatus === 1 ? 'success' : 'danger'" size="mini">
|
||||
{{ detailData.dormStatus === 1 ? '可用' : '禁用' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注">{{ detailData.remark || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div style="margin-top: 18px; margin-bottom: 8px; font-weight: bold; font-size: 14px;">
|
||||
当前入住学员 ({{ (detailData.occupants && detailData.occupants.length) || 0 }} 人)
|
||||
</div>
|
||||
<el-table :data="detailData.occupants" size="small" border empty-text="当前暂无入住人员">
|
||||
<el-table-column label="学员姓名" prop="studentName" align="center" />
|
||||
<el-table-column label="性别" align="center" width="70">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.gender === '1' || scope.row.gender === '男' ? '男' : (scope.row.gender === '2' || scope.row.gender === '女' ? '女' : '未知') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="所属单位" prop="orgName" align="center" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="分配床位号" align="center" width="100">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.bedNo || '未指定' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分配入住时间" prop="assignedTime" align="center" width="160" />
|
||||
<el-table-column label="住宿状态" align="center" width="90">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.accommodationStatus === 1 ? 'success' : 'info'" size="mini">
|
||||
{{ scope.row.accommodationStatus === 1 ? '已入住' : '预分配' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="detailOpen = false">关 闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listDormitory, getDormitory, delDormitory, addDormitory, updateDormitory } from "@/api/train/dormitory"
|
||||
import { listDormitory, getDormitory, addDormitory } from '@/api/train/dormitory'
|
||||
|
||||
export default {
|
||||
name: "Dormitory",
|
||||
name: 'Dormitory',
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
ids: [],
|
||||
single: true,
|
||||
multiple: true,
|
||||
showSearch: true,
|
||||
total: 0,
|
||||
dormitoryList: [],
|
||||
title: "",
|
||||
title: '',
|
||||
open: false,
|
||||
detailOpen: false,
|
||||
detailData: {},
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
dormBuilding: null,
|
||||
dormFloor: null,
|
||||
dormRoom: null,
|
||||
genderType: null,
|
||||
dormStatus: null
|
||||
},
|
||||
form: {},
|
||||
rules: {
|
||||
dormBuilding: [
|
||||
{ required: true, message: "宿舍楼栋不能为空", trigger: "blur" }
|
||||
{ required: true, message: '宿舍楼栋不能为空', trigger: 'blur' }
|
||||
],
|
||||
dormFloor: [
|
||||
{ required: true, message: '所在楼层不能为空', trigger: 'blur' }
|
||||
],
|
||||
dormRoom: [
|
||||
{ required: true, message: "房间号不能为空", trigger: "blur" }
|
||||
{ required: true, message: '房间号不能为空', trigger: 'blur' }
|
||||
],
|
||||
bedTotal: [
|
||||
{ required: true, message: "总床位数不能为空", trigger: "blur" }
|
||||
capacity: [
|
||||
{ required: true, message: '核定床位容量不能为空', trigger: 'blur' }
|
||||
],
|
||||
genderType: [
|
||||
{ required: true, message: '适用性别不能为空', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -197,69 +267,45 @@ export default {
|
||||
this.form = {
|
||||
dormId: null,
|
||||
dormBuilding: null,
|
||||
dormFloor: null,
|
||||
dormRoom: null,
|
||||
bedTotal: 4,
|
||||
bedUsed: 0,
|
||||
capacity: 4,
|
||||
genderType: 0,
|
||||
lockDeviceSn: null,
|
||||
dormStatus: 1,
|
||||
createUser: "admin"
|
||||
remark: null
|
||||
}
|
||||
this.resetForm("form")
|
||||
this.resetForm('form')
|
||||
},
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
resetQuery() {
|
||||
this.resetForm("queryForm")
|
||||
this.resetForm('queryForm')
|
||||
this.handleQuery()
|
||||
},
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.dormId)
|
||||
this.single = selection.length !== 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
this.open = true
|
||||
this.title = "添加宿舍信息"
|
||||
this.title = '新增宿舍房间'
|
||||
},
|
||||
handleUpdate(row) {
|
||||
this.reset()
|
||||
const dormId = row.dormId || this.ids
|
||||
handleDetail(row) {
|
||||
const dormId = row.dormId
|
||||
getDormitory(dormId).then(response => {
|
||||
this.form = response.data
|
||||
this.open = true
|
||||
this.title = "修改宿舍信息"
|
||||
this.detailData = response.data || {}
|
||||
this.detailOpen = true
|
||||
})
|
||||
},
|
||||
submitForm() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
this.$refs['form'].validate(valid => {
|
||||
if (valid) {
|
||||
if (this.form.dormId != null) {
|
||||
updateDormitory(this.form).then(response => {
|
||||
this.$modal.msgSuccess("修改成功")
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
} else {
|
||||
addDormitory(this.form).then(response => {
|
||||
this.$modal.msgSuccess("新增成功")
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
}
|
||||
addDormitory(this.form).then(() => {
|
||||
this.$modal.msgSuccess('新增成功')
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
const dormIds = row.dormId || this.ids
|
||||
this.$modal.confirm('是否确认删除宿舍编号为"' + dormIds + '"的数据项?').then(() => {
|
||||
return delDormitory(dormIds)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("删除成功")
|
||||
}).catch(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
# ISSUE-01-05 宿舍房间与楼宇基础数据维护开发验收文档
|
||||
|
||||
## 1. 任务概述与环境基线
|
||||
|
||||
- **任务 ID:** `ISSUE-01-05`
|
||||
- **任务名称:** 宿舍房间与楼宇基础数据维护
|
||||
- **所属 Epic:** `EPIC-01 基础主数据必要集合`
|
||||
- **优先级:** P0
|
||||
- **工作区目录:** `/Users/wzb/Desktop/项目/RailwaySystem-worktrees/issue-01-05-dormitory`
|
||||
- **当前分支:** `feat/issue-01-05-dormitory`
|
||||
- **正确基线 Commit:** `4682df03c566776562b693ed5cb48fd98cda2472` (`integration/wave1a-foundation`)
|
||||
- 基线祖先校验状态:`PASS`(当前分支直接派生自该正式基线)
|
||||
- **API 契约基准:** `docs/api-v2/02_module_a_base_resource_api.md` 4.4 节、`docs/api-v2/06_dto_vo_catalog.md`、`docs/api-v2/10_permission_and_error_codes.md`
|
||||
- **数据库基线:** Database V2 (`train_dormitory_info`)
|
||||
- **Generator 冻结策略:** `YES (单表结构)`(依据 `docs/issues-v1/02_p0_issue_backlog.md` 与 `docs/issues-v1/05_agent_assignment.md`)
|
||||
- **本轮 Generator 实际执行:** `Generator actually executed = NO`(骨架已在此前生成,本次按 V2 契约与手写逻辑完成重构与对齐,未重新执行 Generator)
|
||||
- **核心业务目标:** 建立宿舍物理房间主数据台账(楼栋、楼层、房间号、核定容量、适用性别、门锁SN),提供同楼栋房号查重与防重、实时在住人数与剩余床位只读动态聚合、当前入住学员列表回显以及楼栋-楼层-房间三级结构化级联下拉选项。
|
||||
|
||||
---
|
||||
|
||||
## 2. HTTP Endpoint 最终收口与物理映射
|
||||
|
||||
严格依照冻结契约收口 Controller 公开接口,**非冻结接口全部从 Controller 移除,不再对外公开**:
|
||||
- 彻底移除历史 `/train/dorminfo` 路由及 Alias;
|
||||
- 彻底移除 `PUT /train/dormitory`、`DELETE /train/dormitory/{dormIds}`、`POST /train/dormitory/export` 等非 P0 接口。
|
||||
|
||||
### 2.1 最终公开物理 Endpoint 完整列表(共且仅有 4 个正式 P0)
|
||||
|
||||
| 优先级 | HTTP Method | URI 路径 | Controller 方法 | 权限字符 | 适用角色 | 功能说明与契约对齐 |
|
||||
|:---|:---|:---|:---|:---|:---|:---|
|
||||
| **P0** | `GET` | `/train/dormitory/list` | `list(TrainDormitoryInfo)` | `train:dormitory:list` | 管理员, 宿管 | 分页查询宿舍列表,附带 capacity、usedCount、remainingBeds |
|
||||
| **P0** | `GET` | `/train/dormitory/{dormId}` | `getInfo(@PathVariable)` | `train:dormitory:list` | 管理员, 宿管 | 获取指定房间详情及当前入住人员列表 (occupants) |
|
||||
| **P0** | `POST` | `/train/dormitory` | `add(@Validated @RequestBody)` | `train:dormitory:edit` | 管理员, 宿管 | 新增房间(前置查重与底层唯一约束冲突均转换为 40006) |
|
||||
| **P0** | `GET` | `/train/dormitory/options` | `options()` | `train:dormitory:list` | 管理员, 宿管 | 获取结构化的楼栋 -> 楼层 -> 房间层级树(分房弹窗必需) |
|
||||
|
||||
- **Contract-extra Endpoint 数量:** 0 个。
|
||||
|
||||
---
|
||||
|
||||
## 3. DTO 与 VO 设计规范
|
||||
|
||||
### 3.1 `DormitoryCreateDTO`
|
||||
- `dormBuilding` (String, `@NotBlank`, `@Size(max = 50)`): 宿舍楼栋
|
||||
- `dormFloor` (String, `@NotBlank`, `@Size(max = 20)`): 所在楼层
|
||||
- `dormRoom` (String, `@NotBlank`, `@Size(max = 30)`): 房间号
|
||||
- `capacity` (Integer, `@NotNull`, `@Min(1)`, `@Max(8)`): 核定床位容量 (1~8)
|
||||
- `genderType` (Integer, `@NotNull`): 适用性别 (0:不限, 1:男, 2:女)
|
||||
- `lockDeviceSn` (String, `@Size(max = 64)`): 智能门锁设备序列号 (可选)
|
||||
- `remark` (String, `@Size(max = 500)`): 备注 (可选)
|
||||
|
||||
### 3.2 `DormitoryItemVO`
|
||||
- `dormId` (String): 宿舍ID主键
|
||||
- `dormBuilding` (String): 宿舍楼栋
|
||||
- `dormFloor` (String): 所在楼层
|
||||
- `dormRoom` (String): 房间号
|
||||
- `capacity` (Integer): 核定床位容量
|
||||
- `usedCount` (Integer): 已住人数 (动态聚合自 `train_accommodation`)
|
||||
- `remainingBeds` (Integer): 剩余床位 (`capacity - usedCount`)
|
||||
- `genderType` (Integer): 适用性别 (0:不限, 1:男, 2:女)
|
||||
- `lockDeviceSn` (String): 智能门锁SN
|
||||
- `dormStatus` (Integer): 状态 (0:禁用, 1:可用)
|
||||
- `remark` (String): 备注
|
||||
- `createTime` (Date): 创建时间
|
||||
|
||||
### 3.3 `DormitoryOccupantVO`
|
||||
- `enrollmentId` (Long): 参训档案ID
|
||||
- `studentId` (String): 学员主档ID
|
||||
- `studentName` (String): 学员姓名
|
||||
- `gender` (String): 性别
|
||||
- `orgName` (String): 所属单位
|
||||
- `bedNo` (String): 分配床位号 (可选)
|
||||
- `assignedTime` (Date): 分配入住时间
|
||||
- `accommodationStatus` (Integer): 住宿状态 (0:预分配, 1:已入住)
|
||||
|
||||
### 3.4 `DormitoryDetailVO`
|
||||
- 包含房间基础信息与动态统计 (`dormId`, `dormBuilding`, `dormFloor`, `dormRoom`, `capacity`, `usedCount`, `remainingBeds`, `genderType`, `lockDeviceSn`, `dormStatus`, `remark`, `createTime`);
|
||||
- `occupants` (`List<DormitoryOccupantVO>`): 当前入住学员列表(空房时返回 `[]`,严禁返回 `null`)。
|
||||
|
||||
### 3.5 `DormOptionsVO`
|
||||
- 楼栋 -> 楼层 -> 房间三级树节点,包含 `value`, `label`, `type` ("building"/"floor"/"room"), `children`;
|
||||
- 叶子节点(房间)挂载 `capacity`, `usedCount`, `remainingBeds`, `genderType`, `lockDeviceSn` 元数据,方便前端分房弹窗直接消费。
|
||||
|
||||
---
|
||||
|
||||
## 4. 业务规则与边界约束
|
||||
|
||||
### 4.1 容量与性别规则
|
||||
- **容量范围:** 严格限定在 $1 \le \text{capacity} \le 8$ 之间,非正数或大于 8 抛出业务校验异常。
|
||||
- **适用性别:** 必须为合法枚举值(0: 不限, 1: 男, 2: 女)。
|
||||
|
||||
### 4.2 动态房态聚合口径
|
||||
- **依据:** `RuoYi-Vue/sql/train/v2/007_dicts.sql` 中 `train_accommodation_status` 字典定义:
|
||||
- `0`: 预分配 (报到前系统预先分配)
|
||||
- `1`: 已入住 (报到确认并已入住)
|
||||
- `2`: 已退宿 (培训结束已离宿)
|
||||
- **实际占用定义:** 仅处于“预分配”与“已入住”状态的记录占用床位,退宿后释放。
|
||||
- **实际 SQL 过滤条件:**
|
||||
```sql
|
||||
SELECT dorm_id, COUNT(1) AS used_count
|
||||
FROM train_accommodation
|
||||
WHERE accommodation_status IN (0, 1)
|
||||
GROUP BY dorm_id
|
||||
```
|
||||
`remaining_beds = (capacity - COALESCE(acc.used_count, 0))`。
|
||||
|
||||
### 4.3 UNIQUE 并发语义保证
|
||||
- **数据库约束:** `uq_dorm_building_room UNIQUE (dorm_building, dorm_room)`。
|
||||
- **并发保护机制:**
|
||||
1. 应用层前置查重:查询 `train_dormitory_info` 是否存在同楼栋同房号未删除记录,存在即抛出 `ServiceException("同一楼栋下该宿舍房号已存在", 40006)`;
|
||||
2. 底层并发冲突兜底:高并发场景下两请求同时绕过前置查重时,底层 INSERT 触发唯一约束冲突抛出 `DuplicateKeyException`,Service 层显式捕获并转换为 40006 业务异常,杜绝 500 裸异常泄漏。
|
||||
|
||||
### 4.4 业务边界铁律
|
||||
- **Dormitory 仅代表物理房间基础资源:** 负责房间基础资产的维护与只读聚合。
|
||||
- **无独立 DormBed 实体:** Phase 1 阶段严禁引入 `train_dorm_bed` 表;`bed_no` 仅为 `train_accommodation` 的可空业务字段。
|
||||
- **禁止 Accommodation 写操作:** 本 Issue 严禁分房、调房、退宿、门禁下发与同步、报到入住办理以及级联物理删除。
|
||||
|
||||
---
|
||||
|
||||
## 5. 验证结果与质量达标记录
|
||||
|
||||
1. **单元测试:**
|
||||
- 执行测试:`DormitoryServiceTest` 包含 6 个测试用例:
|
||||
- `testAddAndDuplicateInterception`: 前置防重与 40006 拦截
|
||||
- `testConcurrentInsertDuplicateKeyConversion`: 高并发 `DuplicateKeyException` 转换为 40006
|
||||
- `testCapacityAndGenderValidation`: 容量 (1~8) 与性别枚举合法性
|
||||
- `testList`: 动态聚合 `usedCount` 与 `remainingBeds`
|
||||
- `testGet`: 房间详情与 `occupants` 列表回显
|
||||
- `testOptions`: 楼栋-楼层-房间三级树结构组装
|
||||
- 运行结果:`Tests run: 6, Failures: 0, Errors: 0, Skipped: 0` 全部通过。
|
||||
|
||||
2. **模块依赖测试:**
|
||||
- 命令:`mvn test -pl ruoyi-train -am -f RuoYi-Vue/pom.xml`
|
||||
- 运行结果:`Tests run: 20, Failures: 0, Errors: 0, Skipped: 0` 全部通过。
|
||||
|
||||
3. **后端 Reactor 编译:**
|
||||
- 命令:`mvn compile -DskipTests -f RuoYi-Vue/pom.xml`
|
||||
- 运行结果:8 个子模块全部 `SUCCESS`。
|
||||
|
||||
4. **前端 ESLint 检查:**
|
||||
- 命令:`./node_modules/.bin/eslint --no-ignore src/api/train/dormitory.js src/views/train/dormitory/index.vue`
|
||||
- 运行结果:0 errors, 0 warnings。
|
||||
|
||||
5. **前端生产打包 (build:prod):**
|
||||
- 命令:`npm run build:prod`
|
||||
- 运行结果:`Build complete. The dist directory is ready to be deployed.` 构建成功。构建完成后已彻底清理临时产物与软链接。
|
||||
|
||||
6. **格式检查 (git diff --check):**
|
||||
- 命令:`git diff --check`
|
||||
- 运行结果:无任何 trailing whitespace,洁净通过。
|
||||
|
||||
7. **Commit 状态:**
|
||||
- 本地暂存区与工作区保留全部修改,**未执行 git commit**,等待评审。
|
||||
Reference in new issue
Block a user