yeg
2026-09-01 d1cb58bebb89c000275d9b092dd644c809296707
ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsDevelopmentSimulator.java
@@ -403,7 +403,10 @@
        for (BtsHistoryRecord item : btsRecords(deviceId, channelId))
        {
            String key = item.cycleNo() + ":" + item.stepNo();
            grouped.computeIfAbsent(key, ignored -> new BtsStepAccumulator(item)).add(item);
            int position = BtsExcelFixture.records().stream()
                    .filter(record -> record.cycleNo() == item.cycleNo())
                    .map(BtsExcelFixture.FixtureRecord::stepNo).distinct().toList().indexOf(item.stepNo()) + 1;
            grouped.computeIfAbsent(key, ignored -> new BtsStepAccumulator(item, position)).add(item);
        }
        return grouped.values().stream().map(BtsStepAccumulator::finish).toList();
    }
@@ -430,11 +433,15 @@
    /** 返回检测柜测试方案事实;方案行来自 Excel 夹具,当前状态来自该通道播放状态。 */
    public synchronized BtsTestSnapshot btsTest(int deviceId, int channelId)
    { return btsTest(deviceId, channelId, null); }
    /** 返回指定循环的检测柜测试方案;未开始接收的测试固定使用第一个循环作为核对上下文。 */
    public synchronized BtsTestSnapshot btsTest(int deviceId, int channelId, Integer forcedCycleNo)
    {
        DeviceState device = requireDevice(deviceId);
        requireChannel(device, channelId);
        BtsPlaybackState playback = playbackState(btsKey(device, channelId), Instant.now());
        int requestedCycleNo = playback.cycleNo();
        int requestedCycleNo = forcedCycleNo == null ? playback.cycleNo() : forcedCycleNo;
        int currentCycleNo = requestedCycleNo;
        List<BtsExcelFixture.FixtureRecord> currentCycleRecords = BtsExcelFixture.records().stream()
                .filter(item -> item.cycleNo() == requestedCycleNo).toList();
@@ -450,24 +457,25 @@
            firstByStep.putIfAbsent(item.stepNo(), item);
        List<BtsTestPlanRow> rows = new ArrayList<>();
        int cyclePosition = 1;
        for (BtsExcelFixture.FixtureRecord first : firstByStep.values())
        {
            List<BtsExcelFixture.FixtureRecord> sameCycle = currentCycleRecords.stream()
                    .filter(item -> item.stepNo() == first.stepNo()).toList();
            BtsExcelFixture.FixtureRecord last = sameCycle.get(sameCycle.size() - 1);
            // step 号在每个循环内重复,跨循环时必须按当前循环的工步位置判断,不能把上一循环的同号工步当成当前状态。
            String status = playback.naturallyFinished() ? "已完成"
            String status = forcedCycleNo != null ? "待执行" : playback.naturallyFinished() ? "已完成"
                    : playback.stepNo() > first.stepNo() ? "已完成"
                            : playback.stepNo() == first.stepNo() ? "进行中" : "待执行";
            rows.add(new BtsTestPlanRow("STEP", first.stepNo(), first.stepName(),
            rows.add(new BtsTestPlanRow("STEP", first.stepNo(), cyclePosition++, first.stepName(),
                    Math.max(0d, last.offsetSeconds() - first.offsetSeconds()), first.voltageV(), status,
                    null, null));
        }
        rows.add(new BtsTestPlanRow("CYCLE", BtsExcelFixture.startStepNo(), "循环", 0d, null,
                playback.naturallyFinished() ? "已完成" : "进行中", currentCycleNo,
        rows.add(new BtsTestPlanRow("CYCLE", BtsExcelFixture.startStepNo(), null, "循环", 0d, null,
                forcedCycleNo != null ? "待执行" : playback.naturallyFinished() ? "已完成" : "进行中", currentCycleNo,
                (long) currentCycleRecords.size()));
        rows.add(new BtsTestPlanRow("END", 0, "结束", 0d, null,
                playback.naturallyFinished() ? "已完成" : "待执行", null, (long) BtsExcelFixture.recordCount()));
        rows.add(new BtsTestPlanRow("END", 0, null, "结束", 0d, null,
                forcedCycleNo != null ? "待执行" : playback.naturallyFinished() ? "已完成" : "待执行", null, (long) BtsExcelFixture.recordCount()));
        return new BtsTestSnapshot(currentCycleNo, playback.stepNo(), BtsExcelFixture.cycleCount(), rows);
    }
@@ -891,7 +899,7 @@
        stateStore.saveBtsPlaybackState(key, playback);
    }
    public record BtsStepRecord(int cycleNo, int stepNo, String stepName, String stepType, long recordCount,
    public record BtsStepRecord(int cycleNo, int stepNo, Integer cyclePosition, String stepName, String stepType, long recordCount,
            Instant startedAt, Instant endedAt, double durationSeconds, double capacityAh, double energyWh,
            double startVoltageV, double endVoltageV, double chargePeakVoltageV, double dischargeValleyVoltageV) { }
@@ -899,7 +907,7 @@
            double chargeCapacityAh, double dischargeCapacityAh, double chargeEnergyWh, double dischargeEnergyWh,
            double efficiency) { }
    public record BtsTestPlanRow(String rowType, int stepNo, String stepName, double durationSeconds,
    public record BtsTestPlanRow(String rowType, int stepNo, Integer cyclePosition, String stepName, double durationSeconds,
            Double voltageV, String executionStatus, Integer cycleCount, Long recordCount) { }
    public record BtsTestSnapshot(int currentCycleNo, int currentStepNo, int plannedCycleCount,
@@ -915,12 +923,12 @@
    private static final class BtsStepAccumulator
    {
        private final int cycle; private final int step; private final String name; private final String type;
        private final int cycle; private final int step; private final int position; private final String name; private final String type;
        private long count; private Instant first; private Instant last; private double duration; private double capacity; private double energy;
        private double startVoltage; private double endVoltage; private double chargePeak = Double.NEGATIVE_INFINITY; private double dischargeValley = Double.POSITIVE_INFINITY;
        BtsStepAccumulator(BtsHistoryRecord first) { cycle = first.cycleNo(); step = first.stepNo(); name = first.stepName(); type = first.stepType(); }
        BtsStepAccumulator(BtsHistoryRecord first, int position) { cycle = first.cycleNo(); step = first.stepNo(); this.position = position; name = first.stepName(); type = first.stepType(); }
        void add(BtsHistoryRecord item) { count++; if (first == null) { first = item.deviceTime(); startVoltage = item.voltageV(); } last = item.deviceTime(); endVoltage = item.voltageV(); duration = first == null ? 0 : Math.max(0, java.time.Duration.between(first, last).toMillis() / 1000d); capacity = item.capacityAh(); energy = item.energyWh(); if (item.currentA() >= 0) chargePeak = Math.max(chargePeak, item.voltageV()); else dischargeValley = Math.min(dischargeValley, item.voltageV()); }
        BtsStepRecord finish() { return new BtsStepRecord(cycle, step, name, type, count, first, last, duration, capacity, energy, startVoltage, endVoltage, chargePeak == Double.NEGATIVE_INFINITY ? 0 : chargePeak, dischargeValley == Double.POSITIVE_INFINITY ? 0 : dischargeValley); }
        BtsStepRecord finish() { return new BtsStepRecord(cycle, step, position, name, type, count, first, last, duration, capacity, energy, startVoltage, endVoltage, chargePeak == Double.NEGATIVE_INFINITY ? 0 : chargePeak, dischargeValley == Double.POSITIVE_INFINITY ? 0 : dischargeValley); }
    }
    private static final class BtsCycleAccumulator