From e1797a873bf114329f4870062201d388155ef014 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 14 八月 2026 13:39:51 +0800
Subject: [PATCH] refactor(erp): 移除采购来票审核流程并简化业务逻辑

---
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java |  229 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 220 insertions(+), 9 deletions(-)

diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java
index 5d1b679..73b86db 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java
@@ -1,30 +1,38 @@
 package cn.iocoder.yudao.module.srm.service.tender;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 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.system.api.storage.StorageAttachmentApi;
 import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmTenderMaterialSaveReqVO;
 import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmTenderProjectPageReqVO;
 import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmTenderProjectPartialUpdateReqVO;
 import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmTenderProjectSaveReqVO;
-import cn.iocoder.yudao.module.srm.dal.dataobject.SrmBidAwardDO;
-import cn.iocoder.yudao.module.srm.dal.dataobject.SrmTenderMaterialDO;
-import cn.iocoder.yudao.module.srm.dal.dataobject.SrmTenderProjectDO;
-import cn.iocoder.yudao.module.srm.dal.mysql.SrmBidAwardMapper;
-import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderBidMapper;
-import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderMaterialMapper;
-import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderProjectMapper;
+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.BiddingModeEnum;
+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.module.srm.service.apply.SrmSupplierApplyService;
 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 lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
-import java.util.List;
-import java.util.Map;
+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;
 
@@ -32,7 +40,13 @@
 
 @Service
 @Validated
+@Slf4j
 public class SrmTenderProjectServiceImpl implements SrmTenderProjectService {
+
+    /**
+     * 鎷涙爣椤圭洰璧勮川瑕佹眰闄勪欢鐨勪笟鍔¤褰曠被鍨嬶紝瀵瑰簲 {@link cn.iocoder.yudao.module.system.enums.storage.StorageRecordTypeEnum#SRM_TENDER_PROJECT}
+     */
+    public static final String TENDER_PROJECT_RECORD_TYPE = "srm_tender_project";
 
     @Resource
     private SrmTenderProjectMapper tenderProjectMapper;
@@ -43,15 +57,39 @@
     @Resource
     private SrmTenderBidMapper tenderBidMapper;
 
+    @Resource
+    private SrmTenderConfigService tenderConfigService;
+    @Resource
+    private SrmSupplierMapper supplierMapper;
+    @Resource
+    private SrmSupplierQuoteMapper supplierQuoteMapper;
+    @Resource
+    private MdmItemApi mdmItemApi;
+    @Resource
+    private ErpPurchaseOrderApi erpPurchaseOrderApi;
+    @Resource
+    private SrmSupplierApplyService supplierApplyService;
+    @Resource
+    private StorageAttachmentApi storageAttachmentApi;
+
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public Long createTenderProject(SrmTenderProjectSaveReqVO createReqVO) {
         SrmTenderProjectDO project = BeanUtils.toBean(createReqVO, SrmTenderProjectDO.class);
         project.setTenderStatus(TenderStatusEnum.DRAFT.getCode());
+        if (project.getBiddingMode() == null || project.getBiddingMode().isEmpty()) {
+            project.setBiddingMode(tenderConfigService.getConfig().getDefaultBiddingMode());
+        }
         tenderProjectMapper.insert(project);
+        // 缁戝畾璧勮川瑕佹眰闄勪欢
+        if (CollUtil.isNotEmpty(createReqVO.getBlobIds())) {
+            storageAttachmentApi.bindAttachments("file", TENDER_PROJECT_RECORD_TYPE, project.getId(), createReqVO.getBlobIds());
+        }
         return project.getId();
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     @LogRecord(type = LOG_TENDER_TYPE, subType = LOG_TENDER_UPDATE_SUB_TYPE,
             bizNo = "{{#updateReqVO.id}}", success = LOG_TENDER_UPDATE_SUCCESS)
     public void updateTenderProject(SrmTenderProjectSaveReqVO updateReqVO) {
@@ -63,6 +101,8 @@
             SrmTenderProjectDO updateObj = BeanUtils.toBean(updateReqVO, SrmTenderProjectDO.class);
             updateObj.setTenderStatus(status); // 鐘舵�佷笉鍏佽鍓嶇浼犲叆瑕嗙洊
             tenderProjectMapper.updateById(updateObj);
+            // 澧為噺鏇存柊璧勮川瑕佹眰闄勪欢
+            storageAttachmentApi.updateAttachments("file", TENDER_PROJECT_RECORD_TYPE, updateReqVO.getId(), updateReqVO.getBlobIds());
             LogRecordContext.putVariable("tenderName", project.getTenderName());
             LogRecordContext.putVariable("oldTenderDesc", project.getTenderDesc());
             LogRecordContext.putVariable("newTenderDesc", updateReqVO.getTenderDesc());
@@ -78,6 +118,8 @@
             updateObj.setTenderDesc(updateReqVO.getTenderDesc());
             updateObj.setQualificationRequirements(updateReqVO.getQualificationRequirements());
             tenderProjectMapper.updateById(updateObj);
+            // 澧為噺鏇存柊璧勮川瑕佹眰闄勪欢
+            storageAttachmentApi.updateAttachments("file", TENDER_PROJECT_RECORD_TYPE, updateReqVO.getId(), updateReqVO.getBlobIds());
             LogRecordContext.putVariable("tenderName", project.getTenderName());
             LogRecordContext.putVariable("oldTenderDesc", project.getTenderDesc());
             LogRecordContext.putVariable("newTenderDesc", updateReqVO.getTenderDesc());
@@ -91,6 +133,7 @@
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void deleteTenderProject(Long id) {
         SrmTenderProjectDO project = validateTenderExists(id);
         if (!TenderStatusEnum.DRAFT.getCode().equals(project.getTenderStatus())) {
@@ -100,6 +143,8 @@
         if (tenderBidMapper.countByTenderProjectId(id) > 0) {
             throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_HAS_BIDS_CANNOT_DELETE);
         }
+        // 鍒犻櫎璧勮川瑕佹眰闄勪欢
+        storageAttachmentApi.deleteAttachmentsByRecord(TENDER_PROJECT_RECORD_TYPE, id);
         tenderMaterialMapper.deleteByTenderProjectId(id);
         tenderProjectMapper.deleteById(id);
     }
@@ -186,7 +231,171 @@
         tenderProjectMapper.updateById(project);
     }
 
+    // ========== 绠�鏄撴ā寮忔搷浣� ==========
+
     @Override
+    public void startPriceComparing(Long id) {
+        SrmTenderProjectDO project = validateTenderExists(id);
+        if (!BiddingModeEnum.isSimple(project.getBiddingMode())) {
+            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_MODE_NOT_SIMPLE);
+        }
+        if (!TenderStatusEnum.canStartPriceComparing(project.getTenderStatus())) {
+            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_STATUS_NOT_DRAFT);
+        }
+        // 妫�鏌ユ槸鍚︽湁鐗╂枡
+        if (tenderMaterialMapper.countByTenderProjectId(id) == 0) {
+            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_NO_MATERIALS);
+        }
+        project.setTenderStatus(TenderStatusEnum.PRICE_COMPARING.getCode());
+        tenderProjectMapper.updateById(project);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void confirmTenderProject(Long id, Long bidId) {
+        // 1. 鏍¢獙椤圭洰鐘舵��
+        SrmTenderProjectDO project = validateTenderExists(id);
+        if (!BiddingModeEnum.isSimple(project.getBiddingMode())) {
+            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_MODE_NOT_SIMPLE);
+        }
+        if (!TenderStatusEnum.canConfirm(project.getTenderStatus())) {
+            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_STATUS_NOT_PRICE_COMPARING);
+        }
+
+        // 2. 鏍¢獙鎶曟爣璁板綍
+        SrmTenderBidDO bid = tenderBidMapper.selectById(bidId);
+        if (bid == null) {
+            throw ServiceExceptionUtil.exception(ErrorCodeConstants.BID_NOT_EXISTS);
+        }
+        if (!bid.getTenderProjectId().equals(id)) {
+            throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_BID_NOT_IN_PROJECT);
+        }
+        if (BidStatusEnum.WITHDRAWN.getCode().equals(bid.getBidStatus())) {
+            throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_BID_WITHDRAWN);
+        }
+
+        // 3. 鍒涘缓瀹氭爣璁板綍锛堢洿鎺ュ凡瀹氭爣锛岃烦杩囧鎵癸級
+        SrmBidAwardDO award = new SrmBidAwardDO();
+        award.setTenderProjectId(id);
+        award.setSupplierId(bid.getSupplierId());
+        award.setBidId(bidId);
+        award.setAwardNo("SMP" + System.currentTimeMillis());
+        award.setAwardStatus(AwardStatusEnum.AWARDED.getCode());
+        award.setAwardAmount(bid.getBidTotalAmount());
+        award.setAwardTime(LocalDateTime.now());
+        award.setApprovalTime(LocalDateTime.now());
+        bidAwardMapper.insert(award);
+
+        // 4. 璁剧疆鎶曟爣鐘舵�佷负涓爣
+        bid.setBidStatus(BidStatusEnum.WIN.getCode());
+        tenderBidMapper.updateById(bid);
+
+        // 5. 鑷姩鐢熸垚閲囪喘璁㈠崟
+        generatePurchaseOrderForSimple(award, project);
+
+        // 6. 鏇存柊椤圭洰鐘舵��
+        project.setTenderStatus(TenderStatusEnum.CONFIRMED.getCode());
+        tenderProjectMapper.updateById(project);
+    }
+
+    /**
+     * 绠�鏄撴ā寮忥細鏍规嵁瀹氭爣淇℃伅鑷姩鐢熸垚閲囪喘璁㈠崟
+     */
+    private void generatePurchaseOrderForSimple(SrmBidAwardDO award, SrmTenderProjectDO project) {
+        // 鏌ヤ緵搴斿晢
+        SrmSupplierDO supplier = supplierMapper.selectById(award.getSupplierId());
+        if (supplier == null) {
+            throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_SUPPLIER_NOT_SYNCED);
+        }
+
+        // 渚涘簲鍟嗘湭鍑嗗叆鍒欒嚜鍔ㄥ噯鍏ュ苟閫氳繃锛屼繚璇侀噰璐鍗曟祦绋嬪彲鐢�
+        supplierApplyService.ensureSupplierApproved(supplier.getId());
+
+        // 鏌ユ嫑鏍囩墿鏂�
+        List<SrmTenderMaterialDO> materials = tenderMaterialMapper
+                .selectListByTenderProjectId(award.getTenderProjectId());
+        if (CollUtil.isEmpty(materials)) {
+            throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_NOT_EXISTS);
+        }
+
+        // 鏌ヤ腑鏍囨姤浠�
+        List<SrmSupplierQuoteDO> quotes = supplierQuoteMapper.selectListByBidId(award.getBidId());
+        if (CollUtil.isEmpty(quotes)) {
+            throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_QUOTE_NOT_EXISTS);
+        }
+        Map<Long, SrmSupplierQuoteDO> quoteMap = quotes.stream()
+                .collect(Collectors.toMap(
+                        SrmSupplierQuoteDO::getTenderMaterialId,
+                        Function.identity(),
+                        (a, b) -> a));
+
+        // 鏌� MDM 鐗╂枡
+        Set<Long> productIds = materials.stream()
+                .map(SrmTenderMaterialDO::getProductId)
+                .collect(Collectors.toSet());
+        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemList(productIds).getCheckedData()
+                .stream().collect(Collectors.toMap(MdmItemRespDTO::getId, Function.identity()));
+
+        // 璁$畻浠锋牸
+        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<ErpPurchaseOrderCreateReqDTO.Item> 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);
+        }
+
+        // 璋冪敤 ERP 鍒涘缓閲囪喘璁㈠崟
+        ErpPurchaseOrderCreateReqDTO createReq = new ErpPurchaseOrderCreateReqDTO();
+        createReq.setSupplierId(supplier.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("[generatePurchaseOrderForSimple] 鑾峰彇閲囪喘璁㈠崟鍙峰け璐ワ紝purchaseOrderId={}", purchaseOrderId, e);
+        }
+
+        // 鏇存柊瀹氭爣璁板綍
+        award.setPurchaseOrderId(purchaseOrderId);
+        award.setPurchaseOrderNo(purchaseOrderNo);
+        award.setAwardStatus(AwardStatusEnum.ORDER_GENERATED.getCode());
+        bidAwardMapper.updateById(award);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
     @LogRecord(type = LOG_TENDER_TYPE, subType = LOG_TENDER_UPDATE_SUB_TYPE,
             bizNo = "{{#updateReqVO.id}}", success = LOG_TENDER_UPDATE_SUCCESS)
     public void updateTenderProjectPartial(SrmTenderProjectPartialUpdateReqVO updateReqVO) {
@@ -199,6 +408,8 @@
         updateObj.setTenderDesc(updateReqVO.getTenderDesc());
         updateObj.setQualificationRequirements(updateReqVO.getQualificationRequirements());
         tenderProjectMapper.updateById(updateObj);
+        // 澧為噺鏇存柊璧勮川瑕佹眰闄勪欢
+        storageAttachmentApi.updateAttachments("file", TENDER_PROJECT_RECORD_TYPE, updateReqVO.getId(), updateReqVO.getBlobIds());
         LogRecordContext.putVariable("tenderName", project.getTenderName());
         LogRecordContext.putVariable("oldTenderDesc", project.getTenderDesc());
         LogRecordContext.putVariable("newTenderDesc", updateReqVO.getTenderDesc());

--
Gitblit v1.9.3