yeg
2026-09-08 497e08d7d183e511dfc752486353ba957f3e9a2c
battery-business/src/main/java/com/battery/business/report/FormationReportService.java
@@ -115,6 +115,7 @@
        settings = stepSettings(settings, matrix.items(), false);
        validateValues(settings, matrix.items()); validateChargeSelections(settings, matrix.items());
        var snapshot = new LinkedHashMap<String, Object>();
        snapshot.put("curveMode", "actual-steps");
        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", "%"));
@@ -161,8 +162,12 @@
        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(validateFields(settings.fields(), false), values,
                settings.chargeStepSelections() == null ? Map.of() : settings.chargeStepSelections(), settings.metrics(), settings.seriesColors(), settings.manualChargeSteps(), settings.curveStepSelections(), settings.hiddenSeriesKeys(), settings.activeCycle(), settings.sourceDetailOverrides()));
        var refreshedSettings = new Settings(validateFields(settings.fields(), false), values,
                settings.chargeStepSelections() == null ? Map.of() : settings.chargeStepSelections(), settings.metrics(), settings.seriesColors(),
                settings.manualChargeSteps(), settings.curveStepSelections(), settings.hiddenSeriesKeys(), settings.activeCycle(), settings.sourceDetailOverrides());
        snapshot.put("curveMode", "actual-steps");
        snapshot.put("items", matrix.items()); snapshot.put("cycleNos", matrix.cycleNos());
        snapshot.put("settings", stepSettings(refreshedSettings, matrix.items(), true));
        curveUnits(snapshot);
        reports.append(id, request.baseVersionNo() + 1, snapshot.getString("name"), json(snapshot), userId, userName, "按最新来源生成:" + request.changeSummary().trim());
        return get(id, null);
@@ -317,7 +322,8 @@
            if (refresh) choice = new FormationReportRequest.StepSelection(choice.charge().stream().filter(chargeAllowed::contains).toList(), choice.discharge().stream().filter(dischargeAllowed::contains).toList());
            if (choice.charge() == null || choice.discharge() == null || !chargeAllowed.containsAll(choice.charge()) || !dischargeAllowed.containsAll(choice.discharge())
                    || new HashSet<>(choice.charge()).size() != choice.charge().size() || new HashSet<>(choice.discharge()).size() != choice.discharge().size()) throw new ServiceException("曲线工步不属于当前测试电池、循环或充放电分组");
            if (choice.charge().stream().anyMatch(choice.discharge()::contains)) throw new ServiceException("同一实际工步不能同时指定为充电和放电");
            if (choice.charge().stream().anyMatch(choice.discharge()::contains) || requested.stream().anyMatch(choice.discharge()::contains))
                throw new ServiceException("同一实际工步不能同时指定为充电和放电");
            selected.put(key, choice);
        }
        if (!refresh && ((settings.manualChargeSteps() != null && !allowed.containsAll(settings.manualChargeSteps().keySet()))
@@ -343,13 +349,9 @@
            // 早期版本未冻结工步时保持缺失,普通编辑不能重新读取设备事实。
            List<ChargeStep> choices = item.chargeSteps() == null ? List.of() : 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("所选充电工步不属于当前测试、电池和循环");
            }
            // 旧版保留冻结的单选事实,不用新增协议识别规则改判历史选择。
            if (selected != null && choices.stream().noneMatch(step -> step.stepNo() == selected))
                throw new ServiceException("所选充电工步不属于当前测试、电池和循环");
        }
    }
@@ -371,7 +373,6 @@
        }
        return result;
    }
    private boolean isReliableCharge(String type) { return directions.resolve(type) == Direction.CHARGE; }
    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; }