feat(wm-dispatch): #69 调度指令全生命周期管理
- 增强 DispatchCommand 实体,支持全生命周期状态流转 - 新增 CommandExecutionRecord(执行记录)、CommandTracking(过程追踪)实体 - 新增 DTO: CommandCreateRequest, CommandQueryRequest, ExecutionRequest, CommandStatVO - 新增 Mapper: CommandExecutionRecordMapper, CommandTrackingMapper, 增强 DispatchCommandMapper(分页+统计) - 新增 Service: - CommandLifecycleService: DRAFT→ISSUED→RECEIVED→EXECUTING→COMPLETED/REJECTED/CANCELLED 全流程 - CommandLedgerService: 指令台账(多维度查询/统计/导出) - CommandTrackingService: 全过程追踪时间线 - 新增 DispatchCommandController: 18个 RESTful 端点 (/api/dispatch/command/*) - DDL: V2__command_lifecycle.sql(3张表 + 索引) - 单元测试: CommandLifecycleServiceTest(13个), CommandTrackingServiceTest(4个) - 同步修复 DispatchBizService/DispatchController 适配新实体字段
This commit is contained in:
+159
@@ -0,0 +1,159 @@
|
||||
package com.water.dispatch.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.water.common.core.result.R;
|
||||
import com.water.dispatch.entity.CommandExecutionRecord;
|
||||
import com.water.dispatch.entity.CommandTracking;
|
||||
import com.water.dispatch.entity.DispatchCommand;
|
||||
import com.water.dispatch.entity.dto.*;
|
||||
import com.water.dispatch.service.CommandLedgerService;
|
||||
import com.water.dispatch.service.CommandLifecycleService;
|
||||
import com.water.dispatch.service.CommandTrackingService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 调度指令管理 - 全生命周期
|
||||
*/
|
||||
@Tag(name = "调度指令管理")
|
||||
@RestController
|
||||
@RequestMapping("/api/dispatch/command")
|
||||
@RequiredArgsConstructor
|
||||
public class DispatchCommandController {
|
||||
|
||||
private final CommandLifecycleService lifecycleService;
|
||||
private final CommandLedgerService ledgerService;
|
||||
private final CommandTrackingService trackingService;
|
||||
|
||||
// ==================== 生命周期操作 ====================
|
||||
|
||||
@Operation(summary = "1. 创建指令")
|
||||
@PostMapping
|
||||
public R<DispatchCommand> create(@RequestBody CommandCreateRequest req) {
|
||||
return R.ok(lifecycleService.create(req));
|
||||
}
|
||||
|
||||
@Operation(summary = "2. 下发指令")
|
||||
@PostMapping("/{id}/issue")
|
||||
public R<DispatchCommand> issue(@PathVariable Long id) {
|
||||
return R.ok(lifecycleService.issue(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "3. 接收确认")
|
||||
@PostMapping("/{id}/receive")
|
||||
public R<DispatchCommand> receive(@PathVariable Long id,
|
||||
@RequestParam(required = false) Long receiverId,
|
||||
@RequestParam(required = false) String receiverName) {
|
||||
return R.ok(lifecycleService.receive(id, receiverId, receiverName));
|
||||
}
|
||||
|
||||
@Operation(summary = "4. 开始执行")
|
||||
@PostMapping("/{id}/start")
|
||||
public R<DispatchCommand> startExecution(@PathVariable Long id,
|
||||
@RequestParam(required = false) Long executorId,
|
||||
@RequestParam(required = false) String executorName) {
|
||||
return R.ok(lifecycleService.startExecution(id, executorId, executorName));
|
||||
}
|
||||
|
||||
@Operation(summary = "5. 执行进度反馈")
|
||||
@PostMapping("/{id}/progress")
|
||||
public R<DispatchCommand> reportProgress(@PathVariable Long id,
|
||||
@RequestBody ExecutionRequest req) {
|
||||
return R.ok(lifecycleService.reportProgress(id, req.getExecutorId(), req.getExecutorName(),
|
||||
req.getDescription(), req.getProgress() != null ? req.getProgress() : 0));
|
||||
}
|
||||
|
||||
@Operation(summary = "6. 完成指令")
|
||||
@PostMapping("/{id}/complete")
|
||||
public R<DispatchCommand> complete(@PathVariable Long id,
|
||||
@RequestBody ExecutionRequest req) {
|
||||
return R.ok(lifecycleService.complete(id, req.getExecutorId(), req.getExecutorName(), req.getExecuteResult()));
|
||||
}
|
||||
|
||||
@Operation(summary = "7. 驳回指令")
|
||||
@PostMapping("/{id}/reject")
|
||||
public R<DispatchCommand> reject(@PathVariable Long id,
|
||||
@RequestBody ExecutionRequest req) {
|
||||
return R.ok(lifecycleService.reject(id, req.getExecutorId(), req.getExecutorName(), req.getRejectReason()));
|
||||
}
|
||||
|
||||
@Operation(summary = "8. 取消指令")
|
||||
@PostMapping("/{id}/cancel")
|
||||
public R<DispatchCommand> cancel(@PathVariable Long id,
|
||||
@RequestParam(required = false) Long operatorId,
|
||||
@RequestParam(required = false) String operatorName,
|
||||
@RequestParam(required = false) String reason) {
|
||||
return R.ok(lifecycleService.cancel(id, operatorId, operatorName, reason));
|
||||
}
|
||||
|
||||
// ==================== 查询 ====================
|
||||
|
||||
@Operation(summary = "9. 获取指令详情(按ID)")
|
||||
@GetMapping("/{id}")
|
||||
public R<DispatchCommand> getById(@PathVariable Long id) {
|
||||
return R.ok(lifecycleService.getById(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "10. 根据指令编号获取详情")
|
||||
@GetMapping("/no/{commandNo}")
|
||||
public R<DispatchCommand> getByCommandNo(@PathVariable String commandNo) {
|
||||
return R.ok(lifecycleService.getByCommandNo(commandNo));
|
||||
}
|
||||
|
||||
// ==================== 台账 ====================
|
||||
|
||||
@Operation(summary = "11. 指令台账分页查询")
|
||||
@GetMapping("/page")
|
||||
public R<IPage<DispatchCommand>> queryPage(CommandQueryRequest req) {
|
||||
return R.ok(ledgerService.queryPage(req));
|
||||
}
|
||||
|
||||
@Operation(summary = "12. 指令台账列表查询")
|
||||
@GetMapping("/list")
|
||||
public R<List<DispatchCommand>> queryList(CommandQueryRequest req) {
|
||||
return R.ok(ledgerService.queryList(req));
|
||||
}
|
||||
|
||||
@Operation(summary = "13. 指令统计概览")
|
||||
@GetMapping("/statistics")
|
||||
public R<CommandStatVO> statistics() {
|
||||
return R.ok(ledgerService.statistics());
|
||||
}
|
||||
|
||||
@Operation(summary = "14. 指令数据导出")
|
||||
@GetMapping("/export")
|
||||
public R<List<DispatchCommand>> exportData(CommandQueryRequest req) {
|
||||
return R.ok(ledgerService.exportData(req));
|
||||
}
|
||||
|
||||
// ==================== 追踪 ====================
|
||||
|
||||
@Operation(summary = "15. 获取指令追踪时间线")
|
||||
@GetMapping("/{id}/timeline")
|
||||
public R<List<CommandTracking>> getTimeline(@PathVariable Long id) {
|
||||
return R.ok(trackingService.getTimeline(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "16. 获取执行记录")
|
||||
@GetMapping("/{id}/executions")
|
||||
public R<List<CommandExecutionRecord>> getExecutions(@PathVariable Long id) {
|
||||
return R.ok(trackingService.getExecutionRecords(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "17. 获取完整时间线(追踪+执行记录合并)")
|
||||
@GetMapping("/{id}/full-timeline")
|
||||
public R<List<Map<String, Object>>> getFullTimeline(@PathVariable Long id) {
|
||||
return R.ok(trackingService.getFullTimeline(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "18. 按指令编号获取追踪时间线")
|
||||
@GetMapping("/no/{commandNo}/timeline")
|
||||
public R<List<CommandTracking>> getTimelineByCommandNo(@PathVariable String commandNo) {
|
||||
return R.ok(trackingService.getTimelineByCommandNo(commandNo));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
package com.water.dispatch.controller;
|
||||
|
||||
import com.water.common.core.result.R;
|
||||
import com.water.dispatch.entity.*;
|
||||
import com.water.dispatch.service.DispatchBizService;
|
||||
@@ -6,19 +7,71 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.*;
|
||||
@Tag(name="调度工作台") @RestController @RequestMapping("/dispatch") @RequiredArgsConstructor
|
||||
|
||||
@Tag(name = "调度工作台")
|
||||
@RestController
|
||||
@RequestMapping("/dispatch")
|
||||
@RequiredArgsConstructor
|
||||
public class DispatchController {
|
||||
private final DispatchBizService svc;
|
||||
@GetMapping("/duty/today") public R<List<DutySchedule>> todayDuty() { return R.ok(svc.getTodayDuty()); }
|
||||
@PostMapping("/command") public R<Map<String,Object>> createCommand(@RequestBody Map<String,Object> req) { return R.ok(svc.createCommand(req)); }
|
||||
@PostMapping("/command/{cmdNo}/issue") public R<Map<String,Object>> issue(@PathVariable String cmdNo) { return R.ok(svc.issueCommand(cmdNo)); }
|
||||
@GetMapping("/command/list") public R<List<DispatchCommand>> listCommands(@RequestParam(required=false) Integer status) { return R.ok(svc.listCommands(status)); }
|
||||
@PostMapping("/work-order") public R<Map<String,Object>> createWO(@RequestBody Map<String,Object> req) { return R.ok(svc.createWorkOrder(req)); }
|
||||
@PutMapping("/work-order/{id}/status") public R<Map<String,Object>> updateWOStatus(@PathVariable Long id, @RequestParam int status) { return R.ok(svc.updateWorkOrderStatus(id, status)); }
|
||||
@PostMapping("/duty-log") public R<String> addLog(@RequestParam Long scheduleId, @RequestParam Long userId, @RequestParam String type, @RequestParam String content) { svc.addDutyLog(scheduleId,userId,type,content); return R.ok("OK"); }
|
||||
@GetMapping("/duty-log/{scheduleId}") public R<List<DutyLog>> getLogs(@PathVariable Long scheduleId) { return R.ok(svc.getDutyLogs(scheduleId)); }
|
||||
@GetMapping("/strategy") public R<List<DispatchStrategy>> listStrategies(@RequestParam(required=false) String type) { return R.ok(svc.listStrategies(type)); }
|
||||
@GetMapping("/emergency-plan") public R<List<EmergencyPlan>> listPlans(@RequestParam(required=false) String type) { return R.ok(svc.listPlans(type)); }
|
||||
@PostMapping("/emergency/simulate") public R<Map<String,Object>> simulate(@RequestParam String type, @RequestParam double lng, @RequestParam double lat) { return R.ok(svc.simulateEmergency(type,lng,lat)); }
|
||||
|
||||
@GetMapping("/duty/today")
|
||||
public R<List<DutySchedule>> todayDuty() {
|
||||
return R.ok(svc.getTodayDuty());
|
||||
}
|
||||
|
||||
@PostMapping("/command")
|
||||
public R<Map<String, Object>> createCommand(@RequestBody Map<String, Object> req) {
|
||||
return R.ok(svc.createCommand(req));
|
||||
}
|
||||
|
||||
@PostMapping("/command/{cmdNo}/issue")
|
||||
public R<Map<String, Object>> issue(@PathVariable String cmdNo) {
|
||||
return R.ok(svc.issueCommand(cmdNo));
|
||||
}
|
||||
|
||||
@GetMapping("/command/list")
|
||||
public R<List<DispatchCommand>> listCommands(@RequestParam(required = false) String status) {
|
||||
return R.ok(svc.listCommands(status));
|
||||
}
|
||||
|
||||
@PostMapping("/work-order")
|
||||
public R<Map<String, Object>> createWO(@RequestBody Map<String, Object> req) {
|
||||
return R.ok(svc.createWorkOrder(req));
|
||||
}
|
||||
|
||||
@PutMapping("/work-order/{id}/status")
|
||||
public R<Map<String, Object>> updateWOStatus(@PathVariable Long id, @RequestParam int status) {
|
||||
return R.ok(svc.updateWorkOrderStatus(id, status));
|
||||
}
|
||||
|
||||
@PostMapping("/duty-log")
|
||||
public R<String> addLog(@RequestParam Long scheduleId, @RequestParam Long userId,
|
||||
@RequestParam String type, @RequestParam String content) {
|
||||
svc.addDutyLog(scheduleId, userId, type, content);
|
||||
return R.ok("OK");
|
||||
}
|
||||
|
||||
@GetMapping("/duty-log/{scheduleId}")
|
||||
public R<List<DutyLog>> getLogs(@PathVariable Long scheduleId) {
|
||||
return R.ok(svc.getDutyLogs(scheduleId));
|
||||
}
|
||||
|
||||
@GetMapping("/strategy")
|
||||
public R<List<DispatchStrategy>> listStrategies(@RequestParam(required = false) String type) {
|
||||
return R.ok(svc.listStrategies(type));
|
||||
}
|
||||
|
||||
@GetMapping("/emergency-plan")
|
||||
public R<List<EmergencyPlan>> listPlans(@RequestParam(required = false) String type) {
|
||||
return R.ok(svc.listPlans(type));
|
||||
}
|
||||
|
||||
@PostMapping("/emergency/simulate")
|
||||
public R<Map<String, Object>> simulate(@RequestParam String type,
|
||||
@RequestParam double lng, @RequestParam double lat) {
|
||||
return R.ok(svc.simulateEmergency(type, lng, lat));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.water.dispatch.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 指令执行记录
|
||||
*/
|
||||
@Data
|
||||
@TableName("disp_command_execution_record")
|
||||
public class CommandExecutionRecord {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 关联指令ID */
|
||||
private Long commandId;
|
||||
|
||||
/** 关联指令编号 */
|
||||
private String commandNo;
|
||||
|
||||
/** 执行人ID */
|
||||
private Long executorId;
|
||||
|
||||
/** 执行人姓名 */
|
||||
private String executorName;
|
||||
|
||||
/** 动作: RECEIVE / START / PROGRESS / COMPLETE / REJECT */
|
||||
private String action;
|
||||
|
||||
/** 描述 */
|
||||
private String description;
|
||||
|
||||
/** 附件(JSON array) */
|
||||
private String attachments;
|
||||
|
||||
/** 进度 0-100 */
|
||||
private Integer progress;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.water.dispatch.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 指令过程追踪(时间线)
|
||||
*/
|
||||
@Data
|
||||
@TableName("disp_command_tracking")
|
||||
public class CommandTracking {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 关联指令ID */
|
||||
private Long commandId;
|
||||
|
||||
/** 关联指令编号 */
|
||||
private String commandNo;
|
||||
|
||||
/** 阶段: CREATED / ISSUED / RECEIVED / EXECUTING / COMPLETED / REJECTED / CANCELLED */
|
||||
private String stage;
|
||||
|
||||
/** 操作人ID */
|
||||
private Long operatorId;
|
||||
|
||||
/** 操作人姓名 */
|
||||
private String operatorName;
|
||||
|
||||
/** 动作描述 */
|
||||
private String actionDesc;
|
||||
|
||||
/** 来源状态 */
|
||||
private String fromStatus;
|
||||
|
||||
/** 目标状态 */
|
||||
private String toStatus;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 调度指令 - 全生命周期: 下发→接收→执行→完成→驳回
|
||||
* 调度指令 - 全生命周期: DRAFT→ISSUED→RECEIVED→EXECUTING→COMPLETED/REJECTED/CANCELLED
|
||||
*/
|
||||
@Data
|
||||
@TableName("disp_dispatch_command")
|
||||
@@ -30,6 +30,9 @@ public class DispatchCommand {
|
||||
/** 优先级: LOW-低 MEDIUM-中 HIGH-高 URGENT-紧急 */
|
||||
private String priority;
|
||||
|
||||
/** 状态: DRAFT/ISSUED/RECEIVED/EXECUTING/COMPLETED/REJECTED/CANCELLED */
|
||||
private String status;
|
||||
|
||||
/** 下发人ID */
|
||||
private Long issuerId;
|
||||
|
||||
@@ -45,8 +48,8 @@ public class DispatchCommand {
|
||||
/** 关联设施ID */
|
||||
private Long facilityId;
|
||||
|
||||
/** 状态: ISSUED-下发 RECEIVED-接收 EXECUTING-执行 COMPLETED-完成 REJECTED-驳回 CANCELLED-取消 */
|
||||
private String status;
|
||||
/** 截止时间 */
|
||||
private LocalDateTime deadline;
|
||||
|
||||
/** 下发时间 */
|
||||
private LocalDateTime issuedAt;
|
||||
@@ -60,19 +63,23 @@ public class DispatchCommand {
|
||||
/** 完成时间 */
|
||||
private LocalDateTime completedAt;
|
||||
|
||||
/** 驳回时间 */
|
||||
private LocalDateTime rejectedAt;
|
||||
|
||||
/** 驳回原因 */
|
||||
private String rejectReason;
|
||||
|
||||
/** 执行结果 */
|
||||
private String executeResult;
|
||||
|
||||
/** 截止时间 */
|
||||
private LocalDateTime deadline;
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.water.dispatch.entity.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 指令创建请求
|
||||
*/
|
||||
@Data
|
||||
public class CommandCreateRequest {
|
||||
private String title;
|
||||
private String content;
|
||||
private String commandType; // NORMAL / EMERGENCY / MAINTENANCE
|
||||
private String priority; // LOW / MEDIUM / HIGH / URGENT
|
||||
private Long issuerId;
|
||||
private String issuerName;
|
||||
private Long receiverId;
|
||||
private String receiverName;
|
||||
private Long facilityId;
|
||||
private LocalDateTime deadline;
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.water.dispatch.entity.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 指令台账查询请求
|
||||
*/
|
||||
@Data
|
||||
public class CommandQueryRequest {
|
||||
private String commandNo;
|
||||
private String title;
|
||||
private String commandType;
|
||||
private String priority;
|
||||
private String status;
|
||||
private Long issuerId;
|
||||
private Long receiverId;
|
||||
private LocalDateTime startTime;
|
||||
private LocalDateTime endTime;
|
||||
private Integer pageNum = 1;
|
||||
private Integer pageSize = 20;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.water.dispatch.entity.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 指令统计VO
|
||||
*/
|
||||
@Data
|
||||
public class CommandStatVO {
|
||||
private long total;
|
||||
private Map<String, Long> byStatus;
|
||||
private Map<String, Long> byType;
|
||||
private Map<String, Long> byPriority;
|
||||
private long overdueCount;
|
||||
private long todayCreated;
|
||||
private long todayCompleted;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.water.dispatch.entity.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 执行反馈请求
|
||||
*/
|
||||
@Data
|
||||
public class ExecutionRequest {
|
||||
private Long commandId;
|
||||
private Long executorId;
|
||||
private String executorName;
|
||||
private String action; // RECEIVE / START / PROGRESS / COMPLETE / REJECT
|
||||
private String description;
|
||||
private String attachments; // JSON array
|
||||
private Integer progress;
|
||||
private String rejectReason; // 驳回时填写
|
||||
private String executeResult; // 完成时填写
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.water.dispatch.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.water.dispatch.entity.CommandExecutionRecord;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface CommandExecutionRecordMapper extends BaseMapper<CommandExecutionRecord> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.water.dispatch.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.water.dispatch.entity.CommandTracking;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface CommandTrackingMapper extends BaseMapper<CommandTracking> {
|
||||
}
|
||||
@@ -1,5 +1,98 @@
|
||||
package com.water.dispatch.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.water.dispatch.entity.DispatchCommand;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper public interface DispatchCommandMapper extends BaseMapper<DispatchCommand> {}
|
||||
import org.apache.ibatis.annotations.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface DispatchCommandMapper extends BaseMapper<DispatchCommand> {
|
||||
|
||||
/**
|
||||
* 分页 + 多维度查询指令台账
|
||||
*/
|
||||
@SelectProvider(type = CommandSqlProvider.class, method = "buildLedgerQuery")
|
||||
IPage<DispatchCommand> selectLedgerPage(
|
||||
Page<?> page,
|
||||
@Param("commandNo") String commandNo,
|
||||
@Param("title") String title,
|
||||
@Param("commandType") String commandType,
|
||||
@Param("priority") String priority,
|
||||
@Param("status") String status,
|
||||
@Param("issuerId") Long issuerId,
|
||||
@Param("receiverId") Long receiverId,
|
||||
@Param("startTime") String startTime,
|
||||
@Param("endTime") String endTime);
|
||||
|
||||
/**
|
||||
* 按状态统计
|
||||
*/
|
||||
@Select("SELECT status, COUNT(*) as cnt FROM disp_dispatch_command WHERE deleted=0 GROUP BY status")
|
||||
List<Map<String, Object>> countByStatus();
|
||||
|
||||
/**
|
||||
* 按类型统计
|
||||
*/
|
||||
@Select("SELECT command_type, COUNT(*) as cnt FROM disp_dispatch_command WHERE deleted=0 GROUP BY command_type")
|
||||
List<Map<String, Object>> countByType();
|
||||
|
||||
/**
|
||||
* 按优先级统计
|
||||
*/
|
||||
@Select("SELECT priority, COUNT(*) as cnt FROM disp_dispatch_command WHERE deleted=0 GROUP BY priority")
|
||||
List<Map<String, Object>> countByPriority();
|
||||
|
||||
/**
|
||||
* 逾期指令数
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM disp_dispatch_command WHERE deleted=0 AND status NOT IN ('COMPLETED','CANCELLED','REJECTED') AND deadline < NOW()")
|
||||
long countOverdue();
|
||||
|
||||
class CommandSqlProvider {
|
||||
public String buildLedgerQuery(
|
||||
@Param("commandNo") String commandNo,
|
||||
@Param("title") String title,
|
||||
@Param("commandType") String commandType,
|
||||
@Param("priority") String priority,
|
||||
@Param("status") String status,
|
||||
@Param("issuerId") Long issuerId,
|
||||
@Param("receiverId") Long receiverId,
|
||||
@Param("startTime") String startTime,
|
||||
@Param("endTime") String endTime) {
|
||||
StringBuilder sb = new StringBuilder("SELECT * FROM disp_dispatch_command WHERE deleted=0");
|
||||
if (commandNo != null && !commandNo.isEmpty()) {
|
||||
sb.append(" AND command_no LIKE '%").append(commandNo).append("%'");
|
||||
}
|
||||
if (title != null && !title.isEmpty()) {
|
||||
sb.append(" AND title LIKE '%").append(title).append("%'");
|
||||
}
|
||||
if (commandType != null && !commandType.isEmpty()) {
|
||||
sb.append(" AND command_type = '").append(commandType).append("'");
|
||||
}
|
||||
if (priority != null && !priority.isEmpty()) {
|
||||
sb.append(" AND priority = '").append(priority).append("'");
|
||||
}
|
||||
if (status != null && !status.isEmpty()) {
|
||||
sb.append(" AND status = '").append(status).append("'");
|
||||
}
|
||||
if (issuerId != null) {
|
||||
sb.append(" AND issuer_id = ").append(issuerId);
|
||||
}
|
||||
if (receiverId != null) {
|
||||
sb.append(" AND receiver_id = ").append(receiverId);
|
||||
}
|
||||
if (startTime != null && !startTime.isEmpty()) {
|
||||
sb.append(" AND created_at >= '").append(startTime).append("'");
|
||||
}
|
||||
if (endTime != null && !endTime.isEmpty()) {
|
||||
sb.append(" AND created_at <= '").append(endTime).append("'");
|
||||
}
|
||||
sb.append(" ORDER BY created_at DESC");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.water.dispatch.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.water.dispatch.entity.DispatchCommand;
|
||||
import com.water.dispatch.entity.dto.CommandQueryRequest;
|
||||
import com.water.dispatch.entity.dto.CommandStatVO;
|
||||
import com.water.dispatch.mapper.DispatchCommandMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 指令台账服务 - 多维度查询/统计/导出
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CommandLedgerService {
|
||||
|
||||
private final DispatchCommandMapper commandMapper;
|
||||
|
||||
/**
|
||||
* 分页多维度查询
|
||||
*/
|
||||
public IPage<DispatchCommand> queryPage(CommandQueryRequest req) {
|
||||
Page<DispatchCommand> page = new Page<>(req.getPageNum(), req.getPageSize());
|
||||
String startTime = req.getStartTime() != null ? req.getStartTime().toString() : null;
|
||||
String endTime = req.getEndTime() != null ? req.getEndTime().toString() : null;
|
||||
|
||||
return commandMapper.selectLedgerPage(page,
|
||||
req.getCommandNo(), req.getTitle(), req.getCommandType(),
|
||||
req.getPriority(), req.getStatus(),
|
||||
req.getIssuerId(), req.getReceiverId(),
|
||||
startTime, endTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表查询(不分页)
|
||||
*/
|
||||
public List<DispatchCommand> queryList(CommandQueryRequest req) {
|
||||
LambdaQueryWrapper<DispatchCommand> wrapper = new LambdaQueryWrapper<>();
|
||||
if (req.getCommandNo() != null && !req.getCommandNo().isEmpty()) {
|
||||
wrapper.like(DispatchCommand::getCommandNo, req.getCommandNo());
|
||||
}
|
||||
if (req.getTitle() != null && !req.getTitle().isEmpty()) {
|
||||
wrapper.like(DispatchCommand::getTitle, req.getTitle());
|
||||
}
|
||||
if (req.getCommandType() != null && !req.getCommandType().isEmpty()) {
|
||||
wrapper.eq(DispatchCommand::getCommandType, req.getCommandType());
|
||||
}
|
||||
if (req.getPriority() != null && !req.getPriority().isEmpty()) {
|
||||
wrapper.eq(DispatchCommand::getPriority, req.getPriority());
|
||||
}
|
||||
if (req.getStatus() != null && !req.getStatus().isEmpty()) {
|
||||
wrapper.eq(DispatchCommand::getStatus, req.getStatus());
|
||||
}
|
||||
if (req.getIssuerId() != null) {
|
||||
wrapper.eq(DispatchCommand::getIssuerId, req.getIssuerId());
|
||||
}
|
||||
if (req.getReceiverId() != null) {
|
||||
wrapper.eq(DispatchCommand::getReceiverId, req.getReceiverId());
|
||||
}
|
||||
if (req.getStartTime() != null) {
|
||||
wrapper.ge(DispatchCommand::getCreatedAt, req.getStartTime());
|
||||
}
|
||||
if (req.getEndTime() != null) {
|
||||
wrapper.le(DispatchCommand::getCreatedAt, req.getEndTime());
|
||||
}
|
||||
wrapper.orderByDesc(DispatchCommand::getCreatedAt);
|
||||
return commandMapper.selectList(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计概览
|
||||
*/
|
||||
public CommandStatVO statistics() {
|
||||
CommandStatVO vo = new CommandStatVO();
|
||||
vo.setTotal(commandMapper.selectCount(new LambdaQueryWrapper<>()));
|
||||
|
||||
Map<String, Long> byStatus = new HashMap<>();
|
||||
commandMapper.countByStatus().forEach(m ->
|
||||
byStatus.put(String.valueOf(m.get("status")), ((Number) m.get("cnt")).longValue()));
|
||||
vo.setByStatus(byStatus);
|
||||
|
||||
Map<String, Long> byType = new HashMap<>();
|
||||
commandMapper.countByType().forEach(m ->
|
||||
byType.put(String.valueOf(m.get("command_type")), ((Number) m.get("cnt")).longValue()));
|
||||
vo.setByType(byType);
|
||||
|
||||
Map<String, Long> byPriority = new HashMap<>();
|
||||
commandMapper.countByPriority().forEach(m ->
|
||||
byPriority.put(String.valueOf(m.get("priority")), ((Number) m.get("cnt")).longValue()));
|
||||
vo.setByPriority(byPriority);
|
||||
|
||||
vo.setOverdueCount(commandMapper.countOverdue());
|
||||
|
||||
LocalDateTime todayStart = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
|
||||
LocalDateTime todayEnd = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
|
||||
vo.setTodayCreated(commandMapper.selectCount(
|
||||
new LambdaQueryWrapper<DispatchCommand>()
|
||||
.ge(DispatchCommand::getCreatedAt, todayStart)
|
||||
.le(DispatchCommand::getCreatedAt, todayEnd)));
|
||||
vo.setTodayCompleted(commandMapper.selectCount(
|
||||
new LambdaQueryWrapper<DispatchCommand>()
|
||||
.eq(DispatchCommand::getStatus, "COMPLETED")
|
||||
.ge(DispatchCommand::getCompletedAt, todayStart)
|
||||
.le(DispatchCommand::getCompletedAt, todayEnd)));
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出用数据(全量列表)
|
||||
*/
|
||||
public List<DispatchCommand> exportData(CommandQueryRequest req) {
|
||||
return queryList(req);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
package com.water.dispatch.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.water.common.core.exception.BusinessException;
|
||||
import com.water.dispatch.entity.CommandExecutionRecord;
|
||||
import com.water.dispatch.entity.CommandTracking;
|
||||
import com.water.dispatch.entity.DispatchCommand;
|
||||
import com.water.dispatch.entity.dto.CommandCreateRequest;
|
||||
import com.water.dispatch.entity.dto.ExecutionRequest;
|
||||
import com.water.dispatch.mapper.CommandExecutionRecordMapper;
|
||||
import com.water.dispatch.mapper.CommandTrackingMapper;
|
||||
import com.water.dispatch.mapper.DispatchCommandMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 调度指令全生命周期服务
|
||||
* DRAFT → ISSUED → RECEIVED → EXECUTING → COMPLETED / REJECTED / CANCELLED
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CommandLifecycleService {
|
||||
|
||||
private final DispatchCommandMapper commandMapper;
|
||||
private final CommandExecutionRecordMapper executionMapper;
|
||||
private final CommandTrackingMapper trackingMapper;
|
||||
|
||||
/**
|
||||
* 创建指令(DRAFT)
|
||||
*/
|
||||
@Transactional
|
||||
public DispatchCommand create(CommandCreateRequest req) {
|
||||
DispatchCommand cmd = new DispatchCommand();
|
||||
cmd.setCommandNo(generateCommandNo());
|
||||
cmd.setTitle(req.getTitle());
|
||||
cmd.setContent(req.getContent());
|
||||
cmd.setCommandType(req.getCommandType() != null ? req.getCommandType() : "NORMAL");
|
||||
cmd.setPriority(req.getPriority() != null ? req.getPriority() : "MEDIUM");
|
||||
cmd.setStatus("DRAFT");
|
||||
cmd.setIssuerId(req.getIssuerId());
|
||||
cmd.setIssuerName(req.getIssuerName());
|
||||
cmd.setReceiverId(req.getReceiverId());
|
||||
cmd.setReceiverName(req.getReceiverName());
|
||||
cmd.setFacilityId(req.getFacilityId());
|
||||
cmd.setDeadline(req.getDeadline());
|
||||
cmd.setRemark(req.getRemark());
|
||||
commandMapper.insert(cmd);
|
||||
|
||||
addTracking(cmd, "CREATED", null, "DRAFT", req.getIssuerId(), req.getIssuerName(), "创建指令");
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下发指令(DRAFT → ISSUED)
|
||||
*/
|
||||
@Transactional
|
||||
public DispatchCommand issue(Long commandId) {
|
||||
DispatchCommand cmd = getCommandOrThrow(commandId);
|
||||
assertStatus(cmd, "DRAFT");
|
||||
|
||||
cmd.setStatus("ISSUED");
|
||||
cmd.setIssuedAt(LocalDateTime.now());
|
||||
commandMapper.updateById(cmd);
|
||||
|
||||
addTracking(cmd, "ISSUED", "DRAFT", "ISSUED", cmd.getIssuerId(), cmd.getIssuerName(), "下发指令");
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收确认(ISSUED → RECEIVED)
|
||||
*/
|
||||
@Transactional
|
||||
public DispatchCommand receive(Long commandId, Long receiverId, String receiverName) {
|
||||
DispatchCommand cmd = getCommandOrThrow(commandId);
|
||||
assertStatus(cmd, "ISSUED");
|
||||
|
||||
cmd.setStatus("RECEIVED");
|
||||
cmd.setReceivedAt(LocalDateTime.now());
|
||||
if (receiverId != null) {
|
||||
cmd.setReceiverId(receiverId);
|
||||
cmd.setReceiverName(receiverName);
|
||||
}
|
||||
commandMapper.updateById(cmd);
|
||||
|
||||
addExecution(cmd, receiverId, receiverName, "RECEIVE", "确认接收指令", null, 0);
|
||||
addTracking(cmd, "RECEIVED", "ISSUED", "RECEIVED", receiverId, receiverName, "接收确认");
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始执行(RECEIVED → EXECUTING)
|
||||
*/
|
||||
@Transactional
|
||||
public DispatchCommand startExecution(Long commandId, Long executorId, String executorName) {
|
||||
DispatchCommand cmd = getCommandOrThrow(commandId);
|
||||
assertStatus(cmd, "RECEIVED");
|
||||
|
||||
cmd.setStatus("EXECUTING");
|
||||
cmd.setExecutedAt(LocalDateTime.now());
|
||||
commandMapper.updateById(cmd);
|
||||
|
||||
addExecution(cmd, executorId, executorName, "START", "开始执行", null, 10);
|
||||
addTracking(cmd, "EXECUTING", "RECEIVED", "EXECUTING", executorId, executorName, "开始执行");
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行进度反馈(EXECUTING stays)
|
||||
*/
|
||||
@Transactional
|
||||
public DispatchCommand reportProgress(Long commandId, Long executorId, String executorName,
|
||||
String description, int progress) {
|
||||
DispatchCommand cmd = getCommandOrThrow(commandId);
|
||||
assertStatus(cmd, "EXECUTING");
|
||||
|
||||
addExecution(cmd, executorId, executorName, "PROGRESS", description, null, progress);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成指令(EXECUTING → COMPLETED)
|
||||
*/
|
||||
@Transactional
|
||||
public DispatchCommand complete(Long commandId, Long executorId, String executorName, String result) {
|
||||
DispatchCommand cmd = getCommandOrThrow(commandId);
|
||||
assertStatus(cmd, "EXECUTING");
|
||||
|
||||
cmd.setStatus("COMPLETED");
|
||||
cmd.setCompletedAt(LocalDateTime.now());
|
||||
cmd.setExecuteResult(result);
|
||||
commandMapper.updateById(cmd);
|
||||
|
||||
addExecution(cmd, executorId, executorName, "COMPLETE", result, null, 100);
|
||||
addTracking(cmd, "COMPLETED", "EXECUTING", "COMPLETED", executorId, executorName, "执行完成: " + result);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* 驳回指令(任意活跃状态 → REJECTED)
|
||||
*/
|
||||
@Transactional
|
||||
public DispatchCommand reject(Long commandId, Long operatorId, String operatorName, String reason) {
|
||||
DispatchCommand cmd = getCommandOrThrow(commandId);
|
||||
String fromStatus = cmd.getStatus();
|
||||
if ("COMPLETED".equals(fromStatus) || "CANCELLED".equals(fromStatus) || "REJECTED".equals(fromStatus)) {
|
||||
throw new BusinessException("当前状态不允许驳回: " + fromStatus);
|
||||
}
|
||||
|
||||
cmd.setStatus("REJECTED");
|
||||
cmd.setRejectedAt(LocalDateTime.now());
|
||||
cmd.setRejectReason(reason);
|
||||
commandMapper.updateById(cmd);
|
||||
|
||||
addExecution(cmd, operatorId, operatorName, "REJECT", reason, null, 0);
|
||||
addTracking(cmd, "REJECTED", fromStatus, "REJECTED", operatorId, operatorName, "驳回: " + reason);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消指令
|
||||
*/
|
||||
@Transactional
|
||||
public DispatchCommand cancel(Long commandId, Long operatorId, String operatorName, String reason) {
|
||||
DispatchCommand cmd = getCommandOrThrow(commandId);
|
||||
String fromStatus = cmd.getStatus();
|
||||
if ("COMPLETED".equals(fromStatus) || "CANCELLED".equals(fromStatus)) {
|
||||
throw new BusinessException("当前状态不允许取消: " + fromStatus);
|
||||
}
|
||||
|
||||
cmd.setStatus("CANCELLED");
|
||||
commandMapper.updateById(cmd);
|
||||
|
||||
addTracking(cmd, "CANCELLED", fromStatus, "CANCELLED", operatorId, operatorName,
|
||||
"取消指令" + (reason != null ? ": " + reason : ""));
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指令详情
|
||||
*/
|
||||
public DispatchCommand getById(Long id) {
|
||||
return getCommandOrThrow(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据编号获取
|
||||
*/
|
||||
public DispatchCommand getByCommandNo(String commandNo) {
|
||||
DispatchCommand cmd = commandMapper.selectOne(
|
||||
new LambdaQueryWrapper<DispatchCommand>()
|
||||
.eq(DispatchCommand::getCommandNo, commandNo));
|
||||
if (cmd == null) {
|
||||
throw new BusinessException("指令不存在: " + commandNo);
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
// ========== Internal ==========
|
||||
|
||||
private DispatchCommand getCommandOrThrow(Long id) {
|
||||
DispatchCommand cmd = commandMapper.selectById(id);
|
||||
if (cmd == null) {
|
||||
throw new BusinessException("指令不存在: " + id);
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private void assertStatus(DispatchCommand cmd, String expected) {
|
||||
if (!expected.equals(cmd.getStatus())) {
|
||||
throw new BusinessException("指令状态不正确,期望: " + expected + ",实际: " + cmd.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
private String generateCommandNo() {
|
||||
return "CMD-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
|
||||
+ "-" + (int) (Math.random() * 9000 + 1000);
|
||||
}
|
||||
|
||||
private void addTracking(DispatchCommand cmd, String stage, String fromStatus, String toStatus,
|
||||
Long operatorId, String operatorName, String desc) {
|
||||
CommandTracking t = new CommandTracking();
|
||||
t.setCommandId(cmd.getId());
|
||||
t.setCommandNo(cmd.getCommandNo());
|
||||
t.setStage(stage);
|
||||
t.setFromStatus(fromStatus);
|
||||
t.setToStatus(toStatus);
|
||||
t.setOperatorId(operatorId);
|
||||
t.setOperatorName(operatorName);
|
||||
t.setActionDesc(desc);
|
||||
trackingMapper.insert(t);
|
||||
}
|
||||
|
||||
private void addExecution(DispatchCommand cmd, Long executorId, String executorName,
|
||||
String action, String description, String attachments, int progress) {
|
||||
CommandExecutionRecord r = new CommandExecutionRecord();
|
||||
r.setCommandId(cmd.getId());
|
||||
r.setCommandNo(cmd.getCommandNo());
|
||||
r.setExecutorId(executorId);
|
||||
r.setExecutorName(executorName);
|
||||
r.setAction(action);
|
||||
r.setDescription(description);
|
||||
r.setAttachments(attachments);
|
||||
r.setProgress(progress);
|
||||
executionMapper.insert(r);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.water.dispatch.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.water.dispatch.entity.CommandExecutionRecord;
|
||||
import com.water.dispatch.entity.CommandTracking;
|
||||
import com.water.dispatch.mapper.CommandExecutionRecordMapper;
|
||||
import com.water.dispatch.mapper.CommandTrackingMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 指令全过程追踪服务 - 时间线
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CommandTrackingService {
|
||||
|
||||
private final CommandTrackingMapper trackingMapper;
|
||||
private final CommandExecutionRecordMapper executionMapper;
|
||||
|
||||
/**
|
||||
* 获取指令追踪时间线
|
||||
*/
|
||||
public List<CommandTracking> getTimeline(Long commandId) {
|
||||
return trackingMapper.selectList(
|
||||
new LambdaQueryWrapper<CommandTracking>()
|
||||
.eq(CommandTracking::getCommandId, commandId)
|
||||
.orderByAsc(CommandTracking::getCreatedAt));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指令编号获取追踪时间线
|
||||
*/
|
||||
public List<CommandTracking> getTimelineByCommandNo(String commandNo) {
|
||||
return trackingMapper.selectList(
|
||||
new LambdaQueryWrapper<CommandTracking>()
|
||||
.eq(CommandTracking::getCommandNo, commandNo)
|
||||
.orderByAsc(CommandTracking::getCreatedAt));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取执行记录列表
|
||||
*/
|
||||
public List<CommandExecutionRecord> getExecutionRecords(Long commandId) {
|
||||
return executionMapper.selectList(
|
||||
new LambdaQueryWrapper<CommandExecutionRecord>()
|
||||
.eq(CommandExecutionRecord::getCommandId, commandId)
|
||||
.orderByAsc(CommandExecutionRecord::getCreatedAt));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取完整时间线(tracking + execution records 合并)
|
||||
*/
|
||||
public List<Map<String, Object>> getFullTimeline(Long commandId) {
|
||||
List<CommandTracking> trackings = getTimeline(commandId);
|
||||
List<CommandExecutionRecord> records = getExecutionRecords(commandId);
|
||||
|
||||
List<Map<String, Object>> timeline = new ArrayList<>();
|
||||
|
||||
for (CommandTracking t : trackings) {
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
item.put("type", "TRACKING");
|
||||
item.put("id", t.getId());
|
||||
item.put("time", t.getCreatedAt());
|
||||
item.put("stage", t.getStage());
|
||||
item.put("operatorId", t.getOperatorId());
|
||||
item.put("operatorName", t.getOperatorName());
|
||||
item.put("description", t.getActionDesc());
|
||||
item.put("fromStatus", t.getFromStatus());
|
||||
item.put("toStatus", t.getToStatus());
|
||||
timeline.add(item);
|
||||
}
|
||||
|
||||
for (CommandExecutionRecord r : records) {
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
item.put("type", "EXECUTION");
|
||||
item.put("id", r.getId());
|
||||
item.put("time", r.getCreatedAt());
|
||||
item.put("action", r.getAction());
|
||||
item.put("executorId", r.getExecutorId());
|
||||
item.put("executorName", r.getExecutorName());
|
||||
item.put("description", r.getDescription());
|
||||
item.put("progress", r.getProgress());
|
||||
item.put("attachments", r.getAttachments());
|
||||
timeline.add(item);
|
||||
}
|
||||
|
||||
// Sort by time
|
||||
timeline.sort(Comparator.comparing(m -> (java.time.LocalDateTime) m.get("time")));
|
||||
return timeline;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,19 @@
|
||||
package com.water.dispatch.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.water.dispatch.entity.*;
|
||||
import com.water.dispatch.mapper.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.time.*; import java.util.*;
|
||||
@Service @RequiredArgsConstructor
|
||||
|
||||
import java.time.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 调度工作台 - 原有业务(值班/工单/策略/预案)
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DispatchBizService {
|
||||
private final DutyScheduleMapper dutyMapper;
|
||||
private final DispatchCommandMapper cmdMapper;
|
||||
@@ -16,66 +24,84 @@ public class DispatchBizService {
|
||||
|
||||
public List<DutySchedule> getTodayDuty() {
|
||||
return dutyMapper.selectList(new LambdaQueryWrapper<DutySchedule>()
|
||||
.eq(DutySchedule::getDutyDate, LocalDate.now()));
|
||||
.eq(DutySchedule::getDutyDate, LocalDate.now()));
|
||||
}
|
||||
public Map<String,Object> createCommand(Map<String,Object> req) {
|
||||
|
||||
public Map<String, Object> createCommand(Map<String, Object> req) {
|
||||
DispatchCommand c = new DispatchCommand();
|
||||
c.setCmdNo("CMD-" + System.currentTimeMillis());
|
||||
c.setTitle((String)req.get("title")); c.setContent((String)req.get("content"));
|
||||
c.setType((String)req.getOrDefault("type","常规")); c.setStatus(0);
|
||||
c.setCommandNo("CMD-" + System.currentTimeMillis());
|
||||
c.setTitle((String) req.get("title"));
|
||||
c.setContent((String) req.get("content"));
|
||||
c.setCommandType((String) req.getOrDefault("commandType", "NORMAL"));
|
||||
c.setPriority((String) req.getOrDefault("priority", "MEDIUM"));
|
||||
c.setStatus("DRAFT");
|
||||
cmdMapper.insert(c);
|
||||
return Map.of("id",c.getId(),"cmdNo",c.getCmdNo());
|
||||
return Map.of("id", c.getId(), "commandNo", c.getCommandNo());
|
||||
}
|
||||
public Map<String,Object> issueCommand(String cmdNo) {
|
||||
|
||||
public Map<String, Object> issueCommand(String commandNo) {
|
||||
DispatchCommand c = cmdMapper.selectOne(new LambdaQueryWrapper<DispatchCommand>()
|
||||
.eq(DispatchCommand::getCmdNo, cmdNo));
|
||||
if(c==null) throw new RuntimeException("指令不存在");
|
||||
c.setStatus(1); c.setIssuedTime(LocalDateTime.now());
|
||||
.eq(DispatchCommand::getCommandNo, commandNo));
|
||||
if (c == null) throw new RuntimeException("指令不存在");
|
||||
c.setStatus("ISSUED");
|
||||
c.setIssuedAt(LocalDateTime.now());
|
||||
cmdMapper.updateById(c);
|
||||
return Map.of("cmdNo",cmdNo,"status",1);
|
||||
return Map.of("commandNo", commandNo, "status", c.getStatus());
|
||||
}
|
||||
public List<DispatchCommand> listCommands(Integer status) {
|
||||
|
||||
public List<DispatchCommand> listCommands(String status) {
|
||||
return cmdMapper.selectList(new LambdaQueryWrapper<DispatchCommand>()
|
||||
.eq(status!=null, DispatchCommand::getStatus, status));
|
||||
.eq(status != null, DispatchCommand::getStatus, status));
|
||||
}
|
||||
public Map<String,Object> createWorkOrder(Map<String,Object> req) {
|
||||
|
||||
public Map<String, Object> createWorkOrder(Map<String, Object> req) {
|
||||
WorkOrder w = new WorkOrder();
|
||||
w.setOrderNo("WO-" + System.currentTimeMillis());
|
||||
w.setTitle((String)req.get("title")); w.setDescription((String)req.get("description"));
|
||||
w.setType((String)req.getOrDefault("type","维修")); w.setStatus(0);
|
||||
w.setPriority((String)req.getOrDefault("priority","中"));
|
||||
w.setTitle((String) req.get("title"));
|
||||
w.setDescription((String) req.get("description"));
|
||||
w.setType((String) req.getOrDefault("type", "维修"));
|
||||
w.setStatus(0);
|
||||
w.setPriority((String) req.getOrDefault("priority", "中"));
|
||||
woMapper.insert(w);
|
||||
return Map.of("id",w.getId(),"orderNo",w.getOrderNo());
|
||||
return Map.of("id", w.getId(), "orderNo", w.getOrderNo());
|
||||
}
|
||||
public Map<String,Object> updateWorkOrderStatus(Long id, int status) {
|
||||
|
||||
public Map<String, Object> updateWorkOrderStatus(Long id, int status) {
|
||||
WorkOrder w = woMapper.selectById(id);
|
||||
if(w==null) throw new RuntimeException("工单不存在");
|
||||
if (w == null) throw new RuntimeException("工单不存在");
|
||||
w.setStatus(status);
|
||||
if(status==2) w.setCompletedAt(LocalDateTime.now());
|
||||
if (status == 2) w.setCompletedAt(LocalDateTime.now());
|
||||
woMapper.updateById(w);
|
||||
return Map.of("id",id,"status",status);
|
||||
return Map.of("id", id, "status", status);
|
||||
}
|
||||
|
||||
public void addDutyLog(Long scheduleId, Long userId, String type, String content) {
|
||||
DutyLog l = new DutyLog();
|
||||
l.setScheduleId(scheduleId); l.setUserId(userId);
|
||||
l.setLogType(type); l.setContent(content);
|
||||
l.setScheduleId(scheduleId);
|
||||
l.setUserId(userId);
|
||||
l.setLogType(type);
|
||||
l.setContent(content);
|
||||
logMapper.insert(l);
|
||||
}
|
||||
|
||||
public List<DutyLog> getDutyLogs(Long scheduleId) {
|
||||
return logMapper.selectList(new LambdaQueryWrapper<DutyLog>()
|
||||
.eq(DutyLog::getScheduleId, scheduleId));
|
||||
.eq(DutyLog::getScheduleId, scheduleId));
|
||||
}
|
||||
|
||||
public List<DispatchStrategy> listStrategies(String type) {
|
||||
return stratMapper.selectList(new LambdaQueryWrapper<DispatchStrategy>()
|
||||
.eq(type!=null, DispatchStrategy::getType, type));
|
||||
.eq(type != null, DispatchStrategy::getType, type));
|
||||
}
|
||||
|
||||
public List<EmergencyPlan> listPlans(String type) {
|
||||
return planMapper.selectList(new LambdaQueryWrapper<EmergencyPlan>()
|
||||
.eq(type!=null, EmergencyPlan::getType, type));
|
||||
.eq(type != null, EmergencyPlan::getType, type));
|
||||
}
|
||||
public Map<String,Object> simulateEmergency(String type, double lng, double lat) {
|
||||
return Map.of("type",type,"lng",lng,"lat",lat,
|
||||
"affectedArea","半径500米","affectedUsers",120,
|
||||
"estimatedDuration","4小时");
|
||||
|
||||
public Map<String, Object> simulateEmergency(String type, double lng, double lat) {
|
||||
return Map.of("type", type, "lng", lng, "lat", lat,
|
||||
"affectedArea", "半径500米", "affectedUsers", 120,
|
||||
"estimatedDuration", "4小时");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
-- V2: 调度指令全生命周期管理
|
||||
-- 新增/重建表,支持 创建→下发→接收确认→执行反馈→完成归档 全流程
|
||||
|
||||
-- 1. 调度指令主表(增强)
|
||||
CREATE TABLE IF NOT EXISTS disp_dispatch_command (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
command_no VARCHAR(64) NOT NULL UNIQUE,
|
||||
title VARCHAR(200) NOT NULL,
|
||||
content TEXT,
|
||||
command_type VARCHAR(30) NOT NULL DEFAULT 'NORMAL', -- NORMAL / EMERGENCY / MAINTENANCE
|
||||
priority VARCHAR(20) NOT NULL DEFAULT 'MEDIUM', -- LOW / MEDIUM / HIGH / URGENT
|
||||
status VARCHAR(30) NOT NULL DEFAULT 'DRAFT', -- DRAFT / ISSUED / RECEIVED / EXECUTING / COMPLETED / REJECTED / CANCELLED
|
||||
issuer_id BIGINT,
|
||||
issuer_name VARCHAR(64),
|
||||
receiver_id BIGINT,
|
||||
receiver_name VARCHAR(64),
|
||||
facility_id BIGINT,
|
||||
deadline TIMESTAMP,
|
||||
issued_at TIMESTAMP,
|
||||
received_at TIMESTAMP,
|
||||
executed_at TIMESTAMP,
|
||||
completed_at TIMESTAMP,
|
||||
rejected_at TIMESTAMP,
|
||||
reject_reason TEXT,
|
||||
execute_result TEXT,
|
||||
remark TEXT,
|
||||
deleted INT DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_cmd_status ON disp_dispatch_command(status);
|
||||
CREATE INDEX idx_cmd_type ON disp_dispatch_command(command_type);
|
||||
CREATE INDEX idx_cmd_priority ON disp_dispatch_command(priority);
|
||||
CREATE INDEX idx_cmd_issuer ON disp_dispatch_command(issuer_id);
|
||||
CREATE INDEX idx_cmd_receiver ON disp_dispatch_command(receiver_id);
|
||||
CREATE INDEX idx_cmd_deadline ON disp_dispatch_command(deadline);
|
||||
CREATE INDEX idx_cmd_created_at ON disp_dispatch_command(created_at);
|
||||
|
||||
-- 2. 执行记录表
|
||||
CREATE TABLE IF NOT EXISTS disp_command_execution_record (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
command_id BIGINT NOT NULL,
|
||||
command_no VARCHAR(64),
|
||||
executor_id BIGINT NOT NULL,
|
||||
executor_name VARCHAR(64),
|
||||
action VARCHAR(30) NOT NULL, -- RECEIVE / START / PROGRESS / COMPLETE / REJECT
|
||||
description TEXT,
|
||||
attachments TEXT, -- JSON array of file URLs
|
||||
progress INT DEFAULT 0, -- 0-100
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_exec_cmd_id ON disp_command_execution_record(command_id);
|
||||
CREATE INDEX idx_exec_cmd_no ON disp_command_execution_record(command_no);
|
||||
|
||||
-- 3. 过程追踪表(时间线)
|
||||
CREATE TABLE IF NOT EXISTS disp_command_tracking (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
command_id BIGINT NOT NULL,
|
||||
command_no VARCHAR(64),
|
||||
stage VARCHAR(30) NOT NULL, -- CREATED / ISSUED / RECEIVED / EXECUTING / COMPLETED / REJECTED / CANCELLED
|
||||
operator_id BIGINT,
|
||||
operator_name VARCHAR(64),
|
||||
action_desc VARCHAR(500),
|
||||
from_status VARCHAR(30),
|
||||
to_status VARCHAR(30),
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_track_cmd_id ON disp_command_tracking(command_id);
|
||||
CREATE INDEX idx_track_cmd_no ON disp_command_tracking(command_no);
|
||||
CREATE INDEX idx_track_stage ON disp_command_tracking(stage);
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
package com.water.dispatch.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.water.common.core.exception.BusinessException;
|
||||
import com.water.dispatch.entity.CommandExecutionRecord;
|
||||
import com.water.dispatch.entity.CommandTracking;
|
||||
import com.water.dispatch.entity.DispatchCommand;
|
||||
import com.water.dispatch.entity.dto.CommandCreateRequest;
|
||||
import com.water.dispatch.mapper.CommandExecutionRecordMapper;
|
||||
import com.water.dispatch.mapper.CommandTrackingMapper;
|
||||
import com.water.dispatch.mapper.DispatchCommandMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CommandLifecycleServiceTest {
|
||||
|
||||
@Mock
|
||||
private DispatchCommandMapper commandMapper;
|
||||
@Mock
|
||||
private CommandExecutionRecordMapper executionMapper;
|
||||
@Mock
|
||||
private CommandTrackingMapper trackingMapper;
|
||||
|
||||
@InjectMocks
|
||||
private CommandLifecycleService lifecycleService;
|
||||
|
||||
@Test
|
||||
void testCreateCommand() {
|
||||
when(commandMapper.insert(any())).thenReturn(1);
|
||||
when(trackingMapper.insert(any())).thenReturn(1);
|
||||
|
||||
CommandCreateRequest req = new CommandCreateRequest();
|
||||
req.setTitle("测试指令");
|
||||
req.setContent("测试内容");
|
||||
req.setCommandType("NORMAL");
|
||||
req.setPriority("HIGH");
|
||||
req.setIssuerId(1L);
|
||||
req.setIssuerName("张三");
|
||||
|
||||
DispatchCommand result = lifecycleService.create(req);
|
||||
|
||||
assertNotNull(result.getCommandNo());
|
||||
assertEquals("DRAFT", result.getStatus());
|
||||
assertEquals("测试指令", result.getTitle());
|
||||
assertEquals("NORMAL", result.getCommandType());
|
||||
assertEquals("HIGH", result.getPriority());
|
||||
verify(commandMapper).insert(any());
|
||||
verify(trackingMapper).insert(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIssueCommand() {
|
||||
DispatchCommand cmd = buildCommand(1L, "DRAFT");
|
||||
when(commandMapper.selectById(1L)).thenReturn(cmd);
|
||||
when(commandMapper.updateById(any())).thenReturn(1);
|
||||
when(trackingMapper.insert(any())).thenReturn(1);
|
||||
|
||||
DispatchCommand result = lifecycleService.issue(1L);
|
||||
|
||||
assertEquals("ISSUED", result.getStatus());
|
||||
assertNotNull(result.getIssuedAt());
|
||||
verify(commandMapper).updateById(any());
|
||||
verify(trackingMapper).insert(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIssueCommandWrongStatus() {
|
||||
DispatchCommand cmd = buildCommand(1L, "ISSUED");
|
||||
when(commandMapper.selectById(1L)).thenReturn(cmd);
|
||||
|
||||
assertThrows(BusinessException.class, () -> lifecycleService.issue(1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReceiveCommand() {
|
||||
DispatchCommand cmd = buildCommand(1L, "ISSUED");
|
||||
when(commandMapper.selectById(1L)).thenReturn(cmd);
|
||||
when(commandMapper.updateById(any())).thenReturn(1);
|
||||
when(executionMapper.insert(any())).thenReturn(1);
|
||||
when(trackingMapper.insert(any())).thenReturn(1);
|
||||
|
||||
DispatchCommand result = lifecycleService.receive(1L, 2L, "李四");
|
||||
|
||||
assertEquals("RECEIVED", result.getStatus());
|
||||
assertNotNull(result.getReceivedAt());
|
||||
assertEquals(2L, result.getReceiverId());
|
||||
|
||||
ArgumentCaptor<CommandExecutionRecord> execCaptor = ArgumentCaptor.forClass(CommandExecutionRecord.class);
|
||||
verify(executionMapper).insert(execCaptor.capture());
|
||||
assertEquals("RECEIVE", execCaptor.getValue().getAction());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStartExecution() {
|
||||
DispatchCommand cmd = buildCommand(1L, "RECEIVED");
|
||||
when(commandMapper.selectById(1L)).thenReturn(cmd);
|
||||
when(commandMapper.updateById(any())).thenReturn(1);
|
||||
when(executionMapper.insert(any())).thenReturn(1);
|
||||
when(trackingMapper.insert(any())).thenReturn(1);
|
||||
|
||||
DispatchCommand result = lifecycleService.startExecution(1L, 2L, "李四");
|
||||
|
||||
assertEquals("EXECUTING", result.getStatus());
|
||||
assertNotNull(result.getExecutedAt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCompleteCommand() {
|
||||
DispatchCommand cmd = buildCommand(1L, "EXECUTING");
|
||||
when(commandMapper.selectById(1L)).thenReturn(cmd);
|
||||
when(commandMapper.updateById(any())).thenReturn(1);
|
||||
when(executionMapper.insert(any())).thenReturn(1);
|
||||
when(trackingMapper.insert(any())).thenReturn(1);
|
||||
|
||||
DispatchCommand result = lifecycleService.complete(1L, 2L, "李四", "已完成管道修复");
|
||||
|
||||
assertEquals("COMPLETED", result.getStatus());
|
||||
assertNotNull(result.getCompletedAt());
|
||||
assertEquals("已完成管道修复", result.getExecuteResult());
|
||||
|
||||
ArgumentCaptor<CommandExecutionRecord> execCaptor = ArgumentCaptor.forClass(CommandExecutionRecord.class);
|
||||
verify(executionMapper).insert(execCaptor.capture());
|
||||
assertEquals(100, execCaptor.getValue().getProgress());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRejectCommand() {
|
||||
DispatchCommand cmd = buildCommand(1L, "ISSUED");
|
||||
when(commandMapper.selectById(1L)).thenReturn(cmd);
|
||||
when(commandMapper.updateById(any())).thenReturn(1);
|
||||
when(executionMapper.insert(any())).thenReturn(1);
|
||||
when(trackingMapper.insert(any())).thenReturn(1);
|
||||
|
||||
DispatchCommand result = lifecycleService.reject(1L, 2L, "李四", "信息不足,无法执行");
|
||||
|
||||
assertEquals("REJECTED", result.getStatus());
|
||||
assertEquals("信息不足,无法执行", result.getRejectReason());
|
||||
assertNotNull(result.getRejectedAt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRejectCompletedCommand() {
|
||||
DispatchCommand cmd = buildCommand(1L, "COMPLETED");
|
||||
when(commandMapper.selectById(1L)).thenReturn(cmd);
|
||||
|
||||
assertThrows(BusinessException.class,
|
||||
() -> lifecycleService.reject(1L, 2L, "李四", "原因"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCancelCommand() {
|
||||
DispatchCommand cmd = buildCommand(1L, "DRAFT");
|
||||
when(commandMapper.selectById(1L)).thenReturn(cmd);
|
||||
when(commandMapper.updateById(any())).thenReturn(1);
|
||||
when(trackingMapper.insert(any())).thenReturn(1);
|
||||
|
||||
DispatchCommand result = lifecycleService.cancel(1L, 1L, "张三", "计划变更");
|
||||
|
||||
assertEquals("CANCELLED", result.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCancelAlreadyCancelledCommand() {
|
||||
DispatchCommand cmd = buildCommand(1L, "CANCELLED");
|
||||
when(commandMapper.selectById(1L)).thenReturn(cmd);
|
||||
|
||||
assertThrows(BusinessException.class,
|
||||
() -> lifecycleService.cancel(1L, 1L, "张三", "reason"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCommandNotFound() {
|
||||
when(commandMapper.selectById(999L)).thenReturn(null);
|
||||
assertThrows(BusinessException.class, () -> lifecycleService.issue(999L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReportProgress() {
|
||||
DispatchCommand cmd = buildCommand(1L, "EXECUTING");
|
||||
when(commandMapper.selectById(1L)).thenReturn(cmd);
|
||||
when(executionMapper.insert(any())).thenReturn(1);
|
||||
|
||||
DispatchCommand result = lifecycleService.reportProgress(1L, 2L, "李四", "完成50%", 50);
|
||||
|
||||
assertEquals("EXECUTING", result.getStatus()); // status unchanged
|
||||
ArgumentCaptor<CommandExecutionRecord> captor = ArgumentCaptor.forClass(CommandExecutionRecord.class);
|
||||
verify(executionMapper).insert(captor.capture());
|
||||
assertEquals("PROGRESS", captor.getValue().getAction());
|
||||
assertEquals(50, captor.getValue().getProgress());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFullLifecycle() {
|
||||
// Create
|
||||
when(commandMapper.insert(any())).thenReturn(1);
|
||||
when(trackingMapper.insert(any())).thenReturn(1);
|
||||
when(executionMapper.insert(any())).thenReturn(1);
|
||||
|
||||
CommandCreateRequest req = new CommandCreateRequest();
|
||||
req.setTitle("全流程测试");
|
||||
req.setCommandType("EMERGENCY");
|
||||
req.setPriority("URGENT");
|
||||
req.setIssuerId(1L);
|
||||
req.setIssuerName("张三");
|
||||
|
||||
DispatchCommand cmd = lifecycleService.create(req);
|
||||
cmd.setId(100L);
|
||||
assertEquals("DRAFT", cmd.getStatus());
|
||||
|
||||
// Issue
|
||||
when(commandMapper.selectById(100L)).thenReturn(cmd);
|
||||
when(commandMapper.updateById(any())).thenReturn(1);
|
||||
lifecycleService.issue(100L);
|
||||
assertEquals("ISSUED", cmd.getStatus());
|
||||
|
||||
// Receive
|
||||
lifecycleService.receive(100L, 2L, "李四");
|
||||
assertEquals("RECEIVED", cmd.getStatus());
|
||||
|
||||
// Start
|
||||
lifecycleService.startExecution(100L, 2L, "李四");
|
||||
assertEquals("EXECUTING", cmd.getStatus());
|
||||
|
||||
// Complete
|
||||
lifecycleService.complete(100L, 2L, "李四", "抢修完成");
|
||||
assertEquals("COMPLETED", cmd.getStatus());
|
||||
}
|
||||
|
||||
private DispatchCommand buildCommand(Long id, String status) {
|
||||
DispatchCommand cmd = new DispatchCommand();
|
||||
cmd.setId(id);
|
||||
cmd.setCommandNo("CMD-TEST-001");
|
||||
cmd.setTitle("测试指令");
|
||||
cmd.setStatus(status);
|
||||
cmd.setIssuerId(1L);
|
||||
cmd.setIssuerName("张三");
|
||||
return cmd;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.water.dispatch.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.water.dispatch.entity.CommandExecutionRecord;
|
||||
import com.water.dispatch.entity.CommandTracking;
|
||||
import com.water.dispatch.mapper.CommandExecutionRecordMapper;
|
||||
import com.water.dispatch.mapper.CommandTrackingMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CommandTrackingServiceTest {
|
||||
|
||||
@Mock
|
||||
private CommandTrackingMapper trackingMapper;
|
||||
@Mock
|
||||
private CommandExecutionRecordMapper executionMapper;
|
||||
|
||||
@InjectMocks
|
||||
private CommandTrackingService trackingService;
|
||||
|
||||
@Test
|
||||
void testGetTimeline() {
|
||||
CommandTracking t1 = new CommandTracking();
|
||||
t1.setId(1L);
|
||||
t1.setCommandId(100L);
|
||||
t1.setStage("CREATED");
|
||||
t1.setCreatedAt(LocalDateTime.of(2026, 6, 14, 10, 0));
|
||||
|
||||
CommandTracking t2 = new CommandTracking();
|
||||
t2.setId(2L);
|
||||
t2.setCommandId(100L);
|
||||
t2.setStage("ISSUED");
|
||||
t2.setCreatedAt(LocalDateTime.of(2026, 6, 14, 10, 5));
|
||||
|
||||
when(trackingMapper.selectList(any(LambdaQueryWrapper.class)))
|
||||
.thenReturn(List.of(t1, t2));
|
||||
|
||||
List<CommandTracking> result = trackingService.getTimeline(100L);
|
||||
assertEquals(2, result.size());
|
||||
assertEquals("CREATED", result.get(0).getStage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetExecutionRecords() {
|
||||
CommandExecutionRecord r1 = new CommandExecutionRecord();
|
||||
r1.setId(1L);
|
||||
r1.setCommandId(100L);
|
||||
r1.setAction("RECEIVE");
|
||||
r1.setProgress(0);
|
||||
|
||||
when(executionMapper.selectList(any(LambdaQueryWrapper.class)))
|
||||
.thenReturn(List.of(r1));
|
||||
|
||||
List<CommandExecutionRecord> result = trackingService.getExecutionRecords(100L);
|
||||
assertEquals(1, result.size());
|
||||
assertEquals("RECEIVE", result.get(0).getAction());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFullTimeline() {
|
||||
CommandTracking t = new CommandTracking();
|
||||
t.setId(1L);
|
||||
t.setCommandId(100L);
|
||||
t.setStage("CREATED");
|
||||
t.setOperatorName("张三");
|
||||
t.setActionDesc("创建指令");
|
||||
t.setCreatedAt(LocalDateTime.of(2026, 6, 14, 10, 0));
|
||||
|
||||
CommandExecutionRecord r = new CommandExecutionRecord();
|
||||
r.setId(1L);
|
||||
r.setCommandId(100L);
|
||||
r.setAction("RECEIVE");
|
||||
r.setExecutorName("李四");
|
||||
r.setDescription("确认接收");
|
||||
r.setProgress(0);
|
||||
r.setCreatedAt(LocalDateTime.of(2026, 6, 14, 10, 5));
|
||||
|
||||
when(trackingMapper.selectList(any(LambdaQueryWrapper.class)))
|
||||
.thenReturn(List.of(t));
|
||||
when(executionMapper.selectList(any(LambdaQueryWrapper.class)))
|
||||
.thenReturn(List.of(r));
|
||||
|
||||
List<Map<String, Object>> timeline = trackingService.getFullTimeline(100L);
|
||||
assertEquals(2, timeline.size());
|
||||
assertEquals("TRACKING", timeline.get(0).get("type"));
|
||||
assertEquals("EXECUTION", timeline.get(1).get("type"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetTimelineByCommandNo() {
|
||||
CommandTracking t = new CommandTracking();
|
||||
t.setCommandNo("CMD-20260614-001");
|
||||
t.setStage("ISSUED");
|
||||
t.setCreatedAt(LocalDateTime.now());
|
||||
|
||||
when(trackingMapper.selectList(any(LambdaQueryWrapper.class)))
|
||||
.thenReturn(List.of(t));
|
||||
|
||||
List<CommandTracking> result = trackingService.getTimelineByCommandNo("CMD-20260614-001");
|
||||
assertEquals(1, result.size());
|
||||
assertEquals("ISSUED", result.get(0).getStage());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user