Browse Source

执法端-计划

dev-V1.0.1
庄锐波 5 months ago
parent
commit
f5a55cc6dc
  1. 8
      script/sql/zfjd-refactoring-v1/013-计划任务表设计.sql
  2. 5
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/EfcodeCpPlan.java
  3. 5
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/EfcodeCpPlanBo.java
  4. 6
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/EfcodeCpPlanVo.java
  5. 11
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/CpPlanAutoMatchJob.java
  6. 9
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpPlanService.java
  7. 170
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpPlanServiceImpl.java
  8. 12
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpTaskServiceImpl.java
  9. 354
      zdxt-web-server/zdxt-admin-web-server/src/test/java/com/zdxt/rest/test/cp/AutoMatchReserveTest.java

8
script/sql/zfjd-refactoring-v1/013-计划任务表设计.sql

@ -25,6 +25,8 @@ CREATE TABLE `efcode_cp_plan`
`union_bureau_dept_names` varchar(500) DEFAULT NULL COMMENT '联合执法单位(多个逗号分隔)',
`reserve_status` tinyint NOT NULL DEFAULT 0 COMMENT '预约状态:0=草稿,1=待预约(未发起联合),2=预约中,3=预约完成,4=重新预约中,5=待预约(还有位置可以加入)',
`task_id` varchar(50) NULL DEFAULT '' COMMENT '任务id:空串表示还没生成任务',
`system_reserved` tinyint NOT NULL DEFAULT 0 COMMENT '是否已经经过系统推荐预约:1=是,0=否',
`manual_reserved` tinyint NOT NULL DEFAULT 0 COMMENT '是否已经经过手动预约:1=是,0=否',
`bureau_dept_id` varchar(50) NOT NULL COMMENT '执法单位ID',
@ -181,14 +183,14 @@ CREATE TABLE `efcode_cp_setting`
CREATE TABLE `efcode_cp_log`
(
`id` varchar(50) NOT NULL COMMENT '主键ID' PRIMARY KEY,
`id` varchar(50) NOT NULL COMMENT '主键ID' PRIMARY KEY,
`record_id` varchar(50) NULL COMMENT '关联记录',
`table_name` varchar(50) NULL COMMENT '关联表',
`log_type` varchar(50) NULL COMMENT '日志类型',
`op_slog` varchar(2000) NULL COMMENT '短日志',
`op_llog` text NULL COMMENT '长日志',
`op_user_id` varchar(50) NOT NULL COMMENT '操作人ID',
`op_user_name` varchar(50) NOT NULL COMMENT '操作人姓名',
`op_user_id` varchar(50) NOT NULL COMMENT '操作人ID',
`op_user_name` varchar(50) NOT NULL COMMENT '操作人姓名',
`remark` varchar(500) NULL COMMENT '备注',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_dept` varchar(64) DEFAULT NULL COMMENT '创建人部门',

5
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/EfcodeCpPlan.java

@ -114,6 +114,11 @@ public class EfcodeCpPlan extends BaseEntity {
*/
private Long reserveStatus;
/**
* 任务id:空串表示还没生成任务
*/
private String taskId;
/**
* 是否已经经过系统推荐预约:1=是,0=否
*/

5
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/EfcodeCpPlanBo.java

@ -128,6 +128,11 @@ public class EfcodeCpPlanBo extends BaseEntity {
@NotNull(message = "预约状态:0=草稿,1=待预约,2=预约中,3=预约完成不能为空", groups = { AddGroup.class, EditGroup.class })
private Long reserveStatus;
/**
* 任务id:空串表示还没生成任务
*/
private String taskId;
/**
* 是否已经经过系统推荐预约:1=是,0=否
*/

6
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/EfcodeCpPlanVo.java

@ -145,6 +145,12 @@ public class EfcodeCpPlanVo implements Serializable {
@ExcelProperty(value = "预约状态:0=草稿,1=待预约,2=预约中,3=预约完成")
private Long reserveStatus;
/**
* 任务id:空串表示还没生成任务
*/
@ExcelProperty(value = "任务ID")
private String taskId;
/**
* 是否已经经过系统推荐预约:1=是,0=否
*/

11
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/CpPlanAutoMatchJob.java

@ -20,9 +20,18 @@ public class CpPlanAutoMatchJob {
/**
* 任务执行入口(public,供 JobInvokeUtil 通过反射调用)
* <p>默认非强制模式:受 CP_SETTING_AUTO_MATCH_START 日期控制</p>
*/
public void execute() {
log.info("[CpPlanAutoMatchJob] 定时任务触发:计划自动匹配预约");
cpPlanService.autoMatchReserve();
cpPlanService.autoMatchReserve(false, true, true);
}
/**
* 强制执行入口(忽略日期校验,立即执行匹配)
*/
public void executeForce() {
log.info("[CpPlanAutoMatchJob] 强制触发:计划自动匹配预约");
cpPlanService.autoMatchReserve(true, true, true);
}
}

9
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpPlanService.java

@ -7,6 +7,8 @@ import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.zdxt.enforcementcode.domain.EfcodeCpReserve;
/**
* 行政执法检查计划-计划Service接口
@ -79,6 +81,11 @@ public interface IEfcodeCpPlanService {
/**
* 计划自动匹配预约(定时任务调用)
* <p>扫描待预约状态的计划,按规则自动匹配并生成预约记录</p>
*
* @param forceMatch 是否强制匹配(忽略 CP_SETTING_AUTO_MATCH_START 日期校验)
* @param updatePlan 是否更新计划记录(system_reserved、reserve_status)
* @param insertReserve 是否将预约记录插入数据库;为false时仅计算匹配结果不落库
* @return 按 planGroupId 分组的预约记录列表(key=planGroupId, value=该组预约记录)
*/
void autoMatchReserve();
Map<String, List<EfcodeCpReserve>> autoMatchReserve(boolean forceMatch, boolean updatePlan, boolean insertReserve);
}

170
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpPlanServiceImpl.java

@ -19,14 +19,24 @@ import com.zdxt.enforcementcode.mapper.EfcodeCpPlanMapper;
import com.zdxt.enforcementcode.mapper.EfcodeCpPlanUsersMapper;
import com.zdxt.enforcementcode.service.IEfcodeCpPlanService;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.Collection;
import java.util.stream.Collectors;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.satoken.utils.LoginHelper;
import com.zdxt.enforcementcode.domain.EfcodeCpReserve;
import com.zdxt.enforcementcode.domain.EfcodeCpSetting;
import com.zdxt.enforcementcode.mapper.EfcodeCpReserveMapper;
import com.zdxt.enforcementcode.mapper.EfcodeCpSettingMapper;
/**
* 行政执法检查计划-计划Service业务层处理
@ -41,6 +51,8 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService {
private final EfcodeCpPlanMapper baseMapper;
private final EfcodeCpPlanUsersMapper efcodeCpPlanUsersMapper;
private final EfcodeCpReserveMapper efcodeCpReserveMapper;
private final EfcodeCpSettingMapper efcodeCpSettingMapper;
/**
* 查询行政执法检查计划-计划
@ -348,11 +360,161 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService {
/**
* 计划自动匹配预约(定时任务调用)
* <p>扫描待预约状态的计划,按规则自动匹配并生成预约记录</p>
*
* @param forceMatch 是否强制匹配(忽略 CP_SETTING_AUTO_MATCH_START 日期校验)
* @param updatePlan 是否更新计划记录(system_reserved、reserve_status)
* @param insertReserve 是否将预约记录插入数据库;为false时仅计算匹配结果不落库
* @return 按 planGroupId 分组的预约记录列表
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void autoMatchReserve() {
log.info("[CpPlan] 开始执行计划自动匹配预约...");
// TODO 后续实现:扫描待预约计划 → 按企业+日期+时段匹配 → 自动生成预约记录
log.info("[CpPlan] 计划自动匹配预约执行完毕");
public Map<String, List<EfcodeCpReserve>> autoMatchReserve(boolean forceMatch, boolean updatePlan, boolean insertReserve) {
log.info("[CpPlan] 开始执行计划自动匹配预约, forceMatch={}, updatePlan={}, insertReserve={}", forceMatch, updatePlan, insertReserve);
Map<String, List<EfcodeCpReserve>> resultMap = new HashMap<>();
// 1. 日期校验:如果不是强制匹配,则判断当前天数是否等于 CP_SETTING_AUTO_MATCH_START
if (!forceMatch) {
String autoMatchStartDay = getSettingValue("CP_SETTING_AUTO_MATCH_START");
int today = LocalDate.now().getDayOfMonth();
if (StringUtils.isNotBlank(autoMatchStartDay)) {
int settingDay = Integer.parseInt(autoMatchStartDay.trim());
if (today != settingDay) {
log.info("[CpPlan] 当前日期{}号,不等于设定的自动匹配日期{}号,跳过执行", today, settingDay);
return resultMap;
}
}
}
// 2. 获取最大联合单位数量
int maxUnionUnit = 3; // 默认值
String maxUnionStr = getSettingValue("CP_MAX_UNION_UNIT");
if (StringUtils.isNotBlank(maxUnionStr)) {
maxUnionUnit = Integer.parseInt(maxUnionStr.trim());
}
// 3. 查询待匹配的计划:plan_date > 当前时间 且 system_reserved=0, reserve_status=1, is_public=1, is_union=1
Date now = new Date();
LambdaQueryWrapper<EfcodeCpPlan> lqw = Wrappers.lambdaQuery();
lqw.gt(EfcodeCpPlan::getPlanDate, now);
lqw.eq(EfcodeCpPlan::getSystemReserved, 0L);
lqw.eq(EfcodeCpPlan::getReserveStatus, 1L);
lqw.eq(EfcodeCpPlan::getIsPublic, 1L);
lqw.eq(EfcodeCpPlan::getIsUnion, 1L);
lqw.eq(EfcodeCpPlan::getIsDelete, 0L);
lqw.orderByAsc(EfcodeCpPlan::getPlanDate);
List<EfcodeCpPlan> pendingPlans = baseMapper.selectList(lqw);
if (pendingPlans.isEmpty()) {
log.info("[CpPlan] 没有找到待匹配的计划记录,执行结束");
return resultMap;
}
log.info("[CpPlan] 共查询到{}条待匹配计划", pendingPlans.size());
// 4. 已处理过的企业信用代码集合(同一轮中已匹配过的不再重复处理)
Set<String> processedEnterprises = new HashSet<>();
int matchedCount = 0;
for (EfcodeCpPlan plan : pendingPlans) {
String enterpriseNumber = plan.getEnterpriseNumber();
if (StringUtils.isBlank(enterpriseNumber)) {
continue;
}
// 跳过本轮已处理的企业
if (processedEnterprises.contains(enterpriseNumber)) {
continue;
}
// 5. 查找一周内同企业、不同执法单位的其他待匹配计划
LocalDate planLocalDate = plan.getPlanDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
Date weekStart = Date.from(planLocalDate.minusDays(3).atStartOfDay(ZoneId.systemDefault()).toInstant());
Date weekEnd = Date.from(planLocalDate.plusDays(4).atStartOfDay(ZoneId.systemDefault()).toInstant());
LambdaQueryWrapper<EfcodeCpPlan> weekLqw = Wrappers.lambdaQuery();
weekLqw.ge(EfcodeCpPlan::getPlanDate, weekStart);
weekLqw.le(EfcodeCpPlan::getPlanDate, weekEnd);
weekLqw.eq(EfcodeCpPlan::getEnterpriseNumber, enterpriseNumber);
weekLqw.eq(EfcodeCpPlan::getSystemReserved, 0L);
weekLqw.eq(EfcodeCpPlan::getReserveStatus, 1L);
weekLqw.eq(EfcodeCpPlan::getIsPublic, 1L);
weekLqw.eq(EfcodeCpPlan::getIsUnion, 1L);
weekLqw.eq(EfcodeCpPlan::getIsDelete, 0L);
weekLqw.orderByAsc(EfcodeCpPlan::getPlanDate);
List<EfcodeCpPlan> samePlans = baseMapper.selectList(weekLqw);
// 6. 按不同执法单位去重,取不同 bureau_dept_id 的计划
List<EfcodeCpPlan> distinctUnitPlans = new ArrayList<>();
Set<String> seenBureauDeptIds = new HashSet<>();
for (EfcodeCpPlan sp : samePlans) {
if (!seenBureauDeptIds.contains(sp.getBureauDeptId())) {
seenBureauDeptIds.add(sp.getBureauDeptId());
distinctUnitPlans.add(sp);
}
// 限制最大联合数量
if (distinctUnitPlans.size() >= maxUnionUnit) {
break;
}
}
// 7. 如果只有一个单位的计划,说明没有可联合的,跳过
if (distinctUnitPlans.size() <= 1) {
log.debug("[CpPlan] 企业[{}]一周内只有1个单位的计划,跳过", enterpriseNumber);
continue;
}
// 8. 生成预约记录 - 使用同一个 plan_group_id
String planGroupId = "Temp"+"_"+UUID.randomUUID().toString().replace("-", "");
Date reserveTime = new Date();
List<EfcodeCpReserve> groupReserves = new ArrayList<>();
for (int i = 0; i < distinctUnitPlans.size(); i++) {
EfcodeCpPlan matchedPlan = distinctUnitPlans.get(i);
EfcodeCpReserve reserve = new EfcodeCpReserve();
reserve.setPlanId(matchedPlan.getId());
reserve.setPlanGroupId(planGroupId);
reserve.setReserveTime(reserveTime);
reserve.setOpStatus(3L); // 待确认
reserve.setOpOrder((long) (i + 1));
reserve.setOpMessage("");
reserve.setOpSetDate(matchedPlan.getPlanDate());
reserve.setOpSetTime(matchedPlan.getPlanTime());
reserve.setResFrom(1L); // 系统匹配
if (insertReserve) {
efcodeCpReserveMapper.insert(reserve);
}
groupReserves.add(reserve);
// 9. 更新计划: system_reserved=1, reserve_status=2
if (updatePlan) {
EfcodeCpPlan planUpdate = new EfcodeCpPlan();
planUpdate.setId(matchedPlan.getId());
planUpdate.setSystemReserved(1L);
planUpdate.setReserveStatus(2L);
baseMapper.updateById(planUpdate);
}
}
resultMap.put(planGroupId, groupReserves);
// 标记该企业已处理
processedEnterprises.add(enterpriseNumber);
matchedCount++;
log.info("[CpPlan] 企业[{}]匹配成功,联合{}个单位,planGroupId={}",
enterpriseNumber, distinctUnitPlans.size(), planGroupId);
}
log.info("[CpPlan] 计划自动匹配预约执行完毕,共匹配{}组联合预约", matchedCount);
return resultMap;
}
/**
* 获取 efcode_cp_setting 配置值
*
* @param settingId 设置ID
* @return 设置值
*/
private String getSettingValue(String settingId) {
EfcodeCpSetting setting = efcodeCpSettingMapper.selectById(settingId);
return setting != null ? setting.getSettingValue() : null;
}
}

12
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpTaskServiceImpl.java

@ -116,6 +116,18 @@ public class EfcodeCpTaskServiceImpl implements IEfcodeCpTaskService {
//TODO 做一些数据校验,如唯一约束
}
/**
* 验证是否符合生成任务的条件
* <p>校验预约分组内所有记录是否满足生成执法任务的前置条件</p>
*
* @param planGroupId 计划分组ID
* @return true=符合生成条件, false=不符合
*/
public boolean checkCanGenerateTask(String planGroupId) {
// TODO 后续实现:校验分组内预约状态、时间窗口、人员配置等前置条件
return true;
}
/**
* 校验并批量删除行政执法检查计划-任务信息
*

354
zdxt-web-server/zdxt-admin-web-server/src/test/java/com/zdxt/rest/test/cp/AutoMatchReserveTest.java

@ -0,0 +1,354 @@
package com.zdxt.rest.test.cp;
import cn.dev33.satoken.context.mock.SaTokenContextMockUtil;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zdxt.enforcementcode.domain.EfcodeCpPlan;
import com.zdxt.enforcementcode.domain.EfcodeCpReserve;
import com.zdxt.enforcementcode.mapper.EfcodeCpPlanMapper;
import com.zdxt.enforcementcode.mapper.EfcodeCpReserveMapper;
import com.zdxt.enforcementcode.service.IEfcodeCpPlanService;
import org.dromara.ZdxtAdminApplication;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;
/**
* 计划自动匹配预约 autoMatchReserve 方法测试
* <p>
* 测试场景:
* 1. 强制模式下执行自动匹配(绕过日期校验)
* 2. 验证匹配结果:同企业不同单位的计划被联合
* 3. 验证计划状态更新:system_reserved=1, reserve_status=2
* 4. 验证预约记录生成:相同 plan_group_id 关联多条记录
* </p>
*/
@SpringBootTest(classes = ZdxtAdminApplication.class)
@AutoConfigureMockMvc
@ActiveProfiles({"dev", "dev-cust-zrb"})
@DisplayName("autoMatchReserve 自动匹配预约测试")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Tag("dev")
public class AutoMatchReserveTest {
@Autowired
private IEfcodeCpPlanService cpPlanService;
@Autowired
private EfcodeCpPlanMapper cpPlanMapper;
@Autowired
private EfcodeCpReserveMapper cpReserveMapper;
@BeforeEach
public void setUp() {
SaTokenContextMockUtil.setMockContext();
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
mockRequest.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(mockRequest));
}
@AfterEach
public void tearDown() {
try {
StpUtil.logout();
} catch (Exception ignored) {
}
RequestContextHolder.resetRequestAttributes();
SaTokenContextMockUtil.clearContext();
}
@DisplayName("0. 插入1000条适合匹配的计划数据")
@Test
@Order(0)
public void testInsert1000MatchablePlans() {
// 模拟登录(避免自动填充 createBy 报空)
StpUtil.login(1);
// 准备数据:200个企业 × 每企业5个不同执法单位 = 1000条计划
int enterpriseCount = 200;
int unitsPerEnterprise = 5;
Random random = new Random();
// 模拟执法单位ID池(确保不同单位)
String[] bureauDeptIds = {
"BUREAU_TEST_A", "BUREAU_TEST_B", "BUREAU_TEST_C",
"BUREAU_TEST_D", "BUREAU_TEST_E", "BUREAU_TEST_F",
"BUREAU_TEST_G", "BUREAU_TEST_H"
};
String[] bureauDeptNames = {
"测试执法局A", "测试执法局B", "测试执法局C",
"测试执法局D", "测试执法局E", "测试执法局F",
"测试执法局G", "测试执法局H"
};
// 基准日期:从明天开始
LocalDate baseDate = LocalDate.now().plusDays(1);
int insertedCount = 0;
for (int e = 0; e < enterpriseCount; e++) {
String enterpriseNumber = String.format("91440300TEST%06d", e);
String enterpriseName = "测试企业" + e + "号";
String enterpriseId = "ENT_TEST_" + e;
// 同一企业的计划日期在基准日期附3天内(确保一周内可匹配)
Date planDate = Date.from(
baseDate.plusDays(random.nextInt(3))
.atStartOfDay(ZoneId.systemDefault()).toInstant()
);
for (int u = 0; u < unitsPerEnterprise; u++) {
int bureauIdx = (e + u) % bureauDeptIds.length;
EfcodeCpPlan plan = new EfcodeCpPlan();
plan.setId(UUID.randomUUID().toString().replace("-", ""));
plan.setPlanType(random.nextBoolean() ? 1L : 2L);
plan.setPlanDate(planDate);
plan.setPlanTime((long) (random.nextInt(3) + 1)); // 1=上午,2=下午,3=其他
plan.setPlanActualDate(plan.getPlanDate());
plan.setPlanActualTime(plan.getPlanTime()); // 1=上午,2=下午,3=其他
plan.setEnforceContent("自动匹配测试-执法事项" + e + "-" + u);
plan.setEnterpriseName(enterpriseName);
plan.setEnterpriseNumber(enterpriseNumber);
plan.setEnterpriseId(enterpriseId);
plan.setEnforceUserNames("测试人员" + u);
plan.setEnforceUserIds(String.valueOf(1000 + u));
plan.setEnforceUserCodes("CODE" + u);
plan.setEnforceUserTels("1380000" + String.format("%04d", u));
plan.setIsPublic(1L);
plan.setIsUnion(1L);
plan.setReserveStatus(1L); // 待预约
plan.setSystemReserved(0L); // 未经系统匹配
plan.setManualReserved(0L);
plan.setBureauDeptId(bureauDeptIds[bureauIdx]);
plan.setBureauDeptName(bureauDeptNames[bureauIdx]);
plan.setOrganId(plan.getBureauDeptId());
plan.setOrganName(plan.getBureauDeptName());
plan.setDeptId(plan.getBureauDeptId());
plan.setDeptName(plan.getBureauDeptName());
plan.setIsDelete(0L);
plan.setCreateBy(1L);
plan.setCreateTime(new Date());
plan.setUpdateBy(1L);
plan.setUpdateTime(new Date());
cpPlanMapper.insert(plan);
insertedCount++;
}
}
System.out.println("========== 插入匹配测试数据完成 ==========");
System.out.println("共插入计划数: " + insertedCount);
System.out.println("涉及企业数: " + enterpriseCount);
System.out.println("每企业不同单位数: " + unitsPerEnterprise);
System.out.println("预期可匹配企业组数: " + enterpriseCount + " (每企业都有>=2个单位)");
System.out.println("===========================================");
Assertions.assertEquals(1000, insertedCount, "应插入恰好1000条计划");
}
@DisplayName("1. 查看当前待匹配计划数据概况")
@Test
@Order(1)
public void testCheckPendingPlans() {
// 查询符合自动匹配条件的计划
LambdaQueryWrapper<EfcodeCpPlan> lqw = Wrappers.lambdaQuery();
lqw.gt(EfcodeCpPlan::getPlanDate, new Date());
lqw.eq(EfcodeCpPlan::getSystemReserved, 0L);
lqw.eq(EfcodeCpPlan::getReserveStatus, 1L);
lqw.eq(EfcodeCpPlan::getIsPublic, 1L);
lqw.eq(EfcodeCpPlan::getIsUnion, 1L);
lqw.eq(EfcodeCpPlan::getIsDelete, 0L);
List<EfcodeCpPlan> pendingPlans = cpPlanMapper.selectList(lqw);
System.out.println("========== 待匹配计划数据概况 ==========");
System.out.println("符合条件的计划总数: " + pendingPlans.size());
// 按企业信用代码分组统计
Map<String, List<EfcodeCpPlan>> groupByEnterprise = pendingPlans.stream()
.filter(p -> p.getEnterpriseNumber() != null)
.collect(Collectors.groupingBy(EfcodeCpPlan::getEnterpriseNumber));
int potentialMatchCount = 0;
for (Map.Entry<String, List<EfcodeCpPlan>> entry : groupByEnterprise.entrySet()) {
// 统计不同执法单位数量
long distinctUnits = entry.getValue().stream()
.map(EfcodeCpPlan::getBureauDeptId)
.distinct()
.count();
if (distinctUnits >= 2) {
potentialMatchCount++;
System.out.println(" 企业[" + entry.getKey() + "] - 有" + distinctUnits + "个不同单位的计划,可匹配");
}
}
System.out.println("潜在可匹配的企业数: " + potentialMatchCount);
System.out.println("=======================================");
}
@DisplayName("2. 强制执行 autoMatchReserve 并验证结果")
@Test
@Order(2)
public void testAutoMatchReserveForceMode() {
// 执行前:统计当前系统匹配预约记录数
long beforeReserveCount = cpReserveMapper.selectCount(
Wrappers.<EfcodeCpReserve>lambdaQuery().eq(EfcodeCpReserve::getResFrom, 1L)
);
System.out.println("执行前系统匹配预约记录数: " + beforeReserveCount);
// 执行前:统计已被系统预约的计划数
long beforeMatchedPlanCount = cpPlanMapper.selectCount(
Wrappers.<EfcodeCpPlan>lambdaQuery()
.eq(EfcodeCpPlan::getSystemReserved, 1L)
.eq(EfcodeCpPlan::getIsDelete, 0L)
);
System.out.println("执行前已系统匹配计划数: " + beforeMatchedPlanCount);
// 执行自动匹配(强制模式,忽略日期校验)
System.out.println("\n========== 开始执行 autoMatchReserve(true) ==========");
cpPlanService.autoMatchReserve(true, true, true);
System.out.println("========== 执行完毕 ==========\n");
// 执行后:统计系统匹配预约记录数
long afterReserveCount = cpReserveMapper.selectCount(
Wrappers.<EfcodeCpReserve>lambdaQuery().eq(EfcodeCpReserve::getResFrom, 1L)
);
System.out.println("执行后系统匹配预约记录数: " + afterReserveCount);
System.out.println("本次新增预约记录数: " + (afterReserveCount - beforeReserveCount));
// 执行后:统计已被系统预约的计划数
long afterMatchedPlanCount = cpPlanMapper.selectCount(
Wrappers.<EfcodeCpPlan>lambdaQuery()
.eq(EfcodeCpPlan::getSystemReserved, 1L)
.eq(EfcodeCpPlan::getIsDelete, 0L)
);
System.out.println("执行后已系统匹配计划数: " + afterMatchedPlanCount);
System.out.println("本次新增匹配计划数: " + (afterMatchedPlanCount - beforeMatchedPlanCount));
// 验证:如果有符合条件的数据,则应产生匹配结果
if (afterReserveCount > beforeReserveCount) {
System.out.println("\n[验证通过] 自动匹配产生了新的预约记录");
} else {
System.out.println("\n[提示] 未产生新预约记录(可能没有符合条件的跨单位同企业计划)");
}
}
@DisplayName("3. 验证匹配后的 plan_group_id 分组一致性")
@Test
@Order(3)
public void testVerifyPlanGroupConsistency() {
// 查询所有系统匹配生成的预约记录
LambdaQueryWrapper<EfcodeCpReserve> lqw = Wrappers.lambdaQuery();
lqw.eq(EfcodeCpReserve::getResFrom, 1L);
lqw.eq(EfcodeCpReserve::getOpStatus, 3L); // 待确认
lqw.orderByDesc(EfcodeCpReserve::getCreateTime);
List<EfcodeCpReserve> reserves = cpReserveMapper.selectList(lqw);
if (reserves.isEmpty()) {
System.out.println("[跳过] 无系统匹配的预约记录,无法验证分组一致性");
return;
}
// 按 plan_group_id 分组
Map<String, List<EfcodeCpReserve>> groupMap = reserves.stream()
.collect(Collectors.groupingBy(EfcodeCpReserve::getPlanGroupId));
System.out.println("========== plan_group_id 分组验证 ==========");
System.out.println("共有 " + groupMap.size() + " 组联合预约");
int validGroups = 0;
for (Map.Entry<String, List<EfcodeCpReserve>> entry : groupMap.entrySet()) {
List<EfcodeCpReserve> group = entry.getValue();
// 每组至少2条记录(2个不同单位)
if (group.size() >= 2) {
validGroups++;
// 验证同组内关联的计划属于不同执法单位
Set<String> planIds = group.stream()
.map(EfcodeCpReserve::getPlanId)
.collect(Collectors.toSet());
List<EfcodeCpPlan> plans = cpPlanMapper.selectByIds(planIds);
Set<String> bureauDeptIds = plans.stream()
.map(EfcodeCpPlan::getBureauDeptId)
.collect(Collectors.toSet());
System.out.println(" groupId=" + entry.getKey()
+ " | 记录数=" + group.size()
+ " | 涉及单位数=" + bureauDeptIds.size());
// 断言:同组关联的计划应来自不同单位
Assertions.assertTrue(bureauDeptIds.size() >= 2,
"同一planGroupId下应关联至少2个不同执法单位的计划");
}
}
System.out.println("有效分组数(>=2条记录): " + validGroups);
System.out.println("=============================================");
}
@DisplayName("4. 验证匹配后计划状态已更新")
@Test
@Order(4)
public void testVerifyPlanStatusUpdated() {
// 查询已被系统匹配的计划
LambdaQueryWrapper<EfcodeCpPlan> lqw = Wrappers.lambdaQuery();
lqw.eq(EfcodeCpPlan::getSystemReserved, 1L);
lqw.eq(EfcodeCpPlan::getReserveStatus, 2L); // 预约中
lqw.eq(EfcodeCpPlan::getIsDelete, 0L);
List<EfcodeCpPlan> matchedPlans = cpPlanMapper.selectList(lqw);
System.out.println("========== 计划状态验证 ==========");
System.out.println("system_reserved=1 且 reserve_status=2 的计划数: " + matchedPlans.size());
if (!matchedPlans.isEmpty()) {
// 验证每条计划都满足状态要求
for (EfcodeCpPlan plan : matchedPlans) {
Assertions.assertEquals(1L, plan.getSystemReserved(),
"计划[" + plan.getId() + "]的system_reserved应为1");
Assertions.assertEquals(2L, plan.getReserveStatus(),
"计划[" + plan.getId() + "]的reserve_status应为2(预约中)");
}
System.out.println("[验证通过] 所有已匹配计划状态正确");
} else {
System.out.println("[提示] 无已匹配计划数据");
}
System.out.println("==================================");
}
@DisplayName("5. 再次执行 autoMatchReserve 验证幂等性(不重复匹配)")
@Test
@Order(5)
public void testAutoMatchReserveIdempotent() {
// 先统计当前预约记录数
long beforeCount = cpReserveMapper.selectCount(
Wrappers.<EfcodeCpReserve>lambdaQuery().eq(EfcodeCpReserve::getResFrom, 1L)
);
// 再次执行
System.out.println("========== 二次执行 autoMatchReserve(true) 验证幂等性 ==========");
cpPlanService.autoMatchReserve(true, true, true);
long afterCount = cpReserveMapper.selectCount(
Wrappers.<EfcodeCpReserve>lambdaQuery().eq(EfcodeCpReserve::getResFrom, 1L)
);
long newRecords = afterCount - beforeCount;
System.out.println("二次执行新增预约记录数: " + newRecords);
// 已匹配过的计划不应再次匹配(system_reserved已设为1)
// 但如果有新的符合条件的数据也可能产生新记录
System.out.println("[说明] 已匹配的计划(system_reserved=1)不会被重复匹配");
System.out.println("================================================================");
}
}
Loading…
Cancel
Save