package cn.iocoder.yudao.module.srm.service.tender; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmSupplierQuoteSaveReqVO; import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmTenderBidSaveReqVO; import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierQuoteDO; import cn.iocoder.yudao.module.srm.dal.dataobject.SrmTenderBidDO; import cn.iocoder.yudao.module.srm.dal.dataobject.SrmTenderProjectDO; import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierApplyMapper; import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierQuoteMapper; import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderBidMapper; import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderProjectMapper; 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 cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; import com.mzt.logapi.context.LogRecordContext; import com.mzt.logapi.starter.annotation.LogRecord; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; import java.util.List; import static cn.iocoder.yudao.module.srm.enums.LogRecordConstants.*; /** * SRM 投标管理 Service 实现类 */ @Service @Validated public class SrmTenderBidServiceImpl implements SrmTenderBidService { @Resource private SrmTenderBidMapper tenderBidMapper; @Resource private SrmSupplierQuoteMapper supplierQuoteMapper; @Resource private SrmTenderProjectMapper tenderProjectMapper; @Resource private SrmSupplierApplyMapper supplierApplyMapper; // ========== 投标管理 ========== @Override @Transactional(rollbackFor = Exception.class) @LogRecord(type = LOG_BID_TYPE, subType = LOG_BID_CREATE_SUB_TYPE, bizNo = "{{#createReqVO.bidNo}}", success = LOG_BID_CREATE_SUCCESS) public Long createBid(SrmTenderBidSaveReqVO createReqVO) { // 校验招标项目状态(PUBLISHED/BIDDING 才允许投标) SrmTenderProjectDO project = validateTenderStatus(createReqVO.getTenderProjectId()); // 检查投标截止时间 if (project.getEndTime() != null && LocalDateTime.now().isAfter(project.getEndTime())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_END_TIME_PASSED); } // 校验供应商是否已通过准入审批 if (!supplierApplyMapper.existsApprovedBySupplierId(createReqVO.getSupplierId())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.BID_SUPPLIER_NOT_APPROVED); } // 校验投标编号是否重复 if (tenderBidMapper.existsByBidNo(createReqVO.getBidNo())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.BID_NO_EXISTS); } // 校验供应商是否已参与投标 SrmTenderBidDO existing = tenderBidMapper.selectByTenderAndSupplier(createReqVO.getTenderProjectId(), createReqVO.getSupplierId()); if (existing != null) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.BID_SUPPLIER_EXISTS); } SrmTenderBidDO bid = BeanUtils.toBean(createReqVO, SrmTenderBidDO.class); bid.setBidStatus(BidStatusEnum.REGISTERED.getCode()); bid.setBidTime(LocalDateTime.now()); tenderBidMapper.insert(bid); LogRecordContext.putVariable("bidNo", bid.getBidNo()); LogRecordContext.putVariable("tenderName", project.getTenderName()); return bid.getId(); } @Override public SrmTenderBidDO getBid(Long id) { return tenderBidMapper.selectById(id); } @Override public List getBidListByProject(Long tenderProjectId) { return tenderBidMapper.selectListByTenderProjectId(tenderProjectId); } @Override @Transactional(rollbackFor = Exception.class) @LogRecord(type = LOG_BID_TYPE, subType = LOG_BID_WITHDRAW_SUB_TYPE, bizNo = "{{#id}}", success = LOG_BID_WITHDRAW_SUCCESS) public void withdrawBid(Long id) { SrmTenderBidDO bid = validateBidExists(id); // 检查招标项目状态:仅 PUBLISHED/BIDDING 允许撤标 SrmTenderProjectDO project = tenderProjectMapper.selectById(bid.getTenderProjectId()); if (project == null || !TenderStatusEnum.canCreateOrUpdateQuote(project.getTenderStatus())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_BID_FORBIDDEN_WITHDRAW); } bid.setBidStatus(BidStatusEnum.WITHDRAWN.getCode()); tenderBidMapper.updateById(bid); // 删除报价 supplierQuoteMapper.deleteByBidId(id); LogRecordContext.putVariable("bidNo", bid.getBidNo()); } // ========== 报价管理 ========== @Override @LogRecord(type = LOG_QUOTE_TYPE, subType = LOG_QUOTE_CREATE_SUB_TYPE, bizNo = "{{#createReqVO.bidId}}", success = LOG_QUOTE_CREATE_SUCCESS) public Long createQuote(SrmSupplierQuoteSaveReqVO createReqVO) { // 校验投标状态(撤标后不允许报价) SrmTenderBidDO bid = validateBidNotWithdrawn(createReqVO.getBidId()); // 校验招标项目状态:仅 PUBLISHED/BIDDING 允许报价 SrmTenderProjectDO project = tenderProjectMapper.selectById(bid.getTenderProjectId()); if (project == null || !TenderStatusEnum.canCreateOrUpdateQuote(project.getTenderStatus())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_QUOTE_FORBIDDEN); } // 校验同一投标下物料不可重复报价 if (supplierQuoteMapper.existsByBidAndMaterial(createReqVO.getBidId(), createReqVO.getTenderMaterialId())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.QUOTE_MATERIAL_DUPLICATE); } SrmSupplierQuoteDO quote = BeanUtils.toBean(createReqVO, SrmSupplierQuoteDO.class); // 从投标记录中获取招标项目ID和供应商ID,避免前端遗漏导致数据库约束失败 quote.setTenderProjectId(bid.getTenderProjectId()); quote.setSupplierId(bid.getSupplierId()); supplierQuoteMapper.insert(quote); return quote.getId(); } @Override public void updateQuote(SrmSupplierQuoteSaveReqVO updateReqVO) { // 校验投标状态(撤标后不允许报价) SrmTenderBidDO bid = validateBidNotWithdrawn(updateReqVO.getBidId()); // 校验招标项目状态:仅 PUBLISHED/BIDDING 允许报价 SrmTenderProjectDO project = tenderProjectMapper.selectById(bid.getTenderProjectId()); if (project == null || !TenderStatusEnum.canCreateOrUpdateQuote(project.getTenderStatus())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_QUOTE_FORBIDDEN); } // 校验同一投标下物料不可重复报价(排除自身) SrmSupplierQuoteDO existing = supplierQuoteMapper.selectById(updateReqVO.getId()); if (existing != null && !existing.getTenderMaterialId().equals(updateReqVO.getTenderMaterialId())) { if (supplierQuoteMapper.existsByBidAndMaterial(updateReqVO.getBidId(), updateReqVO.getTenderMaterialId())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.QUOTE_MATERIAL_DUPLICATE); } } SrmSupplierQuoteDO updateObj = BeanUtils.toBean(updateReqVO, SrmSupplierQuoteDO.class); supplierQuoteMapper.updateById(updateObj); } @Override public List getQuoteList(Long bidId) { return supplierQuoteMapper.selectListByBidId(bidId); } // ========== 校验方法 ========== private SrmTenderBidDO validateBidExists(Long id) { SrmTenderBidDO bid = tenderBidMapper.selectById(id); if (bid == null) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.BID_NOT_EXISTS); } return bid; } private SrmTenderBidDO validateBidNotWithdrawn(Long bidId) { SrmTenderBidDO bid = validateBidExists(bidId); if (BidStatusEnum.WITHDRAWN.getCode().equals(bid.getBidStatus())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.BID_WITHDRAWN_CANNOT_QUOTE); } return bid; } private SrmTenderProjectDO validateTenderStatus(Long tenderProjectId) { SrmTenderProjectDO project = tenderProjectMapper.selectById(tenderProjectId); if (project == null) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_NOT_EXISTS); } if (!TenderStatusEnum.canCreateBid(project.getTenderStatus())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_BID_FORBIDDEN); } return project; } }