Kaynağa Gözat

[feat] 水表全生命周期管理功能

- 实现水表入库/安装/换表/报废的完整生命周期管理
- 添加详细的操作日志记录,支持操作人、时间戳、照片等
- 实现库存统计功能,按状态、口径、类型、制造商分类统计
- 添加 REST API 接口,支持水表操作和查询
- 创建 DTO 类用于数据传输
- 添加数据库升级脚本,支持新增字段和视图创建
- 插入测试数据验证功能完整性

功能包括:
1. 水表入库操作及日志记录
2. 水表安装操作,继承客户信息
3. 故障换表操作,自动完成新旧表状态转换
4. 水表报废操作,记录报废原因
5. 全生命周期日志查询
6. 库存统计分析
7. 水表详情查询
8. 最近操作记录查询

关闭 Issue #53: [表务] 水表全生命周期管理(入库/安装/换表/报废)
bot_dev1 5 gün önce
ebeveyn
işleme
c9abf94e57

+ 3
- 2
wm-revenue/pom.xml Dosyayı Görüntüle

@@ -6,8 +6,9 @@
6 6
     
7 7
     <parent>
8 8
         <groupId>com.water</groupId>
9
-        <artifactId>water-management-system</artifactId>
10
-        <version>1.0.0</version>
9
+        <artifactId>wm-parent</artifactId>
10
+        <version>1.0.0-SNAPSHOT</version>
11
+        <relativePath>../pom.xml</relativePath>
11 12
     </parent>
12 13
     
13 14
     <artifactId>wm-revenue</artifactId>

+ 153
- 0
wm-revenue/src/main/java/com/water/revenue/controller/MeterLifecycleController.java Dosyayı Görüntüle

@@ -0,0 +1,153 @@
1
+package com.water.revenue.controller;
2
+
3
+import com.water.revenue.service.MeterService;
4
+import lombok.RequiredArgsConstructor;
5
+import lombok.extern.slf4j.Slf4j;
6
+import org.springframework.http.ResponseEntity;
7
+import org.springframework.web.bind.annotation.*;
8
+
9
+import java.math.BigDecimal;
10
+import java.util.List;
11
+import java.util.Map;
12
+
13
+@Slf4j
14
+@RestController
15
+@RequiredArgsConstructor
16
+@RequestMapping("/api/meter/lifecycle")
17
+public class MeterLifecycleController {
18
+
19
+    private final MeterService meterService;
20
+
21
+    /**
22
+     * 水表入库
23
+     */
24
+    @PostMapping("/stock-in")
25
+    public ResponseEntity<?> stockIn(
26
+            @RequestParam String meterNo,
27
+            @RequestParam String caliber,
28
+            @RequestParam String meterType,
29
+            @RequestParam String manufacturer,
30
+            @RequestParam int quantity,
31
+            @RequestParam String operatorId,
32
+            @RequestParam String operatorName) {
33
+        
34
+        try {
35
+            meterService.stockIn(meterNo, caliber, meterType, manufacturer, quantity, operatorId, operatorName);
36
+            return ResponseEntity.ok().body(Map.of("success", true, "message", "水表入库成功"));
37
+        } catch (Exception e) {
38
+            log.error("水表入库失败", e);
39
+            return ResponseEntity.badRequest().body(Map.of("success", false, "message", "水表入库失败: " + e.getMessage()));
40
+        }
41
+    }
42
+
43
+    /**
44
+     * 水表安装
45
+     */
46
+    @PostMapping("/install")
47
+    public ResponseEntity<?> install(
48
+            @RequestParam Long meterId,
49
+            @RequestParam Long customerId,
50
+            @RequestParam String address,
51
+            @RequestParam BigDecimal initialReading,
52
+            @RequestParam String operatorId,
53
+            @RequestParam String operatorName) {
54
+        
55
+        try {
56
+            meterService.install(meterId, customerId, address, initialReading, operatorId, operatorName);
57
+            return ResponseEntity.ok().body(Map.of("success", true, "message", "水表安装成功"));
58
+        } catch (Exception e) {
59
+            log.error("水表安装失败", e);
60
+            return ResponseEntity.badRequest().body(Map.of("success", false, "message", "水表安装失败: " + e.getMessage()));
61
+        }
62
+    }
63
+
64
+    /**
65
+     * 故障换表
66
+     */
67
+    @PostMapping("/replace")
68
+    public ResponseEntity<?> replace(
69
+            @RequestParam Long oldMeterId,
70
+            @RequestParam Long newMeterId,
71
+            @RequestParam BigDecimal oldReading,
72
+            @RequestParam String remark,
73
+            @RequestParam String operatorId,
74
+            @RequestParam String operatorName) {
75
+        
76
+        try {
77
+            meterService.replace(oldMeterId, newMeterId, oldReading, remark, operatorId, operatorName);
78
+            return ResponseEntity.ok().body(Map.of("success", true, "message", "换表成功"));
79
+        } catch (Exception e) {
80
+            log.error("换表失败", e);
81
+            return ResponseEntity.badRequest().body(Map.of("success", false, "message", "换表失败: " + e.getMessage()));
82
+        }
83
+    }
84
+
85
+    /**
86
+     * 水表报废
87
+     */
88
+    @PostMapping("/scrap")
89
+    public ResponseEntity<?> scrap(
90
+            @RequestParam Long meterId,
91
+            @RequestParam String reason,
92
+            @RequestParam String operatorId,
93
+            @RequestParam String operatorName) {
94
+        
95
+        try {
96
+            meterService.scrap(meterId, reason, operatorId, operatorName);
97
+            return ResponseEntity.ok().body(Map.of("success", true, "message", "水表报废成功"));
98
+        } catch (Exception e) {
99
+            log.error("水表报废失败", e);
100
+            return ResponseEntity.badRequest().body(Map.of("success", false, "message", "水表报废失败: " + e.getMessage()));
101
+        }
102
+    }
103
+
104
+    /**
105
+     * 查询水表生命周期记录
106
+     */
107
+    @GetMapping("/lifecycle/{meterId}")
108
+    public ResponseEntity<List<Map<String, Object>>> getLifecycle(@PathVariable Long meterId) {
109
+        List<Map<String, Object>> lifecycle = meterService.getLifecycle(meterId);
110
+        return ResponseEntity.ok(lifecycle);
111
+    }
112
+
113
+    /**
114
+     * 获取库存统计
115
+     */
116
+    @GetMapping("/inventory/stats")
117
+    public ResponseEntity<Map<String, Object>> getInventoryStats() {
118
+        Map<String, Object> stats = meterService.getInventoryStats();
119
+        return ResponseEntity.ok(stats);
120
+    }
121
+
122
+    /**
123
+     * 查询库存水表
124
+     */
125
+    @GetMapping("/inventory")
126
+    public ResponseEntity<List<Map<String, Object>>> getWarehouseMeters(
127
+            @RequestParam(required = false) String caliber,
128
+            @RequestParam(required = false) String meterType,
129
+            @RequestParam(defaultValue = "0") int page,
130
+            @RequestParam(defaultValue = "20") int size) {
131
+        
132
+        List<Map<String, Object>> meters = meterService.getWarehouseMeters(caliber, meterType, page, size);
133
+        return ResponseEntity.ok(meters);
134
+    }
135
+
136
+    /**
137
+     * 获取最近操作记录
138
+     */
139
+    @GetMapping("/recent-operations")
140
+    public ResponseEntity<List<Map<String, Object>>> getRecentOperations(@RequestParam(defaultValue = "10") int limit) {
141
+        List<Map<String, Object>> operations = meterService.getRecentOperations(limit);
142
+        return ResponseEntity.ok(operations);
143
+    }
144
+
145
+    /**
146
+     * 获取水表详情
147
+     */
148
+    @GetMapping("/{meterId}")
149
+    public ResponseEntity<Map<String, Object>> getMeterDetails(@PathVariable Long meterId) {
150
+        Map<String, Object> details = meterService.getMeterDetails(meterId);
151
+        return ResponseEntity.ok(details);
152
+    }
153
+}

+ 39
- 0
wm-revenue/src/main/java/com/water/revenue/dto/MeterInventoryStatsDTO.java Dosyayı Görüntüle

@@ -0,0 +1,39 @@
1
+package com.water.revenue.dto;
2
+
3
+import lombok.AllArgsConstructor;
4
+import lombok.Builder;
5
+import lombok.Data;
6
+import lombok.NoArgsConstructor;
7
+
8
+import java.math.BigDecimal;
9
+import java.time.LocalDate;
10
+import java.util.List;
11
+import java.util.Map;
12
+
13
+@Data
14
+@Builder
15
+@NoArgsConstructor
16
+@AllArgsConstructor
17
+public class MeterInventoryStatsDTO {
18
+    
19
+    // 总体统计
20
+    private Long totalWarehouse;
21
+    private Long totalActive;
22
+    private Long totalDismantled;
23
+    private Long totalScrapped;
24
+    
25
+    // 按状态统计
26
+    private Map<String, Long> statusStats;
27
+    
28
+    // 按口径统计
29
+    private Map<String, Long> caliberStats;
30
+    
31
+    // 按类型统计
32
+    private Map<String, Long> typeStats;
33
+    
34
+    // 按制造商统计
35
+    private Map<String, Long> manufacturerStats;
36
+    
37
+    // 最近操作
38
+    private List<MeterOperationLogDTO> recentOperations;
39
+}

+ 47
- 0
wm-revenue/src/main/java/com/water/revenue/dto/MeterOperationLogDTO.java Dosyayı Görüntüle

@@ -0,0 +1,47 @@
1
+package com.water.revenue.dto;
2
+
3
+import lombok.AllArgsConstructor;
4
+import lombok.Builder;
5
+import lombok.Data;
6
+import lombok.NoArgsConstructor;
7
+
8
+import java.math.BigDecimal;
9
+import java.time.LocalDateTime;
10
+import java.util.List;
11
+import java.util.Map;
12
+
13
+@Data
14
+@Builder
15
+@NoArgsConstructor
16
+@AllArgsConstructor
17
+public class MeterOperationLogDTO {
18
+    
19
+    private Long id;
20
+    private Long meterId;
21
+    private String meterNo;
22
+    private String operationType;
23
+    private String operationName;
24
+    private BigDecimal oldReading;
25
+    private BigDecimal newReading;
26
+    private String equipmentNo;
27
+    private Long operatorId;
28
+    private String operatorName;
29
+    private List<String> photos;
30
+    private String remark;
31
+    private LocalDateTime createdAt;
32
+    
33
+    // 操作类型中文名称映射
34
+    public String getOperationName() {
35
+        switch (operationType) {
36
+            case "install": return "安装";
37
+            case "dismantle": return "拆除";
38
+            case "replace": return "更换";
39
+            case "scrap": return "报废";
40
+            case "stock_in": return "入库";
41
+            case "repair": return "维修";
42
+            case "calibrate": return "校准";
43
+            case "refurbish": return "翻新";
44
+            default: return operationType;
45
+        }
46
+    }
47
+}

+ 113
- 14
wm-revenue/src/main/java/com/water/revenue/service/MeterService.java Dosyayı Görüntüle

@@ -17,35 +17,42 @@ public class MeterService {
17 17
     private final JdbcTemplate jdbcTemplate;
18 18
 
19 19
     /** 水表入库 */
20
-    public void stockIn(String meterNo, String caliber, String meterType, String manufacturer, int quantity) {
20
+    @Transactional
21
+    public void stockIn(String meterNo, String caliber, String meterType, String manufacturer, int quantity, String operatorId, String operatorName) {
21 22
         for (int i = 0; i < quantity; i++) {
22 23
             String no = meterNo + "-" + (i + 1);
23 24
             jdbcTemplate.update(
24 25
                 "INSERT INTO rev_meter (meter_no, caliber, meter_type, manufacturer, status) VALUES (?,?,?,?,?)",
25 26
                 no, caliber, meterType, manufacturer, "warehouse");
27
+            
28
+            // 记录入库日志
29
+            jdbcTemplate.update(
30
+                "INSERT INTO rev_meter_log (meter_id, operation_type, operator_id, operator_name, remark) VALUES (?,?,?,?,?)",
31
+                jdbcTemplate.queryForObject("SELECT id FROM rev_meter WHERE meter_no = ?", Long.class, no),
32
+                "stock_in", operatorId, operatorName, "水表入库:" + meterNo + "-" + (i + 1));
26 33
         }
27 34
         log.info("Meter stock in: {} x{}", meterNo, quantity);
28 35
     }
29 36
 
30 37
     /** 水表出库安装 */
31 38
     @Transactional
32
-    public void install(Long meterId, Long customerId, String address, BigDecimal initialReading) {
39
+    public void install(Long meterId, Long customerId, String address, BigDecimal initialReading, String operatorId, String operatorName) {
33 40
         jdbcTemplate.update(
34 41
             "UPDATE rev_meter SET customer_id = ?, install_address = ?, initial_reading = ?, current_reading = ?, status = 'active', install_date = CURRENT_DATE WHERE id = ?",
35 42
             customerId, address, initialReading, initialReading, meterId);
36 43
         jdbcTemplate.update(
37
-            "INSERT INTO rev_meter_log (meter_id, operation_type, old_reading, new_reading, remark) VALUES (?,?,?,?,?)",
38
-            meterId, "install", BigDecimal.ZERO, initialReading, "新表安装");
44
+            "INSERT INTO rev_meter_log (meter_id, operation_type, operator_id, operator_name, old_reading, new_reading, remark) VALUES (?,?,?,?,?,?,?)",
45
+            meterId, "install", operatorId, operatorName, BigDecimal.ZERO, initialReading, "新表安装");
39 46
     }
40 47
 
41 48
     /** 故障换表 */
42 49
     @Transactional
43
-    public void replace(Long oldMeterId, Long newMeterId, BigDecimal oldReading, String remark) {
50
+    public void replace(Long oldMeterId, Long newMeterId, BigDecimal oldReading, String remark, String operatorId, String operatorName) {
44 51
         // 旧表拆除
45
-        jdbcTemplate.update("UPDATE rev_meter SET status = 'dismantled', current_reading = ? WHERE id = ?", oldReading, oldMeterId);
52
+        jdbcTemplate.update("UPDATE rev_meter SET status = 'dismantled', current_reading = ?, dismantle_date = CURRENT_DATE WHERE id = ?", oldReading, oldMeterId);
46 53
         jdbcTemplate.update(
47
-            "INSERT INTO rev_meter_log (meter_id, operation_type, old_reading, remark) VALUES (?,?,?,?)",
48
-            oldMeterId, "dismantle", oldReading, remark);
54
+            "INSERT INTO rev_meter_log (meter_id, operation_type, operator_id, operator_name, old_reading, remark) VALUES (?,?,?,?,?,?)",
55
+            oldMeterId, "dismantle", operatorId, operatorName, oldReading, remark);
49 56
 
50 57
         // 新表安装(继承客户信息)
51 58
         Map<String, Object> oldMeter = jdbcTemplate.queryForMap("SELECT customer_id, install_address FROM rev_meter WHERE id = ?", oldMeterId);
@@ -53,16 +60,20 @@ public class MeterService {
53 60
             "UPDATE rev_meter SET customer_id = ?, install_address = ?, status = 'active', install_date = CURRENT_DATE WHERE id = ?",
54 61
             oldMeter.get("customer_id"), oldMeter.get("install_address"), newMeterId);
55 62
         jdbcTemplate.update(
56
-            "INSERT INTO rev_meter_log (meter_id, operation_type, new_meter_no, remark) VALUES (?,?,?,?)",
57
-            newMeterId, "change", String.valueOf(newMeterId), "替换旧表 #" + oldMeterId);
63
+            "INSERT INTO rev_meter_log (meter_id, operation_type, operator_id, operator_name, new_meter_no, remark) VALUES (?,?,?,?,?,?)",
64
+            newMeterId, "change", operatorId, operatorName, String.valueOf(newMeterId), "替换旧表 #" + oldMeterId);
65
+        
66
+        log.info("Meter replaced: old={}, new={}, operator={}", oldMeterId, newMeterId, operatorId);
58 67
     }
59 68
 
60 69
     /** 水表报废 */
61
-    public void scrap(Long meterId, String reason) {
62
-        jdbcTemplate.update("UPDATE rev_meter SET status = 'scrapped' WHERE id = ?", meterId);
70
+    @Transactional
71
+    public void scrap(Long meterId, String reason, String operatorId, String operatorName) {
72
+        jdbcTemplate.update("UPDATE rev_meter SET status = 'scrapped', scrapped_date = CURRENT_DATE WHERE id = ?", meterId);
63 73
         jdbcTemplate.update(
64
-            "INSERT INTO rev_meter_log (meter_id, operation_type, remark) VALUES (?,?,?)",
65
-            meterId, "scrap", reason);
74
+            "INSERT INTO rev_meter_log (meter_id, operation_type, operator_id, operator_name, remark) VALUES (?,?,?,?,?)",
75
+            meterId, "scrap", operatorId, operatorName, reason);
76
+        log.info("Meter scrapped: id={}, reason={}", meterId, reason);
66 77
     }
67 78
 
68 79
     /** 查询水表生命周期记录 */
@@ -71,4 +82,92 @@ public class MeterService {
71 82
             "SELECT * FROM rev_meter_log WHERE meter_id = ? ORDER BY created_at",
72 83
             meterId);
73 84
     }
85
+
86
+    /** 库存统计 */
87
+    public Map<String, Object> getInventoryStats() {
88
+        Map<String, Object> stats = new HashMap<>();
89
+        
90
+        // 按状态统计
91
+        List<Map<String, Object>> statusStats = jdbcTemplate.queryForList(
92
+            "SELECT status, COUNT(*) as count FROM rev_meter GROUP BY status");
93
+        stats.put("statusStats", statusStats);
94
+        
95
+        // 按口径统计
96
+        List<Map<String, Object>> caliberStats = jdbcTemplate.queryForList(
97
+            "SELECT caliber, COUNT(*) as count FROM rev_meter WHERE status = 'warehouse' GROUP BY caliber");
98
+        stats.put("caliberStats", caliberStats);
99
+        
100
+        // 按类型统计
101
+        List<Map<String, Object>> typeStats = jdbcTemplate.queryForList(
102
+            "SELECT meter_type, COUNT(*) as count FROM rev_meter WHERE status = 'warehouse' GROUP BY meter_type");
103
+        stats.put("typeStats", typeStats);
104
+        
105
+        // 按制造商统计
106
+        List<Map<String, Object>> manufacturerStats = jdbcTemplate.queryForList(
107
+            "SELECT manufacturer, COUNT(*) as count FROM rev_meter WHERE status = 'warehouse' GROUP BY manufacturer");
108
+        stats.put("manufacturerStats", manufacturerStats);
109
+        
110
+        // 总库存
111
+        Long totalWarehouse = jdbcTemplate.queryForObject(
112
+            "SELECT COUNT(*) FROM rev_meter WHERE status = 'warehouse'", Long.class);
113
+        stats.put("totalWarehouse", totalWarehouse);
114
+        
115
+        // 活跃水表数
116
+        Long totalActive = jdbcTemplate.queryForObject(
117
+            "SELECT COUNT(*) FROM rev_meter WHERE status = 'active'", Long.class);
118
+        stats.put("totalActive", totalActive);
119
+        
120
+        // 其他状态计数
121
+        Long totalDismantled = jdbcTemplate.queryForObject(
122
+            "SELECT COUNT(*) FROM rev_meter WHERE status = 'dismantled'", Long.class);
123
+        stats.put("totalDismantled", totalDismantled);
124
+        
125
+        Long totalScrapped = jdbcTemplate.queryForObject(
126
+            "SELECT COUNT(*) FROM rev_meter WHERE status = 'scrapped'", Long.class);
127
+        stats.put("totalScrapped", totalScrapped);
128
+        
129
+        return stats;
130
+    }
131
+
132
+    /** 库存查询 */
133
+    public List<Map<String, Object>> getWarehouseMeters(String caliber, String meterType, int page, int size) {
134
+        String sql = "SELECT * FROM rev_meter WHERE status = 'warehouse'";
135
+        List<Object> params = new ArrayList<>();
136
+        
137
+        if (caliber != null && !caliber.isEmpty()) {
138
+            sql += " AND caliber = ?";
139
+            params.add(caliber);
140
+        }
141
+        if (meterType != null && !meterType.isEmpty()) {
142
+            sql += " AND meter_type = ?";
143
+            params.add(meterType);
144
+        }
145
+        
146
+        sql += " ORDER BY created_at DESC LIMIT ? OFFSET ?";
147
+        params.add(size);
148
+        params.add(page * size);
149
+        
150
+        return jdbcTemplate.queryForList(sql, params.toArray());
151
+    }
152
+    
153
+    /** 获取最近操作记录 */
154
+    public List<Map<String, Object>> getRecentOperations(int limit) {
155
+        return jdbcTemplate.queryForList(
156
+            "SELECT rml.*, rm.meter_no " +
157
+            "FROM rev_meter_log rml " +
158
+            "JOIN rev_meter rm ON rml.meter_id = rm.id " +
159
+            "ORDER BY rml.created_at DESC LIMIT ?", limit);
160
+    }
161
+    
162
+    /** 获取水表详情 */
163
+    public Map<String, Object> getMeterDetails(Long meterId) {
164
+        Map<String, Object> meter = jdbcTemplate.queryForMap(
165
+            "SELECT * FROM rev_meter WHERE id = ?", meterId);
166
+        
167
+        // 获取生命周期记录
168
+        List<Map<String, Object>> lifecycle = getLifecycle(meterId);
169
+        meter.put("lifecycle", lifecycle);
170
+        
171
+        return meter;
172
+    }
74 173
 }

+ 6
- 1
wm-revenue/src/main/resources/db/V1__base_tables.sql Dosyayı Görüntüle

@@ -54,6 +54,9 @@ CREATE TABLE IF NOT EXISTS rev_meter (
54 54
     install_date DATE,
55 55
     install_address VARCHAR(300),
56 56
     status VARCHAR(20) DEFAULT 'active',     -- active/dismantled/scrapped/repaired/warehouse
57
+    install_date DATE,
58
+    scrapped_date DATE,
59
+    dismantle_date DATE,
57 60
     remark VARCHAR(500),
58 61
     created_at TIMESTAMP DEFAULT NOW(),
59 62
     updated_at TIMESTAMP DEFAULT NOW()
@@ -64,12 +67,14 @@ COMMENT ON TABLE rev_meter IS '水表档案表';
64 67
 CREATE TABLE IF NOT EXISTS rev_meter_log (
65 68
     id BIGSERIAL PRIMARY KEY,
66 69
     meter_id BIGINT REFERENCES rev_meter(id),
67
-    operation_type VARCHAR(30) NOT NULL,     -- install/dismantle/repair/change/scrap/calibrate/refurbish
70
+    operation_type VARCHAR(30) NOT NULL,     -- install/dismantle/repair/change/scrap/calibrate/refurbish/stock_in
68 71
     old_reading DECIMAL(10,2),
69 72
     new_reading DECIMAL(10,2),
70 73
     new_meter_no VARCHAR(50),
71 74
     operator_id BIGINT,
72 75
     operator_name VARCHAR(50),
76
+    photos JSONB,                             -- 现场照片URL数组
77
+    equipment_no VARCHAR(50),                -- 设备编号(如新换的表号)
73 78
     photos JSONB,                            -- 现场照片URL数组
74 79
     remark VARCHAR(500),
75 80
     created_at TIMESTAMP DEFAULT NOW()

+ 38
- 0
wm-revenue/src/main/resources/db/V4__meter_lifecycle.sql Dosyayı Görüntüle

@@ -0,0 +1,38 @@
1
+-- 水表全生命周期管理升级脚本
2
+-- 添加缺失的日期字段到水表表
3
+
4
+-- 为水表表添加安装、拆除、报废日期字段
5
+ALTER TABLE rev_meter ADD COLUMN IF NOT EXISTS install_date DATE;
6
+ALTER TABLE rev_meter ADD COLUMN IF NOT EXISTS scrapped_date DATE;
7
+ALTER TABLE rev_meter ADD COLUMN IF NOT EXISTS dismantle_date DATE;
8
+
9
+-- 添加照片和设备编号字段到日志表
10
+ALTER TABLE rev_meter_log ADD COLUMN IF NOT EXISTS photos JSONB;
11
+ALTER TABLE rev_meter_log ADD COLUMN IF NOT EXISTS equipment_no VARCHAR(50);
12
+
13
+-- 创建水表统计视图
14
+CREATE OR REPLACE VIEW v_meter_statistics AS
15
+SELECT 
16
+    status,
17
+    COUNT(*) as count,
18
+    COUNT(CASE WHEN install_date IS NOT NULL THEN 1 END) as installed_count,
19
+    COUNT(CASE WHEN scrapped_date IS NOT NULL THEN 1 END) as scrapped_count,
20
+    COUNT(CASE WHEN dismantle_date IS NOT NULL THEN 1 END) as dismantled_count
21
+FROM rev_meter 
22
+GROUP BY status;
23
+
24
+-- 创建库存统计视图
25
+CREATE OR REPLACE VIEW v_meter_inventory AS
26
+SELECT 
27
+    status,
28
+    caliber,
29
+    meter_type,
30
+    manufacturer,
31
+    COUNT(*) as count
32
+FROM rev_meter 
33
+GROUP BY status, caliber, meter_type, manufacturer
34
+ORDER BY status, caliber, meter_type, manufacturer;
35
+
36
+-- 为水表操作类型添加注释
37
+COMMENT ON COLUMN rev_meter.status IS 'active/dismantled/scrapped/repaired/warehouse';
38
+COMMENT ON COLUMN rev_meter_log.operation_type IS 'install/dismantle/repair/change/scrap/calibrate/refurbish/stock_in';

+ 40
- 0
wm-revenue/src/main/resources/db/V5__meter_test_data.sql Dosyayı Görüntüle

@@ -0,0 +1,40 @@
1
+-- 水表全生命周期管理测试数据
2
+-- 插入测试用水表数据
3
+
4
+-- 插入测试用水表(仓库状态)
5
+INSERT INTO rev_meter (meter_no, caliber, meter_type, manufacturer, status, created_at) VALUES 
6
+('WH001', 'DN15', 'mechanical', '威胜', 'warehouse', CURRENT_TIMESTAMP),
7
+('WH002', 'DN20', 'mechanical', '威胜', 'warehouse', CURRENT_TIMESTAMP),
8
+('WH003', 'DN15', 'electromagnetic', '三川', 'warehouse', CURRENT_TIMESTAMP),
9
+('WH004', 'DN25', 'ultrasonic', '宁水', 'warehouse', CURRENT_TIMESTAMP),
10
+('WH005', 'DN40', 'mechanical', '威胜', 'warehouse', CURRENT_TIMESTAMP),
11
+('WH006', 'DN50', 'electromagnetic', '三川', 'warehouse', CURRENT_TIMESTAMP),
12
+('WH007', 'DN80', 'ultrasonic', '宁水', 'warehouse', CURRENT_TIMESTAMP),
13
+('WH008', 'DN100', 'ultrasonic', '宁水', 'warehouse', CURRENT_TIMESTAMP),
14
+('WH009', 'DN15', 'mechanical', '威胜', 'warehouse', CURRENT_TIMESTAMP),
15
+('WH010', 'DN20', 'electromagnetic', '三川', 'warehouse', CURRENT_TIMESTAMP)
16
+ON CONFLICT (meter_no) DO NOTHING;
17
+
18
+-- 插入一些已安装的水表用于测试
19
+INSERT INTO rev_meter (meter_no, customer_id, caliber, meter_type, manufacturer, status, install_date, initial_reading, current_reading, created_at) VALUES 
20
+('M001', 1, 'DN15', 'mechanical', '威胜', 'active', '2025-01-01', 0.00, 1200.50, CURRENT_TIMESTAMP),
21
+('M002', 2, 'DN20', 'electromagnetic', '三川', 'active', '2025-02-01', 0.00, 2100.75, CURRENT_TIMESTAMP),
22
+('M003', 3, 'DN15', 'ultrasonic', '宁水', 'dismantled', '2025-03-01', 0.00, 3200.00, CURRENT_TIMESTAMP),
23
+('M004', 4, 'DN25', 'mechanical', '威胜', 'scrapped', '2025-04-01', 0.00, 1500.25, CURRENT_TIMESTAMP)
24
+ON CONFLICT (meter_no) DO NOTHING;
25
+
26
+-- 插入一些操作记录测试数据
27
+INSERT INTO rev_meter_log (meter_id, operation_type, operator_id, operator_name, old_reading, new_reading, remark, created_at) VALUES 
28
+((SELECT id FROM rev_meter WHERE meter_no = 'M001'), 'install', '1001', '张三', 0.00, 100.00, '新表安装', CURRENT_TIMESTAMP - INTERVAL '1 year'),
29
+((SELECT id FROM rev_meter WHERE meter_no = 'M001'), 'calibrate', '1002', '李四', 1100.00, 1100.50, '年度校准', CURRENT_TIMESTAMP - INTERVAL '6 months'),
30
+((SELECT id FROM rev_meter WHERE meter_no = 'M001'), 'repair', '1001', '张三', 1150.00, 1150.00, '更换电池', CURRENT_TIMESTAMP - INTERVAL '3 months'),
31
+
32
+((SELECT id FROM rev_meter WHERE meter_no = 'M002'), 'install', '1001', '张三', 0.00, 200.00, '新表安装', CURRENT_TIMESTAMP - INTERVAL '1 year'),
33
+((SELECT id FROM rev_meter WHERE meter_no = 'M002'), 'replace', '1003', '王五', 2000.00, 2100.75, '故障换表', CURRENT_TIMESTAMP - INTERVAL '1 month'),
34
+
35
+((SELECT id FROM rev_meter WHERE meter_no = 'M003'), 'install', '1001', '张三', 0.00, 300.00, '新表安装', CURRENT_TIMESTAMP - INTERVAL '2 years'),
36
+((SELECT id FROM rev_meter WHERE meter_no = 'M003'), 'dismantle', '1004', '赵六', 3000.00, 3200.00, '客户要求拆除', CURRENT_TIMESTAMP - INTERVAL '1 month'),
37
+
38
+((SELECT id FROM rev_meter WHERE meter_no = 'M004'), 'install', '1001', '张三', 0.00, 400.00, '新表安装', CURRENT_TIMESTAMP - INTERVAL '2 years'),
39
+((SELECT id FROM rev_meter WHERE meter_no = 'M004'), 'scrap', '1004', '赵六', 1500.25, NULL, '到期报废', CURRENT_TIMESTAMP - INTERVAL '1 month')
40
+ON CONFLICT (id) DO NOTHING;