fix: 删除全过程鱼类放鱼明细数据&全过程鱼类放鱼数据明细

This commit is contained in:
tangwei 2026-08-20 13:41:03 +08:00
parent bb7ec04a38
commit 4c500b1fd8
4 changed files with 426 additions and 8 deletions

View File

@ -214,6 +214,12 @@ public class FbStationController {
return ResponseResult.successData(fbStationService.getFishRunQgcKendoListCust(dataSourceRequest));
}
@PostMapping("/fishrun/qgc/detail/GetKendoListCust")
@Operation(summary = "全过程鱼类放鱼数据明细(细粒度,按站点+年份+鱼种下钻)")
public ResponseResult getFishRunQgcDetailKendoListCust(@RequestBody DataSourceRequest dataSourceRequest) {
return ResponseResult.successData(fbStationService.getFishRunQgcDetailKendoListCust(dataSourceRequest));
}
@Log(module = "鱼类增殖站模块", value = "删除全过程鱼类放鱼数据")
@PostMapping("/fishrun/qgc/removeFishRunQgcData")
@Operation(summary = "删除全过程鱼类放鱼数据(按站点+年份+鱼种)")
@ -222,6 +228,14 @@ public class FbStationController {
return ResponseResult.success();
}
@Log(module = "鱼类增殖站模块", value = "删除全过程鱼类放鱼明细数据")
@PostMapping("/fishrun/qgc/removeFishRunQgcDetailData")
@Operation(summary = "删除全过程鱼类放鱼明细数据(按明细ID批量)")
public ResponseResult removeFishRunQgcDetailData(@RequestBody List<String> ids) {
fbStationService.removeFishRunQgcDetailData(ids);
return ResponseResult.success();
}
@PostMapping("/fishType/GetKendoListCust")
@Operation(summary = "增殖站鱼种完成情况统计")
public ResponseResult getFishTypeKendoListCust(@RequestBody DataSourceRequest dataSourceRequest) {

View File

@ -0,0 +1,69 @@
package com.yfd.platform.qgc_env.fb.entity.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* 全过程鱼类放鱼数据明细(细粒度)对应 SD_RPIMNFISH_R 明细行
*/
@Data
@Schema(description = "全过程鱼类放鱼数据明细")
public class FbFishRunQgcDetailVo {
@Schema(description = "明细ID (SD_RPIMNFISH_R.ID)")
private String id;
@Schema(description = "放流主表ID (SD_ENGFR_R.ID)")
private String rpimnId;
@Schema(description = "电站站码")
private String stcd;
@Schema(description = "电站名称")
private String stnm;
@Schema(description = "放流年份 yyyy")
private String flyr;
@Schema(description = "鱼种类")
private String ftp;
@Schema(description = "鱼种类名称")
private String ftpName;
@Schema(description = "平均长度")
private BigDecimal fsZ;
@Schema(description = "平均体重")
private BigDecimal fwet;
@Schema(description = "计划放殖鱼数量")
private Long fcnt;
@Schema(description = "标记类型")
private String tagte;
@Schema(description = "标记数量")
private Long tagcnt;
@Schema(description = "鱼龄")
private String fage;
@Schema(description = "鱼类档案ID")
private String fid;
@Schema(description = "保护等级")
private String protectlvl;
@Schema(description = "亲本来源")
private String fa;
@Schema(description = "备注")
private String remark;
@Schema(description = "放流时间")
private Date tm;
}

View File

@ -21,6 +21,13 @@ public interface FbStationService {
DataSourceResult<FbFishRunQgcVo> getFishRunQgcKendoListCust(DataSourceRequest dataSourceRequest);
/**
* 查询全过程鱼类放鱼数据明细(细粒度)用于聚合行下钻查看底层放鱼明细
*
* @param dataSourceRequest 支持 stcd/flyr/ftp 等过滤条件与分页排序
*/
DataSourceResult<FbFishRunQgcDetailVo> getFishRunQgcDetailKendoListCust(DataSourceRequest dataSourceRequest);
/**
* 批量删除全过程鱼类放鱼数据 站点+年份+鱼种 维度逻辑删除 SD_RPIMNFISH_R 明细
*
@ -28,6 +35,13 @@ public interface FbStationService {
*/
boolean removeFishRunQgcData(List<FbFishRunQgcDeleteParam> params);
/**
* 按明细ID批量逻辑删除全过程鱼类放鱼数据并记录操作日志
*
* @param ids 明细ID列表SD_RPIMNFISH_R.ID
*/
boolean removeFishRunQgcDetailData(List<String> ids);
DataSourceResult<FbFishTypeFinishVo> getFishTypeKendoListCust(DataSourceRequest dataSourceRequest);
DataSourceResult<FbFishTypeStcdVo> getFishTypeStcdList(DataSourceRequest dataSourceRequest);

View File

@ -195,6 +195,74 @@ public class FbStationServiceImpl implements FbStationService {
return result;
}
@Override
public DataSourceResult<FbFishRunQgcDetailVo> getFishRunQgcDetailKendoListCust(DataSourceRequest dataSourceRequest) {
DataSourceLoadOptionsBase loadOptions = dataSourceRequest == null ? null : dataSourceRequest.toDevRequest();
String stcd = loadOptions == null ? null : QgcQueryWrapperUtil.getFilterFieldValue(loadOptions, "stcd");
String flyr = loadOptions == null ? null : QgcQueryWrapperUtil.getFilterFieldValue(loadOptions, "flyr");
String ftp = loadOptions == null ? null : QgcQueryWrapperUtil.getFilterFieldValue(loadOptions, "ftp");
Map<String, Object> paramMap = new HashMap<>();
StringBuilder sql = new StringBuilder();
sql.append("SELECT det.ID AS id, ")
.append("run.ID AS rpimnId, ")
.append("run.STCD AS stcd, ")
.append("eng.ENNM AS stnm, ")
.append("TO_CHAR(run.TM, 'YYYY') AS flyr, ")
.append("det.FTP AS ftp, ")
.append(buildFishNameExpr("det.FTP")).append(" AS ftpName, ")
.append("det.FSZ AS fsZ, ")
.append("det.FWET AS fwet, ")
.append("det.FCNT AS fcnt, ")
.append("det.TAGTE AS tagte, ")
.append("det.TAGCNT AS tagcnt, ")
.append("det.FAGE AS fage, ")
.append("det.FID AS fid, ")
.append("det.PROTECTLVL AS protectlvl, ")
.append("det.FA AS fa, ")
.append("det.REMARK AS remark, ")
.append("run.TM AS tm ")
.append("FROM SD_RPIMNFISH_R det ")
.append("INNER JOIN SD_ENGFR_R run ON det.RPIMN_ID = run.ID AND NVL(run.IS_DELETED, 0) = 0 ")
.append("LEFT JOIN SD_ENGINFO_B_H eng ON eng.STCD = run.STCD AND NVL(eng.IS_DELETED, 0) = 0 ")
.append(buildFishJoinSql("det.FTP", "eng.HBRVCD"))
.append("WHERE NVL(det.IS_DELETED, 0) = 0 ");
if (StrUtil.isNotBlank(stcd)) {
paramMap.put("detailStcd", stcd);
sql.append("AND run.STCD = #{map.detailStcd} ");
}
if (StrUtil.isNotBlank(flyr)) {
paramMap.put("detailFlyr", flyr);
sql.append("AND TO_CHAR(run.TM, 'YYYY') = #{map.detailFlyr} ");
}
if (StrUtil.isNotBlank(ftp)) {
paramMap.put("detailFtp", ftp);
sql.append("AND det.FTP = #{map.detailFtp} ");
}
String filterSql = buildFishRunQgcDetailFilterCondition(
dataSourceRequest == null ? null : dataSourceRequest.getFilter(),
paramMap,
new int[]{0}
);
if (StrUtil.isNotBlank(filterSql)) {
sql.append(" AND ").append(filterSql).append(" ");
}
sql.append(buildFishRunQgcDetailOrderBySql(dataSourceRequest == null ? null : dataSourceRequest.getSort()));
Page<?> page = loadOptions == null ? null : QgcQueryWrapperUtil.buildPage(loadOptions, loadOptions.getSkip(), loadOptions.getTake());
List<FbFishRunQgcDetailVo> list = microservicDynamicSQLMapper.pageAllListWithResultType(
page,
sql.toString(),
paramMap,
FbFishRunQgcDetailVo.class
);
DataSourceResult<FbFishRunQgcDetailVo> result = new DataSourceResult<>();
result.setData(list);
result.setTotal(page != null ? page.getTotal() : list.size());
result.setAggregates(new HashMap<>());
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean removeFishRunQgcData(List<FbFishRunQgcDeleteParam> params) {
@ -224,19 +292,67 @@ public class FbStationServiceImpl implements FbStationService {
" AND TO_CHAR(run.TM, 'YYYY') = ? )",
resolveLogOperator(), param.getFtp(), param.getStcd(), param.getFlyr()
);
recordFishRunDeleteLog(beforeRows, param, deleteCount);
recordFishRunDeleteLog(beforeRows, String.format("删除全过程鱼类放鱼数据(站点:%s,年份:%s,删除条数:%d)",
param.getStcd(), param.getFlyr(), deleteCount), deleteCount);
}
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean removeFishRunQgcDetailData(List<String> ids) {
if (CollUtil.isEmpty(ids)) {
return true;
}
List<String> distinctIds = ids.stream()
.filter(StrUtil::isNotBlank)
.distinct()
.collect(Collectors.toList());
if (distinctIds.isEmpty()) {
return true;
}
// 1. 查询删除前的明细行(含站点等信息, 用于记录日志)
List<Map<String, Object>> beforeRows = queryFishRunDetailRowsByIds(distinctIds);
// 2. 分批逻辑删除(ID IN, 防止超过数据库 IN 限制)
int deleteCount = 0;
for (List<String> part : CollUtil.split(distinctIds, 500)) {
deleteCount += batchSoftDeleteFishRunDetail(part);
}
// 3. 记录删除日志(每条明细一条主日志, 子日志挂对应主日志)
recordFishRunDeleteLog(beforeRows, String.format("删除全过程鱼类放鱼数据(明细删除,删除条数:%d)", deleteCount), deleteCount);
return true;
}
private List<Map<String, Object>> queryFishRunDetailRows(FbFishRunQgcDeleteParam param) {
String sql = "SELECT det.ID, det.RPIMN_ID, det.FTP, det.FSZ, det.FWET, det.FCNT, " +
String sql = "SELECT det.ID, det.RPIMN_ID, run.STCD, det.FTP, det.FSZ, det.FWET, det.FCNT, " +
"det.TAGTE, det.TAGCNT, det.FAGE, det.FID, det.PROTECTLVL, det.FA, det.REMARK " +
"FROM SD_RPIMNFISH_R det " +
"INNER JOIN SD_ENGFR_R run ON det.RPIMN_ID = run.ID AND NVL(run.IS_DELETED, 0) = 0 " +
"WHERE NVL(det.IS_DELETED, 0) = 0 " +
" AND det.FTP = ? AND run.STCD = ? AND TO_CHAR(run.TM, 'YYYY') = ?";
List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, param.getFtp(), param.getStcd(), param.getFlyr());
return queryFishRunDetailRowsInternal(sql, param.getFtp(), param.getStcd(), param.getFlyr());
}
private List<Map<String, Object>> queryFishRunDetailRowsByIds(List<String> ids) {
StringBuilder sql = new StringBuilder();
sql.append("SELECT det.ID, det.RPIMN_ID, run.STCD, det.FTP, det.FSZ, det.FWET, det.FCNT, ")
.append("det.TAGTE, det.TAGCNT, det.FAGE, det.FID, det.PROTECTLVL, det.FA, det.REMARK ")
.append("FROM SD_RPIMNFISH_R det ")
.append("INNER JOIN SD_ENGFR_R run ON det.RPIMN_ID = run.ID AND NVL(run.IS_DELETED, 0) = 0 ")
.append("WHERE NVL(det.IS_DELETED, 0) = 0 ")
.append(" AND det.ID IN (");
for (int i = 0; i < ids.size(); i++) {
if (i > 0) {
sql.append(", ");
}
sql.append("?");
}
sql.append(")");
return queryFishRunDetailRowsInternal(sql.toString(), ids.toArray());
}
private List<Map<String, Object>> queryFishRunDetailRowsInternal(String sql, Object... args) {
List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, args);
List<Map<String, Object>> normalized = new ArrayList<>();
for (Map<String, Object> row : rows) {
normalized.add(normalizeFishRunLogRow(row));
@ -244,6 +360,28 @@ public class FbStationServiceImpl implements FbStationService {
return normalized;
}
private int batchSoftDeleteFishRunDetail(List<String> ids) {
if (CollUtil.isEmpty(ids)) {
return 0;
}
StringBuilder sql = new StringBuilder();
sql.append("UPDATE SD_RPIMNFISH_R det ")
.append("SET det.IS_DELETED = 1, det.DELETE_USER = ?, det.DELETE_TIME = SYSDATE ")
.append("WHERE NVL(det.IS_DELETED, 0) = 0 ")
.append(" AND det.ID IN (");
List<Object> params = new ArrayList<>();
params.add(resolveLogOperator());
for (int i = 0; i < ids.size(); i++) {
if (i > 0) {
sql.append(", ");
}
sql.append("?");
params.add(ids.get(i));
}
sql.append(")");
return jdbcTemplate.update(sql.toString(), params.toArray());
}
private Map<String, Object> normalizeFishRunLogRow(Map<String, Object> row) {
Map<String, Object> normalized = new LinkedHashMap<>();
if (row == null || row.isEmpty()) {
@ -255,19 +393,20 @@ public class FbStationServiceImpl implements FbStationService {
return normalized;
}
private void recordFishRunDeleteLog(List<Map<String, Object>> rows, FbFishRunQgcDeleteParam param, int deleteCount) {
private void recordFishRunDeleteLog(List<Map<String, Object>> rows, String remark, int deleteCount) {
if (CollUtil.isEmpty(rows)) {
return;
}
List<MsOperationLogDetail> details = new ArrayList<>();
for (Map<String, Object> row : rows) {
String recordId = asString(row.get("ID"));
String stcd = asString(row.get("STCD"));
MsOperationLog mainLog = new MsOperationLog();
mainLog.setOperator(resolveLogOperator());
mainLog.setRecordId(String.valueOf(row.get("ID")));
mainLog.setRecordId(recordId);
mainLog.setOperateTime(new Date());
mainLog.setTableName(FISH_RUN_DETAIL_TABLE_NAME);
mainLog.setRemark("删除全过程鱼类放鱼数据(站点:" + param.getStcd()
+ ",年份:" + param.getFlyr() + ",删除条数:" + deleteCount + ")");
mainLog.setRemark(remark);
msOperationLogMapper.insert(mainLog);
for (Map.Entry<String, Object> entry : row.entrySet()) {
if (StrUtil.isBlank(entry.getKey()) || entry.getValue() == null) {
@ -276,7 +415,7 @@ public class FbStationServiceImpl implements FbStationService {
MsOperationLogDetail detail = new MsOperationLogDetail();
detail.setMainId(mainLog.getId());
detail.setTableName(FISH_RUN_DETAIL_TABLE_NAME);
detail.setStationCode(param.getStcd());
detail.setStationCode(stcd);
detail.setFieldCode(entry.getKey());
detail.setFieldMeaning(FISH_RUN_FIELD_MEANING_MAP.getOrDefault(entry.getKey(), entry.getKey()));
detail.setOldValueCode(formatFishRunLogValue(entry.getValue()));
@ -290,6 +429,10 @@ public class FbStationServiceImpl implements FbStationService {
}
}
private String asString(Object value) {
return value == null ? null : String.valueOf(value);
}
private String resolveLogOperator() {
try {
return SecurityUtils.getCurrentUsername();
@ -1412,6 +1555,184 @@ public class FbStationServiceImpl implements FbStationService {
return " ORDER BY " + String.join(", ", orderByParts);
}
private String mapFishRunQgcDetailColumn(String field) {
if (StrUtil.isBlank(field)) {
return null;
}
return switch (field) {
case "id" -> "det.ID";
case "rpimnId" -> "det.RPIMN_ID";
case "stcd" -> "run.STCD";
case "stnm" -> "eng.ENNM";
case "flyr" -> "TO_CHAR(run.TM, 'YYYY')";
case "ftp" -> "det.FTP";
case "ftpName" -> buildFishNameExpr("det.FTP");
case "fsZ" -> "det.FSZ";
case "fwet" -> "det.FWET";
case "fcnt" -> "det.FCNT";
case "tagte" -> "det.TAGTE";
case "tagcnt" -> "det.TAGCNT";
case "fage" -> "det.FAGE";
case "fid" -> "det.FID";
case "protectlvl" -> "det.PROTECTLVL";
case "fa" -> "det.FA";
case "remark" -> "det.REMARK";
case "tm" -> "run.TM";
default -> null;
};
}
private Object normalizeFishRunQgcDetailValue(String field, Object value) {
if (value == null) {
return null;
}
if ("fcnt".equals(field) || "tagcnt".equals(field)) {
try {
return Long.parseLong(String.valueOf(value).trim());
} catch (Exception ex) {
return null;
}
}
return value;
}
private String buildFishRunQgcDetailFilterCondition(DataSourceRequest.FilterDescriptor filter,
Map<String, Object> paramMap,
int[] indexHolder) {
if (filter == null) {
return "";
}
if (StrUtil.isNotBlank(filter.getField())) {
return buildFishRunQgcDetailLeafCondition(filter, paramMap, indexHolder);
}
if (CollUtil.isEmpty(filter.getFilters())) {
return "";
}
List<String> conditions = new ArrayList<>();
for (DataSourceRequest.FilterDescriptor child : filter.getFilters()) {
String childSql = buildFishRunQgcDetailFilterCondition(child, paramMap, indexHolder);
if (StrUtil.isNotBlank(childSql)) {
conditions.add("(" + childSql + ")");
}
}
if (conditions.isEmpty()) {
return "";
}
String logic = "or".equalsIgnoreCase(filter.getLogic()) ? " OR " : " AND ";
return String.join(logic, conditions);
}
private String buildFishRunQgcDetailLeafCondition(DataSourceRequest.FilterDescriptor filter,
Map<String, Object> paramMap,
int[] indexHolder) {
// 这三个字段已在主 SQL 中作为下钻条件处理避免重复拼接
if ("stcd".equalsIgnoreCase(filter.getField()) || "flyr".equalsIgnoreCase(filter.getField())
|| "ftp".equalsIgnoreCase(filter.getField())) {
return "";
}
String column = mapFishRunQgcDetailColumn(filter.getField());
if (StrUtil.isBlank(column)) {
return "";
}
String operator = StrUtil.blankToDefault(filter.getOperator(), "eq").toLowerCase();
if ("isnull".equals(operator)) {
return column + " IS NULL";
}
if ("isnotnull".equals(operator)) {
return column + " IS NOT NULL";
}
if ("isempty".equals(operator)) {
return "(" + column + " IS NULL OR " + column + " = '')";
}
if ("isnotempty".equals(operator)) {
return "(" + column + " IS NOT NULL AND " + column + " <> '')";
}
if ("in".equals(operator)) {
List<Object> values = normalizeQgcBsmfRValues(filter.getValue());
if (values.isEmpty()) {
return "";
}
List<String> placeholders = new ArrayList<>();
for (Object value : values) {
String key = "fishRunQgcDetailP" + indexHolder[0]++;
paramMap.put(key, normalizeFishRunQgcDetailValue(filter.getField(), value));
placeholders.add("#{map." + key + "}");
}
return column + " IN (" + String.join(", ", placeholders) + ")";
}
Object value = filter.getValue();
if (value == null) {
return "";
}
String key = "fishRunQgcDetailP" + indexHolder[0]++;
Object normalizedValue = normalizeFishRunQgcDetailValue(filter.getField(), value);
return switch (operator) {
case "eq" -> {
paramMap.put(key, normalizedValue);
yield column + " = #{map." + key + "}";
}
case "neq" -> {
paramMap.put(key, normalizedValue);
yield column + " <> #{map." + key + "}";
}
case "gt" -> {
paramMap.put(key, normalizedValue);
yield column + " > #{map." + key + "}";
}
case "gte" -> {
paramMap.put(key, normalizedValue);
yield column + " >= #{map." + key + "}";
}
case "lt" -> {
paramMap.put(key, normalizedValue);
yield column + " < #{map." + key + "}";
}
case "lte" -> {
paramMap.put(key, normalizedValue);
yield column + " <= #{map." + key + "}";
}
case "contains" -> {
paramMap.put(key, "%" + String.valueOf(normalizedValue).trim() + "%");
yield column + " LIKE #{map." + key + "}";
}
case "doesnotcontain" -> {
paramMap.put(key, "%" + String.valueOf(normalizedValue).trim() + "%");
yield column + " NOT LIKE #{map." + key + "}";
}
case "startswith" -> {
paramMap.put(key, String.valueOf(normalizedValue).trim() + "%");
yield column + " LIKE #{map." + key + "}";
}
case "endswith" -> {
paramMap.put(key, "%" + String.valueOf(normalizedValue).trim());
yield column + " LIKE #{map." + key + "}";
}
default -> "";
};
}
private String buildFishRunQgcDetailOrderBySql(List<DataSourceRequest.SortDescriptor> sorts) {
if (CollUtil.isEmpty(sorts)) {
return " ORDER BY run.TM DESC, det.ID DESC";
}
List<String> orderByParts = new ArrayList<>();
for (DataSourceRequest.SortDescriptor sort : sorts) {
if (sort == null || StrUtil.isBlank(sort.getField())) {
continue;
}
String column = mapFishRunQgcDetailColumn(sort.getField());
if (StrUtil.isBlank(column)) {
continue;
}
String direction = "desc".equalsIgnoreCase(sort.getDir()) ? "DESC" : "ASC";
orderByParts.add(column + " " + direction);
}
if (orderByParts.isEmpty()) {
return " ORDER BY run.TM DESC, det.ID DESC";
}
return " ORDER BY " + String.join(", ", orderByParts);
}
private Date[] parseFishRunFlyrRange(String flyrFilter) {
if (StrUtil.isBlank(flyrFilter)) {
return new Date[]{null, null};