From 1eafc84ff7546fcd51d1f2884c77400b75529e3b Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期三, 02 九月 2026 13:57:57 +0800
Subject: [PATCH] 对齐测试控制台当前有效数据

---
 ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TestConsoleDataRevisionQueryServiceTest.java |   10 +-
 ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TestDataQueryServiceEffectiveDataTest.java   |   83 ++++++++++++++++++++
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TestDataQueryServiceImpl.java               |   62 ++++++++++++++
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TestConsoleDataServiceImpl.java             |   41 +---------
 4 files changed, 150 insertions(+), 46 deletions(-)

diff --git a/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TestConsoleDataRevisionQueryServiceTest.java b/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TestConsoleDataRevisionQueryServiceTest.java
index c884534..74bb5a5 100644
--- a/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TestConsoleDataRevisionQueryServiceTest.java
+++ b/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TestConsoleDataRevisionQueryServiceTest.java
@@ -97,7 +97,7 @@
     }
 
     @Test
-    void physicallyDeletesSamplesBehindStepAggregate()
+    void keepsOriginalSamplesWhenDeletingStepAggregate()
     {
         TTestDataRevisionAction action = new TTestDataRevisionAction();
         action.setTestId(26L); action.setDataType("STEP"); action.setOperationType("DELETE");
@@ -105,12 +105,10 @@
         action.setReason("异常工步"); action.setOperatorName("tester");
         TTestDataOverride override = new TTestDataOverride();
         override.setStableIdentity("step:1:2:3:4"); override.setDeleted(true);
-        when(mapper.selectBtsSampleIdsByDeviceChannelStep(26L, 1L, 2L, 3, 4))
-                .thenReturn(List.of(101L, 102L));
-
         service.submitAction(action, List.of(override), List.of(), List.of());
 
-        verify(mapper).deleteBtsMeasurementsBySampleIds(List.of(101L, 102L));
-        verify(mapper).deleteBtsSamplesByIds(26L, List.of(101L, 102L));
+        verify(mapper).insertDataRevisionAction(action);
+        verify(mapper).upsertDataOverride(override);
+        org.mockito.Mockito.verifyNoMoreInteractions(mapper);
     }
 }
diff --git a/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TestDataQueryServiceEffectiveDataTest.java b/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TestDataQueryServiceEffectiveDataTest.java
new file mode 100644
index 0000000..8099bc6
--- /dev/null
+++ b/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TestDataQueryServiceEffectiveDataTest.java
@@ -0,0 +1,83 @@
+package com.ruoyi.system.service.impl;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.when;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import com.ruoyi.system.domain.TTest;
+import com.ruoyi.system.domain.TTestDataOverride;
+import com.ruoyi.system.mapper.TTestMapper;
+import com.ruoyi.system.service.ITTestService;
+
+/** Ticket01:三类查询直接返回数据库计算的当前有效数据,删除不抹除原始事实。 */
+@ExtendWith(MockitoExtension.class)
+class TestDataQueryServiceEffectiveDataTest
+{
+    @Mock
+    private TTestMapper mapper;
+    @Mock
+    private ITTestService tests;
+
+    @Test
+    void appliesRealtimeEditAndFiltersDeletedRow()
+    {
+        when(tests.selectTestById(7L)).thenReturn(new TTest());
+        Map<String, Object> edited = row("deviceId", 22L, "channelId", 8L, "sequenceId", 3L,
+                "voltage", 3.7d);
+        Map<String, Object> deleted = row("deviceId", 22L, "channelId", 8L, "sequenceId", 4L,
+                "voltage", 3.8d);
+        TTestDataOverride edit = override("22:8:3", "{\"voltage\":\"4.1000\"}", false);
+        TTestDataOverride remove = override("22:8:4", null, true);
+        when(mapper.selectPersistedRecords(7L)).thenReturn(List.of(edited, deleted));
+        when(mapper.selectDataOverrides(7L, "REALTIME")).thenReturn(List.of(edit, remove));
+
+        List<Map<String, Object>> result = new TestDataQueryServiceImpl(mapper, tests).records(7L);
+
+        assertEquals(1, result.size());
+        assertEquals("4.1000", result.get(0).get("voltage"));
+        assertEquals(3L, result.get(0).get("sequenceId"));
+    }
+
+    @Test
+    void usesStepAndCycleStableIdentities()
+    {
+        when(tests.selectTestById(8L)).thenReturn(new TTest());
+        Map<String, Object> step = row("deviceId", 22L, "channelId", 8L, "cycleNo", 2, "stepNo", 4,
+                "capacity", 1d);
+        Map<String, Object> cycle = row("deviceId", 22L, "channelId", 8L, "cycleNo", 2,
+                "efficiency", 90d);
+        when(mapper.selectPersistedSteps(8L)).thenReturn(List.of(step));
+        when(mapper.selectPersistedCycles(8L)).thenReturn(List.of(cycle));
+        when(mapper.selectDataOverrides(8L, "STEP")).thenReturn(List.of(override("step:22:8:2:4",
+                "{\"capacity\":\"2.0000\"}", false)));
+        when(mapper.selectDataOverrides(8L, "CYCLE")).thenReturn(List.of(override("cycle:22:8:2",
+                "{\"efficiency\":\"95.00\"}", false)));
+        TestDataQueryServiceImpl service = new TestDataQueryServiceImpl(mapper, tests);
+
+        assertEquals("2.0000", service.steps(8L).get(0).get("capacity"));
+        assertEquals("95.00", service.cycles(8L).get(0).get("efficiency"));
+        assertTrue(service.cycles(8L).get(0).containsKey("cycleNo"));
+    }
+
+    private static TTestDataOverride override(String identity, String values, boolean deleted)
+    {
+        TTestDataOverride result = new TTestDataOverride();
+        result.setStableIdentity(identity);
+        result.setFieldValues(values);
+        result.setDeleted(deleted);
+        return result;
+    }
+
+    private static Map<String, Object> row(Object... values)
+    {
+        Map<String, Object> result = new HashMap<>();
+        for (int index = 0; index < values.length; index += 2) result.put(String.valueOf(values[index]), values[index + 1]);
+        return result;
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TestConsoleDataServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TestConsoleDataServiceImpl.java
index 4655e0d..d82d7e4 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TestConsoleDataServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TestConsoleDataServiceImpl.java
@@ -4,7 +4,6 @@
 import java.util.List;
 import java.util.Set;
 import java.util.Map;
-import java.util.ArrayList;
 import java.util.HashSet;
 import com.alibaba.fastjson2.JSON;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -114,42 +113,8 @@
                 mapper.upsertDataOverride(item);
             }
         }
-        if ("DELETE".equals(action.getOperationType()) && overrides != null)
-        {
-            List<Long> resolvedIds = new ArrayList<>();
-            for (TTestDataOverride item : overrides)
-            {
-                Long[] identity = parseDeleteIdentity(item.getStableIdentity(), action.getDataType());
-                if (identity == null) throw new ServiceException("删除数据稳定身份无效");
-                switch (action.getDataType())
-                {
-                    case "REALTIME" -> {
-                        Long sampleId = mapper.selectBtsSampleIdByDeviceChannelSequence(action.getTestId(),
-                                identity[0], identity[1], identity[2]);
-                        if (sampleId != null) resolvedIds.add(sampleId);
-                    }
-                    case "STEP" -> resolvedIds.addAll(mapper.selectBtsSampleIdsByDeviceChannelStep(action.getTestId(),
-                            identity[0], identity[1], identity[2].intValue(), identity[3].intValue()));
-                    case "CYCLE" -> resolvedIds.addAll(mapper.selectBtsSampleIdsByDeviceChannelCycle(action.getTestId(),
-                            identity[0], identity[1], identity[2].intValue()));
-                    default -> throw new ServiceException("控制台数据类型无效");
-                }
-            }
-            if (!resolvedIds.isEmpty())
-            {
-                resolvedIds = new ArrayList<>(new HashSet<>(resolvedIds));
-                // 工步和循环是采样聚合视图,物理删除需在同一事务内清理对应底层采样。
-                mapper.deleteBtsMeasurementsBySampleIds(resolvedIds);
-                mapper.deleteBtsSamplesByIds(action.getTestId(), resolvedIds);
-            }
-        }
-        if (btsSampleIds != null && !btsSampleIds.isEmpty())
-        {
-            // 采样明细受外键保护,必须在主采样物理删除前清理。
-            mapper.deleteBtsMeasurementsBySampleIds(btsSampleIds);
-            mapper.deleteBtsSamplesByIds(action.getTestId(), btsSampleIds);
-        }
-        if (collectorSampleIds != null && !collectorSampleIds.isEmpty()) mapper.deleteCollectorSamplesByIds(action.getTestId(), collectorSampleIds);
+        // 删除仅写入 deleted 覆盖,保留设备原始采样和采集器事实供追溯。
+        // btsSampleIds/collectorSampleIds 保留在接口中兼容旧调用方,但不再执行物理删除。
         return action.getActionId();
     }
 
@@ -196,6 +161,8 @@
 
     private void validateOverride(TTestDataOverride item, String operation, String dataType)
     {
+        if ("DELETE".equals(operation) && parseDeleteIdentity(item.getStableIdentity(), dataType) == null)
+            throw new ServiceException("删除数据稳定身份无效");
         if (!"EDIT".equals(operation) || item.getFieldValues() == null || item.getFieldValues().isBlank()) return;
         try
         {
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TestDataQueryServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TestDataQueryServiceImpl.java
index 10226fb..c0184c6 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TestDataQueryServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TestDataQueryServiceImpl.java
@@ -1,9 +1,13 @@
 package com.ruoyi.system.service.impl;
 
 import java.util.List;
+import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.HashMap;
+import com.alibaba.fastjson2.JSON;
 import org.springframework.stereotype.Service;
 import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.system.domain.TTestDataOverride;
 import com.ruoyi.system.mapper.TTestMapper;
 import com.ruoyi.system.service.ITTestService;
 import com.ruoyi.system.service.ITestDataQueryService;
@@ -24,19 +28,71 @@
     @Override public List<Map<String, Object>> records(Long testId)
     {
         requireTest(testId);
-        return mapper.selectPersistedRecords(testId);
+        return applyEffectiveData(testId, "REALTIME", mapper.selectPersistedRecords(testId));
     }
 
     @Override public List<Map<String, Object>> steps(Long testId)
     {
         requireTest(testId);
-        return mapper.selectPersistedSteps(testId);
+        return applyEffectiveData(testId, "STEP", mapper.selectPersistedSteps(testId));
     }
 
     @Override public List<Map<String, Object>> cycles(Long testId)
     {
         requireTest(testId);
-        return mapper.selectPersistedCycles(testId);
+        return applyEffectiveData(testId, "CYCLE", mapper.selectPersistedCycles(testId));
+    }
+
+    /** 将最新人工覆盖应用到设备事实,删除只影响当前有效视图,不抹除原始采样。 */
+    private List<Map<String, Object>> applyEffectiveData(Long testId, String dataType,
+            List<Map<String, Object>> source)
+    {
+        List<TTestDataOverride> overrides = mapper.selectDataOverrides(testId, dataType);
+        if (source == null || source.isEmpty() || overrides == null || overrides.isEmpty())
+            return source == null ? List.of() : source;
+        Map<String, TTestDataOverride> byIdentity = new HashMap<>();
+        for (TTestDataOverride override : overrides)
+            if (override != null && override.getStableIdentity() != null)
+                byIdentity.put(override.getStableIdentity(), override);
+        List<Map<String, Object>> effective = new java.util.ArrayList<>(source.size());
+        for (Map<String, Object> row : source)
+        {
+            String identity = stableIdentity(dataType, row);
+            TTestDataOverride override = byIdentity.get(identity);
+            if (override != null && Boolean.TRUE.equals(override.getDeleted())) continue;
+            Map<String, Object> copy = new LinkedHashMap<>(row);
+            if (override != null && override.getFieldValues() != null
+                    && !override.getFieldValues().isBlank())
+            {
+                try
+                {
+                    Map<String, Object> values = JSON.parseObject(override.getFieldValues(), Map.class);
+                    if (values != null) copy.putAll(values);
+                }
+                catch (RuntimeException ignored)
+                {
+                    // 覆盖已由修订服务校验;解析异常时保留原始事实,避免查询整体失败。
+                }
+            }
+            effective.add(copy);
+        }
+        return effective;
+    }
+
+    private String stableIdentity(String dataType, Map<String, Object> row)
+    {
+        String deviceId = value(row, "deviceId");
+        String channelId = value(row, "channelId");
+        String cycleNo = value(row, "cycleNo");
+        if ("REALTIME".equals(dataType)) return deviceId + ":" + channelId + ":" + value(row, "sequenceId");
+        if ("STEP".equals(dataType)) return "step:" + deviceId + ":" + channelId + ":" + cycleNo + ":" + value(row, "stepNo");
+        return "cycle:" + deviceId + ":" + channelId + ":" + cycleNo;
+    }
+
+    private String value(Map<String, Object> row, String key)
+    {
+        Object value = row == null ? null : row.get(key);
+        return value == null ? "" : String.valueOf(value);
     }
 
     private void requireTest(Long testId)

--
Gitblit v1.7.1