package cn.iocoder.yudao.module.srm.service.bond;
|
|
import cn.hutool.core.util.StrUtil;
|
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
|
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO;
|
import cn.iocoder.yudao.module.srm.controller.admin.bond.vo.SrmBidBondPageReqVO;
|
import cn.iocoder.yudao.module.srm.controller.admin.bond.vo.SrmBidBondRespVO;
|
import cn.iocoder.yudao.module.srm.controller.admin.bond.vo.SrmBidBondSaveReqVO;
|
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmBidBondDO;
|
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmInquiryDO;
|
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
|
import cn.iocoder.yudao.module.srm.dal.mysql.SrmBidBondMapper;
|
import cn.iocoder.yudao.module.srm.dal.mysql.SrmInquiryMapper;
|
import cn.iocoder.yudao.module.srm.enums.BidBondRefundStatusEnum;
|
import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
|
import cn.iocoder.yudao.module.srm.service.supplier.SrmSupplierService;
|
import jakarta.annotation.Resource;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.validation.annotation.Validated;
|
|
import java.math.BigDecimal;
|
import java.time.LocalDate;
|
import java.time.format.DateTimeFormatter;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Set;
|
import java.util.stream.Collectors;
|
|
/**
|
* SRM 投标保证金 Service 实现
|
*/
|
@Service
|
@Validated
|
@Slf4j
|
public class SrmBidBondServiceImpl implements SrmBidBondService {
|
|
private static final DateTimeFormatter NO_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMM");
|
|
@Resource
|
private SrmBidBondMapper bidBondMapper;
|
@Resource
|
private SrmInquiryMapper inquiryMapper;
|
@Resource
|
private SrmSupplierService supplierService;
|
@Resource
|
private ErpPurchaseOrderApi erpPurchaseOrderApi;
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public Long createBidBond(SrmBidBondSaveReqVO createReqVO) {
|
validateBidBond(createReqVO);
|
SrmBidBondDO bond = BeanUtils.toBean(createReqVO, SrmBidBondDO.class);
|
bond.setId(null);
|
bond.setNo(generateBondNo());
|
bond.setWon(Boolean.TRUE.equals(createReqVO.getWon()));
|
bond.setRefundStatus(resolveRefundStatus(createReqVO));
|
if (bond.getRefundStatus() == null) {
|
bond.setRefundStatus(BidBondRefundStatusEnum.NOT_REFUNDED.getStatus());
|
}
|
bidBondMapper.insert(bond);
|
return bond.getId();
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void updateBidBond(SrmBidBondSaveReqVO updateReqVO) {
|
validateBidBondExists(updateReqVO.getId());
|
validateBidBond(updateReqVO);
|
SrmBidBondDO updateObj = BeanUtils.toBean(updateReqVO, SrmBidBondDO.class);
|
updateObj.setNo(null);
|
updateObj.setWon(Boolean.TRUE.equals(updateReqVO.getWon()));
|
updateObj.setRefundStatus(resolveRefundStatus(updateReqVO));
|
bidBondMapper.updateById(updateObj);
|
}
|
|
@Override
|
public void deleteBidBond(Long id) {
|
validateBidBondExists(id);
|
bidBondMapper.deleteById(id);
|
}
|
|
@Override
|
public SrmBidBondDO getBidBond(Long id) {
|
return bidBondMapper.selectById(id);
|
}
|
|
@Override
|
public SrmBidBondRespVO getBidBondDetail(Long id) {
|
SrmBidBondDO bond = validateBidBondExists(id);
|
return buildRespList(List.of(bond)).get(0);
|
}
|
|
@Override
|
public PageResult<SrmBidBondRespVO> getBidBondPage(SrmBidBondPageReqVO pageReqVO) {
|
PageResult<SrmBidBondDO> page = bidBondMapper.selectPage(pageReqVO);
|
return new PageResult<>(buildRespList(page.getList()), page.getTotal());
|
}
|
|
// ========== 校验 ==========
|
|
private void validateBidBond(SrmBidBondSaveReqVO reqVO) {
|
if (reqVO.getBondAmount() == null || reqVO.getBondAmount().compareTo(BigDecimal.ZERO) < 0) {
|
throw new ServiceException(ErrorCodeConstants.BOND_AMOUNT_INVALID.getCode(),
|
"保证金金额为【" + reqVO.getBondAmount() + "】,不能小于 0,请填写正确的缴纳金额。");
|
}
|
SrmSupplierDO supplier = supplierService.getSupplier(reqVO.getSupplierId());
|
if (supplier == null) {
|
throw new ServiceException(ErrorCodeConstants.SUPPLIER_NOT_EXISTS.getCode(),
|
"供应商ID=" + reqVO.getSupplierId() + " 不存在,请刷新供应商列表后重新选择。");
|
}
|
// 关联询价单:校验存在,并在未填项目名称时自动带出询价标题
|
if (reqVO.getInquiryId() != null) {
|
SrmInquiryDO inquiry = inquiryMapper.selectById(reqVO.getInquiryId());
|
if (inquiry == null) {
|
throw new ServiceException(ErrorCodeConstants.INQUIRY_NOT_EXISTS.getCode(),
|
"关联询价单ID=" + reqVO.getInquiryId() + " 不存在,请刷新询价单列表后重新选择。");
|
}
|
if (StrUtil.isBlank(reqVO.getProjectName())) {
|
reqVO.setProjectName(inquiry.getTitle());
|
}
|
}
|
// 关联采购订单:校验存在且供应商一致
|
if (reqVO.getPurchaseOrderId() != null) {
|
ErpPurchaseOrderRespDTO order = erpPurchaseOrderApi.getPurchaseOrder(reqVO.getPurchaseOrderId())
|
.getCheckedData();
|
if (order == null) {
|
throw new ServiceException(ErrorCodeConstants.BOND_NOT_EXISTS.getCode(),
|
"关联采购订单ID=" + reqVO.getPurchaseOrderId() + " 不存在,请刷新采购订单列表后重新选择。");
|
}
|
if (!reqVO.getSupplierId().equals(order.getSupplierId())) {
|
throw new ServiceException(ErrorCodeConstants.BOND_SUPPLIER_MISMATCH.getCode(),
|
"关联采购订单【" + order.getNo() + "】的供应商ID=" + order.getSupplierId()
|
+ ",与保证金供应商ID=" + reqVO.getSupplierId() + " 不一致,请重新选择订单或供应商。");
|
}
|
if (reqVO.getWon() == null) {
|
reqVO.setWon(true);
|
}
|
}
|
// 退回金额/日期
|
if (reqVO.getRefundAmount() != null) {
|
if (reqVO.getRefundAmount().compareTo(BigDecimal.ZERO) <= 0
|
|| reqVO.getRefundAmount().compareTo(reqVO.getBondAmount()) > 0) {
|
throw new ServiceException(ErrorCodeConstants.BOND_REFUND_AMOUNT_INVALID.getCode(),
|
"退回金额为【" + reqVO.getRefundAmount() + "】,保证金金额为【" + reqVO.getBondAmount()
|
+ "】,退回金额必须大于 0 且不能超过保证金金额,请修改后重新保存。");
|
}
|
}
|
if (BidBondRefundStatusEnum.REFUNDED.getStatus().equals(reqVO.getRefundStatus())) {
|
if (reqVO.getRefundDate() == null) {
|
throw new ServiceException(ErrorCodeConstants.BOND_REFUND_DATE_REQUIRED.getCode(),
|
"退回状态选择【已退回】时,退回日期不能为空,请填写实际退回日期。");
|
}
|
if (reqVO.getRefundAmount() == null) {
|
throw new ServiceException(ErrorCodeConstants.BOND_REFUND_AMOUNT_INVALID.getCode(),
|
"退回状态选择【已退回】时,退回金额不能为空,请填写实际退回金额。");
|
}
|
}
|
}
|
|
/**
|
* 未显式选择退回状态时,按是否中标推导默认值
|
*/
|
private Integer resolveRefundStatus(SrmBidBondSaveReqVO reqVO) {
|
if (reqVO.getRefundStatus() != null) {
|
return reqVO.getRefundStatus();
|
}
|
if (Boolean.TRUE.equals(reqVO.getWon())) {
|
return BidBondRefundStatusEnum.NO_NEED.getStatus();
|
}
|
return BidBondRefundStatusEnum.NOT_REFUNDED.getStatus();
|
}
|
|
private SrmBidBondDO validateBidBondExists(Long id) {
|
SrmBidBondDO bond = bidBondMapper.selectById(id);
|
if (bond == null) {
|
throw ServiceExceptionUtil.exception(ErrorCodeConstants.BOND_NOT_EXISTS);
|
}
|
return bond;
|
}
|
|
private String generateBondNo() {
|
String date = LocalDate.now().format(NO_DATE_FORMAT);
|
for (int attempt = 0; attempt < 20; attempt++) {
|
int seq = (int) (Math.random() * 9000) + 1000;
|
String no = "BZ" + date + String.format("%04d", seq);
|
if (bidBondMapper.selectByNo(no) == null) {
|
return no;
|
}
|
}
|
throw new ServiceException(ErrorCodeConstants.BOND_NO_EXISTS.getCode(),
|
"保证金单号在多次尝试后仍重复,请稍后重试。");
|
}
|
|
// ========== 组装 ==========
|
|
private List<SrmBidBondRespVO> buildRespList(List<SrmBidBondDO> bonds) {
|
if (bonds == null || bonds.isEmpty()) {
|
return List.of();
|
}
|
Set<Long> supplierIds = bonds.stream().map(SrmBidBondDO::getSupplierId).collect(Collectors.toSet());
|
Map<Long, SrmSupplierDO> supplierMap = supplierService.getSupplierMap(supplierIds);
|
Set<Long> inquiryIds = bonds.stream().map(SrmBidBondDO::getInquiryId)
|
.filter(java.util.Objects::nonNull).collect(Collectors.toSet());
|
Map<Long, SrmInquiryDO> inquiryMap = inquiryIds.isEmpty() ? Map.of()
|
: inquiryMapper.selectBatchIds(inquiryIds).stream()
|
.collect(Collectors.toMap(SrmInquiryDO::getId, item -> item));
|
|
return bonds.stream().map(bond -> {
|
SrmBidBondRespVO respVO = BeanUtils.toBean(bond, SrmBidBondRespVO.class);
|
SrmSupplierDO supplier = supplierMap.get(bond.getSupplierId());
|
if (supplier != null) {
|
respVO.setSupplierName(supplier.getName());
|
}
|
SrmInquiryDO inquiry = inquiryMap.get(bond.getInquiryId());
|
if (inquiry != null) {
|
respVO.setInquiryNo(inquiry.getNo());
|
if (StrUtil.isBlank(respVO.getProjectName())) {
|
respVO.setProjectName(inquiry.getTitle());
|
}
|
}
|
if (bond.getPurchaseOrderId() != null) {
|
try {
|
ErpPurchaseOrderRespDTO order = erpPurchaseOrderApi.getPurchaseOrder(bond.getPurchaseOrderId())
|
.getCheckedData();
|
if (order != null) {
|
respVO.setPurchaseOrderNo(order.getNo());
|
}
|
} catch (Exception ex) {
|
log.warn("[buildRespList] 获取采购订单号失败,purchaseOrderId={}", bond.getPurchaseOrderId(), ex);
|
}
|
}
|
return respVO;
|
}).collect(Collectors.toList());
|
}
|
|
}
|