diff --git a/wm-patrol/src/main/java/com/water/patrol/controller/PatrolAreaController.java b/wm-patrol/src/main/java/com/water/patrol/controller/PatrolAreaController.java new file mode 100644 index 00000000..c8de7d60 --- /dev/null +++ b/wm-patrol/src/main/java/com/water/patrol/controller/PatrolAreaController.java @@ -0,0 +1,16 @@ +package com.water.patrol.controller; +import com.water.common.core.result.R; import com.water.patrol.entity.PatrolArea; import com.water.patrol.service.PatrolAreaService; +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.math.BigDecimal; import java.util.Map; +@Tag(name="巡检区域") @RestController @RequestMapping("/api/patrol/area") @RequiredArgsConstructor +public class PatrolAreaController { + private final PatrolAreaService svc; + @Operation(summary="创建区域") @PostMapping public R create(@RequestParam String areaName, @RequestParam String areaCode, @RequestParam(required=false) String description, @RequestParam BigDecimal centerLng, @RequestParam BigDecimal centerLat, @RequestParam(required=false) String boundary, @RequestParam(required=false) Double radius) { return R.ok(svc.create(areaName,areaCode,description,centerLng,centerLat,boundary,radius)); } + @Operation(summary="区域列表") @GetMapping public R> list(@RequestParam(required=false) String keyword, @RequestParam(required=false) String status, @RequestParam(defaultValue="1") int page, @RequestParam(defaultValue="20") int size) { return R.ok(svc.list(keyword,status,page,size)); } + @Operation(summary="区域详情") @GetMapping("/{id}") public R detail(@PathVariable Long id) { return R.ok(svc.getDetail(id)); } + @Operation(summary="更新区域") @PutMapping("/{id}") public R update(@PathVariable Long id, @RequestBody PatrolArea patch) { return R.ok(svc.update(id,patch)); } + @Operation(summary="更新状态") @PutMapping("/{id}/status") public R updateStatus(@PathVariable Long id, @RequestParam String status) { svc.updateStatus(id,status); return R.ok("OK"); } + @Operation(summary="删除区域") @DeleteMapping("/{id}") public R delete(@PathVariable Long id) { svc.delete(id); return R.ok("OK"); } + @Operation(summary="区域统计") @GetMapping("/stats") public R> stats() { return R.ok(svc.getStats()); } +} diff --git a/wm-patrol/src/main/java/com/water/patrol/controller/PatrolCoreController.java b/wm-patrol/src/main/java/com/water/patrol/controller/PatrolCoreController.java new file mode 100644 index 00000000..c0501a13 --- /dev/null +++ b/wm-patrol/src/main/java/com/water/patrol/controller/PatrolCoreController.java @@ -0,0 +1,186 @@ +package com.water.patrol.controller; + +import com.water.common.core.result.R; +import com.water.patrol.service.PatrolCoreService; +import com.water.patrol.service.PatrolWoService; +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; + +/** + * 巡检管理核心控制器 - Issue #86 + * PAT-01~PAT-06 + */ +@Tag(name = "巡检管理核心") +@RestController +@RequestMapping("/patrol/core") +@RequiredArgsConstructor +public class PatrolCoreController { + + private final PatrolCoreService coreService; + private final PatrolWoService woService; + + // ========== PAT-01 巡检总览 ========== + + @GetMapping("/overview") + @Operation(summary = "巡检总览数据") + public R> overview() { + return R.ok(coreService.getOverview()); + } + + @GetMapping("/overview/today-tasks") + @Operation(summary = "今日任务") + public R>> todayTasks() { + return R.ok(coreService.getTodayTasks()); + } + + @GetMapping("/overview/recent-issues") + @Operation(summary = "最近问题") + public R>> recentIssues(@RequestParam(defaultValue = "10") int limit) { + return R.ok(coreService.getRecentIssues(limit)); + } + + // ========== PAT-02 任务轨迹 ========== + + @PostMapping("/track/record") + @Operation(summary = "记录GPS轨迹点") + public R> recordTrack(@RequestBody Map req) { + Long taskId = Long.parseLong(String.valueOf(req.get("taskId"))); + Long workerId = req.get("workerId") != null ? Long.parseLong(String.valueOf(req.get("workerId"))) : null; + double lng = ((Number) req.get("lng")).doubleValue(); + double lat = ((Number) req.get("lat")).doubleValue(); + double speed = req.get("speed") != null ? ((Number) req.get("speed")).doubleValue() : 0; + double accuracy = req.get("accuracy") != null ? ((Number) req.get("accuracy")).doubleValue() : 0; + return R.ok(coreService.recordTrackPoint(taskId, workerId, lng, lat, speed, accuracy)); + } + + @GetMapping("/track/{taskId}") + @Operation(summary = "获取任务轨迹") + public R>> getTrack(@PathVariable Long taskId) { + return R.ok(coreService.getTrack(taskId)); + } + + @GetMapping("/track/{taskId}/replay") + @Operation(summary = "轨迹回放") + public R> replayTrack(@PathVariable Long taskId) { + return R.ok(coreService.replayTrack(taskId)); + } + + @GetMapping("/track/{taskId}/stats") + @Operation(summary = "轨迹统计") + public R> trackStats(@PathVariable Long taskId) { + return R.ok(coreService.getTrackStats(taskId)); + } + + // ========== PAT-03 任务台账 ========== + + @GetMapping("/ledger/tasks") + @Operation(summary = "任务台账") + public R> taskLedger( + @RequestParam(required = false) String status, + @RequestParam(required = false) Long workerId, + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int size) { + return R.ok(coreService.getTaskLedger(status, workerId, page, size)); + } + + // ========== PAT-04 设备台账 ========== + + @GetMapping("/ledger/devices") + @Operation(summary = "设备台账") + public R> deviceLedger( + @RequestParam(required = false) String type, + @RequestParam(required = false) String status, + @RequestParam(required = false) String area, + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int size) { + return R.ok(coreService.getDeviceLedger(type, status, area, page, size)); + } + + @PostMapping("/device") + @Operation(summary = "新增设备") + public R> createDevice(@RequestBody Map req) { + return R.ok(coreService.createDevice( + (String) req.get("deviceNo"), + (String) req.get("deviceName"), + (String) req.get("deviceType"), + (String) req.get("location"), + ((Number) req.getOrDefault("lng", 0)).doubleValue(), + ((Number) req.getOrDefault("lat", 0)).doubleValue(), + (String) req.get("area"))); + } + + @PutMapping("/device/{deviceId}/status") + @Operation(summary = "更新设备状态") + public R> updateDeviceStatus(@PathVariable Long deviceId, + @RequestBody Map req) { + return R.ok(coreService.updateDeviceStatus(deviceId, (String) req.get("status"))); + } + + // ========== PAT-05 工单管理 ========== + + @PostMapping("/work-order") + @Operation(summary = "创建巡检工单") + public R> createWo(@RequestBody Map req) { + Long taskId = req.get("taskId") != null ? Long.parseLong(String.valueOf(req.get("taskId"))) : null; + return R.ok(woService.create( + (String) req.get("issueType"), + (String) req.get("description"), + (String) req.get("severity"), + (String) req.get("location"), + req.get("lng") != null ? ((Number) req.get("lng")).doubleValue() : null, + req.get("lat") != null ? ((Number) req.get("lat")).doubleValue() : null, + taskId)); + } + + @GetMapping("/work-order/list") + @Operation(summary = "工单列表") + public R> listWo( + @RequestParam(required = false) String status, + @RequestParam(required = false) String severity, + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int size) { + return R.ok(woService.list(status, severity, page, size)); + } + + @GetMapping("/work-order/{woId}") + @Operation(summary = "工单详情") + public R> woDetail(@PathVariable Long woId) { + Map detail = woService.getDetail(woId); + if (detail == null) { + return R.fail("工单不存在"); + } + return R.ok(detail); + } + + @GetMapping("/work-order/stats") + @Operation(summary = "工单统计") + public R> woStats() { + return R.ok(woService.stats()); + } + + // ========== PAT-06 工单处理 ========== + + @PutMapping("/work-order/{woId}/assign") + @Operation(summary = "分派工单") + public R> assignWo(@PathVariable Long woId, @RequestBody Map req) { + Long assigneeId = Long.parseLong(String.valueOf(req.get("assigneeId"))); + return R.ok(woService.assign(woId, assigneeId, (String) req.get("assigneeName"))); + } + + @PutMapping("/work-order/{woId}/process") + @Operation(summary = "处理工单") + public R> processWo(@PathVariable Long woId, @RequestBody Map req) { + return R.ok(woService.process(woId, (String) req.get("resolution"))); + } + + @PutMapping("/work-order/{woId}/resolve") + @Operation(summary = "解决工单") + public R> resolveWo(@PathVariable Long woId) { + return R.ok(woService.resolve(woId)); + } +} diff --git a/wm-patrol/src/main/java/com/water/patrol/controller/PatrolFormController.java b/wm-patrol/src/main/java/com/water/patrol/controller/PatrolFormController.java new file mode 100644 index 00000000..153fd6c4 --- /dev/null +++ b/wm-patrol/src/main/java/com/water/patrol/controller/PatrolFormController.java @@ -0,0 +1,15 @@ +package com.water.patrol.controller; +import com.water.common.core.result.R; import com.water.patrol.entity.PatrolForm; import com.water.patrol.service.PatrolFormService; +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.Map; +@Tag(name="巡检表单") @RestController @RequestMapping("/api/patrol/form") @RequiredArgsConstructor +public class PatrolFormController { + private final PatrolFormService svc; + @Operation(summary="创建表单") @PostMapping public R create(@RequestBody Map req) { return R.ok(svc.create((String)req.get("formName"),(String)req.get("formCode"),(String)req.get("formType"),(String)req.get("fieldConfig"))); } + @Operation(summary="表单列表") @GetMapping public R> list(@RequestParam(required=false) String keyword, @RequestParam(required=false) String formType, @RequestParam(required=false) String status, @RequestParam(defaultValue="1") int page, @RequestParam(defaultValue="20") int size) { return R.ok(svc.list(keyword,formType,status,page,size)); } + @Operation(summary="表单详情") @GetMapping("/{id}") public R detail(@PathVariable Long id) { return R.ok(svc.getDetail(id)); } + @Operation(summary="更新表单") @PutMapping("/{id}") public R update(@PathVariable Long id, @RequestBody PatrolForm patch) { return R.ok(svc.update(id,patch)); } + @Operation(summary="删除表单") @DeleteMapping("/{id}") public R delete(@PathVariable Long id) { svc.delete(id); return R.ok("OK"); } + @Operation(summary="表单统计") @GetMapping("/stats") public R> stats() { return R.ok(svc.getStats()); } +} diff --git a/wm-patrol/src/main/java/com/water/patrol/controller/PatrolRouteSetupController.java b/wm-patrol/src/main/java/com/water/patrol/controller/PatrolRouteSetupController.java new file mode 100644 index 00000000..19e01942 --- /dev/null +++ b/wm-patrol/src/main/java/com/water/patrol/controller/PatrolRouteSetupController.java @@ -0,0 +1,16 @@ +package com.water.patrol.controller; +import com.water.common.core.result.R; import com.water.patrol.entity.PatrolRouteSetup; import com.water.patrol.service.PatrolRouteSetupService; +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.Map; +@Tag(name="巡检路线设置") @RestController @RequestMapping("/api/patrol/route-setup") @RequiredArgsConstructor +public class PatrolRouteSetupController { + private final PatrolRouteSetupService svc; + @Operation(summary="创建路线") @PostMapping public R create(@RequestBody Map req) { return R.ok(svc.create((String)req.get("routeName"),(String)req.get("routeCode"),((Number)req.get("areaId")).longValue(),(String)req.get("areaName"),(Integer)req.get("checkpointCount"),(String)req.get("checkpointList"),(Double)req.get("totalDistance"),(Integer)req.get("estimatedMinutes"))); } + @Operation(summary="路线列表") @GetMapping public R> list(@RequestParam(required=false) String keyword, @RequestParam(required=false) String status, @RequestParam(required=false) Long areaId, @RequestParam(defaultValue="1") int page, @RequestParam(defaultValue="20") int size) { return R.ok(svc.list(keyword,status,areaId,page,size)); } + @Operation(summary="路线详情") @GetMapping("/{id}") public R detail(@PathVariable Long id) { return R.ok(svc.getDetail(id)); } + @Operation(summary="更新路线") @PutMapping("/{id}") public R update(@PathVariable Long id, @RequestBody PatrolRouteSetup patch) { return R.ok(svc.update(id,patch)); } + @Operation(summary="更新状态") @PutMapping("/{id}/status") public R updateStatus(@PathVariable Long id, @RequestParam String status) { svc.updateStatus(id,status); return R.ok("OK"); } + @Operation(summary="删除路线") @DeleteMapping("/{id}") public R delete(@PathVariable Long id) { svc.delete(id); return R.ok("OK"); } + @Operation(summary="路线统计") @GetMapping("/stats") public R> stats() { return R.ok(svc.getStats()); } +} diff --git a/wm-patrol/src/main/java/com/water/patrol/controller/PatrolTemplateController.java b/wm-patrol/src/main/java/com/water/patrol/controller/PatrolTemplateController.java new file mode 100644 index 00000000..fa1a584a --- /dev/null +++ b/wm-patrol/src/main/java/com/water/patrol/controller/PatrolTemplateController.java @@ -0,0 +1,16 @@ +package com.water.patrol.controller; +import com.water.common.core.result.R; import com.water.patrol.entity.PatrolTemplate; import com.water.patrol.service.PatrolTemplateService; +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.Map; +@Tag(name="巡检模板") @RestController @RequestMapping("/api/patrol/template") @RequiredArgsConstructor +public class PatrolTemplateController { + private final PatrolTemplateService svc; + @Operation(summary="创建模板") @PostMapping public R create(@RequestBody Map req) { return R.ok(svc.create((String)req.get("templateName"),(String)req.get("templateCode"),((Number)req.get("routeId")).longValue(),(String)req.get("routeName"),((Number)req.get("formId")).longValue(),(String)req.get("formName"),(String)req.get("scheduleType"),(String)req.get("scheduleConfig"))); } + @Operation(summary="模板列表") @GetMapping public R> list(@RequestParam(required=false) String keyword, @RequestParam(required=false) String status, @RequestParam(required=false) String scheduleType, @RequestParam(defaultValue="1") int page, @RequestParam(defaultValue="20") int size) { return R.ok(svc.list(keyword,status,scheduleType,page,size)); } + @Operation(summary="模板详情") @GetMapping("/{id}") public R detail(@PathVariable Long id) { return R.ok(svc.getDetail(id)); } + @Operation(summary="更新模板") @PutMapping("/{id}") public R update(@PathVariable Long id, @RequestBody PatrolTemplate patch) { return R.ok(svc.update(id,patch)); } + @Operation(summary="更新状态") @PutMapping("/{id}/status") public R updateStatus(@PathVariable Long id, @RequestParam String status) { svc.updateStatus(id,status); return R.ok("OK"); } + @Operation(summary="删除模板") @DeleteMapping("/{id}") public R delete(@PathVariable Long id) { svc.delete(id); return R.ok("OK"); } + @Operation(summary="模板统计") @GetMapping("/stats") public R> stats() { return R.ok(svc.getStats()); } +} diff --git a/wm-patrol/src/main/java/com/water/patrol/entity/PatrolArea.java b/wm-patrol/src/main/java/com/water/patrol/entity/PatrolArea.java new file mode 100644 index 00000000..a44b9507 --- /dev/null +++ b/wm-patrol/src/main/java/com/water/patrol/entity/PatrolArea.java @@ -0,0 +1,15 @@ +package com.water.patrol.entity; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.math.BigDecimal; +import java.time.LocalDateTime; +@Data @TableName("pat_area") +public class PatrolArea { + @TableId(type = IdType.AUTO) private Long id; + private String areaName; private String areaCode; private String description; + private BigDecimal centerLng; private BigDecimal centerLat; + private String boundary; private Double radius; + private String status; + @TableField(fill = FieldFill.INSERT) private LocalDateTime createdAt; + @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updatedAt; +} diff --git a/wm-patrol/src/main/java/com/water/patrol/entity/PatrolForm.java b/wm-patrol/src/main/java/com/water/patrol/entity/PatrolForm.java new file mode 100644 index 00000000..905af99e --- /dev/null +++ b/wm-patrol/src/main/java/com/water/patrol/entity/PatrolForm.java @@ -0,0 +1,13 @@ +package com.water.patrol.entity; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.time.LocalDateTime; +@Data @TableName("pat_form") +public class PatrolForm { + @TableId(type = IdType.AUTO) private Long id; + private String formName; private String formCode; + private String formType; private String fieldConfig; + private String status; private String remark; + @TableField(fill = FieldFill.INSERT) private LocalDateTime createdAt; + @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updatedAt; +} diff --git a/wm-patrol/src/main/java/com/water/patrol/entity/PatrolRouteSetup.java b/wm-patrol/src/main/java/com/water/patrol/entity/PatrolRouteSetup.java new file mode 100644 index 00000000..f487bc48 --- /dev/null +++ b/wm-patrol/src/main/java/com/water/patrol/entity/PatrolRouteSetup.java @@ -0,0 +1,15 @@ +package com.water.patrol.entity; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.time.LocalDateTime; +@Data @TableName("pat_route_setup") +public class PatrolRouteSetup { + @TableId(type = IdType.AUTO) private Long id; + private String routeName; private String routeCode; + private Long areaId; private String areaName; + private Integer checkpointCount; private String checkpointList; + private Double totalDistance; private Integer estimatedMinutes; + private String status; private String remark; + @TableField(fill = FieldFill.INSERT) private LocalDateTime createdAt; + @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updatedAt; +} diff --git a/wm-patrol/src/main/java/com/water/patrol/entity/PatrolTemplate.java b/wm-patrol/src/main/java/com/water/patrol/entity/PatrolTemplate.java new file mode 100644 index 00000000..93195196 --- /dev/null +++ b/wm-patrol/src/main/java/com/water/patrol/entity/PatrolTemplate.java @@ -0,0 +1,15 @@ +package com.water.patrol.entity; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.time.LocalDateTime; +@Data @TableName("pat_template") +public class PatrolTemplate { + @TableId(type = IdType.AUTO) private Long id; + private String templateName; private String templateCode; + private Long routeId; private String routeName; + private Long formId; private String formName; + private String scheduleType; private String scheduleConfig; + private String status; private String remark; + @TableField(fill = FieldFill.INSERT) private LocalDateTime createdAt; + @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updatedAt; +} diff --git a/wm-patrol/src/main/java/com/water/patrol/mapper/PatrolAreaMapper.java b/wm-patrol/src/main/java/com/water/patrol/mapper/PatrolAreaMapper.java new file mode 100644 index 00000000..15d21db2 --- /dev/null +++ b/wm-patrol/src/main/java/com/water/patrol/mapper/PatrolAreaMapper.java @@ -0,0 +1,5 @@ +package com.water.patrol.mapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.water.patrol.entity.PatrolArea; +import org.apache.ibatis.annotations.Mapper; +@Mapper public interface PatrolAreaMapper extends BaseMapper {} diff --git a/wm-patrol/src/main/java/com/water/patrol/mapper/PatrolFormMapper.java b/wm-patrol/src/main/java/com/water/patrol/mapper/PatrolFormMapper.java new file mode 100644 index 00000000..4f11a591 --- /dev/null +++ b/wm-patrol/src/main/java/com/water/patrol/mapper/PatrolFormMapper.java @@ -0,0 +1,5 @@ +package com.water.patrol.mapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.water.patrol.entity.PatrolForm; +import org.apache.ibatis.annotations.Mapper; +@Mapper public interface PatrolFormMapper extends BaseMapper {} diff --git a/wm-patrol/src/main/java/com/water/patrol/mapper/PatrolRouteSetupMapper.java b/wm-patrol/src/main/java/com/water/patrol/mapper/PatrolRouteSetupMapper.java new file mode 100644 index 00000000..a6bfe1ed --- /dev/null +++ b/wm-patrol/src/main/java/com/water/patrol/mapper/PatrolRouteSetupMapper.java @@ -0,0 +1,5 @@ +package com.water.patrol.mapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.water.patrol.entity.PatrolRouteSetup; +import org.apache.ibatis.annotations.Mapper; +@Mapper public interface PatrolRouteSetupMapper extends BaseMapper {} diff --git a/wm-patrol/src/main/java/com/water/patrol/mapper/PatrolTemplateMapper.java b/wm-patrol/src/main/java/com/water/patrol/mapper/PatrolTemplateMapper.java new file mode 100644 index 00000000..27a70c02 --- /dev/null +++ b/wm-patrol/src/main/java/com/water/patrol/mapper/PatrolTemplateMapper.java @@ -0,0 +1,5 @@ +package com.water.patrol.mapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.water.patrol.entity.PatrolTemplate; +import org.apache.ibatis.annotations.Mapper; +@Mapper public interface PatrolTemplateMapper extends BaseMapper {} diff --git a/wm-patrol/src/main/java/com/water/patrol/service/PatrolAreaService.java b/wm-patrol/src/main/java/com/water/patrol/service/PatrolAreaService.java new file mode 100644 index 00000000..e3eef628 --- /dev/null +++ b/wm-patrol/src/main/java/com/water/patrol/service/PatrolAreaService.java @@ -0,0 +1,53 @@ +package com.water.patrol.service; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.water.patrol.entity.PatrolArea; import com.water.patrol.mapper.PatrolAreaMapper; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import java.math.BigDecimal; import java.util.*; +@Slf4j @Service @RequiredArgsConstructor +public class PatrolAreaService { + private final PatrolAreaMapper mapper; + public PatrolArea create(String areaName, String areaCode, String desc, BigDecimal cLng, BigDecimal cLat, String boundary, Double radius) { + PatrolArea a = new PatrolArea(); a.setAreaName(areaName); a.setAreaCode(areaCode); + a.setDescription(desc); a.setCenterLng(cLng); a.setCenterLat(cLat); + a.setBoundary(boundary); a.setRadius(radius); a.setStatus("active"); + mapper.insert(a); return a; + } + public PatrolArea update(Long id, PatrolArea patch) { + PatrolArea a = mapper.selectById(id); + if (a == null) throw new RuntimeException("区域不存在: "+id); + if (patch.getAreaName() != null) a.setAreaName(patch.getAreaName()); + if (patch.getDescription() != null) a.setDescription(patch.getDescription()); + if (patch.getCenterLng() != null) a.setCenterLng(patch.getCenterLng()); + if (patch.getCenterLat() != null) a.setCenterLat(patch.getCenterLat()); + if (patch.getBoundary() != null) a.setBoundary(patch.getBoundary()); + if (patch.getRadius() != null) a.setRadius(patch.getRadius()); + mapper.updateById(a); return a; + } + public void delete(Long id) { mapper.deleteById(id); } + public PatrolArea getDetail(Long id) { return mapper.selectById(id); } + public Map list(String keyword, String status, int page, int size) { + LambdaQueryWrapper w = new LambdaQueryWrapper<>(); + if (keyword != null && !keyword.isEmpty()) w.and(q -> q.like(PatrolArea::getAreaName, keyword).or().like(PatrolArea::getAreaCode, keyword)); + if (status != null && !status.isEmpty()) w.eq(PatrolArea::getStatus, status); + w.orderByDesc(PatrolArea::getCreatedAt); + Page p = mapper.selectPage(new Page<>(page, size), w); + Map r = new LinkedHashMap<>(); + r.put("records", p.getRecords()); r.put("total", p.getTotal()); + r.put("page", page); r.put("size", size); + return r; + } + public void updateStatus(Long id, String status) { + PatrolArea a = mapper.selectById(id); + if (a == null) throw new RuntimeException("区域不存在: "+id); + a.setStatus(status); mapper.updateById(a); + } + public Map getStats() { + Map r = new LinkedHashMap<>(); + r.put("total", mapper.selectCount(null)); + r.put("active", mapper.selectCount(new LambdaQueryWrapper().eq(PatrolArea::getStatus,"active"))); + r.put("inactive", mapper.selectCount(new LambdaQueryWrapper().eq(PatrolArea::getStatus,"inactive"))); + return r; + } +} diff --git a/wm-patrol/src/main/java/com/water/patrol/service/PatrolCoreService.java b/wm-patrol/src/main/java/com/water/patrol/service/PatrolCoreService.java new file mode 100644 index 00000000..6dc7d172 --- /dev/null +++ b/wm-patrol/src/main/java/com/water/patrol/service/PatrolCoreService.java @@ -0,0 +1,252 @@ +package com.water.patrol.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; + +/** + * 巡检管理核心服务 - Issue #86 + * PAT-01 总览, PAT-02 轨迹, PAT-03 任务台账, PAT-04 设备台账 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class PatrolCoreService { + + private final JdbcTemplate jdbc; + + // ========== PAT-01 巡检总览 ========== + + public Map getOverview() { + LocalDate today = LocalDate.now(); + LocalDate monthStart = today.withDayOfMonth(1); + + long todayTotal = countQuery("SELECT COUNT(*) FROM patrol_task WHERE task_date = ?", today); + long todayCompleted = countQuery("SELECT COUNT(*) FROM patrol_task WHERE task_date = ? AND status = 'completed'", today); + long monthTotal = countQuery("SELECT COUNT(*) FROM patrol_task WHERE task_date >= ?", monthStart); + long monthCompleted = countQuery("SELECT COUNT(*) FROM patrol_task WHERE task_date >= ? AND status = 'completed'", monthStart); + + long pendingIssues = countQuery("SELECT COUNT(*) FROM patrol_work_order WHERE status IN ('pending','assigned','processing')"); + long resolvedIssues = countQuery("SELECT COUNT(*) FROM patrol_work_order WHERE status = 'resolved'"); + + long coverageRate = monthTotal > 0 ? Math.round(monthCompleted * 100.0 / monthTotal) : 0; + + Map overview = new LinkedHashMap<>(); + overview.put("todayTotal", todayTotal); + overview.put("todayCompleted", todayCompleted); + overview.put("monthTotal", monthTotal); + overview.put("monthCompleted", monthCompleted); + overview.put("coverageRate", coverageRate); + overview.put("pendingIssues", pendingIssues); + overview.put("resolvedIssues", resolvedIssues); + return overview; + } + + public List> getTodayTasks() { + return jdbc.queryForList( + "SELECT pt.*, pr.route_name, pr.area FROM patrol_task pt " + + "LEFT JOIN patrol_route pr ON pt.route_id = pr.id " + + "WHERE pt.task_date = CURRENT_DATE ORDER BY pt.plan_start"); + } + + public List> getRecentIssues(int limit) { + return jdbc.queryForList( + "SELECT * FROM patrol_work_order ORDER BY created_at DESC LIMIT ?", limit); + } + + // ========== PAT-02 任务轨迹(GPS记录与回放) ========== + + public Map recordTrackPoint(Long taskId, Long workerId, + double lng, double lat, + double speed, double accuracy) { + jdbc.update( + "INSERT INTO patrol_track_point (task_id, worker_id, lng, lat, speed, accuracy) " + + "VALUES (?,?,?,?,?,?)", + taskId, workerId, lng, lat, speed, accuracy); + Map result = new LinkedHashMap<>(); + result.put("taskId", taskId); + result.put("workerId", workerId); + result.put("lng", lng); + result.put("lat", lat); + result.put("recorded", true); + return result; + } + + public List> getTrack(Long taskId) { + return jdbc.queryForList( + "SELECT * FROM patrol_track_point WHERE task_id = ? ORDER BY recorded_at", taskId); + } + + public Map replayTrack(Long taskId) { + List> points = getTrack(taskId); + Map result = new LinkedHashMap<>(); + result.put("taskId", taskId); + result.put("points", points); + result.put("totalPoints", points.size()); + if (!points.isEmpty()) { + result.put("startTime", points.get(0).get("recorded_at")); + result.put("endTime", points.get(points.size() - 1).get("recorded_at")); + } + return result; + } + + public Map getTrackStats(Long taskId) { + List> points = getTrack(taskId); + Map stats = new LinkedHashMap<>(); + stats.put("taskId", taskId); + stats.put("totalPoints", points.size()); + + if (points.size() < 2) { + stats.put("totalDistance", 0.0); + stats.put("duration", 0); + stats.put("avgSpeed", 0.0); + return stats; + } + + double totalDistance = 0; + for (int i = 1; i < points.size(); i++) { + double lat1 = ((Number) points.get(i - 1).get("lat")).doubleValue(); + double lon1 = ((Number) points.get(i - 1).get("lng")).doubleValue(); + double lat2 = ((Number) points.get(i).get("lat")).doubleValue(); + double lon2 = ((Number) points.get(i).get("lng")).doubleValue(); + totalDistance += haversine(lat1, lon1, lat2, lon2); + } + + double avgSpeed = points.stream() + .filter(p -> p.get("speed") != null) + .mapToDouble(p -> ((Number) p.get("speed")).doubleValue()) + .average().orElse(0); + + stats.put("totalDistance", Math.round(totalDistance * 100.0) / 100.0); + stats.put("avgSpeed", Math.round(avgSpeed * 100.0) / 100.0); + return stats; + } + + // ========== PAT-03 任务台账 ========== + + public Map getTaskLedger(String status, Long workerId, int page, int size) { + StringBuilder sql = new StringBuilder("SELECT * FROM patrol_task WHERE 1=1"); + List params = new ArrayList<>(); + if (status != null && !status.isEmpty()) { + sql.append(" AND status = ?"); + params.add(status); + } + if (workerId != null) { + sql.append(" AND assignee_id = ?"); + params.add(workerId); + } + sql.append(" ORDER BY task_date DESC LIMIT ? OFFSET ?"); + params.add(size); + params.add((page - 1) * size); + + List> records = jdbc.queryForList(sql.toString(), params.toArray()); + + StringBuilder countSql = new StringBuilder("SELECT COUNT(*) FROM patrol_task WHERE 1=1"); + List countParams = new ArrayList<>(); + if (status != null && !status.isEmpty()) { + countSql.append(" AND status = ?"); + countParams.add(status); + } + if (workerId != null) { + countSql.append(" AND assignee_id = ?"); + countParams.add(workerId); + } + long total = countQuery(countSql.toString(), countParams.toArray()); + + Map result = new LinkedHashMap<>(); + result.put("total", total); + result.put("page", page); + result.put("size", size); + result.put("records", records); + return result; + } + + // ========== PAT-04 设备台账 ========== + + public Map getDeviceLedger(String deviceType, String status, String area, + int page, int size) { + StringBuilder sql = new StringBuilder("SELECT * FROM patrol_device WHERE 1=1"); + List params = new ArrayList<>(); + if (deviceType != null && !deviceType.isEmpty()) { + sql.append(" AND device_type = ?"); + params.add(deviceType); + } + if (status != null && !status.isEmpty()) { + sql.append(" AND status = ?"); + params.add(status); + } + if (area != null && !area.isEmpty()) { + sql.append(" AND area = ?"); + params.add(area); + } + sql.append(" ORDER BY created_at DESC LIMIT ? OFFSET ?"); + params.add(size); + params.add((page - 1) * size); + + List> records = jdbc.queryForList(sql.toString(), params.toArray()); + + StringBuilder countSql = new StringBuilder("SELECT COUNT(*) FROM patrol_device WHERE 1=1"); + List countParams = new ArrayList<>(); + if (deviceType != null && !deviceType.isEmpty()) { + countSql.append(" AND device_type = ?"); + countParams.add(deviceType); + } + if (status != null && !status.isEmpty()) { + countSql.append(" AND status = ?"); + countParams.add(status); + } + if (area != null && !area.isEmpty()) { + countSql.append(" AND area = ?"); + countParams.add(area); + } + long total = countQuery(countSql.toString(), countParams.toArray()); + + Map result = new LinkedHashMap<>(); + result.put("total", total); + result.put("page", page); + result.put("size", size); + result.put("records", records); + return result; + } + + public Map createDevice(String deviceNo, String deviceName, String deviceType, + String location, double lng, double lat, String area) { + jdbc.update( + "INSERT INTO patrol_device (device_no, device_name, device_type, location, lng, lat, area) " + + "VALUES (?,?,?,?,?,?,?)", + deviceNo, deviceName, deviceType, location, lng, lat, area); + Map result = new LinkedHashMap<>(); + result.put("deviceNo", deviceNo); + result.put("deviceName", deviceName); + result.put("created", true); + return result; + } + + public Map updateDeviceStatus(Long deviceId, String status) { + jdbc.update("UPDATE patrol_device SET status = ?, updated_at = NOW() WHERE id = ?", + status, deviceId); + return Map.of("deviceId", deviceId, "status", status, "updated", true); + } + + // ========== 工具方法 ========== + + private long countQuery(String sql, Object... args) { + Long count = jdbc.queryForObject(sql, Long.class, args); + return count != null ? count : 0; + } + + private double haversine(double lat1, double lon1, double lat2, double lon2) { + double R = 6371; + double dLat = Math.toRadians(lat2 - lat1); + double dLon = Math.toRadians(lon2 - lon1); + double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) + * Math.sin(dLon / 2) * Math.sin(dLon / 2); + return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + } +} diff --git a/wm-patrol/src/main/java/com/water/patrol/service/PatrolFormService.java b/wm-patrol/src/main/java/com/water/patrol/service/PatrolFormService.java new file mode 100644 index 00000000..29bb1b52 --- /dev/null +++ b/wm-patrol/src/main/java/com/water/patrol/service/PatrolFormService.java @@ -0,0 +1,45 @@ +package com.water.patrol.service; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.water.patrol.entity.PatrolForm; import com.water.patrol.mapper.PatrolFormMapper; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import java.util.*; +@Slf4j @Service @RequiredArgsConstructor +public class PatrolFormService { + private final PatrolFormMapper mapper; + public PatrolForm create(String formName, String formCode, String formType, String fieldConfig) { + PatrolForm f = new PatrolForm(); f.setFormName(formName); f.setFormCode(formCode); + f.setFormType(formType); f.setFieldConfig(fieldConfig); f.setStatus("active"); + mapper.insert(f); return f; + } + public PatrolForm update(Long id, PatrolForm patch) { + PatrolForm f = mapper.selectById(id); + if (f == null) throw new RuntimeException("表单不存在: "+id); + if (patch.getFormName() != null) f.setFormName(patch.getFormName()); + if (patch.getFieldConfig() != null) f.setFieldConfig(patch.getFieldConfig()); + if (patch.getFormType() != null) f.setFormType(patch.getFormType()); + if (patch.getRemark() != null) f.setRemark(patch.getRemark()); + mapper.updateById(f); return f; + } + public void delete(Long id) { mapper.deleteById(id); } + public PatrolForm getDetail(Long id) { return mapper.selectById(id); } + public Map list(String keyword, String formType, String status, int page, int size) { + LambdaQueryWrapper w = new LambdaQueryWrapper<>(); + if (keyword != null && !keyword.isEmpty()) w.and(q -> q.like(PatrolForm::getFormName, keyword).or().like(PatrolForm::getFormCode, keyword)); + if (formType != null && !formType.isEmpty()) w.eq(PatrolForm::getFormType, formType); + if (status != null && !status.isEmpty()) w.eq(PatrolForm::getStatus, status); + w.orderByDesc(PatrolForm::getCreatedAt); + Page p = mapper.selectPage(new Page<>(page, size), w); + Map r = new LinkedHashMap<>(); + r.put("records", p.getRecords()); r.put("total", p.getTotal()); + r.put("page", page); r.put("size", size); + return r; + } + public Map getStats() { + Map r = new LinkedHashMap<>(); + r.put("total", mapper.selectCount(null)); + r.put("active", mapper.selectCount(new LambdaQueryWrapper().eq(PatrolForm::getStatus,"active"))); + return r; + } +} diff --git a/wm-patrol/src/main/java/com/water/patrol/service/PatrolRouteSetupService.java b/wm-patrol/src/main/java/com/water/patrol/service/PatrolRouteSetupService.java new file mode 100644 index 00000000..ec3a9648 --- /dev/null +++ b/wm-patrol/src/main/java/com/water/patrol/service/PatrolRouteSetupService.java @@ -0,0 +1,55 @@ +package com.water.patrol.service; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.water.patrol.entity.PatrolRouteSetup; import com.water.patrol.mapper.PatrolRouteSetupMapper; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import java.util.*; +@Slf4j @Service @RequiredArgsConstructor +public class PatrolRouteSetupService { + private final PatrolRouteSetupMapper mapper; + public PatrolRouteSetup create(String routeName, String routeCode, Long areaId, String areaName, + Integer checkpointCount, String checkpointList, Double totalDistance, Integer estimatedMinutes) { + PatrolRouteSetup r = new PatrolRouteSetup(); r.setRouteName(routeName); r.setRouteCode(routeCode); + r.setAreaId(areaId); r.setAreaName(areaName); r.setCheckpointCount(checkpointCount); + r.setCheckpointList(checkpointList); r.setTotalDistance(totalDistance); + r.setEstimatedMinutes(estimatedMinutes); r.setStatus("active"); + mapper.insert(r); return r; + } + public PatrolRouteSetup update(Long id, PatrolRouteSetup patch) { + PatrolRouteSetup r = mapper.selectById(id); + if (r == null) throw new RuntimeException("路线不存在: "+id); + if (patch.getRouteName() != null) r.setRouteName(patch.getRouteName()); + if (patch.getCheckpointCount() != null) r.setCheckpointCount(patch.getCheckpointCount()); + if (patch.getCheckpointList() != null) r.setCheckpointList(patch.getCheckpointList()); + if (patch.getTotalDistance() != null) r.setTotalDistance(patch.getTotalDistance()); + if (patch.getEstimatedMinutes() != null) r.setEstimatedMinutes(patch.getEstimatedMinutes()); + if (patch.getRemark() != null) r.setRemark(patch.getRemark()); + mapper.updateById(r); return r; + } + public void delete(Long id) { mapper.deleteById(id); } + public PatrolRouteSetup getDetail(Long id) { return mapper.selectById(id); } + public Map list(String keyword, String status, Long areaId, int page, int size) { + LambdaQueryWrapper w = new LambdaQueryWrapper<>(); + if (keyword != null && !keyword.isEmpty()) w.and(q -> q.like(PatrolRouteSetup::getRouteName, keyword).or().like(PatrolRouteSetup::getRouteCode, keyword)); + if (status != null && !status.isEmpty()) w.eq(PatrolRouteSetup::getStatus, status); + if (areaId != null) w.eq(PatrolRouteSetup::getAreaId, areaId); + w.orderByDesc(PatrolRouteSetup::getCreatedAt); + Page p = mapper.selectPage(new Page<>(page, size), w); + Map result = new LinkedHashMap<>(); + result.put("records", p.getRecords()); result.put("total", p.getTotal()); + result.put("page", page); result.put("size", size); + return result; + } + public void updateStatus(Long id, String status) { + PatrolRouteSetup r = mapper.selectById(id); + if (r == null) throw new RuntimeException("路线不存在: "+id); + r.setStatus(status); mapper.updateById(r); + } + public Map getStats() { + Map r = new LinkedHashMap<>(); + r.put("total", mapper.selectCount(null)); + r.put("active", mapper.selectCount(new LambdaQueryWrapper().eq(PatrolRouteSetup::getStatus,"active"))); + return r; + } +} diff --git a/wm-patrol/src/main/java/com/water/patrol/service/PatrolTemplateService.java b/wm-patrol/src/main/java/com/water/patrol/service/PatrolTemplateService.java new file mode 100644 index 00000000..082ab494 --- /dev/null +++ b/wm-patrol/src/main/java/com/water/patrol/service/PatrolTemplateService.java @@ -0,0 +1,59 @@ +package com.water.patrol.service; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.water.patrol.entity.PatrolTemplate; import com.water.patrol.mapper.PatrolTemplateMapper; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import java.util.*; +@Slf4j @Service @RequiredArgsConstructor +public class PatrolTemplateService { + private final PatrolTemplateMapper mapper; + public PatrolTemplate create(String templateName, String templateCode, Long routeId, String routeName, + Long formId, String formName, String scheduleType, String scheduleConfig) { + PatrolTemplate t = new PatrolTemplate(); t.setTemplateName(templateName); t.setTemplateCode(templateCode); + t.setRouteId(routeId); t.setRouteName(routeName); t.setFormId(formId); t.setFormName(formName); + t.setScheduleType(scheduleType); t.setScheduleConfig(scheduleConfig); t.setStatus("active"); + mapper.insert(t); return t; + } + public PatrolTemplate update(Long id, PatrolTemplate patch) { + PatrolTemplate t = mapper.selectById(id); + if (t == null) throw new RuntimeException("模板不存在: "+id); + if (patch.getTemplateName() != null) t.setTemplateName(patch.getTemplateName()); + if (patch.getRouteId() != null) t.setRouteId(patch.getRouteId()); + if (patch.getRouteName() != null) t.setRouteName(patch.getRouteName()); + if (patch.getFormId() != null) t.setFormId(patch.getFormId()); + if (patch.getFormName() != null) t.setFormName(patch.getFormName()); + if (patch.getScheduleType() != null) t.setScheduleType(patch.getScheduleType()); + if (patch.getScheduleConfig() != null) t.setScheduleConfig(patch.getScheduleConfig()); + if (patch.getRemark() != null) t.setRemark(patch.getRemark()); + mapper.updateById(t); return t; + } + public void delete(Long id) { mapper.deleteById(id); } + public PatrolTemplate getDetail(Long id) { return mapper.selectById(id); } + public Map list(String keyword, String status, String scheduleType, int page, int size) { + LambdaQueryWrapper w = new LambdaQueryWrapper<>(); + if (keyword != null && !keyword.isEmpty()) w.and(q -> q.like(PatrolTemplate::getTemplateName, keyword).or().like(PatrolTemplate::getTemplateCode, keyword)); + if (status != null && !status.isEmpty()) w.eq(PatrolTemplate::getStatus, status); + if (scheduleType != null && !scheduleType.isEmpty()) w.eq(PatrolTemplate::getScheduleType, scheduleType); + w.orderByDesc(PatrolTemplate::getCreatedAt); + Page p = mapper.selectPage(new Page<>(page, size), w); + Map r = new LinkedHashMap<>(); + r.put("records", p.getRecords()); r.put("total", p.getTotal()); + r.put("page", page); r.put("size", size); + return r; + } + public void updateStatus(Long id, String status) { + PatrolTemplate t = mapper.selectById(id); + if (t == null) throw new RuntimeException("模板不存在: "+id); + t.setStatus(status); mapper.updateById(t); + } + public Map getStats() { + Map r = new LinkedHashMap<>(); + r.put("total", mapper.selectCount(null)); + r.put("active", mapper.selectCount(new LambdaQueryWrapper().eq(PatrolTemplate::getStatus,"active"))); + r.put("daily", mapper.selectCount(new LambdaQueryWrapper().eq(PatrolTemplate::getScheduleType,"daily"))); + r.put("weekly", mapper.selectCount(new LambdaQueryWrapper().eq(PatrolTemplate::getScheduleType,"weekly"))); + r.put("monthly", mapper.selectCount(new LambdaQueryWrapper().eq(PatrolTemplate::getScheduleType,"monthly"))); + return r; + } +} diff --git a/wm-patrol/src/main/java/com/water/patrol/service/PatrolWoService.java b/wm-patrol/src/main/java/com/water/patrol/service/PatrolWoService.java new file mode 100644 index 00000000..139f4131 --- /dev/null +++ b/wm-patrol/src/main/java/com/water/patrol/service/PatrolWoService.java @@ -0,0 +1,122 @@ +package com.water.patrol.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +import java.util.*; + +/** + * 巡检工单服务 - Issue #86 + * PAT-05 工单管理, PAT-06 工单处理 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class PatrolWoService { + + private final JdbcTemplate jdbc; + + /** 创建巡检工单 */ + public Map create(String issueType, String description, String severity, + String location, Double lng, Double lat, Long taskId) { + String orderNo = "PWO-" + System.currentTimeMillis(); + jdbc.update( + "INSERT INTO patrol_work_order (order_no, task_id, issue_type, description, severity, " + + "location, lng, lat, status) VALUES (?,?,?,?,?,?,?,?,?)", + orderNo, taskId, issueType, description, + severity != null ? severity : "medium", + location, lng, lat, "pending"); + Map result = new LinkedHashMap<>(); + result.put("orderNo", orderNo); + result.put("issueType", issueType); + result.put("status", "pending"); + result.put("created", true); + return result; + } + + /** 分派工单 */ + public Map assign(Long woId, Long assigneeId, String assigneeName) { + int rows = jdbc.update( + "UPDATE patrol_work_order SET assignee_id = ?, assignee_name = ?, status = 'assigned' " + + "WHERE id = ?", assigneeId, assigneeName, woId); + return Map.of("success", rows > 0, "woId", woId); + } + + /** 处理工单 */ + public Map process(Long woId, String resolution) { + int rows = jdbc.update( + "UPDATE patrol_work_order SET status = 'processing', resolution = ? WHERE id = ?", + resolution, woId); + return Map.of("success", rows > 0, "woId", woId); + } + + /** 解决工单 */ + public Map resolve(Long woId) { + int rows = jdbc.update( + "UPDATE patrol_work_order SET status = 'resolved', resolved_at = NOW() WHERE id = ?", woId); + return Map.of("success", rows > 0, "woId", woId); + } + + /** 工单详情 */ + public Map getDetail(Long woId) { + List> rows = jdbc.queryForList( + "SELECT * FROM patrol_work_order WHERE id = ?", woId); + return rows.isEmpty() ? null : rows.get(0); + } + + /** 工单列表 */ + public Map list(String status, String severity, int page, int size) { + StringBuilder sql = new StringBuilder("SELECT * FROM patrol_work_order WHERE 1=1"); + List params = new ArrayList<>(); + if (status != null && !status.isEmpty()) { + sql.append(" AND status = ?"); + params.add(status); + } + if (severity != null && !severity.isEmpty()) { + sql.append(" AND severity = ?"); + params.add(severity); + } + sql.append(" ORDER BY created_at DESC LIMIT ? OFFSET ?"); + params.add(size); + params.add((page - 1) * size); + + List> records = jdbc.queryForList(sql.toString(), params.toArray()); + + StringBuilder countSql = new StringBuilder("SELECT COUNT(*) FROM patrol_work_order WHERE 1=1"); + List countParams = new ArrayList<>(); + if (status != null && !status.isEmpty()) { + countSql.append(" AND status = ?"); + countParams.add(status); + } + if (severity != null && !severity.isEmpty()) { + countSql.append(" AND severity = ?"); + countParams.add(severity); + } + long total = countQuery(countSql.toString(), countParams.toArray()); + + Map result = new LinkedHashMap<>(); + result.put("total", total); + result.put("page", page); + result.put("size", size); + result.put("records", records); + return result; + } + + /** 工单统计 */ + public Map stats() { + List> rows = jdbc.queryForList( + "SELECT status, COUNT(*) as count FROM patrol_work_order GROUP BY status"); + Map result = new LinkedHashMap<>(); + for (Map row : rows) { + result.put(String.valueOf(row.get("status")), ((Number) row.get("count")).longValue()); + } + return result; + } + + private long countQuery(String sql, Object... args) { + Long count = jdbc.queryForObject(sql, Long.class, args); + return count != null ? count : 0; + } +} diff --git a/wm-patrol/src/main/resources/sql/V87__patrol_setup.sql b/wm-patrol/src/main/resources/sql/V87__patrol_setup.sql new file mode 100644 index 00000000..fcdcf666 --- /dev/null +++ b/wm-patrol/src/main/resources/sql/V87__patrol_setup.sql @@ -0,0 +1,24 @@ +CREATE TABLE IF NOT EXISTS pat_area ( + id BIGSERIAL PRIMARY KEY, area_name VARCHAR(100), area_code VARCHAR(32) UNIQUE, + description VARCHAR(500), center_lng NUMERIC(10,6), center_lat NUMERIC(10,6), + boundary TEXT, radius DOUBLE PRECISION, status VARCHAR(20) DEFAULT 'active', + created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() +); +CREATE TABLE IF NOT EXISTS pat_route_setup ( + id BIGSERIAL PRIMARY KEY, route_name VARCHAR(100), route_code VARCHAR(32) UNIQUE, + area_id BIGINT, area_name VARCHAR(100), checkpoint_count INT DEFAULT 0, + checkpoint_list TEXT, total_distance DOUBLE PRECISION, estimated_minutes INT, + status VARCHAR(20) DEFAULT 'active', remark VARCHAR(500), + created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() +); +CREATE TABLE IF NOT EXISTS pat_form ( + id BIGSERIAL PRIMARY KEY, form_name VARCHAR(100), form_code VARCHAR(32) UNIQUE, + form_type VARCHAR(30), field_config TEXT, status VARCHAR(20) DEFAULT 'active', + remark VARCHAR(500), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() +); +CREATE TABLE IF NOT EXISTS pat_template ( + id BIGSERIAL PRIMARY KEY, template_name VARCHAR(100), template_code VARCHAR(32) UNIQUE, + route_id BIGINT, route_name VARCHAR(100), form_id BIGINT, form_name VARCHAR(100), + schedule_type VARCHAR(20), schedule_config TEXT, status VARCHAR(20) DEFAULT 'active', + remark VARCHAR(500), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() +); diff --git a/wm-patrol/src/test/java/com/water/patrol/PatrolSetupTest.java b/wm-patrol/src/test/java/com/water/patrol/PatrolSetupTest.java new file mode 100644 index 00000000..f6ca7f60 --- /dev/null +++ b/wm-patrol/src/test/java/com/water/patrol/PatrolSetupTest.java @@ -0,0 +1,25 @@ +package com.water.patrol; +import com.water.patrol.entity.*; import com.water.patrol.mapper.*; import com.water.patrol.service.*; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; import org.mockito.junit.jupiter.MockitoExtension; +import java.math.BigDecimal; import java.util.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +@ExtendWith(MockitoExtension.class) +class PatrolSetupTest { + @Mock PatrolAreaMapper areaMapper; @Mock PatrolRouteSetupMapper routeMapper; + @Mock PatrolFormMapper formMapper; @Mock PatrolTemplateMapper tplMapper; + @InjectMocks PatrolAreaService areaSvc; @InjectMocks PatrolRouteSetupService routeSvc; + @InjectMocks PatrolFormService formSvc; @InjectMocks PatrolTemplateService tplSvc; + + @Test void testCreateArea() { when(areaMapper.insert(any())).thenReturn(1); PatrolArea a=areaSvc.create("A区","AREA-001","供水主管",new BigDecimal("116"),new BigDecimal("39"),null,500.0); assertEquals("active",a.getStatus()); assertEquals("A区",a.getAreaName()); } + @Test void testUpdateArea() { PatrolArea a=new PatrolArea(); a.setId(1L); a.setAreaName("A区"); a.setStatus("active"); when(areaMapper.selectById(1L)).thenReturn(a); when(areaMapper.updateById(any())).thenReturn(1); PatrolArea p=new PatrolArea(); p.setAreaName("B区"); assertEquals("B区",areaSvc.update(1L,p).getAreaName()); } + @Test void testAreaStatus() { PatrolArea a=new PatrolArea(); a.setId(1L); a.setStatus("active"); when(areaMapper.selectById(1L)).thenReturn(a); when(areaMapper.updateById(any())).thenReturn(1); areaSvc.updateStatus(1L,"inactive"); assertEquals("inactive",a.getStatus()); } + @Test void testCreateRoute() { when(routeMapper.insert(any())).thenReturn(1); PatrolRouteSetup r=routeSvc.create("主管线","ROUTE-001",1L,"A区",5,null,2000.0,60); assertEquals("active",r.getStatus()); assertEquals(5,(int)r.getCheckpointCount()); } + @Test void testUpdateRoute() { PatrolRouteSetup r=new PatrolRouteSetup(); r.setId(1L); r.setRouteName("R1"); when(routeMapper.selectById(1L)).thenReturn(r); when(routeMapper.updateById(any())).thenReturn(1); PatrolRouteSetup p=new PatrolRouteSetup(); p.setRouteName("R2"); assertEquals("R2",routeSvc.update(1L,p).getRouteName()); } + @Test void testRouteStatus() { PatrolRouteSetup r=new PatrolRouteSetup(); r.setId(1L); r.setStatus("active"); when(routeMapper.selectById(1L)).thenReturn(r); when(routeMapper.updateById(any())).thenReturn(1); routeSvc.updateStatus(1L,"inactive"); assertEquals("inactive",r.getStatus()); } + @Test void testCreateForm() { when(formMapper.insert(any())).thenReturn(1); PatrolForm f=formSvc.create("日常巡检表","FORM-001","checklist","{\"fields\":[]}"); assertEquals("active",f.getStatus()); assertEquals("checklist",f.getFormType()); } + @Test void testUpdateForm() { PatrolForm f=new PatrolForm(); f.setId(1L); f.setFormName("F1"); when(formMapper.selectById(1L)).thenReturn(f); when(formMapper.updateById(any())).thenReturn(1); PatrolForm p=new PatrolForm(); p.setFormName("F2"); assertEquals("F2",formSvc.update(1L,p).getFormName()); } + @Test void testCreateTemplate() { when(tplMapper.insert(any())).thenReturn(1); PatrolTemplate t=tplSvc.create("日常模板","TPL-001",1L,"主管线",1L,"日常巡检表","daily","08:00"); assertEquals("active",t.getStatus()); assertEquals("daily",t.getScheduleType()); } + @Test void testUpdateTemplate() { PatrolTemplate t=new PatrolTemplate(); t.setId(1L); t.setTemplateName("T1"); when(tplMapper.selectById(1L)).thenReturn(t); when(tplMapper.updateById(any())).thenReturn(1); PatrolTemplate p=new PatrolTemplate(); p.setTemplateName("T2"); assertEquals("T2",tplSvc.update(1L,p).getTemplateName()); } +}