From 20b346ef9d4fcbbbe05abfe08e4f4f0303b0812e Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期日, 30 八月 2026 20:37:45 +0800
Subject: [PATCH] 实现 Ticket12 人工结束与顺序接入

---
 ruoyi-admin/src/main/resources/db/migration/V20__manual_end_reception_intervals.sql        |   38 +++++++++
 ruoyi-admin/src/test/java/com/ruoyi/config/FlywayMigrationContractTest.java                |    6 +
 ruoyi-system/src/main/java/com/ruoyi/system/domain/TTest.java                              |    3 
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TTestServiceImpl.java             |   16 +++
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/TTestController.java             |   10 ++
 ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TTestServiceImplTest.java          |   20 +++++
 ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/TTestControllerContractTest.java |    3 
 ruoyi-system/src/main/resources/mapper/system/TTestMapper.xml                              |   33 ++++++++
 ruoyi-system/src/main/java/com/ruoyi/system/mapper/TTestMapper.java                        |   11 ++
 ruoyi-system/src/main/java/com/ruoyi/system/domain/TTestReceptionInterval.java             |   42 ++++++++++
 10 files changed, 179 insertions(+), 3 deletions(-)

diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/TTestController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/TTestController.java
index ae50652..0387c7b 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/TTestController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/TTestController.java
@@ -125,6 +125,16 @@
         return success(testService.selectTestById(testId));
     }
 
+    /** 人工结束接收动作别名;与停止测试共用原子关闭区间和释放租约事务。 */
+    @PreAuthorize("@ss.hasPermi('system:test:stop')")
+    @Log(title = "人工结束接收", businessType = BusinessType.UPDATE)
+    @PostMapping("/{testId}/reception/end")
+    public AjaxResult endReception(@PathVariable Long testId)
+    {
+        testService.stopTest(testId, getUserId(), getUsername());
+        return success(testService.selectTestById(testId));
+    }
+
     @PreAuthorize("@ss.hasPermi('system:test:remove')")
     @Log(title = "测试管理", businessType = BusinessType.DELETE)
     @DeleteMapping("/{testIds}")
diff --git a/ruoyi-admin/src/main/resources/db/migration/V20__manual_end_reception_intervals.sql b/ruoyi-admin/src/main/resources/db/migration/V20__manual_end_reception_intervals.sql
new file mode 100644
index 0000000..d754d60
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/db/migration/V20__manual_end_reception_intervals.sql
@@ -0,0 +1,38 @@
+-- Ticket12:人工结束关闭全部开放接收区间,并为顺序接入保留稳定的来源时间索引。
+-- DDL 使用 information_schema 守卫,便于已迁移开发库重复启动时安全执行。
+SET @boundary_constraint_sql := (
+    SELECT IF(COUNT(*) = 0,
+        'ALTER TABLE t_test_reception_interval ADD CONSTRAINT chk_reception_interval_boundary_order CHECK (end_boundary_at_utc IS NULL OR end_boundary_at_utc >= start_boundary_at_utc)',
+        'SELECT 1')
+    FROM information_schema.table_constraints
+    WHERE constraint_schema = DATABASE()
+      AND table_name = 't_test_reception_interval'
+      AND constraint_name = 'chk_reception_interval_boundary_order');
+PREPARE boundary_constraint_stmt FROM @boundary_constraint_sql;
+EXECUTE boundary_constraint_stmt;
+DEALLOCATE PREPARE boundary_constraint_stmt;
+
+SET @source_order_index_sql := (
+    SELECT IF(COUNT(*) = 0,
+        'ALTER TABLE t_test_reception_interval ADD KEY idx_reception_interval_source_order (source_type, source_id, source_channel_code, start_boundary_at_utc, interval_id)',
+        'SELECT 1')
+    FROM information_schema.statistics
+    WHERE table_schema = DATABASE()
+      AND table_name = 't_test_reception_interval'
+      AND index_name = 'idx_reception_interval_source_order');
+PREPARE source_order_index_stmt FROM @source_order_index_sql;
+EXECUTE source_order_index_stmt;
+DEALLOCATE PREPARE source_order_index_stmt;
+
+-- 停止测试权限由 V16 提供;此处保留权限插入的幂等保护。
+SET @stop_menu_id := (SELECT COALESCE(MAX(menu_id), 0) + 1 FROM sys_menu);
+INSERT INTO sys_menu
+    (menu_id, menu_name, parent_id, order_num, path, component, query, route_name,
+     is_frame, is_cache, menu_type, visible, status, perms, icon,
+     create_by, create_time, update_by, update_time, remark)
+SELECT @stop_menu_id, '停止测试', menu_id, 9, '', NULL, NULL, '', 1, 0, 'F', '0', '0',
+       'system:test:stop', '#', 'admin', NOW(), '', NULL, '测试人工结束权限'
+FROM sys_menu
+WHERE perms = 'system:test:list'
+  AND NOT EXISTS (SELECT 1 FROM sys_menu existing WHERE existing.perms = 'system:test:stop')
+LIMIT 1;
diff --git a/ruoyi-admin/src/test/java/com/ruoyi/config/FlywayMigrationContractTest.java b/ruoyi-admin/src/test/java/com/ruoyi/config/FlywayMigrationContractTest.java
index 0473fd3..b595c92 100644
--- a/ruoyi-admin/src/test/java/com/ruoyi/config/FlywayMigrationContractTest.java
+++ b/ruoyi-admin/src/test/java/com/ruoyi/config/FlywayMigrationContractTest.java
@@ -143,6 +143,12 @@
         assertTrue(v19.contains("ADD UNIQUE KEY uk_collector_exception_open (open_dedupe_key)"));
         assertFalse(v19.matches("(?s).*\\bDROP\\s+TABLE\\b.*"));
 
+        String v20 = resource("db/migration/V20__manual_end_reception_intervals.sql");
+        assertTrue(v20.contains("chk_reception_interval_boundary_order"));
+        assertTrue(v20.contains("idx_reception_interval_source_order"));
+        assertTrue(v20.contains("system:test:stop"));
+        assertFalse(v20.matches("(?s).*\\bDROP\\s+TABLE\\b.*"));
+
         String testMapper = resource("mapper/system/TTestMapper.xml");
         assertTrue(testMapper.contains("selectReceivingChannelsForAdapter"));
         assertTrue(testMapper.contains("RECEPTION_RESUMED"));
diff --git a/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TTestServiceImplTest.java b/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TTestServiceImplTest.java
index b48d88d..0dd6a09 100644
--- a/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TTestServiceImplTest.java
+++ b/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TTestServiceImplTest.java
@@ -389,7 +389,9 @@
 
         assertEquals(1, testService.startReception(10L, 7L, "operator"));
         verify(testMapper).insertChannelLease(eq(100L), eq(10L), eq(30L), eq(7L), any());
+        verify(testMapper).insertReceptionInterval(eq(10L), eq(30L), eq("CABINET_CHANNEL"), eq(100L), isNull(), any(), eq("RECEPTION_STARTED"));
         verify(testMapper, times(3)).insertCollectorChannelLease(eq(8L), any(), eq(10L), eq(30L), eq(7L), any());
+        verify(testMapper, times(3)).insertReceptionInterval(eq(10L), eq(30L), eq("COLLECTOR_CHANNEL"), eq(8L), any(), any(), eq("RECEPTION_STARTED"));
         verify(testMapper).insertTestAudit(eq(10L), eq("RECEPTION_STARTED"), eq(7L), eq("operator"), isNull(), any());
     }
 
@@ -542,12 +544,30 @@
 
         assertEquals(1, testService.stopTest(10L, 7L, "operator"));
         verify(testMapper).updateReceptionStopped(eq(10L), eq("operator"), any());
+        verify(testMapper).closeOpenReceptionIntervals(eq(10L), any(), eq("MANUAL_END"));
         verify(testMapper).deleteChannelLeasesByTestId(10L);
         verify(testMapper).deleteCollectorChannelLeasesByTestId(10L);
         verify(testMapper).insertTestAudit(eq(10L), eq("TEST_STOPPED"), eq(7L), eq("operator"), isNull(),
                 org.mockito.ArgumentMatchers.argThat(value -> value != null && value.contains("COMPLETED")));
     }
 
+    @Test
+    void stopTestShouldAuditImmediateReleaseAndClosedIntervals()
+    {
+        TTest current = validTest();
+        current.setTestId(10L);
+        current.setReceptionStatus("RECEIVING");
+        when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current);
+        when(testMapper.updateReceptionStopped(eq(10L), eq("operator"), any())).thenReturn(1);
+        when(testMapper.closeOpenReceptionIntervals(eq(10L), any(), eq("MANUAL_END"))).thenReturn(4);
+
+        testService.stopTest(10L, 7L, "operator");
+
+        verify(testMapper).insertTestAudit(eq(10L), eq("TEST_STOPPED"), eq(7L), eq("operator"), isNull(),
+                org.mockito.ArgumentMatchers.argThat(value -> value != null
+                        && value.contains("closedIntervals") && value.contains("IMMEDIATE")));
+    }
+
     private void mockEnabledOptions()
     {
         mockEnabledPeopleAndType();
diff --git a/ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/TTestControllerContractTest.java b/ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/TTestControllerContractTest.java
index a86fef1..f91bc15 100644
--- a/ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/TTestControllerContractTest.java
+++ b/ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/TTestControllerContractTest.java
@@ -27,6 +27,9 @@
         assertPermission("stopTest", "@ss.hasPermi('system:test:stop')", Long.class);
         assertEquals("/{testId}/stop", TTestController.class.getMethod("stopTest", Long.class)
                 .getAnnotation(PostMapping.class).value()[0]);
+        assertPermission("endReception", "@ss.hasPermi('system:test:stop')", Long.class);
+        assertEquals("/{testId}/reception/end", TTestController.class.getMethod("endReception", Long.class)
+                .getAnnotation(PostMapping.class).value()[0]);
     }
 
     private void assertPermission(String methodName, String permission, Class<?>... parameterTypes) throws Exception
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/TTest.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/TTest.java
index 6af3e29..ff492bc 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/TTest.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/TTest.java
@@ -55,6 +55,7 @@
     @NotEmpty(message = "阈值配置不能为空")
     private List<TTestWarningThreshold> thresholds;
     private List<TTestFieldSnapshot> fieldSnapshots;
+    private List<TTestReceptionInterval> receptionIntervals;
     private List<TTestResult> resultSummary;
 
     public Long getTestId() { return testId; }
@@ -107,6 +108,8 @@
     public void setThresholds(List<TTestWarningThreshold> value) { thresholds = value; }
     public List<TTestFieldSnapshot> getFieldSnapshots() { return fieldSnapshots; }
     public void setFieldSnapshots(List<TTestFieldSnapshot> value) { fieldSnapshots = value; }
+    public List<TTestReceptionInterval> getReceptionIntervals() { return receptionIntervals; }
+    public void setReceptionIntervals(List<TTestReceptionInterval> value) { receptionIntervals = value; }
     public List<TTestResult> getResultSummary() { return resultSummary; }
     public void setResultSummary(List<TTestResult> value) { resultSummary = value; }
 }
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/TTestReceptionInterval.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/TTestReceptionInterval.java
new file mode 100644
index 0000000..1ece2e3
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/TTestReceptionInterval.java
@@ -0,0 +1,42 @@
+package com.ruoyi.system.domain;
+
+import java.util.Date;
+
+/** 测试数据来源的追加接收区间事实。 */
+public class TTestReceptionInterval
+{
+    private Long intervalId;
+    private Long testId;
+    private Long bindingId;
+    private String sourceType;
+    private Long sourceId;
+    private String sourceChannelCode;
+    private Date actionAtUtc;
+    private Date startBoundaryAtUtc;
+    private Date endBoundaryAtUtc;
+    private String boundaryNote;
+    private String intervalStatus;
+
+    public Long getIntervalId() { return intervalId; }
+    public void setIntervalId(Long value) { intervalId = value; }
+    public Long getTestId() { return testId; }
+    public void setTestId(Long value) { testId = value; }
+    public Long getBindingId() { return bindingId; }
+    public void setBindingId(Long value) { bindingId = value; }
+    public String getSourceType() { return sourceType; }
+    public void setSourceType(String value) { sourceType = value; }
+    public Long getSourceId() { return sourceId; }
+    public void setSourceId(Long value) { sourceId = value; }
+    public String getSourceChannelCode() { return sourceChannelCode; }
+    public void setSourceChannelCode(String value) { sourceChannelCode = value; }
+    public Date getActionAtUtc() { return actionAtUtc; }
+    public void setActionAtUtc(Date value) { actionAtUtc = value; }
+    public Date getStartBoundaryAtUtc() { return startBoundaryAtUtc; }
+    public void setStartBoundaryAtUtc(Date value) { startBoundaryAtUtc = value; }
+    public Date getEndBoundaryAtUtc() { return endBoundaryAtUtc; }
+    public void setEndBoundaryAtUtc(Date value) { endBoundaryAtUtc = value; }
+    public String getBoundaryNote() { return boundaryNote; }
+    public void setBoundaryNote(String value) { boundaryNote = value; }
+    public String getIntervalStatus() { return intervalStatus; }
+    public void setIntervalStatus(String value) { intervalStatus = 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 ee5884e..2dd1aeb 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
@@ -13,6 +13,7 @@
 import com.ruoyi.system.domain.TTestWarningThreshold;
 import com.ruoyi.system.domain.TTestCollectorSource;
 import com.ruoyi.system.domain.TTestCollectorExceptionInterval;
+import com.ruoyi.system.domain.TTestReceptionInterval;
 import com.ruoyi.system.domain.TDetectionChannel;
 import com.ruoyi.system.domain.vo.TestBatteryOptionVo;
 import com.ruoyi.system.domain.vo.TestChannelOptionVo;
@@ -49,6 +50,8 @@
     List<TTestChannelBinding> selectTestChannelBindings(Long testId);
     List<TTestCollectorChannel> selectTestCollectorChannels(Long testId);
     List<TTestWarningThreshold> selectTestThresholds(Long testId);
+    /** 按来源和起始边界顺序返回测试接收区间。 */
+    List<TTestReceptionInterval> selectTestReceptionIntervals(Long testId);
     List<TTestResult> selectTestResults(TTestResult result);
     int countTestTypeEnabled(Long testTypeId);
     int countUserEnabled(Long userId);
@@ -75,6 +78,14 @@
     int insertCollectorChannelLease(@Param("collectorId") Long collectorId, @Param("channelCode") String channelCode,
             @Param("testId") Long testId, @Param("bindingId") Long bindingId,
             @Param("acquiredBy") Long acquiredBy, @Param("acquiredAtUtc") Date acquiredAtUtc);
+    /** 为每个绑定来源追加一个开放接收区间;区间关闭后仅保留历史事实。 */
+    int insertReceptionInterval(@Param("testId") Long testId, @Param("bindingId") Long bindingId,
+            @Param("sourceType") String sourceType, @Param("sourceId") Long sourceId,
+            @Param("sourceChannelCode") String sourceChannelCode, @Param("actionAtUtc") Date actionAtUtc,
+            @Param("boundaryNote") String boundaryNote);
+    /** 在停止动作事务内关闭该测试全部开放区间,返回实际关闭数量。 */
+    int closeOpenReceptionIntervals(@Param("testId") Long testId, @Param("endBoundaryAtUtc") Date endBoundaryAtUtc,
+            @Param("boundaryNote") String boundaryNote);
     int deleteChannelLeasesByTestId(Long testId);
     int deleteCollectorChannelLeasesByTestId(Long testId);
     int hasReceivingTestForAdapter(@Param("adapterCode") String adapterCode);
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 4e655b7..329d523 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
@@ -180,8 +180,8 @@
             TDetectionChannel channel = testMapper.selectReceptionChannelForUpdate(binding.getChannelId());
             if (channel == null || !isEnabled(channel.getStatus()) || !isOnline(channel.getOnlineStatus()))
                 throw new ServiceException("检测柜通道 " + binding.getChannelId() + " 当前离线或不可观测");
-            runtimeAttach |= "WORKING".equalsIgnoreCase(channel.getWorkStatus())
-                    || "PAUSED".equalsIgnoreCase(channel.getWorkStatus());
+            // 只有设备事实为运行中时才标记“运行中接入”;设备暂停属于独立设备状态。
+            runtimeAttach |= "WORKING".equalsIgnoreCase(channel.getWorkStatus());
             if (testMapper.selectChannelLeaseForUpdate(binding.getChannelId()) != null)
                 throw new ServiceException("检测柜通道 " + binding.getChannelId() + " 已被其他测试占用");
             if (battery.getCollectorId() == null || testMapper.countCollectorEnabled(battery.getCollectorId()) == 0)
@@ -200,9 +200,15 @@
             {
                 TTestChannelBinding binding = battery.getBinding();
                 testMapper.insertChannelLease(binding.getChannelId(), testId, binding.getBindingId(), operatorId, actionAt);
+                testMapper.insertReceptionInterval(testId, binding.getBindingId(), "CABINET_CHANNEL",
+                        binding.getChannelId(), null, actionAt, "RECEPTION_STARTED");
                 for (TTestCollectorChannel mapping : mutable(battery.getCollectorChannels()))
+                {
                     testMapper.insertCollectorChannelLease(battery.getCollectorId(), mapping.getChannelCode(), testId,
                             binding.getBindingId(), operatorId, actionAt);
+                    testMapper.insertReceptionInterval(testId, binding.getBindingId(), "COLLECTOR_CHANNEL",
+                            battery.getCollectorId(), mapping.getChannelCode(), actionAt, "RECEPTION_STARTED");
+                }
             }
             if (testMapper.updateReceptionStarted(testId, actionAt, runtimeAttach, updateBy) != 1)
                 throw new ServiceException("测试状态已变更,请刷新后重试");
@@ -266,10 +272,12 @@
         Date actionAt = new Date();
         if (testMapper.updateReceptionStopped(testId, updateBy, actionAt) != 1)
             throw new ServiceException("测试状态已变更,请刷新后重试");
+        int closedIntervals = testMapper.closeOpenReceptionIntervals(testId, actionAt, "MANUAL_END");
         testMapper.deleteChannelLeasesByTestId(testId);
         testMapper.deleteCollectorChannelLeasesByTestId(testId);
         testMapper.insertTestAudit(testId, "TEST_STOPPED", operatorId, updateBy, null,
-                JSON.toJSONString(Map.of("testStatus", "COMPLETED", "receptionStatus", "ENDED", "polling", "STOPPED")));
+                JSON.toJSONString(Map.of("testStatus", "COMPLETED", "receptionStatus", "ENDED",
+                        "polling", "STOPPED", "closedIntervals", closedIntervals, "release", "IMMEDIATE")));
         return 1;
     }
 
@@ -360,6 +368,8 @@
         test.setBatteryNo(joinBatteryNos(batteries));
         List<TTestWarningThreshold> loadedThresholds = testMapper.selectTestThresholds(testId);
         test.setThresholds(loadedThresholds == null ? mutable(test.getThresholds()) : mutable(loadedThresholds));
+        List<com.ruoyi.system.domain.TTestReceptionInterval> intervals = testMapper.selectTestReceptionIntervals(testId);
+        test.setReceptionIntervals(intervals == null ? List.of() : intervals);
         List<TTestFieldSnapshot> loadedSnapshots = sharedFieldSnapshotService.selectByTestId(testId);
         test.setFieldSnapshots(loadedSnapshots == null ? mutable(test.getFieldSnapshots()) : loadedSnapshots);
     }
diff --git a/ruoyi-system/src/main/resources/mapper/system/TTestMapper.xml b/ruoyi-system/src/main/resources/mapper/system/TTestMapper.xml
index 09d0c51..0c10891 100644
--- a/ruoyi-system/src/main/resources/mapper/system/TTestMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/TTestMapper.xml
@@ -125,6 +125,19 @@
         <result property="intervalStatus" column="interval_status" />
         <result property="detail" column="detail" />
     </resultMap>
+    <resultMap id="TTestReceptionIntervalResultMap" type="TTestReceptionInterval">
+        <id property="intervalId" column="interval_id" />
+        <result property="testId" column="test_id" />
+        <result property="bindingId" column="binding_id" />
+        <result property="sourceType" column="source_type" />
+        <result property="sourceId" column="source_id" />
+        <result property="sourceChannelCode" column="source_channel_code" />
+        <result property="actionAtUtc" column="action_at_utc" />
+        <result property="startBoundaryAtUtc" column="start_boundary_at_utc" />
+        <result property="endBoundaryAtUtc" column="end_boundary_at_utc" />
+        <result property="boundaryNote" column="boundary_note" />
+        <result property="intervalStatus" column="interval_status" />
+    </resultMap>
 
     <sql id="selectTestBase">
         select t.test_id, t.test_name, t.creator_id, creator.nick_name creator_name,
@@ -303,6 +316,13 @@
         select threshold_id, test_id, metric_code, min_value, max_value, unit, severity, del_flag
         from t_test_warning_threshold where test_id = #{testId} and del_flag = 0 order by threshold_id
     </select>
+    <select id="selectTestReceptionIntervals" parameterType="Long" resultMap="TTestReceptionIntervalResultMap">
+        select interval_id, test_id, binding_id, source_type, source_id, source_channel_code,
+               action_at_utc, start_boundary_at_utc, end_boundary_at_utc, boundary_note, interval_status
+        from t_test_reception_interval
+        where test_id = #{testId}
+        order by source_type, source_id, source_channel_code, start_boundary_at_utc, interval_id
+    </select>
     <select id="selectTestResults" parameterType="TTestResult" resultMap="TTestResultResultMap">
         select result.*, battery.test_id from t_test_result result
         join t_test_battery battery on battery.relation_id = result.relation_id
@@ -426,6 +446,19 @@
         insert into t_collector_channel_lease(collector_id, channel_code, test_id, binding_id, acquired_by, acquired_at_utc)
         values(#{collectorId}, #{channelCode}, #{testId}, #{bindingId}, #{acquiredBy}, #{acquiredAtUtc})
     </insert>
+    <insert id="insertReceptionInterval">
+        insert into t_test_reception_interval
+        (test_id, binding_id, source_type, source_id, source_channel_code,
+         action_at_utc, start_boundary_at_utc, boundary_note, interval_status)
+        values(#{testId}, #{bindingId}, #{sourceType}, #{sourceId}, #{sourceChannelCode},
+               #{actionAtUtc}, #{actionAtUtc}, #{boundaryNote}, 'OPEN')
+    </insert>
+    <update id="closeOpenReceptionIntervals">
+        update t_test_reception_interval
+        set end_boundary_at_utc = #{endBoundaryAtUtc},
+            boundary_note = #{boundaryNote}, interval_status = 'CLOSED'
+        where test_id = #{testId} and interval_status = 'OPEN'
+    </update>
     <delete id="deleteChannelLeasesByTestId">
         delete from t_channel_lease where test_id = #{testId}
     </delete>

--
Gitblit v1.7.1