huminmin
7 天以前 0fdd0bf60b7d98d21c9b5ff95bb55f0fa6495007
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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;
    }
}