Browse Source

2026-06-24

项目信息的【Excel导入】功能优化;
待报价清单【报价】优化,做了防御性保护,遇到循环引用的结构会给出明确提示;
master
fengyuan_yang 1 month ago
parent
commit
f65e0d342f
  1. 39
      src/main/java/com/spring/modules/part/service/impl/PartInformationServiceImpl.java
  2. 52
      src/main/java/com/spring/modules/quote/service/impl/QuoteDetailBomTreeServiceImpl.java

39
src/main/java/com/spring/modules/part/service/impl/PartInformationServiceImpl.java

@ -3864,23 +3864,29 @@ public class PartInformationServiceImpl extends ServiceImpl<PartInformationMappe
@Override @Override
@Transactional @Transactional
public void uploadProjectPartExcel(MultipartFile file, GetParamInData data) { public void uploadProjectPartExcel(MultipartFile file, GetParamInData data) {
try{
// 转流
InputStream is = file.getInputStream();
// 读取工作簿
XSSFWorkbook workbook = new XSSFWorkbook(is);
try (InputStream is = file.getInputStream(); XSSFWorkbook workbook = new XSSFWorkbook(is)) {
// 读取工作表 // 读取工作表
XSSFSheet sheet = workbook.getSheetAt(0); XSSFSheet sheet = workbook.getSheetAt(0);
// 获取行数
int rows = sheet.getPhysicalNumberOfRows();
// 获取最后一行索引从0开始
int lastRowNum = sheet.getLastRowNum();
int lastDataRowNum = 0;
for (int j = 1; j <= lastRowNum; j++) {
XSSFRow row = sheet.getRow(j);
if (!isProjectPartImportRowEmpty(row)) {
lastDataRowNum = j;
}
}
// 声明对象 // 声明对象
PartInformationVo task = null; PartInformationVo task = null;
// 遍历每一行从第二行开始 // 遍历每一行从第二行开始
for (int j = 1; j < rows; j++) {
// 实例化对象
task = new PartInformationVo();
for (int j = 1; j <= lastDataRowNum; j++) {
//获得该行 //获得该行
XSSFRow row = sheet.getRow(j); XSSFRow row = sheet.getRow(j);
if (isProjectPartImportRowEmpty(row)) {
throw new RuntimeException("第" + (j + 1) + "行为空白,存在中间空白行,请删除空白行后重试");
}
// 实例化对象
task = new PartInformationVo();
// 为对象赋值 // 为对象赋值
task.setSite(data.getOrderRef1()); // site task.setSite(data.getOrderRef1()); // site
task.setProjectId(data.getOrderRef2()); // 项目编码 task.setProjectId(data.getOrderRef2()); // 项目编码
@ -3938,6 +3944,19 @@ public class PartInformationServiceImpl extends ServiceImpl<PartInformationMappe
} }
} }
private boolean isProjectPartImportRowEmpty(XSSFRow row) {
if (row == null) {
return true;
}
// Excel模板约定有效导入列为A-G均为空则视为导入结束
for (int i = 0; i <= 6; i++) {
if (StringUtils.hasText(getCellValueAsString(row.getCell(i)))) {
return false;
}
}
return true;
}
private static String getCellValueAsString(Cell cell) { private static String getCellValueAsString(Cell cell) {
if (cell == null) { if (cell == null) {
return ""; return "";

52
src/main/java/com/spring/modules/quote/service/impl/QuoteDetailBomTreeServiceImpl.java

@ -133,7 +133,22 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
* *
* @param ifsConFactory IFS 连接工厂每个并发线程调用 get() 获取独立连接避免共享连接线程安全问题 * @param ifsConFactory IFS 连接工厂每个并发线程调用 get() 获取独立连接避免共享连接线程安全问题
*/ */
private BomNodeData collectBomData(QuoteDetail detail, Long parentId, Integer level, Supplier<Server> ifsConFactory, Map<String, BigDecimal> costCache, Map<String, BigDecimal> workCenterCostCache) {
private BomNodeData collectBomData(QuoteDetail detail,
Long parentId,
Integer level,
Supplier<Server> ifsConFactory,
Map<String, BigDecimal> costCache,
Map<String, BigDecimal> workCenterCostCache) {
return collectBomData(detail, parentId, level, ifsConFactory, costCache, workCenterCostCache, new LinkedHashSet<>());
}
private BomNodeData collectBomData(QuoteDetail detail,
Long parentId,
Integer level,
Supplier<Server> ifsConFactory,
Map<String, BigDecimal> costCache,
Map<String, BigDecimal> workCenterCostCache,
Set<String> recursionPath) {
// 统一先尝试将临时料映射到正式料保证新建与切换版本刷新取数来源一致 // 统一先尝试将临时料映射到正式料保证新建与切换版本刷新取数来源一致
PartInformationEntity mappedPart = baseMapper.queryPart(detail.getSite(), detail.getPartNo()); PartInformationEntity mappedPart = baseMapper.queryPart(detail.getSite(), detail.getPartNo());
if (Objects.nonNull(mappedPart) && "Y".equals(mappedPart.getStatus())) { if (Objects.nonNull(mappedPart) && "Y".equals(mappedPart.getStatus())) {
@ -161,6 +176,15 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
if (Objects.isNull(bom)) { if (Objects.isNull(bom)) {
return null; return null;
} }
String currentBomPathKey = buildBomPathKey(bom);
if (recursionPath.contains(currentBomPathKey)) {
String cyclePath = formatBomPath(recursionPath, currentBomPathKey);
log.error("[BOM_PROCESS] 检测到BOM循环引用,终止报价创建. key={}, path={}",
currentBomPathKey, cyclePath);
throw new RuntimeException("检测到BOM循环引用,请先在BOM维护中调整后再报价。环路:" + cyclePath);
}
Set<String> nextRecursionPath = new LinkedHashSet<>(recursionPath);
nextRecursionPath.add(currentBomPathKey);
bom.setParentId(parentId); bom.setParentId(parentId);
bom.setLevel(level); bom.setLevel(level);
log.info("BOM信息:{}", bom); log.info("BOM信息:{}", bom);
@ -205,7 +229,15 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
if (Objects.nonNull(childBomCheck) && "Y".equals(component.getBomFlag())) { if (Objects.nonNull(childBomCheck) && "Y".equals(component.getBomFlag())) {
log.debug("[BOM_PROCESS] 递归处理半成品 - PartNo: {}", component.getComponentPart()); log.debug("[BOM_PROCESS] 递归处理半成品 - PartNo: {}", component.getComponentPart());
// 递归收集子节点parentId 占位 -1写入时由 doSaveBomDataRecursive 覆盖 // 递归收集子节点parentId 占位 -1写入时由 doSaveBomDataRecursive 覆盖
BomNodeData childNode = collectBomData(childDetail, -1L, level + 1, ifsConFactory, costCache, workCenterCostCache);
BomNodeData childNode = collectBomData(
childDetail,
-1L,
level + 1,
ifsConFactory,
costCache,
workCenterCostCache,
nextRecursionPath
);
if (childNode != null) { if (childNode != null) {
nodeData.children.add(childNode); nodeData.children.add(childNode);
// 半成品bomId 在写入阶段填充先清零价格IFS 并行阶段会覆盖 // 半成品bomId 在写入阶段填充先清零价格IFS 并行阶段会覆盖
@ -908,6 +940,22 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
return quoteDetail; return quoteDetail;
} }
private String buildBomPathKey(QuoteDetailBomTree bom) {
return String.join("|",
Objects.toString(bom.getSite(), ""),
Objects.toString(bom.getPartNo(), ""),
Objects.toString(bom.getBomType(), ""),
Objects.toString(bom.getEngChgLevel(), ""),
Objects.toString(bom.getAlternativeNo(), "")
);
}
private String formatBomPath(Set<String> recursionPath, String currentBomPathKey) {
List<String> fullPath = new ArrayList<>(recursionPath);
fullPath.add(currentBomPathKey);
return String.join(" -> ", fullPath);
}
private void copyCommonFields(QuoteDetail source, QuoteDetail target) { private void copyCommonFields(QuoteDetail source, QuoteDetail target) {
target.setId(source.getId()); target.setId(source.getId());
target.setQuoteId(source.getQuoteId()); target.setQuoteId(source.getQuoteId());

Loading…
Cancel
Save