From b75608fc100a5d77d9e68d8650fd846ddfd83dc5 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期一, 07 九月 2026 14:01:57 +0800
Subject: [PATCH] 实现化成充电工步终止电压

---
 battery-business/src/main/java/com/battery/business/report/FormationReportRequest.java |    3 ++-
 battery-business/src/main/java/com/battery/business/report/FormationReportService.java |   50 +++++++++++++++++++++++++++++++++++++++++++-------
 2 files changed, 45 insertions(+), 8 deletions(-)

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
index ba4b875..832badf 100644
--- a/battery-business/src/main/java/com/battery/business/report/FormationReportRequest.java
+++ b/battery-business/src/main/java/com/battery/business/report/FormationReportRequest.java
@@ -18,7 +18,8 @@
 
     /** 值按测试、电池、循环、字段归属,只属于报表展示,不反写测量快照。 */
     public record Settings(@NotNull @Size(min = 1, max = 100) List<@NotNull @Valid FormationField> fields,
-            @NotNull Map<String, @NotNull String> values)
+            @NotNull Map<String, @NotNull String> values,
+            @NotNull Map<String, @NotNull Integer> chargeStepSelections)
     {
         /** 展示设置拒绝来源和测量快照。 */
         @JsonAnySetter
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
index 791c8c5..9713bb1 100644
--- a/battery-business/src/main/java/com/battery/business/report/FormationReportService.java
+++ b/battery-business/src/main/java/com/battery/business/report/FormationReportService.java
@@ -41,8 +41,10 @@
     { 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 ChargeStep(int stepNo, String name, String stepType, BigDecimal endVoltage) { }
     public record Item(Candidate source, List<Integer> cycleNos, List<Map<String, Object>> cycles,
-            Map<String, Map<String, BigDecimal>> metrics, List<CurrentBatteryField> businessFields) { }
+            Map<String, Map<String, BigDecimal>> metrics, List<CurrentBatteryField> businessFields,
+            Map<Integer, List<ChargeStep>> chargeSteps) { }
     public record Matrix(List<Item> items, List<Integer> cycleNos) { }
     public record Summary(long id, String name, String creator, long createdAt, String testName, String batteryGroup) { }
 
@@ -90,7 +92,7 @@
             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()));
+            items.add(new Item(candidate(test, battery), actual, rows, metrics(rows, actual), battery.getFields(), chargeSteps(steps.get(selection.testId()), battery, actual)));
         }
         return new Matrix(items, items.stream().flatMap(item -> item.cycleNos().stream()).distinct().sorted().toList());
     }
@@ -99,9 +101,10 @@
     @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());
+        Settings settings = new Settings(validateFields(request.settings().fields(), true), request.settings().values(),
+                request.settings().chargeStepSelections() == null ? Map.of() : request.settings().chargeStepSelections());
         Matrix matrix = freeze(preview(request.selections()), settings.fields());
-        validateValues(settings, matrix.items());
+        validateValues(settings, matrix.items()); validateChargeSelections(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);
@@ -128,7 +131,7 @@
     {
         var snapshot = current(id, request.baseVersionNo());
         validateFields(request.settings().fields(), false);
-        validateValues(request.settings(), items(snapshot));
+        validateValues(request.settings(), items(snapshot)); validateChargeSelections(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);
@@ -144,7 +147,8 @@
         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));
+        snapshot.put("items", matrix.items()); snapshot.put("cycleNos", matrix.cycleNos()); snapshot.put("settings", new Settings(settings.fields(), values,
+                settings.chargeStepSelections() == null ? Map.of() : settings.chargeStepSelections()));
         reports.append(id, request.baseVersionNo() + 1, snapshot.getString("name"), json(snapshot), userId, userName, "按最新来源生成:" + request.changeSummary().trim());
         return get(id, null);
     }
@@ -198,7 +202,7 @@
             }).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());
+                            ? field.key().equals(value.getFieldName()) : field.fieldDefinitionId().equals(value.getFieldDefinitionId())))).toList(), item.chargeSteps());
         }).toList();
         return new Matrix(frozen, matrix.cycleNos());
     }
@@ -210,6 +214,38 @@
             throw new ServiceException("人工值不属于当前字段、测试电池或实际循环");
     }
 
+    private void validateChargeSelections(Settings settings, List<Item> items)
+    {
+        Map<String, Integer> selections = settings.chargeStepSelections() == null ? Map.of() : settings.chargeStepSelections();
+        for (Item item : items) for (int cycle : item.cycleNos()) {
+            String key = item.source().selection().testId() + "|" + item.source().selection().testBatteryId() + "|" + cycle;
+            List<ChargeStep> choices = item.chargeSteps().getOrDefault(cycle, List.of());
+            Integer selected = selections.get(key);
+            if (choices.stream().filter(step -> isReliableCharge(step.stepType())).count() == 1) {
+                int expected = choices.stream().filter(step -> isReliableCharge(step.stepType())).findFirst().get().stepNo();
+                if (selected != null && selected != expected) throw new ServiceException("充电工步选择与实际工步不一致");
+            } else if (!choices.isEmpty()) {
+                if (selected == null) throw new ServiceException("请为无法自动识别的充电工步完成选择");
+                if (choices.stream().noneMatch(step -> step.stepNo() == selected)) throw new ServiceException("所选充电工步不属于当前测试、电池和循环");
+            }
+        }
+    }
+
+    private Map<Integer, List<ChargeStep>> chargeSteps(List<Map<String, Object>> rows, TTestBattery battery, List<Integer> cycles)
+    {
+        Map<Integer, List<ChargeStep>> result = new LinkedHashMap<>();
+        for (int cycle : cycles) result.put(cycle, rows == null ? List.of() : rows.stream()
+                .filter(row -> Objects.equals(battery.getBatteryNo(), row.get("batteryNo")) && cycleNo(row) == cycle)
+                .map(row -> new ChargeStep(number(row.get("stepNo")), String.valueOf(row.getOrDefault("stepName", "")),
+                        String.valueOf(row.getOrDefault("stepType", "")), decimal(row.get("endVoltage"))))
+                // 无法可靠识别类型时保留该循环的全部实际工步,由操作员选择,不凭名称或电流猜测。
+                .filter(step -> step.stepNo() > 0).toList());
+        return result;
+    }
+    private boolean isReliableCharge(String type) { String value = type == null ? "" : type.trim().toLowerCase(); return value.equals("cc") || value.equals("charge") || value.equals("charging"); }
+    private int number(Object value) { return value instanceof Number n ? n.intValue() : 0; }
+    private BigDecimal decimal(Object value) { return value instanceof Number n ? new BigDecimal(n.toString()) : null; }
+
     private Set<String> valueKeys(List<FormationField> fields, List<Item> items)
     {
         Set<String> keys = new HashSet<>();

--
Gitblit v1.7.1