Sfoglia il codice sorgente

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

- PatrolAppTaskService: 我的任务/接受/完成/统计
- PatrolAppWorkOrderService: 我的工单/接受/解决
- PatrolAppIssueService: 问题上报/我的上报
- PatrolAppProfileService: 个人数据汇总
- 4个Controller共15+端点
- Entity: PatrolIssueReport + DDL pat_issue_report
- 10个单元测试
bot_dev2 4 giorni fa
parent
commit
53038417a1

+ 12
- 0
wm-patrol/src/main/java/com/water/patrol/controller/PatrolAppIssueController.java Vedi File

1
+package com.water.patrol.controller;
2
+import com.water.common.core.result.R; import com.water.patrol.entity.PatrolIssueReport; import com.water.patrol.service.PatrolAppIssueService;
3
+import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag;
4
+import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*;
5
+import java.math.BigDecimal; import java.util.Map;
6
+@Tag(name="APP-问题上报") @RestController @RequestMapping("/api/patrol/app/issue") @RequiredArgsConstructor
7
+public class PatrolAppIssueController {
8
+    private final PatrolAppIssueService svc;
9
+    @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)); }
10
+    @Operation(summary="我的上报") @GetMapping("/my") public R<Map<String,Object>> myReports(@RequestParam Long reporterId) { return R.ok(svc.myReports(reporterId)); }
11
+    @Operation(summary="上报详情") @GetMapping("/{id}") public R<PatrolIssueReport> detail(@PathVariable Long id) { return R.ok(svc.getDetail(id)); }
12
+}

+ 10
- 0
wm-patrol/src/main/java/com/water/patrol/controller/PatrolAppProfileController.java Vedi File

1
+package com.water.patrol.controller;
2
+import com.water.common.core.result.R; import com.water.patrol.service.PatrolAppProfileService;
3
+import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag;
4
+import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*;
5
+import java.util.Map;
6
+@Tag(name="APP-个人中心") @RestController @RequestMapping("/api/patrol/app/profile") @RequiredArgsConstructor
7
+public class PatrolAppProfileController {
8
+    private final PatrolAppProfileService svc;
9
+    @Operation(summary="个人数据") @GetMapping public R<Map<String,Object>> profile(@RequestParam Long workerId) { return R.ok(svc.getProfile(workerId)); }
10
+}

+ 14
- 0
wm-patrol/src/main/java/com/water/patrol/controller/PatrolAppTaskController.java Vedi File

1
+package com.water.patrol.controller;
2
+import com.water.common.core.result.R; import com.water.patrol.entity.PatrolTask; import com.water.patrol.service.PatrolAppTaskService;
3
+import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag;
4
+import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*;
5
+import java.util.Map;
6
+@Tag(name="APP-我的任务") @RestController @RequestMapping("/api/patrol/app/task") @RequiredArgsConstructor
7
+public class PatrolAppTaskController {
8
+    private final PatrolAppTaskService svc;
9
+    @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)); }
10
+    @Operation(summary="任务详情") @GetMapping("/{id}") public R<PatrolTask> detail(@PathVariable Long id) { return R.ok(svc.getDetail(id)); }
11
+    @Operation(summary="接受任务") @PutMapping("/{id}/accept") public R<String> accept(@PathVariable Long id) { svc.accept(id); return R.ok("OK"); }
12
+    @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"); }
13
+    @Operation(summary="我的统计") @GetMapping("/my/stats") public R<Map<String,Object>> myStats(@RequestParam Long workerId) { return R.ok(svc.myStats(workerId)); }
14
+}

+ 13
- 0
wm-patrol/src/main/java/com/water/patrol/controller/PatrolAppWoController.java Vedi File

1
+package com.water.patrol.controller;
2
+import com.water.common.core.result.R; import com.water.patrol.entity.PatrolWorkOrder; import com.water.patrol.service.PatrolAppWorkOrderService;
3
+import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag;
4
+import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*;
5
+import java.util.Map;
6
+@Tag(name="APP-我的工单") @RestController @RequestMapping("/api/patrol/app/work-order") @RequiredArgsConstructor
7
+public class PatrolAppWoController {
8
+    private final PatrolAppWorkOrderService svc;
9
+    @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)); }
10
+    @Operation(summary="工单详情") @GetMapping("/{id}") public R<PatrolWorkOrder> detail(@PathVariable Long id) { return R.ok(svc.getDetail(id)); }
11
+    @Operation(summary="接受工单") @PutMapping("/{id}/accept") public R<String> accept(@PathVariable Long id) { svc.accept(id); return R.ok("OK"); }
12
+    @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"); }
13
+}

+ 19
- 0
wm-patrol/src/main/java/com/water/patrol/entity/PatrolIssueReport.java Vedi File

1
+package com.water.patrol.entity;
2
+import com.baomidou.mybatisplus.annotation.*;
3
+import lombok.Data;
4
+import java.math.BigDecimal;
5
+import java.time.LocalDateTime;
6
+@Data @TableName("pat_issue_report")
7
+public class PatrolIssueReport {
8
+    @TableId(type = IdType.AUTO) private Long id;
9
+    private String reportNo; private Long taskId;
10
+    private Long reporterId; private String reporterName;
11
+    private String issueType; private String description;
12
+    private String photos; private String videos;
13
+    private BigDecimal lng; private BigDecimal lat;
14
+    private String address; private String severity;
15
+    private String status; private Long workOrderId;
16
+    private String remark;
17
+    @TableField(fill = FieldFill.INSERT) private LocalDateTime createdAt;
18
+    @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updatedAt;
19
+}

+ 5
- 0
wm-patrol/src/main/java/com/water/patrol/mapper/PatrolIssueReportMapper.java Vedi File

1
+package com.water.patrol.mapper;
2
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
3
+import com.water.patrol.entity.PatrolIssueReport;
4
+import org.apache.ibatis.annotations.Mapper;
5
+@Mapper public interface PatrolIssueReportMapper extends BaseMapper<PatrolIssueReport> {}

+ 30
- 0
wm-patrol/src/main/java/com/water/patrol/service/PatrolAppIssueService.java Vedi File

1
+package com.water.patrol.service;
2
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
3
+import com.water.patrol.entity.PatrolIssueReport; import com.water.patrol.mapper.PatrolIssueReportMapper;
4
+import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j;
5
+import org.springframework.stereotype.Service;
6
+import java.math.BigDecimal; import java.util.*;
7
+@Slf4j @Service @RequiredArgsConstructor
8
+public class PatrolAppIssueService {
9
+    private final PatrolIssueReportMapper mapper;
10
+    public PatrolIssueReport submit(Long taskId, Long reporterId, String reporterName,
11
+            String issueType, String description, String photos, String videos,
12
+            BigDecimal lng, BigDecimal lat, String address, String severity) {
13
+        PatrolIssueReport r = new PatrolIssueReport();
14
+        r.setReportNo("PIR-"+System.currentTimeMillis()); r.setTaskId(taskId);
15
+        r.setReporterId(reporterId); r.setReporterName(reporterName);
16
+        r.setIssueType(issueType); r.setDescription(description);
17
+        r.setPhotos(photos); r.setVideos(videos);
18
+        r.setLng(lng); r.setLat(lat); r.setAddress(address);
19
+        r.setSeverity(severity != null ? severity : "medium");
20
+        r.setStatus("submitted");
21
+        mapper.insert(r); return r;
22
+    }
23
+    public Map<String, Object> myReports(Long reporterId) {
24
+        Map<String, Object> r = new LinkedHashMap<>();
25
+        r.put("reports", mapper.selectList(new LambdaQueryWrapper<PatrolIssueReport>().eq(PatrolIssueReport::getReporterId, reporterId).orderByDesc(PatrolIssueReport::getCreatedAt)));
26
+        r.put("totalCount", mapper.selectCount(new LambdaQueryWrapper<PatrolIssueReport>().eq(PatrolIssueReport::getReporterId, reporterId)));
27
+        return r;
28
+    }
29
+    public PatrolIssueReport getDetail(Long id) { return mapper.selectById(id); }
30
+}

+ 23
- 0
wm-patrol/src/main/java/com/water/patrol/service/PatrolAppProfileService.java Vedi File

1
+package com.water.patrol.service;
2
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
3
+import com.water.patrol.entity.PatrolTask; import com.water.patrol.entity.PatrolWorkOrder;
4
+import com.water.patrol.mapper.PatrolTaskMapper; import com.water.patrol.mapper.PatrolWorkOrderMapper;
5
+import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j;
6
+import org.springframework.stereotype.Service;
7
+import java.util.*;
8
+@Slf4j @Service @RequiredArgsConstructor
9
+public class PatrolAppProfileService {
10
+    private final PatrolTaskMapper taskMapper;
11
+    private final PatrolWorkOrderMapper woMapper;
12
+    public Map<String, Object> getProfile(Long workerId) {
13
+        Map<String, Object> r = new LinkedHashMap<>();
14
+        r.put("workerId", workerId);
15
+        r.put("totalTasks", taskMapper.selectCount(new LambdaQueryWrapper<PatrolTask>().eq(PatrolTask::getWorkerId, workerId)));
16
+        r.put("completedTasks", taskMapper.selectCount(new LambdaQueryWrapper<PatrolTask>().eq(PatrolTask::getWorkerId, workerId).eq(PatrolTask::getStatus, "completed")));
17
+        r.put("totalDistance", 0);
18
+        r.put("totalWorkOrders", woMapper.selectCount(new LambdaQueryWrapper<PatrolWorkOrder>().eq(PatrolWorkOrder::getAssigneeId, workerId)));
19
+        r.put("resolvedWorkOrders", woMapper.selectCount(new LambdaQueryWrapper<PatrolWorkOrder>().eq(PatrolWorkOrder::getAssigneeId, workerId).eq(PatrolWorkOrder::getStatus, "resolved")));
20
+        r.put("reportedIssues", 0);
21
+        return r;
22
+    }
23
+}

+ 42
- 0
wm-patrol/src/main/java/com/water/patrol/service/PatrolAppTaskService.java Vedi File

1
+package com.water.patrol.service;
2
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
3
+import com.water.patrol.entity.PatrolTask; import com.water.patrol.mapper.PatrolTaskMapper;
4
+import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j;
5
+import org.springframework.stereotype.Service;
6
+import java.time.LocalDateTime; import java.util.*;
7
+@Slf4j @Service @RequiredArgsConstructor
8
+public class PatrolAppTaskService {
9
+    private final PatrolTaskMapper mapper;
10
+    public Map<String, Object> myTasks(Long workerId, String status) {
11
+        LambdaQueryWrapper<PatrolTask> w = new LambdaQueryWrapper<>();
12
+        w.eq(PatrolTask::getWorkerId, workerId);
13
+        if (status != null && !status.isEmpty()) w.eq(PatrolTask::getStatus, status);
14
+        w.orderByDesc(PatrolTask::getCreatedAt);
15
+        Map<String, Object> r = new LinkedHashMap<>();
16
+        r.put("tasks", mapper.selectList(w));
17
+        r.put("totalCount", mapper.selectCount(new LambdaQueryWrapper<PatrolTask>().eq(PatrolTask::getWorkerId, workerId)));
18
+        r.put("pendingCount", mapper.selectCount(new LambdaQueryWrapper<PatrolTask>().eq(PatrolTask::getWorkerId, workerId).eq(PatrolTask::getStatus, "pending")));
19
+        r.put("inProgressCount", mapper.selectCount(new LambdaQueryWrapper<PatrolTask>().eq(PatrolTask::getWorkerId, workerId).eq(PatrolTask::getStatus, "in_progress")));
20
+        return r;
21
+    }
22
+    public PatrolTask getDetail(Long id) { return mapper.selectById(id); }
23
+    public void accept(Long id) {
24
+        PatrolTask t = mapper.selectById(id);
25
+        if (t == null) throw new RuntimeException("任务不存在");
26
+        t.setStatus("in_progress"); t.setStartTime(LocalDateTime.now()); mapper.updateById(t);
27
+    }
28
+    public void complete(Long id, Integer cc, Double dist, String remark) {
29
+        PatrolTask t = mapper.selectById(id);
30
+        if (t == null) throw new RuntimeException("任务不存在");
31
+        t.setStatus("completed"); t.setEndTime(LocalDateTime.now());
32
+        t.setCompletedCheckpoints(cc); t.setDistance(dist); t.setRemark(remark);
33
+        mapper.updateById(t);
34
+    }
35
+    public Map<String, Object> myStats(Long workerId) {
36
+        Map<String, Object> r = new LinkedHashMap<>();
37
+        r.put("total", mapper.selectCount(new LambdaQueryWrapper<PatrolTask>().eq(PatrolTask::getWorkerId, workerId)));
38
+        r.put("completed", mapper.selectCount(new LambdaQueryWrapper<PatrolTask>().eq(PatrolTask::getWorkerId, workerId).eq(PatrolTask::getStatus, "completed")));
39
+        r.put("totalDistance", 0);
40
+        return r;
41
+    }
42
+}

+ 36
- 0
wm-patrol/src/main/java/com/water/patrol/service/PatrolAppWorkOrderService.java Vedi File

1
+package com.water.patrol.service;
2
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
3
+import com.water.patrol.entity.PatrolWorkOrder; import com.water.patrol.mapper.PatrolWorkOrderMapper;
4
+import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j;
5
+import org.springframework.stereotype.Service;
6
+import java.time.LocalDateTime; import java.util.*;
7
+@Slf4j @Service @RequiredArgsConstructor
8
+public class PatrolAppWorkOrderService {
9
+    private final PatrolWorkOrderMapper mapper;
10
+    public Map<String, Object> myWorkOrders(Long assigneeId, String status) {
11
+        LambdaQueryWrapper<PatrolWorkOrder> w = new LambdaQueryWrapper<>();
12
+        w.eq(PatrolWorkOrder::getAssigneeId, assigneeId);
13
+        if (status != null && !status.isEmpty()) w.eq(PatrolWorkOrder::getStatus, status);
14
+        w.orderByDesc(PatrolWorkOrder::getCreatedAt);
15
+        Map<String, Object> r = new LinkedHashMap<>();
16
+        r.put("workOrders", mapper.selectList(w));
17
+        r.put("totalCount", mapper.selectCount(new LambdaQueryWrapper<PatrolWorkOrder>().eq(PatrolWorkOrder::getAssigneeId, assigneeId)));
18
+        r.put("pendingCount", mapper.selectCount(new LambdaQueryWrapper<PatrolWorkOrder>().eq(PatrolWorkOrder::getAssigneeId, assigneeId).eq(PatrolWorkOrder::getStatus, "assigned")));
19
+        r.put("inProgressCount", mapper.selectCount(new LambdaQueryWrapper<PatrolWorkOrder>().eq(PatrolWorkOrder::getAssigneeId, assigneeId).eq(PatrolWorkOrder::getStatus, "in_progress")));
20
+        return r;
21
+    }
22
+    public PatrolWorkOrder getDetail(Long id) { return mapper.selectById(id); }
23
+    public void accept(Long id) {
24
+        PatrolWorkOrder wo = mapper.selectById(id);
25
+        if (wo == null) throw new RuntimeException("工单不存在");
26
+        wo.setStatus("in_progress"); mapper.updateById(wo);
27
+    }
28
+    public void resolve(Long id, String resolution, String resultPhotos) {
29
+        PatrolWorkOrder wo = mapper.selectById(id);
30
+        if (wo == null) throw new RuntimeException("工单不存在");
31
+        wo.setStatus("resolved"); wo.setResolution(resolution);
32
+        wo.setResolvedAt(LocalDateTime.now());
33
+        if (resultPhotos != null) wo.setPhotos(resultPhotos);
34
+        mapper.updateById(wo);
35
+    }
36
+}

+ 13
- 0
wm-patrol/src/main/resources/sql/V88__patrol_app.sql Vedi File

1
+CREATE TABLE IF NOT EXISTS pat_issue_report (
2
+    id BIGSERIAL PRIMARY KEY,
3
+    report_no VARCHAR(32) UNIQUE,
4
+    task_id BIGINT, reporter_id BIGINT, reporter_name VARCHAR(50),
5
+    issue_type VARCHAR(30), description TEXT, photos TEXT, videos TEXT,
6
+    lng NUMERIC(10,6), lat NUMERIC(10,6), address VARCHAR(200),
7
+    severity VARCHAR(20) DEFAULT 'medium', status VARCHAR(20) DEFAULT 'submitted',
8
+    work_order_id BIGINT, remark VARCHAR(500),
9
+    created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
10
+);
11
+CREATE INDEX IF NOT EXISTS idx_pat_ir_task ON pat_issue_report(task_id);
12
+CREATE INDEX IF NOT EXISTS idx_pat_ir_reporter ON pat_issue_report(reporter_id);
13
+CREATE INDEX IF NOT EXISTS idx_pat_ir_status ON pat_issue_report(status);

+ 24
- 0
wm-patrol/src/test/java/com/water/patrol/PatrolAppTest.java Vedi File

1
+package com.water.patrol;
2
+import com.water.patrol.entity.*; import com.water.patrol.mapper.*; import com.water.patrol.service.*;
3
+import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith;
4
+import org.mockito.*; import org.mockito.junit.jupiter.MockitoExtension;
5
+import java.math.BigDecimal; import java.util.*;
6
+import static org.junit.jupiter.api.Assertions.*;
7
+import static org.mockito.Mockito.*;
8
+@ExtendWith(MockitoExtension.class)
9
+class PatrolAppTest {
10
+    @Mock PatrolTaskMapper taskMapper; @Mock PatrolWorkOrderMapper woMapper; @Mock PatrolIssueReportMapper irMapper;
11
+    @InjectMocks PatrolAppTaskService taskSvc; @InjectMocks PatrolAppWorkOrderService woSvc;
12
+    @InjectMocks PatrolAppIssueService issueSvc; @InjectMocks PatrolAppProfileService profileSvc;
13
+
14
+    @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")); }
15
+    @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()); }
16
+    @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()); }
17
+    @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); }
18
+    @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()); }
19
+    @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()); }
20
+    @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()); }
21
+    @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")); }
22
+    @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")); }
23
+    @Test void testMyStats() { when(taskMapper.selectCount(any())).thenReturn(5L); Map<String,Object> r=taskSvc.myStats(100L); assertNotNull(r); }
24
+}