From d1cb58bebb89c000275d9b092dd644c809296707 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期二, 01 九月 2026 15:52:56 +0800
Subject: [PATCH] 修复未开始测试继承设备历史

---
 ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsDevelopmentSimulator.java                    |   34 ++++++++++------
 ruoyi-system/src/main/java/com/ruoyi/system/domain/TTestLogicStepMapping.java                   |    6 +-
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TTestServiceImpl.java                  |    2 
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TTestLiveServiceImpl.java              |    6 ++-
 ruoyi-system/src/main/java/com/ruoyi/system/service/ITestLiveService.java                       |    2 
 ruoyi-device/src/test/java/com/ruoyi/device/bts/BtsDevelopmentSimulatorRecordStepCycleTest.java |   19 +++++++++
 6 files changed, 49 insertions(+), 20 deletions(-)

diff --git a/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsDevelopmentSimulator.java b/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsDevelopmentSimulator.java
index cded5ba..140acd0 100644
--- a/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsDevelopmentSimulator.java
+++ b/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
diff --git a/ruoyi-device/src/test/java/com/ruoyi/device/bts/BtsDevelopmentSimulatorRecordStepCycleTest.java b/ruoyi-device/src/test/java/com/ruoyi/device/bts/BtsDevelopmentSimulatorRecordStepCycleTest.java
index f4ba1ac..be948f4 100644
--- a/ruoyi-device/src/test/java/com/ruoyi/device/bts/BtsDevelopmentSimulatorRecordStepCycleTest.java
+++ b/ruoyi-device/src/test/java/com/ruoyi/device/bts/BtsDevelopmentSimulatorRecordStepCycleTest.java
@@ -65,7 +65,10 @@
                     .map(BtsExcelFixture.FixtureRecord::stepNo).distinct().toList();
             var actualSteps = snapshot.rows().stream().filter(row -> "STEP".equals(row.rowType()))
                     .map(BtsDevelopmentSimulator.BtsTestPlanRow::stepNo).toList();
+            var cyclePositions = snapshot.rows().stream().filter(row -> "STEP".equals(row.rowType()))
+                    .map(BtsDevelopmentSimulator.BtsTestPlanRow::cyclePosition).toList();
             assertEquals(expectedSteps, actualSteps);
+            assertEquals(java.util.stream.IntStream.rangeClosed(1, expectedSteps.size()).boxed().toList(), cyclePositions);
             assertTrue(snapshot.rows().stream().anyMatch(row -> "STEP".equals(row.rowType()) && "进行中".equals(row.executionStatus())));
             assertTrue(snapshot.rows().stream().anyMatch(row -> "STEP".equals(row.rowType()) && "待执行".equals(row.executionStatus())));
             var cycleRow = snapshot.rows().stream().filter(row -> "CYCLE".equals(row.rowType())).findFirst().orElseThrow();
@@ -75,6 +78,22 @@
     }
 
     @Test
+    void forcedFirstCycleKeepsUnstartedTestPlanIndependentOfSharedPlayback() throws Exception
+    {
+        InMemoryBtsSimulatorStateStore store = new InMemoryBtsSimulatorStateStore();
+        try (BtsDevelopmentSimulator simulator = new BtsDevelopmentSimulator(0, store))
+        {
+            simulator.updateChannel(1, 1, true, DeviceWorkStatus.WORKING);
+            simulator.nextBtsCycle(1, 1);
+            simulator.nextBtsCycle(1, 1);
+            var snapshot = simulator.btsTest(1, 1, 1);
+            assertEquals(1, snapshot.currentCycleNo());
+            assertTrue(snapshot.rows().stream().filter(row -> "STEP".equals(row.rowType()))
+                    .allMatch(row -> "待执行".equals(row.executionStatus())));
+        }
+    }
+
+    @Test
     void playbackSpeedPauseResumeAndRestartAreIndependentOfDeviceStatus() throws Exception
     {
         Instant start = Instant.parse("2026-08-31T00:00:00Z");
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/TTestLogicStepMapping.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/TTestLogicStepMapping.java
index 558729e..8042c7d 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/TTestLogicStepMapping.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/TTestLogicStepMapping.java
@@ -4,13 +4,13 @@
 import jakarta.validation.constraints.Min;
 import jakarta.validation.constraints.NotBlank;
 
-/** 当前测试的实际工步到逻辑工步映射;只影响展示和统计,不改写原始检测柜数据。 */
+/** 当前测试循环内工步位置到逻辑工步的映射;只影响展示和统计,不改写原始检测柜数据。 */
 public class TTestLogicStepMapping implements Serializable {
     private static final long serialVersionUID = 1L;
     @Min(value = 1, message = "逻辑工步编号必须大于0") private Integer logicalNo;
     @NotBlank(message = "逻辑工步名称不能为空") private String logicalName;
-    @Min(value = 1, message = "实际工步起始编号必须大于0") private Integer startStepNo;
-    @Min(value = 1, message = "实际工步结束编号必须大于0") private Integer endStepNo;
+    @Min(value = 1, message = "循环内起始位置必须大于0") private Integer startStepNo;
+    @Min(value = 1, message = "循环内结束位置必须大于0") private Integer endStepNo;
     public Integer getLogicalNo() { return logicalNo; }
     public void setLogicalNo(Integer value) { logicalNo = value; }
     public String getLogicalName() { return logicalName; }
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ITestLiveService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ITestLiveService.java
index 61a552b..521901f 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ITestLiveService.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ITestLiveService.java
@@ -29,7 +29,7 @@
     }
     record TestPlanSnapshot(int currentCycleNo, int currentStepNo, int plannedCycleCount,
             List<TestPlanRow> rows) { }
-    record TestPlanRow(String rowType, int stepNo, String stepName, double durationSeconds,
+    record TestPlanRow(String rowType, int stepNo, Integer cyclePosition, String stepName, double durationSeconds,
             Double voltageV, String executionStatus, Integer cycleCount, Long recordCount) { }
     record LiveSource(String sourceType, Long sourceId, String batteryNo, String deviceStatus,
             Map<String, Object> latestSnapshot) { }
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TTestLiveServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TTestLiveServiceImpl.java
index 3a360d1..fab0140 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TTestLiveServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TTestLiveServiceImpl.java
@@ -106,9 +106,11 @@
                     || battery.getBinding().getChlid() == null) continue;
             try
             {
-                var source = simulator.btsTest(battery.getBinding().getDevid(), battery.getBinding().getChlid());
+                var source = "NOT_STARTED".equals(test.getReceptionStatus())
+                        ? simulator.btsTest(battery.getBinding().getDevid(), battery.getBinding().getChlid(), 1)
+                        : simulator.btsTest(battery.getBinding().getDevid(), battery.getBinding().getChlid());
                 List<TestPlanRow> rows = source.rows().stream()
-                        .map(row -> new TestPlanRow(row.rowType(), row.stepNo(), row.stepName(), row.durationSeconds(),
+                        .map(row -> new TestPlanRow(row.rowType(), row.stepNo(), row.cyclePosition(), row.stepName(), row.durationSeconds(),
                                 row.voltageV(), row.executionStatus(), row.cycleCount(), row.recordCount()))
                         .toList();
                 return new TestPlanSnapshot(source.currentCycleNo(), source.currentStepNo(),
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TTestServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TTestServiceImpl.java
index 7c2e5f4..649b676 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TTestServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TTestServiceImpl.java
@@ -350,7 +350,7 @@
                     || mapping.getStartStepNo() == null || mapping.getEndStepNo() == null
                     || mapping.getStartStepNo() < 1 || mapping.getEndStepNo() < mapping.getStartStepNo()
                     || mapping.getStartStepNo() <= previousEnd || StringUtils.isEmpty(trimToNull(mapping.getLogicalName())))
-                throw new ServiceException("逻辑工步必须按连续实际工步升序且互不重叠");
+                throw new ServiceException("逻辑工步必须按循环内连续位置升序且互不重叠");
             mapping.setLogicalName(trimToNull(mapping.getLogicalName()));
             previousEnd = mapping.getEndStepNo();
             expectedNo++;

--
Gitblit v1.7.1