From 551bf2b42dddc3a0d8d0949da17bb2b1ec733b01 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期一, 07 九月 2026 12:39:19 +0800
Subject: [PATCH] 实现化成循环矩阵与独立报表版本
---
battery-business/src/main/java/com/battery/business/report/FormationFieldTemplateService.java | 73 +++++
battery-business/src/main/java/com/battery/business/report/CompareReportRepository.java | 93 ------
ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml | 4
ruoyi-admin/src/main/resources/db/migration-final/V15__formation_reports_and_templates.sql | 18 +
battery-business/src/main/resources/mapper/battery/TBatteryGroupFieldMapper.xml | 2
battery-business/src/main/java/com/battery/business/report/FormationReportRequest.java | 41 +++
battery-business/src/main/java/com/battery/business/report/FormationReportService.java | 249 +++++++++++++++++++
ruoyi-admin/src/main/java/com/battery/business/web/FormationFieldTemplateController.java | 31 ++
battery-business/src/main/java/com/battery/business/report/FormationField.java | 36 ++
battery-business/src/main/java/com/battery/business/report/FormationReportRepository.java | 11
battery-business/src/main/java/com/battery/business/report/ReportVersionRepository.java | 102 +++++++
ruoyi-admin/src/main/java/com/battery/business/web/FormationReportController.java | 69 +++++
12 files changed, 637 insertions(+), 92 deletions(-)
diff --git a/battery-business/src/main/java/com/battery/business/report/CompareReportRepository.java b/battery-business/src/main/java/com/battery/business/report/CompareReportRepository.java
index dd39495..866c240 100644
--- a/battery-business/src/main/java/com/battery/business/report/CompareReportRepository.java
+++ b/battery-business/src/main/java/com/battery/business/report/CompareReportRepository.java
@@ -1,98 +1,11 @@
package com.battery.business.report;
-import java.sql.Timestamp;
-import java.util.List;
-import java.util.Map;
-import com.alibaba.fastjson2.JSON;
import javax.sql.DataSource;
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.stereotype.Repository;
-import com.ruoyi.common.exception.ServiceException;
-/** 报表身份和版本存储;重读只访问报表表,不联查来源测试。 */
+/** 对比报表身份与版本只使用对比表,复用独立版本持久化规则。 */
@Repository
-public class CompareReportRepository
+public class CompareReportRepository extends ReportVersionRepository
{
- private final JdbcTemplate jdbc;
-
- public CompareReportRepository(DataSource dataSource) { jdbc = new JdbcTemplate(dataSource); }
-
- public record Summary(long id, String name, String creator, long createdAt) { }
- public record Version(Summary report, int versionNo, String snapshotJson, String operatorName, long versionCreatedAt, String changeSummary) { }
- public record History(int versionNo, String operatorName, long createdAt, String changeSummary) { }
-
- /** 查询所有已保存报表摘要,允许授权用户跨创建人查看。 */
- public List<Summary> list()
- {
- return jdbc.query("select report_id, report_name, creator_name, created_at_utc from t_compare_report order by created_at_utc desc, report_id desc",
- (rs, index) -> new Summary(rs.getLong(1), rs.getString(2), rs.getString(3), rs.getTimestamp(4).getTime()));
- }
-
- /** 重读最新完整版本;来源不存在不影响结果。 */
- public Version get(long id)
- { return get(id, null); }
-
- /** 读取指定历史版,版本号为空时读取最新版本。 */
- public Version get(long id, Integer versionNo)
- {
- var rows = jdbc.query("select r.report_id, r.report_name, r.creator_name, r.created_at_utc, v.version_no, v.snapshot_json, v.operator_name, v.created_at_utc, v.change_summary "
- + "from t_compare_report r join t_compare_report_version v on v.report_id=r.report_id where r.report_id=? "
- + (versionNo == null ? "and v.version_no=(select max(v2.version_no) from t_compare_report_version v2 where v2.report_id=r.report_id)" : "and v.version_no=?"),
- (rs, index) -> new Version(new Summary(rs.getLong(1), rs.getString(2), rs.getString(3), rs.getTimestamp(4).getTime()),
- rs.getInt(5), rs.getString(6), rs.getString(7), rs.getTimestamp(8).getTime(), rs.getString(9)),
- versionNo == null ? new Object[] { id } : new Object[] { id, versionNo });
- if (rows.isEmpty()) throw new ServiceException("对比报表或版本不存在");
- return rows.get(0);
- }
-
- /** 历史摘要只来自正式版本,不返回测量快照。 */
- public List<History> history(long id)
- {
- return jdbc.query("select version_no,operator_name,created_at_utc,change_summary from t_compare_report_version where report_id=? order by version_no desc",
- (rs, index) -> new History(rs.getInt(1), rs.getString(2), rs.getTimestamp(3).getTime(), rs.getString(4)), id);
- }
-
- /** 业务事务内锁定报表,版本号分配与旧基准拒绝共用此锁。 */
- public void lock(long id)
- {
- if (jdbc.queryForList("select report_id from t_compare_report where report_id=? for update", Long.class, id).isEmpty())
- throw new ServiceException("对比报表不存在");
- }
-
- /** 仅在业务事务和报表锁内调用;物理清理全部版本,同步保留最小操作日志。 */
- public void delete(long id, long operatorId, String operatorName)
- {
- String name = jdbc.queryForObject("select report_name from t_compare_report where report_id=?", String.class, id);
- jdbc.update("delete from t_compare_report_version where report_id=?", id);
- jdbc.update("delete from t_compare_report where report_id=?", id);
- // 不走异步 @Log,日志失败必须回滚删除;只保留身份,不读取或记录测量快照。
- jdbc.update("insert into sys_oper_log(title,business_type,method,request_method,operator_type,oper_name,oper_url,oper_param,status,oper_time) values(?,3,?,'DELETE',1,?,?,?,0,?)",
- "对比报表删除", "compare-report.delete", operatorName, "/system/compare-report/" + id,
- JSON.toJSONString(Map.of("reportId", id, "reportName", name, "operatorId", operatorId)), new Timestamp(System.currentTimeMillis()));
- }
-
- /** 只追加版本并更新列表名称;业务事务保证两步同时成功。 */
- public void append(long id, int versionNo, String name, String snapshot, long operatorId, String operatorName, String summary)
- {
- jdbc.update("insert into t_compare_report_version(report_id,version_no,snapshot_json,operator_id,operator_name,created_at_utc,change_summary) values(?,?,?,?,?,?,?)",
- id, versionNo, snapshot, operatorId, operatorName, new Timestamp(System.currentTimeMillis()), summary);
- jdbc.update("update t_compare_report set report_name=? where report_id=?", name, id);
- }
-
- /** 由业务事务调用,身份与首个版本任一写入失败必须一起回滚。 */
- public long insert(String name, String snapshot, long operatorId, String operatorName, long createdAt)
- {
- var keys = new GeneratedKeyHolder();
- jdbc.update(connection -> {
- var statement = connection.prepareStatement("insert into t_compare_report(report_name,creator_id,creator_name,created_at_utc) values(?,?,?,?)", java.sql.Statement.RETURN_GENERATED_KEYS);
- statement.setString(1, name); statement.setLong(2, operatorId); statement.setString(3, operatorName);
- statement.setTimestamp(4, new Timestamp(createdAt));
- return statement;
- }, keys);
- long id = keys.getKey().longValue();
- jdbc.update("insert into t_compare_report_version(report_id,version_no,snapshot_json,operator_id,operator_name,created_at_utc,change_summary) values(?,1,?,?,?,?,?)",
- id, snapshot, operatorId, operatorName, new Timestamp(createdAt), "首次保存对比报表");
- return id;
- }
+ public CompareReportRepository(DataSource dataSource) { super(dataSource, "compare", "对比"); }
}
diff --git a/battery-business/src/main/java/com/battery/business/report/FormationField.java b/battery-business/src/main/java/com/battery/business/report/FormationField.java
new file mode 100644
index 0000000..5968dc5
--- /dev/null
+++ b/battery-business/src/main/java/com/battery/business/report/FormationField.java
@@ -0,0 +1,36 @@
+package com.battery.business.report;
+
+import java.util.Map;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.ruoyi.common.exception.ServiceException;
+
+/** 化成字段结构不携带数据;循环测量单位固定,未知公式仅允许人工项。 */
+public record FormationField(String key, String scope, String kind, String displayName, String unit, Long fieldDefinitionId)
+{
+ public static final Map<String, String> METRIC_UNITS = Map.of("chargeCapacity", "mAh", "dischargeCapacity", "mAh",
+ "chargeEnergy", "mWh", "dischargeEnergy", "mWh", "efficiency", "%", "energyEfficiency", "%");
+
+ public FormationField
+ {
+ if (key == null || key.isBlank() || key.length() > 100 || key.contains("|")
+ || !("detail".equals(scope) || "cycle".equals(scope))
+ || !("battery".equals(kind) || "metric".equals(kind) || "manual".equals(kind)))
+ throw new ServiceException("化成字段身份或分类无效");
+ displayName = displayName == null ? key : displayName.trim();
+ unit = unit == null ? "" : unit.trim();
+ if (displayName.isBlank() || displayName.length() > 100 || unit.length() > 30
+ || ("battery".equals(kind) && !"detail".equals(scope))
+ || (fieldDefinitionId != null && (fieldDefinitionId <= 0 || !"battery".equals(kind)))
+ || ("metric".equals(kind) && (!"cycle".equals(scope) || !unit.equals(METRIC_UNITS.get(key)))))
+ throw new ServiceException("化成字段展示名称、单位或共享身份无效");
+ if ((METRIC_UNITS.containsKey(key) && !"metric".equals(kind))
+ || (java.util.Set.of("Z", "H", "F", "A", "B").contains(key) && !"battery".equals(kind))
+ || (java.util.Set.of("FN", "C", "method2", "manual", "chargePerGram", "chargePerGramTotal", "capacity1v", "capacity1vf",
+ "dischargeInflection", "dischargeInflectionTotal").contains(key) && !"manual".equals(kind)))
+ throw new ServiceException("固定字段不能改变来源分类");
+ }
+
+ /** 模板和字段设置均拒绝混入测量、测试选择或人工值。 */
+ @JsonAnySetter
+ public void rejectUnknown(String field, Object value) { throw new ServiceException("化成字段不允许提交属性:" + field); }
+}
diff --git a/battery-business/src/main/java/com/battery/business/report/FormationFieldTemplateService.java b/battery-business/src/main/java/com/battery/business/report/FormationFieldTemplateService.java
new file mode 100644
index 0000000..e55af41
--- /dev/null
+++ b/battery-business/src/main/java/com/battery/business/report/FormationFieldTemplateService.java
@@ -0,0 +1,73 @@
+package com.battery.business.report;
+
+import java.sql.Timestamp;
+import java.util.List;
+import javax.sql.DataSource;
+import com.alibaba.fastjson2.JSON;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.ruoyi.common.exception.ServiceException;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.support.GeneratedKeyHolder;
+import org.springframework.stereotype.Service;
+
+/** 化成模板仅存详情和循环字段结构,独立于对比模板及所有报表版本。 */
+@Service
+public class FormationFieldTemplateService
+{
+ private final JdbcTemplate jdbc;
+ private final FormationReportService reports;
+ public FormationFieldTemplateService(DataSource source, FormationReportService reports)
+ { this.jdbc = new JdbcTemplate(source); this.reports = reports; }
+
+ public record Template(long id, String name, String type, List<FormationField> fields, String creator, long updatedAt) { }
+ public record Request(@NotBlank @Size(max = 100) String name,
+ @NotNull @Size(min = 1, max = 100) List<@NotNull @Valid FormationField> fields)
+ {
+ /** 模板不接受整份报表、来源选择或数据值。 */
+ @JsonAnySetter
+ public void rejectUnknown(String field, Object value) { throw new ServiceException("化成模板不允许提交属性:" + field); }
+ }
+
+ /** 查看该类型全部模板,保存顺序即应用顺序。 */
+ public List<Template> list()
+ {
+ return jdbc.query("select template_id,template_name,fields_json,creator_name,updated_at_utc from t_formation_report_field_template where template_type='formation' order by template_name,template_id",
+ (rs, index) -> new Template(rs.getLong(1), rs.getString(2), "formation", JSON.parseArray(rs.getString(3), FormationField.class), rs.getString(4), rs.getTimestamp(5).getTime()));
+ }
+
+ /** 新增前解析共享定义身份,同名模板须显式更新。 */
+ public long create(Request request, long userId, String userName)
+ {
+ var fields = reports.validateFields(request.fields(), true);
+ var key = new GeneratedKeyHolder();
+ try
+ {
+ jdbc.update(connection -> {
+ var statement = connection.prepareStatement("insert into t_formation_report_field_template(template_name,template_type,fields_json,creator_id,creator_name,created_at_utc,updated_at_utc) values(?,'formation',?,?,?,?,?)", java.sql.Statement.RETURN_GENERATED_KEYS);
+ statement.setString(1, request.name().trim()); statement.setString(2, JSON.toJSONString(fields));
+ statement.setLong(3, userId); statement.setString(4, userName);
+ var now = new Timestamp(System.currentTimeMillis()); statement.setTimestamp(5, now); statement.setTimestamp(6, now);
+ return statement;
+ }, key);
+ return key.getKey().longValue();
+ }
+ catch (org.springframework.dao.DuplicateKeyException error) { throw new ServiceException("模板名称已存在"); }
+ }
+
+ /** 修改只影响后续主动应用,不访问报表版本;允许跨创建人协作。 */
+ public void update(long id, Request request)
+ {
+ var fields = reports.validateFields(request.fields(), true);
+ try
+ {
+ if (jdbc.update("update t_formation_report_field_template set template_name=?,fields_json=?,updated_at_utc=? where template_id=? and template_type='formation'",
+ request.name().trim(), JSON.toJSONString(fields), new Timestamp(System.currentTimeMillis()), id) == 0)
+ throw new ServiceException("化成字段模板不存在");
+ }
+ catch (org.springframework.dao.DuplicateKeyException error) { throw new ServiceException("模板名称已存在"); }
+ }
+}
diff --git a/battery-business/src/main/java/com/battery/business/report/FormationReportRepository.java b/battery-business/src/main/java/com/battery/business/report/FormationReportRepository.java
new file mode 100644
index 0000000..52dbef0
--- /dev/null
+++ b/battery-business/src/main/java/com/battery/business/report/FormationReportRepository.java
@@ -0,0 +1,11 @@
+package com.battery.business.report;
+
+import javax.sql.DataSource;
+import org.springframework.stereotype.Repository;
+
+/** 化成身份与版本使用独立表,复用对比报表的事务内存储规则。 */
+@Repository
+public class FormationReportRepository extends ReportVersionRepository
+{
+ public FormationReportRepository(DataSource dataSource) { super(dataSource, "formation", "化成"); }
+}
diff --git a/battery-business/src/main/java/com/battery/business/report/FormationReportRequest.java b/battery-business/src/main/java/com/battery/business/report/FormationReportRequest.java
new file mode 100644
index 0000000..ba4b875
--- /dev/null
+++ b/battery-business/src/main/java/com/battery/business/report/FormationReportRequest.java
@@ -0,0 +1,41 @@
+package com.battery.business.report;
+
+import java.util.List;
+import java.util.Map;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.ruoyi.common.exception.ServiceException;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Positive;
+import jakarta.validation.constraints.Size;
+
+/** 新建化成报表仅提交来源身份和展示内容,事实在保存时由后端读取。 */
+public record FormationReportRequest(@NotBlank @Size(max = 100) String name, @Size(max = 2000) String description,
+ @NotNull @Size(min = 1) List<@NotNull @Valid Selection> selections, @NotNull @Valid Settings settings)
+{
+ public record Selection(@NotNull @Positive Long testId, @NotNull @Positive Long testBatteryId) { }
+
+ /** 值按测试、电池、循环、字段归属,只属于报表展示,不反写测量快照。 */
+ public record Settings(@NotNull @Size(min = 1, max = 100) List<@NotNull @Valid FormationField> fields,
+ @NotNull Map<String, @NotNull String> values)
+ {
+ /** 展示设置拒绝来源和测量快照。 */
+ @JsonAnySetter
+ public void rejectUnknown(String field, Object value) { throw new ServiceException("化成设置不允许提交属性:" + field); }
+ }
+
+ /** 编辑保持后端冻结来源范围,不接收替代来源或测量。 */
+ public record Edit(@Positive int baseVersionNo, @NotBlank @Size(max = 100) String name,
+ @Size(max = 2000) String description, @NotNull @Valid Settings settings,
+ @NotBlank @Size(max = 500) String changeSummary)
+ {
+ /** 拒绝借普通编辑替换冻结来源或测量。 */
+ @JsonAnySetter
+ public void rejectUnknown(String field, Object value) { throw new ServiceException("化成编辑不允许提交属性:" + field); }
+ }
+
+ /** 新建只接收定义的展示内容与真实来源身份。 */
+ @JsonAnySetter
+ public void rejectUnknown(String field, Object value) { throw new ServiceException("化成报表不允许提交属性:" + field); }
+}
diff --git a/battery-business/src/main/java/com/battery/business/report/FormationReportService.java b/battery-business/src/main/java/com/battery/business/report/FormationReportService.java
new file mode 100644
index 0000000..014f3c5
--- /dev/null
+++ b/battery-business/src/main/java/com/battery/business/report/FormationReportService.java
@@ -0,0 +1,249 @@
+package com.battery.business.report;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Stream;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.alibaba.fastjson2.JSONWriter;
+import com.battery.business.domain.CurrentBatteryField;
+import com.battery.business.domain.TBatteryGroupField;
+import com.battery.business.domain.TTest;
+import com.battery.business.domain.TTestBattery;
+import com.battery.business.report.FormationReportRequest.Selection;
+import com.battery.business.report.FormationReportRequest.Settings;
+import com.battery.business.service.ITBatteryGroupFieldService;
+import com.battery.business.service.ITTestService;
+import com.battery.business.service.ITestDataQueryService;
+import com.ruoyi.common.exception.ServiceException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/** 化成按实际循环并集展示;来源读取仅发生在预览、首次保存及主动更新。 */
+@Service
+public class FormationReportService
+{
+ private final ITTestService tests;
+ private final ITestDataQueryService data;
+ private final FormationReportRepository reports;
+ private final ITBatteryGroupFieldService definitions;
+
+ public FormationReportService(ITTestService tests, ITestDataQueryService data,
+ FormationReportRepository reports, ITBatteryGroupFieldService definitions)
+ { this.tests = tests; this.data = data; this.reports = reports; this.definitions = definitions; }
+
+ public record Candidate(Selection selection, String name, String type, long groupId, String batteryGroup, String batteryNo) { }
+ public record Item(Candidate source, List<Integer> cycleNos, List<Map<String, Object>> cycles,
+ Map<String, Map<String, BigDecimal>> metrics, List<CurrentBatteryField> businessFields) { }
+ public record Matrix(List<Item> items, List<Integer> cycleNos) { }
+ public record Summary(long id, String name, String creator, long createdAt, String testName, String batteryGroup) { }
+
+ /** 列表来源名称取自已保存版本,测试删除后仍保留筛选与识别信息。 */
+ public List<Summary> list()
+ {
+ return reports.list().stream().map(summary -> {
+ var sources = items(JSON.parseObject(reports.get(summary.id()).snapshotJson()));
+ return new Summary(summary.id(), summary.name(), summary.creator(), summary.createdAt(),
+ String.join("、", sources.stream().map(item -> item.source().name()).distinct().toList()),
+ String.join("、", sources.stream().map(item -> item.source().batteryGroup()).distinct().toList()));
+ }).toList();
+ }
+
+ /** 只提供类型名称精确为“化成”的检测完成测试及其真实参与电池。 */
+ public List<Candidate> sources()
+ {
+ TTest query = new TTest(); query.setTestStatus("COMPLETED");
+ List<Candidate> result = new ArrayList<>();
+ for (TTest summary : tests.selectTestList(query))
+ {
+ if (!"化成".equals(summary.getTestTypeName())) continue;
+ TTest test = tests.selectTestById(summary.getTestId());
+ if (eligible(test)) for (TTestBattery battery : test.getBatteries()) result.add(candidate(test, battery));
+ }
+ return result;
+ }
+
+ /** 校验电池属于所选测试,返回三类持久化记录实际出现的循环号与循环统计。 */
+ public Matrix preview(List<Selection> selections)
+ {
+ if (selections == null || selections.isEmpty() || selections.contains(null)
+ || new HashSet<>(selections).size() != selections.size()) throw new ServiceException("请选择不重复的化成测试电池");
+ Map<Long, TTest> loaded = new HashMap<>();
+ Map<Long, List<Map<String, Object>>> cycles = new HashMap<>(), records = new HashMap<>(), steps = new HashMap<>();
+ List<Item> items = new ArrayList<>();
+ for (Selection selection : selections)
+ {
+ if (selection.testId() == null || selection.testBatteryId() == null) throw new ServiceException("测试和电池身份不能为空");
+ TTest test = loaded.computeIfAbsent(selection.testId(), tests::selectTestById);
+ if (!eligible(test)) throw new ServiceException("仅可选择类型为化成的检测完成测试");
+ TTestBattery battery = test.getBatteries().stream().filter(row -> selection.testBatteryId().equals(row.getTestBatteryId()))
+ .findFirst().orElseThrow(() -> new ServiceException("电池不属于所选测试"));
+ List<Map<String, Object>> rows = batteryRows(cycles.computeIfAbsent(selection.testId(), data::cycles), battery);
+ var actual = Stream.of(rows, batteryRows(records.computeIfAbsent(selection.testId(), data::records), battery),
+ batteryRows(steps.computeIfAbsent(selection.testId(), data::steps), battery)).flatMap(List::stream)
+ .map(this::cycleNo).filter(no -> no > 0).distinct().sorted().toList();
+ items.add(new Item(candidate(test, battery), actual, rows, metrics(rows, actual), battery.getFields()));
+ }
+ return new Matrix(items, items.stream().flatMap(item -> item.cycleNos().stream()).distinct().sorted().toList());
+ }
+
+ /** 从当前来源冻结所选字段;首版和报表身份在同一事务提交。 */
+ @Transactional(rollbackFor = Exception.class)
+ public Map<String, Object> create(FormationReportRequest request, long userId, String userName)
+ {
+ Settings settings = new Settings(validateFields(request.settings().fields(), true), request.settings().values());
+ Matrix matrix = freeze(preview(request.selections()), settings.fields());
+ validateValues(settings, matrix.items());
+ var snapshot = new LinkedHashMap<String, Object>();
+ snapshot.put("name", request.name().trim()); snapshot.put("description", request.description());
+ snapshot.put("items", matrix.items()); snapshot.put("cycleNos", matrix.cycleNos()); snapshot.put("settings", settings);
+ snapshot.put("units", Map.of("chargeCapacity", "Ah", "dischargeCapacity", "Ah", "chargeEnergy", "Wh", "dischargeEnergy", "Wh", "efficiency", "%", "energyEfficiency", "%"));
+ snapshot.put("displayUnits", FormationField.METRIC_UNITS); snapshot.put("timeUnit", "epoch-milliseconds");
+ return get(reports.insert(request.name().trim(), json(snapshot), userId, userName, System.currentTimeMillis()), null);
+ }
+
+ /** 当前或历史版本只读取独立快照,来源删除后仍能重开。 */
+ public Map<String, Object> get(long id, Integer versionNo)
+ {
+ var version = reports.get(id, versionNo);
+ var result = JSON.parseObject(version.snapshotJson());
+ result.put("id", id); result.put("versionNo", version.versionNo());
+ result.put("creator", version.report().creator()); result.put("createdAt", version.report().createdAt());
+ result.put("operatorName", version.operatorName()); result.put("versionCreatedAt", version.versionCreatedAt());
+ result.put("changeSummary", version.changeSummary());
+ return result;
+ }
+
+ /** 仅追加报表展示内容;共享定义或来源已删除也不影响基于快照的普通编辑。 */
+ @Transactional(rollbackFor = Exception.class)
+ public Map<String, Object> edit(long id, FormationReportRequest.Edit request, long userId, String userName)
+ {
+ var snapshot = current(id, request.baseVersionNo());
+ validateFields(request.settings().fields(), false);
+ validateValues(request.settings(), items(snapshot));
+ snapshot.put("name", request.name().trim()); snapshot.put("description", request.description()); snapshot.put("settings", request.settings());
+ reports.append(id, request.baseVersionNo() + 1, request.name().trim(), json(snapshot), userId, userName, request.changeSummary().trim());
+ return get(id, null);
+ }
+
+ /** 主动重读已保存范围,保留仍属于当前循环的人工值;不存在的来源使整次提交失败。 */
+ @Transactional(rollbackFor = Exception.class)
+ public Map<String, Object> refresh(long id, CompareReportEditRequest.Refresh request, long userId, String userName)
+ {
+ var snapshot = current(id, request.baseVersionNo());
+ var settings = JSON.parseObject(json(snapshot.get("settings")), Settings.class);
+ Matrix matrix = freeze(preview(items(snapshot).stream().map(item -> item.source().selection()).toList()), settings.fields());
+ Set<String> allowed = valueKeys(settings.fields(), matrix.items());
+ Map<String, String> values = new LinkedHashMap<>();
+ settings.values().forEach((key, value) -> { if (allowed.contains(key)) values.put(key, value); });
+ snapshot.put("items", matrix.items()); snapshot.put("cycleNos", matrix.cycleNos()); snapshot.put("settings", new Settings(settings.fields(), values));
+ reports.append(id, request.baseVersionNo() + 1, snapshot.getString("name"), json(snapshot), userId, userName, "按最新来源生成:" + request.changeSummary().trim());
+ return get(id, null);
+ }
+
+ /** 删除全部化成版本,与最小删除日志在同一事务提交,不删除测试事实。 */
+ @Transactional(rollbackFor = Exception.class)
+ public void delete(long id, long userId, String userName) { reports.lock(id); reports.delete(id, userId, userName); }
+
+ /** 校验有序结构;首次保存及模板解析共享身份,普通编辑沿用冻结身份。 */
+ public List<FormationField> validateFields(List<FormationField> fields, boolean resolveDefinitions)
+ {
+ if (fields == null || fields.isEmpty() || fields.size() > 100 || fields.contains(null)) throw new ServiceException("化成字段不能为空");
+ var names = new HashSet<String>(); var ids = new HashSet<Long>();
+ var current = resolveDefinitions ? definitions.selectBatteryGroupFieldList(new TBatteryGroupField()) : List.<TBatteryGroupField>of();
+ return fields.stream().map(field -> {
+ if (!names.add(field.scope() + "|" + field.key())) throw new ServiceException("化成字段不能重复");
+ if (!"battery".equals(field.kind())) return field;
+ Long id = field.fieldDefinitionId();
+ if (resolveDefinitions)
+ {
+ var definition = current.stream().filter(value -> field.fieldDefinitionId() == null
+ ? value.getFieldName().equals(field.key()) : value.getFieldId().equals(field.fieldDefinitionId())).findFirst();
+ if (definition.isEmpty() && !Set.of("Z", "H", "F", "A", "B").contains(field.key()))
+ throw new ServiceException("共享字段不存在,请重新选择:" + field.key());
+ if (definition.isPresent()) id = definition.get().getFieldId();
+ }
+ if (id != null && !ids.add(id)) throw new ServiceException("共享字段不能重复");
+ return new FormationField(field.key(), field.scope(), field.kind(), field.displayName(), field.unit(), id);
+ }).toList();
+ }
+
+ private JSONObject current(long id, int baseVersionNo)
+ {
+ // 与删除共用报表身份锁;旧页面只能重开,不能从陈旧基准覆盖新版本。
+ reports.lock(id);
+ var version = reports.get(id);
+ if (version.versionNo() != baseVersionNo) throw new ServiceException("报表已有新版本,请重新打开后编辑");
+ return JSON.parseObject(version.snapshotJson());
+ }
+
+ private Matrix freeze(Matrix matrix, List<FormationField> fields)
+ {
+ Set<String> selected = new HashSet<>(fields.stream().filter(field -> "metric".equals(field.kind())).map(FormationField::key).toList());
+ if (selected.contains("energyEfficiency")) selected.addAll(List.of("chargeEnergy", "dischargeEnergy"));
+ var frozen = matrix.items().stream().map(item -> {
+ var rows = item.cycles().stream().map(row -> {
+ Map<String, Object> copy = new LinkedHashMap<>(row);
+ FormationField.METRIC_UNITS.keySet().stream().filter(key -> !selected.contains(key)).forEach(copy::remove);
+ return copy;
+ }).toList();
+ return new Item(item.source(), item.cycleNos(), rows, metrics(rows, item.cycleNos()), item.businessFields().stream()
+ .filter(value -> fields.stream().anyMatch(field -> "battery".equals(field.kind()) && (field.fieldDefinitionId() == null
+ ? field.key().equals(value.getFieldName()) : field.fieldDefinitionId().equals(value.getFieldDefinitionId())))).toList());
+ }).toList();
+ return new Matrix(frozen, matrix.cycleNos());
+ }
+
+ private void validateValues(Settings settings, List<Item> items)
+ {
+ if (settings.values() == null || !valueKeys(settings.fields(), items).containsAll(settings.values().keySet())
+ || settings.values().values().stream().anyMatch(value -> value == null || value.length() > 500))
+ throw new ServiceException("人工值不属于当前字段、测试电池或实际循环");
+ }
+
+ private Set<String> valueKeys(List<FormationField> fields, List<Item> items)
+ {
+ Set<String> keys = new HashSet<>();
+ for (Item item : items) for (FormationField field : fields)
+ {
+ String prefix = item.source().selection().testId() + "|" + item.source().selection().testBatteryId() + "|";
+ for (int cycle : "detail".equals(field.scope()) ? List.of(0) : item.cycleNos()) keys.add(prefix + cycle + "|" + field.key());
+ }
+ return keys;
+ }
+
+ private Map<String, Map<String, BigDecimal>> metrics(List<Map<String, Object>> rows, List<Integer> cycleNos)
+ {
+ Map<String, Map<String, BigDecimal>> result = new LinkedHashMap<>();
+ for (int no : cycleNos)
+ {
+ var matches = rows.stream().filter(row -> cycleNo(row) == no).toList();
+ Map<String, BigDecimal> values = new LinkedHashMap<>();
+ // 多条统计记录没有已确认的合并口径,保持缺失,不挑任意一条或借用其他循环。
+ if (matches.size() == 1) for (String key : FormationField.METRIC_UNITS.keySet())
+ if (matches.get(0).get(key) instanceof Number number) values.put(key, new BigDecimal(number.toString()));
+ BigDecimal charge = values.get("chargeEnergy"), discharge = values.get("dischargeEnergy");
+ if (charge != null && discharge != null && charge.signum() > 0)
+ values.put("energyEfficiency", discharge.multiply(BigDecimal.valueOf(100)).divide(charge, MathContext.DECIMAL128));
+ result.put(Integer.toString(no), values);
+ }
+ return result;
+ }
+
+ private boolean eligible(TTest test) { return "COMPLETED".equals(test.getTestStatus()) && "化成".equals(test.getTestTypeName()); }
+ private Candidate candidate(TTest test, TTestBattery battery)
+ { return new Candidate(new Selection(test.getTestId(), battery.getTestBatteryId()), test.getTestName(), test.getTestTypeName(), battery.getGroupId(), battery.getGroupName(), battery.getBatteryNo()); }
+ private List<Map<String, Object>> batteryRows(List<Map<String, Object>> rows, TTestBattery battery)
+ { return rows.stream().filter(row -> Objects.equals(battery.getBatteryNo(), row.get("batteryNo"))).toList(); }
+ private int cycleNo(Map<String, Object> row) { return row.get("cycleNo") instanceof Number number ? number.intValue() : 0; }
+ private List<Item> items(JSONObject snapshot) { return JSON.parseArray(json(snapshot.get("items")), Item.class); }
+ private String json(Object value) { return JSON.toJSONString(value, JSONWriter.Feature.WriteMapNullValue); }
+}
diff --git a/battery-business/src/main/java/com/battery/business/report/ReportVersionRepository.java b/battery-business/src/main/java/com/battery/business/report/ReportVersionRepository.java
new file mode 100644
index 0000000..32b05e0
--- /dev/null
+++ b/battery-business/src/main/java/com/battery/business/report/ReportVersionRepository.java
@@ -0,0 +1,102 @@
+package com.battery.business.report;
+
+import java.sql.Timestamp;
+import java.util.List;
+import java.util.Map;
+import com.alibaba.fastjson2.JSON;
+import javax.sql.DataSource;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.support.GeneratedKeyHolder;
+import com.ruoyi.common.exception.ServiceException;
+
+/** 报表身份和版本存储;重读只访问报表表,不联查来源测试。 */
+public abstract class ReportVersionRepository
+{
+ private final JdbcTemplate jdbc;
+ private final String type;
+ private final String title;
+
+ protected ReportVersionRepository(DataSource dataSource, String type, String title)
+ { jdbc = new JdbcTemplate(dataSource); this.type = type; this.title = title; }
+
+ // 类型仅由两个仓储构造器指定,SQL 表名不接受请求输入;两类共用已验证的版本和删除规则。
+ private String sql(String statement) { return statement.replace("t_compare_report", "t_" + type + "_report"); }
+
+ public record Summary(long id, String name, String creator, long createdAt) { }
+ public record Version(Summary report, int versionNo, String snapshotJson, String operatorName, long versionCreatedAt, String changeSummary) { }
+ public record History(int versionNo, String operatorName, long createdAt, String changeSummary) { }
+
+ /** 查询所有已保存报表摘要,允许授权用户跨创建人查看。 */
+ public List<Summary> list()
+ {
+ return jdbc.query(sql("select report_id, report_name, creator_name, created_at_utc from t_compare_report order by created_at_utc desc, report_id desc"),
+ (rs, index) -> new Summary(rs.getLong(1), rs.getString(2), rs.getString(3), rs.getTimestamp(4).getTime()));
+ }
+
+ /** 重读最新完整版本;来源不存在不影响结果。 */
+ public Version get(long id)
+ { return get(id, null); }
+
+ /** 读取指定历史版,版本号为空时读取最新版本。 */
+ public Version get(long id, Integer versionNo)
+ {
+ var rows = jdbc.query(sql("select r.report_id, r.report_name, r.creator_name, r.created_at_utc, v.version_no, v.snapshot_json, v.operator_name, v.created_at_utc, v.change_summary "
+ + "from t_compare_report r join t_compare_report_version v on v.report_id=r.report_id where r.report_id=? "
+ + (versionNo == null ? "and v.version_no=(select max(v2.version_no) from t_compare_report_version v2 where v2.report_id=r.report_id)" : "and v.version_no=?")),
+ (rs, index) -> new Version(new Summary(rs.getLong(1), rs.getString(2), rs.getString(3), rs.getTimestamp(4).getTime()),
+ rs.getInt(5), rs.getString(6), rs.getString(7), rs.getTimestamp(8).getTime(), rs.getString(9)),
+ versionNo == null ? new Object[] { id } : new Object[] { id, versionNo });
+ if (rows.isEmpty()) throw new ServiceException(title + "报表或版本不存在");
+ return rows.get(0);
+ }
+
+ /** 历史摘要只来自正式版本,不返回测量快照。 */
+ public List<History> history(long id)
+ {
+ return jdbc.query(sql("select version_no,operator_name,created_at_utc,change_summary from t_compare_report_version where report_id=? order by version_no desc"),
+ (rs, index) -> new History(rs.getInt(1), rs.getString(2), rs.getTimestamp(3).getTime(), rs.getString(4)), id);
+ }
+
+ /** 业务事务内锁定报表,版本号分配与旧基准拒绝共用此锁。 */
+ public void lock(long id)
+ {
+ if (jdbc.queryForList(sql("select report_id from t_compare_report where report_id=? for update"), Long.class, id).isEmpty())
+ throw new ServiceException(title + "报表不存在");
+ }
+
+ /** 仅在业务事务和报表锁内调用;物理清理全部版本,同步保留最小操作日志。 */
+ public void delete(long id, long operatorId, String operatorName)
+ {
+ String name = jdbc.queryForObject(sql("select report_name from t_compare_report where report_id=?"), String.class, id);
+ jdbc.update(sql("delete from t_compare_report_version where report_id=?"), id);
+ jdbc.update(sql("delete from t_compare_report where report_id=?"), id);
+ // 不走异步 @Log,日志失败必须回滚删除;只保留身份,不读取或记录测量快照。
+ jdbc.update("insert into sys_oper_log(title,business_type,method,request_method,operator_type,oper_name,oper_url,oper_param,status,oper_time) values(?,3,?,'DELETE',1,?,?,?,0,?)",
+ title + "报表删除", type + "-report.delete", operatorName, "/system/" + type + "-report/" + id,
+ JSON.toJSONString(Map.of("reportId", id, "reportName", name, "operatorId", operatorId)), new Timestamp(System.currentTimeMillis()));
+ }
+
+ /** 只追加版本并更新列表名称;业务事务保证两步同时成功。 */
+ public void append(long id, int versionNo, String name, String snapshot, long operatorId, String operatorName, String summary)
+ {
+ jdbc.update(sql("insert into t_compare_report_version(report_id,version_no,snapshot_json,operator_id,operator_name,created_at_utc,change_summary) values(?,?,?,?,?,?,?)"),
+ id, versionNo, snapshot, operatorId, operatorName, new Timestamp(System.currentTimeMillis()), summary);
+ jdbc.update(sql("update t_compare_report set report_name=? where report_id=?"), name, id);
+ }
+
+ /** 由业务事务调用,身份与首个版本任一写入失败必须一起回滚。 */
+ public long insert(String name, String snapshot, long operatorId, String operatorName, long createdAt)
+ {
+ var keys = new GeneratedKeyHolder();
+ jdbc.update(connection -> {
+ var statement = connection.prepareStatement(sql("insert into t_compare_report(report_name,creator_id,creator_name,created_at_utc) values(?,?,?,?)"), java.sql.Statement.RETURN_GENERATED_KEYS);
+ statement.setString(1, name); statement.setLong(2, operatorId); statement.setString(3, operatorName);
+ statement.setTimestamp(4, new Timestamp(createdAt));
+ return statement;
+ }, keys);
+ long id = keys.getKey().longValue();
+ jdbc.update(sql("insert into t_compare_report_version(report_id,version_no,snapshot_json,operator_id,operator_name,created_at_utc,change_summary) values(?,1,?,?,?,?,?)"),
+ id, snapshot, operatorId, operatorName, new Timestamp(createdAt), "首次保存" + title + "报表");
+ return id;
+ }
+}
diff --git a/battery-business/src/main/resources/mapper/battery/TBatteryGroupFieldMapper.xml b/battery-business/src/main/resources/mapper/battery/TBatteryGroupFieldMapper.xml
index 56007ea..5eebafc 100644
--- a/battery-business/src/main/resources/mapper/battery/TBatteryGroupFieldMapper.xml
+++ b/battery-business/src/main/resources/mapper/battery/TBatteryGroupFieldMapper.xml
@@ -104,5 +104,7 @@
where battery_group_field_id = #{fieldId} and del_flag = '0')
+ (select count(1) from t_compare_report_field_template
where template_type = 'compare' and JSON_CONTAINS(fields_json, JSON_OBJECT('fieldDefinitionId', #{fieldId})))
+ + (select count(1) from t_formation_report_field_template
+ where template_type = 'formation' and JSON_CONTAINS(fields_json, JSON_OBJECT('fieldDefinitionId', #{fieldId})))
</select>
</mapper>
diff --git a/ruoyi-admin/src/main/java/com/battery/business/web/FormationFieldTemplateController.java b/ruoyi-admin/src/main/java/com/battery/business/web/FormationFieldTemplateController.java
new file mode 100644
index 0000000..4302ae2
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/battery/business/web/FormationFieldTemplateController.java
@@ -0,0 +1,31 @@
+package com.battery.business.web;
+
+import com.battery.business.report.FormationFieldTemplateService;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/** 化成模板沿用化成动作权限,不读取或修改对比模板。 */
+@RestController
+@RequestMapping("/system/formation-report/templates")
+public class FormationFieldTemplateController extends BaseController
+{
+ private final FormationFieldTemplateService service;
+ public FormationFieldTemplateController(FormationFieldTemplateService service) { this.service = service; }
+
+ /** 查看、新增或编辑报表均可读取模板。 */
+ @GetMapping @PreAuthorize("@ss.hasAnyPermi('system:formation-report:query,system:formation-report:add,system:formation-report:edit')")
+ public AjaxResult list() { return success(service.list()); }
+
+ /** 新增模板仅接受结构,拒绝值和来源身份。 */
+ @PostMapping @PreAuthorize("@ss.hasPermi('system:formation-report:add')")
+ public AjaxResult create(@Valid @RequestBody FormationFieldTemplateService.Request request)
+ { return success(service.create(request, getUserId(), getUsername())); }
+
+ /** 显式更新所属类型模板,已有报表不随之变化。 */
+ @PutMapping("/{id}") @PreAuthorize("@ss.hasPermi('system:formation-report:edit')")
+ public AjaxResult update(@PathVariable long id, @Valid @RequestBody FormationFieldTemplateService.Request request)
+ { service.update(id, request); return success(); }
+}
diff --git a/ruoyi-admin/src/main/java/com/battery/business/web/FormationReportController.java b/ruoyi-admin/src/main/java/com/battery/business/web/FormationReportController.java
new file mode 100644
index 0000000..9617a90
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/battery/business/web/FormationReportController.java
@@ -0,0 +1,69 @@
+package com.battery.business.web;
+
+import java.util.List;
+import com.battery.business.report.FormationReportRepository;
+import com.battery.business.report.FormationReportService;
+import com.battery.business.report.FormationReportRequest;
+import com.battery.business.report.CompareReportEditRequest;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/** 化成报表按所属动作授权,授权用户可以跨创建人协作。 */
+@RestController
+@RequestMapping("/system/formation-report")
+public class FormationReportController extends BaseController
+{
+ private final FormationReportService service;
+ private final FormationReportRepository reports;
+ public FormationReportController(FormationReportService service, FormationReportRepository reports)
+ { this.service = service; this.reports = reports; }
+
+ /** 查询真实化成来源,不额外要求测试管理权限。 */
+ @GetMapping("/sources") @PreAuthorize("@ss.hasPermi('system:formation-report:add')")
+ public AjaxResult sources() { return success(service.sources()); }
+
+ /** 后端校验归属并读取当前循环及业务字段。 */
+ @PostMapping("/preview") @PreAuthorize("@ss.hasPermi('system:formation-report:add')")
+ public AjaxResult preview(@RequestBody List<FormationReportRequest.Selection> selections) { return success(service.preview(selections)); }
+
+ /** 查询独立报表摘要。 */
+ @GetMapping("/list") @PreAuthorize("@ss.hasPermi('system:formation-report:query')")
+ public AjaxResult list() { return success().put("rows", service.list()); }
+
+ /** 创建身份及首个完整版本。 */
+ @PostMapping @PreAuthorize("@ss.hasPermi('system:formation-report:add')")
+ public AjaxResult create(@Valid @RequestBody FormationReportRequest request) { return success(service.create(request, getUserId(), getUsername())); }
+
+ /** 重开最新保存版本,不读取来源测试。 */
+ @GetMapping("/{id}") @PreAuthorize("@ss.hasPermi('system:formation-report:query')")
+ public AjaxResult get(@PathVariable long id) { return success(service.get(id, null)); }
+
+ /** 编辑权限独立读取最新基准。 */
+ @GetMapping("/{id}/editing") @PreAuthorize("@ss.hasPermi('system:formation-report:edit')")
+ public AjaxResult editing(@PathVariable long id) { return success(service.get(id, null)); }
+
+ /** 查看历史版本的操作人、时间和说明。 */
+ @GetMapping("/{id}/versions") @PreAuthorize("@ss.hasPermi('system:formation-report:query')")
+ public AjaxResult history(@PathVariable long id) { return success(reports.history(id)); }
+
+ /** 指定历史版本只读,不提供回滚。 */
+ @GetMapping("/{id}/versions/{versionNo}") @PreAuthorize("@ss.hasPermi('system:formation-report:query')")
+ public AjaxResult version(@PathVariable long id, @PathVariable int versionNo) { return success(service.get(id, versionNo)); }
+
+ /** 普通编辑不接受来源或测量快照。 */
+ @PostMapping("/{id}/versions") @PreAuthorize("@ss.hasPermi('system:formation-report:edit')")
+ public AjaxResult edit(@PathVariable long id, @Valid @RequestBody FormationReportRequest.Edit request)
+ { return success(service.edit(id, request, getUserId(), getUsername())); }
+
+ /** 明确按当前来源生成新版本。 */
+ @PostMapping("/{id}/refresh-source") @PreAuthorize("@ss.hasPermi('system:formation-report:edit')")
+ public AjaxResult refresh(@PathVariable long id, @Valid @RequestBody CompareReportEditRequest.Refresh request)
+ { return success(service.refresh(id, request, getUserId(), getUsername())); }
+
+ /** 整体删除及最小日志同成同败。 */
+ @DeleteMapping("/{id}") @PreAuthorize("@ss.hasPermi('system:formation-report:remove')")
+ public AjaxResult delete(@PathVariable long id) { service.delete(id, getUserId(), getUsername()); return success(); }
+}
diff --git a/ruoyi-admin/src/main/resources/db/migration-final/V15__formation_reports_and_templates.sql b/ruoyi-admin/src/main/resources/db/migration-final/V15__formation_reports_and_templates.sql
new file mode 100644
index 0000000..8fd654e
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/db/migration-final/V15__formation_reports_and_templates.sql
@@ -0,0 +1,18 @@
+-- 化成报表与来源测试独立;保存和整体删除复用对比版本规则。
+CREATE TABLE t_formation_report LIKE t_compare_report;
+ALTER TABLE t_formation_report COMMENT='化成报表身份';
+CREATE TABLE t_formation_report_version LIKE t_compare_report_version;
+ALTER TABLE t_formation_report_version COMMENT='化成报表独立版本';
+CREATE TABLE t_formation_report_field_template LIKE t_compare_report_field_template;
+ALTER TABLE t_formation_report_field_template
+ ALTER COLUMN template_type SET DEFAULT 'formation', COMMENT='化成报表字段模板';
+
+SET @analysis_menu_id = (SELECT menu_id FROM sys_menu WHERE path='data-analysis' AND parent_id=0 LIMIT 1);
+INSERT INTO sys_menu(menu_name,parent_id,order_num,path,menu_type,visible,status,perms,create_by,create_time)
+VALUES ('化成报表',@analysis_menu_id,2,'report-formation','C','0','0','','system',UTC_TIMESTAMP(3));
+SET @formation_menu_id = LAST_INSERT_ID();
+INSERT INTO sys_menu(menu_name,parent_id,order_num,path,menu_type,visible,status,perms,create_by,create_time)
+VALUES ('查看',@formation_menu_id,1,'#','F','0','0','system:formation-report:query','system',UTC_TIMESTAMP(3)),
+ ('新增',@formation_menu_id,2,'#','F','0','0','system:formation-report:add','system',UTC_TIMESTAMP(3)),
+ ('编辑',@formation_menu_id,3,'#','F','0','0','system:formation-report:edit','system',UTC_TIMESTAMP(3)),
+ ('删除',@formation_menu_id,4,'#','F','0','0','system:formation-report:remove','system',UTC_TIMESTAMP(3));
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml
index 3ab9300..7849c6e 100644
--- a/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml
@@ -74,7 +74,7 @@
#{operId}
</foreach>
<!-- 报表删除只保留这一份最小追溯记录,平台日志清理也不能移除。 -->
- and (method is null or method <> 'compare-report.delete')
+ and (method is null or method not in ('compare-report.delete', 'formation-report.delete'))
</delete>
<select id="selectOperLogById" parameterType="Long" resultMap="SysOperLogResult">
@@ -83,7 +83,7 @@
</select>
<update id="cleanOperLog">
- delete from sys_oper_log where method is null or method <> 'compare-report.delete'
+ delete from sys_oper_log where method is null or method not in ('compare-report.delete', 'formation-report.delete')
</update>
</mapper>
--
Gitblit v1.7.1