Browse Source

2026-03-09

销售报价卡顿优化
master
fengyuan_yang 1 month ago
parent
commit
2551c3f59f
  1. 753
      src/main/java/com/spring/modules/quote/service/impl/QuoteDetailBomTreeServiceImpl.java
  2. 3
      src/main/java/com/spring/modules/quote/service/impl/QuoteDetailServiceImpl.java
  3. 21
      src/main/resources/mapper/quote/QuoteDetailMapper.xml
  4. 49
      src/main/resources/mapper/quote/QuoteMapper.xml

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

@ -56,32 +56,67 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
this.server = ifsServer.getIfsServer(ifsUsername,ifsPassword);
}
// =========================================================================
// 用于在事务外承载收集好的待写入数据
// =========================================================================
/**
* 一个 BOM 节点及其对应的 Routing子物料数据包
* collectBomData 阶段所有耗时的 IFS 调用数据库 SELECT 均在此阶段完成
* doSaveBomData 阶段事务内只做纯粹的数据库写入不含任何外部 IO
*/
private static class BomNodeData {
QuoteDetailBomTree tree;
QuoteDetailRouting routing;
/** 当前节点下的子物料列表(已填充好成本价格) */
List<QuoteDetailBom> components = new ArrayList<>();
/** 是否顶级节点(决定是否触发 handleTool) */
boolean isRoot;
/**
* 子节点数据包 components bomFlag="Y" 的条目按顺序一一对应
* 写入时先写子节点拿到真实 treeId再回填 component.bomId
*/
List<BomNodeData> children = new ArrayList<>();
}
// =========================================================================
// 公开方法两阶段执行
// =========================================================================
/**
* 初始化 BOM
* 阶段一无事务递归收集所有 BOM/Routing/子物料数据并调用 IFS 查询成本
* 阶段二短事务批量写入已准备好的数据事务内无外部 IO大幅缩短锁持有时间
*/
@Override
@Transactional
public long initQuoteDetailBomTree(QuoteDetail detail, Long parentId, Integer level) {
//获取当前用户的ifs账号和连接 LR 2025-05-30 Start
//获取当前操作的账号
String username = ((SysUserEntity) SecurityUtils.getSubject().getPrincipal()).getUsername();
SysUserEntity ifsUser = sysUserDao.selectOne(new QueryWrapper<SysUserEntity>().eq("username", username));
if (ifsUser == null || !org.apache.commons.lang3.StringUtils.isNotBlank(ifsUser.getIfsUsername()) || !org.apache.commons.lang3.StringUtils.isNotBlank(ifsUser.getIfsPassword())) {
throw new RuntimeException("请维护IFS账号和密码!");
// 阶段一事务外获取 IFS 连接并递归收集数据
Server ifsCon = resolveIfsCon();
BomNodeData rootNode = collectBomData(detail, parentId, level, ifsCon);
if (rootNode == null) {
return 0;
}
Server ifsCon = ifsServer.getIfsServer(ifsUser.getIfsUsername(), ifsUser.getIfsPassword());
//获取当前用户的ifs账号和连接 LR 2025-05-30 End
// 阶段二短事务纯粹写入
return doSaveBomData(rootNode, detail);
}
// =========================================================================
// 阶段一递归收集数据无事务 IFS 调用
// =========================================================================
// 1通过PartNoSite和BuNo 查询BOM信息 失效日期日期为空 替代为* 和Routing (存在BOM的物料)
if (parentId.equals(0L)){
// 顶级物料强制使用 Manufacturing 类型
/**
* 递归收集 BOM 树节点数据所有耗时操作IFS 接口调用数据库查询均在此完成
* 返回 null 表示当前节点无 BOM 信息
*/
private BomNodeData collectBomData(QuoteDetail detail, Long parentId, Integer level, Server ifsCon) {
// 确定 BOM 类型
if (parentId.equals(0L)) {
detail.setBomType("Manufacturing");
}else {
// 子物料先查询物料信息
PartInformationEntity part = baseMapper.queryPart(detail.getSite(),detail.getPartNo());
if (Objects.nonNull(part) && "Y".equals(part.getStatus())){
} else {
PartInformationEntity part = baseMapper.queryPart(detail.getSite(), detail.getPartNo());
if (Objects.nonNull(part) && "Y".equals(part.getStatus())) {
detail.setPartNo(part.getPartNo());
}
// 只有在bomType为空时新增报价单场景才根据物料类型自动设置BOM类型
// 切换版本时bomType已经由用户选择保留用户的选择
if (detail.getBomType() == null || detail.getBomType().isEmpty()) {
String partType = baseMapper.queryPartType(detail.getSite(), detail.getPartNo());
if ("Manufactured".equals(partType) || "Manufactured Recipe".equals(partType)) {
@ -94,342 +129,176 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
log.info("子物料 {} 使用用户选择的BOM类型 {}", detail.getPartNo(), detail.getBomType());
}
}
// 获取BOM信息
QuoteDetailBomTree bom = baseMapper.queryPartBom(detail);
if (Objects.isNull(bom)){
return 0;
if (Objects.isNull(bom)) {
return null;
}
bom.setParentId(parentId);
bom.setLevel(level);
// 2通过Bom查询 Bom子物料
log.info("BOM信息:{}",bom);
save(bom);
// 查BOM对应Routing
log.info("BOM信息:{}", bom);
// 查询 Routing
QuoteDetailRouting routing = baseMapper.queryPartBomRouting(detail);
if (Objects.nonNull(routing)) {
routing.setTreeId(bom.getId());
routing.setCreateBy(detail.getCreateBy());
routing.setCreateDate(new Date());
quoteDetailRoutingService.saveQuoteDetailRouting(routing);
}
// 当为顶级BOM 需要插入对应Routing的工具信息
if (parentId.equals(0L)){
handleTool(detail, routing);
}
// 查询BOM的子物料
// 查询子物料
List<QuoteDetailBom> componentParts = baseMapper.queryBomComponentPart(bom);
// 判断BOM Type是否是Purchase且没有子物料时需要将自己当成自己的子物料,用于计算成本
// if ("Purchase".equals(bom.getBomType()) && componentParts.isEmpty()) {
// QuoteDetailBom purchase = getPurchaseComponentPart(detail, bom, componentParts.size()+1);
// componentParts.add(purchase);
// }
// 统计日志记录子物料数量和需要查询成本的数量
int totalComponents = componentParts.size();
long needCostQueryCount = componentParts.stream().filter(c -> "Y".equals(c.getStatus())).count();
log.info("[BOM_PROCESS] 开始处理子物料 - 父物料: {}, 层级: {}, 子物料总数: {}, 需查询成本数: {}",
bom.getPartNo(), level, totalComponents, needCostQueryCount);
// 3查询子物料是否存在BOM信息
int processedCount = 0;
BomNodeData nodeData = new BomNodeData();
nodeData.tree = bom;
nodeData.routing = routing;
nodeData.isRoot = parentId.equals(0L);
int costQuerySuccessCount = 0;
int costQueryFailCount = 0;
for (QuoteDetailBom component : componentParts) {
processedCount++;
component.setCreateBy(detail.getCreateBy());
component.setCreateDate(detail.getCreateDate());
QuoteDetail quoteDetail = createQuoteDetail(detail, component);
// 物料是半成品
QuoteDetailBomTree bomTree = isComponentBom(quoteDetail);
if (Objects.nonNull(bomTree) && "Y".equals(component.getBomFlag())) {
log.debug("[BOM_PROCESS] 递归处理半成品 - PartNo: {}, 当前进度: {}/{}",
component.getComponentPart(), processedCount, totalComponents);
long id = initQuoteDetailBomTree(quoteDetail, bom.getId(), level + 1);
// 如果是BOM 为子料绑定是哪个Bom
component.setBomId(id);
// 半成品 价格为0
QuoteDetail childDetail = createQuoteDetail(detail, component);
QuoteDetailBomTree childBomCheck = isComponentBom(childDetail);
if (Objects.nonNull(childBomCheck) && "Y".equals(component.getBomFlag())) {
log.debug("[BOM_PROCESS] 递归处理半成品 - PartNo: {}", component.getComponentPart());
// 递归收集子节点parentId 占位 -1写入时由 doSaveBomDataRecursive 覆盖
BomNodeData childNode = collectBomData(childDetail, -1L, level + 1, ifsCon);
if (childNode != null) {
nodeData.children.add(childNode);
}
// 半成品价格为 0bomId 在写入阶段填充
component.setBomFlag("Y");
component.setUnitPrice(BigDecimal.ZERO);
component.setActualPrice(BigDecimal.ZERO);
component.setQuotePrice(BigDecimal.ZERO);
} else {
component.setBomFlag("N");
}
// 正式物料在事务外调用 IFS 查询成本
if ("Y".equals(component.getStatus())) {
// 记录查询前的状态用于判断是否成功
BigDecimal beforePrice = component.getUnitPrice();
getFinalPartCost(component, ifsCon);
// 判断成本查询是否成功成功后价格不为0或有变化
if (component.getUnitPrice() != null && component.getUnitPrice().compareTo(BigDecimal.ZERO) > 0) {
costQuerySuccessCount++;
} else {
costQueryFailCount++;
}
}
//新增子物料信息
component.setTreeId(bom.getId());
quoteDetailBomService.save(component);
}
nodeData.components.add(component);
}
// 统计日志记录处理结果
log.info("[BOM_PROCESS] 子物料处理完成 - 父物料: {}, 层级: {}, 处理总数: {}, 成本查询成功: {}, 成本查询失败: {}",
bom.getPartNo(), level, processedCount, costQuerySuccessCount, costQueryFailCount);
bom.getPartNo(), level, costQuerySuccessCount + costQueryFailCount, costQuerySuccessCount, costQueryFailCount);
return bom.getId();
return nodeData;
}
private void handleTool(QuoteDetail detail, QuoteDetailRouting routing) {
// 1清空工具信息
/* quoteDetailToolService.lambdaUpdate()
.eq(QuoteDetailTool::getQuoteDetailId, detail.getId())
.remove();*/
QuoteDetailTool tool = new QuoteDetailTool();
tool.setQuoteDetailId(detail.getId());
List<QuoteDetailTool> quoteDetailTools = quoteDetailToolMapper.queryQuoteDetailTool(tool);
if (Objects.nonNull(routing)){
// 处理工具
// 2根据routing 生成工具信息
if (quoteDetailTools.stream().allMatch(item -> Objects.equals(item.getToolDesc(), "其他"))){
quoteDetailToolService.saveToolByRouting(routing);
}
// 3插入一条其他工具信息
// quoteDetailToolService.saveQuoteDetailOtherTool(routing);
}else {
// quoteDetailToolService.saveQuoteDetailOtherTool(detail);
}
}
private QuoteDetailBom getPurchaseComponentPart(QuoteDetail detail, QuoteDetailBomTree bom,Integer lineSequence ) {
QuoteDetailBom purchase = new QuoteDetailBom();
purchase.setQuoteDetailId(detail.getId());
purchase.setQuoteId(detail.getQuoteId());
purchase.setQuoteDetailItemNo(detail.getItemNo());
purchase.setSite(detail.getSite());
purchase.setBuNo(detail.getBuNo());
purchase.setQuoteNo(detail.getQuoteNo());
purchase.setVersionNo(detail.getVersionNo());
purchase.setPartNo(bom.getPartNo());
purchase.setEngChgLevel(bom.getEngChgLevel());
purchase.setBomType(bom.getBomType());
purchase.setAlternativeNo(bom.getAlternativeNo());
purchase.setComponentPart(bom.getPartNo());
purchase.setPrintUnit(bom.getUmName());
purchase.setQtyPerAssembly(BigDecimal.ONE);
purchase.setComponentScrap(BigDecimal.ZERO);
purchase.setIssueType("");
purchase.setShrinkageFactor(BigDecimal.ZERO);
BigDecimal price = baseMapper.getPartCost(purchase.getSite(),purchase.getPartNo());
purchase.setUnitPrice(price);
purchase.setActualPrice(price);
purchase.setQuotePrice(price);
purchase.setLineSequence(lineSequence);
purchase.setBomFlag("N");
purchase.setStatus(bom.getStatus());
return purchase;
}
// =========================================================================
// 阶段二短事务递归写入无外部 IO
// =========================================================================
/**
* 获取物料成本带重试机制
* @param component 物料组件
* @param ifsServer IFS服务器连接
* 短事务入口将收集好的 BomNodeData 写入数据库
* 调用方在自己的事务上下文中直接调用 doSaveBomDataRecursive 即可 changeQuoteDetailBomTree
*/
private void getFinalPartCost(QuoteDetailBom component, Server ifsServer) {
// 最大重试次数设置为2次平衡成功率和响应时间
final int MAX_RETRY_COUNT = 2;
// 重试间隔毫秒设置为500ms减少等待时间
final long RETRY_INTERVAL_MS = 500;
String partNo = component.getComponentPart();
String site = component.getSite();
log.info("[COST_QUERY] 开始查询物料成本 - PartNo: {}, Site: {}", partNo, site);
PartInformationEntity part = new PartInformationEntity();
part.setSite(site);
part.setPartNo(partNo);
Map<String, String> map = null;
boolean success = false;
String lastErrorMsg = null;
// 重试机制
for (int retryCount = 1; retryCount <= MAX_RETRY_COUNT; retryCount++) {
try {
log.debug("[COST_QUERY] 第 {} 次尝试查询 - PartNo: {}", retryCount, partNo);
map = baseSearchBean.getInventoryValueByPartNo(ifsServer, part);
if (Objects.equals(map.get("resultCode"), "200")) {
// 返回成功
InventoryPartUnitCostSumVo unitCostSumVo = JSONObject.parseObject(map.get("obj"), InventoryPartUnitCostSumVo.class);
BigDecimal unitCost = new BigDecimal(unitCostSumVo.getInventoryValue());
component.setUnitPrice(unitCost);
component.setActualPrice(unitCost);
component.setQuotePrice(unitCost);
log.info("[COST_QUERY] 查询成功 - PartNo: {}, Site: {}, UnitCost: {}, 尝试次数: {}",
partNo, site, unitCost, retryCount);
success = true;
break;
} else {
// 接口返回错误
lastErrorMsg = map.get("resultMsg");
log.warn("[COST_QUERY] 第 {} 次查询失败 - PartNo: {}, Site: {}, ResultCode: {}, ErrorMsg: {}",
retryCount, partNo, site, map.get("resultCode"), lastErrorMsg);
// 判断是否需要重试某些明确的业务错误不需要重试
if (lastErrorMsg != null) {
// 账号密码错误不重试直接抛异常
if (lastErrorMsg.contains("You have entered an invalid username and/or password")) {
log.error("[COST_QUERY] IFS账号密码错误,停止重试 - PartNo: {}", partNo);
throw new RuntimeException(lastErrorMsg);
}
// 业务数据不存在类的错误不重试重试也不会成功
if (lastErrorMsg.contains("不存在") || lastErrorMsg.contains("not exist")
|| lastErrorMsg.contains("No data found") || lastErrorMsg.contains("not found")) {
log.info("[COST_QUERY] 物料成本数据不存在,无需重试 - PartNo: {}, ErrorMsg: {}", partNo, lastErrorMsg);
break;
}
@Transactional
public long doSaveBomData(BomNodeData nodeData, QuoteDetail detail) {
return doSaveBomDataRecursive(nodeData, detail, nodeData.tree.getParentId());
}
// 如果不是最后一次重试等待后继续仅对网络/临时性错误重试
if (retryCount < MAX_RETRY_COUNT) {
log.info("[COST_QUERY] 等待 {}ms 后进行第 {} 次重试 - PartNo: {}",
RETRY_INTERVAL_MS, retryCount + 1, partNo);
Thread.sleep(RETRY_INTERVAL_MS);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("[COST_QUERY] 重试等待被中断 - PartNo: {}, Site: {}", partNo, site);
break;
} catch (RuntimeException e) {
// 重新抛出运行时异常如账号密码错误
throw e;
} catch (Exception e) {
lastErrorMsg = e.getMessage();
log.warn("[COST_QUERY] 第 {} 次查询异常 - PartNo: {}, Site: {}, Exception: {}",
retryCount, partNo, site, e.getMessage());
/**
* 递归写入 BOM 树节点全程无外部 IO事务持有时间极短
*/
private long doSaveBomDataRecursive(BomNodeData nodeData, QuoteDetail detail, Long parentId) {
QuoteDetailBomTree bom = nodeData.tree;
bom.setParentId(parentId);
// 如果不是最后一次重试等待后继续
if (retryCount < MAX_RETRY_COUNT) {
try {
Thread.sleep(RETRY_INTERVAL_MS);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
}
// 写入 BOM 树节点获得自增 ID
save(bom);
long treeId = bom.getId();
// 所有重试都失败设置成本为0
if (!success) {
log.error("[COST_QUERY] 查询失败(已重试 {} 次)- PartNo: {}, Site: {}, 最后错误: {}",
MAX_RETRY_COUNT, partNo, site, lastErrorMsg);
component.setUnitPrice(BigDecimal.ZERO);
component.setActualPrice(BigDecimal.ZERO);
component.setQuotePrice(BigDecimal.ZERO);
}
// 写入 Routing
if (Objects.nonNull(nodeData.routing)) {
nodeData.routing.setTreeId(treeId);
quoteDetailRoutingService.saveQuoteDetailRouting(nodeData.routing);
}
private QuoteDetailBomTree isComponentBom(QuoteDetail component) {
// 根据子物料信息检查是否有BOM信息
return baseMapper.queryPartBom(component);
// 顶级节点处理工具信息
if (nodeData.isRoot) {
handleTool(detail, nodeData.routing);
}
private QuoteDetail createQuoteDetail(QuoteDetail detail, QuoteDetailBom component) {
QuoteDetail quoteDetail = new QuoteDetail();
quoteDetail.setPartNo(component.getComponentPart());
quoteDetail.setSite(component.getSite());
quoteDetail.setBuNo(component.getBuNo());
quoteDetail.setCreateBy(detail.getCreateBy());
quoteDetail.setCreateDate(detail.getCreateDate());
copyCommonFields(detail, quoteDetail);
return quoteDetail;
// 递归写入子节点收集 childNode 对应的真实 treeId用于回填 bomId
// nodeData.children nodeData.components bomFlag="Y" 的条目按顺序一一对应
int childIndex = 0;
for (QuoteDetailBom component : nodeData.components) {
if ("Y".equals(component.getBomFlag()) && childIndex < nodeData.children.size()) {
BomNodeData childNode = nodeData.children.get(childIndex);
long childTreeId = doSaveBomDataRecursive(childNode, detail, treeId);
component.setBomId(childTreeId);
childIndex++;
}
private void copyCommonFields(QuoteDetail source, QuoteDetail target) {
target.setId(source.getId());
target.setQuoteId(source.getQuoteId());
target.setQuoteNo(source.getQuoteNo());
target.setItemNo(source.getItemNo());
target.setVersionNo(source.getVersionNo());
component.setTreeId(treeId);
}
@Override
public List<QuoteDetailBomTree> queryDetailBomTree(QuoteDetail detail) {
// 查询 所有 BOM 信息
List<QuoteDetailBomTree> list = baseMapper.queryDetailBomTree(detail);
// 生成BOM树
QuoteDetailBomTree quoteDetailBomTree = buildTree(list);
// 转换成List
List<QuoteDetailBomTree> treeList = new ArrayList<>();
if (Objects.nonNull(quoteDetailBomTree)){
treeList.add(quoteDetailBomTree);
}
return treeList;
// 写入子物料SQL Server 逐条 save 以确保自增主键回填与原逻辑保持一致
for (QuoteDetailBom component : nodeData.components) {
quoteDetailBomService.save(component);
}
@Override
public List<Long> getAllChildIds(QuoteDetail detail,Long id) {
List<QuoteDetailBomTree> allNodes = getAllNodes(detail);
List<Long> result = new ArrayList<>();
findChildIds(id, allNodes, result);
return result;
return treeId;
}
@Override
public List<QuoteDetailBomTree> queryDetailBomVersion(QuoteDetailBom bom) {
if (StringUtils.isEmpty(bom.getPlmPartNo()) && "Y".equals(bom.getPartStatus())){
// 获取正式料号信息
PartInformationEntity part = baseMapper.queryPart(bom.getSite(), bom.getPartNo());
if (Objects.isNull(part)){
throw new RuntimeException("临时物料:"+bom.getPartNo()+"没有找到绑定的正式料号");
}
bom.setPartNo(part.getPartNo());
}
return baseMapper.queryDetailBomVersion(bom);
}
@Override
public List<QuoteDetailBomTree> queryDetailBomAlternative(QuoteDetailBom bom) {
return baseMapper.queryDetailBomAlternative(bom);
}
// =========================================================================
// changeQuoteDetailBomTree
// =========================================================================
@Override
@Transactional
public void changeQuoteDetailBomTree(QuoteDetailBomTree tree) {
QuoteDetail detail = null;
// 获得选中的树
if (Objects.nonNull(tree.getId())){
if (Objects.nonNull(tree.getId())) {
QuoteDetailBomTree bomTree = getById(tree.getId());
// 获得对应的QuoteDetail
detail = quoteDetailService.getById(bomTree.getQuoteDetailId());
// 获得节点的所有ids
List<Long> ids = getAllChildIds(detail, bomTree.getId());
ids.add(bomTree.getId());
// 删除子节点内容
lambdaUpdate().in(QuoteDetailBomTree::getId,ids).remove();
quoteDetailBomService.lambdaUpdate().in(QuoteDetailBom::getTreeId,ids).remove();
quoteDetailRoutingService.lambdaUpdate().in(QuoteDetailRouting::getTreeId,ids).remove();
// 替换BOM树
detail.setPartNo(tree.getPartNo());
detail.setSite(tree.getSite());
detail.setBuNo(tree.getBuNo());
detail.setBomType(tree.getBomType());
detail.setEngChgLevel(tree.getEngChgLevel());
detail.setAlternativeNo(tree.getAlternativeNo());
long bomId = initQuoteDetailBomTree(detail, bomTree.getParentId(), Optional.ofNullable(tree.getLevel()).orElse(0));
// 先在事务外完成所有 IFS 调用和数据收集避免 IFS 阻塞期间持有数据库行锁
Server ifsCon = resolveIfsCon();
Long newParentId = bomTree.getParentId();
Integer newLevel = Optional.ofNullable(tree.getLevel()).orElse(0);
BomNodeData nodeData = collectBomData(detail, newParentId, newLevel, ifsCon);
// 数据已就绪开始执行 DML事务持有锁的时间仅限于纯 DB 写入阶段
List<Long> ids = getAllChildIds(detail, bomTree.getId());
ids.add(bomTree.getId());
lambdaUpdate().in(QuoteDetailBomTree::getId, ids).remove();
quoteDetailBomService.lambdaUpdate().in(QuoteDetailBom::getTreeId, ids).remove();
quoteDetailRoutingService.lambdaUpdate().in(QuoteDetailRouting::getTreeId, ids).remove();
long bomId = 0;
if (nodeData != null) {
bomId = doSaveBomDataRecursive(nodeData, detail, newParentId);
}
quoteDetailBomService.lambdaUpdate()
.set(QuoteDetailBom::getBomId,bomId)
.eq(QuoteDetailBom::getBomId,bomTree.getId())
.set(QuoteDetailBom::getBomId, bomId)
.eq(QuoteDetailBom::getBomId, bomTree.getId())
.update();
}else {
} else {
detail = new QuoteDetail();
detail.setQuoteId(tree.getQuoteId());
detail.setQuoteNo(tree.getQuoteNo());
@ -441,10 +310,15 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
detail.setBuNo(tree.getBuNo());
detail.setEngChgLevel(tree.getEngChgLevel());
detail.setAlternativeNo(tree.getAlternativeNo());
initQuoteDetailBomTree(detail, 0L,0);
// 先收集数据再写入
Server ifsCon = resolveIfsCon();
BomNodeData nodeData = collectBomData(detail, 0L, 0, ifsCon);
if (nodeData != null) {
doSaveBomDataRecursive(nodeData, detail, 0L);
}
}
// 如果勾选了重新计算成本则执行成本计算
if (Boolean.TRUE.equals(tree.getRecalculateCost()) && detail != null) {
log.info("[BOM_SWITCH] Recalculate cost is enabled, executing cost calculation for QuoteDetailId: {}", detail.getId());
try {
@ -455,62 +329,32 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
throw new RuntimeException("成本计算失败: " + e.getMessage());
}
}
}
public List<QuoteDetailBomTree> getAllNodes(QuoteDetail detail) {
return baseMapper.queryDetailBomTree(detail);
}
// 递归方法
private void findChildIds(Long id, List<QuoteDetailBomTree> allNodes, List<Long> result) {
// 过滤出当前节点的子节点
List<QuoteDetailBomTree> children = allNodes.stream()
.filter(node -> id.equals(node.getParentId()))
.collect(Collectors.toList());
for (QuoteDetailBomTree child : children) {
result.add(child.getId());
findChildIds(child.getId(), allNodes, result); // 递归查找子节点
}
}
private QuoteDetailBomTree buildTree(List<QuoteDetailBomTree> nodes){
QuoteDetailBomTree root = null;
// 找到根节点并构建树
for (QuoteDetailBomTree node : nodes) {
if (node.getParentId() == null || node.getParentId() == 0) {
root = node;
root.addChildren(nodes); // 从根节点开始递归构建树
break;
}
}
return root;
}
// =========================================================================
// againQuoteDetailBomTree
// =========================================================================
@Override
public void againQuoteDetailBomTree(QuoteDetail quoteDetail, Long detailId) {
QuoteDetail detail = new QuoteDetail();
detail.setId(detailId);
detail.setItemNo(quoteDetail.getItemNo());
detail.setCreateBy(quoteDetail.getCreateBy());
detail.setCreateDate(quoteDetail.getCreateDate());
detail.setUpdateBy(quoteDetail.getUpdateBy());
detail.setUpdateDate(quoteDetail.getUpdateDate());
List<QuoteDetailBomTree> list = queryDetailBomTree(detail);
// 创建老ID到新ID的映射关系
Map<Long, Long> oldToNewIdMapping = new HashMap<>();
loopTree(list, quoteDetail, 0L, oldToNewIdMapping);
// 复制完成后更新BOM明细中的bomId引用
// 批量更新 bomId 引用替代原来循环逐条 updateById
updateBomIdReferencesWithCorrectLogic(quoteDetail.getId(), oldToNewIdMapping);
}
private void loopTree (List<QuoteDetailBomTree> list,QuoteDetail quoteDetail,Long parentId){
if (!list.isEmpty()){
private void loopTree(List<QuoteDetailBomTree> list, QuoteDetail quoteDetail, Long parentId) {
if (!list.isEmpty()) {
for (QuoteDetailBomTree tree : list) {
tree.setQuoteId(quoteDetail.getQuoteId());
tree.setQuoteDetailId(quoteDetail.getId());
@ -522,11 +366,9 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
tree.setId(null);
save(tree);
// 调用Bom的again方
quoteDetailBomService.againQuoteDetailBom(tree, id);
// 调用Routing的again方法
quoteDetailRoutingService.againQuoteDetailRouting(tree, id);
loopTree(tree.getList(),quoteDetail,tree.getId());
loopTree(tree.getList(), quoteDetail, tree.getId());
}
}
}
@ -544,12 +386,9 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
tree.setId(null);
save(tree);
// 记录老ID到新ID的映射关系
oldToNewIdMapping.put(oldId, tree.getId());
// 调用Bom的again方
quoteDetailBomService.againQuoteDetailBom(tree, oldId);
// 调用Routing的again方法
quoteDetailRoutingService.againQuoteDetailRouting(tree, oldId);
loopTree(tree.getList(), quoteDetail, tree.getId(), oldToNewIdMapping);
}
@ -557,38 +396,82 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
}
/**
* 根据正确的业务逻辑更新BOM明细中的bomId引用
* bomId应该指向子BOM树节点的ID而不是根节点ID
* 批量更新 BOM 明细中的 bomId 引用替代原来循环逐条 updateById
*/
private void updateBomIdReferencesWithCorrectLogic(Long quoteDetailId, Map<Long, Long> oldToNewIdMapping) {
// 查询新复制的所有BOM明细
if (oldToNewIdMapping.isEmpty()) {
return;
}
List<QuoteDetailBom> bomList = quoteDetailBomService.lambdaQuery()
.eq(QuoteDetailBom::getQuoteDetailId, quoteDetailId)
.isNotNull(QuoteDetailBom::getBomId) // 只处理有bomId的记录
.isNotNull(QuoteDetailBom::getBomId)
.list();
List<QuoteDetailBom> toUpdate = new ArrayList<>();
for (QuoteDetailBom bom : bomList) {
// 获取原来的bomId
Long oldBomId = bom.getBomId();
// 根据老ID找到新ID
Long newBomId = oldToNewIdMapping.get(oldBomId);
Long newBomId = oldToNewIdMapping.get(bom.getBomId());
if (newBomId != null) {
bom.setBomId(newBomId);
quoteDetailBomService.updateById(bom);
toUpdate.add(bom);
}
}
if (!toUpdate.isEmpty()) {
quoteDetailBomService.updateBatchById(toUpdate);
}
}
// =========================================================================
// 其他 Service 方法
// =========================================================================
@Override
public List<QuoteDetailBomTree> queryDetailBomTree(QuoteDetail detail) {
List<QuoteDetailBomTree> list = baseMapper.queryDetailBomTree(detail);
QuoteDetailBomTree quoteDetailBomTree = buildTree(list);
List<QuoteDetailBomTree> treeList = new ArrayList<>();
if (Objects.nonNull(quoteDetailBomTree)) {
treeList.add(quoteDetailBomTree);
}
return treeList;
}
@Override
public List<Long> getAllChildIds(QuoteDetail detail, Long id) {
List<QuoteDetailBomTree> allNodes = getAllNodes(detail);
List<Long> result = new ArrayList<>();
findChildIds(id, allNodes, result);
return result;
}
@Override
public List<QuoteDetailBomTree> queryDetailBomVersion(QuoteDetailBom bom) {
if (StringUtils.isEmpty(bom.getPlmPartNo()) && "Y".equals(bom.getPartStatus())) {
PartInformationEntity part = baseMapper.queryPart(bom.getSite(), bom.getPartNo());
if (Objects.isNull(part)) {
throw new RuntimeException("临时物料:" + bom.getPartNo() + "没有找到绑定的正式料号");
}
bom.setPartNo(part.getPartNo());
}
return baseMapper.queryDetailBomVersion(bom);
}
@Override
public List<QuoteDetailBomTree> queryDetailBomAlternative(QuoteDetailBom bom) {
return baseMapper.queryDetailBomAlternative(bom);
}
public List<QuoteDetailBomTree> getAllNodes(QuoteDetail detail) {
return baseMapper.queryDetailBomTree(detail);
}
@Override
public String queryPart(QuoteDetailBom bom) {
// 测试料号使用
PartInformationEntity entity = baseMapper.queryPart(bom.getSite(), bom.getPartNo());
if (Objects.nonNull(entity)){
if (Objects.nonNull(entity)) {
return entity.getPartNo();
}
// 正式料号使用
PartInformationEntity part = baseMapper.queryPLMPart(bom.getSite(), bom.getPartNo());
if (Objects.nonNull(part)){
if (Objects.nonNull(part)) {
return part.getPartNo();
}
return "";
@ -598,11 +481,9 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
public BigDecimal queryEstimatedMaterialCost(String site, String partNo) {
log.info("queryEstimatedMaterialCost - Request params: site={}, partNo={}", site, partNo);
// 从part表查询物料状态
String status = baseMapper.queryPartStatus(site, partNo);
log.info("queryEstimatedMaterialCost - Query part status from database: status={}", status);
// 非正式物料(status='N'或为空)直接从数据库查询
if (!"Y".equals(status)) {
BigDecimal cost = baseMapper.queryEstimatedMaterialCost(site, partNo);
BigDecimal result = cost != null ? cost : BigDecimal.ZERO;
@ -610,9 +491,7 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
return result;
}
// 正式物料(status='Y')调用IFS接口
try {
// 获取当前用户的IFS连接
String username = ((SysUserEntity) SecurityUtils.getSubject().getPrincipal()).getUsername();
SysUserEntity ifsUser = sysUserDao.selectOne(new QueryWrapper<SysUserEntity>().eq("username", username));
if (ifsUser == null || !org.apache.commons.lang3.StringUtils.isNotBlank(ifsUser.getIfsUsername())
@ -625,7 +504,6 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
}
Server ifsCon = ifsServer.getIfsServer(ifsUser.getIfsUsername(), ifsUser.getIfsPassword());
// 调用IFS接口获取预估材料成本
PartInformationEntity part = new PartInformationEntity();
part.setSite(site);
part.setPartNo(partNo);
@ -656,4 +534,197 @@ public class QuoteDetailBomTreeServiceImpl extends ServiceImpl<QuoteDetailBomTre
public String queryPartType(String site, String partNo) {
return baseMapper.queryPartType(site, partNo);
}
// =========================================================================
// 私有辅助方法
// =========================================================================
/** 获取当前登录用户的 IFS Server 连接(在事务外调用) */
private Server resolveIfsCon() {
String username = ((SysUserEntity) SecurityUtils.getSubject().getPrincipal()).getUsername();
SysUserEntity ifsUser = sysUserDao.selectOne(new QueryWrapper<SysUserEntity>().eq("username", username));
if (ifsUser == null
|| !org.apache.commons.lang3.StringUtils.isNotBlank(ifsUser.getIfsUsername())
|| !org.apache.commons.lang3.StringUtils.isNotBlank(ifsUser.getIfsPassword())) {
throw new RuntimeException("请维护IFS账号和密码!");
}
return ifsServer.getIfsServer(ifsUser.getIfsUsername(), ifsUser.getIfsPassword());
}
private void handleTool(QuoteDetail detail, QuoteDetailRouting routing) {
QuoteDetailTool tool = new QuoteDetailTool();
tool.setQuoteDetailId(detail.getId());
List<QuoteDetailTool> quoteDetailTools = quoteDetailToolMapper.queryQuoteDetailTool(tool);
if (Objects.nonNull(routing)) {
if (quoteDetailTools.stream().allMatch(item -> Objects.equals(item.getToolDesc(), "其他"))) {
quoteDetailToolService.saveToolByRouting(routing);
}
}
}
private QuoteDetailBom getPurchaseComponentPart(QuoteDetail detail, QuoteDetailBomTree bom, Integer lineSequence) {
QuoteDetailBom purchase = new QuoteDetailBom();
purchase.setQuoteDetailId(detail.getId());
purchase.setQuoteId(detail.getQuoteId());
purchase.setQuoteDetailItemNo(detail.getItemNo());
purchase.setSite(detail.getSite());
purchase.setBuNo(detail.getBuNo());
purchase.setQuoteNo(detail.getQuoteNo());
purchase.setVersionNo(detail.getVersionNo());
purchase.setPartNo(bom.getPartNo());
purchase.setEngChgLevel(bom.getEngChgLevel());
purchase.setBomType(bom.getBomType());
purchase.setAlternativeNo(bom.getAlternativeNo());
purchase.setComponentPart(bom.getPartNo());
purchase.setPrintUnit(bom.getUmName());
purchase.setQtyPerAssembly(BigDecimal.ONE);
purchase.setComponentScrap(BigDecimal.ZERO);
purchase.setIssueType("");
purchase.setShrinkageFactor(BigDecimal.ZERO);
BigDecimal price = baseMapper.getPartCost(purchase.getSite(), purchase.getPartNo());
purchase.setUnitPrice(price);
purchase.setActualPrice(price);
purchase.setQuotePrice(price);
purchase.setLineSequence(lineSequence);
purchase.setBomFlag("N");
purchase.setStatus(bom.getStatus());
return purchase;
}
/**
* 获取物料成本带重试机制
*/
private void getFinalPartCost(QuoteDetailBom component, Server ifsServer) {
final int MAX_RETRY_COUNT = 2;
final long RETRY_INTERVAL_MS = 500;
String partNo = component.getComponentPart();
String site = component.getSite();
log.info("[COST_QUERY] 开始查询物料成本 - PartNo: {}, Site: {}", partNo, site);
PartInformationEntity part = new PartInformationEntity();
part.setSite(site);
part.setPartNo(partNo);
Map<String, String> map = null;
boolean success = false;
String lastErrorMsg = null;
for (int retryCount = 1; retryCount <= MAX_RETRY_COUNT; retryCount++) {
try {
log.debug("[COST_QUERY] 第 {} 次尝试查询 - PartNo: {}", retryCount, partNo);
map = baseSearchBean.getInventoryValueByPartNo(ifsServer, part);
if (Objects.equals(map.get("resultCode"), "200")) {
InventoryPartUnitCostSumVo unitCostSumVo = JSONObject.parseObject(map.get("obj"), InventoryPartUnitCostSumVo.class);
BigDecimal unitCost = new BigDecimal(unitCostSumVo.getInventoryValue());
component.setUnitPrice(unitCost);
component.setActualPrice(unitCost);
component.setQuotePrice(unitCost);
log.info("[COST_QUERY] 查询成功 - PartNo: {}, Site: {}, UnitCost: {}, 尝试次数: {}",
partNo, site, unitCost, retryCount);
success = true;
break;
} else {
lastErrorMsg = map.get("resultMsg");
log.warn("[COST_QUERY] 第 {} 次查询失败 - PartNo: {}, Site: {}, ResultCode: {}, ErrorMsg: {}",
retryCount, partNo, site, map.get("resultCode"), lastErrorMsg);
if (lastErrorMsg != null) {
if (lastErrorMsg.contains("You have entered an invalid username and/or password")) {
log.error("[COST_QUERY] IFS账号密码错误,停止重试 - PartNo: {}", partNo);
throw new RuntimeException(lastErrorMsg);
}
if (lastErrorMsg.contains("不存在") || lastErrorMsg.contains("not exist")
|| lastErrorMsg.contains("No data found") || lastErrorMsg.contains("not found")) {
log.info("[COST_QUERY] 物料成本数据不存在,无需重试 - PartNo: {}, ErrorMsg: {}", partNo, lastErrorMsg);
break;
}
}
if (retryCount < MAX_RETRY_COUNT) {
log.info("[COST_QUERY] 等待 {}ms 后进行第 {} 次重试 - PartNo: {}",
RETRY_INTERVAL_MS, retryCount + 1, partNo);
Thread.sleep(RETRY_INTERVAL_MS);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("[COST_QUERY] 重试等待被中断 - PartNo: {}, Site: {}", partNo, site);
break;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
lastErrorMsg = e.getMessage();
log.warn("[COST_QUERY] 第 {} 次查询异常 - PartNo: {}, Site: {}, Exception: {}",
retryCount, partNo, site, e.getMessage());
if (retryCount < MAX_RETRY_COUNT) {
try {
Thread.sleep(RETRY_INTERVAL_MS);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
}
if (!success) {
log.error("[COST_QUERY] 查询失败(已重试 {} 次)- PartNo: {}, Site: {}, 最后错误: {}",
MAX_RETRY_COUNT, partNo, site, lastErrorMsg);
component.setUnitPrice(BigDecimal.ZERO);
component.setActualPrice(BigDecimal.ZERO);
component.setQuotePrice(BigDecimal.ZERO);
}
}
private QuoteDetailBomTree isComponentBom(QuoteDetail component) {
return baseMapper.queryPartBom(component);
}
private QuoteDetail createQuoteDetail(QuoteDetail detail, QuoteDetailBom component) {
QuoteDetail quoteDetail = new QuoteDetail();
quoteDetail.setPartNo(component.getComponentPart());
quoteDetail.setSite(component.getSite());
quoteDetail.setBuNo(component.getBuNo());
quoteDetail.setCreateBy(detail.getCreateBy());
quoteDetail.setCreateDate(detail.getCreateDate());
copyCommonFields(detail, quoteDetail);
return quoteDetail;
}
private void copyCommonFields(QuoteDetail source, QuoteDetail target) {
target.setId(source.getId());
target.setQuoteId(source.getQuoteId());
target.setQuoteNo(source.getQuoteNo());
target.setItemNo(source.getItemNo());
target.setVersionNo(source.getVersionNo());
}
private void findChildIds(Long id, List<QuoteDetailBomTree> allNodes, List<Long> result) {
List<QuoteDetailBomTree> children = allNodes.stream()
.filter(node -> id.equals(node.getParentId()))
.collect(Collectors.toList());
for (QuoteDetailBomTree child : children) {
result.add(child.getId());
findChildIds(child.getId(), allNodes, result);
}
}
private QuoteDetailBomTree buildTree(List<QuoteDetailBomTree> nodes) {
QuoteDetailBomTree root = null;
for (QuoteDetailBomTree node : nodes) {
if (node.getParentId() == null || node.getParentId() == 0) {
root = node;
root.addChildren(nodes);
break;
}
}
return root;
}
}

3
src/main/java/com/spring/modules/quote/service/impl/QuoteDetailServiceImpl.java

@ -838,7 +838,8 @@ public class QuoteDetailServiceImpl extends ServiceImpl<QuoteDetailMapper, Quote
public QuoteDetail queryQuoteDetailById(Long id) {
QuoteDetail detail = new QuoteDetail();
detail.setId(id);
return baseMapper.queryQuoteDetail(detail).isEmpty() ? null : baseMapper.queryQuoteDetail(detail).get(0);
List<QuoteDetail> list = baseMapper.queryQuoteDetail(detail);
return list.isEmpty() ? null : list.get(0);
}
@Override

21
src/main/resources/mapper/quote/QuoteDetailMapper.xml

@ -65,11 +65,11 @@
qd.quote_tax_total_price,
qd.quote_tax_unit_price,
qd.currency1,
dbo.plm_get_dictDataLabel('plm_customer_information_customer_customer_currency', qd.currency1, qd.site) as currencyDesc1,
sdd1.dict_label as currencyDesc1,
qd.final_transaction_price,
qd.exchange_rate1,
qd.currency2,
dbo.plm_get_dictDataLabel('plm_customer_information_customer_customer_currency', qd.currency2, qd.site) as currencyDesc2,
sdd2.dict_label as currencyDesc2,
qd.exchange_rate2,
qd.moq,
qd.currency_total_cost1,
@ -88,6 +88,10 @@
from plm_quote_detail qd
left join plm_quote q on qd.quote_id = q.id
left join part pp on qd.part_no = pp.part_no and qd.site = pp.site
left join sys_dict_data sdd1 on sdd1.dict_type = 'plm_customer_information_customer_customer_currency'
and sdd1.dict_value = qd.currency1 and sdd1.site = qd.site
left join sys_dict_data sdd2 on sdd2.dict_type = 'plm_customer_information_customer_customer_currency'
and sdd2.dict_value = qd.currency2 and sdd2.site = qd.site
<where>
<if test="id != null">
and qd.id = #{id}
@ -177,12 +181,10 @@
qd.quote_tax_total_price,
qd.quote_tax_unit_price,
qd.currency1,
dbo.plm_get_dictDataLabel('plm_customer_information_customer_customer_currency', qd.currency1,
qd.site) as currencyDesc1,
sdd1.dict_label as currencyDesc1,
qd.exchange_rate1,
qd.currency2,
dbo.plm_get_dictDataLabel('plm_customer_information_customer_customer_currency', qd.currency2,
qd.site) as currencyDesc2,
sdd2.dict_label as currencyDesc2,
qd.exchange_rate2,
qd.moq,
qd.currency_total_cost1,
@ -196,8 +198,11 @@
qd.adjust_else_cost
from plm_quote_detail qd
left join plm_quote q on qd.quote_id = q.id
left join part pp
on qd.part_no = pp.part_no and qd.site = pp.site
left join part pp on qd.part_no = pp.part_no and qd.site = pp.site
left join sys_dict_data sdd1 on sdd1.dict_type = 'plm_customer_information_customer_customer_currency'
and sdd1.dict_value = qd.currency1 and sdd1.site = qd.site
left join sys_dict_data sdd2 on sdd2.dict_type = 'plm_customer_information_customer_customer_currency'
and sdd2.dict_value = qd.currency2 and sdd2.site = qd.site
<where>
<if test="params.quoteId != null">
and qd.quote_id = #{params.quoteId}

49
src/main/resources/mapper/quote/QuoteMapper.xml

@ -137,7 +137,8 @@
</where>
</select>
<!-- 报价单查询条件片段(供 Count 和 Data 两个查询共用) -->
<!-- 报价单查询条件片段(供 Count 和 Data 两个查询共用)-->
<!-- purchase/quoter 过滤通过 LEFT JOIN sys_user 后直接比较 user_display,避免逐行调用标量函数 -->
<sql id="quotePageWhereConditions">
<if test="params.id != null">
and q.id = #{params.id}
@ -170,10 +171,10 @@
and p.project_name like #{params.projectDesc}
</if>
<if test="params.purchase != null and params.purchase != ''">
and dbo.plm_get_user_display(q.site, q.purchase) like #{params.purchase}
and su_purchase.user_display like #{params.purchase}
</if>
<if test="params.quoter != null and params.quoter != ''">
and dbo.plm_get_user_display(q.site, q.quoter) like #{params.quoter}
and su_quoter.user_display like #{params.quoter}
</if>
<if test="params.customerInquiryNo != null and params.customerInquiryNo != ''">
and q.customer_inquiry_no like #{params.customerInquiryNo}
@ -213,20 +214,27 @@
</sql>
<!-- 报价单分页-总数(直接 COUNT,不经过 PaginationInterceptor) -->
<!-- 仅当条件涉及 nodeId/approvalUsername 时才需要 join 审批流表,否则去掉这两个代价较高的 join -->
<select id="queryQuotePageCount" resultType="long">
SELECT COUNT(1)
FROM plm_quote q
left join plm_customer_information c on q.customer_no = c.customer_no
left join plm_project_info p on q.project_no = p.project_id and q.site = p.site
left join sys_user su_purchase on q.purchase = su_purchase.username
left join sys_user su_quoter on q.quoter = su_quoter.username
<if test="params.nodeId != null and params.nodeId != '' or params.approvalUsername != null and params.approvalUsername != ''">
left join plm_request_header prh on q.site = prh.site and prh.menu_id = '5011'
left join plm_request_node prn on q.site = prn.site and prh.classification_no = prn.classification_no
and prh.workflow_id = prn.workflow_id and q.step_id = prn.step_id
</if>
<where>
<include refid="quotePageWhereConditions"/>
</where>
</select>
<!-- 报价单分页-数据(原生 OFFSET/FETCH NEXT,按报价单号倒排,不经过 PaginationInterceptor) -->
<!-- 用 LEFT JOIN sys_user 替代 plm_get_user_display 标量函数,避免逐行调用,大幅提升分页性能 -->
<!-- 审批流 JOIN 仅在有 nodeId/approvalUsername 条件时才加入 -->
<select id="queryQuotePageData" resultType="com.spring.modules.quote.entity.Quote">
select q.id,
q.quote_no,
@ -242,8 +250,7 @@
q.purchase,
q.quoter,
q.currency,
dbo.plm_get_dictDataLabel('plm_customer_information_customer_customer_currency', q.currency,
q.site) as currencyDesc,
sdd_currency.dict_label as currencyDesc,
q.status,
q.customer_inquiry_no,
q.inside_inquiry_no,
@ -266,10 +273,10 @@
q.reject_flag,
q.reject_step_id,
q.annual_sales,
dbo.plm_get_user_display(q.site, q.quoter) as quoterName,
dbo.plm_get_user_display(q.site, q.purchase) as purchaseName,
su_quoter.user_display as quoterName,
su_purchase.user_display as purchaseName,
p.final_customer_id as finalCustomerNo,
dbo.plm_get_dictDataLabel('finalCustomer', p.final_customer_id, q.site) as finalCustomerDesc,
sdd_final.dict_label as finalCustomerDesc,
prn.node_id as nodeId,
prn.node_name as nodeName,
prn.is_reject as isReject,
@ -281,6 +288,12 @@
from plm_quote q
left join plm_customer_information c on q.customer_no = c.customer_no
left join plm_project_info p on q.project_no = p.project_id and q.site = p.site
left join sys_user su_purchase on q.purchase = su_purchase.username
left join sys_user su_quoter on q.quoter = su_quoter.username
left join sys_dict_data sdd_currency on sdd_currency.dict_type = 'plm_customer_information_customer_customer_currency'
and sdd_currency.dict_value = q.currency and sdd_currency.site = q.site
left join sys_dict_data sdd_final on sdd_final.dict_type = 'finalCustomer'
and sdd_final.dict_value = p.final_customer_id and sdd_final.site = q.site
left join plm_request_header prh on q.site = prh.site and prh.menu_id = '5011'
left join plm_request_node prn on q.site = prn.site and prh.classification_no = prn.classification_no
and prh.workflow_id = prn.workflow_id and q.step_id = prn.step_id
@ -312,8 +325,7 @@
q.purchase,
q.quoter,
q.currency,
dbo.plm_get_dictDataLabel('plm_customer_information_customer_customer_currency', q.currency,
q.site) as currencyDesc,
sdd_currency.dict_label as currencyDesc,
q.status,
q.customer_inquiry_no,
q.inside_inquiry_no,
@ -332,14 +344,19 @@
q.cost_model,
q.markup,
q.chip_price,
dbo.plm_get_user_display(q.site, q.quoter) as quoterName,
dbo.plm_get_user_display(q.site, q.purchase) as purchaseName,
su_quoter.user_display as quoterName,
su_purchase.user_display as purchaseName,
p.final_customer_id as finalCustomerNo,
dbo.plm_get_dictDataLabel('finalCustomer', p.final_customer_id,
q.site) as finalCustomerDesc
sdd_final.dict_label as finalCustomerDesc
from plm_quote q
left join plm_customer_information c on q.customer_no = c.customer_no
left join plm_project_info p on q.project_no = p.project_id and q.site = p.site
left join sys_user su_purchase on q.purchase = su_purchase.username
left join sys_user su_quoter on q.quoter = su_quoter.username
left join sys_dict_data sdd_currency on sdd_currency.dict_type = 'plm_customer_information_customer_customer_currency'
and sdd_currency.dict_value = q.currency and sdd_currency.site = q.site
left join sys_dict_data sdd_final on sdd_final.dict_type = 'finalCustomer'
and sdd_final.dict_value = p.final_customer_id and sdd_final.site = q.site
<where>
<if test="id != null">
and q.id = #{id}
@ -369,10 +386,10 @@
and p.project_name like #{ projectDesc}
</if>
<if test="purchase != null and purchase != ''">
and dbo.plm_get_user_display(q.site, q.purchase) like #{ purchase}
and su_purchase.user_display like #{ purchase}
</if>
<if test="quoter != null and quoter != ''">
and dbo.plm_get_user_display(q.site, q.quoter) like #{ quoter}
and su_quoter.user_display like #{ quoter}
</if>
<if test="customerInquiryNo != null and customerInquiryNo != ''">
and q.customer_inquiry_no like #{ customerInquiryNo}

Loading…
Cancel
Save