Browse Source

执法端-计划

dev-V1.0.1
庄锐波 5 months ago
parent
commit
3549bb5f5e
  1. 51
      script/sql/zfjd-refactoring-v1/013-计划任务表设计.sql
  2. 105
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/EfcodeCpLogController.java
  3. 37
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnfCpReserveController.java
  4. 71
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/EfcodeCpLog.java
  5. 72
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/EfcodeCpLogBo.java
  6. 86
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/EfcodeCpLogVo.java
  7. 20
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/EfcodeCpReserveVo.java
  8. 15
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/EfcodeCpLogMapper.java
  9. 12
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/EfcodeCpReserveMapper.java
  10. 28
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/CpPlanAutoMatchJob.java
  11. 34
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/ENFDemoTimeJob.java
  12. 68
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpLogService.java
  13. 6
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpPlanService.java
  14. 16
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpReserveService.java
  15. 138
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpLogServiceImpl.java
  16. 11
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpPlanServiceImpl.java
  17. 74
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpReserveServiceImpl.java
  18. 6
      zdxt-modules/zdxt-enforcement-code/src/main/resources/mapper/enforcementcode/EfcodeCpLogMapper.xml
  19. 43
      zdxt-modules/zdxt-enforcement-code/src/main/resources/mapper/enforcementcode/EfcodeCpReserveMapper.xml
  20. 16
      zdxt-web-client/zdxt-efcode-enforcement-app/src/api/enforcement/reserve.js
  21. 9
      zdxt-web-client/zdxt-efcode-enforcement-app/src/router/enforcement-app/enf-main-router.js
  22. 4
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/enforcementMy.vue
  23. 102
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/reserve/cp_reserve_list.vue
  24. 457
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/reserve/cp_reserve_manual.vue
  25. 442
      zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/reserve/cp_reserve_system.vue
  26. 220
      zdxt-web-server/zdxt-admin-web-server/src/test/java/com/zdxt/rest/test/cp/AppEnfCpReserveTest.java

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

@ -17,14 +17,14 @@ CREATE TABLE `efcode_cp_plan`
`enforce_user_names` varchar(512) NOT NULL COMMENT '执法人员姓名(多个逗号分隔)',
`enforce_user_ids` varchar(512) NOT NULL COMMENT '执法人员姓名(多个逗号分隔)',
`enforce_user_tels` varchar(512) NULL COMMENT '执法人员电话号码(多个逗号分隔)',
`enforce_user_tels` varchar(512) NULL COMMENT '执法人员电话号码(多个逗号分隔)',
`enforce_user_codes` varchar(512) NOT NULL COMMENT '执法人员证件编号(多个逗号分隔)',
`is_public` tinyint NOT NULL DEFAULT 1 COMMENT '是否公开:1=是,0=否',
`is_union` tinyint NOT NULL DEFAULT 1 COMMENT '是否联合执法:1=是,0=否',
`union_bureau_dept_names` varchar(500) DEFAULT NULL COMMENT '联合执法单位(多个逗号分隔)',
`reserve_status` tinyint NOT NULL DEFAULT 0 COMMENT '预约状态:0=草稿,1=待预约,2=预约中,3=预约完成',
`reserve_status` tinyint NOT NULL DEFAULT 0 COMMENT '预约状态:0=草稿,1=待预约(未发起联合),2=预约中,3=预约完成,4=重新预约中,5=待预约(还有位置可以加入)',
`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',
@ -64,7 +64,7 @@ CREATE TABLE `efcode_cp_plan_users`
`plan_id` varchar(50) NOT NULL COMMENT '计划ID',
`user_id` varchar(50) NOT NULL COMMENT '用户ID',
`user_name` varchar(100) NOT NULL COMMENT '用户名称',
`user_tel` varchar(50) NULL COMMENT '执法人员电话号码',
`user_tel` varchar(50) NULL COMMENT '执法人员电话号码',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_dept` varchar(64) DEFAULT NULL COMMENT '创建人部门',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
@ -81,12 +81,12 @@ CREATE TABLE `efcode_cp_reserve`
`op_status` tinyint NOT NULL COMMENT '操作状态' DEFAULT 3 COMMENT '操作状态:1=同意,2=拒绝,3=待确认,4=超期取消',
`op_order` int NOT NULL COMMENT '联合时用户的操作顺序',
`op_time` datetime NULL COMMENT '联合时用户的操作时间',
`op_time` datetime NULL COMMENT '联合时用户的操作时间',
`op_message` varchar(500) NOT NULL COMMENT '联合时用户输入的操作消息',
`op_set_date` datetime NOT NULL COMMENT '联合时用户选择的操作时间',
`op_set_time` tinyint NOT NULL COMMENT '执行时段:1=上午,2=下午,3=其他',
`res_from` tinyint NOT NULL COMMENT '来自' COMMENT '来自:系统匹配=1,手动匹配=0',
`res_from` tinyint NOT NULL COMMENT '来自' COMMENT '来自:系统匹配=1,手动匹配=0',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_dept` varchar(64) DEFAULT NULL COMMENT '创建人部门',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
@ -117,7 +117,7 @@ CREATE TABLE `efcode_cp_notice`
`enterprise_number` varchar(32) DEFAULT NULL COMMENT '企业统一社会信用代码',
`contact_person` varchar(100) NOT NULL COMMENT '企业联系人',
`contact_phone` varchar(200) NULL COMMENT '企业联系电话',
`contact_phone` varchar(200) NULL COMMENT '企业联系电话',
`enforce_content` varchar(1000) NOT NULL COMMENT '执法事项',
`enforce_requirement` varchar(1000) NOT NULL COMMENT '执法要求',
@ -142,7 +142,7 @@ CREATE TABLE `efcode_cp_notice_users`
`notice_id` varchar(50) NOT NULL COMMENT '通知ID',
`user_id` varchar(50) NOT NULL COMMENT '用户ID',
`user_name` varchar(100) NOT NULL COMMENT '用户名称',
`user_tel` varchar(50) NULL COMMENT '执法人员电话号码',
`user_tel` varchar(50) NULL COMMENT '执法人员电话号码',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_dept` varchar(64) DEFAULT NULL COMMENT '创建人部门',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
@ -179,13 +179,46 @@ CREATE TABLE `efcode_cp_setting`
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) comment '行政执法检查计划-设置表' row_format = DYNAMIC;
CREATE TABLE `efcode_cp_log`
(
`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 '操作人姓名',
`remark` varchar(500) NULL COMMENT '备注',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_dept` varchar(64) DEFAULT NULL COMMENT '创建人部门',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) COMMENT '行政执法检查计划-日志表' ROW_FORMAT = DYNAMIC;
/*初始化数据*/
INSERT INTO `efcode_cp_setting` (`id`, `setting_name`, `setting_value`, `remark`, `create_by`, `create_dept`)
VALUES ('CP_SETTING_SUBMIT_DEADLINE', '下阶段计划提交最晚时间', '26', '每月几号前需提交下阶段计划<=', '0', '0'),
('CP_SETTING_AUTO_MATCH_START', '系统自动推荐匹配开始时间', '26', '系统自动推荐匹配的开始时间(号)>=', '0',
'0'),
('CP_SETTING_AUTO_MATCH_START', '系统自动推荐匹配开始时间', '27', '系统自动推荐匹配的开始时间(号)=', '0', '0'),
('CP_SETTING_RESERVE_CANCEL_DURATION', '预约自动取消时长', '7', '发起预约后,如果多长时间没有处理,则自动取消',
'0', '0'),
('CP_SETTING_MANUAL_MATCH_DAYS', '手工匹配距计划开始天数', '3', '手工匹配距离计划开始的天数>=', '0', '0'),
('CP_MAX_UNION_UNIT', '单次联合单位最大数量', '3', '单次联合单位最大数量<=3', '0', '0'),
('CP_MAX_INNER_UNIT_AC_PERSON_NUM', '单位内部单次计划最多参与人数', '3', '单位内部单次计划最多参与人数<=3', '0',
'0');
/*创建计划任务*/
INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by,
create_time, remark)
VALUES ('计划自动匹配预约',
'DEFAULT',
'cpPlanAutoMatchJob.execute()',
'0 0 2 * * ?',
'3',
'1',
'0',
'admin',
NOW(),
'每天凌晨2点扫描待预约计划并自动匹配生成预约记录');

105
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/EfcodeCpLogController.java

@ -0,0 +1,105 @@
package com.zdxt.enforcementcode.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import com.zdxt.enforcementcode.domain.vo.EfcodeCpLogVo;
import com.zdxt.enforcementcode.domain.bo.EfcodeCpLogBo;
import com.zdxt.enforcementcode.service.IEfcodeCpLogService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 行政执法检查计划-日志
*
* @author Lion Li
* @date 2026-05-25
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/enforcementcode/cpLog")
public class EfcodeCpLogController extends BaseController {
private final IEfcodeCpLogService efcodeCpLogService;
/**
* 查询行政执法检查计划-日志列表
*/
@SaCheckPermission("enforcementcode:cpLog:list")
@GetMapping("/list")
public TableDataInfo<EfcodeCpLogVo> list(EfcodeCpLogBo bo, PageQuery pageQuery) {
return efcodeCpLogService.queryPageList(bo, pageQuery);
}
/**
* 导出行政执法检查计划-日志列表
*/
@SaCheckPermission("enforcementcode:cpLog:export")
@Log(title = "行政执法检查计划-日志", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(EfcodeCpLogBo bo, HttpServletResponse response) {
List<EfcodeCpLogVo> list = efcodeCpLogService.queryList(bo);
ExcelUtil.exportExcel(list, "行政执法检查计划-日志", EfcodeCpLogVo.class, response);
}
/**
* 获取行政执法检查计划-日志详细信息
*
* @param id 主键
*/
@SaCheckPermission("enforcementcode:cpLog:query")
@GetMapping("/{id}")
public R<EfcodeCpLogVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable String id) {
return R.ok(efcodeCpLogService.queryById(id));
}
/**
* 新增行政执法检查计划-日志
*/
@SaCheckPermission("enforcementcode:cpLog:add")
@Log(title = "行政执法检查计划-日志", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody EfcodeCpLogBo bo) {
return toAjax(efcodeCpLogService.insertByBo(bo));
}
/**
* 修改行政执法检查计划-日志
*/
@SaCheckPermission("enforcementcode:cpLog:edit")
@Log(title = "行政执法检查计划-日志", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody EfcodeCpLogBo bo) {
return toAjax(efcodeCpLogService.updateByBo(bo));
}
/**
* 删除行政执法检查计划-日志
*
* @param ids 主键串
*/
@SaCheckPermission("enforcementcode:cpLog:remove")
@Log(title = "行政执法检查计划-日志", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable String[] ids) {
return toAjax(efcodeCpLogService.deleteWithValidByIds(List.of(ids), true));
}
}

37
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnfCpReserveController.java

@ -1,7 +1,14 @@
package com.zdxt.enforcementcode.controller.app.enforcement;
import com.zdxt.enforcementcode.controller.web.supervision.EfcodeCpReserveController;
import com.zdxt.enforcementcode.domain.bo.EfcodeCpReserveBo;
import com.zdxt.enforcementcode.domain.vo.EfcodeCpReserveVo;
import com.zdxt.enforcementcode.service.IEfcodeCpReserveService;
import org.dromara.common.core.domain.R;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -16,7 +23,37 @@ import org.springframework.web.bind.annotation.*;
@RequestMapping("/app-enf/cpReserve")
public class AppEnfCpReserveController extends EfcodeCpReserveController {
@Autowired
private IEfcodeCpReserveService cpReserveService;
public AppEnfCpReserveController(IEfcodeCpReserveService efcodeCpReserveService) {
super(efcodeCpReserveService);
this.cpReserveService = efcodeCpReserveService;
}
/**
* 查询当前用户相关的预约列表(仅查询当前用户计划所关联的预约记录)
*/
@Override
@GetMapping("/list")
public TableDataInfo<EfcodeCpReserveVo> list(EfcodeCpReserveBo bo, PageQuery pageQuery) {
bo.setCreateBy(LoginHelper.getUserId());
return cpReserveService.queryPageList(bo, pageQuery);
}
/**
* 同意预约
*/
@PutMapping("/approve/{id}")
public R<Void> approve(@PathVariable String id) {
return cpReserveService.approveReserve(id);
}
/**
* 拒绝预约
*/
@PutMapping("/reject/{id}")
public R<Void> reject(@PathVariable String id) {
return cpReserveService.rejectReserve(id);
}
}

71
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/EfcodeCpLog.java

@ -0,0 +1,71 @@
package com.zdxt.enforcementcode.domain;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 行政执法检查计划-日志对象 efcode_cp_log
*
* @author Lion Li
* @date 2026-05-25
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("efcode_cp_log")
public class EfcodeCpLog extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private String id;
/**
* 关联记录
*/
private String recordId;
/**
* 关联表
*/
private String tableName;
/**
* 日志类型
*/
private String logType;
/**
* 短日志
*/
private String opSlog;
/**
* 长日志
*/
private String opLlog;
/**
* 操作人ID
*/
private String opUserId;
/**
* 操作人姓名
*/
private String opUserName;
/**
* 备注
*/
private String remark;
}

72
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bo/EfcodeCpLogBo.java

@ -0,0 +1,72 @@
package com.zdxt.enforcementcode.domain.bo;
import com.zdxt.enforcementcode.domain.EfcodeCpLog;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
* 行政执法检查计划-日志业务对象 efcode_cp_log
*
* @author Lion Li
* @date 2026-05-25
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = EfcodeCpLog.class, reverseConvertGenerate = false)
public class EfcodeCpLogBo extends BaseEntity {
/**
* 主键ID
*/
@NotBlank(message = "主键ID不能为空", groups = { EditGroup.class })
private String id;
/**
* 关联记录
*/
private String recordId;
/**
* 关联表
*/
private String tableName;
/**
* 日志类型
*/
private String logType;
/**
* 短日志
*/
private String opSlog;
/**
* 长日志
*/
private String opLlog;
/**
* 操作人ID
*/
@NotBlank(message = "操作人ID不能为空", groups = { AddGroup.class, EditGroup.class })
private String opUserId;
/**
* 操作人姓名
*/
@NotBlank(message = "操作人姓名不能为空", groups = { AddGroup.class, EditGroup.class })
private String opUserName;
/**
* 备注
*/
private String remark;
}

86
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/EfcodeCpLogVo.java

@ -0,0 +1,86 @@
package com.zdxt.enforcementcode.domain.vo;
import com.zdxt.enforcementcode.domain.EfcodeCpLog;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 行政执法检查计划-日志视图对象 efcode_cp_log
*
* @author Lion Li
* @date 2026-05-25
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = EfcodeCpLog.class)
public class EfcodeCpLogVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private String id;
/**
* 关联记录
*/
@ExcelProperty(value = "关联记录")
private String recordId;
/**
* 关联表
*/
@ExcelProperty(value = "关联表")
private String tableName;
/**
* 日志类型
*/
@ExcelProperty(value = "日志类型")
private String logType;
/**
* 短日志
*/
@ExcelProperty(value = "短日志")
private String opSlog;
/**
* 长日志
*/
@ExcelProperty(value = "长日志")
private String opLlog;
/**
* 操作人ID
*/
@ExcelProperty(value = "操作人ID")
private String opUserId;
/**
* 操作人姓名
*/
@ExcelProperty(value = "操作人姓名")
private String opUserName;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}

20
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/EfcodeCpReserveVo.java

@ -96,5 +96,25 @@ public class EfcodeCpReserveVo implements Serializable {
@ExcelProperty(value = "来自:系统匹配=1,手动匹配=0")
private Long resFrom;
// ===== 计划关联字段(非数据库映射,由服务层填充) =====
/** 计划日期 */
private Date planDate;
/** 执行时段:1=上午,2=下午,3=其他 */
private Long planTime;
/** 企业名称 */
private String enterpriseName;
/** 执法事项 */
private String enforceContent;
/** 执法单位名称 */
private String bureauDeptName;
/** 执法人员姓名 */
private String enforceUserNames;
}

15
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/EfcodeCpLogMapper.java

@ -0,0 +1,15 @@
package com.zdxt.enforcementcode.mapper;
import com.zdxt.enforcementcode.domain.EfcodeCpLog;
import com.zdxt.enforcementcode.domain.vo.EfcodeCpLogVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 行政执法检查计划-日志Mapper接口
*
* @author Lion Li
* @date 2026-05-25
*/
public interface EfcodeCpLogMapper extends BaseMapperPlus<EfcodeCpLog, EfcodeCpLogVo> {
}

12
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/EfcodeCpReserveMapper.java

@ -1,7 +1,9 @@
package com.zdxt.enforcementcode.mapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zdxt.enforcementcode.domain.EfcodeCpReserve;
import com.zdxt.enforcementcode.domain.vo.EfcodeCpReserveVo;
import org.apache.ibatis.annotations.Param;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
@ -12,4 +14,14 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
*/
public interface EfcodeCpReserveMapper extends BaseMapperPlus<EfcodeCpReserve, EfcodeCpReserveVo> {
/**
* 分页查询预约处理列表(手动预约 / 系统匹配),关联计划表获取计划详情
*/
Page<EfcodeCpReserveVo> selectReservePageList(
@Param("page") Page<EfcodeCpReserveVo> page,
@Param("resFrom") Long resFrom,
@Param("opStatus") Long opStatus,
@Param("currentUserId") Long currentUserId
);
}

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

@ -0,0 +1,28 @@
package com.zdxt.enforcementcode.quartz.job;
import com.zdxt.enforcementcode.service.IEfcodeCpPlanService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 计划自动匹配预约 - 定时任务
* <p>定期扫描待预约状态的计划,按规则自动匹配并生成预约记录</p>
*
* @author zrb
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class CpPlanAutoMatchJob {
private final IEfcodeCpPlanService cpPlanService;
/**
* 任务执行入口(public,供 JobInvokeUtil 通过反射调用)
*/
public void execute() {
log.info("[CpPlanAutoMatchJob] 定时任务触发:计划自动匹配预约");
cpPlanService.autoMatchReserve();
}
}

34
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/ENFDemoTimeJob.java

@ -0,0 +1,34 @@
package com.zdxt.enforcementcode.quartz.job;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 演示定时任务 - 每秒打印当前时间
*
* @author ruoyi
*/
@Slf4j
@Component
public class ENFDemoTimeJob extends QuartzJobBean {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
protected void executeInternal(JobExecutionContext context) {
execute();
}
/**
* 任务执行入口(public,供 JobInvokeUtil 通过反射调用)
*/
public void execute() {
log.info("[Quartz Demo] 当前时间: {}", LocalDateTime.now().format(FORMATTER));
}
}

68
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpLogService.java

@ -0,0 +1,68 @@
package com.zdxt.enforcementcode.service;
import com.zdxt.enforcementcode.domain.vo.EfcodeCpLogVo;
import com.zdxt.enforcementcode.domain.bo.EfcodeCpLogBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 行政执法检查计划-日志Service接口
*
* @author Lion Li
* @date 2026-05-25
*/
public interface IEfcodeCpLogService {
/**
* 查询行政执法检查计划-日志
*
* @param id 主键
* @return 行政执法检查计划-日志
*/
EfcodeCpLogVo queryById(String id);
/**
* 分页查询行政执法检查计划-日志列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 行政执法检查计划-日志分页列表
*/
TableDataInfo<EfcodeCpLogVo> queryPageList(EfcodeCpLogBo bo, PageQuery pageQuery);
/**
* 查询符合条件的行政执法检查计划-日志列表
*
* @param bo 查询条件
* @return 行政执法检查计划-日志列表
*/
List<EfcodeCpLogVo> queryList(EfcodeCpLogBo bo);
/**
* 新增行政执法检查计划-日志
*
* @param bo 行政执法检查计划-日志
* @return 是否新增成功
*/
Boolean insertByBo(EfcodeCpLogBo bo);
/**
* 修改行政执法检查计划-日志
*
* @param bo 行政执法检查计划-日志
* @return 是否修改成功
*/
Boolean updateByBo(EfcodeCpLogBo bo);
/**
* 校验并批量删除行政执法检查计划-日志信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid);
}

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

@ -75,4 +75,10 @@ public interface IEfcodeCpPlanService {
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid);
/**
* 计划自动匹配预约(定时任务调用)
* <p>扫描待预约状态的计划,按规则自动匹配并生成预约记录</p>
*/
void autoMatchReserve();
}

16
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IEfcodeCpReserveService.java

@ -84,4 +84,20 @@ public interface IEfcodeCpReserveService {
* @return 操作结果
*/
R<Void> doReserve(String targetPlanId, String opMessage);
/**
* 同意预约
*
* @param reserveId 预约记录ID
* @return 操作结果
*/
R<Void> approveReserve(String reserveId);
/**
* 拒绝预约
*
* @param reserveId 预约记录ID
* @return 操作结果
*/
R<Void> rejectReserve(String reserveId);
}

138
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpLogServiceImpl.java

@ -0,0 +1,138 @@
package com.zdxt.enforcementcode.service.impl;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import com.zdxt.enforcementcode.domain.bo.EfcodeCpLogBo;
import com.zdxt.enforcementcode.domain.vo.EfcodeCpLogVo;
import com.zdxt.enforcementcode.domain.EfcodeCpLog;
import com.zdxt.enforcementcode.mapper.EfcodeCpLogMapper;
import com.zdxt.enforcementcode.service.IEfcodeCpLogService;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
* 行政执法检查计划-日志Service业务层处理
*
* @author Lion Li
* @date 2026-05-25
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class EfcodeCpLogServiceImpl implements IEfcodeCpLogService {
private final EfcodeCpLogMapper baseMapper;
/**
* 查询行政执法检查计划-日志
*
* @param id 主键
* @return 行政执法检查计划-日志
*/
@Override
public EfcodeCpLogVo queryById(String id){
return baseMapper.selectVoById(id);
}
/**
* 分页查询行政执法检查计划-日志列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 行政执法检查计划-日志分页列表
*/
@Override
public TableDataInfo<EfcodeCpLogVo> queryPageList(EfcodeCpLogBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<EfcodeCpLog> lqw = buildQueryWrapper(bo);
Page<EfcodeCpLogVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询符合条件的行政执法检查计划-日志列表
*
* @param bo 查询条件
* @return 行政执法检查计划-日志列表
*/
@Override
public List<EfcodeCpLogVo> queryList(EfcodeCpLogBo bo) {
LambdaQueryWrapper<EfcodeCpLog> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<EfcodeCpLog> buildQueryWrapper(EfcodeCpLogBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<EfcodeCpLog> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(EfcodeCpLog::getId);
lqw.eq(StringUtils.isNotBlank(bo.getRecordId()), EfcodeCpLog::getRecordId, bo.getRecordId());
lqw.like(StringUtils.isNotBlank(bo.getTableName()), EfcodeCpLog::getTableName, bo.getTableName());
lqw.eq(StringUtils.isNotBlank(bo.getLogType()), EfcodeCpLog::getLogType, bo.getLogType());
lqw.eq(StringUtils.isNotBlank(bo.getOpSlog()), EfcodeCpLog::getOpSlog, bo.getOpSlog());
lqw.eq(StringUtils.isNotBlank(bo.getOpLlog()), EfcodeCpLog::getOpLlog, bo.getOpLlog());
lqw.eq(StringUtils.isNotBlank(bo.getOpUserId()), EfcodeCpLog::getOpUserId, bo.getOpUserId());
lqw.like(StringUtils.isNotBlank(bo.getOpUserName()), EfcodeCpLog::getOpUserName, bo.getOpUserName());
return lqw;
}
/**
* 新增行政执法检查计划-日志
*
* @param bo 行政执法检查计划-日志
* @return 是否新增成功
*/
@Override
public Boolean insertByBo(EfcodeCpLogBo bo) {
EfcodeCpLog add = MapstructUtils.convert(bo, EfcodeCpLog.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改行政执法检查计划-日志
*
* @param bo 行政执法检查计划-日志
* @return 是否修改成功
*/
@Override
public Boolean updateByBo(EfcodeCpLogBo bo) {
EfcodeCpLog update = MapstructUtils.convert(bo, EfcodeCpLog.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(EfcodeCpLog entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 校验并批量删除行政执法检查计划-日志信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
public Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
}
}

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

@ -344,4 +344,15 @@ public class EfcodeCpPlanServiceImpl implements IEfcodeCpPlanService {
}
return baseMapper.deleteByIds(ids) > 0;
}
/**
* 计划自动匹配预约(定时任务调用)
* <p>扫描待预约状态的计划,按规则自动匹配并生成预约记录</p>
*/
@Override
public void autoMatchReserve() {
log.info("[CpPlan] 开始执行计划自动匹配预约...");
// TODO 后续实现:扫描待预约计划 → 按企业+日期+时段匹配 → 自动生成预约记录
log.info("[CpPlan] 计划自动匹配预约执行完毕");
}
}

74
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeCpReserveServiceImpl.java

@ -60,8 +60,12 @@ public class EfcodeCpReserveServiceImpl implements IEfcodeCpReserveService {
*/
@Override
public TableDataInfo<EfcodeCpReserveVo> queryPageList(EfcodeCpReserveBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<EfcodeCpReserve> lqw = buildQueryWrapper(bo);
Page<EfcodeCpReserveVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
Page<EfcodeCpReserveVo> result = baseMapper.selectReservePageList(
pageQuery.build(),
bo.getResFrom(),
bo.getOpStatus(),
bo.getCreateBy()
);
return TableDataInfo.build(result);
}
@ -80,7 +84,7 @@ public class EfcodeCpReserveServiceImpl implements IEfcodeCpReserveService {
private LambdaQueryWrapper<EfcodeCpReserve> buildQueryWrapper(EfcodeCpReserveBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<EfcodeCpReserve> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(EfcodeCpReserve::getId);
lqw.orderByDesc(EfcodeCpReserve::getReserveTime);
lqw.eq(StringUtils.isNotBlank(bo.getPlanId()), EfcodeCpReserve::getPlanId, bo.getPlanId());
lqw.eq(bo.getReserveTime() != null, EfcodeCpReserve::getReserveTime, bo.getReserveTime());
lqw.eq(bo.getOpStatus() != null, EfcodeCpReserve::getOpStatus, bo.getOpStatus());
@ -90,6 +94,21 @@ public class EfcodeCpReserveServiceImpl implements IEfcodeCpReserveService {
lqw.eq(bo.getOpSetDate() != null, EfcodeCpReserve::getOpSetDate, bo.getOpSetDate());
lqw.eq(bo.getOpSetTime() != null, EfcodeCpReserve::getOpSetTime, bo.getOpSetTime());
lqw.eq(bo.getResFrom() != null, EfcodeCpReserve::getResFrom, bo.getResFrom());
// 如果指定了createBy,则筛选当前用户计划关联的预约记录(planId IN 当前用户的计划)
if (bo.getCreateBy() != null) {
List<String> myPlanIds = cpPlanMapper.selectList(
Wrappers.<EfcodeCpPlan>lambdaQuery()
.eq(EfcodeCpPlan::getCreateBy, bo.getCreateBy())
.eq(EfcodeCpPlan::getIsDelete, 0L)
.select(EfcodeCpPlan::getId)
).stream().map(EfcodeCpPlan::getId).collect(Collectors.toList());
if (myPlanIds.isEmpty()) {
// 当前用户无计划,返回空结果
lqw.eq(EfcodeCpReserve::getId, "__IMPOSSIBLE__");
} else {
lqw.in(EfcodeCpReserve::getPlanId, myPlanIds);
}
}
return lqw;
}
@ -291,4 +310,53 @@ public class EfcodeCpReserveServiceImpl implements IEfcodeCpReserveService {
record.setResFrom(resFrom);
baseMapper.insert(record);
}
/**
* 同意预约
*/
@Override
public R<Void> approveReserve(String reserveId) {
EfcodeCpReserve reserve = baseMapper.selectById(reserveId);
if (reserve == null) {
return R.fail("预约记录不存在");
}
if (!Long.valueOf(3L).equals(reserve.getOpStatus())) {
return R.fail("该预约记录已被处理");
}
reserve.setOpStatus(1L); // 同意
reserve.setOpTime(new Date());
baseMapper.updateById(reserve);
afterOperate(reserve, "approve");
return R.ok();
}
/**
* 拒绝预约
*/
@Override
public R<Void> rejectReserve(String reserveId) {
EfcodeCpReserve reserve = baseMapper.selectById(reserveId);
if (reserve == null) {
return R.fail("预约记录不存在");
}
if (!Long.valueOf(3L).equals(reserve.getOpStatus())) {
return R.fail("该预约记录已被处理");
}
reserve.setOpStatus(2L); // 拒绝
reserve.setOpTime(new Date());
baseMapper.updateById(reserve);
afterOperate(reserve, "reject");
return R.ok();
}
/**
* 同意/拒绝操作后置处理
*
* @param reserve 预约记录
* @param action 操作类型: "approve" 或 "reject"
*/
private void afterOperate(EfcodeCpReserve reserve, String action) {
// TODO: 可在此处扩展后置逻辑,如通知、状态同步等
log.info("预约记录[{}]执行{}操作后置处理, planGroupId={}", reserve.getId(), action, reserve.getPlanGroupId());
}
}

6
zdxt-modules/zdxt-enforcement-code/src/main/resources/mapper/enforcementcode/EfcodeCpLogMapper.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zdxt.enforcementcode.mapper.EfcodeCpLogMapper">
</mapper>

43
zdxt-modules/zdxt-enforcement-code/src/main/resources/mapper/enforcementcode/EfcodeCpReserveMapper.xml

@ -3,4 +3,47 @@
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zdxt.enforcementcode.mapper.EfcodeCpReserveMapper">
<!-- 预约处理列表(手动预约 / 系统匹配):关联计划表获取计划详情 -->
<select id="selectReservePageList" resultType="com.zdxt.enforcementcode.domain.vo.EfcodeCpReserveVo">
SELECT
r.id,
r.plan_id,
r.plan_group_id,
r.reserve_time,
r.op_status,
r.op_order,
r.op_time,
r.op_message,
r.op_set_date,
r.op_set_time,
r.res_from,
p.plan_date,
p.plan_time,
p.enterprise_name,
p.enforce_content,
p.bureau_dept_name,
p.enforce_user_names
FROM efcode_cp_reserve r
LEFT JOIN efcode_cp_plan p ON r.plan_id = p.id
<where>
<!-- 按来源过滤:0=手动预约,1=系统匹配 -->
<if test="resFrom != null">
AND r.res_from = #{resFrom}
</if>
<!-- 按操作状态过滤 -->
<if test="opStatus != null">
AND r.op_status = #{opStatus}
</if>
<!-- 当前用户权限:仅查看关联自己计划的预约记录 -->
<if test="currentUserId != null">
AND r.plan_id IN (
SELECT id FROM efcode_cp_plan
WHERE create_by = #{currentUserId} AND is_delete = 0
)
</if>
</where>
ORDER BY r.reserve_time DESC
</select>
</mapper>

16
zdxt-web-client/zdxt-efcode-enforcement-app/src/api/enforcement/reserve.js

@ -0,0 +1,16 @@
import { _get, _post, _put } from "@/http/index";
// region 预约处理接口
const reserveBaseUrl = "/app-enf/cpReserve";
// 预约列表(分页,支持 resFrom 过滤:0=手动预约,1=系统匹配)
export const getReserveList = (params, success, error) => _get(reserveBaseUrl + "/list", params, success, error);
// 预约处理:同意
export const approveReserve = (id, success, error) => _put(reserveBaseUrl + "/approve/" + id, {}, success, error);
// 预约处理:拒绝
export const rejectReserve = (id, success, error) => _put(reserveBaseUrl + "/reject/" + id, {}, success, error);
// endregion

9
zdxt-web-client/zdxt-efcode-enforcement-app/src/router/enforcement-app/enf-main-router.js

@ -90,6 +90,15 @@ const routes = [
isKeepAlive: false,
},
},
// 执法端-预约处理列表
{
path: "/enforcement/reserve",
name: "cpReserveList",
component: () => import("@/views/enforcement/my/reserve/cp_reserve_list.vue"),
meta: {
isKeepAlive: false,
},
},
// 执法端-登记页面
{
path: "/enforcement/enforcementRegister",

4
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/enforcementMy.vue

@ -120,13 +120,15 @@ const onMenuClick = (item) => {
switch (item.key) {
case 'notice':
case 'task':
case 'appointment':
// 跳至消息中心待办 tab,并带类型过滤参数
router.push({
path: '/enforcement/message',
query: { tab: 'todo', type: item.key },
});
break;
case 'appointment':
router.push({ path: '/enforcement/reserve' });
break;
case 'plan':
router.push({ path: '/enforcement/plan' });
break;

102
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/reserve/cp_reserve_list.vue

@ -0,0 +1,102 @@
<template>
<div class="reserve-page app_content">
<!-- 顶部导航 -->
<van-nav-bar
title="预约处理"
left-arrow
@click-left="onBack"
/>
<!-- Tab 切换 -->
<van-tabs
v-model:active="activeTab"
title-active-color="#111"
title-inactive-color="#666"
color="#2f8bd3"
line-width="36px"
line-height="2px"
:border="false"
>
<van-tab title="手动预约" name="manual">
<cp-reserve-manual />
</van-tab>
<van-tab title="系统匹配" name="system">
<cp-reserve-system />
</van-tab>
</van-tabs>
</div>
</template>
<script setup name="cpReserveList">
import { useRouter } from 'vue-router';
import CpReserveManual from './cp_reserve_manual.vue';
import CpReserveSystem from './cp_reserve_system.vue';
const router = useRouter();
const activeTab = ref('manual');
const onBack = () => {
router.back();
};
</script>
<style lang="less" scoped>
.reserve-page {
display: flex;
flex-direction: column;
height: 100%;
background: #fff;
position: relative;
overflow: hidden;
}
:deep(.van-nav-bar) {
background: #347bc3;
flex-shrink: 0;
.van-nav-bar__title {
color: #fff;
font-size: 18px;
font-weight: 600;
}
.van-icon {
color: #fff;
}
}
:deep(.van-tabs) {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
}
:deep(.van-tabs__wrap) {
height: 54px;
background: #fff;
border-bottom: 1px solid #e4edf7;
flex-shrink: 0;
}
:deep(.van-tab) {
font-size: 16px;
}
:deep(.van-tab--active) {
font-weight: 600;
}
:deep(.van-tabs__content),
:deep(.van-tabs__track),
:deep(.van-tab__panel-wrapper),
:deep(.van-tab__panel) {
flex: 1;
min-height: 0;
height: 100%;
}
:deep(.van-tabs__content) {
overflow: hidden;
}
</style>

457
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/reserve/cp_reserve_manual.vue

@ -0,0 +1,457 @@
<template>
<div class="reserve-tab">
<!-- 状态过滤 -->
<div class="filter-bar">
<div
v-for="opt in filterOptions"
:key="opt.value"
class="filter-item"
:class="{ active: filterStatus === opt.value }"
@click="onFilterChange(opt.value)"
>
{{ opt.label }}
</div>
</div>
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<div class="reserve-list">
<section
v-for="item in reserveList"
:key="item.id"
class="reserve-card"
:class="{ 'approved-card': item.opStatus === 1, 'rejected-card': item.opStatus === 2, 'pending-card': item.opStatus === 3, 'selected-card': selectedId === item.id }"
@click="onSelectCard(item)"
>
<div class="card-header">
<div class="card-label"></div>
<div class="status" :class="getStatusClass(item.opStatus)">
{{ getStatusText(item.opStatus) }}
</div>
</div>
<div class="plan-title">{{ item.enforceContent || '执法事项' }}</div>
<div class="info-row">
<div class="label">企业名称:</div>
<div class="value">{{ item.enterpriseName || '-' }}</div>
</div>
<div class="info-row">
<div class="label">计划时间:</div>
<div class="value">{{ item.planDate || '-' }} {{ getPlanTimeText(item.planTime) }}</div>
</div>
<div class="info-row">
<div class="label">执法单位:</div>
<div class="value">{{ item.bureauDeptName || '-' }}</div>
</div>
<div class="info-row">
<div class="label">预约时间:</div>
<div class="value">{{ formatDate(item.reserveTime) }}</div>
</div>
<!-- 选中且待确认状态才显示操作按钮 -->
<div v-if="item.opStatus === 3 && selectedId === item.id" class="card-actions">
<div class="actions-left">
<div v-if="item.opMessage" class="action-item message-action" @click.stop="onShowMessage(item)">
<van-icon name="chat-o" />
<span>查看留言</span>
</div>
</div>
<div class="actions-right">
<div class="action-item reject-action" @click.stop="onReject(item)">
<van-icon name="cross" />
<span>拒绝</span>
</div>
<div class="action-item approve-action" @click.stop="onApprove(item)">
<van-icon name="success" />
<span>同意</span>
</div>
</div>
</div>
</section>
</div>
<!-- 空状态 -->
<van-empty v-if="!loading && reserveList.length === 0" description="暂无手动预约记录" />
</van-list>
</van-pull-refresh>
</div>
</template>
<script setup name="cpReserveManual">
import { showConfirmDialog, showSuccessToast, showDialog } from 'vant';
import { getReserveList, approveReserve, rejectReserve } from '@/api/enforcement/reserve';
const selectedId = ref(null);
const reserveList = ref([]);
const loading = ref(false);
const finished = ref(false);
const refreshing = ref(false);
const pageNum = ref(1);
const pageSize = 10;
const filterStatus = ref(null); // null=全部, 1=已同意, 2=已拒绝, 3=待确认
const filterOptions = [
{ label: '全部', value: null },
{ label: '待确认', value: 3 },
{ label: '已同意', value: 1 },
{ label: '已拒绝', value: 2 },
];
// 状态文本映射
const getStatusText = (status) => {
const map = { 1: '已同意', 2: '已拒绝', 3: '待确认', 4: '已取消' };
return map[status] || '未知';
};
// 状态样式
const getStatusClass = (status) => {
const map = { 1: 'green', 2: 'red', 3: 'blue', 4: 'gray' };
return map[status] || '';
};
// 时段文本
const getPlanTimeText = (time) => {
const map = { 1: '上午', 2: '下午', 3: '其他' };
return map[time] || '';
};
// 格式化日期
const formatDate = (dateStr) => {
if (!dateStr) return '-';
const d = new Date(dateStr);
if (isNaN(d.getTime())) return dateStr;
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
const h = String(d.getHours()).padStart(2, '0');
const min = String(d.getMinutes()).padStart(2, '0');
return `${y}-${m}-${day} ${h}:${min}`;
};
// 加载数据
const onLoad = () => {
fetchList();
};
// 选中卡片(再次点击取消选中)
const onSelectCard = (item) => {
if (item.opStatus !== 3) return; // 非待确认状态不可选
selectedId.value = selectedId.value === item.id ? null : item.id;
};
// 下拉刷新
const onRefresh = () => {
pageNum.value = 1;
finished.value = false;
reserveList.value = [];
fetchList();
};
// 过滤切换
const onFilterChange = (val) => {
filterStatus.value = val;
onRefresh();
};
// 请求列表
const fetchList = () => {
loading.value = true;
const params = { pageNum: pageNum.value, pageSize, resFrom: 0 };
if (filterStatus.value !== null) {
params.opStatus = filterStatus.value;
}
getReserveList(
params,
(res) => {
refreshing.value = false;
loading.value = false;
if (res.code === 200) {
const rows = res.rows || [];
if (pageNum.value === 1) {
reserveList.value = rows;
} else {
reserveList.value.push(...rows);
}
if (rows.length < pageSize) {
finished.value = true;
} else {
pageNum.value++;
}
} else {
finished.value = true;
}
},
() => {
refreshing.value = false;
loading.value = false;
finished.value = true;
}
);
};
// 查看留言
const onShowMessage = (item) => {
showDialog({
title: '预约留言',
message: item.opMessage,
confirmButtonText: '关闭',
});
};
// 同意
const onApprove = (item) => {
showConfirmDialog({
title: '提示',
message: '确定同意该预约申请吗?',
}).then(() => {
approveReserve(item.id, (res) => {
if (res.code === 200) {
showSuccessToast('已同意');
afterOperate(item, 'approve');
} else {
showDialog({ title: '提示', message: res.msg || '操作失败' });
}
});
}).catch(() => {});
};
// 拒绝
const onReject = (item) => {
showConfirmDialog({
title: '提示',
message: '确定拒绝该预约申请吗?',
}).then(() => {
rejectReserve(item.id, (res) => {
if (res.code === 200) {
showSuccessToast('已拒绝');
afterOperate(item, 'reject');
} else {
showDialog({ title: '提示', message: res.msg || '操作失败' });
}
});
}).catch(() => {});
};
// 操作后置处理
const afterOperate = (item, action) => {
// TODO: 可在此处扩展后置逻辑,如通知、跳转等
selectedId.value = null;
onRefresh();
};
</script>
<style lang="less" scoped>
.reserve-tab {
height: 100%;
overflow-y: auto;
padding: 12px;
padding-top: 12px;
box-sizing: border-box;
}
.filter-bar {
display: flex;
gap: 10px;
margin-bottom: 14px;
}
.filter-item {
padding: 5px 14px;
font-size: 13px;
font-weight: 500;
border-radius: 16px;
background: #f0f4f8;
color: #666;
transition: all 0.2s;
&.active {
background: #1f75c9;
color: #fff;
}
}
.reserve-list {
display: flex;
flex-direction: column;
gap: 14px;
}
.reserve-card {
background: #fff;
border-radius: 14px;
padding: 16px;
box-shadow: 0 4px 16px rgba(44, 116, 190, 0.12), 0 1px 4px rgba(0, 0, 0, 0.06);
overflow: hidden;
}
.approved-card {
border: 1.5px solid rgba(7, 193, 96, 0.4);
}
.rejected-card {
border: 1.5px solid rgba(238, 10, 36, 0.35);
}
.pending-card {
border: 1.5px solid rgba(35, 130, 207, 0.35);
}
.selected-card {
background: radial-gradient(circle at 78% 5%, rgba(65, 188, 255, 0.75) 0, rgba(65, 188, 255, 0.25) 20%, transparent 42%),
linear-gradient(135deg, #064ec6 0%, #0b63d9 48%, #268ee5 100%) !important;
border-color: transparent;
.card-header .card-label {
background: rgba(255, 255, 255, 0.15);
color: rgba(255, 255, 255, 0.85);
}
.card-header .status {
color: #f0b66c !important;
}
.plan-title {
color: #fff;
}
.info-row .label {
color: #fff;
}
.info-row .value {
color: rgba(255, 255, 255, 0.7);
&.msg {
color: rgba(255, 255, 255, 0.8);
}
}
.card-actions {
border-top-color: rgba(255, 255, 255, 0.18);
}
.message-action {
color: rgba(255, 255, 255, 0.85);
}
.approve-action {
color: #fff;
}
.reject-action {
color: rgba(255, 255, 255, 0.8);
}
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 14px;
}
.card-label {
font-size: 13px;
font-weight: 500;
color: #666;
background: #f0f6ff;
padding: 3px 10px;
border-radius: 10px;
}
.status {
font-size: 14px;
font-weight: 500;
&.blue { color: #1f75c9; }
&.green { color: #07c160; }
&.red { color: #ee0a24; }
&.gray { color: #999; }
}
.plan-title {
font-size: 16px;
font-weight: 700;
color: #111;
line-height: 1.5;
margin-bottom: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.info-row {
display: flex;
align-items: flex-start;
margin-bottom: 8px;
font-size: 14px;
line-height: 1.5;
}
.label {
width: 72px;
flex-shrink: 0;
color: #333;
font-weight: 500;
}
.value {
flex: 1;
color: #8a8a8a;
&.msg {
color: #666;
font-style: italic;
}
}
.card-actions {
margin-top: 14px;
padding-top: 12px;
border-top: 1px solid rgba(0, 0, 0, 0.06);
display: flex;
justify-content: space-between;
align-items: center;
}
.actions-left {
display: flex;
align-items: center;
}
.actions-right {
display: flex;
align-items: center;
gap: 24px;
}
.message-action {
color: #1f75c9;
}
.action-item {
display: flex;
align-items: center;
gap: 4px;
font-size: 14px;
cursor: pointer;
font-weight: 500;
}
.approve-action {
color: #07c160;
}
.reject-action {
color: #ee0a24;
}
</style>

442
zdxt-web-client/zdxt-efcode-enforcement-app/src/views/enforcement/my/reserve/cp_reserve_system.vue

@ -0,0 +1,442 @@
<template>
<div class="reserve-tab">
<!-- 状态过滤 -->
<div class="filter-bar">
<div
v-for="opt in filterOptions"
:key="opt.value"
class="filter-item"
:class="{ active: filterStatus === opt.value }"
@click="onFilterChange(opt.value)"
>
{{ opt.label }}
</div>
</div>
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<div class="reserve-list">
<section
v-for="item in reserveList"
:key="item.id"
class="reserve-card"
:class="{ 'approved-card': item.opStatus === 1, 'rejected-card': item.opStatus === 2, 'pending-card': item.opStatus === 3, 'selected-card': selectedId === item.id }"
@click="onSelectCard(item)"
>
<div class="card-header">
<div class="status" :class="getStatusClass(item.opStatus)">
{{ getStatusText(item.opStatus) }}
</div>
</div>
<div class="plan-title">{{ item.enforceContent || '执法事项' }}</div>
<div class="info-row">
<div class="label">企业名称:</div>
<div class="value">{{ item.enterpriseName || '-' }}</div>
</div>
<div class="info-row">
<div class="label">计划时间:</div>
<div class="value">{{ item.planDate || '-' }} {{ getPlanTimeText(item.planTime) }}</div>
</div>
<div class="info-row">
<div class="label">执法单位:</div>
<div class="value">{{ item.bureauDeptName || '-' }}</div>
</div>
<div class="info-row">
<div class="label">匹配时间:</div>
<div class="value">{{ formatDate(item.reserveTime) }}</div>
</div>
<!-- 选中且待确认状态才显示操作按钮 -->
<div v-if="item.opStatus === 3 && selectedId === item.id" class="card-actions">
<div class="actions-left">
<div v-if="item.opMessage" class="action-item message-action" @click.stop="onShowMessage(item)">
<van-icon name="chat-o" />
<span>查看备注</span>
</div>
</div>
<div class="actions-right">
<div class="action-item reject-action" @click.stop="onReject(item)">
<van-icon name="cross" />
<span>拒绝</span>
</div>
<div class="action-item approve-action" @click.stop="onApprove(item)">
<van-icon name="success" />
<span>同意</span>
</div>
</div>
</div>
</section>
</div>
<!-- 空状态 -->
<van-empty v-if="!loading && reserveList.length === 0" description="暂无系统匹配记录" />
</van-list>
</van-pull-refresh>
</div>
</template>
<script setup name="cpReserveSystem">
import { showConfirmDialog, showSuccessToast, showDialog } from 'vant';
import { getReserveList, approveReserve, rejectReserve } from '@/api/enforcement/reserve';
const selectedId = ref(null);
const reserveList = ref([]);
const loading = ref(false);
const finished = ref(false);
const refreshing = ref(false);
const pageNum = ref(1);
const pageSize = 10;
const filterStatus = ref(null); // null=全部, 1=已同意, 2=已拒绝, 3=待确认
const filterOptions = [
{ label: '全部', value: null },
{ label: '待确认', value: 3 },
{ label: '已同意', value: 1 },
{ label: '已拒绝', value: 2 },
];
// 状态文本映射
const getStatusText = (status) => {
const map = { 1: '已同意', 2: '已拒绝', 3: '待确认', 4: '已取消' };
return map[status] || '未知';
};
// 状态样式
const getStatusClass = (status) => {
const map = { 1: 'green', 2: 'red', 3: 'blue', 4: 'gray' };
return map[status] || '';
};
// 时段文本
const getPlanTimeText = (time) => {
const map = { 1: '上午', 2: '下午', 3: '其他' };
return map[time] || '';
};
// 格式化日期
const formatDate = (dateStr) => {
if (!dateStr) return '-';
const d = new Date(dateStr);
if (isNaN(d.getTime())) return dateStr;
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
const h = String(d.getHours()).padStart(2, '0');
const min = String(d.getMinutes()).padStart(2, '0');
return `${y}-${m}-${day} ${h}:${min}`;
};
// 加载数据
const onLoad = () => {
fetchList();
};
// 选中卡片(再次点击取消选中)
const onSelectCard = (item) => {
if (item.opStatus !== 3) return; // 非待确认状态不可选
selectedId.value = selectedId.value === item.id ? null : item.id;
};
// 下拉刷新
const onRefresh = () => {
pageNum.value = 1;
finished.value = false;
reserveList.value = [];
fetchList();
};
// 过滤切换
const onFilterChange = (val) => {
filterStatus.value = val;
onRefresh();
};
// 请求列表
const fetchList = () => {
loading.value = true;
const params = { pageNum: pageNum.value, pageSize, resFrom: 1 };
if (filterStatus.value !== null) {
params.opStatus = filterStatus.value;
}
getReserveList(
params,
(res) => {
refreshing.value = false;
loading.value = false;
if (res.code === 200) {
const rows = res.rows || [];
if (pageNum.value === 1) {
reserveList.value = rows;
} else {
reserveList.value.push(...rows);
}
if (rows.length < pageSize) {
finished.value = true;
} else {
pageNum.value++;
}
} else {
finished.value = true;
}
},
() => {
refreshing.value = false;
loading.value = false;
finished.value = true;
}
);
};
// 查看备注
const onShowMessage = (item) => {
showDialog({
title: '备注信息',
message: item.opMessage,
confirmButtonText: '关闭',
});
};
// 同意
const onApprove = (item) => {
showConfirmDialog({
title: '提示',
message: '确定同意该系统匹配吗?',
}).then(() => {
approveReserve(item.id, (res) => {
if (res.code === 200) {
showSuccessToast('已同意');
afterOperate(item, 'approve');
} else {
showDialog({ title: '提示', message: res.msg || '操作失败' });
}
});
}).catch(() => {});
};
// 拒绝
const onReject = (item) => {
showConfirmDialog({
title: '提示',
message: '确定拒绝该系统匹配吗?',
}).then(() => {
rejectReserve(item.id, (res) => {
if (res.code === 200) {
showSuccessToast('已拒绝');
afterOperate(item, 'reject');
} else {
showDialog({ title: '提示', message: res.msg || '操作失败' });
}
});
}).catch(() => {});
};
// 操作后置处理
const afterOperate = (item, action) => {
// TODO: 可在此处扩展后置逻辑,如通知、跳转等
selectedId.value = null;
onRefresh();
};
</script>
<style lang="less" scoped>
.reserve-tab {
height: 100%;
overflow-y: auto;
padding: 12px;
padding-top: 12px;
box-sizing: border-box;
}
.filter-bar {
display: flex;
gap: 10px;
margin-bottom: 14px;
}
.filter-item {
padding: 5px 14px;
font-size: 13px;
font-weight: 500;
border-radius: 16px;
background: #f0f4f8;
color: #666;
transition: all 0.2s;
&.active {
background: #1f75c9;
color: #fff;
}
}
.reserve-list {
display: flex;
flex-direction: column;
gap: 14px;
}
.reserve-card {
background: #fff;
border-radius: 14px;
padding: 16px;
box-shadow: 0 4px 16px rgba(44, 116, 190, 0.12), 0 1px 4px rgba(0, 0, 0, 0.06);
overflow: hidden;
}
.approved-card {
border: 1.5px solid rgba(7, 193, 96, 0.4);
}
.rejected-card {
border: 1.5px solid rgba(238, 10, 36, 0.35);
}
.pending-card {
border: 1.5px solid rgba(35, 130, 207, 0.35);
}
.selected-card {
background: radial-gradient(circle at 78% 5%, rgba(65, 188, 255, 0.75) 0, rgba(65, 188, 255, 0.25) 20%, transparent 42%),
linear-gradient(135deg, #064ec6 0%, #0b63d9 48%, #268ee5 100%) !important;
border-color: transparent;
.card-header .status {
color: #f0b66c !important;
}
.plan-title {
color: #fff;
}
.info-row .label {
color: #fff;
}
.info-row .value {
color: rgba(255, 255, 255, 0.7);
&.msg {
color: rgba(255, 255, 255, 0.8);
}
}
.card-actions {
border-top-color: rgba(255, 255, 255, 0.18);
}
.message-action {
color: rgba(255, 255, 255, 0.85);
}
.approve-action {
color: #fff;
}
.reject-action {
color: rgba(255, 255, 255, 0.8);
}
}
.card-header {
display: flex;
justify-content: flex-end;
align-items: center;
margin-bottom: 14px;
}
.status {
font-size: 14px;
font-weight: 500;
&.blue { color: #1f75c9; }
&.green { color: #07c160; }
&.red { color: #ee0a24; }
&.gray { color: #999; }
}
.plan-title {
font-size: 16px;
font-weight: 700;
color: #111;
line-height: 1.5;
margin-bottom: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.info-row {
display: flex;
align-items: flex-start;
margin-bottom: 8px;
font-size: 14px;
line-height: 1.5;
}
.label {
width: 72px;
flex-shrink: 0;
color: #333;
font-weight: 500;
}
.value {
flex: 1;
color: #8a8a8a;
&.msg {
color: #666;
font-style: italic;
}
}
.card-actions {
margin-top: 14px;
padding-top: 12px;
border-top: 1px solid rgba(0, 0, 0, 0.06);
display: flex;
justify-content: space-between;
align-items: center;
}
.actions-left {
display: flex;
align-items: center;
}
.actions-right {
display: flex;
align-items: center;
gap: 24px;
}
.message-action {
color: #1f75c9;
}
.action-item {
display: flex;
align-items: center;
gap: 4px;
font-size: 14px;
cursor: pointer;
font-weight: 500;
}
.approve-action {
color: #07c160;
}
.reject-action {
color: #ee0a24;
}
</style>

220
zdxt-web-server/zdxt-admin-web-server/src/test/java/com/zdxt/rest/test/cp/AppEnfCpReserveTest.java

@ -0,0 +1,220 @@
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 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.util.*;
import java.util.stream.Collectors;
/**
* 预约记录批量生成测试
* <p>
* 基于已创建的计划数据,为多个用户交替创建 efcode_cp_reserve 预约记录。
* 模拟场景:用户A的计划被用户B预约,用户B的计划被用户C预约...循环交替。
* 预约状态交替:待确认(3)、已同意(1)、已拒绝(2)
* 预约来源交替:手动预约(0)、系统匹配(1)
* </p>
*/
@SpringBootTest(classes = ZdxtAdminApplication.class)
@AutoConfigureMockMvc
@ActiveProfiles({"dev", "dev-cust-zrb"})
@DisplayName("AppEnfCpReserve 批量生成预约记录测试")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Tag("dev")
public class AppEnfCpReserveTest {
@Autowired
private EfcodeCpPlanMapper cpPlanMapper;
@Autowired
private EfcodeCpReserveMapper cpReserveMapper;
/**
* 测试账号对应的 userId(需从数据库匹配)
* 这里通过查询 plan 表的 create_by 字段获取对应用户ID
*/
private static final String[] TEST_ACCOUNTS = {"liuqianlin", "baining", "caizifan", "chenchong4"};
/** 每个用户最多取多少条计划来生成预约 */
private static final int MAX_PLANS_PER_USER = 50;
/** 预约留言模板 */
private static final String[] MESSAGES = {
"希望联合执法,请确认时间",
"我单位有相关检查需求,申请联合",
"建议合并检查,减轻企业负担",
"时间安排一致,申请联合执法",
"", // 部分无留言
"请尽快确认预约",
"",
"我方已做好准备,等待确认"
};
@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("基于已有计划批量生成交替预约记录")
@Test
@Order(1)
public void testBatchCreateReserves() {
// 1. 收集每个用户的计划(通过 enforce_user_names LIKE 账号中文名识别)
// 实际通过 create_by 字段分组获取不同用户的计划
Map<Long, List<EfcodeCpPlan>> userPlansMap = loadUserPlansMap();
List<Long> userIds = new ArrayList<>(userPlansMap.keySet());
Assertions.assertTrue(userIds.size() >= 2, "至少需要2个用户的计划数据才能交叉预约");
System.out.println("========== 检测到 " + userIds.size() + " 个用户的计划数据 ==========");
for (Long uid : userIds) {
System.out.println(" userId=" + uid + ", 计划数=" + userPlansMap.get(uid).size());
}
Random random = new Random(42); // 固定种子,可重复
int totalCreated = 0;
int reserveIndex = 0;
// 2. 交替为每个用户的计划创建来自其他用户的预约
for (int ownerIdx = 0; ownerIdx < userIds.size(); ownerIdx++) {
Long ownerUserId = userIds.get(ownerIdx);
List<EfcodeCpPlan> ownerPlans = userPlansMap.get(ownerUserId);
// 限制数量
int planCount = Math.min(ownerPlans.size(), MAX_PLANS_PER_USER);
for (int planIdx = 0; planIdx < planCount; planIdx++) {
EfcodeCpPlan targetPlan = ownerPlans.get(planIdx);
// 选择预约发起者:循环选取其他用户
int reserverIdx = (ownerIdx + 1 + planIdx) % userIds.size();
if (reserverIdx == ownerIdx) {
reserverIdx = (reserverIdx + 1) % userIds.size();
}
Long reserverUserId = userIds.get(reserverIdx);
List<EfcodeCpPlan> reserverPlans = userPlansMap.get(reserverUserId);
// 找预约者匹配的计划(同日期+同时段+同企业),如果没有就用预约者的任意一条计划
EfcodeCpPlan reserverPlan = findMatchingPlan(reserverPlans, targetPlan);
if (reserverPlan == null && !reserverPlans.isEmpty()) {
reserverPlan = reserverPlans.get(planIdx % reserverPlans.size());
}
if (reserverPlan == null) continue;
// 3. 确定 planGroupId
String planGroupId = targetPlan.getId();
// 4. 交替 opStatus: 3(待确认), 1(已同意), 2(已拒绝), 3, 1, 2...
long opStatus = getAlternatingStatus(reserveIndex);
// 5. 交替 resFrom: 0(手动), 1(系统)
long resFrom = reserveIndex % 2;
// 6. 留言
String message = MESSAGES[reserveIndex % MESSAGES.length];
// 插入目标计划方的记录(计划拥有者)
insertReserve(targetPlan.getId(), planGroupId, "", targetPlan.getPlanDate(),
targetPlan.getPlanTime(), resFrom, opStatus);
// 插入预约发起方的记录
insertReserve(reserverPlan.getId(), planGroupId, message, targetPlan.getPlanDate(),
targetPlan.getPlanTime(), resFrom, 3L); // 发起方始终待确认
totalCreated += 2;
reserveIndex++;
}
System.out.println("[userId=" + ownerUserId + "] 已为其 " + planCount + " 条计划生成预约记录");
}
System.out.println("========== 预约记录生成完成 ==========");
System.out.println("总计插入: " + totalCreated + " 条预约记录");
Assertions.assertTrue(totalCreated > 0, "应至少生成1条预约记录");
}
/**
* 从数据库加载各用户的计划,按 create_by 分组
* 只取未删除、reserve_status=1(待预约)的计划
*/
private Map<Long, List<EfcodeCpPlan>> loadUserPlansMap() {
LambdaQueryWrapper<EfcodeCpPlan> lqw = Wrappers.lambdaQuery();
lqw.eq(EfcodeCpPlan::getIsDelete, 0L);
lqw.eq(EfcodeCpPlan::getReserveStatus, 1L); // 待预约状态
lqw.like(EfcodeCpPlan::getEnforceContent, "批量测试执法事项"); // 只取测试数据
lqw.orderByAsc(EfcodeCpPlan::getPlanDate);
List<EfcodeCpPlan> allPlans = cpPlanMapper.selectList(lqw);
return allPlans.stream()
.filter(p -> p.getCreateBy() != null)
.collect(Collectors.groupingBy(EfcodeCpPlan::getCreateBy));
}
/**
* 在预约者的计划列表中查找与目标计划匹配的计划(同日期+同时段+同企业)
*/
private EfcodeCpPlan findMatchingPlan(List<EfcodeCpPlan> plans, EfcodeCpPlan target) {
if (plans == null || plans.isEmpty()) return null;
for (EfcodeCpPlan p : plans) {
if (p.getPlanDate() != null && p.getPlanDate().equals(target.getPlanDate())
&& Objects.equals(p.getPlanTime(), target.getPlanTime())
&& Objects.equals(p.getEnterpriseId(), target.getEnterpriseId())) {
return p;
}
}
return null;
}
/**
* 交替状态: 3, 1, 2, 3, 1, 2...
*/
private long getAlternatingStatus(int index) {
int mod = index % 3;
return mod == 0 ? 3L : (mod == 1 ? 1L : 2L);
}
/**
* 插入一条预约记录
*/
private void insertReserve(String planId, String planGroupId, String opMessage,
Date opSetDate, Long opSetTime, Long resFrom, Long opStatus) {
EfcodeCpReserve record = new EfcodeCpReserve();
record.setPlanId(planId);
record.setPlanGroupId(planGroupId);
record.setReserveTime(new Date());
record.setOpStatus(opStatus);
record.setOpOrder(0L);
record.setOpMessage(opMessage);
record.setOpSetDate(opSetDate);
record.setOpSetTime(opSetTime);
record.setResFrom(resFrom);
cpReserveMapper.insert(record);
}
}
Loading…
Cancel
Save