feat(wm-patrol): #86 巡检管理核心
- PatrolOverviewService: 今日任务/完成率/异常数/在线巡检员/月度统计 - PatrolTrackService: GPS轨迹记录/回放/Haversine距离/导出 - PatrolLedgerService: 任务CRUD/分页查询/开始完成/统计 - PatrolDeviceService: 设备注册/状态/维护记录/分页/统计 - PatrolWorkOrderService: 工单创建/分配/处理/解决/关闭/统计 - 5个Controller共25+端点, 10个单元测试 - DDL: pat_track_point/pat_task/pat_device/pat_work_order + 索引
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
package com.water.patrol.controller;
|
||||
import com.water.common.core.result.R; import com.water.patrol.entity.PatrolDevice; import com.water.patrol.service.PatrolDeviceService;
|
||||
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/device") @RequiredArgsConstructor
|
||||
public class PatrolDeviceController {
|
||||
private final PatrolDeviceService svc;
|
||||
@Operation(summary="注册") @PostMapping public R<PatrolDevice> create(@RequestParam String deviceNo, @RequestParam String deviceName, @RequestParam String deviceType, @RequestParam String location, @RequestParam BigDecimal lng, @RequestParam BigDecimal lat) { return R.ok(svc.create(deviceNo,deviceName,deviceType,location,lng,lat)); }
|
||||
@Operation(summary="列表") @GetMapping public R<Map<String,Object>> list(@RequestParam(required=false) String deviceType, @RequestParam(required=false) String status, @RequestParam(required=false) String keyword, @RequestParam(defaultValue="1") int page, @RequestParam(defaultValue="20") int size) { return R.ok(svc.list(deviceType,status,keyword,page,size)); }
|
||||
@Operation(summary="详情") @GetMapping("/{id}") public R<PatrolDevice> detail(@PathVariable Long id) { return R.ok(svc.getDetail(id)); }
|
||||
@Operation(summary="状态") @PutMapping("/{id}/status") public R<String> updateStatus(@PathVariable Long id, @RequestParam String status) { svc.updateStatus(id,status); return R.ok("OK"); }
|
||||
@Operation(summary="维护") @PutMapping("/{id}/maintenance") public R<String> maintenance(@PathVariable Long id) { svc.recordMaintenance(id); return R.ok("OK"); }
|
||||
@Operation(summary="统计") @GetMapping("/stats") public R<Map<String,Object>> stats() { return R.ok(svc.getStats()); }
|
||||
@Operation(summary="删除") @DeleteMapping("/{id}") public R<String> delete(@PathVariable Long id) { svc.delete(id); return R.ok("OK"); }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.water.patrol.controller;
|
||||
import com.water.common.core.result.R; import com.water.patrol.entity.PatrolTask; import com.water.patrol.service.PatrolLedgerService;
|
||||
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/ledger") @RequiredArgsConstructor
|
||||
public class PatrolLedgerController {
|
||||
private final PatrolLedgerService svc;
|
||||
@Operation(summary="列表") @GetMapping public R<Map<String,Object>> list(@RequestParam(required=false) String status, @RequestParam(required=false) String workerName, @RequestParam(required=false) String startDate, @RequestParam(required=false) String endDate, @RequestParam(defaultValue="1") int page, @RequestParam(defaultValue="20") int size) { return R.ok(svc.listTasks(status,workerName,startDate,endDate,page,size)); }
|
||||
@Operation(summary="详情") @GetMapping("/{id}") public R<PatrolTask> detail(@PathVariable Long id) { return R.ok(svc.getDetail(id)); }
|
||||
@Operation(summary="创建") @PostMapping public R<PatrolTask> create(@RequestBody Map<String,Object> req) { return R.ok(svc.createTask((String)req.get("taskName"),((Number)req.get("routeId")).longValue(),((Number)req.get("workerId")).longValue(),(String)req.get("workerName"))); }
|
||||
@Operation(summary="开始") @PutMapping("/{id}/start") public R<String> start(@PathVariable Long id) { svc.startTask(id); return R.ok("OK"); }
|
||||
@Operation(summary="完成") @PutMapping("/{id}/complete") public R<String> complete(@PathVariable Long id, @RequestParam Integer completedCheckpoints, @RequestParam Double distance) { svc.completeTask(id,completedCheckpoints,distance); return R.ok("OK"); }
|
||||
@Operation(summary="统计") @GetMapping("/stats") public R<Map<String,Object>> stats() { return R.ok(svc.getStats()); }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.water.patrol.controller;
|
||||
import com.water.common.core.result.R; import com.water.patrol.service.PatrolOverviewService;
|
||||
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/overview") @RequiredArgsConstructor
|
||||
public class PatrolOverviewController {
|
||||
private final PatrolOverviewService svc;
|
||||
@Operation(summary="总览") @GetMapping public R<Map<String,Object>> overview() { return R.ok(svc.getOverview()); }
|
||||
@Operation(summary="月度统计") @GetMapping("/monthly") public R<Map<String,Object>> monthly(@RequestParam int year, @RequestParam int month) { return R.ok(svc.getMonthlyStats(year,month)); }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.water.patrol.controller;
|
||||
import com.water.common.core.result.R; import com.water.patrol.entity.PatrolTrackPoint; import com.water.patrol.service.PatrolTrackService;
|
||||
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.*;
|
||||
@Tag(name="巡检轨迹") @RestController @RequestMapping("/api/patrol/track") @RequiredArgsConstructor
|
||||
public class PatrolTrackController {
|
||||
private final PatrolTrackService svc;
|
||||
@Operation(summary="记录GPS") @PostMapping public R<PatrolTrackPoint> record(@RequestParam Long taskId, @RequestParam Long workerId, @RequestParam BigDecimal lng, @RequestParam BigDecimal lat, @RequestParam(required=false) BigDecimal speed, @RequestParam(required=false) BigDecimal accuracy, @RequestParam(required=false) String address) { return R.ok(svc.recordPoint(taskId,workerId,lng,lat,speed,accuracy,address)); }
|
||||
@Operation(summary="轨迹") @GetMapping("/{taskId}") public R<List<PatrolTrackPoint>> getTrack(@PathVariable Long taskId) { return R.ok(svc.getTrack(taskId)); }
|
||||
@Operation(summary="摘要") @GetMapping("/{taskId}/summary") public R<Map<String,Object>> summary(@PathVariable Long taskId) { return R.ok(svc.getTrackSummary(taskId)); }
|
||||
@Operation(summary="导出") @GetMapping("/{taskId}/export") public R<List<Map<String,Object>>> export(@PathVariable Long taskId) { return R.ok(svc.exportTrack(taskId)); }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.water.patrol.controller;
|
||||
import com.water.common.core.result.R; import com.water.patrol.entity.PatrolWorkOrder; import com.water.patrol.service.PatrolWorkOrderService;
|
||||
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/work-order") @RequiredArgsConstructor
|
||||
public class PatrolWoController {
|
||||
private final PatrolWorkOrderService svc;
|
||||
@Operation(summary="创建") @PostMapping public R<PatrolWorkOrder> create(@RequestBody Map<String,Object> req) { return R.ok(svc.create(((Number)req.get("taskId")).longValue(),(String)req.get("issueType"),(String)req.get("description"),(String)req.get("photos"),(String)req.get("severity"))); }
|
||||
@Operation(summary="列表") @GetMapping public R<Map<String,Object>> list(@RequestParam(required=false) String status, @RequestParam(required=false) String severity, @RequestParam(required=false) String keyword, @RequestParam(defaultValue="1") int page, @RequestParam(defaultValue="20") int size) { return R.ok(svc.list(status,severity,keyword,page,size)); }
|
||||
@Operation(summary="详情") @GetMapping("/{id}") public R<PatrolWorkOrder> detail(@PathVariable Long id) { return R.ok(svc.getDetail(id)); }
|
||||
@Operation(summary="分配") @PutMapping("/{id}/assign") public R<String> assign(@PathVariable Long id, @RequestParam Long assigneeId, @RequestParam String assigneeName) { svc.assign(id,assigneeId,assigneeName); return R.ok("OK"); }
|
||||
@Operation(summary="开始") @PutMapping("/{id}/start") public R<String> start(@PathVariable Long id) { svc.startProcess(id); return R.ok("OK"); }
|
||||
@Operation(summary="解决") @PutMapping("/{id}/resolve") public R<String> resolve(@PathVariable Long id, @RequestParam String resolution) { svc.resolve(id,resolution); return R.ok("OK"); }
|
||||
@Operation(summary="关闭") @PutMapping("/{id}/close") public R<String> close(@PathVariable Long id) { svc.close(id); return R.ok("OK"); }
|
||||
@Operation(summary="统计") @GetMapping("/stats") public R<Map<String,Object>> stats() { return R.ok(svc.getStats()); }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.water.patrol.entity;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
@Data @TableName("pat_device")
|
||||
public class PatrolDevice {
|
||||
@TableId(type = IdType.AUTO) private Long id;
|
||||
private String deviceNo; private String deviceName; private String deviceType;
|
||||
private String location; private BigDecimal lng; private BigDecimal lat;
|
||||
private String status; private LocalDate lastMaintenanceDate; private LocalDate nextMaintenanceDate;
|
||||
private String remark;
|
||||
@TableField(fill = FieldFill.INSERT) private LocalDateTime createdAt;
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.water.patrol.entity;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
@Data @TableName("pat_task")
|
||||
public class PatrolTask {
|
||||
@TableId(type = IdType.AUTO) private Long id;
|
||||
private String taskNo; private String taskName; private Long routeId;
|
||||
private Long workerId; private String workerName; private String status;
|
||||
private LocalDateTime startTime; private LocalDateTime endTime;
|
||||
private Integer checkpoints; private Integer completedCheckpoints; private Integer abnormalCount;
|
||||
private Double distance; private String remark;
|
||||
@TableField(fill = FieldFill.INSERT) private LocalDateTime createdAt;
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.water.patrol.entity;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
@Data @TableName("pat_track_point")
|
||||
public class PatrolTrackPoint {
|
||||
@TableId(type = IdType.AUTO) private Long id;
|
||||
private Long taskId; private Long workerId;
|
||||
private BigDecimal lng; private BigDecimal lat; private BigDecimal speed; private BigDecimal accuracy;
|
||||
private String address; private LocalDateTime timestamp;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.water.patrol.entity;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
@Data @TableName("pat_work_order")
|
||||
public class PatrolWorkOrder {
|
||||
@TableId(type = IdType.AUTO) private Long id;
|
||||
private String orderNo; private Long taskId; private String issueType;
|
||||
private String description; private String photos; private String severity;
|
||||
private Long assigneeId; private String assigneeName; private String status;
|
||||
private String resolution; private LocalDateTime resolvedAt;
|
||||
@TableField(fill = FieldFill.INSERT) private LocalDateTime createdAt;
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.water.patrol.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.water.patrol.entity.PatrolDevice;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper public interface PatrolDeviceMapper extends BaseMapper<PatrolDevice> {}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.water.patrol.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.water.patrol.entity.PatrolTask;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper public interface PatrolTaskMapper extends BaseMapper<PatrolTask> {}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.water.patrol.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.water.patrol.entity.PatrolTrackPoint;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper public interface PatrolTrackPointMapper extends BaseMapper<PatrolTrackPoint> {}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.water.patrol.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.water.patrol.entity.PatrolWorkOrder;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper public interface PatrolWorkOrderMapper extends BaseMapper<PatrolWorkOrder> {}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.water.patrol.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.water.patrol.entity.PatrolDevice;
|
||||
import com.water.patrol.mapper.PatrolDeviceMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PatrolDeviceService {
|
||||
private final PatrolDeviceMapper deviceMapper;
|
||||
|
||||
public PatrolDevice create(String deviceNo, String deviceName, String deviceType,
|
||||
String location, BigDecimal lng, BigDecimal lat) {
|
||||
PatrolDevice d = new PatrolDevice();
|
||||
d.setDeviceNo(deviceNo);
|
||||
d.setDeviceName(deviceName);
|
||||
d.setDeviceType(deviceType);
|
||||
d.setLocation(location);
|
||||
d.setLng(lng);
|
||||
d.setLat(lat);
|
||||
d.setStatus("normal");
|
||||
d.setCreatedAt(LocalDateTime.now());
|
||||
d.setUpdatedAt(LocalDateTime.now());
|
||||
deviceMapper.insert(d);
|
||||
log.info("Device created: {}", deviceNo);
|
||||
return d;
|
||||
}
|
||||
|
||||
public Map<String, Object> list(String deviceType, String status, String keyword,
|
||||
int page, int size) {
|
||||
LambdaQueryWrapper<PatrolDevice> w = new LambdaQueryWrapper<>();
|
||||
if (deviceType != null && !deviceType.isEmpty()) w.eq(PatrolDevice::getDeviceType, deviceType);
|
||||
if (status != null && !status.isEmpty()) w.eq(PatrolDevice::getStatus, status);
|
||||
if (keyword != null && !keyword.isEmpty()) {
|
||||
w.and(q -> q.like(PatrolDevice::getDeviceName, keyword)
|
||||
.or().like(PatrolDevice::getDeviceNo, keyword)
|
||||
.or().like(PatrolDevice::getLocation, keyword));
|
||||
}
|
||||
w.orderByDesc(PatrolDevice::getCreatedAt);
|
||||
|
||||
Long total = deviceMapper.selectCount(w);
|
||||
w.last("LIMIT " + size + " OFFSET " + ((page - 1) * size));
|
||||
List<PatrolDevice> records = deviceMapper.selectList(w);
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("total", total);
|
||||
result.put("page", page);
|
||||
result.put("size", size);
|
||||
result.put("records", records);
|
||||
return result;
|
||||
}
|
||||
|
||||
public PatrolDevice getDetail(Long id) {
|
||||
return deviceMapper.selectById(id);
|
||||
}
|
||||
|
||||
public void updateStatus(Long id, String status) {
|
||||
PatrolDevice d = deviceMapper.selectById(id);
|
||||
if (d == null) throw new RuntimeException("Device not found: " + id);
|
||||
d.setStatus(status);
|
||||
d.setUpdatedAt(LocalDateTime.now());
|
||||
deviceMapper.updateById(d);
|
||||
log.info("Device {} status -> {}", id, status);
|
||||
}
|
||||
|
||||
public void recordMaintenance(Long id) {
|
||||
PatrolDevice d = deviceMapper.selectById(id);
|
||||
if (d == null) throw new RuntimeException("Device not found: " + id);
|
||||
d.setLastMaintenanceDate(LocalDate.now());
|
||||
d.setNextMaintenanceDate(LocalDate.now().plusMonths(6));
|
||||
d.setStatus("normal");
|
||||
d.setUpdatedAt(LocalDateTime.now());
|
||||
deviceMapper.updateById(d);
|
||||
log.info("Maintenance recorded for device {}", id);
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> r = new LinkedHashMap<>();
|
||||
Long total = deviceMapper.selectCount(new LambdaQueryWrapper<>());
|
||||
r.put("total", total);
|
||||
|
||||
LambdaQueryWrapper<PatrolDevice> normal = new LambdaQueryWrapper<PatrolDevice>().eq(PatrolDevice::getStatus, "normal");
|
||||
r.put("normal", deviceMapper.selectCount(normal));
|
||||
|
||||
LambdaQueryWrapper<PatrolDevice> fault = new LambdaQueryWrapper<PatrolDevice>().eq(PatrolDevice::getStatus, "fault");
|
||||
r.put("fault", deviceMapper.selectCount(fault));
|
||||
|
||||
LambdaQueryWrapper<PatrolDevice> offline = new LambdaQueryWrapper<PatrolDevice>().eq(PatrolDevice::getStatus, "offline");
|
||||
r.put("offline", deviceMapper.selectCount(offline));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public void delete(Long id) {
|
||||
deviceMapper.deleteById(id);
|
||||
log.info("Device deleted: {}", id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
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.PatrolTask; import com.water.patrol.mapper.PatrolTaskMapper;
|
||||
import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.time.LocalDateTime; import java.util.*;
|
||||
@Slf4j @Service @RequiredArgsConstructor
|
||||
public class PatrolLedgerService {
|
||||
private final PatrolTaskMapper mapper;
|
||||
public Map<String, Object> listTasks(String status, String workerName, String startDate, String endDate, int page, int size) {
|
||||
LambdaQueryWrapper<PatrolTask> w = new LambdaQueryWrapper<>();
|
||||
if (status != null && !status.isEmpty()) w.eq(PatrolTask::getStatus, status);
|
||||
if (workerName != null && !workerName.isEmpty()) w.like(PatrolTask::getWorkerName, workerName);
|
||||
if (startDate != null && !startDate.isEmpty()) w.ge(PatrolTask::getCreatedAt, startDate+"T00:00:00");
|
||||
if (endDate != null && !endDate.isEmpty()) w.le(PatrolTask::getCreatedAt, endDate+"T23:59:59");
|
||||
w.orderByDesc(PatrolTask::getCreatedAt);
|
||||
Page<PatrolTask> p = mapper.selectPage(new Page<>(page, size), w);
|
||||
Map<String, Object> r = new LinkedHashMap<>();
|
||||
r.put("records", p.getRecords()); r.put("total", p.getTotal());
|
||||
r.put("page", page); r.put("size", size); r.put("pages", p.getPages());
|
||||
return r;
|
||||
}
|
||||
public PatrolTask getDetail(Long id) { return mapper.selectById(id); }
|
||||
public PatrolTask createTask(String taskName, Long routeId, Long workerId, String workerName) {
|
||||
PatrolTask t = new PatrolTask(); t.setTaskNo("PAT-"+System.currentTimeMillis());
|
||||
t.setTaskName(taskName); t.setRouteId(routeId); t.setWorkerId(workerId); t.setWorkerName(workerName);
|
||||
t.setStatus("pending"); t.setCheckpoints(0); t.setCompletedCheckpoints(0);
|
||||
t.setAbnormalCount(0); t.setDistance(0.0); mapper.insert(t); return t;
|
||||
}
|
||||
public void startTask(Long id) {
|
||||
PatrolTask t = mapper.selectById(id);
|
||||
if (t == null) throw new RuntimeException("任务不存在: "+id);
|
||||
t.setStatus("in_progress"); t.setStartTime(LocalDateTime.now()); mapper.updateById(t);
|
||||
}
|
||||
public void completeTask(Long id, Integer cc, Double dist) {
|
||||
PatrolTask t = mapper.selectById(id);
|
||||
if (t == null) throw new RuntimeException("任务不存在: "+id);
|
||||
t.setStatus("completed"); t.setEndTime(LocalDateTime.now());
|
||||
t.setCompletedCheckpoints(cc); t.setDistance(dist); mapper.updateById(t);
|
||||
}
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> r = new LinkedHashMap<>();
|
||||
r.put("total", mapper.selectCount(null));
|
||||
r.put("completed", mapper.selectCount(new LambdaQueryWrapper<PatrolTask>().eq(PatrolTask::getStatus,"completed")));
|
||||
r.put("inProgress", mapper.selectCount(new LambdaQueryWrapper<PatrolTask>().eq(PatrolTask::getStatus,"in_progress")));
|
||||
r.put("pending", mapper.selectCount(new LambdaQueryWrapper<PatrolTask>().eq(PatrolTask::getStatus,"pending")));
|
||||
return r;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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.util.*;
|
||||
@Slf4j @Service @RequiredArgsConstructor
|
||||
public class PatrolOverviewService {
|
||||
private final JdbcTemplate jdbc;
|
||||
public Map<String, Object> getOverview() {
|
||||
LocalDate today = LocalDate.now();
|
||||
Map<String, Object> r = new LinkedHashMap<>();
|
||||
r.put("todayTasks", jdbc.queryForObject("SELECT COUNT(*) FROM pat_task WHERE DATE(created_at)=?", Integer.class, today));
|
||||
r.put("completedTasks", jdbc.queryForObject("SELECT COUNT(*) FROM pat_task WHERE DATE(created_at)=? AND status='completed'", Integer.class, today));
|
||||
int t = (Integer) r.get("todayTasks"), c = (Integer) r.get("completedTasks");
|
||||
r.put("completionRate", t > 0 ? Math.round(c * 100.0 / t) : 0);
|
||||
r.put("abnormalCount", jdbc.queryForObject("SELECT COUNT(*) FROM pat_work_order WHERE DATE(created_at)=? AND status!='closed'", Integer.class, today));
|
||||
r.put("onlineWorkers", jdbc.queryForObject("SELECT COUNT(DISTINCT worker_id) FROM pat_task WHERE status='in_progress'", Integer.class));
|
||||
r.put("monthDistance", jdbc.queryForObject("SELECT COALESCE(SUM(distance),0) FROM pat_task WHERE DATE_PART('month',created_at)=DATE_PART('month',?::date)", Double.class, today.toString()));
|
||||
r.put("pendingOrders", jdbc.queryForObject("SELECT COUNT(*) FROM pat_work_order WHERE status IN ('pending','assigned')", Integer.class));
|
||||
return r;
|
||||
}
|
||||
public Map<String, Object> getMonthlyStats(int year, int month) {
|
||||
String s = String.format("%d-%02d-01", year, month), e = String.format("%d-%02d-31", year, month);
|
||||
Map<String, Object> r = new LinkedHashMap<>();
|
||||
r.put("totalTasks", jdbc.queryForObject("SELECT COUNT(*) FROM pat_task WHERE created_at BETWEEN ?::date AND ?::date", Integer.class, s, e));
|
||||
r.put("completedTasks", jdbc.queryForObject("SELECT COUNT(*) FROM pat_task WHERE created_at BETWEEN ?::date AND ?::date AND status='completed'", Integer.class, s, e));
|
||||
r.put("totalDistance", jdbc.queryForObject("SELECT COALESCE(SUM(distance),0) FROM pat_task WHERE created_at BETWEEN ?::date AND ?::date", Double.class, s, e));
|
||||
r.put("totalAbnormals", jdbc.queryForObject("SELECT COUNT(*) FROM pat_work_order WHERE created_at BETWEEN ?::date AND ?::date", Integer.class, s, e));
|
||||
return r;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.water.patrol.service;
|
||||
import com.water.patrol.entity.PatrolTrackPoint; import com.water.patrol.mapper.PatrolTrackPointMapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.*;
|
||||
@Slf4j @Service @RequiredArgsConstructor
|
||||
public class PatrolTrackService {
|
||||
private final PatrolTrackPointMapper mapper;
|
||||
public PatrolTrackPoint recordPoint(Long taskId, Long workerId, BigDecimal lng, BigDecimal lat, BigDecimal speed, BigDecimal accuracy, String address) {
|
||||
PatrolTrackPoint p = new PatrolTrackPoint();
|
||||
p.setTaskId(taskId); p.setWorkerId(workerId); p.setLng(lng); p.setLat(lat);
|
||||
p.setSpeed(speed); p.setAccuracy(accuracy); p.setAddress(address); p.setTimestamp(LocalDateTime.now());
|
||||
mapper.insert(p); return p;
|
||||
}
|
||||
public List<PatrolTrackPoint> getTrack(Long taskId) {
|
||||
return mapper.selectList(new LambdaQueryWrapper<PatrolTrackPoint>().eq(PatrolTrackPoint::getTaskId, taskId).orderByAsc(PatrolTrackPoint::getTimestamp));
|
||||
}
|
||||
public Map<String, Object> getTrackSummary(Long taskId) {
|
||||
List<PatrolTrackPoint> pts = getTrack(taskId);
|
||||
Map<String, Object> r = new LinkedHashMap<>();
|
||||
r.put("totalPoints", pts.size());
|
||||
if (!pts.isEmpty()) {
|
||||
r.put("startTime", pts.get(0).getTimestamp()); r.put("endTime", pts.get(pts.size()-1).getTimestamp());
|
||||
double d = 0; for (int i = 1; i < pts.size(); i++) d += haversine(pts.get(i-1), pts.get(i));
|
||||
r.put("totalDistance", Math.round(d));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
public List<Map<String, Object>> exportTrack(Long taskId) {
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (PatrolTrackPoint p : getTrack(taskId)) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("lng", p.getLng()); m.put("lat", p.getLat()); m.put("speed", p.getSpeed());
|
||||
m.put("timestamp", p.getTimestamp()); m.put("address", p.getAddress());
|
||||
result.add(m);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
private double haversine(PatrolTrackPoint a, PatrolTrackPoint b) {
|
||||
double R=6371000, dLat=Math.toRadians(b.getLat().doubleValue()-a.getLat().doubleValue()),
|
||||
dLng=Math.toRadians(b.getLng().doubleValue()-a.getLng().doubleValue()),
|
||||
la1=Math.toRadians(a.getLat().doubleValue()), la2=Math.toRadians(b.getLat().doubleValue());
|
||||
double x=Math.sin(dLat/2)*Math.sin(dLat/2)+Math.cos(la1)*Math.cos(la2)*Math.sin(dLng/2)*Math.sin(dLng/2);
|
||||
return R*2*Math.atan2(Math.sqrt(x),Math.sqrt(1-x));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.water.patrol.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.water.patrol.entity.PatrolWorkOrder;
|
||||
import com.water.patrol.mapper.PatrolWorkOrderMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PatrolWorkOrderService {
|
||||
private final PatrolWorkOrderMapper woMapper;
|
||||
|
||||
public PatrolWorkOrder create(Long taskId, String issueType, String description,
|
||||
String photos, String severity) {
|
||||
PatrolWorkOrder wo = new PatrolWorkOrder();
|
||||
wo.setOrderNo("PWO-" + System.currentTimeMillis());
|
||||
wo.setTaskId(taskId);
|
||||
wo.setIssueType(issueType);
|
||||
wo.setDescription(description);
|
||||
wo.setPhotos(photos);
|
||||
wo.setSeverity(severity != null ? severity : "medium");
|
||||
wo.setStatus("pending");
|
||||
wo.setCreatedAt(LocalDateTime.now());
|
||||
wo.setUpdatedAt(LocalDateTime.now());
|
||||
woMapper.insert(wo);
|
||||
log.info("Work order created: {}", wo.getOrderNo());
|
||||
return wo;
|
||||
}
|
||||
|
||||
public Map<String, Object> list(String status, String severity, String keyword,
|
||||
int page, int size) {
|
||||
LambdaQueryWrapper<PatrolWorkOrder> w = new LambdaQueryWrapper<>();
|
||||
if (status != null && !status.isEmpty()) w.eq(PatrolWorkOrder::getStatus, status);
|
||||
if (severity != null && !severity.isEmpty()) w.eq(PatrolWorkOrder::getSeverity, severity);
|
||||
if (keyword != null && !keyword.isEmpty()) {
|
||||
w.and(q -> q.like(PatrolWorkOrder::getDescription, keyword)
|
||||
.or().like(PatrolWorkOrder::getOrderNo, keyword));
|
||||
}
|
||||
w.orderByDesc(PatrolWorkOrder::getCreatedAt);
|
||||
|
||||
Long total = woMapper.selectCount(w);
|
||||
w.last("LIMIT " + size + " OFFSET " + ((page - 1) * size));
|
||||
List<PatrolWorkOrder> records = woMapper.selectList(w);
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("total", total);
|
||||
result.put("page", page);
|
||||
result.put("size", size);
|
||||
result.put("records", records);
|
||||
return result;
|
||||
}
|
||||
|
||||
public PatrolWorkOrder getDetail(Long id) {
|
||||
return woMapper.selectById(id);
|
||||
}
|
||||
|
||||
public void assign(Long id, Long assigneeId, String assigneeName) {
|
||||
PatrolWorkOrder wo = woMapper.selectById(id);
|
||||
if (wo == null) throw new RuntimeException("Work order not found: " + id);
|
||||
wo.setAssigneeId(assigneeId);
|
||||
wo.setAssigneeName(assigneeName);
|
||||
wo.setStatus("assigned");
|
||||
wo.setUpdatedAt(LocalDateTime.now());
|
||||
woMapper.updateById(wo);
|
||||
log.info("Work order {} assigned to {}", id, assigneeName);
|
||||
}
|
||||
|
||||
public void startProcess(Long id) {
|
||||
PatrolWorkOrder wo = woMapper.selectById(id);
|
||||
if (wo == null) throw new RuntimeException("Work order not found: " + id);
|
||||
wo.setStatus("in_progress");
|
||||
wo.setUpdatedAt(LocalDateTime.now());
|
||||
woMapper.updateById(wo);
|
||||
log.info("Work order {} started", id);
|
||||
}
|
||||
|
||||
public void resolve(Long id, String resolution) {
|
||||
PatrolWorkOrder wo = woMapper.selectById(id);
|
||||
if (wo == null) throw new RuntimeException("Work order not found: " + id);
|
||||
wo.setStatus("resolved");
|
||||
wo.setResolution(resolution);
|
||||
wo.setResolvedAt(LocalDateTime.now());
|
||||
wo.setUpdatedAt(LocalDateTime.now());
|
||||
woMapper.updateById(wo);
|
||||
log.info("Work order {} resolved", id);
|
||||
}
|
||||
|
||||
public void close(Long id) {
|
||||
PatrolWorkOrder wo = woMapper.selectById(id);
|
||||
if (wo == null) throw new RuntimeException("Work order not found: " + id);
|
||||
wo.setStatus("closed");
|
||||
wo.setUpdatedAt(LocalDateTime.now());
|
||||
woMapper.updateById(wo);
|
||||
log.info("Work order {} closed", id);
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> r = new LinkedHashMap<>();
|
||||
Long total = woMapper.selectCount(new LambdaQueryWrapper<>());
|
||||
r.put("total", total);
|
||||
|
||||
for (String s : List.of("pending", "assigned", "in_progress", "resolved", "closed")) {
|
||||
LambdaQueryWrapper<PatrolWorkOrder> w = new LambdaQueryWrapper<PatrolWorkOrder>()
|
||||
.eq(PatrolWorkOrder::getStatus, s);
|
||||
r.put(s, woMapper.selectCount(w));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
CREATE TABLE IF NOT EXISTS pat_track_point (
|
||||
id BIGSERIAL PRIMARY KEY, task_id BIGINT NOT NULL, worker_id BIGINT,
|
||||
lng NUMERIC(10,6), lat NUMERIC(10,6), speed NUMERIC(8,2), accuracy NUMERIC(8,2),
|
||||
address VARCHAR(200), timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_pat_track_task ON pat_track_point(task_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pat_task (
|
||||
id BIGSERIAL PRIMARY KEY, task_no VARCHAR(32) UNIQUE NOT NULL, task_name VARCHAR(100),
|
||||
route_id BIGINT, worker_id BIGINT, worker_name VARCHAR(50), status VARCHAR(20) DEFAULT 'pending',
|
||||
start_time TIMESTAMPTZ, end_time TIMESTAMPTZ, checkpoints INT DEFAULT 0,
|
||||
completed_checkpoints INT DEFAULT 0, abnormal_count INT DEFAULT 0,
|
||||
distance DOUBLE PRECISION DEFAULT 0, remark VARCHAR(500),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_pat_task_status ON pat_task(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_pat_task_worker ON pat_task(worker_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pat_device (
|
||||
id BIGSERIAL PRIMARY KEY, device_no VARCHAR(32) UNIQUE NOT NULL, device_name VARCHAR(100),
|
||||
device_type VARCHAR(30), location VARCHAR(200), lng NUMERIC(10,6), lat NUMERIC(10,6),
|
||||
status VARCHAR(20) DEFAULT 'normal', last_maintenance_date DATE, next_maintenance_date DATE,
|
||||
remark VARCHAR(500), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_pat_device_type ON pat_device(device_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_pat_device_status ON pat_device(status);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pat_work_order (
|
||||
id BIGSERIAL PRIMARY KEY, order_no VARCHAR(32) UNIQUE NOT NULL, task_id BIGINT,
|
||||
issue_type VARCHAR(30), description TEXT, photos TEXT, severity VARCHAR(20) DEFAULT 'medium',
|
||||
assignee_id BIGINT, assignee_name VARCHAR(50), status VARCHAR(20) DEFAULT 'pending',
|
||||
resolution TEXT, resolved_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_pat_wo_status ON pat_work_order(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_pat_wo_task ON pat_work_order(task_id);
|
||||
@@ -0,0 +1,27 @@
|
||||
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 org.springframework.jdbc.core.JdbcTemplate;
|
||||
import java.math.BigDecimal; import java.util.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PatrolCoreTest {
|
||||
@Mock JdbcTemplate jdbc; @Mock PatrolTaskMapper taskMapper; @Mock PatrolTrackPointMapper trackMapper;
|
||||
@Mock PatrolDeviceMapper deviceMapper; @Mock PatrolWorkOrderMapper woMapper;
|
||||
@InjectMocks PatrolOverviewService overviewSvc; @InjectMocks PatrolLedgerService ledgerSvc;
|
||||
@InjectMocks PatrolTrackService trackSvc; @InjectMocks PatrolDeviceService deviceSvc;
|
||||
@InjectMocks PatrolWorkOrderService woSvc;
|
||||
|
||||
@Test void testOverview() { when(jdbc.queryForObject(anyString(),eq(Integer.class),any())).thenReturn(10); when(jdbc.queryForObject(anyString(),eq(Double.class),any())).thenReturn(5000.0); assertNotNull(overviewSvc.getOverview()); }
|
||||
@Test void testCreateTask() { when(taskMapper.insert(any())).thenReturn(1); PatrolTask t=ledgerSvc.createTask("巡检",1L,100L,"张三"); assertTrue(t.getTaskNo().startsWith("PAT-")); assertEquals("pending",t.getStatus()); }
|
||||
@Test void testStartTask() { PatrolTask t=new PatrolTask(); t.setId(1L); t.setStatus("pending"); when(taskMapper.selectById(1L)).thenReturn(t); when(taskMapper.updateById(any())).thenReturn(1); ledgerSvc.startTask(1L); assertEquals("in_progress",t.getStatus()); }
|
||||
@Test void testRecordGps() { when(trackMapper.insert(any())).thenReturn(1); PatrolTrackPoint p=trackSvc.recordPoint(1L,100L,new BigDecimal("116.4"),new BigDecimal("39.9"),null,null,"test"); assertNotNull(p); assertEquals(1L,p.getTaskId()); }
|
||||
@Test void testCreateDevice() { when(deviceMapper.insert(any())).thenReturn(1); PatrolDevice d=deviceSvc.create("DEV-001","阀门A","valve","A区",new BigDecimal("116"),new BigDecimal("39")); assertEquals("normal",d.getStatus()); }
|
||||
@Test void testDeviceStatus() { PatrolDevice d=new PatrolDevice(); d.setId(1L); d.setStatus("normal"); when(deviceMapper.selectById(1L)).thenReturn(d); when(deviceMapper.updateById(any())).thenReturn(1); deviceSvc.updateStatus(1L,"fault"); assertEquals("fault",d.getStatus()); }
|
||||
@Test void testCreateWO() { when(woMapper.insert(any())).thenReturn(1); PatrolWorkOrder wo=woSvc.create(1L,"leak","管道漏水",null,"high"); assertTrue(wo.getOrderNo().startsWith("PWO-")); assertEquals("high",wo.getSeverity()); }
|
||||
@Test void testAssignWO() { PatrolWorkOrder wo=new PatrolWorkOrder(); wo.setId(1L); wo.setStatus("pending"); when(woMapper.selectById(1L)).thenReturn(wo); when(woMapper.updateById(any())).thenReturn(1); woSvc.assign(1L,200L,"李四"); assertEquals("assigned",wo.getStatus()); }
|
||||
@Test void testResolveWO() { PatrolWorkOrder wo=new PatrolWorkOrder(); wo.setId(1L); wo.setStatus("in_progress"); when(woMapper.selectById(1L)).thenReturn(wo); when(woMapper.updateById(any())).thenReturn(1); woSvc.resolve(1L,"已修复"); assertEquals("resolved",wo.getStatus()); assertNotNull(wo.getResolvedAt()); }
|
||||
@Test void testCloseWO() { PatrolWorkOrder wo=new PatrolWorkOrder(); wo.setId(1L); wo.setStatus("resolved"); when(woMapper.selectById(1L)).thenReturn(wo); when(woMapper.updateById(any())).thenReturn(1); woSvc.close(1L); assertEquals("closed",wo.getStatus()); }
|
||||
}
|
||||
Reference in New Issue
Block a user