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<SrmTenderBidDO> 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<SrmSupplierQuoteDO> 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;
|
}
|
}
|