From a82d847bbb39e2417d4c9773ebc4fc51fdb0b8fc Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期二, 10 十一月 2026 13:31:31 +0800
Subject: [PATCH] feat(crm): 完善投标保证金与报价单关联及作废功能
---
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/bidbond/CrmBidBondServiceImpl.java | 102 +++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 99 insertions(+), 3 deletions(-)
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/bidbond/CrmBidBondServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/bidbond/CrmBidBondServiceImpl.java
index 12e86b7..846ec7f 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/bidbond/CrmBidBondServiceImpl.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/bidbond/CrmBidBondServiceImpl.java
@@ -11,10 +11,13 @@
import cn.iocoder.yudao.module.crm.controller.admin.bidbond.vo.CrmBidBondSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.bidbond.CrmBidBondDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationDO;
import cn.iocoder.yudao.module.crm.dal.mysql.bidbond.CrmBidBondMapper;
+import cn.iocoder.yudao.module.crm.dal.mysql.quotation.CrmSaleQuotationMapper;
import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.crm.enums.bidbond.CrmBidBondRefundStatusEnum;
import cn.iocoder.yudao.module.crm.enums.bidbond.CrmBidResultEnum;
+import cn.iocoder.yudao.module.crm.enums.quotation.CrmSaleQuotationStatusEnum;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi;
import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderRespDTO;
@@ -48,6 +51,8 @@
@Resource
private CrmBidBondMapper bidBondMapper;
@Resource
+ private CrmSaleQuotationMapper saleQuotationMapper;
+ @Resource
private CrmCustomerService customerService;
@Resource
private ErpSaleOrderApi erpSaleOrderApi;
@@ -59,23 +64,42 @@
bond.setId(null);
bond.setNo(generateBondNo());
bidBondMapper.insert(bond);
+ // 鍥炲啓鎶ヤ环鍗曠粦瀹氬叧绯�
+ if (bond.getQuotationId() != null) {
+ saleQuotationMapper.bindBidBond(bond.getQuotationId(), bond.getId(), bond.getNo());
+ }
return bond.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateBidBond(CrmBidBondSaveReqVO updateReqVO) {
- validateBidBondExists(updateReqVO.getId());
+ CrmBidBondDO oldBond = validateBidBondExists(updateReqVO.getId());
CrmBidBondDO updateObj = validateAndBuild(updateReqVO);
updateObj.setId(updateReqVO.getId());
// 鍗曞彿涓嶅厑璁镐慨鏀癸紝淇濇寔鍘熷��
updateObj.setNo(null);
bidBondMapper.updateByIdIncludingNulls(updateObj);
+ // 鎶ヤ环鍗曞彉鍖栨椂锛氬厛瑙g粦鏃ф姤浠峰崟锛屽啀缁戝畾鏂版姤浠峰崟
+ if (oldBond.getQuotationId() != null && !oldBond.getQuotationId().equals(updateObj.getQuotationId())) {
+ saleQuotationMapper.bindBidBond(oldBond.getQuotationId(), null, null);
+ }
+ if (updateObj.getQuotationId() != null && !updateObj.getQuotationId().equals(oldBond.getQuotationId())) {
+ saleQuotationMapper.bindBidBond(updateObj.getQuotationId(), updateObj.getId(), oldBond.getNo());
+ }
}
@Override
+ @Transactional(rollbackFor = Exception.class)
public void deleteBidBond(Long id) {
- validateBidBondExists(id);
+ CrmBidBondDO bond = validateBidBondExists(id);
+ if (CrmBidResultEnum.WON.getResult().equals(bond.getBidResult()) && bond.getQuotationId() != null) {
+ throw ServiceExceptionUtil.exception(ErrorCodeConstants.BID_BOND_DELETE_FAIL_WON);
+ }
+ // 瑙i櫎鎶ヤ环鍗曠粦瀹氬悗鍐嶅垹闄�
+ if (bond.getQuotationId() != null) {
+ saleQuotationMapper.bindBidBond(bond.getQuotationId(), null, null);
+ }
bidBondMapper.deleteById(id);
}
@@ -177,7 +201,10 @@
saleOrderNo = order.getNo();
}
- // 5. 閫�鍥炵姸鎬佷笌閲戦
+ // 5. 鍏宠仈鎶ヤ环鍗�
+ CrmSaleQuotationDO quotation = validateQuotation(reqVO, bidResult);
+
+ // 6. 閫�鍥炵姸鎬佷笌閲戦
Integer refundStatus = resolveRefundStatus(reqVO.getRefundStatus(), bidResult);
validateRefund(reqVO, bondAmount, refundStatus);
@@ -186,7 +213,53 @@
bond.setBidResult(bidResult);
bond.setRefundStatus(refundStatus);
bond.setSaleOrderNo(saleOrderNo);
+ bond.setQuotationNo(quotation != null ? quotation.getNo() : null);
+ bond.setQuotationRound(quotation != null && quotation.getRevisionNo() != null
+ ? quotation.getRevisionNo() : 0);
return bond;
+ }
+
+ /**
+ * 鏍¢獙骞跺姞杞藉叧鑱旀姤浠峰崟
+ */
+ private CrmSaleQuotationDO validateQuotation(CrmBidBondSaveReqVO reqVO, Integer bidResult) {
+ // 涓爣鏃舵姤浠峰崟蹇呭~锛屼繚璇佷腑鏍囧悗鍙互鎹杞悎鍚�
+ if (CrmBidResultEnum.WON.getResult().equals(bidResult) && reqVO.getQuotationId() == null) {
+ throw new ServiceException(ErrorCodeConstants.BID_BOND_WON_QUOTATION_REQUIRED.getCode(),
+ "涓爣缁撴灉涓恒�愬凡涓爣銆戞椂蹇呴』鍏宠仈鎶ヤ环鍗曪紝鍚﹀垯鏃犳硶鍑鎶ヤ环鍗曡浆鍚堝悓銆�"
+ + "璇烽�夋嫨鏈涓爣鐨勯攢鍞姤浠峰崟鍚庨噸鏂颁繚瀛樸��");
+ }
+ if (reqVO.getQuotationId() == null) {
+ return null;
+ }
+ CrmSaleQuotationDO quotation = saleQuotationMapper.selectById(reqVO.getQuotationId());
+ if (quotation == null) {
+ throw new ServiceException(ErrorCodeConstants.BID_BOND_QUOTATION_NOT_EXISTS.getCode(),
+ "鍏宠仈鎶ヤ环鍗�(ID=" + reqVO.getQuotationId() + ")涓嶅瓨鍦紝璇峰埛鏂版姤浠峰崟鍒楄〃鍚庨噸鏂伴�夋嫨銆�");
+ }
+ if (!Objects.equals(reqVO.getCustomerId(), quotation.getCustomerId())) {
+ throw new ServiceException(ErrorCodeConstants.BID_BOND_QUOTATION_CUSTOMER_MISMATCH.getCode(),
+ "鍏宠仈鎶ヤ环鍗昜" + quotation.getNo() + "]鐨勫鎴�(ID=" + quotation.getCustomerId()
+ + ")涓庝繚璇侀噾瀹㈡埛(ID=" + reqVO.getCustomerId()
+ + ")涓嶄竴鑷淬�傝鏀归�夎瀹㈡埛鍚嶄笅鐨勬姤浠峰崟锛屾垨鍏堣皟鏁翠繚璇侀噾瀹㈡埛銆�");
+ }
+ if (!Set.of(CrmSaleQuotationStatusEnum.DRAFT.getStatus(),
+ CrmSaleQuotationStatusEnum.APPROVE.getStatus(),
+ CrmSaleQuotationStatusEnum.REJECT.getStatus()).contains(quotation.getStatus())) {
+ throw new ServiceException(ErrorCodeConstants.BID_BOND_QUOTATION_STATUS_INVALID.getCode(),
+ "鍏宠仈鎶ヤ环鍗昜" + quotation.getNo() + "]褰撳墠鐘舵�佷负銆�"
+ + getQuotationStatusName(quotation.getStatus())
+ + "銆戯紝鍙湁銆岃崏绋裤�佸鎵归�氳繃銆佸鎵逛笉閫氳繃銆嶇殑鎶ヤ环鍗曞彲浠ョ粦瀹氭姇鏍囥��"
+ + "璇锋敼閫夊叾浠栨姤浠峰崟锛屾垨鍏堥噸鏂版姤浠峰悗鍐嶇粦瀹氥��");
+ }
+ if (quotation.getBidBondId() != null && !Objects.equals(quotation.getBidBondId(), reqVO.getId())) {
+ CrmBidBondDO boundBond = bidBondMapper.selectById(quotation.getBidBondId());
+ throw new ServiceException(ErrorCodeConstants.BID_BOND_QUOTATION_ALREADY_BOUND.getCode(),
+ "鍏宠仈鎶ヤ环鍗昜" + quotation.getNo() + "]宸茬粦瀹氭姇鏍嘯"
+ + (boundBond != null ? boundBond.getNo() : "ID=" + quotation.getBidBondId())
+ + "]锛屽悓涓�鎶ヤ环鍗曞彧鑳界粦瀹氫竴寮犳姇鏍囥�傝鏀归�夊叾浠栨姤浠峰崟锛屾垨鍏堝湪璇ユ姇鏍囦腑瑙i櫎鍏宠仈銆�");
+ }
+ return quotation;
}
private Integer resolveRefundStatus(Integer refundStatus, Integer bidResult) {
@@ -281,6 +354,7 @@
Map<Long, CrmCustomerDO> customerMap = customerIds.isEmpty() ? Map.of()
: customerService.getCustomerMap(customerIds);
Map<Long, ErpSaleOrderRespDTO> orderMap = loadSaleOrderMap(bonds);
+ Map<Long, CrmSaleQuotationDO> quotationMap = loadQuotationMap(bonds);
return bonds.stream().map(bond -> {
CrmBidBondRespVO respVO = BeanUtils.toBean(bond, CrmBidBondRespVO.class);
@@ -293,8 +367,25 @@
respVO.setSaleOrderNo(order.getNo());
respVO.setSaleOrderAmount(order.getTotalPrice());
}
+ CrmSaleQuotationDO quotation = bond.getQuotationId() == null ? null
+ : quotationMap.get(bond.getQuotationId());
+ if (quotation != null) {
+ respVO.setQuotationNo(quotation.getNo());
+ respVO.setQuotationStatus(quotation.getStatus());
+ respVO.setQuotationTotalPrice(quotation.getTotalPrice());
+ }
return respVO;
}).collect(Collectors.toList());
+ }
+
+ private Map<Long, CrmSaleQuotationDO> loadQuotationMap(List<CrmBidBondDO> bonds) {
+ Set<Long> quotationIds = bonds.stream().map(CrmBidBondDO::getQuotationId)
+ .filter(Objects::nonNull).collect(Collectors.toSet());
+ if (quotationIds.isEmpty()) {
+ return Map.of();
+ }
+ return saleQuotationMapper.selectByIds(quotationIds).stream()
+ .collect(Collectors.toMap(CrmSaleQuotationDO::getId, q -> q, (a, b) -> a));
}
/**
@@ -324,6 +415,11 @@
return orderMap;
}
+ private String getQuotationStatusName(Integer status) {
+ CrmSaleQuotationStatusEnum statusEnum = CrmSaleQuotationStatusEnum.getByStatus(status);
+ return statusEnum != null ? statusEnum.getName() : "鏈煡鐘舵��(" + status + ")";
+ }
+
private String getSaleOrderStatusName(Integer status) {
if (status == null) {
return "鏈煡";
--
Gitblit v1.9.3