修复住宿:床位重叠冲突返回标准化业务错误码

This commit is contained in:
wangzhibiao committed 2026-09-23 15:29:03 +08:00
1 parent fb5834bb1e
commit eb1ddfcaa8
4 files changed
+66 -4

No files matched your search

@@ -2,6 +2,7 @@ package com.ruoyi.web.controller.train;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.Method;
@@ -181,5 +182,30 @@ public class AccommodationAndAuthApiControllerContractTest
(java.util.Map<String, Object>) controller.getCapabilities().get("data");
assertEquals(Boolean.FALSE, missing.get("manualRevokeAvailable"));
}
/**
* EX-13: 床位时间窗口冲突必须返回标准业务错误码 (AccommodationErrorCode.BED_ALREADY_OCCUPIED = 40060),
* 严禁退化为未处理的系统 500 异常。
*/
@Test
public void testEx13_BedOverlapErrorResponseContract()
{
com.ruoyi.framework.web.exception.GlobalExceptionHandler handler =
new com.ruoyi.framework.web.exception.GlobalExceptionHandler();
com.ruoyi.common.exception.ServiceException ex = new com.ruoyi.common.exception.ServiceException(
"BED_ALREADY_OCCUPIED: 该床位在目标培训周期内已被占用 (宿舍: DORM_101,床位: 1号床)",
com.ruoyi.train.constant.AccommodationErrorCode.BED_ALREADY_OCCUPIED);
com.ruoyi.common.core.domain.AjaxResult result = handler.handleServiceException(ex, null);
assertNotNull(result);
assertEquals("HTTP 响应体 code 必须是标准业务错误码 40060",
Integer.valueOf(com.ruoyi.train.constant.AccommodationErrorCode.BED_ALREADY_OCCUPIED),
result.get(com.ruoyi.common.core.domain.AjaxResult.CODE_TAG));
assertTrue("响应体 code 严禁为系统 500 异常",
!Integer.valueOf(500).equals(result.get(com.ruoyi.common.core.domain.AjaxResult.CODE_TAG)));
assertTrue("响应体消息必须包含 BED_ALREADY_OCCUPIED 提示",
String.valueOf(result.get(com.ruoyi.common.core.domain.AjaxResult.MSG_TAG)).contains("BED_ALREADY_OCCUPIED"));
}
}
@@ -0,0 +1,27 @@
package com.ruoyi.train.constant;
/**
* 住宿管理业务错误码与常量规范 (API Contract V1.0.1 Module D)。
*/
public final class AccommodationErrorCode
{
/** 床位时间窗口已被占用冲突 */
public static final int BED_ALREADY_OCCUPIED = 40060;
public static final String BED_ALREADY_OCCUPIED_MESSAGE = "该床位在目标培训周期内已被占用";
/** 开班计划时间窗口不存在或无效 */
public static final int BED_OCCUPANCY_WINDOW_INVALID = 40061;
public static final String BED_OCCUPANCY_WINDOW_INVALID_MESSAGE = "目标开班计划时间窗口不存在或无效";
/** 床位存在无法解析时间窗口的预订记录 */
public static final int BED_OCCUPANCY_WINDOW_UNRESOLVED = 40062;
public static final String BED_OCCUPANCY_WINDOW_UNRESOLVED_MESSAGE = "目标床位存在无法解析培训时间窗口的有效预订记录";
/** 住宿记录不存在 */
public static final int ACCOMMODATION_NOT_FOUND = 40460;
public static final String ACCOMMODATION_NOT_FOUND_MESSAGE = "住宿记录不存在";
private AccommodationErrorCode()
{
}
}
@@ -7,6 +7,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.train.constant.AccommodationErrorCode;
import com.ruoyi.train.domain.TrainAccommodation;
import com.ruoyi.train.domain.TrainClassPlan;
import com.ruoyi.train.domain.TrainDormitoryInfo;
@@ -462,14 +463,16 @@ public class TrainAccommodationServiceImpl implements ITrainAccommodationService
TrainEnrollment enrollment = trainEnrollmentMapper.selectTrainEnrollmentById(enrollmentId);
if (enrollment == null || enrollment.getPlanId() == null || enrollment.getPlanId().trim().isEmpty())
{
throw new ServiceException("BED_OCCUPANCY_WINDOW_INVALID: 无法解析参训档案关联的开班计划 (enrollmentId: " + enrollmentId + ")");
throw new ServiceException("BED_OCCUPANCY_WINDOW_INVALID: 无法解析参训档案关联的开班计划 (enrollmentId: " + enrollmentId + ")",
AccommodationErrorCode.BED_OCCUPANCY_WINDOW_INVALID);
}
TrainClassPlan targetPlan = trainClassPlanMapper.selectTrainClassPlanByPlanId(enrollment.getPlanId());
if (targetPlan == null || targetPlan.getStartTime() == null || targetPlan.getEndTime() == null
|| !targetPlan.getEndTime().after(targetPlan.getStartTime()))
{
throw new ServiceException("BED_OCCUPANCY_WINDOW_INVALID: 目标开班计划时间窗口不存在或无效 (planId: " + enrollment.getPlanId() + ")");
throw new ServiceException("BED_OCCUPANCY_WINDOW_INVALID: 目标开班计划时间窗口不存在或无效 (planId: " + enrollment.getPlanId() + ")",
AccommodationErrorCode.BED_OCCUPANCY_WINDOW_INVALID);
}
// 2. 检查同一床位是否存在无法可靠解析时间窗口的已有 active accommodation (Fail Closed)
@@ -477,7 +480,8 @@ public class TrainAccommodationServiceImpl implements ITrainAccommodationService
if (unresolvedCount > 0)
{
throw new ServiceException("BED_OCCUPANCY_WINDOW_UNRESOLVED: 目标床位存在无法解析培训时间窗口的有效预订记录,请人工排查 (宿舍: "
+ dormId + ",床位: " + bedNo + ")");
+ dormId + ",床位: " + bedNo + ")",
AccommodationErrorCode.BED_OCCUPANCY_WINDOW_UNRESOLVED);
}
// 3. 检查同一床位在目标时间区间 [start_time, end_time) 内是否存在正长度交集冲突
@@ -485,7 +489,8 @@ public class TrainAccommodationServiceImpl implements ITrainAccommodationService
dormId, bedNo.trim(), targetPlan.getStartTime(), targetPlan.getEndTime(), excludeAccommodationId);
if (overlapCount > 0)
{
throw new ServiceException("BED_ALREADY_OCCUPIED: 该床位在目标培训周期内已被占用 (宿舍: " + dormId + ",床位: " + bedNo + ")");
throw new ServiceException("BED_ALREADY_OCCUPIED: 该床位在目标培训周期内已被占用 (宿舍: " + dormId + ",床位: " + bedNo + ")",
AccommodationErrorCode.BED_ALREADY_OCCUPIED);
}
}
}
@@ -23,6 +23,7 @@ import java.util.concurrent.locks.ReentrantLock;
import org.junit.Before;
import org.junit.Test;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.train.constant.AccommodationErrorCode;
import com.ruoyi.train.domain.TrainAccommodation;
import com.ruoyi.train.domain.TrainClassPlan;
import com.ruoyi.train.domain.TrainDormitoryInfo;
@@ -233,6 +234,7 @@ public class BedOccupancyModelCTest
}
catch (ServiceException e)
{
assertEquals(Integer.valueOf(AccommodationErrorCode.BED_ALREADY_OCCUPIED), e.getCode());
assertTrue(e.getMessage().contains("BED_ALREADY_OCCUPIED"));
}
}
@@ -263,6 +265,7 @@ public class BedOccupancyModelCTest
}
catch (ServiceException e)
{
assertEquals(Integer.valueOf(AccommodationErrorCode.BED_ALREADY_OCCUPIED), e.getCode());
assertTrue(e.getMessage().contains("BED_ALREADY_OCCUPIED"));
}
}
@@ -564,6 +567,7 @@ public class BedOccupancyModelCTest
}
catch (ServiceException e)
{
assertEquals(Integer.valueOf(AccommodationErrorCode.BED_ALREADY_OCCUPIED), e.getCode());
assertTrue(e.getMessage().contains("BED_ALREADY_OCCUPIED"));
}
}