yeg
2026-09-09 b837cc1f88e6fa14442f66eecb59c45464dd7bb3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package com.battery.business.report;
 
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.math.BigDecimal;
import java.util.*;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.battery.business.domain.*;
import com.battery.business.service.*;
import com.battery.device.api.StepDirectionResolver.Direction;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.h2.jdbcx.JdbcDataSource;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
 
class FormationRealReportTest
{
    private final ObjectMapper mapper = new ObjectMapper();
    private final ITTestService tests = mock(ITTestService.class);
    private final ITestDataQueryService data = mock(ITestDataQueryService.class);
    private final FormationReportService service;
 
    FormationRealReportTest()
    {
        var db = new JdbcDataSource();
        db.setURL("jdbc:h2:mem:formation_" + UUID.randomUUID() + ";MODE=MySQL;DB_CLOSE_DELAY=-1");
        var jdbc = new JdbcTemplate(db);
        jdbc.execute("CREATE TABLE t_formation_report(report_id BIGINT AUTO_INCREMENT PRIMARY KEY,report_name VARCHAR(100),creator_id BIGINT,creator_name VARCHAR(100),created_at_utc TIMESTAMP)");
        jdbc.execute("CREATE TABLE t_formation_report_version(report_id BIGINT,version_no INT,snapshot_json CLOB,operator_id BIGINT,operator_name VARCHAR(100),created_at_utc TIMESTAMP,change_summary VARCHAR(500),PRIMARY KEY(report_id,version_no))");
        service = new FormationReportService(tests, data, new FormationReportRepository(db), mock(ITBatteryGroupFieldService.class),
                type -> "cc".equals(type) ? Direction.CHARGE : "dc".equals(type) ? Direction.DISCHARGE : Direction.UNKNOWN);
        var battery = new TTestBattery(); battery.setTestBatteryId(11L); battery.setBatteryNo("电池1"); battery.setGroupId(1L); battery.setGroupName("组1");
        battery.setFields(new ArrayList<>());
        Map.of("H", "2", "Z", "3", "A", "4", "F", "5", "B", "6").forEach((name, value) -> {
            var field = new CurrentBatteryField(); field.setFieldName(name); field.setFieldValue(value); battery.getFields().add(field);
        });
        var test = new TTest(); test.setTestId(1L); test.setTestName("化成甲"); test.setTestStatus("COMPLETED"); test.setTestTypeName("化成"); test.setBatteries(List.of(battery));
        when(tests.selectTestById(1L)).thenReturn(test);
        when(data.records(1L)).thenReturn(List.of()); when(data.cycles(1L)).thenReturn(List.of());
        when(data.steps(1L)).thenReturn(List.of(step(1, "cc", "4.1", "0.1"), step(2, "cc", "4.2", "0.2"), step(3, "dc", "1", "0.25")));
    }
 
    private Map<String, Object> step(int no, String type, String voltage, String capacity)
    {
        var row = new LinkedHashMap<String, Object>(); row.put("batteryNo", "电池1"); row.put("cycleNo", 1); row.put("stepNo", no); row.put("stepType", type);
        row.put("endVoltage", new BigDecimal(voltage)); row.put("capacity", capacity == null ? null : new BigDecimal(capacity)); return row;
    }
 
    private JSONObject request()
    {
        return JSON.parseObject("""
            {"name":"化成真实报表","description":"","selections":[{"testId":1,"testBatteryId":11}],
             "settings":{"fields":[{"key":"manual","scope":"cycle","kind":"manual","displayName":"数字","unit":""}],
              "values":{"1|11|1|manual":"2"},"chargeStepSelections":{}}}
            """);
    }
 
    private JSONObject create(JSONObject request) throws Exception
    { return JSON.parseObject(JSON.toJSONString(service.create(mapper.readValue(request.toJSONString(), FormationReportRequest.class), 1, "操作员"))); }
 
    @Test
    void freezesFormulaInputsAndLastDischargeCapacityWithoutDependingOnDisplayedFields() throws Exception
    {
        var saved = create(request());
        var values = saved.getJSONObject("calculatedValues");
        assertNotNull(values, "公式结果必须随版本保存");
        assertEquals(720, values.getBigDecimal("1|11|0|C").intValueExact());
        assertEquals(240, values.getBigDecimal("1|11|1|chargePerGram").intValueExact());
        assertEquals(288, values.getBigDecimal("1|11|1|chargePerGramTotal").intValueExact());
        assertEquals(250, values.getBigDecimal("1|11|1|capacity1v").intValueExact());
        assertEquals(50, values.getBigDecimal("1|11|1|capacity1vf").intValueExact());
        assertEquals(5, saved.getJSONArray("items").getJSONObject(0).getJSONArray("businessFields").size());
        assertNull(saved.getJSONArray("items").getJSONObject(0).get("stepCurves"), "新快照不应重复保存逐工步采样");
        assertEquals(1, service.list().get(0).versionNo());
        clearInvocations(tests, data);
        assertEquals(values, JSON.parseObject(JSON.toJSONString(service.get(saved.getLongValue("id"), null))).getJSONObject("calculatedValues"));
        verifyNoInteractions(tests, data);
    }
 
    @Test
    void reportFieldEditsRecalculateWithoutReadingOrChangingDeletedSource() throws Exception
    {
        var request = request();
        request.getJSONObject("settings").getJSONArray("fields").add(JSON.parseObject("{\"key\":\"F\",\"scope\":\"detail\",\"kind\":\"battery\",\"displayName\":\"F\",\"unit\":\"\"}"));
        var first = create(request); long id = first.getLongValue("id");
        clearInvocations(tests, data);
        when(tests.selectTestById(1L)).thenThrow(new IllegalStateException("来源已删除"));
        request.remove("selections"); request.put("baseVersionNo", 1); request.put("changeSummary", "修改报表 F");
        request.getJSONObject("settings").getJSONObject("values").put("1|11|0|F", "10");
        var second = JSON.parseObject(JSON.toJSONString(service.edit(id, mapper.readValue(request.toJSONString(), FormationReportRequest.Edit.class), 1, "操作员")));
        assertEquals(1440, second.getJSONObject("calculatedValues").getBigDecimal("1|11|0|C").intValueExact());
        assertEquals(480, second.getJSONObject("calculatedValues").getBigDecimal("1|11|1|chargePerGram").intValueExact());
        assertEquals(25, second.getJSONObject("calculatedValues").getBigDecimal("1|11|1|capacity1vf").intValueExact());
        assertEquals(720, JSON.parseObject(JSON.toJSONString(service.get(id, 1))).getJSONObject("calculatedValues").getBigDecimal("1|11|0|C").intValueExact());
        verifyNoInteractions(tests, data);
    }
 
    @Test
    void missingLastDischargeDoesNotBorrowEarlierCapacityAndManualMustBeNonnegative() throws Exception
    {
        when(data.steps(1L)).thenReturn(List.of(step(1,"dc","1","0.25"),step(2,"dc","1",null)));
        var saved=create(request());
        assertNull(saved.getJSONObject("calculatedValues").get("1|11|1|capacity1v"));
        var bad=request();bad.getJSONObject("settings").getJSONObject("values").put("1|11|1|manual","-1");
        assertThrows(com.ruoyi.common.exception.ServiceException.class,()->create(bad));
        bad.getJSONObject("settings").getJSONObject("values").put("1|11|1|manual","电量");
        assertThrows(com.ruoyi.common.exception.ServiceException.class,()->create(bad));
    }
 
    @Test
    void importedStepNamesSupplyDirectionWithoutInventingProtocolTypes() throws Exception
    {
        var discharge = step(1, null, "1", "0.25"); discharge.put("stepName", "恒流放电");
        var rest = step(2, null, "1", null); rest.put("stepName", "搁置");
        var charge = step(3, null, "4.2", "0.2"); charge.put("stepName", "恒流充电");
        var ambiguous = step(4, "pulse", "99", "99"); ambiguous.put("stepName", "恒流充电");
        when(data.steps(1L)).thenReturn(List.of(discharge, rest, charge, ambiguous));
        var saved = create(request());
        var choices = saved.getJSONObject("settings").getJSONObject("curveStepSelections").getJSONObject("1|11|1");
        assertEquals(List.of(3), choices.getList("charge", Integer.class));
        assertEquals(List.of(1), choices.getList("discharge", Integer.class));
        assertEquals(250, saved.getJSONObject("calculatedValues").getBigDecimal("1|11|1|capacity1v").intValueExact());
        var steps = saved.getJSONArray("items").getJSONObject(0).getJSONObject("chargeSteps").getJSONArray("1");
        assertEquals("REST", steps.getJSONObject(1).getString("direction"));
        assertEquals("UNKNOWN", steps.getJSONObject(3).getString("direction"));
    }
 
    @Test
    void explicitSourceRefreshKeepsManualValuesAndHistoricalClassification() throws Exception
    {
        var imported = step(1, null, "1", "0.25");
        when(data.steps(1L)).thenReturn(List.of(imported));
        var first = create(request());
        long id = first.getLongValue("id");
        assertNull(first.getJSONObject("calculatedValues").get("1|11|1|capacity1v"));
        imported.put("stepName", "恒流放电");
        var refreshed = JSON.parseObject(JSON.toJSONString(service.refresh(id, new CompareReportEditRequest.Refresh(1, "重新读取实际工步"), 1, "操作员")));
        assertEquals(250, refreshed.getJSONObject("calculatedValues").getBigDecimal("1|11|1|capacity1v").intValueExact());
        assertEquals("2", refreshed.getJSONObject("settings").getJSONObject("values").getString("1|11|1|manual"));
        var historical = JSON.parseObject(JSON.toJSONString(service.get(id, 1)));
        assertNull(historical.getJSONObject("calculatedValues").get("1|11|1|capacity1v"));
        assertEquals("UNKNOWN", historical.getJSONArray("items").getJSONObject(0).getJSONObject("chargeSteps").getJSONArray("1").getJSONObject(0).getString("direction"));
    }
 
    @Test
    void automaticDirectionSelectionExcludesUnknownAndKeepsAllRealChargeSteps() throws Exception
    {
        when(data.steps(1L)).thenReturn(List.of(step(1,"cc","4.1","0.1"),step(2,"unknown","99","99"),step(3,"cc","4.2","0.2"),step(4,"dc","1","0.25")));
        var request=request();
        request.getJSONObject("settings").put("manualChargeSteps",Map.of("1|11|1",List.of(2)));
        request.getJSONObject("settings").put("curveStepSelections",Map.of("1|11|1",Map.of("charge",List.of(1,2),"discharge",List.of(4))));
        var saved=create(request);
        var selected=saved.getJSONObject("settings").getJSONObject("curveStepSelections").getJSONObject("1|11|1");
        assertEquals(List.of(1,3),selected.getList("charge",Integer.class));
        assertEquals(List.of(4),selected.getList("discharge",Integer.class));
        assertEquals(250,saved.getJSONObject("calculatedValues").getBigDecimal("1|11|1|capacity1v").intValueExact());
    }
}