From 561ff691ae9b58dc534ebe625d63471d040d3160 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期日, 06 九月 2026 00:17:28 +0800
Subject: [PATCH] 实现三类历史数据原子导入与共同修订批次

---
 ruoyi-admin/src/test/java/com/battery/business/web/ConsoleSingleSheetImportDatabaseTest.java |  102 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 101 insertions(+), 1 deletions(-)

diff --git a/ruoyi-admin/src/test/java/com/battery/business/web/ConsoleSingleSheetImportDatabaseTest.java b/ruoyi-admin/src/test/java/com/battery/business/web/ConsoleSingleSheetImportDatabaseTest.java
index cce4e80..d2b0686 100644
--- a/ruoyi-admin/src/test/java/com/battery/business/web/ConsoleSingleSheetImportDatabaseTest.java
+++ b/ruoyi-admin/src/test/java/com/battery/business/web/ConsoleSingleSheetImportDatabaseTest.java
@@ -31,12 +31,13 @@
     private TTestController controller;
     private TTestMapper mapper;
     private ITestConsoleDataService service;
+    private JdbcTemplate jdbc;
 
     @BeforeEach void setUp() throws Exception
     {
         var source = new UnpooledDataSource("org.h2.Driver", "jdbc:h2:mem:" + UUID.randomUUID() + ";MODE=MySQL;DATABASE_TO_LOWER=TRUE", "sa", "");
         keepAlive = source.getConnection();
-        var jdbc = new JdbcTemplate(source);
+        jdbc = new JdbcTemplate(source);
         jdbc.execute("create table sys_user(user_id bigint, nick_name varchar(64), del_flag varchar(1))");
         jdbc.execute("create table t_test_type(test_type_id bigint, test_type_name varchar(64), del_flag int)");
         jdbc.execute("create table t_test(test_id bigint primary key, test_name varchar(100), creator_id bigint, principal_id bigint, test_type_id bigint, test_purpose varchar(100), test_status varchar(20), reception_status varchar(20), monitor_timeout_minutes int, started_at_utc timestamp, reception_boundary_at_utc timestamp, ended_at_utc timestamp, del_flag int default 0, create_by varchar(64), create_time timestamp, update_by varchar(64), update_time timestamp, remark varchar(100), logic_step_mappings_json varchar(100), console_detail_json varchar(100))");
@@ -50,6 +51,9 @@
         jdbc.execute("create table t_detection_cabinet(cabinet_id bigint, adapter_code varchar(64))");
         jdbc.execute("insert into t_detection_cabinet values(1,'adapter-a')");
         jdbc.execute("create table t_test_data_revision_action(action_id bigint auto_increment primary key, test_id bigint, data_type varchar(20), operation_type varchar(20), scope_summary varchar(200), identity_summary json, before_content_json json, changes_summary json, reason varchar(200), operator_id bigint, operator_name varchar(64), created_at_utc timestamp)");
+        jdbc.execute("alter table t_test_data_revision_action add constraint chk_data_revision_type check (data_type in ('REALTIME','STEP','CYCLE'))");
+        org.springframework.jdbc.datasource.init.ScriptUtils.executeSqlScript(keepAlive,
+                new ClassPathResource("db/migration-final/V9__workbook_import_revision.sql"));
         String common = "test_id bigint not null, test_battery_id bigint not null, binding_id bigint not null, adapter_code varchar(64) not null, source_identity varchar(256) not null, source_record_id varchar(128), server_received_at_utc timestamp(3), cycle_no int, content_sha256 binary(32) not null, extension_measurements_json varchar(2000)";
         String metrics = "step_no int, step_name varchar(191), step_type varchar(64), voltage_v decimal(30,12), current_a decimal(30,12), capacity_ah decimal(30,12), energy_wh decimal(30,12) check (energy_wh <> 999)";
         jdbc.execute("create table t_test_realtime_record(record_id bigint auto_increment primary key," + common + "," + metrics + ", cabinet_devtype int, cabinet_devid int, cabinet_subdevid int, cabinet_chlid int, cabinet_sequence_id bigint, cabinet_device_time_utc timestamp(3), cabinet_device_time_raw varchar(64), cabinet_adapter_timezone_id varchar(64), capture_slot_utc timestamp(3) not null, temperature decimal(30,12), positive_potential decimal(30,12), negative_potential decimal(30,12), collector_id bigint, collector_channel_code varchar(100), collector_device_time_utc timestamp(3), unique(test_id,test_battery_id,capture_slot_utc))");
@@ -102,6 +106,102 @@
         assertTrue(service.selectActions(new TTestDataRevisionAction()).isEmpty());
     }
 
+    @Test void workbookImportsThreeSheetsWithOneRevisionAndSkipsRetry()
+    {
+        var sheets = Map.of("REALTIME", List.of(row(), row()), "STEP", List.of(row()), "CYCLE", List.of(row()));
+        var result = submitWorkbook(sheets);
+        assertEquals(3, result.get("importedCount"));
+        assertEquals(1, result.get("skippedCount"));
+        assertNotNull(result.get("actionId"));
+        for (String type : List.of("REALTIME", "STEP", "CYCLE"))
+        {
+            assertEquals(1, mapper.selectImportDataRecords(1L, type).size());
+            var query = new TTestDataRevisionAction(); query.setDataType(type);
+            assertEquals(1, service.selectActions(query).size());
+        }
+        var actions = service.selectActions(new TTestDataRevisionAction());
+        assertEquals(1, actions.size());
+        assertEquals("ALL", actions.get(0).getDataType());
+        assertEquals("三页验收", actions.get(0).getReason());
+        var retry = submitWorkbook(sheets);
+        assertEquals(0, retry.get("importedCount"));
+        assertEquals(4, retry.get("skippedCount"));
+        assertNull(retry.get("actionId"));
+        assertEquals(1, service.selectActions(new TTestDataRevisionAction()).size());
+    }
+
+    private com.ruoyi.common.core.domain.AjaxResult submitWorkbook(Map<String, List<Map<String, Object>>> sheets)
+    {
+        return controller.dataAction(1L, new TTestController.ConsoleDataActionRequest("ALL", "IMPORT", "三 Sheet 导入", "[]",
+                JSON.toJSONString(sheets), "三页验收", List.of()));
+    }
+
+    @Test void workbookLateDatabaseFailureRollsBackAllSheetsAndRevision()
+    {
+        var cycle = row(); cycle.put("dischargeEnergy", 999);
+        jdbc.execute("alter table t_test_cycle_record add check (discharge_energy_wh <> 999)");
+        assertThrows(RuntimeException.class, () -> submitWorkbook(Map.of("REALTIME", List.of(row()), "STEP", List.of(row()), "CYCLE", List.of(cycle))));
+        assertWorkbookEmpty();
+    }
+
+    @Test void workbookMissingEmptyInvalidOrConflictingSheetNeverWritesAnySheet()
+    {
+        var sheets = new LinkedHashMap<String, List<Map<String, Object>>>();
+        sheets.put("REALTIME", List.of(row())); sheets.put("STEP", List.of(row()));
+        assertThrows(RuntimeException.class, () -> submitWorkbook(sheets));
+        assertWorkbookEmpty();
+        sheets.put("CYCLE", List.of());
+        assertThrows(RuntimeException.class, () -> submitWorkbook(sheets));
+        assertWorkbookEmpty();
+        var invalid = row(); invalid.put("cycleNo", 0); invalid.put("sourceRow", 23);
+        sheets.put("CYCLE", List.of(invalid));
+        assertTrue(assertThrows(RuntimeException.class, () -> submitWorkbook(sheets)).getMessage().contains("循环工作表,第23行"));
+        assertWorkbookEmpty();
+        var conflict = row(); conflict.put("dischargeCapacity", 88);
+        sheets.put("CYCLE", List.of(row(), conflict));
+        assertTrue(assertThrows(RuntimeException.class, () -> submitWorkbook(sheets)).getMessage().contains("冲突拒绝"));
+        assertWorkbookEmpty();
+    }
+
+    @Test void workbookRejectsDifferentBatterySetsEvenWhenBothBatteriesBelongToTest()
+    {
+        jdbc.execute("insert into t_test_battery values(6,1,'BAT-2',0)");
+        jdbc.execute("insert into t_test_channel_binding values(7,6,11)");
+        var other = row(); other.put("batteryNo", "BAT-2");
+        var sheets = Map.of("REALTIME", List.of(row()), "STEP", List.of(row()), "CYCLE", List.of(other));
+        assertTrue(assertThrows(RuntimeException.class, () -> submitWorkbook(sheets)).getMessage().contains("电池号集合不一致"));
+        assertWorkbookEmpty();
+        other.put("batteryNo", "OTHER");
+        assertTrue(assertThrows(RuntimeException.class, () -> submitWorkbook(sheets)).getMessage().contains("不属于当前测试"));
+        assertWorkbookEmpty();
+    }
+
+    @Test void workbookConflictWithExistingCyclePreservesFactsAndRejectsOtherSheets()
+    {
+        submit("CYCLE", List.of(row()));
+        var conflict = row(); conflict.put("dischargeCapacity", 88);
+        assertThrows(RuntimeException.class, () -> submitWorkbook(Map.of("REALTIME", List.of(row()), "STEP", List.of(row()), "CYCLE", List.of(conflict))));
+        assertTrue(mapper.selectImportDataRecords(1L, "REALTIME").isEmpty());
+        assertTrue(mapper.selectImportDataRecords(1L, "STEP").isEmpty());
+        assertEquals(1, mapper.selectImportDataRecords(1L, "CYCLE").size());
+        assertEquals(1, service.selectActions(new TTestDataRevisionAction()).size());
+    }
+
+    @Test void workbookCannotImportBeforeTestCompletionOrWithoutReason()
+    {
+        var sheets = Map.of("REALTIME", List.of(row()), "STEP", List.of(row()), "CYCLE", List.of(row()));
+        assertThrows(RuntimeException.class, () -> controller.dataAction(1L, new TTestController.ConsoleDataActionRequest("ALL", "IMPORT", "三 Sheet 导入", "[]", JSON.toJSONString(sheets), " ", List.of())));
+        jdbc.update("update t_test set test_status='RUNNING', reception_status='RECEIVING' where test_id=1");
+        assertThrows(RuntimeException.class, () -> submitWorkbook(sheets));
+        assertWorkbookEmpty();
+    }
+
+    private void assertWorkbookEmpty()
+    {
+        for (String type : List.of("REALTIME", "STEP", "CYCLE")) assertTrue(mapper.selectImportDataRecords(1L, type).isEmpty());
+        assertTrue(service.selectActions(new TTestDataRevisionAction()).isEmpty());
+    }
+
     @Test void differentRecordsInTheSameSecondPreserveDeviceTime()
     {
         var next = row(); next.put("recordNo", "18"); next.put("sequenceId", 18); next.put("stepNo", 2);

--
Gitblit v1.7.1