From 279e85aa564967fb187ae9687b3c81a5eee5e74d Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期一, 07 九月 2026 16:56:08 +0800
Subject: [PATCH] 实现拼接统计与报表保存
---
battery-business/src/main/java/com/battery/business/report/SpliceReportRequest.java | 11 +++++
ruoyi-admin/src/main/resources/db/migration-final/V17__splice_reports.sql | 7 +++
battery-business/src/main/java/com/battery/business/report/SpliceReportService.java | 28 ++++++++++++++
battery-business/src/main/java/com/battery/business/report/SpliceReportRepository.java | 7 +++
ruoyi-admin/src/main/java/com/battery/business/web/SpliceReportController.java | 18 +++++++++
5 files changed, 71 insertions(+), 0 deletions(-)
diff --git a/battery-business/src/main/java/com/battery/business/report/SpliceReportRepository.java b/battery-business/src/main/java/com/battery/business/report/SpliceReportRepository.java
new file mode 100644
index 0000000..9f09ee5
--- /dev/null
+++ b/battery-business/src/main/java/com/battery/business/report/SpliceReportRepository.java
@@ -0,0 +1,7 @@
+package com.battery.business.report;
+import javax.sql.DataSource;
+import org.springframework.stereotype.Repository;
+@Repository
+public class SpliceReportRepository extends ReportVersionRepository {
+ public SpliceReportRepository(DataSource dataSource) { super(dataSource, "splice", "拼接"); }
+}
diff --git a/battery-business/src/main/java/com/battery/business/report/SpliceReportRequest.java b/battery-business/src/main/java/com/battery/business/report/SpliceReportRequest.java
new file mode 100644
index 0000000..2d553fa
--- /dev/null
+++ b/battery-business/src/main/java/com/battery/business/report/SpliceReportRequest.java
@@ -0,0 +1,11 @@
+package com.battery.business.report;
+import java.util.List;
+import java.util.Map;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.*;
+public record SpliceReportRequest(@NotBlank @Size(max=100) String name, @Size(max=2000) String description,
+ @NotNull @Size(min=2) List<@NotNull @Valid Selection> selections, @NotNull Map<String,Object> settings) {
+ public record Selection(@NotNull @Positive Long testId, @NotNull @Positive Long testBatteryId) {}
+ public record Edit(@Positive int baseVersionNo, @NotBlank @Size(max=100) String name, String description,
+ @NotNull Map<String,Object> settings, @NotBlank @Size(max=500) String changeSummary) {}
+}
diff --git a/battery-business/src/main/java/com/battery/business/report/SpliceReportService.java b/battery-business/src/main/java/com/battery/business/report/SpliceReportService.java
new file mode 100644
index 0000000..32e265c
--- /dev/null
+++ b/battery-business/src/main/java/com/battery/business/report/SpliceReportService.java
@@ -0,0 +1,28 @@
+package com.battery.business.report;
+
+import java.math.BigDecimal; import java.math.MathContext; import java.util.*;
+import com.alibaba.fastjson2.*; import com.battery.business.domain.*; import com.battery.business.service.*;
+import com.ruoyi.common.exception.ServiceException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;
+
+/** 拼接报表按来源顺序平移循环号;统计只读取检测完成测试的当前有效循环记录。 */
+@Service public class SpliceReportService {
+ private final ITTestService tests; private final ITestDataQueryService data; private final SpliceReportRepository reports;
+ public SpliceReportService(ITTestService t, ITestDataQueryService d, SpliceReportRepository r){tests=t;data=d;reports=r;}
+ public record Candidate(SpliceReportRequest.Selection selection,String name,String type,long groupId,String group,String batteryNo,long startedAt){}
+ public record Matrix(List<Map<String,Object>> segments,List<Integer> cycleNos){}
+ public List<Candidate> sources(){ TTest q=new TTest(); q.setTestStatus("COMPLETED"); List<Candidate> out=new ArrayList<>(); for(TTest s:tests.selectTestList(q)){TTest t=tests.selectTestById(s.getTestId()); for(TTestBattery b:t.getBatteries()) out.add(candidate(t,b));} return out; }
+ public Matrix preview(List<SpliceReportRequest.Selection> selections){
+ if(selections==null||selections.size()<2||new HashSet<>(selections).size()!=selections.size()) throw new ServiceException("请选择至少两个不重复的检测完成测试电池");
+ List<Map<String,Object>> segments=new ArrayList<>(); Set<Long> groups=new HashSet<>(); Set<String> batteries=new HashSet<>(); int offset=0; long group=0;
+ for(var sel:selections){TTest t=tests.selectTestById(sel.testId()); if(t==null||!"COMPLETED".equals(t.getTestStatus())) throw new ServiceException("仅可选择检测完成测试"); TTestBattery b=t.getBatteries().stream().filter(x->sel.testBatteryId().equals(x.getTestBatteryId())).findFirst().orElseThrow(()->new ServiceException("电池不属于所选测试")); if(group==0)group=b.getGroupId(); if(group!=b.getGroupId()) throw new ServiceException("拼接测试必须属于同一电池组"); groups.add(b.getGroupId()); batteries.add(b.getBatteryNo());
+ List<Map<String,Object>> rows=data.cycles(sel.testId()).stream().filter(r->Objects.equals(r.get("batteryNo"),b.getBatteryNo())).toList(); List<Integer> nos=rows.stream().map(this::cycle).filter(x->x>0).distinct().sorted().toList(); Map<String,Object> seg=new LinkedHashMap<>(); seg.put("source",candidate(t,b)); seg.put("offset",offset); seg.put("cycles",rows); seg.put("originalCycleNos",nos); seg.put("statistics",statistics(rows,nos)); segments.add(seg); offset += nos.size(); }
+ if(batteries.size()==0) throw new ServiceException("未找到共同参与电池"); return new Matrix(segments,java.util.stream.IntStream.range(1,offset+1).boxed().toList()); }
+ private Map<String,Object> statistics(List<Map<String,Object>> rows,List<Integer> nos){Map<String,Object> out=new LinkedHashMap<>(); for(int no:nos){var m=rows.stream().filter(r->cycle(r)==no).toList(); Map<String,Object> v=new LinkedHashMap<>(); if(m.size()==1){Map<String,Object> row=m.get(0); for(String k:List.of("chargeEnergy","dischargeEnergy","chargeCapacity","dischargeCapacity")) if(row.get(k)!=null)v.put(k,row.get(k)); Number c=num(v.get("chargeEnergy")),d=num(v.get("dischargeEnergy")); if(c!=null&&d!=null&&c.doubleValue()>0)v.put("energyEfficiency",BigDecimal.valueOf(d.doubleValue()).multiply(BigDecimal.valueOf(100)).divide(BigDecimal.valueOf(c.doubleValue()),MathContext.DECIMAL128));} out.put(Integer.toString(no),v);} return out;}
+ private List<Map<String,Object>> dataStepRows(Map<String,Object> row){return List.of();}
+ private Number num(Object x){return x instanceof Number?n((Number)x):null;} private Number n(Number x){return x;}
+ @Transactional public Map<String,Object> create(SpliceReportRequest r,long uid,String user){Matrix m=preview(r.selections()); Map<String,Object> snap=new LinkedHashMap<>(); snap.put("name",r.name().trim());snap.put("description",r.description());snap.put("selections",r.selections());snap.put("segments",m.segments());snap.put("cycleNos",m.cycleNos());snap.put("settings",r.settings()); return get(reports.insert(r.name().trim(),JSON.toJSONString(snap),uid,user,System.currentTimeMillis()),null);}
+ public Map<String,Object> get(long id,Integer version){var v=reports.get(id,version);Map<String,Object> x=JSON.parseObject(v.snapshotJson());x.put("id",id);x.put("versionNo",v.versionNo());x.put("creator",v.report().creator());x.put("createdAt",v.report().createdAt());x.put("operatorName",v.operatorName());x.put("versionCreatedAt",v.versionCreatedAt());x.put("changeSummary",v.changeSummary());return x;}
+ @Transactional public Map<String,Object> edit(long id,SpliceReportRequest.Edit r,long uid,String user){reports.lock(id);var v=reports.get(id);if(v.versionNo()!=r.baseVersionNo())throw new ServiceException("报表已有新版本,请重新打开后编辑");var x=JSON.parseObject(v.snapshotJson());x.put("name",r.name().trim());x.put("description",r.description());x.put("settings",r.settings());reports.append(id,r.baseVersionNo()+1,r.name().trim(),x.toJSONString(),uid,user,r.changeSummary().trim());return get(id,null);}
+ @Transactional public Map<String,Object> refresh(long id,int base,String summary,long uid,String user){reports.lock(id);var v=reports.get(id);if(v.versionNo()!=base)throw new ServiceException("报表已有新版本,请重新打开后编辑");var x=JSON.parseObject(v.snapshotJson());var selections=JSON.parseArray(x.getString("selections"),SpliceReportRequest.Selection.class);Matrix m=preview(selections);x.put("segments",m.segments());x.put("cycleNos",m.cycleNos());reports.append(id,base+1,x.getString("name"),x.toJSONString(),uid,user,"按最新来源生成:"+summary);return get(id,null);}
+ public void delete(long id,long uid,String user){reports.lock(id);reports.delete(id,uid,user);} private Candidate candidate(TTest t,TTestBattery b){return new Candidate(new SpliceReportRequest.Selection(t.getTestId(),b.getTestBatteryId()),t.getTestName(),t.getTestTypeName(),b.getGroupId(),b.getGroupName(),b.getBatteryNo(),t.getStartedAtUtc()==null?0:t.getStartedAtUtc().getTime());} private int cycle(Map<String,Object> r){return r.get("cycleNo") instanceof Number n?n.intValue():0;}
+}
diff --git a/ruoyi-admin/src/main/java/com/battery/business/web/SpliceReportController.java b/ruoyi-admin/src/main/java/com/battery/business/web/SpliceReportController.java
new file mode 100644
index 0000000..67c434a
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/battery/business/web/SpliceReportController.java
@@ -0,0 +1,18 @@
+package com.battery.business.web;
+import java.util.List; import com.battery.business.report.*; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*;
+/** 拼接报表正式 API,所有来源与统计由后端校验并冻结。 */
+@RestController @RequestMapping("/system/splice-report") public class SpliceReportController extends BaseController {
+ private final SpliceReportService service; private final SpliceReportRepository reports; public SpliceReportController(SpliceReportService s,SpliceReportRepository r){service=s;reports=r;}
+ @GetMapping("/sources") @PreAuthorize("@ss.hasPermi('system:splice-report:add')") public AjaxResult sources(){return success(service.sources());}
+ @PostMapping("/preview") @PreAuthorize("@ss.hasPermi('system:splice-report:add')") public AjaxResult preview(@RequestBody List<SpliceReportRequest.Selection> s){return success(service.preview(s));}
+ @GetMapping("/list") @PreAuthorize("@ss.hasPermi('system:splice-report:query')") public AjaxResult list(){return success().put("rows",reports.list());}
+ @PostMapping @PreAuthorize("@ss.hasPermi('system:splice-report:add')") public AjaxResult create(@Valid @RequestBody SpliceReportRequest r){return success(service.create(r,getUserId(),getUsername()));}
+ @GetMapping("/{id}") @PreAuthorize("@ss.hasPermi('system:splice-report:query')") public AjaxResult get(@PathVariable long id){return success(service.get(id,null));}
+ @GetMapping("/{id}/editing") @PreAuthorize("@ss.hasPermi('system:splice-report:edit')") public AjaxResult editing(@PathVariable long id){return success(service.get(id,null));}
+ @GetMapping("/{id}/versions") @PreAuthorize("@ss.hasPermi('system:splice-report:query')") public AjaxResult history(@PathVariable long id){return success(reports.history(id));}
+ @GetMapping("/{id}/versions/{version}") @PreAuthorize("@ss.hasPermi('system:splice-report:query')") public AjaxResult version(@PathVariable long id,@PathVariable int version){return success(service.get(id,version));}
+ @GetMapping("/{id}/versions/{version}/print") @PreAuthorize("@ss.hasPermi('system:splice-report:export')") public AjaxResult print(@PathVariable long id,@PathVariable int version){return success(service.get(id,version));}
+ @PostMapping("/{id}/versions") @PreAuthorize("@ss.hasPermi('system:splice-report:edit')") public AjaxResult edit(@PathVariable long id,@Valid @RequestBody SpliceReportRequest.Edit r){return success(service.edit(id,r,getUserId(),getUsername()));}
+ @PostMapping("/{id}/refresh-source") @PreAuthorize("@ss.hasPermi('system:splice-report:edit')") public AjaxResult refresh(@PathVariable long id,@RequestBody java.util.Map<String,Object> body){return success(service.refresh(id,((Number)body.get("baseVersionNo")).intValue(),String.valueOf(body.getOrDefault("changeSummary","")),getUserId(),getUsername()));}
+ @DeleteMapping("/{id}") @PreAuthorize("@ss.hasPermi('system:splice-report:remove')") public AjaxResult delete(@PathVariable long id){service.delete(id,getUserId(),getUsername());return success();}
+}
diff --git a/ruoyi-admin/src/main/resources/db/migration-final/V17__splice_reports.sql b/ruoyi-admin/src/main/resources/db/migration-final/V17__splice_reports.sql
new file mode 100644
index 0000000..3917a55
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/db/migration-final/V17__splice_reports.sql
@@ -0,0 +1,7 @@
+CREATE TABLE t_splice_report LIKE t_compare_report;
+ALTER TABLE t_splice_report COMMENT='拼接报表身份';
+CREATE TABLE t_splice_report_version LIKE t_compare_report_version;
+ALTER TABLE t_splice_report_version COMMENT='拼接报表独立版本';
+INSERT INTO sys_menu(menu_name,parent_id,order_num,path,component,route_name,menu_type,visible,status,perms,icon,create_by,create_time,remark)
+SELECT '拼接报表导出', parent_id, 5, '#', 'F','0','0','0','analysis:splice-report:export','#','admin',UTC_TIMESTAMP(3),'拼接报表导出权限'
+FROM sys_menu WHERE perms='analysis:splice-report:list' LIMIT 1;
--
Gitblit v1.7.1