feat(report): 优化报告生成工具中的文档处理功能

- 在Docx4jUtil中添加enableUpdateFieldsOnOpen方法,确保Word打开时自动刷新页码等域
- 移除ItemAnchorAssembler中不必要的导入包,精简代码结构
- 重构谐波数据处理逻辑,添加谐波衍生值计算功能
- 实现固定占位符替换机制,支持谐波电压和功率表的动态填充
- 优化表格结构分离逻辑,使用空段落而非可见标题分隔多表
- 增强错误范围处理,移除单位后缀并优化谐波电流计算逻辑
- 添加全面的单元测试验证各种谐波检测场景的功能正确性
This commit is contained in:
2026-07-07 22:49:46 +08:00
parent 75b19eb6ab
commit 7a8071c9d7
14 changed files with 1322 additions and 72 deletions

View File

@@ -58,7 +58,21 @@ public enum ItemReportKeyEnum {
POWER_FACTOR("powerFactor", "P表功率因数实测值数据侧暂无留空"),
POS_SEQ("posSeq", "不平衡表:基波正序(数据侧暂无,留空)"),
NEG_SEQ("negSeq", "不平衡表:基波负序(数据侧暂无,留空)"),
ZERO_SEQ("zeroSeq", "不平衡表:基波零序(数据侧暂无,留空)");
ZERO_SEQ("zeroSeq", "不平衡表:基波零序(数据侧暂无,留空)"),
BASE_CURRENT_A("baseCurrentA", "HI表A相基波电流幅值用于计算谐波电流含有率"),
BASE_CURRENT_B("baseCurrentB", "HI表B相基波电流幅值用于计算谐波电流含有率"),
BASE_CURRENT_C("baseCurrentC", "HI表C相基波电流幅值用于计算谐波电流含有率"),
STANDARD_RATE("standardRate", "HI表标准源输出含有率"),
TEST_RATE_A("testRateA", "HI表A相谐波电流含有率"),
TEST_RATE_B("testRateB", "HI表B相谐波电流含有率"),
TEST_RATE_C("testRateC", "HI表C相谐波电流含有率"),
ERROR_RATE_A("errorRateA", "HI表A相谐波电流含有率误差"),
ERROR_RATE_B("errorRateB", "HI表B相谐波电流含有率误差"),
ERROR_RATE_C("errorRateC", "HI表C相谐波电流含有率误差"),
STANDARD_THD("standardThd", "HV/HI表标准源总谐波畸变率"),
THD_A("thdA", "HV/HI表A相总谐波畸变率"),
THD_B("thdB", "HV/HI表B相总谐波畸变率"),
THD_C("thdC", "HV/HI表C相总谐波畸变率");
private String key;

View File

@@ -18,6 +18,7 @@ public enum PowerIndexEnum {
V("V", "电压"),
I("I", "电流"),
P("P", "功率"),
ANGLE("Angle", "基波相位"),
IMBV("IMBV", "负序电压不平衡度"),
IMBA("IMBA", "负序电流不平衡度"),
F("F", "短时电压闪变"),

View File

@@ -865,6 +865,7 @@ public class PqReportServiceImpl extends ServiceImpl<PqReportMapper, PqReport> i
Docx4jUtil.addWatermarkToDocument(baseModelDocument, "非正式");
}
Docx4jUtil.cleanBlankPagesAndRedundantPageBreaks(baseModelDocument);
Docx4jUtil.enableUpdateFieldsOnOpen(baseModelDocument);
baseModelDocument.save(new File(dirPath.concat(File.separator).concat(fileName)));
this.updateDevAndPlanState(devId, devReportParam.getPlanId());
} catch (NoSuchFileException e) {
@@ -945,6 +946,7 @@ public class PqReportServiceImpl extends ServiceImpl<PqReportMapper, PqReport> i
String dirPath = pathConfig.getReportPath().concat(File.separator).concat(FilePathSanitizer.toSafeFileName(devType.getName()));
// 确保目录存在
ensureDirectoryExists(dirPath);
Docx4jUtil.enableUpdateFieldsOnOpen(baseModelDocument);
baseModelDocument.save(new File(dirPath.concat(File.separator).concat(pqDevVO.getCreateId()).concat(ReportConstant.DOCX)));
this.updateDevAndPlanState(devId, devReportParam.getPlanId());
} catch (NoSuchFileException e) {
@@ -1271,11 +1273,11 @@ public class PqReportServiceImpl extends ServiceImpl<PqReportMapper, PqReport> i
// 检测脚本明细按 scriptCode 分组
List<PqScriptDtlDataVO> pqScriptDtlsList = pqScriptDtlsService.getScriptDtlsDataList(devReportParam.getScriptId());
Map<String, List<PqScriptDtlDataVO>> scriptMap = pqScriptDtlsList.stream()
.collect(Collectors.groupingBy(PqScriptDtlDataVO::getScriptCode, LinkedHashMap::new, Collectors.toList()));
.collect(Collectors.groupingBy(item -> itemAnchorGroupKey(item.getScriptCode()), LinkedHashMap::new, Collectors.toList()));
// detail 模板池按 H5 分组
List<Docx4jUtil.HeadingContent> headingContents = Docx4jUtil.extractHeading5Contents(detailDocumentPart.getContent(), detailDocumentPart);
Map<String, List<Docx4jUtil.HeadingContent>> contentMap = headingContents.stream()
.collect(Collectors.groupingBy(Docx4jUtil.HeadingContent::getHeadingText, Collectors.toList()));
.collect(Collectors.groupingBy(item -> itemAnchorGroupKey(item.getHeadingText()), LinkedHashMap::new, Collectors.toList()));
int devChns = pqDevVO.getDevChns() == null ? 1 : pqDevVO.getDevChns();
Set<String> hiddenCodes = new LinkedHashSet<>();
for (BookmarkUtil.BookmarkInfo info : bookmarks) {
@@ -1344,17 +1346,26 @@ public class PqReportServiceImpl extends ServiceImpl<PqReportMapper, PqReport> i
ItemAnchorSectionUtil.stripItemAnchorBookmarks(baseDocumentPart);
}
private static String itemAnchorGroupKey(String value) {
return value == null ? "" : value.trim().toUpperCase(Locale.ROOT);
}
/**
* 新型模板的回路标题(仅多回路时被调用):"测量回路{n}"n 从 1 起。
* 与老路径 getLineTitle 的差异(有意):不带 "n." 前缀、单回路由调用方直接省略。
*/
private P buildItemLineTitle(Map<String, List<Docx4jUtil.HeadingContent>> contentMap, int lineIndex, ObjectFactory factory) {
int lineNo = lineIndex + 1;
if (CollUtil.isNotEmpty(contentMap.get(PowerIndexEnum.LINE_TITLE.getKey()))) {
return Docx4jUtil.createTitle(factory, 2, "测量回路" + lineNo, "SimSun", 30, true);
}
P titleParagraph = factory.createP();
Docx4jUtil.createTitle(factory, titleParagraph, "测量回路" + lineNo, 28, true);
PPr pPr = factory.createPPr();
PPrBase.Spacing spacing = factory.createPPrBaseSpacing();
spacing.setBefore(BigInteger.valueOf(60));
spacing.setAfter(BigInteger.valueOf(20));
spacing.setLine(BigInteger.valueOf(240));
spacing.setLineRule(STLineSpacingRule.AUTO);
pPr.setSpacing(spacing);
titleParagraph.setPPr(pPr);
Docx4jUtil.createTitle(factory, titleParagraph, "测量回路" + lineNo, 24, true);
return titleParagraph;
}

View File

@@ -6,15 +6,9 @@ import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.report.pojo.enums.ItemReportKeyEnum;
import com.njcn.gather.report.pojo.result.SingleTestResult;
import com.njcn.gather.tools.report.util.Docx4jUtil;
import org.docx4j.wml.BooleanDefaultTrue;
import org.docx4j.wml.HpsMeasure;
import org.docx4j.wml.Jc;
import org.docx4j.wml.JcEnumeration;
import org.docx4j.wml.ObjectFactory;
import org.docx4j.wml.P;
import org.docx4j.wml.PPr;
import org.docx4j.wml.R;
import org.docx4j.wml.RFonts;
import org.docx4j.wml.RPr;
import org.docx4j.wml.Tbl;
import org.docx4j.wml.Tc;
@@ -25,11 +19,11 @@ import org.docx4j.wml.TrPr;
import javax.xml.bind.JAXBElement;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
@@ -63,11 +57,15 @@ public final class ItemAnchorAssembler {
if (templateTbl == null || CollUtil.isEmpty(results) || CollUtil.isEmpty(tableKeys)) {
return out;
}
int tableNo = 0;
for (SingleTestResult result : results) {
List<Map<String, String>> rows = flattenRows(result, scriptCode);
if (rows.isEmpty()) {
continue;
}
tableNo++;
applyHarmonicDerivedValues(rows, scriptCode);
Map<String, String> fixedPlaceholders = buildHarmonicFixedPlaceholders(rows, scriptCode);
JAXBElement<Tbl> copied;
try {
copied = Docx4jUtil.deepCopyTbl(templateTbl);
@@ -75,6 +73,8 @@ public final class ItemAnchorAssembler {
throw new RuntimeException("指标锚点模板表深拷贝失败: " + scriptCode, e);
}
Tbl tbl = copied.getValue();
replaceFixedPlaceholders(tbl, fixedPlaceholders);
applyHarmonicPowerHeader(tbl, scriptCode, tableNo, rows);
List<Object> trs = tbl.getContent();
// 模板规范约定:最后一行是 key 行(不走 judgeTableCross新路径自带定位依据
// 从尾部找最后一个真正的 Tr——Word 重存可能把 bookmarkStart/End 漏成 w:tbl 直接子节点(本仓 _GoBack 先例)
@@ -121,14 +121,16 @@ public final class ItemAnchorAssembler {
out.add(copied);
}
// 多表(谐波类多 scriptIndex背靠背插入时OOXML 相邻 w:tbl 会被 Word 合并渲染成一张大表;
// 给每张表补"测点{n}"标题段分隔(客户源文档中多测点谐波表本就是带标题的独立表)。单表不加,输出不变
// 只插入空段落做结构分隔。测点号属于 HP 表内标题单元格,不能作为表外可见段落。
if (out.size() > 1) {
List<Object> withCaptions = new ArrayList<>(out.size() * 2);
List<Object> withSeparators = new ArrayList<>(out.size() * 2 - 1);
for (int i = 0; i < out.size(); i++) {
withCaptions.add(createPointCaption(factory, i + 1));
withCaptions.add(out.get(i));
withSeparators.add(out.get(i));
if (i < out.size() - 1) {
withSeparators.add(factory.createP());
}
}
return withCaptions;
return withSeparators;
}
return out;
}
@@ -182,37 +184,6 @@ public final class ItemAnchorAssembler {
return null;
}
/** "测点{n}"表标题段:仿宋 9 号加粗居中,与客户源文档"谐波功率检验结果测点n"标题段同款式 */
private static P createPointCaption(ObjectFactory factory, int pointNo) {
P p = factory.createP();
PPr pPr = factory.createPPr();
Jc jc = factory.createJc();
jc.setVal(JcEnumeration.CENTER);
pPr.setJc(jc);
p.setPPr(pPr);
R run = factory.createR();
RPr rPr = factory.createRPr();
RFonts fonts = factory.createRFonts();
fonts.setAscii("仿宋");
fonts.setHAnsi("仿宋");
fonts.setEastAsia("仿宋");
fonts.setCs("仿宋");
rPr.setRFonts(fonts);
HpsMeasure sz = factory.createHpsMeasure();
sz.setVal(BigInteger.valueOf(18));
rPr.setSz(sz);
rPr.setSzCs(sz);
BooleanDefaultTrue bold = factory.createBooleanDefaultTrue();
rPr.setB(bold);
rPr.setBCs(bold);
run.setRPr(rPr);
Text text = factory.createText();
text.setValue("测点" + pointNo);
run.getContent().add(text);
p.getContent().add(run);
return p;
}
/**
* 把 SingleTestResult 的三层嵌套 detail 摊平成行列表,并做新模板专属的行级补充:
* errorScope 注入HV/HI/HP 的标准限值列、time 缺失时"测点{n}"兜底F/IMBV/IMBA、P 有功特例。
@@ -239,7 +210,7 @@ public final class ItemAnchorAssembler {
for (Map<String, String> raw : errorEntry.getValue()) {
seq++;
Map<String, String> row = new LinkedHashMap<>(raw);
row.putIfAbsent(ItemReportKeyEnum.ERROR_SCOPE.getKey(), errorScope);
applyErrorScope(row, scriptCode, errorScope);
if (StrUtil.isBlank(row.get(ItemReportKeyEnum.TIME.getKey()))) {
row.put(ItemReportKeyEnum.TIME.getKey(), "测点" + seq);
}
@@ -254,6 +225,23 @@ public final class ItemAnchorAssembler {
return out;
}
private static void applyErrorScope(Map<String, String> row, String scriptCode, String errorScope) {
String key = ItemReportKeyEnum.ERROR_SCOPE.getKey();
if ("HV".equalsIgnoreCase(scriptCode) || "HP".equalsIgnoreCase(scriptCode)) {
String value = StrUtil.isBlank(row.get(key)) ? errorScope : row.get(key);
row.put(key, stripTrailingUnit(value));
return;
}
row.putIfAbsent(key, errorScope);
}
private static String stripTrailingUnit(String value) {
if (StrUtil.isBlank(value)) {
return value;
}
return value.trim().replaceFirst("\\s*(?:(?:[%%‰°℃]|[a-zA-Z]+|[\\u4e00-\\u9fa5]+)\\s*)+$", "");
}
/**
* P有功功率特例activePower = 三相实测之和;设置类与无功/视在/功率因数数据侧暂无,留空。
* spec §6 #2 处置。
@@ -279,6 +267,256 @@ public final class ItemAnchorAssembler {
}
}
private static void applyHarmonicDerivedValues(List<Map<String, String>> rows, String scriptCode) {
if (!"HI".equalsIgnoreCase(scriptCode)) {
return;
}
for (Map<String, String> row : rows) {
String scriptStandardRate = row.get(ItemReportKeyEnum.STANDARD_RATE.getKey());
String standardRate = parseNumber(scriptStandardRate) == null ? "" : scriptStandardRate;
String testRateA = rate(row.get(ItemReportKeyEnum.TEST_A.getKey()), row.get(ItemReportKeyEnum.BASE_CURRENT_A.getKey()));
String testRateB = rate(row.get(ItemReportKeyEnum.TEST_B.getKey()), row.get(ItemReportKeyEnum.BASE_CURRENT_B.getKey()));
String testRateC = rate(row.get(ItemReportKeyEnum.TEST_C.getKey()), row.get(ItemReportKeyEnum.BASE_CURRENT_C.getKey()));
String errorRateA = difference(testRateA, standardRate);
String errorRateB = difference(testRateB, standardRate);
String errorRateC = difference(testRateC, standardRate);
row.put(ItemReportKeyEnum.STANDARD_RATE.getKey(), standardRate);
row.put(ItemReportKeyEnum.TEST_RATE_A.getKey(), testRateA);
row.put(ItemReportKeyEnum.TEST_RATE_B.getKey(), testRateB);
row.put(ItemReportKeyEnum.TEST_RATE_C.getKey(), testRateC);
row.put(ItemReportKeyEnum.ERROR_RATE_A.getKey(), errorRateA);
row.put(ItemReportKeyEnum.ERROR_RATE_B.getKey(), errorRateB);
row.put(ItemReportKeyEnum.ERROR_RATE_C.getKey(), errorRateC);
row.put(ItemReportKeyEnum.ERROR_SCOPE.getKey(), harmonicCurrentErrorScope(standardRate));
}
}
private static String harmonicCurrentErrorScope(String standardRateText) {
Double standardRate = parseNumber(standardRateText);
if (standardRate == null) {
return "";
}
double limit = standardRate >= 3D ? standardRate * 0.05D : 0.15D;
return "±" + format4(limit);
}
private static Map<String, String> buildHarmonicFixedPlaceholders(List<Map<String, String>> rows, String scriptCode) {
Map<String, String> placeholders = new LinkedHashMap<>();
if ("HV".equalsIgnoreCase(scriptCode)) {
placeholders.put(ItemReportKeyEnum.STANDARD_THD.getKey(), thd(rows, ItemReportKeyEnum.STANDARD.getKey()));
placeholders.put(ItemReportKeyEnum.THD_A.getKey(), thd(rows, ItemReportKeyEnum.TEST_A.getKey()));
placeholders.put(ItemReportKeyEnum.THD_B.getKey(), thd(rows, ItemReportKeyEnum.TEST_B.getKey()));
placeholders.put(ItemReportKeyEnum.THD_C.getKey(), thd(rows, ItemReportKeyEnum.TEST_C.getKey()));
} else if ("HI".equalsIgnoreCase(scriptCode)) {
placeholders.put(ItemReportKeyEnum.STANDARD_THD.getKey(), thd(rows, ItemReportKeyEnum.STANDARD_RATE.getKey()));
placeholders.put(ItemReportKeyEnum.THD_A.getKey(), thd(rows, ItemReportKeyEnum.TEST_RATE_A.getKey()));
placeholders.put(ItemReportKeyEnum.THD_B.getKey(), thd(rows, ItemReportKeyEnum.TEST_RATE_B.getKey()));
placeholders.put(ItemReportKeyEnum.THD_C.getKey(), thd(rows, ItemReportKeyEnum.TEST_RATE_C.getKey()));
}
return placeholders;
}
private static void applyHarmonicPowerHeader(Tbl tbl, String scriptCode, int pointNo, List<Map<String, String>> rows) {
if (!"HP".equalsIgnoreCase(scriptCode)) {
return;
}
Tr firstRow = findFirstRow(tbl.getContent());
if (firstRow == null) {
return;
}
Tc titleCell = cellAt(firstRow, 0);
if (titleCell == null) {
return;
}
String title = Docx4jUtil.getTextFromCell(titleCell);
String baseTitle = StrUtil.blankToDefault(title, StrUtil.EMPTY)
.replaceFirst("(测点\\s*\\d+$", "");
setCellText(titleCell, baseTitle + "(测点" + pointNo + "");
fillNextCellByLabel(tbl, "基波电压", firstDataValue(rows, ItemReportKeyEnum.STANDARD_V.getKey()));
fillNextCellByLabel(tbl, "基波电流", firstDataValue(rows, ItemReportKeyEnum.STANDARD_I.getKey()));
}
private static String firstDataValue(List<Map<String, String>> rows, String key) {
if (CollUtil.isEmpty(rows)) {
return "";
}
for (Map<String, String> row : rows) {
String value = row.get(key);
if (StrUtil.isNotBlank(value) && !"/".equals(value.trim())) {
return value;
}
}
return "";
}
private static void fillNextCellByLabel(Tbl tbl, String label, String value) {
if (StrUtil.isBlank(value) || tbl == null || tbl.getContent() == null) {
return;
}
for (Object o : tbl.getContent()) {
Object v = o instanceof JAXBElement ? ((JAXBElement<?>) o).getValue() : o;
if (v instanceof Tr && fillNextCellByLabel((Tr) v, label, value)) {
return;
}
}
}
private static boolean fillNextCellByLabel(Tr row, String label, String value) {
if (StrUtil.isBlank(value) || row == null || row.getContent() == null) {
return false;
}
List<Object> cells = row.getContent();
for (int i = 0; i < cells.size() - 1; i++) {
Tc labelCell = cellAt(row, i);
if (labelCell != null && Docx4jUtil.getTextFromCell(labelCell).contains(label)) {
Tc valueCell = cellAt(row, i + 1);
if (valueCell != null) {
setCellText(valueCell, value);
}
return true;
}
}
return false;
}
private static Tr findFirstRow(List<Object> content) {
for (Object o : content) {
Object v = o instanceof JAXBElement ? ((JAXBElement<?>) o).getValue() : o;
if (v instanceof Tr) {
return (Tr) v;
}
}
return null;
}
private static Tc cellAt(Tr row, int index) {
if (row == null || row.getContent() == null || row.getContent().size() <= index) {
return null;
}
Object cellObj = row.getContent().get(index);
Object value = cellObj instanceof JAXBElement ? ((JAXBElement<?>) cellObj).getValue() : cellObj;
return value instanceof Tc ? (Tc) value : null;
}
private static String rate(String numeratorText, String denominatorText) {
Double numerator = parseNumber(numeratorText);
Double denominator = parseNumber(denominatorText);
if (numerator == null || denominator == null || denominator == 0D) {
return "";
}
return format4(numerator / denominator * 100D);
}
private static String difference(String minuendText, String subtrahendText) {
Double minuend = parseNumber(minuendText);
Double subtrahend = parseNumber(subtrahendText);
if (minuend == null || subtrahend == null) {
return "";
}
return format4(minuend - subtrahend);
}
private static String thd(List<Map<String, String>> rows, String key) {
double sum = 0D;
boolean hasValue = false;
for (Map<String, String> row : rows) {
Double value = parseNumber(row.get(key));
if (value != null) {
sum += value * value;
hasValue = true;
}
}
return hasValue ? format4(Math.sqrt(sum)) : "";
}
private static Double parseNumber(String text) {
if (StrUtil.isBlank(text) || "/".equals(text.trim()) || "--".equals(text.trim())) {
return null;
}
try {
return Double.parseDouble(text.trim());
} catch (NumberFormatException ignored) {
return null;
}
}
private static String format4(double value) {
return String.format(Locale.ROOT, "%.4f", value);
}
private static void replaceFixedPlaceholders(Tbl tbl, Map<String, String> values) {
if (values.isEmpty()) {
return;
}
for (Object rowObj : tbl.getContent()) {
Object rowValue = rowObj instanceof JAXBElement ? ((JAXBElement<?>) rowObj).getValue() : rowObj;
if (!(rowValue instanceof Tr)) {
continue;
}
for (Object cellObj : ((Tr) rowValue).getContent()) {
Object cellValue = cellObj instanceof JAXBElement ? ((JAXBElement<?>) cellObj).getValue() : cellObj;
if (cellValue instanceof Tc) {
Tc cell = (Tc) cellValue;
String replacement = values.get(Docx4jUtil.getTextFromCell(cell));
if (replacement != null) {
setCellText(cell, replacement);
}
}
}
}
}
private static void setCellText(Tc cell, String value) {
P firstParagraph = null;
for (Object content : cell.getContent()) {
if (content instanceof P) {
firstParagraph = (P) content;
break;
}
}
if (firstParagraph == null) {
firstParagraph = new P();
cell.getContent().add(firstParagraph);
}
boolean written = false;
for (Object paragraphObj : cell.getContent()) {
if (!(paragraphObj instanceof P)) {
continue;
}
P paragraph = (P) paragraphObj;
for (Object runObj : paragraph.getContent()) {
if (!(runObj instanceof R)) {
continue;
}
R run = (R) runObj;
for (Object textObj : run.getContent()) {
Text text = unwrapText(textObj);
if (text != null) {
text.setValue(written ? "" : value);
written = true;
}
}
}
}
if (!written) {
R run = new R();
Text text = new Text();
text.setValue(value);
run.getContent().add(text);
firstParagraph.getContent().add(run);
}
}
private static Text unwrapText(Object textObj) {
if (textObj instanceof Text) {
return (Text) textObj;
}
if (textObj instanceof JAXBElement && ((JAXBElement<?>) textObj).getValue() instanceof Text) {
return (Text) ((JAXBElement<?>) textObj).getValue();
}
return null;
}
@SuppressWarnings("unchecked")
private static JAXBElement<Tbl> findFirstTable(Docx4jUtil.HeadingContent template) {
if (template == null || CollUtil.isEmpty(template.getSubContent())) {

View File

@@ -64,6 +64,7 @@ import com.njcn.gather.result.pojo.param.ResultParam;
import com.njcn.gather.result.pojo.vo.*;
import com.njcn.gather.result.service.DataCheckAsyncNotifier;
import com.njcn.gather.result.service.IResultService;
import com.njcn.gather.result.util.AngleResultAssembler;
import com.njcn.gather.result.util.PowerResultAssembler;
import com.njcn.gather.result.util.UnbalanceResultAssembler;
import com.njcn.gather.script.mapper.PqScriptMapper;
@@ -1469,6 +1470,22 @@ public class ResultServiceImpl implements IResultService {
finalContent.put(affectName, errorList);
return;
}
if (PowerIndexEnum.ANGLE.getKey().equalsIgnoreCase(scriptCode)) {
SingleNonHarmParam param = new SingleNonHarmParam(planCode, devId, lineNo, valueTypeList, indexList);
List<SimAndDigNonHarmonicResult> angleResults = simAndDigNonHarmonicService.queryBySortListWithAdTypeCode(param);
List<Map<String, String>> keyFillMapList = AngleResultAssembler.assembleRows(angleResults);
if (CollUtil.isEmpty(keyFillMapList)) {
log.warn("生成基波相位类表格数据跳过结果表数据为空planCode={}, devId={}, lineNo={}, scriptCode={}, scriptIndexList={}",
planCode, devId, lineNo, scriptCode, indexList);
return;
}
Map<String, List<Map<String, String>>> errorScoperMap = keyFillMapList.stream()
.collect(Collectors.groupingBy(map -> map.get(ItemReportKeyEnum.ERROR_SCOPE.getKey()), LinkedHashMap::new, Collectors.toList()));
List<Map<String, List<Map<String, String>>>> errorList = new ArrayList<>();
errorList.add(errorScoperMap);
finalContent.put(affectName, errorList);
return;
}
if (PowerIndexEnum.IMBV.getKey().equalsIgnoreCase(scriptCode)
|| PowerIndexEnum.IMBA.getKey().equalsIgnoreCase(scriptCode)) {
SingleNonHarmParam param = new SingleNonHarmParam(planCode, devId, lineNo, valueTypeList, indexList);
@@ -1552,6 +1569,8 @@ public class ResultServiceImpl implements IResultService {
int timeInt = (int) timeDouble;
// 填充结果数据
if (fillThreePhaseData(singleResult, timeInt, keyFillMap, scriptCode)) {
fillHarmonicScriptStandard(keyFillMap, scriptCode, dtlsList);
fillHarmonicPowerSourceSettings(keyFillMap, scriptCode, scriptDtlDataVOList);
keyFillMapList.add(keyFillMap);
}
});
@@ -1754,9 +1773,100 @@ public class ResultServiceImpl implements IResultService {
keyFillMap.put(ItemReportKeyEnum.RESULT.getKey(), result);
errorScope = dealErrorScope(errorScope).concat(unit);
keyFillMap.put(ItemReportKeyEnum.ERROR_SCOPE.getKey(), errorScope);
if (PowerIndexEnum.HI.getKey().equalsIgnoreCase(scriptCode)) {
fillBaseCurrentData(singleResult, keyFillMap);
}
return true;
}
/**
* HI 新模板明细显示谐波电流含有率,装配期需要同一条谐波结果中的三相基波电流作为分母。
*/
private void fillBaseCurrentData(Object singleResult, Map<String, String> keyFillMap) {
putBaseCurrent(singleResult, PowerConstant.PHASE_A, ItemReportKeyEnum.BASE_CURRENT_A.getKey(), keyFillMap);
putBaseCurrent(singleResult, PowerConstant.PHASE_B, ItemReportKeyEnum.BASE_CURRENT_B.getKey(), keyFillMap);
putBaseCurrent(singleResult, PowerConstant.PHASE_C, ItemReportKeyEnum.BASE_CURRENT_C.getKey(), keyFillMap);
}
private void putBaseCurrent(Object singleResult, String phase, String testKey, Map<String, String> keyFillMap) {
DetectionData base = getResultData(singleResult, 1, phase);
keyFillMap.put(testKey, formatBaseCurrent(base == null ? null : base.getData()));
}
private String formatBaseCurrent(Double value) {
String formatted = value == null ? "" : PubUtils.doubleRoundStr(4, value);
return formatted == null ? "" : formatted;
}
/**
* HI 的标准源输出值在云南模板中展示含有率,优先沿用脚本下发的百分比值。
*/
private void fillHarmonicScriptStandard(Map<String, String> keyFillMap, String scriptCode, List<PqScriptCheckData> checkDataList) {
if (!PowerIndexEnum.HI.getKey().equalsIgnoreCase(scriptCode) || CollUtil.isEmpty(checkDataList)) {
return;
}
if (StrUtil.isNotBlank(keyFillMap.get(ItemReportKeyEnum.STANDARD_RATE.getKey()))) {
return;
}
PqScriptCheckData standard = firstCheckValue(checkDataList);
if (standard == null || standard.getValue() == null) {
return;
}
String value = PubUtils.doubleRoundStr(4, standard.getValue());
if (value != null) {
keyFillMap.put(ItemReportKeyEnum.STANDARD_RATE.getKey(), value);
}
}
private PqScriptCheckData firstCheckValue(List<PqScriptCheckData> checkDataList) {
for (String phase : Arrays.asList(PowerConstant.PHASE_A, PowerConstant.PHASE_B, PowerConstant.PHASE_C)) {
for (PqScriptCheckData data : checkDataList) {
if (phase.equalsIgnoreCase(data.getPhase()) && data.getValue() != null) {
return data;
}
}
}
return checkDataList.stream().filter(data -> data.getValue() != null).findFirst().orElse(null);
}
/**
* HP 新模板表头需要基波电压/基波电流设置值,来源是同一 scriptIndex 的 VOL/CUR 脚本明细。
*/
private void fillHarmonicPowerSourceSettings(Map<String, String> keyFillMap, String scriptCode,
List<? extends PqScriptDtls> scriptDetails) {
if (!PowerIndexEnum.HP.getKey().equalsIgnoreCase(scriptCode) || CollUtil.isEmpty(scriptDetails)) {
return;
}
putScriptSetting(keyFillMap, ItemReportKeyEnum.STANDARD_V.getKey(), firstScriptSetting(scriptDetails, "VOL"));
putScriptSetting(keyFillMap, ItemReportKeyEnum.STANDARD_I.getKey(), firstScriptSetting(scriptDetails, "CUR"));
}
private void putScriptSetting(Map<String, String> keyFillMap, String key, PqScriptDtls detail) {
if (detail == null || detail.getValue() == null || StrUtil.isNotBlank(keyFillMap.get(key))) {
return;
}
String value = PubUtils.doubleRoundStr(4, detail.getValue());
if (StrUtil.isNotBlank(value)) {
keyFillMap.put(key, value);
}
}
private PqScriptDtls firstScriptSetting(List<? extends PqScriptDtls> scriptDetails, String valueType) {
for (String phase : Arrays.asList(PowerConstant.PHASE_A, PowerConstant.PHASE_B, PowerConstant.PHASE_C)) {
for (PqScriptDtls detail : scriptDetails) {
if (valueType.equalsIgnoreCase(detail.getValueType())
&& phase.equalsIgnoreCase(detail.getPhase())
&& detail.getValue() != null) {
return detail;
}
}
}
return scriptDetails.stream()
.filter(detail -> valueType.equalsIgnoreCase(detail.getValueType()) && detail.getValue() != null)
.findFirst()
.orElse(null);
}
/**
* 针对浙江杭州处理脚本输出细节todo... 需要更改更合理的方式,现在算是写死的了部分
* 目前浙江这边就是(间)谐波电压/电流

View File

@@ -0,0 +1,191 @@
package com.njcn.gather.result.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.njcn.gather.detection.pojo.enums.DetectionCodeEnum;
import com.njcn.gather.detection.pojo.vo.DetectionData;
import com.njcn.gather.report.pojo.enums.ItemReportKeyEnum;
import com.njcn.gather.storage.pojo.po.SimAndDigNonHarmonicResult;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
/**
* 基波相位报告结果行装配:云南模板以电压相角为 0 度基准,取 I1A 的 A/B/C 三相电流相角填表。
*/
public final class AngleResultAssembler {
private AngleResultAssembler() {
}
public static List<Map<String, String>> assembleRows(List<SimAndDigNonHarmonicResult> results) {
List<Map<String, String>> rows = new ArrayList<>();
if (CollUtil.isEmpty(results)) {
return rows;
}
Map<Integer, List<SimAndDigNonHarmonicResult>> bySort = results.stream()
.filter(result -> result.getSort() != null)
.collect(Collectors.groupingBy(SimAndDigNonHarmonicResult::getSort, TreeMap::new, Collectors.toList()));
for (List<SimAndDigNonHarmonicResult> sameSortResults : bySort.values()) {
SimAndDigNonHarmonicResult currentAngle = firstCurrentAngle(sameSortResults);
if (currentAngle == null) {
continue;
}
DetectionData[] phases = phases(currentAngle);
DetectionData primary = firstPresent(phases);
if (primary == null) {
continue;
}
Map<String, String> row = defaultAngleRow();
row.put(ItemReportKeyEnum.STANDARD.getKey(), valueOrSlash(primary.getResultData()));
putPhaseDetails(row, phases);
row.put(ItemReportKeyEnum.ERROR_SCOPE.getKey(), errorScope(primary));
row.put(ItemReportKeyEnum.RESULT.getKey(), overallResult(phases));
rows.add(row);
}
return rows;
}
private static SimAndDigNonHarmonicResult firstCurrentAngle(List<SimAndDigNonHarmonicResult> results) {
if (CollUtil.isEmpty(results)) {
return null;
}
for (SimAndDigNonHarmonicResult result : results) {
if (DetectionCodeEnum.I1A.getCode().equalsIgnoreCase(result.getAdType())) {
return result;
}
}
return null;
}
private static Map<String, String> defaultAngleRow() {
Map<String, String> row = new LinkedHashMap<>();
for (ItemReportKeyEnum key : Arrays.asList(
ItemReportKeyEnum.STANDARD,
ItemReportKeyEnum.TEST_A,
ItemReportKeyEnum.TEST_B,
ItemReportKeyEnum.TEST_C,
ItemReportKeyEnum.ERROR_A,
ItemReportKeyEnum.ERROR_B,
ItemReportKeyEnum.ERROR_C,
ItemReportKeyEnum.ERROR_SCOPE,
ItemReportKeyEnum.RESULT,
ItemReportKeyEnum.RESULT_A,
ItemReportKeyEnum.RESULT_B,
ItemReportKeyEnum.RESULT_C)) {
row.put(key.getKey(), "/");
}
return row;
}
private static DetectionData[] phases(SimAndDigNonHarmonicResult result) {
return new DetectionData[]{
parse(result.getAValue()),
parse(result.getBValue()),
parse(result.getCValue())
};
}
private static void putPhaseDetails(Map<String, String> row, DetectionData[] phases) {
List<String> tests = Arrays.asList(ItemReportKeyEnum.TEST_A.getKey(),
ItemReportKeyEnum.TEST_B.getKey(), ItemReportKeyEnum.TEST_C.getKey());
List<String> errors = Arrays.asList(ItemReportKeyEnum.ERROR_A.getKey(),
ItemReportKeyEnum.ERROR_B.getKey(), ItemReportKeyEnum.ERROR_C.getKey());
List<String> results = Arrays.asList(ItemReportKeyEnum.RESULT_A.getKey(),
ItemReportKeyEnum.RESULT_B.getKey(), ItemReportKeyEnum.RESULT_C.getKey());
for (int i = 0; i < phases.length; i++) {
DetectionData phase = phases[i];
row.put(tests.get(i), phase == null ? "/" : valueOrSlash(phase.getData()));
row.put(errors.get(i), phase == null || phase.getErrorData() == null ? "/" : format(phase.getErrorData()));
row.put(results.get(i), phase == null ? "/" : resultText(phase.getIsData()));
}
}
private static DetectionData firstPresent(DetectionData[] values) {
for (DetectionData value : values) {
if (value != null) {
return value;
}
}
return null;
}
private static DetectionData parse(String value) {
if (StrUtil.isBlank(value)) {
return null;
}
return JSONUtil.toBean(value, DetectionData.class);
}
private static String overallResult(DetectionData[] phases) {
boolean hasResult = false;
for (DetectionData phase : phases) {
if (phase == null || phase.getIsData() == null) {
continue;
}
hasResult = true;
if (phase.getIsData() == 2) {
return "不合格";
}
}
return hasResult ? "合格" : "/";
}
private static String resultText(Integer isData) {
if (isData == null) {
return "/";
}
if (isData == 1) {
return "合格";
}
if (isData == 2) {
return "不合格";
}
return "/";
}
private static String errorScope(DetectionData primary) {
String scope = dealErrorScope(primary.getRadius());
if (StrUtil.isNotBlank(primary.getUnit()) && !"/".equals(scope)) {
return scope + primary.getUnit();
}
return scope;
}
private static String dealErrorScope(String errorScope) {
if (StrUtil.isBlank(errorScope)) {
return "/";
}
if (errorScope.contains("~")) {
String[] split = errorScope.split("~");
String begin = split[0];
if (begin.startsWith("-")) {
begin = begin.substring(1);
}
if (split.length > 1 && split[1].equalsIgnoreCase(begin)) {
return "±" + begin;
}
}
return errorScope;
}
private static String valueOrSlash(Double value) {
return value == null ? "/" : format(value);
}
private static String format(Double value) {
return value == null ? "/" : BigDecimal.valueOf(value).setScale(4, RoundingMode.HALF_UP).toPlainString();
}
private static String format(BigDecimal value) {
return value == null ? "/" : value.setScale(4, RoundingMode.HALF_UP).toPlainString();
}
}

View File

@@ -58,7 +58,6 @@ public final class UnbalanceResultAssembler {
private static Map<String, String> defaultRow() {
Map<String, String> row = new LinkedHashMap<>();
for (ItemReportKeyEnum key : Arrays.asList(
ItemReportKeyEnum.TIME,
ItemReportKeyEnum.STANDARD,
ItemReportKeyEnum.TEST,
ItemReportKeyEnum.ERROR,

View File

@@ -1,10 +1,24 @@
package com.njcn.gather.report.service.impl;
import com.njcn.gather.report.pojo.enums.PowerIndexEnum;
import com.njcn.gather.tools.report.util.Docx4jUtil;
import org.docx4j.wml.ObjectFactory;
import org.docx4j.wml.P;
import org.docx4j.wml.R;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.time.LocalDate;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
public class PqReportServiceImplTest {
@@ -22,4 +36,39 @@ public class PqReportServiceImplTest {
public void formatNumberChineseDateUsesChineseDateSeparators() {
assertEquals("2021年1月1日", PqReportServiceImpl.formatNumberChineseDate(LocalDate.of(2021, 1, 1)));
}
@Test
public void buildItemLineTitleCreatesNormalSmallParagraph() throws Exception {
PqReportServiceImpl service = newReportService();
ObjectFactory factory = new ObjectFactory();
Docx4jUtil.HeadingContent lineTitleTemplate = new Docx4jUtil.HeadingContent();
lineTitleTemplate.setHeadingText(PowerIndexEnum.LINE_TITLE.getKey());
Map<String, List<Docx4jUtil.HeadingContent>> contentMap = new HashMap<>();
contentMap.put(PowerIndexEnum.LINE_TITLE.getKey(), Collections.singletonList(lineTitleTemplate));
Method method = PqReportServiceImpl.class.getDeclaredMethod("buildItemLineTitle", Map.class, int.class, ObjectFactory.class);
method.setAccessible(true);
P paragraph = (P) method.invoke(service, contentMap, 0, factory);
assertEquals("测量回路1", Docx4jUtil.getTextFromP(paragraph));
assertNull(paragraph.getPPr().getOutlineLvl());
R run = (R) paragraph.getContent().get(0);
assertNotNull(run.getRPr());
assertEquals(BigInteger.valueOf(24), run.getRPr().getSz().getVal());
}
@Test
public void itemAnchorGroupKeyNormalizesAngleScriptCode() throws Exception {
Method method = PqReportServiceImpl.class.getDeclaredMethod("itemAnchorGroupKey", String.class);
method.setAccessible(true);
assertEquals("ANGLE", method.invoke(null, "Angle"));
assertEquals("ANGLE", method.invoke(null, "ANGLE"));
}
private PqReportServiceImpl newReportService() throws Exception {
Constructor<?> constructor = PqReportServiceImpl.class.getDeclaredConstructors()[0];
constructor.setAccessible(true);
return (PqReportServiceImpl) constructor.newInstance(new Object[constructor.getParameterCount()]);
}
}

View File

@@ -0,0 +1,26 @@
package com.njcn.gather.report.util;
import com.njcn.gather.tools.report.util.Docx4jUtil;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.DocumentSettingsPart;
import org.docx4j.wml.CTSettings;
import org.junit.Test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class Docx4jUtilUpdateFieldsTest {
@Test
public void enableUpdateFieldsOnOpenSetsWordSettingsFlag() throws Exception {
WordprocessingMLPackage wordPackage = WordprocessingMLPackage.createPackage();
Docx4jUtil.enableUpdateFieldsOnOpen(wordPackage);
DocumentSettingsPart settingsPart = wordPackage.getMainDocumentPart().getDocumentSettingsPart();
assertNotNull(settingsPart);
CTSettings settings = settingsPart.getContents();
assertNotNull(settings.getUpdateFields());
assertTrue(settings.getUpdateFields().isVal());
}
}

View File

@@ -75,10 +75,10 @@ public class ItemAnchorAssemblerTest {
private Docx4jUtil.HeadingContent hvTemplate(String... keys) {
Tbl tbl = f.createTbl();
tbl.getContent().add(row("谐波电压检验结果", "基波电压V", "57.7000"));
tbl.getContent().add(row("总谐波畸变率(%", "", "", "", ""));
tbl.getContent().add(row("总谐波畸变率(%", "", "standardThd", "", ""));
tbl.getContent().add(row("测点", "标准源输出值", "被检仪器测量值", "误差", "标准限值", "检验结论"));
tbl.getContent().add(row("", "", "A", "B", "C", "A", "B", "C", "", "A", "B", "C"));
tbl.getContent().add(row("THD", "", "", "", "", "", "", "", "", "", "", ""));
tbl.getContent().add(row("THD", "standardThd", "thdA", "thdB", "thdC", "", "", "", "", "", "", ""));
tbl.getContent().add(row(keys));
JAXBElement<Tbl> el = new JAXBElement<>(new QName(W_NS, "tbl"), Tbl.class, tbl);
Docx4jUtil.HeadingContent hc = new Docx4jUtil.HeadingContent();
@@ -87,6 +87,33 @@ public class ItemAnchorAssemblerTest {
return hc;
}
private Docx4jUtil.HeadingContent hpTemplate(String... keys) {
Tbl tbl = f.createTbl();
tbl.getContent().add(row("谐波功率检验结果", "基波电压V", "", "基波电流A", ""));
tbl.getContent().add(row("谐波次数", "标准源输出值", "被检仪器测量值", "误差", "允许误差", "检定结论"));
tbl.getContent().add(row("", "", "L1", "L2", "L3", "L1", "L2", "L3", "", ""));
tbl.getContent().add(row(keys));
JAXBElement<Tbl> el = new JAXBElement<>(new QName(W_NS, "tbl"), Tbl.class, tbl);
Docx4jUtil.HeadingContent hc = new Docx4jUtil.HeadingContent();
hc.setHeadingText("HP");
hc.addSubContent(el);
return hc;
}
private Docx4jUtil.HeadingContent hpTemplateWithSplitHeader(String... keys) {
Tbl tbl = f.createTbl();
tbl.getContent().add(row("谐波功率检验结果", "基波电压V", ""));
tbl.getContent().add(row("", "基波电流A", ""));
tbl.getContent().add(row("谐波次数", "标准源输出值", "被检仪器测量值", "误差", "允许误差", "检定结论"));
tbl.getContent().add(row("", "", "L1", "L2", "L3", "L1", "L2", "L3", "", ""));
tbl.getContent().add(row(keys));
JAXBElement<Tbl> el = new JAXBElement<>(new QName(W_NS, "tbl"), Tbl.class, tbl);
Docx4jUtil.HeadingContent hc = new Docx4jUtil.HeadingContent();
hc.setHeadingText("HP");
hc.addSubContent(el);
return hc;
}
private SingleTestResult resultWithRows(String errorScope, List<Map<String, String>> rows) {
Map<String, List<Map<String, String>>> byError = new LinkedHashMap<>();
byError.put(errorScope, rows);
@@ -146,6 +173,27 @@ public class ItemAnchorAssemblerTest {
assertEquals("", cellText(tbl, 1, 3)); // 数据侧没有的列留空
}
@Test
public void buildTables_harmonicVoltageAndPowerStripErrorScopeUnit() {
Docx4jUtil.HeadingContent tpl = template("time", "errorScope");
Map<String, String> row = new HashMap<>();
row.put("time", "2");
SingleTestResult hv = resultWithRows("0.15%", Collections.singletonList(row));
SingleTestResult hp = resultWithRows("-0.1~0.1W", Collections.singletonList(row));
List<Object> hvOut = ItemAnchorAssembler.buildTables("HV", Collections.singletonList(hv), tpl,
Arrays.asList("time", "errorScope"), f);
List<Object> hpOut = ItemAnchorAssembler.buildTables("HP", Collections.singletonList(hp), tpl,
Arrays.asList("time", "errorScope"), f);
@SuppressWarnings("unchecked")
Tbl hvTbl = ((JAXBElement<Tbl>) hvOut.get(0)).getValue();
@SuppressWarnings("unchecked")
Tbl hpTbl = ((JAXBElement<Tbl>) hpOut.get(0)).getValue();
assertEquals("0.15", cellText(hvTbl, 1, 1));
assertEquals("-0.1~0.1", cellText(hpTbl, 1, 1));
}
@Test
public void buildTables_powerMapping_sumsThreePhases() {
Docx4jUtil.HeadingContent tpl = template("standardV", "activePower", "reactivePower");
@@ -192,28 +240,66 @@ public class ItemAnchorAssemblerTest {
SingleTestResult empty = new SingleTestResult(); // detail 为 null → 跳过
List<Object> out = ItemAnchorAssembler.buildTables("HV", Arrays.asList(r1, empty, r2), tpl,
Arrays.asList("time", "standard"), f);
// 空结果跳过 → 2 张表;多表时每张表前有"测点{n}"标题段 → 共 4 个元素
assertEquals(4, out.size());
// 空结果跳过 → 2 张表;多表之间允许有空段落防止 Word 合并,但不得出现可见的表外"测点"标题
assertEquals(2, countTables(out));
assertTrue(nonBlankParagraphTexts(out).isEmpty());
}
/** F1多表谐波多 scriptIndex背靠背插入会被 Word 合并渲染成一张表,相邻表之间必须有段落分隔 */
/** HP 多测点:测点号必须写在表格首行标题单元格内,不能生成表外可见标题段 */
@Test
public void buildTables_multipleTables_separatedByPointCaption() {
Docx4jUtil.HeadingContent tpl = template("time", "standard");
public void buildTables_harmonicPowerMultipleTables_putsPointCaptionInTitleCell() {
List<String> keys = Arrays.asList("time", "standard");
Docx4jUtil.HeadingContent tpl = hpTemplate(keys.toArray(new String[0]));
Map<String, String> row = new HashMap<>();
row.put("time", "2");
row.put("time", "3");
row.put("standard", "0.5000");
SingleTestResult r1 = resultWithRows("0.05", Collections.singletonList(row));
SingleTestResult r2 = resultWithRows("0.05", Collections.singletonList(row));
List<Object> out = ItemAnchorAssembler.buildTables("HV", Arrays.asList(r1, r2), tpl,
Arrays.asList("time", "standard"), f);
// 期望序列测点1标题段、表1、测点2标题段、表2
assertEquals(4, out.size());
assertEquals("测点1", captionText(out.get(0)));
assertTrue(unwrap(out.get(1)) instanceof Tbl);
assertEquals("测点2", captionText(out.get(2)));
assertTrue(unwrap(out.get(3)) instanceof Tbl);
List<Object> out = ItemAnchorAssembler.buildTables("HP", Arrays.asList(r1, r2), tpl, keys, f);
assertEquals(2, countTables(out));
assertTrue(nonBlankParagraphTexts(out).isEmpty());
List<Tbl> tables = collectTables(out);
assertEquals("谐波功率检验结果测点1", cellText(tables.get(0), 0, 0));
assertEquals("谐波功率检验结果(测点2", cellText(tables.get(1), 0, 0));
}
@Test
public void buildTables_harmonicPowerFillsBaseVoltageAndCurrentHeaderCells() {
List<String> keys = Arrays.asList("time", "standard");
Docx4jUtil.HeadingContent tpl = hpTemplate(keys.toArray(new String[0]));
Map<String, String> row = new HashMap<>();
row.put("time", "3");
row.put("standard", "23.0800");
row.put("standardV", "57.7000");
row.put("standardI", "5.0000");
SingleTestResult result = resultWithRows("1.5W", Collections.singletonList(row));
List<Object> out = ItemAnchorAssembler.buildTables("HP", Collections.singletonList(result), tpl, keys, f);
@SuppressWarnings("unchecked")
Tbl tbl = ((JAXBElement<Tbl>) out.get(0)).getValue();
assertEquals("57.7000", cellText(tbl, 0, 2));
assertEquals("5.0000", cellText(tbl, 0, 4));
}
@Test
public void buildTables_harmonicPowerFillsCurrentWhenHeaderIsOnSecondRow() {
List<String> keys = Arrays.asList("time", "standard");
Docx4jUtil.HeadingContent tpl = hpTemplateWithSplitHeader(keys.toArray(new String[0]));
Map<String, String> row = new HashMap<>();
row.put("time", "3");
row.put("standard", "23.0800");
row.put("standardV", "57.7000");
row.put("standardI", "5.0000");
SingleTestResult result = resultWithRows("1.5W", Collections.singletonList(row));
List<Object> out = ItemAnchorAssembler.buildTables("HP", Collections.singletonList(result), tpl, keys, f);
@SuppressWarnings("unchecked")
Tbl tbl = ((JAXBElement<Tbl>) out.get(0)).getValue();
assertEquals("57.7000", cellText(tbl, 0, 2));
assertEquals("5.0000", cellText(tbl, 1, 2));
}
/** F1 边界:单表不加标题段,既有单表输出不变 */
@@ -281,6 +367,182 @@ public class ItemAnchorAssemblerTest {
assertEquals(expected, ItemAnchorAssembler.extractTableKeys(Collections.singletonList(tpl)));
}
@Test
public void buildTables_harmonicVoltageCalculatesThdFixedRowFromPhaseRates() {
List<String> keys = Arrays.asList("time", "standard", "testA", "testB", "testC",
"errorA", "errorB", "errorC", "errorScope", "resultA", "resultB", "resultC");
Docx4jUtil.HeadingContent tpl = hvTemplate(keys.toArray(new String[0]));
Map<String, String> row1 = harmonicRow("2", "3.0000", "6.0000", "8.0000");
row1.put("standard", "3.0000");
Map<String, String> row2 = harmonicRow("3", "4.0000", "8.0000", "15.0000");
row2.put("standard", "4.0000");
SingleTestResult r = resultWithRows("0.05", Arrays.asList(row1, row2));
List<Object> out = ItemAnchorAssembler.buildTables("HV", Collections.singletonList(r), tpl, keys, f);
@SuppressWarnings("unchecked")
Tbl tbl = ((JAXBElement<Tbl>) out.get(0)).getValue();
assertEquals("5.0000", cellText(tbl, 1, 2));
assertEquals("5.0000", cellText(tbl, 4, 1));
assertEquals("5.0000", cellText(tbl, 4, 2));
assertEquals("10.0000", cellText(tbl, 4, 3));
assertEquals("17.0000", cellText(tbl, 4, 4));
}
@Test
public void buildTables_harmonicCurrentCalculatesRateRowsAndThdFromBaseCurrent() {
List<String> keys = Arrays.asList("time", "standardRate", "testRateA", "testRateB", "testRateC",
"errorRateA", "errorRateB", "errorRateC", "errorScope", "resultA", "resultB", "resultC");
Docx4jUtil.HeadingContent tpl = hvTemplate(keys.toArray(new String[0]));
Map<String, String> row1 = harmonicRow("2", "1.2000", "1.6000", "2.0000");
row1.put("standard", "1.0000");
row1.put("standardRate", "25.0000");
row1.put("errorA", "0.2000");
row1.put("errorB", "0.6000");
row1.put("errorC", "1.0000");
row1.put("baseCurrentA", "4.0000");
row1.put("baseCurrentB", "4.0000");
row1.put("baseCurrentC", "4.0000");
Map<String, String> row2 = harmonicRow("3", "0.9000", "1.2000", "1.5000");
row2.put("standard", "1.0000");
row2.put("standardRate", "25.0000");
row2.put("errorA", "-0.1000");
row2.put("errorB", "0.2000");
row2.put("errorC", "0.5000");
row2.put("baseCurrentA", "4.0000");
row2.put("baseCurrentB", "4.0000");
row2.put("baseCurrentC", "4.0000");
SingleTestResult r = resultWithRows("0.05", Arrays.asList(row1, row2));
List<Object> out = ItemAnchorAssembler.buildTables("HI", Collections.singletonList(r), tpl, keys, f);
@SuppressWarnings("unchecked")
Tbl tbl = ((JAXBElement<Tbl>) out.get(0)).getValue();
assertEquals("35.3553", cellText(tbl, 1, 2));
assertEquals("35.3553", cellText(tbl, 4, 1));
assertEquals("37.5000", cellText(tbl, 4, 2));
assertEquals("50.0000", cellText(tbl, 4, 3));
assertEquals("62.5000", cellText(tbl, 4, 4));
assertEquals("25.0000", cellText(tbl, 5, 1));
assertEquals("30.0000", cellText(tbl, 5, 2));
assertEquals("40.0000", cellText(tbl, 5, 3));
assertEquals("50.0000", cellText(tbl, 5, 4));
assertEquals("5.0000", cellText(tbl, 5, 5));
assertEquals("15.0000", cellText(tbl, 5, 6));
assertEquals("25.0000", cellText(tbl, 5, 7));
assertEquals("±1.2500", cellText(tbl, 5, 8));
assertEquals("±1.2500", cellText(tbl, 6, 8));
}
@Test
public void buildTables_harmonicCurrentLeavesStandardRateBlankWhenScriptRateMissing() {
List<String> keys = Arrays.asList("time", "standardRate", "testRateA", "testRateB", "testRateC",
"errorRateA", "errorRateB", "errorRateC", "errorScope", "resultA", "resultB", "resultC");
Docx4jUtil.HeadingContent tpl = hvTemplate(keys.toArray(new String[0]));
Map<String, String> row = harmonicRow("2", "1.0000", "1.0000", "1.0000");
row.put("standard", "1.0000");
row.put("baseCurrentA", "5.0000");
row.put("baseCurrentB", "5.0000");
row.put("baseCurrentC", "5.0000");
SingleTestResult r = resultWithRows("0.05", Collections.singletonList(row));
List<Object> out = ItemAnchorAssembler.buildTables("HI", Collections.singletonList(r), tpl, keys, f);
@SuppressWarnings("unchecked")
Tbl tbl = ((JAXBElement<Tbl>) out.get(0)).getValue();
assertEquals("", cellText(tbl, 1, 2));
assertEquals("", cellText(tbl, 4, 1));
assertEquals("", cellText(tbl, 5, 1));
assertEquals("20.0000", cellText(tbl, 5, 2));
assertEquals("", cellText(tbl, 5, 5));
assertEquals("", cellText(tbl, 5, 8));
}
@Test
public void buildTables_harmonicCurrentKeepsScriptStandardRateWhenAmplitudeAvailable() {
List<String> keys = Arrays.asList("time", "standardRate", "testRateA", "testRateB", "testRateC",
"errorRateA", "errorRateB", "errorRateC", "errorScope", "resultA", "resultB", "resultC");
Docx4jUtil.HeadingContent tpl = hvTemplate(keys.toArray(new String[0]));
Map<String, String> row = harmonicRow("2", "1.0000", "1.0000", "1.0000");
row.put("standard", "2.0000");
row.put("standardRate", "1.0000");
row.put("baseCurrentA", "10.0000");
row.put("baseCurrentB", "10.0000");
row.put("baseCurrentC", "10.0000");
SingleTestResult r = resultWithRows("0.05", Collections.singletonList(row));
List<Object> out = ItemAnchorAssembler.buildTables("HI", Collections.singletonList(r), tpl, keys, f);
@SuppressWarnings("unchecked")
Tbl tbl = ((JAXBElement<Tbl>) out.get(0)).getValue();
assertEquals("1.0000", cellText(tbl, 5, 1));
assertEquals("9.0000", cellText(tbl, 5, 5));
assertEquals("±0.1500", cellText(tbl, 5, 8));
}
@Test
public void buildTables_harmonicCurrentErrorRateUsesRateDifference() {
List<String> keys = Arrays.asList("time", "standardRate", "testRateA", "testRateB", "testRateC",
"errorRateA", "errorRateB", "errorRateC", "errorScope", "resultA", "resultB", "resultC");
Docx4jUtil.HeadingContent tpl = hvTemplate(keys.toArray(new String[0]));
Map<String, String> row = harmonicRow("2", "2.0000", "1.0000", "1.0000");
row.put("standard", "1.0000");
row.put("standardRate", "10.0000");
row.put("errorA", "0.0100");
row.put("baseCurrentA", "10.0000");
row.put("baseCurrentB", "10.0000");
row.put("baseCurrentC", "10.0000");
SingleTestResult r = resultWithRows("0.05", Collections.singletonList(row));
List<Object> out = ItemAnchorAssembler.buildTables("HI", Collections.singletonList(r), tpl, keys, f);
@SuppressWarnings("unchecked")
Tbl tbl = ((JAXBElement<Tbl>) out.get(0)).getValue();
assertEquals("10.0000", cellText(tbl, 5, 1));
assertEquals("20.0000", cellText(tbl, 5, 2));
assertEquals("10.0000", cellText(tbl, 5, 5));
}
@Test
public void buildTables_harmonicCurrentKeepsScriptStandardRateWhenAmplitudeMissing() {
List<String> keys = Arrays.asList("time", "standardRate", "testRateA", "testRateB", "testRateC",
"errorRateA", "errorRateB", "errorRateC", "errorScope", "resultA", "resultB", "resultC");
Docx4jUtil.HeadingContent tpl = hvTemplate(keys.toArray(new String[0]));
Map<String, String> row = harmonicRow("2", "0.040144", "0.040248", "0.040116");
row.put("standard", "/");
row.put("standardRate", "1.0000");
row.put("baseCurrentA", "4.0000");
row.put("baseCurrentB", "4.0000");
row.put("baseCurrentC", "4.0000");
SingleTestResult r = resultWithRows("0.0075A", Collections.singletonList(row));
List<Object> out = ItemAnchorAssembler.buildTables("HI", Collections.singletonList(r), tpl, keys, f);
@SuppressWarnings("unchecked")
Tbl tbl = ((JAXBElement<Tbl>) out.get(0)).getValue();
assertEquals("1.0000", cellText(tbl, 1, 2));
assertEquals("1.0000", cellText(tbl, 4, 1));
assertEquals("1.0000", cellText(tbl, 5, 1));
assertEquals("1.0036", cellText(tbl, 5, 2));
assertEquals("0.0036", cellText(tbl, 5, 5));
}
private Map<String, String> harmonicRow(String time, String testA, String testB, String testC) {
Map<String, String> row = new HashMap<>();
row.put("time", time);
row.put("standard", "0.0000");
row.put("testA", testA);
row.put("testB", testB);
row.put("testC", testC);
row.put("errorA", "0.0000");
row.put("errorB", "0.0000");
row.put("errorC", "0.0000");
row.put("resultA", "合格");
row.put("resultB", "合格");
row.put("resultC", "合格");
return row;
}
private Object unwrap(Object o) {
return (o instanceof JAXBElement) ? ((JAXBElement<?>) o).getValue() : o;
}
@@ -295,10 +557,29 @@ public class ItemAnchorAssemblerTest {
return n;
}
private String captionText(Object o) {
Object v = unwrap(o);
assertTrue("相邻表之间应为段落,实际是 " + v.getClass().getSimpleName(), v instanceof P);
return Docx4jUtil.getTextFromP((P) v);
private List<Tbl> collectTables(List<Object> out) {
List<Tbl> tables = new ArrayList<>();
for (Object o : out) {
Object v = unwrap(o);
if (v instanceof Tbl) {
tables.add((Tbl) v);
}
}
return tables;
}
private List<String> nonBlankParagraphTexts(List<Object> out) {
List<String> texts = new ArrayList<>();
for (Object o : out) {
Object v = unwrap(o);
if (v instanceof P) {
String text = Docx4jUtil.getTextFromP((P) v);
if (text != null && !text.trim().isEmpty()) {
texts.add(text);
}
}
}
return texts;
}
private List<Tr> collectTrs(Tbl tbl) {

View File

@@ -0,0 +1,232 @@
package com.njcn.gather.result.service.impl;
import com.njcn.gather.report.pojo.enums.ItemReportKeyEnum;
import com.njcn.gather.report.pojo.result.SingleTestResult;
import com.njcn.gather.script.pojo.po.PqScriptCheckData;
import com.njcn.gather.script.pojo.vo.PqScriptDtlDataVO;
import com.njcn.gather.script.service.IPqScriptCheckDataService;
import com.njcn.gather.storage.pojo.po.SimAndDigHarmonicResult;
import com.njcn.gather.storage.pojo.po.SimAndDigNonHarmonicResult;
import com.njcn.gather.storage.pojo.param.SingleNonHarmParam;
import com.njcn.gather.storage.service.SimAndDigHarmonicService;
import com.njcn.gather.storage.service.SimAndDigNonHarmonicService;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class ResultServiceImplTest {
@Test
public void fillThreePhaseData_harmonicCurrentCarriesBaseCurrentForRateCalculation() throws Exception {
SimAndDigHarmonicResult result = new SimAndDigHarmonicResult();
result.setAValue1(detectionData("4.0000", "10.0000"));
result.setBValue1(detectionData("5.0000", "11.0000"));
result.setCValue1(detectionData("6.0000", "12.0000"));
result.setAValue2(detectionData("1.0000"));
result.setBValue2(detectionData("1.5000"));
result.setCValue2(detectionData("2.0000"));
Map<String, String> row = new HashMap<>();
Method method = ResultServiceImpl.class.getDeclaredMethod(
"fillThreePhaseData", Object.class, Integer.class, Map.class, String.class);
method.setAccessible(true);
boolean filled = (Boolean) method.invoke(newResultService(), result, 2, row, "HI");
assertTrue(filled);
assertEquals("4.0000", row.get(ItemReportKeyEnum.BASE_CURRENT_A.getKey()));
assertEquals("5.0000", row.get(ItemReportKeyEnum.BASE_CURRENT_B.getKey()));
assertEquals("6.0000", row.get(ItemReportKeyEnum.BASE_CURRENT_C.getKey()));
}
@Test
public void fillThreePhaseData_harmonicCurrentUsesBaseCurrentDataWhenResultDataMissing() throws Exception {
SimAndDigHarmonicResult result = new SimAndDigHarmonicResult();
result.setAValue1(detectionDataOnlyData("1.0000"));
result.setBValue1(detectionDataOnlyData("2.0000"));
result.setCValue1(detectionDataOnlyData("3.0000"));
result.setAValue2(detectionData("0.1000"));
result.setBValue2(detectionData("0.2000"));
result.setCValue2(detectionData("0.3000"));
Map<String, String> row = new HashMap<>();
Method method = ResultServiceImpl.class.getDeclaredMethod(
"fillThreePhaseData", Object.class, Integer.class, Map.class, String.class);
method.setAccessible(true);
boolean filled = (Boolean) method.invoke(newResultService(), result, 2, row, "HI");
assertTrue(filled);
assertEquals("1.0000", row.get(ItemReportKeyEnum.BASE_CURRENT_A.getKey()));
assertEquals("2.0000", row.get(ItemReportKeyEnum.BASE_CURRENT_B.getKey()));
assertEquals("3.0000", row.get(ItemReportKeyEnum.BASE_CURRENT_C.getKey()));
}
@Test
public void fillHarmonicScriptStandard_harmonicCurrentCopiesScriptPercentToStandardRate() throws Exception {
Map<String, String> row = new HashMap<>();
List<PqScriptCheckData> checkData = Arrays.asList(
checkData("A", 2D, 1D),
checkData("B", 2D, 1D),
checkData("C", 2D, 1D)
);
Method method = ResultServiceImpl.class.getDeclaredMethod(
"fillHarmonicScriptStandard", Map.class, String.class, List.class);
method.setAccessible(true);
method.invoke(newResultService(), row, "HI", checkData);
assertEquals("1.0000", row.get(ItemReportKeyEnum.STANDARD_RATE.getKey()));
}
@Test
public void getFinalContent_harmonicPowerCarriesBaseVoltageAndCurrentFromScriptDetails() throws Exception {
IPqScriptCheckDataService checkDataService = mock(IPqScriptCheckDataService.class);
SimAndDigHarmonicService harmonicService = mock(SimAndDigHarmonicService.class);
when(checkDataService.listCheckData(eq("script"), anyList()))
.thenReturn(Collections.singletonList(checkData("A", 3D, 23.08D)));
SimAndDigHarmonicResult harmonicResult = new SimAndDigHarmonicResult();
harmonicResult.setAValue3(detectionData("23.0745", "23.0800"));
harmonicResult.setBValue3(detectionData("23.0809", "23.0800"));
harmonicResult.setCValue3(detectionData("23.0814", "23.0800"));
when(harmonicService.getSingleResult(any())).thenReturn(harmonicResult);
List<PqScriptDtlDataVO> scriptDetails = Arrays.asList(
scriptDetail("VOL", "A", 57.7D),
scriptDetail("CUR", "A", 5.0D)
);
SingleTestResult result = newResultService(harmonicService, checkDataService)
.getFinalContent(scriptDetails, "plan", "dev", 1,
Arrays.asList("time", "standardV", "standardI"));
Map<String, String> row = firstRow(result);
assertEquals("57.7000", row.get(ItemReportKeyEnum.STANDARD_V.getKey()));
assertEquals("5.0000", row.get(ItemReportKeyEnum.STANDARD_I.getKey()));
}
@Test
public void getFinalContent_angleUsesCurrentAngleResultRows() throws Exception {
IPqScriptCheckDataService checkDataService = mock(IPqScriptCheckDataService.class);
SimAndDigNonHarmonicService nonHarmonicService = mock(SimAndDigNonHarmonicService.class);
when(checkDataService.listCheckData(eq("script"), anyList())).thenReturn(Collections.emptyList());
SimAndDigNonHarmonicResult result = new SimAndDigNonHarmonicResult();
result.setSort(21);
result.setAdType("I1A");
result.setAValue(detectionAngleData("61.4", "60", "1.4", 2));
result.setBValue(detectionAngleData("61", "60", "1", 1));
result.setCValue(detectionAngleData("61", "60", "1", 1));
when(nonHarmonicService.queryBySortListWithAdTypeCode(any(SingleNonHarmParam.class)))
.thenReturn(Collections.singletonList(result));
SingleTestResult reportResult = newResultService(null, checkDataService, nonHarmonicService)
.getFinalContent(Collections.singletonList(scriptDetail("Angle", "Base", 21)),
"plan", "dev", 1, Arrays.asList("standard", "testA", "testB", "testC"));
Map<String, String> row = firstRow(reportResult);
assertEquals("60.0000", row.get(ItemReportKeyEnum.STANDARD.getKey()));
assertEquals("61.4000", row.get(ItemReportKeyEnum.TEST_A.getKey()));
assertEquals("61.0000", row.get(ItemReportKeyEnum.TEST_B.getKey()));
assertEquals("61.0000", row.get(ItemReportKeyEnum.TEST_C.getKey()));
assertEquals("不合格", row.get(ItemReportKeyEnum.RESULT_A.getKey()));
assertEquals("合格", row.get(ItemReportKeyEnum.RESULT_B.getKey()));
assertEquals("合格", row.get(ItemReportKeyEnum.RESULT_C.getKey()));
}
private ResultServiceImpl newResultService() throws Exception {
return newResultService(null, null);
}
private ResultServiceImpl newResultService(SimAndDigHarmonicService harmonicService,
IPqScriptCheckDataService checkDataService) throws Exception {
return newResultService(harmonicService, checkDataService, null);
}
private ResultServiceImpl newResultService(SimAndDigHarmonicService harmonicService,
IPqScriptCheckDataService checkDataService,
SimAndDigNonHarmonicService nonHarmonicService) throws Exception {
Constructor<?> constructor = ResultServiceImpl.class.getDeclaredConstructors()[0];
constructor.setAccessible(true);
Class<?>[] parameterTypes = constructor.getParameterTypes();
Object[] args = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
if (nonHarmonicService != null && parameterTypes[i].equals(SimAndDigNonHarmonicService.class)) {
args[i] = nonHarmonicService;
} else if (harmonicService != null && parameterTypes[i].equals(SimAndDigHarmonicService.class)) {
args[i] = harmonicService;
} else if (checkDataService != null && parameterTypes[i].equals(IPqScriptCheckDataService.class)) {
args[i] = checkDataService;
}
}
return (ResultServiceImpl) constructor.newInstance(args);
}
private Map<String, String> firstRow(SingleTestResult result) {
return result.getDetail().values().iterator().next()
.get(0).values().iterator().next()
.get(0);
}
private String detectionData(String value) {
return detectionData(value, value);
}
private String detectionData(String data, String resultData) {
return "{\"isData\":1,\"data\":" + data + ",\"resultData\":" + resultData
+ ",\"radius\":\"0.05\",\"unit\":\"A\"}";
}
private String detectionDataOnlyData(String data) {
return "{\"num\":1,\"isData\":4,\"data\":" + data + ",\"radius\":\"0.05\",\"unit\":\"A\"}";
}
private String detectionAngleData(String data, String resultData, String errorData, int isData) {
return "{\"isData\":" + isData + ",\"data\":" + data + ",\"resultData\":" + resultData
+ ",\"errorData\":" + errorData + ",\"radius\":\"-1.0~1.0\",\"unit\":\"\\u00b0\"}";
}
private PqScriptCheckData checkData(String phase, Double harmNum, Double value) {
PqScriptCheckData data = new PqScriptCheckData();
data.setScriptIndex(3);
data.setValueType("HP");
data.setPhase(phase);
data.setHarmNum(harmNum);
data.setValue(value);
return data;
}
private PqScriptDtlDataVO scriptDetail(String valueType, String phase, Double value) {
PqScriptDtlDataVO detail = new PqScriptDtlDataVO();
detail.setScriptId("script");
detail.setScriptCode("HP");
detail.setScriptSubType("Base");
detail.setScriptIndex(3);
detail.setValueType(valueType);
detail.setPhase(phase);
detail.setValue(value);
return detail;
}
private PqScriptDtlDataVO scriptDetail(String scriptCode, String scriptSubType, Integer scriptIndex) {
PqScriptDtlDataVO detail = new PqScriptDtlDataVO();
detail.setScriptId("script");
detail.setScriptCode(scriptCode);
detail.setScriptSubType(scriptSubType);
detail.setScriptIndex(scriptIndex);
return detail;
}
}

View File

@@ -0,0 +1,64 @@
package com.njcn.gather.result.util;
import com.njcn.gather.report.pojo.enums.ItemReportKeyEnum;
import com.njcn.gather.storage.pojo.po.SimAndDigNonHarmonicResult;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class AngleResultAssemblerTest {
@Test
public void assembleRows_usesCurrentAngleI1AAndIgnoresVoltageAngleU1A() {
SimAndDigNonHarmonicResult voltageAngle = result("U1A",
data(0D, 0D, 0D, 1), data(0D, 0D, 0D, 1), data(0D, 0D, 0D, 1));
SimAndDigNonHarmonicResult currentAngle = result("I1A",
data(61.4D, 60D, 1.4D, 2), data(61D, 60D, 1D, 1), data(61D, 60D, 1D, 1));
List<Map<String, String>> rows = AngleResultAssembler.assembleRows(Arrays.asList(voltageAngle, currentAngle));
assertEquals(1, rows.size());
Map<String, String> row = rows.get(0);
assertEquals("60.0000", row.get(ItemReportKeyEnum.STANDARD.getKey()));
assertEquals("61.4000", row.get(ItemReportKeyEnum.TEST_A.getKey()));
assertEquals("61.0000", row.get(ItemReportKeyEnum.TEST_B.getKey()));
assertEquals("61.0000", row.get(ItemReportKeyEnum.TEST_C.getKey()));
assertEquals("1.4000", row.get(ItemReportKeyEnum.ERROR_A.getKey()));
assertEquals("1.0000", row.get(ItemReportKeyEnum.ERROR_B.getKey()));
assertEquals("1.0000", row.get(ItemReportKeyEnum.ERROR_C.getKey()));
assertEquals("不合格", row.get(ItemReportKeyEnum.RESULT_A.getKey()));
assertEquals("合格", row.get(ItemReportKeyEnum.RESULT_B.getKey()));
assertEquals("合格", row.get(ItemReportKeyEnum.RESULT_C.getKey()));
assertEquals("\u00b11.0\u00b0", row.get(ItemReportKeyEnum.ERROR_SCOPE.getKey()));
}
@Test
public void assembleRows_voltageAngleOnlyReturnsNoRows() {
SimAndDigNonHarmonicResult voltageAngle = result("U1A",
data(0D, 0D, 0D, 1), data(0D, 0D, 0D, 1), data(0D, 0D, 0D, 1));
assertTrue(AngleResultAssembler.assembleRows(Collections.singletonList(voltageAngle)).isEmpty());
}
private SimAndDigNonHarmonicResult result(String adType, String a, String b, String c) {
SimAndDigNonHarmonicResult result = new SimAndDigNonHarmonicResult();
result.setSort(21);
result.setAdType(adType);
result.setAValue(a);
result.setBValue(b);
result.setCValue(c);
return result;
}
private String data(Double data, Double resultData, Double errorData, Integer isData) {
return "{\"data\":" + data + ",\"unit\":\"\\u00b0\",\"isData\":" + isData
+ ",\"radius\":\"-1.0~1.0\",\"errorData\":" + errorData
+ ",\"resultData\":" + resultData + "}";
}
}

View File

@@ -9,6 +9,7 @@ import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class UnbalanceResultAssemblerTest {
@@ -72,6 +73,17 @@ public class UnbalanceResultAssemblerTest {
assertEquals("合格", row.get(ItemReportKeyEnum.RESULT.getKey()));
}
@Test
public void assembleRows_leavesTimeUnsetForItemAnchorPointFallback() {
SimAndDigNonHarmonicResult unbalance = total("V_UNBAN", 7,
"{\"data\": 0.05, \"unit\": \"%\", \"isData\": 1, \"radius\": \"-0.2~0.2\", \"errorData\": 0.01, \"resultData\": 0.04}");
List<Map<String, String>> rows = UnbalanceResultAssembler.assembleRows(Arrays.asList(unbalance));
assertEquals(1, rows.size());
assertNull(rows.get(0).get(ItemReportKeyEnum.TIME.getKey()));
}
private SimAndDigNonHarmonicResult total(String code, Integer sort, String tValue) {
SimAndDigNonHarmonicResult result = new SimAndDigNonHarmonicResult();
result.setAdType(code);

View File

@@ -8,6 +8,7 @@ import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.docx4j.XmlUtils;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.DocumentSettingsPart;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.wml.*;
import org.docx4j.wml.TcPrInner.GridSpan;
@@ -31,6 +32,27 @@ import java.util.Map;
@Slf4j
public class Docx4jUtil {
/**
* 让 Word 打开文档时自动刷新 PAGE / NUMPAGES 等域,避免动态插表后页码显示旧值。
*/
public static void enableUpdateFieldsOnOpen(WordprocessingMLPackage wordPackage) throws Exception {
if (wordPackage == null || wordPackage.getMainDocumentPart() == null) {
return;
}
DocumentSettingsPart settingsPart = wordPackage.getMainDocumentPart().getDocumentSettingsPart(true);
CTSettings settings = settingsPart.getContents();
if (settings == null) {
settings = new CTSettings();
settingsPart.setJaxbElement(settings);
}
BooleanDefaultTrue updateFields = settings.getUpdateFields();
if (updateFields == null) {
updateFields = new BooleanDefaultTrue();
settings.setUpdateFields(updateFields);
}
updateFields.setVal(Boolean.TRUE);
}
/**
* 创建标题
*