feat(wm-revenue): #53 水表全生命周期管理
- Entity: WaterMeter, MeterInstallRecord, MeterReplaceRecord, MeterLifecycleLog - Enum: MeterStatus (IN_STOCK/INSTALLED/DISMANTLED/SCRAPPED/REPAIRING) - Mapper: WaterMeterMapper, MeterInstallRecordMapper, MeterReplaceRecordMapper, MeterLifecycleLogMapper - Service: MeterLifecycleService (入库/安装/查询), MeterReplaceService (换表/报废/审批), MeterStatsService (统计) - Controller: MeterController (/api/revenue/meter/*), MeterReplaceController (/api/revenue/meter-replace/*), MeterStatsController (/api/revenue/meter-stats/*) - DDL: V_meter_lifecycle.sql (4张表 + 索引) - Test: MeterLifecycleTest (12个测试用例)
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
-- ============================================================
|
||||
-- 水表全生命周期管理 DDL
|
||||
-- 包含: rev_water_meter, rev_meter_install_record,
|
||||
-- rev_meter_replace_record, rev_meter_lifecycle_log
|
||||
-- ============================================================
|
||||
|
||||
-- 1. 水表主表
|
||||
CREATE TABLE IF NOT EXISTS rev_water_meter (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
meter_no VARCHAR(64) NOT NULL UNIQUE,
|
||||
model VARCHAR(64),
|
||||
diameter INTEGER,
|
||||
manufacturer VARCHAR(128),
|
||||
production_date DATE,
|
||||
install_date DATE,
|
||||
install_address VARCHAR(512),
|
||||
customer_no VARCHAR(64),
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'IN_STOCK',
|
||||
in_stock_time TIMESTAMP,
|
||||
out_stock_time TIMESTAMP,
|
||||
initial_reading NUMERIC(14,4) DEFAULT 0,
|
||||
current_reading NUMERIC(14,4) DEFAULT 0,
|
||||
dismantle_date DATE,
|
||||
scrapped_date DATE,
|
||||
remark VARCHAR(512),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
COMMENT ON TABLE rev_water_meter IS '水表主表';
|
||||
COMMENT ON COLUMN rev_water_meter.meter_no IS '水表编号';
|
||||
COMMENT ON COLUMN rev_water_meter.model IS '水表型号';
|
||||
COMMENT ON COLUMN rev_water_meter.diameter IS '口径(mm)';
|
||||
COMMENT ON COLUMN rev_water_meter.manufacturer IS '制造商';
|
||||
COMMENT ON COLUMN rev_water_meter.production_date IS '生产日期';
|
||||
COMMENT ON COLUMN rev_water_meter.install_date IS '安装日期';
|
||||
COMMENT ON COLUMN rev_water_meter.install_address IS '安装地址';
|
||||
COMMENT ON COLUMN rev_water_meter.customer_no IS '客户编号';
|
||||
COMMENT ON COLUMN rev_water_meter.status IS '状态: IN_STOCK/INSTALLED/DISMANTLED/SCRAPPED/REPAIRING';
|
||||
COMMENT ON COLUMN rev_water_meter.in_stock_time IS '入库时间';
|
||||
COMMENT ON COLUMN rev_water_meter.out_stock_time IS '出库时间';
|
||||
COMMENT ON COLUMN rev_water_meter.initial_reading IS '初始读数';
|
||||
COMMENT ON COLUMN rev_water_meter.current_reading IS '当前读数';
|
||||
COMMENT ON COLUMN rev_water_meter.dismantle_date IS '拆除日期';
|
||||
COMMENT ON COLUMN rev_water_meter.scrapped_date IS '报废日期';
|
||||
|
||||
-- 2. 水表安装记录
|
||||
CREATE TABLE IF NOT EXISTS rev_meter_install_record (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
meter_no VARCHAR(64) NOT NULL,
|
||||
customer_no VARCHAR(64),
|
||||
installer VARCHAR(128),
|
||||
install_date DATE,
|
||||
install_address VARCHAR(512),
|
||||
old_meter_no VARCHAR(64),
|
||||
initial_reading NUMERIC(14,4) DEFAULT 0,
|
||||
remark VARCHAR(512),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
COMMENT ON TABLE rev_meter_install_record IS '水表安装记录';
|
||||
|
||||
-- 3. 水表换表记录
|
||||
CREATE TABLE IF NOT EXISTS rev_meter_replace_record (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
old_meter_no VARCHAR(64) NOT NULL,
|
||||
new_meter_no VARCHAR(64) NOT NULL,
|
||||
customer_no VARCHAR(64),
|
||||
replace_type VARCHAR(32) NOT NULL,
|
||||
reason VARCHAR(512),
|
||||
replacer VARCHAR(128),
|
||||
replace_date DATE,
|
||||
old_reading NUMERIC(14,4),
|
||||
new_reading NUMERIC(14,4),
|
||||
approval_status VARCHAR(32) DEFAULT 'APPROVED',
|
||||
approver VARCHAR(128),
|
||||
approval_time TIMESTAMP,
|
||||
remark VARCHAR(512),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
COMMENT ON TABLE rev_meter_replace_record IS '水表换表记录';
|
||||
COMMENT ON COLUMN rev_meter_replace_record.replace_type IS '换表类型: FAULT-故障, EXPIRED-到期';
|
||||
COMMENT ON COLUMN rev_meter_replace_record.approval_status IS '审批状态: PENDING/APPROVED/REJECTED';
|
||||
|
||||
-- 4. 水表生命周期日志
|
||||
CREATE TABLE IF NOT EXISTS rev_meter_lifecycle_log (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
meter_no VARCHAR(64) NOT NULL,
|
||||
action_type VARCHAR(32) NOT NULL,
|
||||
action_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
operator VARCHAR(128),
|
||||
detail VARCHAR(1024),
|
||||
old_meter_no VARCHAR(64),
|
||||
new_meter_no VARCHAR(64),
|
||||
customer_no VARCHAR(64),
|
||||
remark VARCHAR(512),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
COMMENT ON TABLE rev_meter_lifecycle_log IS '水表生命周期日志';
|
||||
COMMENT ON COLUMN rev_meter_lifecycle_log.action_type IS '操作类型: STOCK_IN/INSTALL/DISMANTLE/REPLACE/SCRAP/REPAIR';
|
||||
|
||||
-- ============================================================
|
||||
-- 索引
|
||||
-- ============================================================
|
||||
|
||||
-- rev_water_meter 索引
|
||||
CREATE INDEX IF NOT EXISTS idx_water_meter_status ON rev_water_meter (status);
|
||||
CREATE INDEX IF NOT EXISTS idx_water_meter_customer ON rev_water_meter (customer_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_water_meter_diameter ON rev_water_meter (diameter);
|
||||
CREATE INDEX IF NOT EXISTS idx_water_meter_manufacturer ON rev_water_meter (manufacturer);
|
||||
CREATE INDEX IF NOT EXISTS idx_water_meter_created ON rev_water_meter (created_at);
|
||||
|
||||
-- rev_meter_install_record 索引
|
||||
CREATE INDEX IF NOT EXISTS idx_install_record_meter ON rev_meter_install_record (meter_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_install_record_customer ON rev_meter_install_record (customer_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_install_record_date ON rev_meter_install_record (install_date);
|
||||
|
||||
-- rev_meter_replace_record 索引
|
||||
CREATE INDEX IF NOT EXISTS idx_replace_record_old ON rev_meter_replace_record (old_meter_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_replace_record_new ON rev_meter_replace_record (new_meter_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_replace_record_customer ON rev_meter_replace_record (customer_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_replace_record_approval ON rev_meter_replace_record (approval_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_replace_record_date ON rev_meter_replace_record (replace_date);
|
||||
|
||||
-- rev_meter_lifecycle_log 索引
|
||||
CREATE INDEX IF NOT EXISTS idx_lifecycle_log_meter ON rev_meter_lifecycle_log (meter_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_lifecycle_log_action ON rev_meter_lifecycle_log (action_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_lifecycle_log_time ON rev_meter_lifecycle_log (action_time);
|
||||
CREATE INDEX IF NOT EXISTS idx_lifecycle_log_customer ON rev_meter_lifecycle_log (customer_no);
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.water.revenue.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.water.common.core.result.R;
|
||||
import com.water.revenue.entity.MeterInstallRecord;
|
||||
import com.water.revenue.entity.MeterLifecycleLog;
|
||||
import com.water.revenue.entity.WaterMeter;
|
||||
import com.water.revenue.enums.MeterStatus;
|
||||
import com.water.revenue.service.MeterLifecycleService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 水表管理 — 入库 / 安装 / 查询
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/api/revenue/meter")
|
||||
public class MeterController {
|
||||
|
||||
private final MeterLifecycleService lifecycleService;
|
||||
|
||||
/* ==================== 入库 ==================== */
|
||||
|
||||
/**
|
||||
* 水表入库登记
|
||||
*/
|
||||
@PostMapping("/stock-in")
|
||||
public R<WaterMeter> stockIn(@RequestBody WaterMeter meter) {
|
||||
try {
|
||||
return R.ok(lifecycleService.stockIn(meter));
|
||||
} catch (Exception e) {
|
||||
log.error("水表入库失败", e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量入库
|
||||
*/
|
||||
@PostMapping("/stock-in/batch")
|
||||
public R<List<WaterMeter>> batchStockIn(@RequestBody List<WaterMeter> meters) {
|
||||
try {
|
||||
return R.ok(lifecycleService.batchStockIn(meters));
|
||||
} catch (Exception e) {
|
||||
log.error("批量入库失败", e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 安装 ==================== */
|
||||
|
||||
/**
|
||||
* 水表出库安装
|
||||
*/
|
||||
@PostMapping("/install")
|
||||
public R<MeterInstallRecord> install(@RequestParam String meterNo,
|
||||
@RequestParam String customerNo,
|
||||
@RequestParam String installer,
|
||||
@RequestParam String installAddress,
|
||||
@RequestParam(required = false, defaultValue = "0") BigDecimal initialReading) {
|
||||
try {
|
||||
return R.ok(lifecycleService.installMeter(meterNo, customerNo, installer, installAddress, initialReading));
|
||||
} catch (Exception e) {
|
||||
log.error("水表安装失败", e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 查询 ==================== */
|
||||
|
||||
/**
|
||||
* 根据表号查询水表详情
|
||||
*/
|
||||
@GetMapping("/{meterNo}")
|
||||
public R<WaterMeter> getByMeterNo(@PathVariable String meterNo) {
|
||||
try {
|
||||
return R.ok(lifecycleService.getByMeterNo(meterNo));
|
||||
} catch (Exception e) {
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询水表列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public R<Page<WaterMeter>> list(@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "20") int size,
|
||||
@RequestParam(required = false) MeterStatus status,
|
||||
@RequestParam(required = false) String keyword) {
|
||||
return R.ok(lifecycleService.listMeters(page, size, status, keyword));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询客户绑定的水表
|
||||
*/
|
||||
@GetMapping("/customer/{customerNo}")
|
||||
public R<List<WaterMeter>> getByCustomer(@PathVariable String customerNo) {
|
||||
return R.ok(lifecycleService.getMetersByCustomer(customerNo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询安装记录
|
||||
*/
|
||||
@GetMapping("/{meterNo}/install-records")
|
||||
public R<List<MeterInstallRecord>> getInstallRecords(@PathVariable String meterNo) {
|
||||
return R.ok(lifecycleService.getInstallRecords(meterNo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询水表生命周期日志
|
||||
*/
|
||||
@GetMapping("/{meterNo}/lifecycle-logs")
|
||||
public R<List<MeterLifecycleLog>> getLifecycleLogs(@PathVariable String meterNo) {
|
||||
return R.ok(lifecycleService.getLifecycleLogs(meterNo));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.water.revenue.controller;
|
||||
|
||||
import com.water.common.core.result.R;
|
||||
import com.water.revenue.entity.MeterReplaceRecord;
|
||||
import com.water.revenue.service.MeterReplaceService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 水表换表 / 报废管理
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/api/revenue/meter-replace")
|
||||
public class MeterReplaceController {
|
||||
|
||||
private final MeterReplaceService replaceService;
|
||||
|
||||
/**
|
||||
* 故障换表
|
||||
*/
|
||||
@PostMapping("/fault")
|
||||
public R<MeterReplaceRecord> faultReplace(@RequestParam String oldMeterNo,
|
||||
@RequestParam String newMeterNo,
|
||||
@RequestParam String replacer,
|
||||
@RequestParam String reason,
|
||||
@RequestParam(required = false) BigDecimal oldReading) {
|
||||
try {
|
||||
return R.ok(replaceService.faultReplace(oldMeterNo, newMeterNo, replacer, reason, oldReading));
|
||||
} catch (Exception e) {
|
||||
log.error("故障换表失败", e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 到期换表
|
||||
*/
|
||||
@PostMapping("/expired")
|
||||
public R<MeterReplaceRecord> expiredReplace(@RequestParam String oldMeterNo,
|
||||
@RequestParam String newMeterNo,
|
||||
@RequestParam String replacer,
|
||||
@RequestParam(required = false) BigDecimal oldReading) {
|
||||
try {
|
||||
return R.ok(replaceService.expiredReplace(oldMeterNo, newMeterNo, replacer, oldReading));
|
||||
} catch (Exception e) {
|
||||
log.error("到期换表失败", e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 水表报废
|
||||
*/
|
||||
@PostMapping("/scrap")
|
||||
public R<Void> scrapMeter(@RequestParam String meterNo,
|
||||
@RequestParam String reason,
|
||||
@RequestParam String operator) {
|
||||
try {
|
||||
replaceService.scrapMeter(meterNo, reason, operator);
|
||||
return R.ok();
|
||||
} catch (Exception e) {
|
||||
log.error("水表报废失败", e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 报废审批
|
||||
*/
|
||||
@PostMapping("/approve/{recordId}")
|
||||
public R<MeterReplaceRecord> approve(@PathVariable Long recordId,
|
||||
@RequestParam String approver,
|
||||
@RequestParam boolean approved,
|
||||
@RequestParam(required = false) String remark) {
|
||||
try {
|
||||
return R.ok(replaceService.approveReplace(recordId, approver, approved, remark));
|
||||
} catch (Exception e) {
|
||||
log.error("审批失败", e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询换表记录
|
||||
*/
|
||||
@GetMapping("/records/{meterNo}")
|
||||
public R<List<MeterReplaceRecord>> getReplaceRecords(@PathVariable String meterNo) {
|
||||
return R.ok(replaceService.getReplaceRecords(meterNo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询待审批列表
|
||||
*/
|
||||
@GetMapping("/pending")
|
||||
public R<List<MeterReplaceRecord>> getPendingApprovals() {
|
||||
return R.ok(replaceService.getPendingApprovals());
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询换表记录
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public R<List<MeterReplaceRecord>> list(@RequestParam(defaultValue = "0") int page,
|
||||
@RequestParam(defaultValue = "20") int size,
|
||||
@RequestParam(required = false) String replaceType) {
|
||||
return R.ok(replaceService.listReplaceRecords(page, size, replaceType));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.water.revenue.controller;
|
||||
|
||||
import com.water.common.core.result.R;
|
||||
import com.water.revenue.entity.MeterLifecycleLog;
|
||||
import com.water.revenue.service.MeterStatsService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 水表统计 — 库存 / 安装 / 报废 / 生命周期日志
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/api/revenue/meter-stats")
|
||||
public class MeterStatsController {
|
||||
|
||||
private final MeterStatsService statsService;
|
||||
|
||||
/**
|
||||
* 库存统计
|
||||
*/
|
||||
@GetMapping("/inventory")
|
||||
public R<Map<String, Object>> getInventoryStats() {
|
||||
return R.ok(statsService.getInventoryStats());
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装统计
|
||||
*/
|
||||
@GetMapping("/install")
|
||||
public R<Map<String, Object>> getInstallStats() {
|
||||
return R.ok(statsService.getInstallStats());
|
||||
}
|
||||
|
||||
/**
|
||||
* 报废统计
|
||||
*/
|
||||
@GetMapping("/scrap")
|
||||
public R<Map<String, Object>> getScrapStats() {
|
||||
return R.ok(statsService.getScrapStats());
|
||||
}
|
||||
|
||||
/**
|
||||
* 水表全生命周期日志
|
||||
*/
|
||||
@GetMapping("/lifecycle/{meterNo}")
|
||||
public R<List<MeterLifecycleLog>> getLifecycleLogs(@PathVariable String meterNo) {
|
||||
return R.ok(statsService.getLifecycleLogs(meterNo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 最近操作记录
|
||||
*/
|
||||
@GetMapping("/recent")
|
||||
public R<List<MeterLifecycleLog>> getRecentLogs(@RequestParam(defaultValue = "20") int limit) {
|
||||
return R.ok(statsService.getRecentLogs(limit));
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作类型统计
|
||||
*/
|
||||
@GetMapping("/action-types")
|
||||
public R<List<Map<String, Object>>> getActionTypeStats() {
|
||||
return R.ok(statsService.getActionTypeStats());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.water.revenue.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 水表安装记录
|
||||
*/
|
||||
@Data
|
||||
@TableName("rev_meter_install_record")
|
||||
public class MeterInstallRecord {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 水表编号 */
|
||||
private String meterNo;
|
||||
|
||||
/** 客户编号 */
|
||||
private String customerNo;
|
||||
|
||||
/** 安装人 */
|
||||
private String installer;
|
||||
|
||||
/** 安装日期 */
|
||||
private LocalDate installDate;
|
||||
|
||||
/** 安装地址 */
|
||||
private String installAddress;
|
||||
|
||||
/** 旧水表编号(换表安装时) */
|
||||
private String oldMeterNo;
|
||||
|
||||
/** 初始读数 */
|
||||
private BigDecimal initialReading;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 创建时间 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/** 更新时间 */
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.water.revenue.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 水表生命周期日志
|
||||
*/
|
||||
@Data
|
||||
@TableName("rev_meter_lifecycle_log")
|
||||
public class MeterLifecycleLog {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 水表编号 */
|
||||
private String meterNo;
|
||||
|
||||
/** 操作类型:STOCK_IN/INSTALL/DISMANTLE/REPLACE/SCRAP/REPAIR */
|
||||
private String actionType;
|
||||
|
||||
/** 操作时间 */
|
||||
private LocalDateTime actionTime;
|
||||
|
||||
/** 操作人 */
|
||||
private String operator;
|
||||
|
||||
/** 操作详情 */
|
||||
private String detail;
|
||||
|
||||
/** 旧表编号 */
|
||||
private String oldMeterNo;
|
||||
|
||||
/** 新表编号 */
|
||||
private String newMeterNo;
|
||||
|
||||
/** 关联客户编号 */
|
||||
private String customerNo;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 创建时间 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.water.revenue.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 水表换表记录
|
||||
*/
|
||||
@Data
|
||||
@TableName("rev_meter_replace_record")
|
||||
public class MeterReplaceRecord {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 旧水表编号 */
|
||||
private String oldMeterNo;
|
||||
|
||||
/** 新水表编号 */
|
||||
private String newMeterNo;
|
||||
|
||||
/** 客户编号 */
|
||||
private String customerNo;
|
||||
|
||||
/** 换表类型:FAULT-故障换表, EXPIRED-到期换表 */
|
||||
private String replaceType;
|
||||
|
||||
/** 换表原因 */
|
||||
private String reason;
|
||||
|
||||
/** 换表人 */
|
||||
private String replacer;
|
||||
|
||||
/** 换表日期 */
|
||||
private LocalDate replaceDate;
|
||||
|
||||
/** 旧表读数 */
|
||||
private BigDecimal oldReading;
|
||||
|
||||
/** 新表初始读数 */
|
||||
private BigDecimal newReading;
|
||||
|
||||
/** 审批状态:PENDING-待审批, APPROVED-已通过, REJECTED-已拒绝 */
|
||||
private String approvalStatus;
|
||||
|
||||
/** 审批人 */
|
||||
private String approver;
|
||||
|
||||
/** 审批时间 */
|
||||
private LocalDateTime approvalTime;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 创建时间 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/** 更新时间 */
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.water.revenue.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.water.revenue.enums.MeterStatus;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 水表实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("rev_water_meter")
|
||||
public class WaterMeter {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 水表编号 */
|
||||
private String meterNo;
|
||||
|
||||
/** 水表型号 */
|
||||
private String model;
|
||||
|
||||
/** 口径(mm) */
|
||||
private Integer diameter;
|
||||
|
||||
/** 制造商 */
|
||||
private String manufacturer;
|
||||
|
||||
/** 生产日期 */
|
||||
private LocalDate productionDate;
|
||||
|
||||
/** 安装日期 */
|
||||
private LocalDate installDate;
|
||||
|
||||
/** 安装地址 */
|
||||
private String installAddress;
|
||||
|
||||
/** 客户编号 */
|
||||
private String customerNo;
|
||||
|
||||
/** 水表状态 */
|
||||
private MeterStatus status;
|
||||
|
||||
/** 入库时间 */
|
||||
private LocalDateTime inStockTime;
|
||||
|
||||
/** 出库时间 */
|
||||
private LocalDateTime outStockTime;
|
||||
|
||||
/** 初始读数 */
|
||||
private BigDecimal initialReading;
|
||||
|
||||
/** 当前读数 */
|
||||
private BigDecimal currentReading;
|
||||
|
||||
/** 拆除日期 */
|
||||
private LocalDate dismantleDate;
|
||||
|
||||
/** 报废日期 */
|
||||
private LocalDate scrappedDate;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 创建时间 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/** 更新时间 */
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.water.revenue.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum MeterStatus {
|
||||
|
||||
IN_STOCK("在库"),
|
||||
INSTALLED("已安装"),
|
||||
DISMANTLED("已拆除"),
|
||||
SCRAPPED("已报废"),
|
||||
REPAIRING("维修中");
|
||||
|
||||
private final String description;
|
||||
|
||||
MeterStatus(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.water.revenue.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.water.revenue.entity.MeterInstallRecord;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface MeterInstallRecordMapper extends BaseMapper<MeterInstallRecord> {
|
||||
|
||||
@Select("SELECT * FROM rev_meter_install_record WHERE meter_no = #{meterNo} ORDER BY install_date DESC")
|
||||
List<MeterInstallRecord> selectByMeterNo(@Param("meterNo") String meterNo);
|
||||
|
||||
@Select("SELECT * FROM rev_meter_install_record WHERE customer_no = #{customerNo} ORDER BY install_date DESC")
|
||||
List<MeterInstallRecord> selectByCustomerNo(@Param("customerNo") String customerNo);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.water.revenue.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.water.revenue.entity.MeterLifecycleLog;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface MeterLifecycleLogMapper extends BaseMapper<MeterLifecycleLog> {
|
||||
|
||||
@Select("SELECT * FROM rev_meter_lifecycle_log WHERE meter_no = #{meterNo} ORDER BY action_time ASC")
|
||||
List<MeterLifecycleLog> selectByMeterNo(@Param("meterNo") String meterNo);
|
||||
|
||||
@Select("SELECT * FROM rev_meter_lifecycle_log ORDER BY action_time DESC LIMIT #{limit}")
|
||||
List<MeterLifecycleLog> selectRecent(@Param("limit") int limit);
|
||||
|
||||
@Select("SELECT action_type, COUNT(*) as count FROM rev_meter_lifecycle_log GROUP BY action_type")
|
||||
List<Map<String, Object>> countByActionType();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.water.revenue.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.water.revenue.entity.MeterReplaceRecord;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface MeterReplaceRecordMapper extends BaseMapper<MeterReplaceRecord> {
|
||||
|
||||
@Select("SELECT * FROM rev_meter_replace_record WHERE old_meter_no = #{meterNo} OR new_meter_no = #{meterNo} ORDER BY replace_date DESC")
|
||||
List<MeterReplaceRecord> selectByMeterNo(@Param("meterNo") String meterNo);
|
||||
|
||||
@Select("SELECT * FROM rev_meter_replace_record WHERE customer_no = #{customerNo} ORDER BY replace_date DESC")
|
||||
List<MeterReplaceRecord> selectByCustomerNo(@Param("customerNo") String customerNo);
|
||||
|
||||
@Select("SELECT * FROM rev_meter_replace_record WHERE approval_status = 'PENDING' ORDER BY created_at ASC")
|
||||
List<MeterReplaceRecord> selectPendingApprovals();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.water.revenue.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.water.revenue.entity.WaterMeter;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface WaterMeterMapper extends BaseMapper<WaterMeter> {
|
||||
|
||||
@Select("SELECT * FROM rev_water_meter WHERE meter_no = #{meterNo} LIMIT 1")
|
||||
WaterMeter selectByMeterNo(@Param("meterNo") String meterNo);
|
||||
|
||||
@Select("SELECT * FROM rev_water_meter WHERE customer_no = #{customerNo} AND status = 'INSTALLED'")
|
||||
List<WaterMeter> selectByCustomerNo(@Param("customerNo") String customerNo);
|
||||
|
||||
@Select("SELECT status, COUNT(*) as count FROM rev_water_meter GROUP BY status")
|
||||
List<Map<String, Object>> countByStatus();
|
||||
|
||||
@Select("SELECT diameter, COUNT(*) as count FROM rev_water_meter WHERE status = 'IN_STOCK' GROUP BY diameter")
|
||||
List<Map<String, Object>> countInStockByDiameter();
|
||||
|
||||
@Select("SELECT manufacturer, COUNT(*) as count FROM rev_water_meter WHERE status = 'IN_STOCK' GROUP BY manufacturer")
|
||||
List<Map<String, Object>> countInStockByManufacturer();
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package com.water.revenue.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.water.revenue.entity.MeterInstallRecord;
|
||||
import com.water.revenue.entity.MeterLifecycleLog;
|
||||
import com.water.revenue.entity.WaterMeter;
|
||||
import com.water.revenue.enums.MeterStatus;
|
||||
import com.water.revenue.mapper.MeterInstallRecordMapper;
|
||||
import com.water.revenue.mapper.MeterLifecycleLogMapper;
|
||||
import com.water.revenue.mapper.WaterMeterMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 水表生命周期服务 — 入库 / 出库安装 / 绑定用户 / 安装记录
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MeterLifecycleService {
|
||||
|
||||
private final WaterMeterMapper waterMeterMapper;
|
||||
private final MeterInstallRecordMapper installRecordMapper;
|
||||
private final MeterLifecycleLogMapper lifecycleLogMapper;
|
||||
|
||||
/* ==================== 入库登记 ==================== */
|
||||
|
||||
/**
|
||||
* 水表入库登记
|
||||
*/
|
||||
@Transactional
|
||||
public WaterMeter stockIn(WaterMeter meter) {
|
||||
// 检查表号唯一性
|
||||
WaterMeter existing = waterMeterMapper.selectByMeterNo(meter.getMeterNo());
|
||||
if (existing != null) {
|
||||
throw new IllegalStateException("水表编号已存在: " + meter.getMeterNo());
|
||||
}
|
||||
|
||||
meter.setStatus(MeterStatus.IN_STOCK);
|
||||
meter.setInStockTime(LocalDateTime.now());
|
||||
meter.setInitialReading(BigDecimal.ZERO);
|
||||
meter.setCurrentReading(BigDecimal.ZERO);
|
||||
waterMeterMapper.insert(meter);
|
||||
|
||||
// 记录日志
|
||||
addLog(meter.getMeterNo(), "STOCK_IN", meter.getMeterNo(), null, null,
|
||||
"水表入库: 型号=" + meter.getModel() + ", 口径=" + meter.getDiameter());
|
||||
|
||||
log.info("水表入库成功: {}", meter.getMeterNo());
|
||||
return meter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量入库
|
||||
*/
|
||||
@Transactional
|
||||
public List<WaterMeter> batchStockIn(List<WaterMeter> meters) {
|
||||
for (WaterMeter meter : meters) {
|
||||
stockIn(meter);
|
||||
}
|
||||
return meters;
|
||||
}
|
||||
|
||||
/* ==================== 出库安装 ==================== */
|
||||
|
||||
/**
|
||||
* 水表出库安装(绑定客户)
|
||||
*/
|
||||
@Transactional
|
||||
public MeterInstallRecord installMeter(String meterNo, String customerNo, String installer,
|
||||
String installAddress, BigDecimal initialReading) {
|
||||
WaterMeter meter = waterMeterMapper.selectByMeterNo(meterNo);
|
||||
if (meter == null) {
|
||||
throw new IllegalArgumentException("水表不存在: " + meterNo);
|
||||
}
|
||||
if (meter.getStatus() != MeterStatus.IN_STOCK) {
|
||||
throw new IllegalStateException("水表状态不允许安装, 当前状态: " + meter.getStatus());
|
||||
}
|
||||
|
||||
// 更新水表状态
|
||||
meter.setStatus(MeterStatus.INSTALLED);
|
||||
meter.setCustomerNo(customerNo);
|
||||
meter.setInstallAddress(installAddress);
|
||||
meter.setInstallDate(LocalDate.now());
|
||||
meter.setOutStockTime(LocalDateTime.now());
|
||||
meter.setInitialReading(initialReading != null ? initialReading : BigDecimal.ZERO);
|
||||
meter.setCurrentReading(meter.getInitialReading());
|
||||
waterMeterMapper.updateById(meter);
|
||||
|
||||
// 创建安装记录
|
||||
MeterInstallRecord record = new MeterInstallRecord();
|
||||
record.setMeterNo(meterNo);
|
||||
record.setCustomerNo(customerNo);
|
||||
record.setInstaller(installer);
|
||||
record.setInstallDate(LocalDate.now());
|
||||
record.setInstallAddress(installAddress);
|
||||
record.setInitialReading(meter.getInitialReading());
|
||||
installRecordMapper.insert(record);
|
||||
|
||||
// 记录日志
|
||||
addLog(meterNo, "INSTALL", meterNo, null, customerNo,
|
||||
"水表安装: 客户=" + customerNo + ", 地址=" + installAddress + ", 安装人=" + installer);
|
||||
|
||||
log.info("水表安装成功: {} -> 客户 {}", meterNo, customerNo);
|
||||
return record;
|
||||
}
|
||||
|
||||
/* ==================== 查询 ==================== */
|
||||
|
||||
/**
|
||||
* 根据表号查询水表
|
||||
*/
|
||||
public WaterMeter getByMeterNo(String meterNo) {
|
||||
WaterMeter meter = waterMeterMapper.selectByMeterNo(meterNo);
|
||||
if (meter == null) {
|
||||
throw new IllegalArgumentException("水表不存在: " + meterNo);
|
||||
}
|
||||
return meter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询水表列表
|
||||
*/
|
||||
public Page<WaterMeter> listMeters(int page, int size, MeterStatus status, String keyword) {
|
||||
LambdaQueryWrapper<WaterMeter> wrapper = new LambdaQueryWrapper<>();
|
||||
if (status != null) {
|
||||
wrapper.eq(WaterMeter::getStatus, status);
|
||||
}
|
||||
if (keyword != null && !keyword.isEmpty()) {
|
||||
wrapper.and(w -> w.like(WaterMeter::getMeterNo, keyword)
|
||||
.or().like(WaterMeter::getCustomerNo, keyword)
|
||||
.or().like(WaterMeter::getInstallAddress, keyword));
|
||||
}
|
||||
wrapper.orderByDesc(WaterMeter::getCreatedAt);
|
||||
return waterMeterMapper.selectPage(new Page<>(page, size), wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询客户绑定的水表
|
||||
*/
|
||||
public List<WaterMeter> getMetersByCustomer(String customerNo) {
|
||||
return waterMeterMapper.selectByCustomerNo(customerNo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询安装记录
|
||||
*/
|
||||
public List<MeterInstallRecord> getInstallRecords(String meterNo) {
|
||||
return installRecordMapper.selectByMeterNo(meterNo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询水表生命周期日志
|
||||
*/
|
||||
public List<MeterLifecycleLog> getLifecycleLogs(String meterNo) {
|
||||
return lifecycleLogMapper.selectByMeterNo(meterNo);
|
||||
}
|
||||
|
||||
/* ==================== 内部方法 ==================== */
|
||||
|
||||
private void addLog(String meterNo, String actionType, String detail,
|
||||
String oldMeterNo, String customerNo, String logDetail) {
|
||||
MeterLifecycleLog l = new MeterLifecycleLog();
|
||||
l.setMeterNo(meterNo);
|
||||
l.setActionType(actionType);
|
||||
l.setActionTime(LocalDateTime.now());
|
||||
l.setOperator("system");
|
||||
l.setDetail(logDetail);
|
||||
l.setOldMeterNo(oldMeterNo);
|
||||
l.setCustomerNo(customerNo);
|
||||
lifecycleLogMapper.insert(l);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package com.water.revenue.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.water.revenue.entity.MeterLifecycleLog;
|
||||
import com.water.revenue.entity.MeterReplaceRecord;
|
||||
import com.water.revenue.entity.WaterMeter;
|
||||
import com.water.revenue.enums.MeterStatus;
|
||||
import com.water.revenue.mapper.MeterLifecycleLogMapper;
|
||||
import com.water.revenue.mapper.MeterReplaceRecordMapper;
|
||||
import com.water.revenue.mapper.WaterMeterMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 水表换表 / 报废服务
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MeterReplaceService {
|
||||
|
||||
private final WaterMeterMapper waterMeterMapper;
|
||||
private final MeterReplaceRecordMapper replaceRecordMapper;
|
||||
private final MeterLifecycleLogMapper lifecycleLogMapper;
|
||||
|
||||
/* ==================== 故障换表 ==================== */
|
||||
|
||||
/**
|
||||
* 故障换表
|
||||
*/
|
||||
@Transactional
|
||||
public MeterReplaceRecord faultReplace(String oldMeterNo, String newMeterNo,
|
||||
String replacer, String reason, BigDecimal oldReading) {
|
||||
return doReplace(oldMeterNo, newMeterNo, "FAULT", replacer, reason, oldReading);
|
||||
}
|
||||
|
||||
/**
|
||||
* 到期换表
|
||||
*/
|
||||
@Transactional
|
||||
public MeterReplaceRecord expiredReplace(String oldMeterNo, String newMeterNo,
|
||||
String replacer, BigDecimal oldReading) {
|
||||
return doReplace(oldMeterNo, newMeterNo, "EXPIRED", replacer, "水表到期更换", oldReading);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行换表
|
||||
*/
|
||||
@Transactional
|
||||
protected MeterReplaceRecord doReplace(String oldMeterNo, String newMeterNo,
|
||||
String replaceType, String replacer,
|
||||
String reason, BigDecimal oldReading) {
|
||||
WaterMeter oldMeter = waterMeterMapper.selectByMeterNo(oldMeterNo);
|
||||
if (oldMeter == null) {
|
||||
throw new IllegalArgumentException("旧水表不存在: " + oldMeterNo);
|
||||
}
|
||||
if (oldMeter.getStatus() != MeterStatus.INSTALLED) {
|
||||
throw new IllegalStateException("旧水表状态不允许换表, 当前状态: " + oldMeter.getStatus());
|
||||
}
|
||||
|
||||
WaterMeter newMeter = waterMeterMapper.selectByMeterNo(newMeterNo);
|
||||
if (newMeter == null) {
|
||||
throw new IllegalArgumentException("新水表不存在: " + newMeterNo);
|
||||
}
|
||||
if (newMeter.getStatus() != MeterStatus.IN_STOCK) {
|
||||
throw new IllegalStateException("新水表状态不允许安装, 当前状态: " + newMeter.getStatus());
|
||||
}
|
||||
|
||||
// 旧表拆除
|
||||
oldMeter.setStatus(MeterStatus.DISMANTLED);
|
||||
oldMeter.setDismantleDate(LocalDate.now());
|
||||
oldMeter.setCurrentReading(oldReading != null ? oldReading : oldMeter.getCurrentReading());
|
||||
waterMeterMapper.updateById(oldMeter);
|
||||
|
||||
// 新表安装(继承客户信息)
|
||||
newMeter.setStatus(MeterStatus.INSTALLED);
|
||||
newMeter.setCustomerNo(oldMeter.getCustomerNo());
|
||||
newMeter.setInstallAddress(oldMeter.getInstallAddress());
|
||||
newMeter.setInstallDate(LocalDate.now());
|
||||
newMeter.setOutStockTime(LocalDateTime.now());
|
||||
newMeter.setInitialReading(BigDecimal.ZERO);
|
||||
newMeter.setCurrentReading(BigDecimal.ZERO);
|
||||
waterMeterMapper.updateById(newMeter);
|
||||
|
||||
// 创建换表记录
|
||||
MeterReplaceRecord record = new MeterReplaceRecord();
|
||||
record.setOldMeterNo(oldMeterNo);
|
||||
record.setNewMeterNo(newMeterNo);
|
||||
record.setCustomerNo(oldMeter.getCustomerNo());
|
||||
record.setReplaceType(replaceType);
|
||||
record.setReason(reason);
|
||||
record.setReplacer(replacer);
|
||||
record.setReplaceDate(LocalDate.now());
|
||||
record.setOldReading(oldReading != null ? oldReading : oldMeter.getCurrentReading());
|
||||
record.setNewReading(BigDecimal.ZERO);
|
||||
record.setApprovalStatus("APPROVED");
|
||||
replaceRecordMapper.insert(record);
|
||||
|
||||
// 记录日志
|
||||
addLog(oldMeterNo, "DISMANTLE", oldMeterNo, newMeterNo, oldMeter.getCustomerNo(),
|
||||
"旧表拆除: 原因=" + reason + ", 换表人=" + replacer);
|
||||
addLog(newMeterNo, "REPLACE", oldMeterNo, newMeterNo, oldMeter.getCustomerNo(),
|
||||
"新表安装(换表): 替换旧表 " + oldMeterNo + ", 换表人=" + replacer);
|
||||
|
||||
log.info("换表成功: {} -> {}", oldMeterNo, newMeterNo);
|
||||
return record;
|
||||
}
|
||||
|
||||
/* ==================== 到期报废 ==================== */
|
||||
|
||||
/**
|
||||
* 水表报废
|
||||
*/
|
||||
@Transactional
|
||||
public void scrapMeter(String meterNo, String reason, String operator) {
|
||||
WaterMeter meter = waterMeterMapper.selectByMeterNo(meterNo);
|
||||
if (meter == null) {
|
||||
throw new IllegalArgumentException("水表不存在: " + meterNo);
|
||||
}
|
||||
if (meter.getStatus() == MeterStatus.INSTALLED) {
|
||||
throw new IllegalStateException("已安装的水表不能直接报废, 请先拆除");
|
||||
}
|
||||
|
||||
meter.setStatus(MeterStatus.SCRAPPED);
|
||||
meter.setScrappedDate(LocalDate.now());
|
||||
meter.setRemark(reason);
|
||||
waterMeterMapper.updateById(meter);
|
||||
|
||||
addLog(meterNo, "SCRAP", meterNo, null, meter.getCustomerNo(),
|
||||
"水表报废: 原因=" + reason + ", 操作人=" + operator);
|
||||
|
||||
log.info("水表报废: {} - {}", meterNo, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* 报废审批
|
||||
*/
|
||||
@Transactional
|
||||
public MeterReplaceRecord approveReplace(Long recordId, String approver, boolean approved, String remark) {
|
||||
MeterReplaceRecord record = replaceRecordMapper.selectById(recordId);
|
||||
if (record == null) {
|
||||
throw new IllegalArgumentException("换表记录不存在: " + recordId);
|
||||
}
|
||||
if (!"PENDING".equals(record.getApprovalStatus())) {
|
||||
throw new IllegalStateException("该记录已审批, 当前状态: " + record.getApprovalStatus());
|
||||
}
|
||||
|
||||
record.setApprovalStatus(approved ? "APPROVED" : "REJECTED");
|
||||
record.setApprover(approver);
|
||||
record.setApprovalTime(LocalDateTime.now());
|
||||
if (remark != null && !remark.isEmpty()) {
|
||||
record.setRemark(record.getRemark() != null ? record.getRemark() + "; " + remark : remark);
|
||||
}
|
||||
replaceRecordMapper.updateById(record);
|
||||
|
||||
log.info("换表审批: id={}, approved={}, approver={}", recordId, approved, approver);
|
||||
return record;
|
||||
}
|
||||
|
||||
/* ==================== 查询 ==================== */
|
||||
|
||||
/**
|
||||
* 查询换表记录
|
||||
*/
|
||||
public List<MeterReplaceRecord> getReplaceRecords(String meterNo) {
|
||||
return replaceRecordMapper.selectByMeterNo(meterNo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询待审批列表
|
||||
*/
|
||||
public List<MeterReplaceRecord> getPendingApprovals() {
|
||||
return replaceRecordMapper.selectPendingApprovals();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询换表记录
|
||||
*/
|
||||
public List<MeterReplaceRecord> listReplaceRecords(int page, int size, String replaceType) {
|
||||
LambdaQueryWrapper<MeterReplaceRecord> wrapper = new LambdaQueryWrapper<>();
|
||||
if (replaceType != null && !replaceType.isEmpty()) {
|
||||
wrapper.eq(MeterReplaceRecord::getReplaceType, replaceType);
|
||||
}
|
||||
wrapper.orderByDesc(MeterReplaceRecord::getCreatedAt);
|
||||
wrapper.last("LIMIT " + size + " OFFSET " + (page * size));
|
||||
return replaceRecordMapper.selectList(wrapper);
|
||||
}
|
||||
|
||||
/* ==================== 内部方法 ==================== */
|
||||
|
||||
private void addLog(String meterNo, String actionType, String oldMeterNo,
|
||||
String newMeterNo, String customerNo, String detail) {
|
||||
MeterLifecycleLog l = new MeterLifecycleLog();
|
||||
l.setMeterNo(meterNo);
|
||||
l.setActionType(actionType);
|
||||
l.setActionTime(LocalDateTime.now());
|
||||
l.setOperator("system");
|
||||
l.setDetail(detail);
|
||||
l.setOldMeterNo(oldMeterNo);
|
||||
l.setNewMeterNo(newMeterNo);
|
||||
l.setCustomerNo(customerNo);
|
||||
lifecycleLogMapper.insert(l);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package com.water.revenue.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.water.revenue.entity.MeterLifecycleLog;
|
||||
import com.water.revenue.entity.WaterMeter;
|
||||
import com.water.revenue.enums.MeterStatus;
|
||||
import com.water.revenue.mapper.MeterLifecycleLogMapper;
|
||||
import com.water.revenue.mapper.MeterReplaceRecordMapper;
|
||||
import com.water.revenue.mapper.WaterMeterMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 水表统计服务 — 生命周期日志 / 库存统计 / 安装统计 / 报废统计
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MeterStatsService {
|
||||
|
||||
private final WaterMeterMapper waterMeterMapper;
|
||||
private final MeterLifecycleLogMapper lifecycleLogMapper;
|
||||
private final MeterReplaceRecordMapper replaceRecordMapper;
|
||||
|
||||
/* ==================== 库存统计 ==================== */
|
||||
|
||||
/**
|
||||
* 库存综合统计
|
||||
*/
|
||||
public Map<String, Object> getInventoryStats() {
|
||||
Map<String, Object> stats = new LinkedHashMap<>();
|
||||
|
||||
// 按状态统计
|
||||
List<Map<String, Object>> statusStats = waterMeterMapper.countByStatus();
|
||||
stats.put("statusStats", statusStats);
|
||||
|
||||
// 在库数量
|
||||
long inStock = countByStatus(MeterStatus.IN_STOCK);
|
||||
stats.put("totalInStock", inStock);
|
||||
|
||||
// 已安装数量
|
||||
long installed = countByStatus(MeterStatus.INSTALLED);
|
||||
stats.put("totalInstalled", installed);
|
||||
|
||||
// 已拆除
|
||||
long dismantled = countByStatus(MeterStatus.DISMANTLED);
|
||||
stats.put("totalDismantled", dismantled);
|
||||
|
||||
// 已报废
|
||||
long scrapped = countByStatus(MeterStatus.SCRAPPED);
|
||||
stats.put("totalScrapped", scrapped);
|
||||
|
||||
// 维修中
|
||||
long repairing = countByStatus(MeterStatus.REPAIRING);
|
||||
stats.put("totalRepairing", repairing);
|
||||
|
||||
// 按口径统计库存
|
||||
stats.put("byDiameter", waterMeterMapper.countInStockByDiameter());
|
||||
|
||||
// 按制造商统计库存
|
||||
stats.put("byManufacturer", waterMeterMapper.countInStockByManufacturer());
|
||||
|
||||
// 总量
|
||||
long total = waterMeterMapper.selectCount(null);
|
||||
stats.put("totalCount", total);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/* ==================== 安装统计 ==================== */
|
||||
|
||||
/**
|
||||
* 安装统计
|
||||
*/
|
||||
public Map<String, Object> getInstallStats() {
|
||||
Map<String, Object> stats = new LinkedHashMap<>();
|
||||
|
||||
long installed = countByStatus(MeterStatus.INSTALLED);
|
||||
stats.put("totalInstalled", installed);
|
||||
|
||||
// 按口径统计已安装
|
||||
LambdaQueryWrapper<WaterMeter> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(WaterMeter::getStatus, MeterStatus.INSTALLED)
|
||||
.select(WaterMeter::getDiameter)
|
||||
.groupBy(WaterMeter::getDiameter);
|
||||
// Using raw SQL for aggregate
|
||||
stats.put("installedByDiameter",
|
||||
waterMeterMapper.selectMaps(
|
||||
new LambdaQueryWrapper<WaterMeter>()
|
||||
.select(WaterMeter::getDiameter)
|
||||
.eq(WaterMeter::getStatus, MeterStatus.INSTALLED)
|
||||
.groupBy(WaterMeter::getDiameter)));
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/* ==================== 报废统计 ==================== */
|
||||
|
||||
/**
|
||||
* 报废统计
|
||||
*/
|
||||
public Map<String, Object> getScrapStats() {
|
||||
Map<String, Object> stats = new LinkedHashMap<>();
|
||||
|
||||
long scrapped = countByStatus(MeterStatus.SCRAPPED);
|
||||
stats.put("totalScrapped", scrapped);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/* ==================== 生命周期日志 ==================== */
|
||||
|
||||
/**
|
||||
* 获取水表全生命周期日志
|
||||
*/
|
||||
public List<MeterLifecycleLog> getLifecycleLogs(String meterNo) {
|
||||
return lifecycleLogMapper.selectByMeterNo(meterNo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 最近操作记录
|
||||
*/
|
||||
public List<MeterLifecycleLog> getRecentLogs(int limit) {
|
||||
return lifecycleLogMapper.selectRecent(limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作类型统计
|
||||
*/
|
||||
public List<Map<String, Object>> getActionTypeStats() {
|
||||
return lifecycleLogMapper.countByActionType();
|
||||
}
|
||||
|
||||
/* ==================== 内部方法 ==================== */
|
||||
|
||||
private long countByStatus(MeterStatus status) {
|
||||
return waterMeterMapper.selectCount(
|
||||
new LambdaQueryWrapper<WaterMeter>().eq(WaterMeter::getStatus, status));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
package com.water.revenue.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.water.revenue.entity.MeterInstallRecord;
|
||||
import com.water.revenue.entity.MeterLifecycleLog;
|
||||
import com.water.revenue.entity.MeterReplaceRecord;
|
||||
import com.water.revenue.entity.WaterMeter;
|
||||
import com.water.revenue.enums.MeterStatus;
|
||||
import com.water.revenue.mapper.MeterInstallRecordMapper;
|
||||
import com.water.revenue.mapper.MeterLifecycleLogMapper;
|
||||
import com.water.revenue.mapper.MeterReplaceRecordMapper;
|
||||
import com.water.revenue.mapper.WaterMeterMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class MeterLifecycleTest {
|
||||
|
||||
@Mock
|
||||
private WaterMeterMapper waterMeterMapper;
|
||||
@Mock
|
||||
private MeterInstallRecordMapper installRecordMapper;
|
||||
@Mock
|
||||
private MeterReplaceRecordMapper replaceRecordMapper;
|
||||
@Mock
|
||||
private MeterLifecycleLogMapper lifecycleLogMapper;
|
||||
|
||||
private MeterLifecycleService lifecycleService;
|
||||
private MeterReplaceService replaceService;
|
||||
private MeterStatsService statsService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
lifecycleService = new MeterLifecycleService(waterMeterMapper, installRecordMapper, lifecycleLogMapper);
|
||||
replaceService = new MeterReplaceService(waterMeterMapper, replaceRecordMapper, lifecycleLogMapper);
|
||||
statsService = new MeterStatsService(waterMeterMapper, lifecycleLogMapper, replaceRecordMapper);
|
||||
}
|
||||
|
||||
/* ==================== Helper ==================== */
|
||||
|
||||
private WaterMeter buildMeter(String meterNo, MeterStatus status) {
|
||||
WaterMeter m = new WaterMeter();
|
||||
m.setId(1L);
|
||||
m.setMeterNo(meterNo);
|
||||
m.setModel("DN20");
|
||||
m.setDiameter(20);
|
||||
m.setManufacturer("TestMfg");
|
||||
m.setProductionDate(LocalDate.of(2025, 1, 1));
|
||||
m.setStatus(status);
|
||||
m.setInStockTime(LocalDateTime.now());
|
||||
m.setInitialReading(BigDecimal.ZERO);
|
||||
m.setCurrentReading(BigDecimal.ZERO);
|
||||
m.setCreatedAt(LocalDateTime.now());
|
||||
m.setUpdatedAt(LocalDateTime.now());
|
||||
return m;
|
||||
}
|
||||
|
||||
/* ==================== Test 1: 水表入库 ==================== */
|
||||
|
||||
@Test
|
||||
@DisplayName("Test 1: 水表入库登记 - 正常入库")
|
||||
void testStockIn_Success() {
|
||||
WaterMeter meter = buildMeter("WM-001", null);
|
||||
when(waterMeterMapper.selectByMeterNo("WM-001")).thenReturn(null);
|
||||
when(waterMeterMapper.insert(any(WaterMeter.class))).thenReturn(1);
|
||||
when(lifecycleLogMapper.insert(any(MeterLifecycleLog.class))).thenReturn(1);
|
||||
|
||||
WaterMeter result = lifecycleService.stockIn(meter);
|
||||
|
||||
assertEquals(MeterStatus.IN_STOCK, result.getStatus());
|
||||
assertNotNull(result.getInStockTime());
|
||||
verify(waterMeterMapper).insert(any(WaterMeter.class));
|
||||
verify(lifecycleLogMapper).insert(any(MeterLifecycleLog.class));
|
||||
}
|
||||
|
||||
/* ==================== Test 2: 重复入库 ==================== */
|
||||
|
||||
@Test
|
||||
@DisplayName("Test 2: 水表入库登记 - 表号重复抛异常")
|
||||
void testStockIn_DuplicateMeterNo() {
|
||||
WaterMeter meter = buildMeter("WM-001", null);
|
||||
when(waterMeterMapper.selectByMeterNo("WM-001")).thenReturn(buildMeter("WM-001", MeterStatus.IN_STOCK));
|
||||
|
||||
assertThrows(IllegalStateException.class, () -> lifecycleService.stockIn(meter));
|
||||
verify(waterMeterMapper, never()).insert(any());
|
||||
}
|
||||
|
||||
/* ==================== Test 3: 水表安装 ==================== */
|
||||
|
||||
@Test
|
||||
@DisplayName("Test 3: 水表出库安装 - 正常安装")
|
||||
void testInstallMeter_Success() {
|
||||
WaterMeter meter = buildMeter("WM-001", MeterStatus.IN_STOCK);
|
||||
when(waterMeterMapper.selectByMeterNo("WM-001")).thenReturn(meter);
|
||||
when(waterMeterMapper.updateById(any(WaterMeter.class))).thenReturn(1);
|
||||
when(installRecordMapper.insert(any(MeterInstallRecord.class))).thenReturn(1);
|
||||
when(lifecycleLogMapper.insert(any(MeterLifecycleLog.class))).thenReturn(1);
|
||||
|
||||
MeterInstallRecord record = lifecycleService.installMeter(
|
||||
"WM-001", "CUST-001", "installer-A", "Test Address", BigDecimal.TEN);
|
||||
|
||||
assertEquals(MeterStatus.INSTALLED, meter.getStatus());
|
||||
assertEquals("CUST-001", meter.getCustomerNo());
|
||||
assertEquals("Test Address", meter.getInstallAddress());
|
||||
assertNotNull(record);
|
||||
verify(installRecordMapper).insert(any(MeterInstallRecord.class));
|
||||
}
|
||||
|
||||
/* ==================== Test 4: 安装状态不对 ==================== */
|
||||
|
||||
@Test
|
||||
@DisplayName("Test 4: 水表安装 - 状态不是 IN_STOCK 抛异常")
|
||||
void testInstallMeter_WrongStatus() {
|
||||
WaterMeter meter = buildMeter("WM-001", MeterStatus.INSTALLED);
|
||||
when(waterMeterMapper.selectByMeterNo("WM-001")).thenReturn(meter);
|
||||
|
||||
assertThrows(IllegalStateException.class, () ->
|
||||
lifecycleService.installMeter("WM-001", "CUST-001", "installer", "addr", BigDecimal.ZERO));
|
||||
}
|
||||
|
||||
/* ==================== Test 5: 故障换表 ==================== */
|
||||
|
||||
@Test
|
||||
@DisplayName("Test 5: 故障换表 - 正常流程")
|
||||
void testFaultReplace_Success() {
|
||||
WaterMeter oldMeter = buildMeter("OLD-001", MeterStatus.INSTALLED);
|
||||
oldMeter.setCustomerNo("CUST-001");
|
||||
oldMeter.setInstallAddress("Addr");
|
||||
oldMeter.setCurrentReading(new BigDecimal("100"));
|
||||
|
||||
WaterMeter newMeter = buildMeter("NEW-001", MeterStatus.IN_STOCK);
|
||||
|
||||
when(waterMeterMapper.selectByMeterNo("OLD-001")).thenReturn(oldMeter);
|
||||
when(waterMeterMapper.selectByMeterNo("NEW-001")).thenReturn(newMeter);
|
||||
when(waterMeterMapper.updateById(any(WaterMeter.class))).thenReturn(1);
|
||||
when(replaceRecordMapper.insert(any(MeterReplaceRecord.class))).thenReturn(1);
|
||||
when(lifecycleLogMapper.insert(any(MeterLifecycleLog.class))).thenReturn(1);
|
||||
|
||||
MeterReplaceRecord record = replaceService.faultReplace(
|
||||
"OLD-001", "NEW-001", "replacer-A", "表具故障", new BigDecimal("100"));
|
||||
|
||||
assertEquals(MeterStatus.DISMANTLED, oldMeter.getStatus());
|
||||
assertEquals(MeterStatus.INSTALLED, newMeter.getStatus());
|
||||
assertEquals("CUST-001", newMeter.getCustomerNo());
|
||||
assertEquals("FAULT", record.getReplaceType());
|
||||
assertEquals("APPROVED", record.getApprovalStatus());
|
||||
|
||||
// 验证日志被记录了2次(拆除+安装)
|
||||
verify(lifecycleLogMapper, times(2)).insert(any(MeterLifecycleLog.class));
|
||||
}
|
||||
|
||||
/* ==================== Test 6: 水表报废 ==================== */
|
||||
|
||||
@Test
|
||||
@DisplayName("Test 6: 水表报废 - 正常报废已拆除的水表")
|
||||
void testScrapMeter_Success() {
|
||||
WaterMeter meter = buildMeter("WM-SCRAP", MeterStatus.DISMANTLED);
|
||||
when(waterMeterMapper.selectByMeterNo("WM-SCRAP")).thenReturn(meter);
|
||||
when(waterMeterMapper.updateById(any(WaterMeter.class))).thenReturn(1);
|
||||
when(lifecycleLogMapper.insert(any(MeterLifecycleLog.class))).thenReturn(1);
|
||||
|
||||
replaceService.scrapMeter("WM-SCRAP", "超期使用", "operator-A");
|
||||
|
||||
assertEquals(MeterStatus.SCRAPPED, meter.getStatus());
|
||||
assertNotNull(meter.getScrappedDate());
|
||||
verify(waterMeterMapper).updateById(meter);
|
||||
}
|
||||
|
||||
/* ==================== Test 7: 已安装水表不能直接报废 ==================== */
|
||||
|
||||
@Test
|
||||
@DisplayName("Test 7: 水表报废 - 已安装水表不能直接报废")
|
||||
void testScrapMeter_InstalledMeterRejected() {
|
||||
WaterMeter meter = buildMeter("WM-ACTIVE", MeterStatus.INSTALLED);
|
||||
when(waterMeterMapper.selectByMeterNo("WM-ACTIVE")).thenReturn(meter);
|
||||
|
||||
assertThrows(IllegalStateException.class, () ->
|
||||
replaceService.scrapMeter("WM-ACTIVE", "reason", "operator"));
|
||||
}
|
||||
|
||||
/* ==================== Test 8: 报废审批 ==================== */
|
||||
|
||||
@Test
|
||||
@DisplayName("Test 8: 报废审批 - 审批通过")
|
||||
void testApproveReplace_Approved() {
|
||||
MeterReplaceRecord record = new MeterReplaceRecord();
|
||||
record.setId(1L);
|
||||
record.setApprovalStatus("PENDING");
|
||||
|
||||
when(replaceRecordMapper.selectById(1L)).thenReturn(record);
|
||||
when(replaceRecordMapper.updateById(any(MeterReplaceRecord.class))).thenReturn(1);
|
||||
|
||||
MeterReplaceRecord result = replaceService.approveReplace(1L, "admin", true, "同意");
|
||||
|
||||
assertEquals("APPROVED", result.getApprovalStatus());
|
||||
assertEquals("admin", result.getApprover());
|
||||
assertNotNull(result.getApprovalTime());
|
||||
}
|
||||
|
||||
/* ==================== Test 9: 重复审批 ==================== */
|
||||
|
||||
@Test
|
||||
@DisplayName("Test 9: 报废审批 - 已审批的记录不能再次审批")
|
||||
void testApproveReplace_AlreadyApproved() {
|
||||
MeterReplaceRecord record = new MeterReplaceRecord();
|
||||
record.setId(1L);
|
||||
record.setApprovalStatus("APPROVED");
|
||||
|
||||
when(replaceRecordMapper.selectById(1L)).thenReturn(record);
|
||||
|
||||
assertThrows(IllegalStateException.class, () ->
|
||||
replaceService.approveReplace(1L, "admin", true, null));
|
||||
}
|
||||
|
||||
/* ==================== Test 10: 库存统计 ==================== */
|
||||
|
||||
@Test
|
||||
@DisplayName("Test 10: 库存统计 - 返回完整统计信息")
|
||||
void testGetInventoryStats() {
|
||||
when(waterMeterMapper.countByStatus()).thenReturn(
|
||||
List.of(Map.of("status", "IN_STOCK", "count", 10L),
|
||||
Map.of("status", "INSTALLED", "count", 20L)));
|
||||
when(waterMeterMapper.countInStockByDiameter()).thenReturn(
|
||||
List.of(Map.of("diameter", 20, "count", 5L)));
|
||||
when(waterMeterMapper.countInStockByManufacturer()).thenReturn(
|
||||
List.of(Map.of("manufacturer", "TestMfg", "count", 10L)));
|
||||
|
||||
// mock selectCount for individual status queries
|
||||
when(waterMeterMapper.selectCount(any(LambdaQueryWrapper.class)))
|
||||
.thenReturn(10L) // IN_STOCK
|
||||
.thenReturn(20L) // INSTALLED
|
||||
.thenReturn(3L) // DISMANTLED
|
||||
.thenReturn(2L) // SCRAPPED
|
||||
.thenReturn(1L); // REPAIRING
|
||||
|
||||
Map<String, Object> stats = statsService.getInventoryStats();
|
||||
|
||||
assertNotNull(stats);
|
||||
assertEquals(10L, stats.get("totalInStock"));
|
||||
assertEquals(20L, stats.get("totalInstalled"));
|
||||
assertEquals(3L, stats.get("totalDismantled"));
|
||||
assertEquals(2L, stats.get("totalScrapped"));
|
||||
assertEquals(1L, stats.get("totalRepairing"));
|
||||
assertNotNull(stats.get("byDiameter"));
|
||||
assertNotNull(stats.get("byManufacturer"));
|
||||
}
|
||||
|
||||
/* ==================== Test 11: 生命周期日志查询 ==================== */
|
||||
|
||||
@Test
|
||||
@DisplayName("Test 11: 生命周期日志查询")
|
||||
void testGetLifecycleLogs() {
|
||||
MeterLifecycleLog log1 = new MeterLifecycleLog();
|
||||
log1.setMeterNo("WM-001");
|
||||
log1.setActionType("STOCK_IN");
|
||||
log1.setActionTime(LocalDateTime.now());
|
||||
|
||||
MeterLifecycleLog log2 = new MeterLifecycleLog();
|
||||
log2.setMeterNo("WM-001");
|
||||
log2.setActionType("INSTALL");
|
||||
log2.setActionTime(LocalDateTime.now());
|
||||
|
||||
when(lifecycleLogMapper.selectByMeterNo("WM-001")).thenReturn(Arrays.asList(log1, log2));
|
||||
|
||||
List<MeterLifecycleLog> logs = lifecycleService.getLifecycleLogs("WM-001");
|
||||
|
||||
assertEquals(2, logs.size());
|
||||
assertEquals("STOCK_IN", logs.get(0).getActionType());
|
||||
assertEquals("INSTALL", logs.get(1).getActionType());
|
||||
}
|
||||
|
||||
/* ==================== Test 12: 到期换表 ==================== */
|
||||
|
||||
@Test
|
||||
@DisplayName("Test 12: 到期换表 - EXPIRED 类型")
|
||||
void testExpiredReplace() {
|
||||
WaterMeter oldMeter = buildMeter("OLD-002", MeterStatus.INSTALLED);
|
||||
oldMeter.setCustomerNo("CUST-002");
|
||||
oldMeter.setInstallAddress("Addr2");
|
||||
|
||||
WaterMeter newMeter = buildMeter("NEW-002", MeterStatus.IN_STOCK);
|
||||
|
||||
when(waterMeterMapper.selectByMeterNo("OLD-002")).thenReturn(oldMeter);
|
||||
when(waterMeterMapper.selectByMeterNo("NEW-002")).thenReturn(newMeter);
|
||||
when(waterMeterMapper.updateById(any(WaterMeter.class))).thenReturn(1);
|
||||
when(replaceRecordMapper.insert(any(MeterReplaceRecord.class))).thenReturn(1);
|
||||
when(lifecycleLogMapper.insert(any(MeterLifecycleLog.class))).thenReturn(1);
|
||||
|
||||
MeterReplaceRecord record = replaceService.expiredReplace(
|
||||
"OLD-002", "NEW-002", "replacer-B", new BigDecimal("200"));
|
||||
|
||||
assertEquals("EXPIRED", record.getReplaceType());
|
||||
assertEquals("水表到期更换", record.getReason());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user