package cn.iocoder.yudao.module.srm.service.tender; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; 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.ErpPurchaseOrderCreateReqDTO; import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmBidAwardSaveReqVO; import cn.iocoder.yudao.module.srm.dal.dataobject.*; import cn.iocoder.yudao.module.srm.dal.mysql.*; import cn.iocoder.yudao.module.srm.enums.AwardStatusEnum; import cn.iocoder.yudao.module.srm.enums.BidStatusEnum; import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.srm.enums.TenderStatusEnum; import com.mzt.logapi.starter.annotation.LogRecord; 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.math.RoundingMode; import java.time.LocalDateTime; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; import static cn.iocoder.yudao.module.srm.enums.LogRecordConstants.*; /** * SRM 定标管理 Service 实现类 */ @Service @Validated @Slf4j public class SrmBidAwardServiceImpl implements SrmBidAwardService { @Resource private SrmBidAwardMapper bidAwardMapper; @Resource private SrmTenderBidMapper tenderBidMapper; @Resource private SrmTenderProjectMapper tenderProjectMapper; @Resource private SrmSupplierMapper supplierMapper; @Resource private SrmTenderMaterialMapper tenderMaterialMapper; @Resource private SrmSupplierQuoteMapper supplierQuoteMapper; @Resource private MdmItemApi mdmItemApi; @Resource private ErpPurchaseOrderApi erpPurchaseOrderApi; @Override @Transactional(rollbackFor = Exception.class) @LogRecord(type = LOG_AWARD_TYPE, subType = LOG_AWARD_CREATE_SUB_TYPE, bizNo = "{{#createReqVO.awardNo}}", success = LOG_AWARD_CREATE_SUCCESS) public Long createAward(SrmBidAwardSaveReqVO createReqVO) { // 检查招标项目状态:仅 EVALUATING 允许创建定标 SrmTenderProjectDO project = tenderProjectMapper.selectById(createReqVO.getTenderProjectId()); if (project == null || !TenderStatusEnum.canAward(project.getTenderStatus())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_AWARD_FORBIDDEN); } // 校验是否已存在定标 SrmBidAwardDO existing = bidAwardMapper.selectByTenderProjectId(createReqVO.getTenderProjectId()); if (existing != null) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_SUPPLIER_EXISTS); } // 校验投标记录:必须属于该项目、未被撤标、且供应商一致 SrmTenderBidDO bid = tenderBidMapper.selectById(createReqVO.getBidId()); if (bid == null) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.BID_NOT_EXISTS); } if (!bid.getTenderProjectId().equals(createReqVO.getTenderProjectId())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_BID_NOT_IN_PROJECT); } if (BidStatusEnum.WITHDRAWN.getCode().equals(bid.getBidStatus())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_BID_WITHDRAWN); } if (!bid.getSupplierId().equals(createReqVO.getSupplierId())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_SUPPLIER_NOT_MATCH_BID); } SrmBidAwardDO award = BeanUtils.toBean(createReqVO, SrmBidAwardDO.class); award.setAwardStatus(AwardStatusEnum.PENDING_APPROVAL.getCode()); award.setAwardTime(LocalDateTime.now()); bidAwardMapper.insert(award); // 更新投标状态为中标 bid.setBidStatus(BidStatusEnum.WIN.getCode()); tenderBidMapper.updateById(bid); return award.getId(); } @Override public SrmBidAwardDO getAward(Long id) { return bidAwardMapper.selectById(id); } @Override public SrmBidAwardDO getAwardByProjectId(Long tenderProjectId) { return bidAwardMapper.selectByTenderProjectId(tenderProjectId); } @Override @Transactional(rollbackFor = Exception.class) public void deleteAward(Long id) { SrmBidAwardDO award = validateAwardExists(id); // 仅待审批状态可删除 if (!AwardStatusEnum.PENDING_APPROVAL.getCode().equals(award.getAwardStatus())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_AWARD_FORBIDDEN); } bidAwardMapper.deleteById(id); } @Override @Transactional(rollbackFor = Exception.class) public void approveAward(Long id) { SrmBidAwardDO award = validateAwardExists(id); // 校验招标项目状态为评标中 SrmTenderProjectDO project = tenderProjectMapper.selectById(award.getTenderProjectId()); if (project == null || !TenderStatusEnum.canAward(project.getTenderStatus())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_AWARD_FORBIDDEN); } award.setAwardStatus(AwardStatusEnum.AWARDED.getCode()); award.setApprovalTime(LocalDateTime.now()); bidAwardMapper.updateById(award); // 更新招标项目状态为已定标 project.setTenderStatus(TenderStatusEnum.AWARDED.getCode()); tenderProjectMapper.updateById(project); } @Override @Transactional(rollbackFor = Exception.class) public void generatePurchaseOrder(Long id) { // 1. 校验定标存在且状态为已定标 SrmBidAwardDO award = validateAwardExists(id); if (!AwardStatusEnum.AWARDED.getCode().equals(award.getAwardStatus())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_STATUS_NOT_AWARDED); } // 校验招标项目状态为已定标 SrmTenderProjectDO project = tenderProjectMapper.selectById(award.getTenderProjectId()); if (project == null || !TenderStatusEnum.AWARDED.getCode().equals(project.getTenderStatus())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_AWARD_FORBIDDEN); } // 2. 查 SRM 供应商 SrmSupplierDO srmSupplier = supplierMapper.selectById(award.getSupplierId()); if (srmSupplier == null) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_SUPPLIER_NOT_SYNCED); } // 3. 查招标物料列表 List materials = tenderMaterialMapper .selectListByTenderProjectId(award.getTenderProjectId()); if (CollUtil.isEmpty(materials)) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_NOT_EXISTS); } // 4. 查中标报价列表 List quotes = supplierQuoteMapper.selectListByBidId(award.getBidId()); if (CollUtil.isEmpty(quotes)) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_QUOTE_NOT_EXISTS); } Map quoteMap = quotes.stream() .collect(Collectors.toMap( SrmSupplierQuoteDO::getTenderMaterialId, Function.identity(), (a, b) -> a)); // 5. 批量查 MDM 物料,获取计量单位 Set productIds = materials.stream() .map(SrmTenderMaterialDO::getProductId) .collect(Collectors.toSet()); Map itemMap = mdmItemApi.getItemList(productIds).getCheckedData() .stream().collect(Collectors.toMap(MdmItemRespDTO::getId, Function.identity())); // 6. 构建订单项(报价为总价,需除以数量得到单价) BigDecimal totalQuoteAmount = quotes.stream() .map(SrmSupplierQuoteDO::getQuotePrice) .reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal awardAmount = award.getAwardAmount() != null ? award.getAwardAmount() : totalQuoteAmount; BigDecimal ratio = totalQuoteAmount.compareTo(BigDecimal.ZERO) > 0 ? awardAmount.divide(totalQuoteAmount, 8, RoundingMode.HALF_UP) : BigDecimal.ONE; List orderItems = new ArrayList<>(); for (SrmTenderMaterialDO material : materials) { SrmSupplierQuoteDO quote = quoteMap.get(material.getId()); if (quote == null) { continue; } MdmItemRespDTO mdmItem = itemMap.get(material.getProductId()); if (mdmItem == null) { continue; } BigDecimal materialTotal = quote.getQuotePrice().multiply(ratio); BigDecimal unitPrice = materialTotal.divide(material.getQuantity(), 4, RoundingMode.HALF_UP); ErpPurchaseOrderCreateReqDTO.Item item = new ErpPurchaseOrderCreateReqDTO.Item(); item.setProductId(material.getProductId()); item.setProductUnitId(mdmItem.getUnitMeasureId()); item.setProductPrice(unitPrice); item.setCount(material.getQuantity()); item.setTaxPercent(quote.getTaxRate()); item.setRemark("招标编号: " + award.getAwardNo()); orderItems.add(item); } // 7. 构建并调用 ERP 创建采购订单 ErpPurchaseOrderCreateReqDTO createReq = new ErpPurchaseOrderCreateReqDTO(); createReq.setSupplierId(srmSupplier.getId()); createReq.setOrderTime(LocalDateTime.now()); createReq.setDepositPrice(BigDecimal.ZERO); createReq.setItems(orderItems); createReq.setRemark("SRM 定标自动生成, 定标编号: " + award.getAwardNo()); Long purchaseOrderId = erpPurchaseOrderApi.createPurchaseOrder(createReq).getCheckedData(); // 获取采购订单号 String purchaseOrderNo = null; try { purchaseOrderNo = erpPurchaseOrderApi.getPurchaseOrder(purchaseOrderId) .getCheckedData().getNo(); } catch (Exception e) { log.warn("[generatePurchaseOrder] 获取采购订单号失败,purchaseOrderId={}", purchaseOrderId, e); } // 8. 更新定标记录 award.setPurchaseOrderId(purchaseOrderId); award.setPurchaseOrderNo(purchaseOrderNo); award.setAwardStatus(AwardStatusEnum.ORDER_GENERATED.getCode()); bidAwardMapper.updateById(award); // 9. 更新招标项目状态 project.setTenderStatus(TenderStatusEnum.CLOSED.getCode()); tenderProjectMapper.updateById(project); } private SrmBidAwardDO validateAwardExists(Long id) { SrmBidAwardDO award = bidAwardMapper.selectById(id); if (award == null) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_NOT_EXISTS); } return award; } }