From 3b0d51d628c772c47845d9fd0b95ebde8a969d07 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期二, 01 九月 2026 11:59:12 +0800
Subject: [PATCH] 实现数据修订行为模型

---
 ruoyi-admin/src/test/java/com/ruoyi/config/Ticket26MigrationContractTest.java                        |   25 +++
 ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TestConsoleDataRevisionQueryServiceTest.java |  116 ++++++++++++++++
 ruoyi-admin/src/main/resources/db/migration/V26__data_revision_action_query_contract.sql             |    3 
 ruoyi-system/src/main/resources/mapper/system/TTestMapper.xml                                        |   60 ++++++++
 ruoyi-system/src/main/java/com/ruoyi/system/domain/TTestDataRevisionAction.java                      |   22 +++
 ruoyi-system/src/main/java/com/ruoyi/system/mapper/TTestMapper.java                                  |   13 +
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/TTestDataRevisionController.java           |   40 +++++
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TestConsoleDataServiceImpl.java             |   94 ++++++++++++-
 ruoyi-system/src/main/java/com/ruoyi/system/service/ITestConsoleDataService.java                     |    6 
 9 files changed, 366 insertions(+), 13 deletions(-)

diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/TTestDataRevisionController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/TTestDataRevisionController.java
new file mode 100644
index 0000000..3476f52
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/TTestDataRevisionController.java
@@ -0,0 +1,40 @@
+package com.ruoyi.web.controller.system;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.system.domain.TTestDataRevisionAction;
+import com.ruoyi.system.service.ITestConsoleDataService;
+
+/** 数据修订行为查询接口;只读返回一次用户行为及其摘要。 */
+@RestController
+@RequestMapping("/system/data-revision")
+public class TTestDataRevisionController extends BaseController
+{
+    @Autowired
+    private ITestConsoleDataService service;
+
+    @PreAuthorize("@ss.hasPermi('system:dataRevision:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(TTestDataRevisionAction query)
+    {
+        startPage();
+        List<TTestDataRevisionAction> rows = service.selectActions(query);
+        return getDataTable(rows);
+    }
+
+    @PreAuthorize("@ss.hasPermi('system:dataRevision:query')")
+    @GetMapping("/{actionId}")
+    public AjaxResult detail(@PathVariable Long actionId)
+    {
+        TTestDataRevisionAction action = service.selectActionById(actionId);
+        return action == null ? error("未找到数据修订行为") : success(action);
+    }
+}
diff --git a/ruoyi-admin/src/main/resources/db/migration/V26__data_revision_action_query_contract.sql b/ruoyi-admin/src/main/resources/db/migration/V26__data_revision_action_query_contract.sql
new file mode 100644
index 0000000..517c854
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/db/migration/V26__data_revision_action_query_contract.sql
@@ -0,0 +1,3 @@
+-- Ticket26:数据修订行为查询使用现有一次行为表,不复制按单元格记录。
+CREATE INDEX idx_data_revision_query_time
+    ON t_test_data_revision_action (data_type, operation_type, created_at_utc, action_id);
diff --git a/ruoyi-admin/src/test/java/com/ruoyi/config/Ticket26MigrationContractTest.java b/ruoyi-admin/src/test/java/com/ruoyi/config/Ticket26MigrationContractTest.java
new file mode 100644
index 0000000..14cb970
--- /dev/null
+++ b/ruoyi-admin/src/test/java/com/ruoyi/config/Ticket26MigrationContractTest.java
@@ -0,0 +1,25 @@
+package com.ruoyi.config;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import org.junit.jupiter.api.Test;
+
+/** Ticket26 迁移只增加行为查询索引,不建立按行或按格修订表。 */
+class Ticket26MigrationContractTest
+{
+    @Test
+    void addsActionQueryIndexWithoutReplacingAuditFacts() throws Exception
+    {
+        try (InputStream stream = getClass().getClassLoader().getResourceAsStream(
+                "db/migration/V26__data_revision_action_query_contract.sql"))
+        {
+            if (stream == null) throw new AssertionError("缺少 Ticket26 迁移");
+            String sql = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
+            assertTrue(sql.contains("idx_data_revision_query_time"));
+            assertTrue(sql.contains("t_test_data_revision_action"));
+            assertFalse(sql.matches("(?s).*\\b(DROP|DELETE|UPDATE)\\b.*"));
+        }
+    }
+}
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
new file mode 100644
index 0000000..c884534
--- /dev/null
+++ b/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TestConsoleDataRevisionQueryServiceTest.java
@@ -0,0 +1,116 @@
+package com.ruoyi.system.service.impl;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.system.domain.TTestDataRevisionAction;
+import com.ruoyi.system.domain.TTestDataOverride;
+import com.ruoyi.system.mapper.TTestMapper;
+
+/** Ticket26:修订行为按一次提交查询,且三类数据与三类操作均受服务端约束。 */
+@ExtendWith(MockitoExtension.class)
+class TestConsoleDataRevisionQueryServiceTest
+{
+    @Mock
+    private TTestMapper mapper;
+    @InjectMocks
+    private TestConsoleDataServiceImpl service;
+    private TTestDataRevisionAction query;
+
+    @BeforeEach
+    void setUp()
+    {
+        query = new TTestDataRevisionAction();
+        query.setTestId(26L);
+        query.setDataType("STEP");
+        query.setOperationType("EDIT");
+    }
+
+    @Test
+    void delegatesOneActionQueryWithFilters()
+    {
+        TTestDataRevisionAction action = new TTestDataRevisionAction();
+        action.setActionId(101L);
+        when(mapper.selectDataRevisionActions(query)).thenReturn(List.of(action));
+
+        assertEquals(101L, service.selectActions(query).get(0).getActionId());
+        verify(mapper).selectDataRevisionActions(query);
+    }
+
+    @Test
+    void rejectsUnknownDataTypeAndOperation()
+    {
+        query.setDataType("CELL");
+        assertThrows(ServiceException.class, () -> service.selectActions(query));
+        query.setDataType("CYCLE");
+        query.setOperationType("RESTORE");
+        assertThrows(ServiceException.class, () -> service.selectActions(query));
+    }
+
+    @Test
+    void returnsReadOnlyActionDetail()
+    {
+        TTestDataRevisionAction action = new TTestDataRevisionAction();
+        action.setActionId(99L);
+        action.setOperationType("DELETE");
+        when(mapper.selectDataRevisionActionById(99L)).thenReturn(action);
+
+        assertEquals("DELETE", service.selectActionById(99L).getOperationType());
+        assertThrows(ServiceException.class, () -> service.selectActionById(0L));
+    }
+
+    @Test
+    void rejectsIdentitySummaryThatDoesNotMatchOverrides()
+    {
+        TTestDataRevisionAction action = new TTestDataRevisionAction();
+        action.setTestId(26L); action.setDataType("REALTIME"); action.setOperationType("DELETE");
+        action.setScopeSummary("删除一条实时数据"); action.setIdentitySummary("[\"record-1\"]");
+        action.setReason("异常点"); action.setOperatorName("tester");
+        TTestDataOverride override = new TTestDataOverride();
+        override.setStableIdentity("record-2"); override.setDeleted(true);
+
+        assertThrows(ServiceException.class,
+                () -> service.submitAction(action, List.of(override), List.of(), List.of()));
+    }
+
+    @Test
+    void rejectsIdentitySummaryWithExtraIdentity()
+    {
+        TTestDataRevisionAction action = new TTestDataRevisionAction();
+        action.setTestId(26L); action.setDataType("STEP"); action.setOperationType("DELETE");
+        action.setScopeSummary("删除一条工步数据"); action.setIdentitySummary("[\"step:1:2:3:4\",\"step:1:2:3:5\"]");
+        action.setReason("异常工步"); action.setOperatorName("tester");
+        TTestDataOverride override = new TTestDataOverride();
+        override.setStableIdentity("step:1:2:3:4"); override.setDeleted(true);
+
+        assertThrows(ServiceException.class,
+                () -> service.submitAction(action, List.of(override), List.of(), List.of()));
+    }
+
+    @Test
+    void physicallyDeletesSamplesBehindStepAggregate()
+    {
+        TTestDataRevisionAction action = new TTestDataRevisionAction();
+        action.setTestId(26L); action.setDataType("STEP"); action.setOperationType("DELETE");
+        action.setScopeSummary("删除一条工步数据"); action.setIdentitySummary("[\"step:1:2:3:4\"]");
+        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));
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/TTestDataRevisionAction.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/TTestDataRevisionAction.java
index 1e5d79f..89497cb 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/TTestDataRevisionAction.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/TTestDataRevisionAction.java
@@ -2,6 +2,9 @@
 
 import java.io.Serializable;
 import java.util.Date;
+import org.springframework.format.annotation.DateTimeFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonFormat;
 
 /** 一次控制台数据编辑、导入或删除行为。 */
 public class TTestDataRevisionAction implements Serializable
@@ -17,7 +20,18 @@
     private String reason;
     private Long operatorId;
     private String operatorName;
+    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "UTC")
     private Date createdAtUtc;
+    /** 查询展示字段:来源测试名称。 */
+    private String testName;
+    /** 返回测试控制台时使用的后端接收状态。 */
+    private String testReceptionStatus;
+    @JsonIgnore
+    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
+    private Date beginCreatedAt;
+    @JsonIgnore
+    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
+    private Date endCreatedAt;
 
     public Long getActionId() { return actionId; }
     public void setActionId(Long value) { actionId = value; }
@@ -41,4 +55,12 @@
     public void setOperatorName(String value) { operatorName = value; }
     public Date getCreatedAtUtc() { return createdAtUtc; }
     public void setCreatedAtUtc(Date value) { createdAtUtc = value; }
+    public String getTestName() { return testName; }
+    public void setTestName(String value) { testName = value; }
+    public String getTestReceptionStatus() { return testReceptionStatus; }
+    public void setTestReceptionStatus(String value) { testReceptionStatus = value; }
+    public Date getBeginCreatedAt() { return beginCreatedAt; }
+    public void setBeginCreatedAt(Date value) { beginCreatedAt = value; }
+    public Date getEndCreatedAt() { return endCreatedAt; }
+    public void setEndCreatedAt(Date value) { endCreatedAt = value; }
 }
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/TTestMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/TTestMapper.java
index 33224d9..c000e5c 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/TTestMapper.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/TTestMapper.java
@@ -192,14 +192,23 @@
     int countOpenExceptions(Long testId);
 
     int insertDataRevisionAction(TTestDataRevisionAction action);
+    List<TTestDataRevisionAction> selectDataRevisionActions(TTestDataRevisionAction query);
+    TTestDataRevisionAction selectDataRevisionActionById(Long actionId);
     int upsertDataOverride(TTestDataOverride override);
     List<TTestDataOverride> selectDataOverrides(@Param("testId") Long testId,
             @Param("dataType") String dataType);
     int deleteBtsSamplesByIds(@Param("testId") Long testId, @Param("sampleIds") List<Long> sampleIds);
     int deleteBtsMeasurementsBySampleIds(@Param("sampleIds") List<Long> sampleIds);
     int deleteCollectorSamplesByIds(@Param("testId") Long testId, @Param("sampleIds") List<Long> sampleIds);
-    Long selectBtsSampleIdByChannelSequence(@Param("testId") Long testId,
-            @Param("channelId") Long channelId, @Param("sequenceId") Long sequenceId);
+    Long selectBtsSampleIdByDeviceChannelSequence(@Param("testId") Long testId,
+            @Param("deviceId") Long deviceId, @Param("channelNo") Long channelNo,
+            @Param("sequenceId") Long sequenceId);
+    List<Long> selectBtsSampleIdsByDeviceChannelStep(@Param("testId") Long testId,
+            @Param("deviceId") Long deviceId, @Param("channelNo") Long channelNo,
+            @Param("cycleNo") Integer cycleNo, @Param("stepNo") Integer stepNo);
+    List<Long> selectBtsSampleIdsByDeviceChannelCycle(@Param("testId") Long testId,
+            @Param("deviceId") Long deviceId, @Param("channelNo") Long channelNo,
+            @Param("cycleNo") Integer cycleNo);
     int insertConsoleImportRecord(TTestConsoleImportRecord record);
     int countTestBatteryNo(@Param("testId") Long testId, @Param("batteryNo") String batteryNo);
 }
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ITestConsoleDataService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ITestConsoleDataService.java
index ef91987..48bc903 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ITestConsoleDataService.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ITestConsoleDataService.java
@@ -7,6 +7,12 @@
 /** 控制台三类数据的修订、导入和物理删除服务。 */
 public interface ITestConsoleDataService
 {
+    /** 按测试、数据类型、操作类型和时间查询一次用户行为。 */
+    List<TTestDataRevisionAction> selectActions(TTestDataRevisionAction query);
+
+    /** 返回一条只读修订行为,不恢复被删除的数据。 */
+    TTestDataRevisionAction selectActionById(Long actionId);
+
     List<TTestDataOverride> selectOverrides(Long testId, String dataType);
 
     /** 在一个事务中写入一次行为、覆盖状态并执行请求的物理删除。 */
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 d557873..4655e0d 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
@@ -28,6 +28,24 @@
     private TTestMapper mapper;
 
     @Override
+    public List<TTestDataRevisionAction> selectActions(TTestDataRevisionAction query)
+    {
+        if (query == null) query = new TTestDataRevisionAction();
+        if (query.getDataType() != null && !query.getDataType().isBlank()
+                && !DATA_TYPES.contains(query.getDataType())) throw new ServiceException("控制台数据类型无效");
+        if (query.getOperationType() != null && !query.getOperationType().isBlank()
+                && !OPERATIONS.contains(query.getOperationType())) throw new ServiceException("数据操作类型无效");
+        return mapper.selectDataRevisionActions(query);
+    }
+
+    @Override
+    public TTestDataRevisionAction selectActionById(Long actionId)
+    {
+        if (actionId == null || actionId < 1) throw new ServiceException("修订行为ID无效");
+        return mapper.selectDataRevisionActionById(actionId);
+    }
+
+    @Override
     public List<TTestDataOverride> selectOverrides(Long testId, String dataType)
     {
         validateType(testId, dataType);
@@ -50,6 +68,7 @@
         }
         if ("EDIT".equals(action.getOperationType())) validateEditableFields(action);
         if (action.getReason() == null || action.getReason().trim().isEmpty()) throw new ServiceException("数据操作原因不能为空");
+        validateActionSummaries(action, overrides);
         if (action.getOperatorName() == null || action.getOperatorName().trim().isEmpty()) action.setOperatorName("未知用户");
         action.setReason(action.getReason().trim());
         action.setCreatedAtUtc(new Date());
@@ -95,18 +114,31 @@
                 mapper.upsertDataOverride(item);
             }
         }
-        if ("DELETE".equals(action.getOperationType()) && "REALTIME".equals(action.getDataType()) && overrides != null)
+        if ("DELETE".equals(action.getOperationType()) && overrides != null)
         {
             List<Long> resolvedIds = new ArrayList<>();
             for (TTestDataOverride item : overrides)
             {
-                Long[] identity = parseRealtimeIdentity(item.getStableIdentity());
-                if (identity == null) continue;
-                Long sampleId = mapper.selectBtsSampleIdByChannelSequence(action.getTestId(), identity[0], identity[1]);
-                if (sampleId != null) resolvedIds.add(sampleId);
+                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);
             }
@@ -175,6 +207,43 @@
         catch (Exception exception) { throw new ServiceException("修改字段格式无效"); }
     }
 
+    /** 防止客户端提交与实际覆盖身份不一致的伪行为摘要。 */
+    private void validateActionSummaries(TTestDataRevisionAction action, List<TTestDataOverride> overrides)
+    {
+        if (action.getScopeSummary() == null || action.getScopeSummary().trim().isEmpty())
+            throw new ServiceException("数据操作范围不能为空");
+        Set<String> summaryIdentities = new HashSet<>();
+        try
+        {
+            Object parsed = JSON.parse(action.getIdentitySummary());
+            if (!(parsed instanceof List<?> values)) throw new ServiceException("身份摘要格式无效");
+            for (Object value : values)
+                if (value == null || !summaryIdentities.add(String.valueOf(value)))
+                    throw new ServiceException("身份摘要存在重复或空值");
+        }
+        catch (ServiceException exception) { throw exception; }
+        catch (Exception exception) { throw new ServiceException("身份摘要格式无效"); }
+        if (overrides == null) return;
+        Set<String> overrideIdentities = new HashSet<>();
+        for (TTestDataOverride item : overrides)
+            if (item == null || !overrideIdentities.add(item.getStableIdentity())
+                    || !summaryIdentities.contains(item.getStableIdentity()))
+                throw new ServiceException("身份摘要与数据覆盖明细不一致");
+        if (!summaryIdentities.equals(overrideIdentities))
+            throw new ServiceException("身份摘要与数据覆盖明细不一致");
+        if ("EDIT".equals(action.getOperationType()))
+        {
+            try
+            {
+                Object parsed = JSON.parse(action.getChangesSummary());
+                if (!(parsed instanceof List<?> changes) || changes.size() < overrides.size())
+                    throw new ServiceException("字段变化摘要与数据覆盖明细不一致");
+            }
+            catch (ServiceException exception) { throw exception; }
+            catch (Exception exception) { throw new ServiceException("字段变化摘要格式无效"); }
+        }
+    }
+
     private Set<String> editableFields(String dataType)
     {
         return switch (dataType)
@@ -188,11 +257,20 @@
         };
     }
 
-    private Long[] parseRealtimeIdentity(String value)
+    private Long[] parseDeleteIdentity(String value, String dataType)
     {
         String[] parts = value.split(":", -1);
-        if (parts.length != 3) return null;
-        try { return new Long[] { Long.valueOf(parts[0]), Long.valueOf(parts[2]) }; }
+        int expectedLength = "STEP".equals(dataType) ? 5 : "CYCLE".equals(dataType) ? 4 : 3;
+        if (parts.length != expectedLength) return null;
+        int offset = "REALTIME".equals(dataType) ? 0 : 1;
+        try
+        {
+            if ("STEP".equals(dataType))
+                return new Long[] { Long.valueOf(parts[offset]), Long.valueOf(parts[offset + 1]),
+                        Long.valueOf(parts[offset + 2]), Long.valueOf(parts[offset + 3]) };
+            return new Long[] { Long.valueOf(parts[offset]), Long.valueOf(parts[offset + 1]),
+                    Long.valueOf(parts[offset + 2]) };
+        }
         catch (NumberFormatException exception) { return null; }
     }
 
diff --git a/ruoyi-system/src/main/resources/mapper/system/TTestMapper.xml b/ruoyi-system/src/main/resources/mapper/system/TTestMapper.xml
index 7f26929..1f0e210 100644
--- a/ruoyi-system/src/main/resources/mapper/system/TTestMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/TTestMapper.xml
@@ -227,6 +227,42 @@
         (#{testName}, #{creatorId}, #{principalId}, #{testTypeId}, #{testPurpose}, #{testStatus}, #{receptionStatus},
          #{monitorTimeoutMinutes}, 0, #{createBy}, #{createTime}, #{remark})
     </insert>
+    <resultMap id="TTestDataRevisionActionResultMap" type="com.ruoyi.system.domain.TTestDataRevisionAction">
+        <id property="actionId" column="action_id"/>
+        <result property="testId" column="test_id"/><result property="testName" column="test_name"/>
+        <result property="testReceptionStatus" column="reception_status"/>
+        <result property="dataType" column="data_type"/><result property="operationType" column="operation_type"/>
+        <result property="scopeSummary" column="scope_summary"/><result property="identitySummary" column="identity_summary"/>
+        <result property="changesSummary" column="changes_summary"/><result property="reason" column="reason"/>
+        <result property="operatorId" column="operator_id"/><result property="operatorName" column="operator_name"/>
+        <result property="createdAtUtc" column="created_at_utc"/>
+    </resultMap>
+    <select id="selectDataRevisionActions" parameterType="com.ruoyi.system.domain.TTestDataRevisionAction" resultMap="TTestDataRevisionActionResultMap">
+        select a.action_id, a.test_id, t.test_name, t.reception_status,
+               a.data_type, a.operation_type, a.scope_summary,
+               cast(a.identity_summary as char) identity_summary,
+               cast(a.changes_summary as char) changes_summary, a.reason,
+               a.operator_id, a.operator_name, a.created_at_utc
+        from t_test_data_revision_action a join t_test t on t.test_id = a.test_id
+        <where>
+            <if test="testId != null">and a.test_id = #{testId}</if>
+            <if test="testName != null and testName != ''">and t.test_name like concat('%', #{testName}, '%')</if>
+            <if test="dataType != null and dataType != ''">and a.data_type = #{dataType}</if>
+            <if test="operationType != null and operationType != ''">and a.operation_type = #{operationType}</if>
+            <if test="beginCreatedAt != null">and a.created_at_utc &gt;= #{beginCreatedAt}</if>
+            <if test="endCreatedAt != null">and a.created_at_utc &lt; #{endCreatedAt}</if>
+        </where>
+        order by a.created_at_utc desc, a.action_id desc
+    </select>
+    <select id="selectDataRevisionActionById" parameterType="long" resultMap="TTestDataRevisionActionResultMap">
+        select a.action_id, a.test_id, t.test_name, t.reception_status,
+               a.data_type, a.operation_type, a.scope_summary,
+               cast(a.identity_summary as char) identity_summary,
+               cast(a.changes_summary as char) changes_summary, a.reason,
+               a.operator_id, a.operator_name, a.created_at_utc
+        from t_test_data_revision_action a join t_test t on t.test_id = a.test_id
+        where a.action_id = #{actionId}
+    </select>
 
     <update id="updateTest" parameterType="TTest">
         update t_test
@@ -822,11 +858,29 @@
         delete from t_test_collector_sample where test_id = #{testId} and sample_id in
         <foreach collection="sampleIds" item="id" open="(" separator="," close=")">#{id}</foreach>
     </delete>
-    <select id="selectBtsSampleIdByChannelSequence" resultType="long">
-        select sample_id from t_test_sample
-        where test_id = #{testId} and channel_id = #{channelId} and seq_id = #{sequenceId}
+    <select id="selectBtsSampleIdByDeviceChannelSequence" resultType="long">
+        select sample.sample_id
+        from t_test_sample sample
+        join t_detection_channel channel_fact on channel_fact.channel_id = sample.channel_id
+        where sample.test_id = #{testId} and channel_fact.devid = #{deviceId}
+          and channel_fact.chlid = #{channelNo} and sample.seq_id = #{sequenceId}
         limit 1
     </select>
+    <select id="selectBtsSampleIdsByDeviceChannelStep" resultType="long">
+        select sample.sample_id
+        from t_test_sample sample
+        join t_detection_channel channel_fact on channel_fact.channel_id = sample.channel_id
+        where sample.test_id = #{testId} and channel_fact.devid = #{deviceId}
+          and channel_fact.chlid = #{channelNo} and sample.cycle_no = #{cycleNo}
+          and sample.step_no = #{stepNo}
+    </select>
+    <select id="selectBtsSampleIdsByDeviceChannelCycle" resultType="long">
+        select sample.sample_id
+        from t_test_sample sample
+        join t_detection_channel channel_fact on channel_fact.channel_id = sample.channel_id
+        where sample.test_id = #{testId} and channel_fact.devid = #{deviceId}
+          and channel_fact.chlid = #{channelNo} and sample.cycle_no = #{cycleNo}
+    </select>
     <insert id="insertConsoleImportRecord" parameterType="com.ruoyi.system.domain.TTestConsoleImportRecord"
             useGeneratedKeys="true" keyProperty="importRecordId">
         insert into t_test_console_import_record

--
Gitblit v1.7.1