feat(wm-production): #65 全工艺药剂投加监控
This commit is contained in:
+172
@@ -0,0 +1,172 @@
|
||||
package com.water.production.controller;
|
||||
|
||||
import com.water.common.core.result.R;
|
||||
import com.water.production.entity.ChemicalDosing;
|
||||
import com.water.production.entity.ChemicalStock;
|
||||
import com.water.production.entity.DosingStrategy;
|
||||
import com.water.production.service.ChemicalDosingService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 全工艺药剂投加监控 Controller
|
||||
* 混凝→沉淀→过滤→消毒 全流程药剂管理
|
||||
*/
|
||||
@Tag(name = "药剂投加监控")
|
||||
@RestController
|
||||
@RequestMapping("/api/production/chemical")
|
||||
@RequiredArgsConstructor
|
||||
public class ChemicalDosingController {
|
||||
|
||||
private final ChemicalDosingService chemicalDosingService;
|
||||
|
||||
// ==================== 1. 投加监控 ====================
|
||||
|
||||
@Operation(summary = "投加记录分页列表")
|
||||
@GetMapping("/dosing/list")
|
||||
public R<Map<String, Object>> listDosing(
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "20") int size,
|
||||
@RequestParam(required = false) String processStage,
|
||||
@RequestParam(required = false) String station,
|
||||
@RequestParam(required = false) String status) {
|
||||
return R.ok(chemicalDosingService.listDosing(page, size, processStage, station, status));
|
||||
}
|
||||
|
||||
@Operation(summary = "投加记录详情")
|
||||
@GetMapping("/dosing/{id}")
|
||||
public R<ChemicalDosing> getDosing(@PathVariable Long id) {
|
||||
ChemicalDosing dosing = chemicalDosingService.getDosingById(id);
|
||||
return dosing != null ? R.ok(dosing) : R.fail(404, "记录不存在");
|
||||
}
|
||||
|
||||
@Operation(summary = "新增投加记录")
|
||||
@PostMapping("/dosing")
|
||||
public R<ChemicalDosing> createDosing(@RequestBody ChemicalDosing dosing) {
|
||||
return R.ok(chemicalDosingService.createDosing(dosing));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新投加记录")
|
||||
@PutMapping("/dosing/{id}")
|
||||
public R<Boolean> updateDosing(@PathVariable Long id, @RequestBody ChemicalDosing dosing) {
|
||||
return R.ok(chemicalDosingService.updateDosing(id, dosing));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除投加记录")
|
||||
@DeleteMapping("/dosing/{id}")
|
||||
public R<Boolean> deleteDosing(@PathVariable Long id) {
|
||||
return R.ok(chemicalDosingService.deleteDosing(id));
|
||||
}
|
||||
|
||||
// ==================== 2. 投加记录 + 趋势分析 ====================
|
||||
|
||||
@Operation(summary = "投加历史记录列表")
|
||||
@GetMapping("/record/list")
|
||||
public R<Map<String, Object>> listRecords(
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "20") int size,
|
||||
@RequestParam(required = false) String processStage,
|
||||
@RequestParam(required = false) String chemicalName,
|
||||
@RequestParam(required = false) String startTime,
|
||||
@RequestParam(required = false) String endTime) {
|
||||
return R.ok(chemicalDosingService.listRecords(page, size, processStage, chemicalName, startTime, endTime));
|
||||
}
|
||||
|
||||
@Operation(summary = "投加趋势分析")
|
||||
@GetMapping("/record/trend")
|
||||
public R<List<Map<String, Object>>> trendAnalysis(
|
||||
@RequestParam String processStage,
|
||||
@RequestParam String startTime,
|
||||
@RequestParam String endTime) {
|
||||
return R.ok(chemicalDosingService.trendAnalysis(processStage, startTime, endTime));
|
||||
}
|
||||
|
||||
// ==================== 3. 药耗统计 ====================
|
||||
|
||||
@Operation(summary = "药耗统计(日/周/月 + 单位产水药耗)")
|
||||
@GetMapping("/statistics")
|
||||
public R<Map<String, Object>> statistics(
|
||||
@RequestParam(defaultValue = "day") String period,
|
||||
@RequestParam(required = false) String station) {
|
||||
return R.ok(chemicalDosingService.statistics(period, station));
|
||||
}
|
||||
|
||||
// ==================== 4. 投加策略 ====================
|
||||
|
||||
@Operation(summary = "投加策略列表")
|
||||
@GetMapping("/strategy/list")
|
||||
public R<List<DosingStrategy>> listStrategies(
|
||||
@RequestParam(required = false) String processStage,
|
||||
@RequestParam(required = false) Boolean enabled) {
|
||||
return R.ok(chemicalDosingService.listStrategies(processStage, enabled));
|
||||
}
|
||||
|
||||
@Operation(summary = "投加策略详情")
|
||||
@GetMapping("/strategy/{id}")
|
||||
public R<DosingStrategy> getStrategy(@PathVariable Long id) {
|
||||
DosingStrategy strategy = chemicalDosingService.getStrategyById(id);
|
||||
return strategy != null ? R.ok(strategy) : R.fail(404, "策略不存在");
|
||||
}
|
||||
|
||||
@Operation(summary = "新增投加策略")
|
||||
@PostMapping("/strategy")
|
||||
public R<DosingStrategy> createStrategy(@RequestBody DosingStrategy strategy) {
|
||||
return R.ok(chemicalDosingService.createStrategy(strategy));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新投加策略")
|
||||
@PutMapping("/strategy/{id}")
|
||||
public R<Boolean> updateStrategy(@PathVariable Long id, @RequestBody DosingStrategy strategy) {
|
||||
return R.ok(chemicalDosingService.updateStrategy(id, strategy));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除投加策略")
|
||||
@DeleteMapping("/strategy/{id}")
|
||||
public R<Boolean> deleteStrategy(@PathVariable Long id) {
|
||||
return R.ok(chemicalDosingService.deleteStrategy(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "计算推荐投加量(基于策略联动)")
|
||||
@PostMapping("/strategy/{id}/calculate")
|
||||
public R<Map<String, Object>> calculateRecommended(
|
||||
@PathVariable Long id,
|
||||
@RequestParam(required = false) BigDecimal turbidity,
|
||||
@RequestParam(required = false) BigDecimal flow,
|
||||
@RequestParam(required = false) BigDecimal ph) {
|
||||
return R.ok(chemicalDosingService.calculateRecommendedDosing(id, turbidity, flow, ph));
|
||||
}
|
||||
|
||||
// ==================== 5. 药剂库存 ====================
|
||||
|
||||
@Operation(summary = "库存列表")
|
||||
@GetMapping("/stock/list")
|
||||
public R<List<ChemicalStock>> listStocks(
|
||||
@RequestParam(required = false) String station,
|
||||
@RequestParam(required = false) String status) {
|
||||
return R.ok(chemicalDosingService.listStocks(station, status));
|
||||
}
|
||||
|
||||
@Operation(summary = "新增库存记录")
|
||||
@PostMapping("/stock")
|
||||
public R<ChemicalStock> createStock(@RequestBody ChemicalStock stock) {
|
||||
return R.ok(chemicalDosingService.createStock(stock));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新库存")
|
||||
@PutMapping("/stock/{id}")
|
||||
public R<Boolean> updateStock(@PathVariable Long id, @RequestBody ChemicalStock stock) {
|
||||
return R.ok(chemicalDosingService.updateStock(id, stock));
|
||||
}
|
||||
|
||||
@Operation(summary = "低库存预警列表")
|
||||
@GetMapping("/stock/low-alert")
|
||||
public R<List<Map<String, Object>>> lowStockAlerts() {
|
||||
return R.ok(chemicalDosingService.lowStockAlerts());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.water.production.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 药剂投加监控记录
|
||||
* 各工艺段(混凝/沉淀/过滤/消毒)的药剂投加量实时监控
|
||||
*/
|
||||
@Data
|
||||
@TableName("prod_chemical_dosing")
|
||||
public class ChemicalDosing {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 工艺段: coagulation/sedimentation/filtration/disinfection */
|
||||
private String processStage;
|
||||
|
||||
/** 药剂名称 */
|
||||
private String chemicalName;
|
||||
|
||||
/** 药剂编码 */
|
||||
private String chemicalCode;
|
||||
|
||||
/** 投加量(kg) */
|
||||
private BigDecimal dosingAmount;
|
||||
|
||||
/** 投加速率(kg/h) */
|
||||
private BigDecimal dosingRate;
|
||||
|
||||
/** 投加浓度(mg/L) */
|
||||
private BigDecimal concentration;
|
||||
|
||||
/** 当时流量(m³/h) */
|
||||
private BigDecimal flowRate;
|
||||
|
||||
/** 站点/水厂 */
|
||||
private String station;
|
||||
|
||||
/** 操作员 */
|
||||
private String operator;
|
||||
|
||||
/** 状态: active/paused/stopped */
|
||||
private String status;
|
||||
|
||||
private String remark;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedTime;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.water.production.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 药剂库存管理
|
||||
*/
|
||||
@Data
|
||||
@TableName("prod_chemical_stock")
|
||||
public class ChemicalStock {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 药剂名称 */
|
||||
private String chemicalName;
|
||||
|
||||
/** 药剂编码 */
|
||||
private String chemicalCode;
|
||||
|
||||
/** 当前库存(kg) */
|
||||
private BigDecimal currentStock;
|
||||
|
||||
/** 最大库存 */
|
||||
private BigDecimal maxStock;
|
||||
|
||||
/** 安全库存(低于此值预警) */
|
||||
private BigDecimal minStock;
|
||||
|
||||
/** 单位 */
|
||||
private String unit;
|
||||
|
||||
/** 仓库位置 */
|
||||
private String warehouse;
|
||||
|
||||
/** 供应商 */
|
||||
private String supplier;
|
||||
|
||||
/** 站点 */
|
||||
private String station;
|
||||
|
||||
/** 状态: normal/low/out */
|
||||
private String status;
|
||||
|
||||
/** 最近入库时间 */
|
||||
private LocalDateTime lastInbound;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedTime;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.water.production.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 投加历史记录(用于趋势分析)
|
||||
*/
|
||||
@Data
|
||||
@TableName("prod_dosing_record")
|
||||
public class DosingRecord {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 关联投加记录ID */
|
||||
private Long dosingId;
|
||||
|
||||
/** 工艺段 */
|
||||
private String processStage;
|
||||
|
||||
/** 药剂名称 */
|
||||
private String chemicalName;
|
||||
|
||||
/** 投加量(kg) */
|
||||
private BigDecimal dosingAmount;
|
||||
|
||||
/** 投加速率(kg/h) */
|
||||
private BigDecimal dosingRate;
|
||||
|
||||
/** 投加浓度(mg/L) */
|
||||
private BigDecimal concentration;
|
||||
|
||||
/** 当时流量(m³/h) */
|
||||
private BigDecimal flowRate;
|
||||
|
||||
/** 站点 */
|
||||
private String station;
|
||||
|
||||
/** 记录时间 */
|
||||
private LocalDateTime recordTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdTime;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.water.production.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 自动投加策略配置(基于原水水质/流量联动)
|
||||
*/
|
||||
@Data
|
||||
@TableName("prod_dosing_strategy")
|
||||
public class DosingStrategy {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/** 策略名称 */
|
||||
private String strategyName;
|
||||
|
||||
/** 工艺段 */
|
||||
private String processStage;
|
||||
|
||||
/** 药剂名称 */
|
||||
private String chemicalName;
|
||||
|
||||
/** 策略类型: auto/manual/semi-auto */
|
||||
private String strategyType;
|
||||
|
||||
/** 基础投加速率 */
|
||||
private BigDecimal baseDosingRate;
|
||||
|
||||
/** 最小投加速率 */
|
||||
private BigDecimal minDosingRate;
|
||||
|
||||
/** 最大投加速率 */
|
||||
private BigDecimal maxDosingRate;
|
||||
|
||||
/** 浊度阈值联动 */
|
||||
private BigDecimal turbidityThreshold;
|
||||
|
||||
/** 流量阈值联动 */
|
||||
private BigDecimal flowThreshold;
|
||||
|
||||
/** pH下限 */
|
||||
private BigDecimal phThresholdMin;
|
||||
|
||||
/** pH上限 */
|
||||
private BigDecimal phThresholdMax;
|
||||
|
||||
/** 投加公式 */
|
||||
private String formula;
|
||||
|
||||
/** 是否启用 */
|
||||
private Boolean enabled;
|
||||
|
||||
/** 站点 */
|
||||
private String station;
|
||||
|
||||
private String remark;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedTime;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.water.production.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.water.production.entity.ChemicalDosing;
|
||||
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 ChemicalDosingMapper extends BaseMapper<ChemicalDosing> {
|
||||
|
||||
/** 按工艺段统计今日投加量 */
|
||||
@Select("SELECT process_stage, chemical_name, SUM(dosing_amount) as total_amount " +
|
||||
"FROM prod_chemical_dosing WHERE DATE(created_time) = CURRENT_DATE " +
|
||||
"GROUP BY process_stage, chemical_name")
|
||||
List<Map<String, Object>> dailyStatistics();
|
||||
|
||||
/** 按时间段统计药耗 */
|
||||
@Select("SELECT process_stage, chemical_name, SUM(dosing_amount) as total_amount, " +
|
||||
"COUNT(*) as record_count FROM prod_chemical_dosing " +
|
||||
"WHERE created_time BETWEEN #{startTime} AND #{endTime} " +
|
||||
"GROUP BY process_stage, chemical_name ORDER BY total_amount DESC")
|
||||
List<Map<String, Object>> statisticsByPeriod(@Param("startTime") String startTime,
|
||||
@Param("endTime") String endTime);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.water.production.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.water.production.entity.ChemicalStock;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ChemicalStockMapper extends BaseMapper<ChemicalStock> {
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.water.production.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.water.production.entity.DosingRecord;
|
||||
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 DosingRecordMapper extends BaseMapper<DosingRecord> {
|
||||
|
||||
/** 趋势分析:按小时聚合投加量 */
|
||||
@Select("SELECT DATE_TRUNC('hour', record_time) as time_bucket, " +
|
||||
"AVG(dosing_rate) as avg_rate, SUM(dosing_amount) as total_amount " +
|
||||
"FROM prod_dosing_record WHERE process_stage = #{stage} " +
|
||||
"AND record_time BETWEEN #{startTime} AND #{endTime} " +
|
||||
"GROUP BY time_bucket ORDER BY time_bucket")
|
||||
List<Map<String, Object>> trendByHour(@Param("stage") String stage,
|
||||
@Param("startTime") String startTime,
|
||||
@Param("endTime") String endTime);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.water.production.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.water.production.entity.DosingStrategy;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface DosingStrategyMapper extends BaseMapper<DosingStrategy> {
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
package com.water.production.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.water.production.entity.ChemicalDosing;
|
||||
import com.water.production.entity.ChemicalStock;
|
||||
import com.water.production.entity.DosingRecord;
|
||||
import com.water.production.entity.DosingStrategy;
|
||||
import com.water.production.mapper.ChemicalDosingMapper;
|
||||
import com.water.production.mapper.ChemicalStockMapper;
|
||||
import com.water.production.mapper.DosingRecordMapper;
|
||||
import com.water.production.mapper.DosingStrategyMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 全工艺药剂投加监控服务
|
||||
* 涵盖混凝→沉淀→过滤→消毒全流程
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ChemicalDosingService {
|
||||
|
||||
private final ChemicalDosingMapper dosingMapper;
|
||||
private final DosingRecordMapper recordMapper;
|
||||
private final ChemicalStockMapper stockMapper;
|
||||
private final DosingStrategyMapper strategyMapper;
|
||||
|
||||
// ==================== 药剂投加监控 ====================
|
||||
|
||||
/** 分页查询投加记录 */
|
||||
public Map<String, Object> listDosing(int page, int size, String processStage, String station, String status) {
|
||||
LambdaQueryWrapper<ChemicalDosing> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(StringUtils.hasText(processStage), ChemicalDosing::getProcessStage, processStage)
|
||||
.eq(StringUtils.hasText(station), ChemicalDosing::getStation, station)
|
||||
.eq(StringUtils.hasText(status), ChemicalDosing::getStatus, status)
|
||||
.orderByDesc(ChemicalDosing::getCreatedTime);
|
||||
Page<ChemicalDosing> result = dosingMapper.selectPage(new Page<>(page, size), wrapper);
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("records", result.getRecords());
|
||||
data.put("total", result.getTotal());
|
||||
data.put("pages", result.getPages());
|
||||
return data;
|
||||
}
|
||||
|
||||
/** 获取投加详情 */
|
||||
public ChemicalDosing getDosingById(Long id) {
|
||||
return dosingMapper.selectById(id);
|
||||
}
|
||||
|
||||
/** 新增投加记录 */
|
||||
public ChemicalDosing createDosing(ChemicalDosing dosing) {
|
||||
if (dosing.getStatus() == null) {
|
||||
dosing.setStatus("active");
|
||||
}
|
||||
dosing.setCreatedTime(LocalDateTime.now());
|
||||
dosing.setUpdatedTime(LocalDateTime.now());
|
||||
dosingMapper.insert(dosing);
|
||||
saveDosingRecord(dosing);
|
||||
return dosing;
|
||||
}
|
||||
|
||||
/** 更新投加记录 */
|
||||
public boolean updateDosing(Long id, ChemicalDosing dosing) {
|
||||
dosing.setId(id);
|
||||
dosing.setUpdatedTime(LocalDateTime.now());
|
||||
int rows = dosingMapper.updateById(dosing);
|
||||
if (rows > 0) {
|
||||
saveDosingRecord(dosing);
|
||||
}
|
||||
return rows > 0;
|
||||
}
|
||||
|
||||
/** 删除投加记录 */
|
||||
public boolean deleteDosing(Long id) {
|
||||
return dosingMapper.deleteById(id) > 0;
|
||||
}
|
||||
|
||||
// ==================== 投加记录 + 趋势分析 ====================
|
||||
|
||||
/** 投加记录列表 */
|
||||
public Map<String, Object> listRecords(int page, int size, String processStage, String chemicalName,
|
||||
String startTime, String endTime) {
|
||||
LambdaQueryWrapper<DosingRecord> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(StringUtils.hasText(processStage), DosingRecord::getProcessStage, processStage)
|
||||
.eq(StringUtils.hasText(chemicalName), DosingRecord::getChemicalName, chemicalName);
|
||||
if (StringUtils.hasText(startTime)) {
|
||||
wrapper.ge(DosingRecord::getRecordTime, LocalDateTime.parse(startTime, DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
||||
}
|
||||
if (StringUtils.hasText(endTime)) {
|
||||
wrapper.le(DosingRecord::getRecordTime, LocalDateTime.parse(endTime, DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
||||
}
|
||||
wrapper.orderByDesc(DosingRecord::getRecordTime);
|
||||
Page<DosingRecord> result = recordMapper.selectPage(new Page<>(page, size), wrapper);
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("records", result.getRecords());
|
||||
data.put("total", result.getTotal());
|
||||
return data;
|
||||
}
|
||||
|
||||
/** 趋势分析 */
|
||||
public List<Map<String, Object>> trendAnalysis(String processStage, String startTime, String endTime) {
|
||||
return recordMapper.trendByHour(processStage, startTime, endTime);
|
||||
}
|
||||
|
||||
// ==================== 药耗统计 ====================
|
||||
|
||||
/** 药耗统计:日/周/月 + 单位产水药耗 */
|
||||
public Map<String, Object> statistics(String period, String station) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime start;
|
||||
LocalDateTime end = now;
|
||||
|
||||
switch (period != null ? period : "day") {
|
||||
case "week":
|
||||
start = now.minusWeeks(1);
|
||||
break;
|
||||
case "month":
|
||||
start = now.minusMonths(1);
|
||||
break;
|
||||
default:
|
||||
start = now.minusDays(1);
|
||||
}
|
||||
|
||||
String startTime = start.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
|
||||
String endTime = end.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
|
||||
List<Map<String, Object>> rawStats = dosingMapper.statisticsByPeriod(startTime, endTime);
|
||||
|
||||
if (StringUtils.hasText(station)) {
|
||||
rawStats = rawStats.stream()
|
||||
.filter(m -> station.equals(m.get("station")))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
BigDecimal totalAmount = rawStats.stream()
|
||||
.map(m -> m.get("total_amount") != null ? new BigDecimal(m.get("total_amount").toString()) : BigDecimal.ZERO)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("period", period);
|
||||
result.put("details", rawStats);
|
||||
result.put("totalAmount", totalAmount.setScale(4, RoundingMode.HALF_UP));
|
||||
result.put("unitConsumption", calculateUnitConsumption(totalAmount, start, end));
|
||||
return result;
|
||||
}
|
||||
|
||||
private BigDecimal calculateUnitConsumption(BigDecimal totalAmount, LocalDateTime start, LocalDateTime end) {
|
||||
long hours = java.time.Duration.between(start, end).toHours();
|
||||
if (hours <= 0) hours = 1;
|
||||
BigDecimal totalWater = BigDecimal.valueOf(hours * 100L);
|
||||
return totalAmount.divide(totalWater, 6, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
// ==================== 投加策略 ====================
|
||||
|
||||
public List<DosingStrategy> listStrategies(String processStage, Boolean enabled) {
|
||||
LambdaQueryWrapper<DosingStrategy> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(StringUtils.hasText(processStage), DosingStrategy::getProcessStage, processStage)
|
||||
.eq(enabled != null, DosingStrategy::getEnabled, enabled)
|
||||
.orderByAsc(DosingStrategy::getProcessStage);
|
||||
return strategyMapper.selectList(wrapper);
|
||||
}
|
||||
|
||||
public DosingStrategy getStrategyById(Long id) {
|
||||
return strategyMapper.selectById(id);
|
||||
}
|
||||
|
||||
public DosingStrategy createStrategy(DosingStrategy strategy) {
|
||||
strategy.setCreatedTime(LocalDateTime.now());
|
||||
strategy.setUpdatedTime(LocalDateTime.now());
|
||||
if (strategy.getEnabled() == null) strategy.setEnabled(true);
|
||||
strategyMapper.insert(strategy);
|
||||
return strategy;
|
||||
}
|
||||
|
||||
public boolean updateStrategy(Long id, DosingStrategy strategy) {
|
||||
strategy.setId(id);
|
||||
strategy.setUpdatedTime(LocalDateTime.now());
|
||||
return strategyMapper.updateById(strategy) > 0;
|
||||
}
|
||||
|
||||
public boolean deleteStrategy(Long id) {
|
||||
return strategyMapper.deleteById(id) > 0;
|
||||
}
|
||||
|
||||
/** 根据策略计算推荐投加量 */
|
||||
public Map<String, Object> calculateRecommendedDosing(Long strategyId, BigDecimal currentTurbidity,
|
||||
BigDecimal currentFlow, BigDecimal currentPh) {
|
||||
DosingStrategy strategy = strategyMapper.selectById(strategyId);
|
||||
if (strategy == null || !Boolean.TRUE.equals(strategy.getEnabled())) {
|
||||
return Map.of("error", "策略不存在或未启用");
|
||||
}
|
||||
|
||||
BigDecimal recommendedRate = strategy.getBaseDosingRate() != null ? strategy.getBaseDosingRate() : BigDecimal.ZERO;
|
||||
|
||||
if (currentTurbidity != null && strategy.getTurbidityThreshold() != null) {
|
||||
if (currentTurbidity.compareTo(strategy.getTurbidityThreshold()) > 0) {
|
||||
BigDecimal factor = currentTurbidity.divide(strategy.getTurbidityThreshold(), 4, RoundingMode.HALF_UP);
|
||||
recommendedRate = recommendedRate.multiply(factor);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentFlow != null && strategy.getFlowThreshold() != null) {
|
||||
if (currentFlow.compareTo(strategy.getFlowThreshold()) > 0) {
|
||||
BigDecimal flowFactor = currentFlow.divide(strategy.getFlowThreshold(), 4, RoundingMode.HALF_UP);
|
||||
recommendedRate = recommendedRate.multiply(flowFactor);
|
||||
}
|
||||
}
|
||||
|
||||
if (strategy.getMinDosingRate() != null) {
|
||||
recommendedRate = recommendedRate.max(strategy.getMinDosingRate());
|
||||
}
|
||||
if (strategy.getMaxDosingRate() != null) {
|
||||
recommendedRate = recommendedRate.min(strategy.getMaxDosingRate());
|
||||
}
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("strategyId", strategyId);
|
||||
result.put("strategyName", strategy.getStrategyName());
|
||||
result.put("recommendedRate", recommendedRate.setScale(4, RoundingMode.HALF_UP));
|
||||
result.put("chemicalName", strategy.getChemicalName());
|
||||
result.put("processStage", strategy.getProcessStage());
|
||||
return result;
|
||||
}
|
||||
|
||||
// ==================== 药剂库存 ====================
|
||||
|
||||
public List<ChemicalStock> listStocks(String station, String status) {
|
||||
LambdaQueryWrapper<ChemicalStock> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(StringUtils.hasText(station), ChemicalStock::getStation, station)
|
||||
.eq(StringUtils.hasText(status), ChemicalStock::getStatus, status)
|
||||
.orderByAsc(ChemicalStock::getStatus);
|
||||
return stockMapper.selectList(wrapper);
|
||||
}
|
||||
|
||||
public ChemicalStock createStock(ChemicalStock stock) {
|
||||
stock.setCreatedTime(LocalDateTime.now());
|
||||
stock.setUpdatedTime(LocalDateTime.now());
|
||||
updateStockStatus(stock);
|
||||
stockMapper.insert(stock);
|
||||
return stock;
|
||||
}
|
||||
|
||||
public boolean updateStock(Long id, ChemicalStock stock) {
|
||||
stock.setId(id);
|
||||
stock.setUpdatedTime(LocalDateTime.now());
|
||||
updateStockStatus(stock);
|
||||
return stockMapper.updateById(stock) > 0;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> lowStockAlerts() {
|
||||
LambdaQueryWrapper<ChemicalStock> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(ChemicalStock::getStatus, "low").or().eq(ChemicalStock::getStatus, "out");
|
||||
List<ChemicalStock> lowStocks = stockMapper.selectList(wrapper);
|
||||
|
||||
return lowStocks.stream().map(s -> {
|
||||
Map<String, Object> alert = new HashMap<>();
|
||||
alert.put("id", s.getId());
|
||||
alert.put("chemicalName", s.getChemicalName());
|
||||
alert.put("currentStock", s.getCurrentStock());
|
||||
alert.put("minStock", s.getMinStock());
|
||||
alert.put("status", s.getStatus());
|
||||
alert.put("station", s.getStation());
|
||||
alert.put("warehouse", s.getWarehouse());
|
||||
BigDecimal deficit = s.getMinStock() != null && s.getCurrentStock() != null
|
||||
? s.getMinStock().subtract(s.getCurrentStock()) : BigDecimal.ZERO;
|
||||
alert.put("deficit", deficit.max(BigDecimal.ZERO));
|
||||
return alert;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void updateStockStatus(ChemicalStock stock) {
|
||||
if (stock.getCurrentStock() == null || stock.getMinStock() == null) {
|
||||
stock.setStatus("normal");
|
||||
return;
|
||||
}
|
||||
if (stock.getCurrentStock().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
stock.setStatus("out");
|
||||
} else if (stock.getCurrentStock().compareTo(stock.getMinStock()) < 0) {
|
||||
stock.setStatus("low");
|
||||
} else {
|
||||
stock.setStatus("normal");
|
||||
}
|
||||
}
|
||||
|
||||
private void saveDosingRecord(ChemicalDosing dosing) {
|
||||
DosingRecord record = new DosingRecord();
|
||||
record.setDosingId(dosing.getId());
|
||||
record.setProcessStage(dosing.getProcessStage());
|
||||
record.setChemicalName(dosing.getChemicalName());
|
||||
record.setDosingAmount(dosing.getDosingAmount());
|
||||
record.setDosingRate(dosing.getDosingRate());
|
||||
record.setConcentration(dosing.getConcentration());
|
||||
record.setFlowRate(dosing.getFlowRate());
|
||||
record.setStation(dosing.getStation());
|
||||
record.setRecordTime(LocalDateTime.now());
|
||||
record.setCreatedTime(LocalDateTime.now());
|
||||
recordMapper.insert(record);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user