yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderBidServiceImpl.java
@@ -12,6 +12,7 @@
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.dal.mysql.SrmTenderMaterialMapper;
import cn.iocoder.yudao.module.srm.enums.BidStatusEnum;
import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.srm.enums.TenderStatusEnum;
@@ -23,8 +24,10 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.module.srm.enums.LogRecordConstants.*;
@@ -46,6 +49,8 @@
    @Resource
    private SrmSupplierMapper supplierMapper;
    @Resource
    private SrmTenderMaterialMapper tenderMaterialMapper;
    // ========== 投标管理 ==========
@@ -117,6 +122,7 @@
    // ========== 报价管理 ==========
    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = LOG_QUOTE_TYPE, subType = LOG_QUOTE_CREATE_SUB_TYPE,
            bizNo = "{{#createReqVO.bidId}}", success = LOG_QUOTE_CREATE_SUCCESS)
    public Long createQuote(SrmSupplierQuoteSaveReqVO createReqVO) {
@@ -127,6 +133,7 @@
        if (project == null || !TenderStatusEnum.canCreateOrUpdateQuote(project.getTenderStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_QUOTE_FORBIDDEN);
        }
        validateQuoteMaterial(createReqVO.getTenderMaterialId(), bid.getTenderProjectId());
        // 校验同一投标下物料不可重复报价
        if (supplierQuoteMapper.existsByBidAndMaterial(createReqVO.getBidId(), createReqVO.getTenderMaterialId())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.QUOTE_MATERIAL_DUPLICATE);
@@ -140,6 +147,7 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateQuote(SrmSupplierQuoteSaveReqVO updateReqVO) {
        // 校验投标状态(撤标后不允许报价)
        SrmTenderBidDO bid = validateBidNotWithdrawn(updateReqVO.getBidId());
@@ -149,14 +157,26 @@
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_QUOTE_FORBIDDEN);
        }
        // 校验同一投标下物料不可重复报价(排除自身)
        SrmSupplierQuoteDO existing = supplierQuoteMapper.selectById(updateReqVO.getId());
        if (existing != null && !existing.getTenderMaterialId().equals(updateReqVO.getTenderMaterialId())) {
        SrmSupplierQuoteDO existing = updateReqVO.getId() == null ? null : supplierQuoteMapper.selectById(updateReqVO.getId());
        if (existing == null) throw ServiceExceptionUtil.exception(ErrorCodeConstants.QUOTE_NOT_EXISTS);
        if (!Objects.equals(existing.getBidId(), bid.getId())) throw ServiceExceptionUtil.exception(ErrorCodeConstants.QUOTE_BID_MISMATCH);
        validateQuoteMaterial(updateReqVO.getTenderMaterialId(), bid.getTenderProjectId());
        if (!Objects.equals(existing.getTenderMaterialId(), updateReqVO.getTenderMaterialId())) {
            if (supplierQuoteMapper.existsByBidAndMaterial(updateReqVO.getBidId(), updateReqVO.getTenderMaterialId())) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.QUOTE_MATERIAL_DUPLICATE);
            }
        }
        SrmSupplierQuoteDO updateObj = BeanUtils.toBean(updateReqVO, SrmSupplierQuoteDO.class);
        updateObj.setTenderProjectId(bid.getTenderProjectId());
        updateObj.setSupplierId(bid.getSupplierId());
        supplierQuoteMapper.updateById(updateObj);
    }
    private void validateQuoteMaterial(Long materialId, Long projectId) {
        var material = materialId == null ? null : tenderMaterialMapper.selectById(materialId);
        if (material == null || !Objects.equals(material.getTenderProjectId(), projectId)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.QUOTE_MATERIAL_NOT_IN_PROJECT);
        }
    }
    @Override
@@ -164,6 +184,24 @@
        return supplierQuoteMapper.selectListByBidId(bidId);
    }
    // ========== 投标总金额(人工维护,可自动带出报价合计后调整) ==========
    @Override
    public void updateBidTotal(Long bidId, BigDecimal bidTotalAmount) {
        // 校验投标状态(撤标后不允许修改)
        SrmTenderBidDO bid = validateBidNotWithdrawn(bidId);
        // 校验招标项目状态:仅允许维护报价的招标状态下可修改
        SrmTenderProjectDO project = tenderProjectMapper.selectById(bid.getTenderProjectId());
        if (project == null || !TenderStatusEnum.canCreateOrUpdateQuote(project.getTenderStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_QUOTE_FORBIDDEN);
        }
        // 仅在传入非 null 时更新(MyBatis-Plus updateById 会忽略 null 字段,null 视为不改)
        if (bidTotalAmount != null) {
            bid.setBidTotalAmount(bidTotalAmount);
            tenderBidMapper.updateById(bid);
        }
    }
    // ========== 校验方法 ==========
    private SrmTenderBidDO validateBidExists(Long id) {