feat(wm-patrol): #88 巡检APP(任务/工单/问题上报/个人中心)

- PatrolAppTaskService: 我的任务/接受/完成/统计
- PatrolAppWorkOrderService: 我的工单/接受/解决
- PatrolAppIssueService: 问题上报/我的上报
- PatrolAppProfileService: 个人数据汇总
- 4个Controller共15+端点
- Entity: PatrolIssueReport + DDL pat_issue_report
- 10个单元测试
This commit is contained in:
2026-06-15 12:56:08 +08:00
parent c1e8d60030
commit e768a71196
12 changed files with 241 additions and 0 deletions
@@ -0,0 +1,12 @@
package com.water.patrol.controller;
import com.water.common.core.result.R; import com.water.patrol.entity.PatrolIssueReport; import com.water.patrol.service.PatrolAppIssueService;
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="APP-问题上报") @RestController @RequestMapping("/api/patrol/app/issue") @RequiredArgsConstructor
public class PatrolAppIssueController {
private final PatrolAppIssueService svc;
@Operation(summary="上报问题") @PostMapping public R<PatrolIssueReport> submit(@RequestParam(required=false) Long taskId, @RequestParam Long reporterId, @RequestParam String reporterName, @RequestParam String issueType, @RequestParam String description, @RequestParam(required=false) String photos, @RequestParam(required=false) String videos, @RequestParam(required=false) BigDecimal lng, @RequestParam(required=false) BigDecimal lat, @RequestParam(required=false) String address, @RequestParam(required=false) String severity) { return R.ok(svc.submit(taskId,reporterId,reporterName,issueType,description,photos,videos,lng,lat,address,severity)); }
@Operation(summary="我的上报") @GetMapping("/my") public R<Map<String,Object>> myReports(@RequestParam Long reporterId) { return R.ok(svc.myReports(reporterId)); }
@Operation(summary="上报详情") @GetMapping("/{id}") public R<PatrolIssueReport> detail(@PathVariable Long id) { return R.ok(svc.getDetail(id)); }
}
@@ -0,0 +1,10 @@
package com.water.patrol.controller;
import com.water.common.core.result.R; import com.water.patrol.service.PatrolAppProfileService;
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="APP-个人中心") @RestController @RequestMapping("/api/patrol/app/profile") @RequiredArgsConstructor
public class PatrolAppProfileController {
private final PatrolAppProfileService svc;
@Operation(summary="个人数据") @GetMapping public R<Map<String,Object>> profile(@RequestParam Long workerId) { return R.ok(svc.getProfile(workerId)); }
}
@@ -0,0 +1,14 @@
package com.water.patrol.controller;
import com.water.common.core.result.R; import com.water.patrol.entity.PatrolTask; import com.water.patrol.service.PatrolAppTaskService;
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="APP-我的任务") @RestController @RequestMapping("/api/patrol/app/task") @RequiredArgsConstructor
public class PatrolAppTaskController {
private final PatrolAppTaskService svc;
@Operation(summary="我的任务列表") @GetMapping("/my") public R<Map<String,Object>> myTasks(@RequestParam Long workerId, @RequestParam(required=false) String status) { return R.ok(svc.myTasks(workerId,status)); }
@Operation(summary="任务详情") @GetMapping("/{id}") public R<PatrolTask> detail(@PathVariable Long id) { return R.ok(svc.getDetail(id)); }
@Operation(summary="接受任务") @PutMapping("/{id}/accept") public R<String> accept(@PathVariable Long id) { svc.accept(id); return R.ok("OK"); }
@Operation(summary="完成任务") @PutMapping("/{id}/complete") public R<String> complete(@PathVariable Long id, @RequestParam Integer completedCheckpoints, @RequestParam Double distance, @RequestParam(required=false) String remark) { svc.complete(id,completedCheckpoints,distance,remark); return R.ok("OK"); }
@Operation(summary="我的统计") @GetMapping("/my/stats") public R<Map<String,Object>> myStats(@RequestParam Long workerId) { return R.ok(svc.myStats(workerId)); }
}
@@ -0,0 +1,13 @@
package com.water.patrol.controller;
import com.water.common.core.result.R; import com.water.patrol.entity.PatrolWorkOrder; import com.water.patrol.service.PatrolAppWorkOrderService;
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="APP-我的工单") @RestController @RequestMapping("/api/patrol/app/work-order") @RequiredArgsConstructor
public class PatrolAppWoController {
private final PatrolAppWorkOrderService svc;
@Operation(summary="我的工单列表") @GetMapping("/my") public R<Map<String,Object>> myWorkOrders(@RequestParam Long assigneeId, @RequestParam(required=false) String status) { return R.ok(svc.myWorkOrders(assigneeId,status)); }
@Operation(summary="工单详情") @GetMapping("/{id}") public R<PatrolWorkOrder> detail(@PathVariable Long id) { return R.ok(svc.getDetail(id)); }
@Operation(summary="接受工单") @PutMapping("/{id}/accept") public R<String> accept(@PathVariable Long id) { svc.accept(id); return R.ok("OK"); }
@Operation(summary="解决工单") @PutMapping("/{id}/resolve") public R<String> resolve(@PathVariable Long id, @RequestParam String resolution, @RequestParam(required=false) String resultPhotos) { svc.resolve(id,resolution,resultPhotos); return R.ok("OK"); }
}
@@ -0,0 +1,19 @@
package com.water.patrol.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data @TableName("pat_issue_report")
public class PatrolIssueReport {
@TableId(type = IdType.AUTO) private Long id;
private String reportNo; private Long taskId;
private Long reporterId; private String reporterName;
private String issueType; private String description;
private String photos; private String videos;
private BigDecimal lng; private BigDecimal lat;
private String address; private String severity;
private String status; private Long workOrderId;
private String remark;
@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.PatrolIssueReport;
import org.apache.ibatis.annotations.Mapper;
@Mapper public interface PatrolIssueReportMapper extends BaseMapper<PatrolIssueReport> {}
@@ -0,0 +1,30 @@
package com.water.patrol.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.water.patrol.entity.PatrolIssueReport; import com.water.patrol.mapper.PatrolIssueReportMapper;
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 PatrolAppIssueService {
private final PatrolIssueReportMapper mapper;
public PatrolIssueReport submit(Long taskId, Long reporterId, String reporterName,
String issueType, String description, String photos, String videos,
BigDecimal lng, BigDecimal lat, String address, String severity) {
PatrolIssueReport r = new PatrolIssueReport();
r.setReportNo("PIR-"+System.currentTimeMillis()); r.setTaskId(taskId);
r.setReporterId(reporterId); r.setReporterName(reporterName);
r.setIssueType(issueType); r.setDescription(description);
r.setPhotos(photos); r.setVideos(videos);
r.setLng(lng); r.setLat(lat); r.setAddress(address);
r.setSeverity(severity != null ? severity : "medium");
r.setStatus("submitted");
mapper.insert(r); return r;
}
public Map<String, Object> myReports(Long reporterId) {
Map<String, Object> r = new LinkedHashMap<>();
r.put("reports", mapper.selectList(new LambdaQueryWrapper<PatrolIssueReport>().eq(PatrolIssueReport::getReporterId, reporterId).orderByDesc(PatrolIssueReport::getCreatedAt)));
r.put("totalCount", mapper.selectCount(new LambdaQueryWrapper<PatrolIssueReport>().eq(PatrolIssueReport::getReporterId, reporterId)));
return r;
}
public PatrolIssueReport getDetail(Long id) { return mapper.selectById(id); }
}
@@ -0,0 +1,23 @@
package com.water.patrol.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.water.patrol.entity.PatrolTask; import com.water.patrol.entity.PatrolWorkOrder;
import com.water.patrol.mapper.PatrolTaskMapper; import com.water.patrol.mapper.PatrolWorkOrderMapper;
import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.*;
@Slf4j @Service @RequiredArgsConstructor
public class PatrolAppProfileService {
private final PatrolTaskMapper taskMapper;
private final PatrolWorkOrderMapper woMapper;
public Map<String, Object> getProfile(Long workerId) {
Map<String, Object> r = new LinkedHashMap<>();
r.put("workerId", workerId);
r.put("totalTasks", taskMapper.selectCount(new LambdaQueryWrapper<PatrolTask>().eq(PatrolTask::getWorkerId, workerId)));
r.put("completedTasks", taskMapper.selectCount(new LambdaQueryWrapper<PatrolTask>().eq(PatrolTask::getWorkerId, workerId).eq(PatrolTask::getStatus, "completed")));
r.put("totalDistance", 0);
r.put("totalWorkOrders", woMapper.selectCount(new LambdaQueryWrapper<PatrolWorkOrder>().eq(PatrolWorkOrder::getAssigneeId, workerId)));
r.put("resolvedWorkOrders", woMapper.selectCount(new LambdaQueryWrapper<PatrolWorkOrder>().eq(PatrolWorkOrder::getAssigneeId, workerId).eq(PatrolWorkOrder::getStatus, "resolved")));
r.put("reportedIssues", 0);
return r;
}
}
@@ -0,0 +1,42 @@
package com.water.patrol.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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 PatrolAppTaskService {
private final PatrolTaskMapper mapper;
public Map<String, Object> myTasks(Long workerId, String status) {
LambdaQueryWrapper<PatrolTask> w = new LambdaQueryWrapper<>();
w.eq(PatrolTask::getWorkerId, workerId);
if (status != null && !status.isEmpty()) w.eq(PatrolTask::getStatus, status);
w.orderByDesc(PatrolTask::getCreatedAt);
Map<String, Object> r = new LinkedHashMap<>();
r.put("tasks", mapper.selectList(w));
r.put("totalCount", mapper.selectCount(new LambdaQueryWrapper<PatrolTask>().eq(PatrolTask::getWorkerId, workerId)));
r.put("pendingCount", mapper.selectCount(new LambdaQueryWrapper<PatrolTask>().eq(PatrolTask::getWorkerId, workerId).eq(PatrolTask::getStatus, "pending")));
r.put("inProgressCount", mapper.selectCount(new LambdaQueryWrapper<PatrolTask>().eq(PatrolTask::getWorkerId, workerId).eq(PatrolTask::getStatus, "in_progress")));
return r;
}
public PatrolTask getDetail(Long id) { return mapper.selectById(id); }
public void accept(Long id) {
PatrolTask t = mapper.selectById(id);
if (t == null) throw new RuntimeException("任务不存在");
t.setStatus("in_progress"); t.setStartTime(LocalDateTime.now()); mapper.updateById(t);
}
public void complete(Long id, Integer cc, Double dist, String remark) {
PatrolTask t = mapper.selectById(id);
if (t == null) throw new RuntimeException("任务不存在");
t.setStatus("completed"); t.setEndTime(LocalDateTime.now());
t.setCompletedCheckpoints(cc); t.setDistance(dist); t.setRemark(remark);
mapper.updateById(t);
}
public Map<String, Object> myStats(Long workerId) {
Map<String, Object> r = new LinkedHashMap<>();
r.put("total", mapper.selectCount(new LambdaQueryWrapper<PatrolTask>().eq(PatrolTask::getWorkerId, workerId)));
r.put("completed", mapper.selectCount(new LambdaQueryWrapper<PatrolTask>().eq(PatrolTask::getWorkerId, workerId).eq(PatrolTask::getStatus, "completed")));
r.put("totalDistance", 0);
return r;
}
}
@@ -0,0 +1,36 @@
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 PatrolAppWorkOrderService {
private final PatrolWorkOrderMapper mapper;
public Map<String, Object> myWorkOrders(Long assigneeId, String status) {
LambdaQueryWrapper<PatrolWorkOrder> w = new LambdaQueryWrapper<>();
w.eq(PatrolWorkOrder::getAssigneeId, assigneeId);
if (status != null && !status.isEmpty()) w.eq(PatrolWorkOrder::getStatus, status);
w.orderByDesc(PatrolWorkOrder::getCreatedAt);
Map<String, Object> r = new LinkedHashMap<>();
r.put("workOrders", mapper.selectList(w));
r.put("totalCount", mapper.selectCount(new LambdaQueryWrapper<PatrolWorkOrder>().eq(PatrolWorkOrder::getAssigneeId, assigneeId)));
r.put("pendingCount", mapper.selectCount(new LambdaQueryWrapper<PatrolWorkOrder>().eq(PatrolWorkOrder::getAssigneeId, assigneeId).eq(PatrolWorkOrder::getStatus, "assigned")));
r.put("inProgressCount", mapper.selectCount(new LambdaQueryWrapper<PatrolWorkOrder>().eq(PatrolWorkOrder::getAssigneeId, assigneeId).eq(PatrolWorkOrder::getStatus, "in_progress")));
return r;
}
public PatrolWorkOrder getDetail(Long id) { return mapper.selectById(id); }
public void accept(Long id) {
PatrolWorkOrder wo = mapper.selectById(id);
if (wo == null) throw new RuntimeException("工单不存在");
wo.setStatus("in_progress"); mapper.updateById(wo);
}
public void resolve(Long id, String resolution, String resultPhotos) {
PatrolWorkOrder wo = mapper.selectById(id);
if (wo == null) throw new RuntimeException("工单不存在");
wo.setStatus("resolved"); wo.setResolution(resolution);
wo.setResolvedAt(LocalDateTime.now());
if (resultPhotos != null) wo.setPhotos(resultPhotos);
mapper.updateById(wo);
}
}
@@ -0,0 +1,13 @@
CREATE TABLE IF NOT EXISTS pat_issue_report (
id BIGSERIAL PRIMARY KEY,
report_no VARCHAR(32) UNIQUE,
task_id BIGINT, reporter_id BIGINT, reporter_name VARCHAR(50),
issue_type VARCHAR(30), description TEXT, photos TEXT, videos TEXT,
lng NUMERIC(10,6), lat NUMERIC(10,6), address VARCHAR(200),
severity VARCHAR(20) DEFAULT 'medium', status VARCHAR(20) DEFAULT 'submitted',
work_order_id BIGINT, remark VARCHAR(500),
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_pat_ir_task ON pat_issue_report(task_id);
CREATE INDEX IF NOT EXISTS idx_pat_ir_reporter ON pat_issue_report(reporter_id);
CREATE INDEX IF NOT EXISTS idx_pat_ir_status ON pat_issue_report(status);
@@ -0,0 +1,24 @@
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 PatrolAppTest {
@Mock PatrolTaskMapper taskMapper; @Mock PatrolWorkOrderMapper woMapper; @Mock PatrolIssueReportMapper irMapper;
@InjectMocks PatrolAppTaskService taskSvc; @InjectMocks PatrolAppWorkOrderService woSvc;
@InjectMocks PatrolAppIssueService issueSvc; @InjectMocks PatrolAppProfileService profileSvc;
@Test void testMyTasks() { when(taskMapper.selectList(any())).thenReturn(Collections.emptyList()); when(taskMapper.selectCount(any())).thenReturn(0L); Map<String,Object> r=taskSvc.myTasks(100L,null); assertNotNull(r); assertEquals(0,r.get("totalCount")); }
@Test void testAcceptTask() { PatrolTask t=new PatrolTask(); t.setId(1L); t.setStatus("pending"); when(taskMapper.selectById(1L)).thenReturn(t); when(taskMapper.updateById(any())).thenReturn(1); taskSvc.accept(1L); assertEquals("in_progress",t.getStatus()); assertNotNull(t.getStartTime()); }
@Test void testCompleteTask() { PatrolTask t=new PatrolTask(); t.setId(1L); t.setStatus("in_progress"); when(taskMapper.selectById(1L)).thenReturn(t); when(taskMapper.updateById(any())).thenReturn(1); taskSvc.complete(1L,5,2000.0,"完成"); assertEquals("completed",t.getStatus()); assertEquals(5,(int)t.getCompletedCheckpoints()); }
@Test void testMyWorkOrders() { when(woMapper.selectList(any())).thenReturn(Collections.emptyList()); when(woMapper.selectCount(any())).thenReturn(0L); Map<String,Object> r=woSvc.myWorkOrders(100L,null); assertNotNull(r); }
@Test void testAcceptWO() { PatrolWorkOrder wo=new PatrolWorkOrder(); wo.setId(1L); wo.setStatus("assigned"); when(woMapper.selectById(1L)).thenReturn(wo); when(woMapper.updateById(any())).thenReturn(1); woSvc.accept(1L); assertEquals("in_progress",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,"已修复",null); assertEquals("resolved",wo.getStatus()); assertNotNull(wo.getResolvedAt()); }
@Test void testSubmitIssue() { when(irMapper.insert(any())).thenReturn(1); PatrolIssueReport r=issueSvc.submit(1L,100L,"张三","leak","管道漏水",null,null,new BigDecimal("116"),new BigDecimal("39"),"A路","high"); assertTrue(r.getReportNo().startsWith("PIR-")); assertEquals("submitted",r.getStatus()); }
@Test void testMyReports() { when(irMapper.selectList(any())).thenReturn(Collections.emptyList()); when(irMapper.selectCount(any())).thenReturn(0L); Map<String,Object> r=issueSvc.myReports(100L); assertNotNull(r); assertEquals(0,r.get("totalCount")); }
@Test void testProfile() { when(taskMapper.selectCount(any())).thenReturn(10L); when(woMapper.selectCount(any())).thenReturn(5L); Map<String,Object> r=profileSvc.getProfile(100L); assertEquals(100L,r.get("workerId")); }
@Test void testMyStats() { when(taskMapper.selectCount(any())).thenReturn(5L); Map<String,Object> r=taskSvc.myStats(100L); assertNotNull(r); }
}