package cn.iocoder.yudao.module.srm.service.inquiry; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; 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.erp.api.purchase.dto.ErpPurchaseOrderRespDTO; import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi; import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO; import cn.iocoder.yudao.module.srm.controller.admin.inquiry.vo.*; import cn.iocoder.yudao.module.srm.dal.dataobject.*; import cn.iocoder.yudao.module.srm.dal.mysql.*; import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.srm.enums.InquiryStatusEnum; import cn.iocoder.yudao.module.srm.service.supplier.SrmSupplierService; 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.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; /** * SRM 询价比价 Service 实现 */ @Service @Validated @Slf4j public class SrmInquiryServiceImpl implements SrmInquiryService { private static final DateTimeFormatter NO_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMM"); @Resource private SrmInquiryMapper inquiryMapper; @Resource private SrmInquiryItemMapper inquiryItemMapper; @Resource private SrmInquirySupplierMapper inquirySupplierMapper; @Resource private SrmInquiryQuoteMapper inquiryQuoteMapper; @Resource private SrmInquiryOrderMapper inquiryOrderMapper; @Resource private SrmSupplierService supplierService; @Resource private MdmItemApi mdmItemApi; @Resource private MdmUnitMeasureApi mdmUnitMeasureApi; @Resource private ErpPurchaseOrderApi erpPurchaseOrderApi; @Override @Transactional(rollbackFor = Exception.class) public Long createInquiry(SrmInquirySaveReqVO createReqVO) { validateInquiryInput(createReqVO); SrmInquiryDO inquiry = new SrmInquiryDO(); inquiry.setNo(generateInquiryNo()); inquiry.setTitle(createReqVO.getTitle()); inquiry.setStatus(InquiryStatusEnum.DRAFT.getStatus()); inquiry.setInquiryDate(createReqVO.getInquiryDate() != null ? createReqVO.getInquiryDate() : LocalDate.now()); inquiry.setDeadline(createReqVO.getDeadline()); inquiry.setPurchaserUserId(createReqVO.getPurchaserUserId() != null ? createReqVO.getPurchaserUserId() : getLoginUserId()); inquiry.setRemark(createReqVO.getRemark()); inquiryMapper.insert(inquiry); List items = saveItems(inquiry.getId(), createReqVO.getItems()); saveSuppliers(inquiry.getId(), createReqVO.getSuppliers()); saveItemQuotes(inquiry.getId(), items, createReqVO.getItems(), createReqVO.getSuppliers().stream() .map(SrmInquirySaveReqVO.Supplier::getSupplierId) .collect(Collectors.toSet())); return inquiry.getId(); } @Override @Transactional(rollbackFor = Exception.class) public void updateInquiry(SrmInquirySaveReqVO updateReqVO) { SrmInquiryDO inquiry = validateInquiryExists(updateReqVO.getId()); if (!InquiryStatusEnum.DRAFT.getStatus().equals(inquiry.getStatus())) { throw new ServiceException(ErrorCodeConstants.INQUIRY_STATUS_NOT_EDITABLE.getCode(), "询价单【" + inquiry.getNo() + "】当前状态为【" + statusName(inquiry.getStatus()) + "】,仅草稿状态可编辑。如需调整产品行或供应商,请取消后重新创建询价单。"); } validateInquiryInput(updateReqVO); SrmInquiryDO updateObj = new SrmInquiryDO(); updateObj.setId(inquiry.getId()); updateObj.setTitle(updateReqVO.getTitle()); updateObj.setInquiryDate(updateReqVO.getInquiryDate()); updateObj.setDeadline(updateReqVO.getDeadline()); updateObj.setPurchaserUserId(updateReqVO.getPurchaserUserId()); updateObj.setRemark(updateReqVO.getRemark()); inquiryMapper.updateById(updateObj); // 草稿阶段允许整体替换产品行、供应商与报价 inquiryQuoteMapper.deleteByInquiryId(inquiry.getId()); inquiryItemMapper.deleteByInquiryId(inquiry.getId()); inquirySupplierMapper.deleteByInquiryId(inquiry.getId()); List items = saveItems(inquiry.getId(), updateReqVO.getItems()); saveSuppliers(inquiry.getId(), updateReqVO.getSuppliers()); saveItemQuotes(inquiry.getId(), items, updateReqVO.getItems(), updateReqVO.getSuppliers().stream() .map(SrmInquirySaveReqVO.Supplier::getSupplierId) .collect(Collectors.toSet())); } @Override @Transactional(rollbackFor = Exception.class) public void deleteInquiry(Long id) { SrmInquiryDO inquiry = validateInquiryExists(id); if (!InquiryStatusEnum.DRAFT.getStatus().equals(inquiry.getStatus())) { throw new ServiceException(ErrorCodeConstants.INQUIRY_STATUS_NOT_EDITABLE.getCode(), "询价单【" + inquiry.getNo() + "】当前状态为【" + statusName(inquiry.getStatus()) + "】,仅草稿状态可删除。已发布或已转订单的询价单请改为取消。"); } inquiryItemMapper.deleteByInquiryId(id); inquirySupplierMapper.deleteByInquiryId(id); inquiryQuoteMapper.deleteByInquiryId(id); inquiryMapper.deleteById(id); } @Override public SrmInquiryDO getInquiry(Long id) { return inquiryMapper.selectById(id); } @Override public SrmInquiryRespVO getInquiryDetail(Long id) { SrmInquiryDO inquiry = validateInquiryExists(id); return buildInquiryResp(inquiry); } @Override public PageResult getInquiryPage(SrmInquiryPageReqVO pageReqVO) { return inquiryMapper.selectPage(pageReqVO); } @Override public List getInquirySimpleList() { return inquiryMapper.selectList(null).stream() .filter(item -> !InquiryStatusEnum.CANCELLED.getStatus().equals(item.getStatus())) .sorted(Comparator.comparing(SrmInquiryDO::getId).reversed()) .collect(Collectors.toList()); } @Override @Transactional(rollbackFor = Exception.class) public void publishInquiry(Long id) { SrmInquiryDO inquiry = validateInquiryExists(id); if (!InquiryStatusEnum.DRAFT.getStatus().equals(inquiry.getStatus())) { throw new ServiceException(ErrorCodeConstants.INQUIRY_STATUS_NOT_QUOTING.getCode(), "询价单【" + inquiry.getNo() + "】当前状态为【" + statusName(inquiry.getStatus()) + "】,仅草稿状态可发布。"); } inquiryMapper.updateByIdAndStatus(id, InquiryStatusEnum.DRAFT.getStatus(), new SrmInquiryDO().setStatus(InquiryStatusEnum.QUOTING.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void cancelInquiry(Long id) { SrmInquiryDO inquiry = validateInquiryExists(id); if (!InquiryStatusEnum.DRAFT.getStatus().equals(inquiry.getStatus()) && !InquiryStatusEnum.QUOTING.getStatus().equals(inquiry.getStatus())) { throw new ServiceException(ErrorCodeConstants.INQUIRY_STATUS_NOT_CANCELLABLE.getCode(), "询价单【" + inquiry.getNo() + "】当前状态为【" + statusName(inquiry.getStatus()) + "】,仅草稿或询价中状态可取消。"); } inquiryMapper.updateById(new SrmInquiryDO() .setId(id).setStatus(InquiryStatusEnum.CANCELLED.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void saveQuotes(SrmInquiryQuoteSaveReqVO saveReqVO) { SrmInquiryDO inquiry = validateInquiryExists(saveReqVO.getInquiryId()); if (!InquiryStatusEnum.DRAFT.getStatus().equals(inquiry.getStatus()) && !InquiryStatusEnum.QUOTING.getStatus().equals(inquiry.getStatus())) { throw new ServiceException(ErrorCodeConstants.INQUIRY_STATUS_NOT_QUOTING.getCode(), "询价单【" + inquiry.getNo() + "】当前状态为【" + statusName(inquiry.getStatus()) + "】,仅草稿或询价中状态可录入报价。"); } List items = inquiryItemMapper.selectListByInquiryId(inquiry.getId()); Map itemMap = items.stream() .collect(Collectors.toMap(SrmInquiryItemDO::getId, Function.identity())); List participants = inquirySupplierMapper.selectListByInquiryId(inquiry.getId()); Set participantIds = participants.stream() .map(SrmInquirySupplierDO::getSupplierId).collect(Collectors.toSet()); Set touchedSupplierIds = new HashSet<>(); for (SrmInquiryQuoteSaveReqVO.Quote quoteVO : saveReqVO.getQuotes()) { SrmInquiryItemDO item = itemMap.get(quoteVO.getInquiryItemId()); if (item == null) { throw new ServiceException(ErrorCodeConstants.INQUIRY_QUOTE_NOT_EXISTS.getCode(), "询价产品行ID=" + quoteVO.getInquiryItemId() + " 不属于询价单【" + inquiry.getNo() + "】,请刷新比价页面后重新录入报价。"); } if (!participantIds.contains(quoteVO.getSupplierId())) { throw new ServiceException(ErrorCodeConstants.INQUIRY_SUPPLIER_NOT_PARTICIPANT.getCode(), "供应商ID=" + quoteVO.getSupplierId() + " 未参与询价单【" + inquiry.getNo() + "】,请先在询价单中添加该供应商。"); } SrmInquiryQuoteDO existing = inquiryQuoteMapper.selectByItemIdAndSupplierId( quoteVO.getInquiryItemId(), quoteVO.getSupplierId()); if (quoteVO.getPrice() == null) { // 清空报价 if (existing != null) { inquiryQuoteMapper.deleteById(existing.getId()); touchedSupplierIds.add(quoteVO.getSupplierId()); } continue; } if (quoteVO.getPrice().compareTo(BigDecimal.ZERO) <= 0) { throw new ServiceException(ErrorCodeConstants.INQUIRY_QUOTE_PRICE_INVALID.getCode(), "产品【" + item.getProductName() + "】供应商ID=" + quoteVO.getSupplierId() + " 的报价单价为【" + quoteVO.getPrice() + "】,必须大于 0,请修改后重新保存。"); } SrmInquiryQuoteDO quote = BeanUtils.toBean(quoteVO, SrmInquiryQuoteDO.class); quote.setInquiryId(inquiry.getId()); quote.setIsSelected(existing != null && Boolean.TRUE.equals(existing.getIsSelected())); if (existing != null) { quote.setId(existing.getId()); inquiryQuoteMapper.updateById(quote); } else { inquiryQuoteMapper.insert(quote); } touchedSupplierIds.add(quoteVO.getSupplierId()); } // 刷新参与供应商报价状态 LocalDateTime now = LocalDateTime.now(); for (Long supplierId : touchedSupplierIds) { SrmInquirySupplierDO participant = inquirySupplierMapper .selectByInquiryIdAndSupplierId(inquiry.getId(), supplierId); if (participant == null) { continue; } Long quoteCount = inquiryQuoteMapper.selectCount(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(SrmInquiryQuoteDO::getInquiryId, inquiry.getId()) .eq(SrmInquiryQuoteDO::getSupplierId, supplierId)); SrmInquirySupplierDO updateObj = new SrmInquirySupplierDO(); updateObj.setId(participant.getId()); updateObj.setQuoteStatus(quoteCount != null && quoteCount > 0 ? 10 : 0); updateObj.setQuoteTime(now); inquirySupplierMapper.updateById(updateObj); } } @Override public SrmInquiryRespVO getCompare(Long id) { return getInquiryDetail(id); } @Override @Transactional(rollbackFor = Exception.class) public SrmInquiryConfirmRespVO confirmInquiry(SrmInquiryConfirmReqVO confirmReqVO) { SrmInquiryDO inquiry = validateInquiryExists(confirmReqVO.getId()); if (!InquiryStatusEnum.DRAFT.getStatus().equals(inquiry.getStatus()) && !InquiryStatusEnum.QUOTING.getStatus().equals(inquiry.getStatus())) { throw new ServiceException(ErrorCodeConstants.INQUIRY_STATUS_NOT_CONFIRMABLE.getCode(), "询价单【" + inquiry.getNo() + "】当前状态为【" + statusName(inquiry.getStatus()) + "】,仅草稿或询价中状态可确认转订单。"); } List items = inquiryItemMapper.selectListByInquiryId(inquiry.getId()); if (CollUtil.isEmpty(items)) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.INQUIRY_ITEM_EMPTY); } Map itemMap = items.stream() .collect(Collectors.toMap(SrmInquiryItemDO::getId, Function.identity())); Set participantIds = inquirySupplierMapper.selectListByInquiryId(inquiry.getId()).stream() .map(SrmInquirySupplierDO::getSupplierId).collect(Collectors.toSet()); List allQuotes = inquiryQuoteMapper.selectListByInquiryId(inquiry.getId()); Map quoteMap = allQuotes.stream() .collect(Collectors.toMap(SrmInquiryQuoteDO::getId, Function.identity())); // 1. 校验每个产品恰好选择一条有效报价 // 未显式传 selections 时,使用询价单中已保存的中标选择(创建/编辑时一并录入) List selections = confirmReqVO.getSelections(); if (CollUtil.isEmpty(selections)) { selections = new ArrayList<>(); for (SrmInquiryItemDO item : items) { if (item.getSelectedQuoteId() != null) { SrmInquiryConfirmReqVO.Selection selection = new SrmInquiryConfirmReqVO.Selection(); selection.setInquiryItemId(item.getId()); selection.setQuoteId(item.getSelectedQuoteId()); selections.add(selection); } } if (CollUtil.isEmpty(selections)) { throw new ServiceException(ErrorCodeConstants.INQUIRY_WINNER_REQUIRED.getCode(), "询价单【" + inquiry.getNo() + "】尚未保存任何中标选择,请编辑询价单为每个产品选择中标供应商后再确认。"); } } Map selectedByItem = new HashMap<>(); for (SrmInquiryConfirmReqVO.Selection selection : selections) { SrmInquiryItemDO item = itemMap.get(selection.getInquiryItemId()); if (item == null) { throw new ServiceException(ErrorCodeConstants.INQUIRY_WINNER_QUOTE_MISMATCH.getCode(), "询价产品行ID=" + selection.getInquiryItemId() + " 不属于询价单【" + inquiry.getNo() + "】,请刷新比价页面后重新选择中标报价。"); } if (selectedByItem.containsKey(item.getId())) { throw new ServiceException(ErrorCodeConstants.INQUIRY_WINNER_DUPLICATE.getCode(), "产品【" + item.getProductName() + "】选择了多条中标报价,请为每个产品只保留一条中标报价。"); } SrmInquiryQuoteDO quote = quoteMap.get(selection.getQuoteId()); if (quote == null) { throw new ServiceException(ErrorCodeConstants.INQUIRY_QUOTE_NOT_EXISTS.getCode(), "中标报价ID=" + selection.getQuoteId() + " 不存在,请刷新比价页面后重新选择。"); } if (!quote.getInquiryItemId().equals(item.getId())) { throw new ServiceException(ErrorCodeConstants.INQUIRY_WINNER_QUOTE_MISMATCH.getCode(), "产品【" + item.getProductName() + "】选择的中标报价属于产品行ID=" + quote.getInquiryItemId() + ",请重新选择该产品自己的报价。"); } if (!participantIds.contains(quote.getSupplierId())) { throw new ServiceException(ErrorCodeConstants.INQUIRY_SUPPLIER_NOT_PARTICIPANT.getCode(), "供应商ID=" + quote.getSupplierId() + " 未参与询价单【" + inquiry.getNo() + "】,无法作为中标供应商。"); } if (quote.getPrice() == null || quote.getPrice().compareTo(BigDecimal.ZERO) <= 0) { throw new ServiceException(ErrorCodeConstants.INQUIRY_QUOTE_PRICE_INVALID.getCode(), "产品【" + item.getProductName() + "】的中标报价单价为空或小于等于 0,请先录入报价后再确认。"); } selectedByItem.put(item.getId(), quote); } for (SrmInquiryItemDO item : items) { if (!selectedByItem.containsKey(item.getId())) { throw new ServiceException(ErrorCodeConstants.INQUIRY_WINNER_REQUIRED.getCode(), "产品【" + StrUtil.blankToDefault(item.getProductName(), "ID=" + item.getProductId()) + "】尚未选择中标供应商,请为该产品选择一条中标报价后再确认。"); } } // 2. 记录中标结果 for (SrmInquiryQuoteDO quote : allQuotes) { if (Boolean.TRUE.equals(quote.getIsSelected())) { inquiryQuoteMapper.updateById(new SrmInquiryQuoteDO().setId(quote.getId()).setIsSelected(false)); } } for (Map.Entry entry : selectedByItem.entrySet()) { SrmInquiryItemDO item = itemMap.get(entry.getKey()); SrmInquiryQuoteDO quote = entry.getValue(); inquiryQuoteMapper.updateById(new SrmInquiryQuoteDO().setId(quote.getId()).setIsSelected(true)); inquiryItemMapper.updateById(new SrmInquiryItemDO() .setId(item.getId()) .setSelectedSupplierId(quote.getSupplierId()) .setSelectedQuoteId(quote.getId()) .setSelectedPrice(quote.getPrice())); } // 3. 按中标供应商分组生成采购订单草稿 Map> winnersBySupplier = new LinkedHashMap<>(); for (Map.Entry entry : selectedByItem.entrySet()) { QuoteWithItem pair = new QuoteWithItem(itemMap.get(entry.getKey()), entry.getValue()); winnersBySupplier.computeIfAbsent(entry.getValue().getSupplierId(), k -> new ArrayList<>()).add(pair); } Map supplierMap = supplierService.getSupplierMap(winnersBySupplier.keySet()); List orders = new ArrayList<>(); for (Map.Entry> entry : winnersBySupplier.entrySet()) { Long supplierId = entry.getKey(); SrmSupplierDO supplier = supplierMap.get(supplierId); String supplierName = supplier != null ? supplier.getName() : "ID=" + supplierId; List orderItems = new ArrayList<>(); for (QuoteWithItem pair : entry.getValue()) { SrmInquiryItemDO item = pair.item; SrmInquiryQuoteDO quote = pair.quote; Long productUnitId = item.getProductUnitId(); if (productUnitId == null) { MdmItemRespDTO mdmItem = mdmItemApi.getItem(item.getProductId()).getCheckedData(); productUnitId = mdmItem != null ? mdmItem.getUnitMeasureId() : null; } ErpPurchaseOrderCreateReqDTO.Item orderItem = new ErpPurchaseOrderCreateReqDTO.Item(); orderItem.setProductId(item.getProductId()); orderItem.setProductUnitId(productUnitId); orderItem.setProductPrice(quote.getPrice()); orderItem.setCount(item.getQuantity()); orderItem.setTaxPercent(quote.getTaxRate()); orderItem.setRemark("SRM询价 " + inquiry.getNo()); // 与采购申请/采购台账一致:新生成的采购明细默认质检 orderItem.setQcCheckFlag(true); orderItems.add(orderItem); } ErpPurchaseOrderCreateReqDTO createReqDTO = new ErpPurchaseOrderCreateReqDTO(); createReqDTO.setSupplierId(supplierId); createReqDTO.setOrderTime(LocalDateTime.now()); createReqDTO.setDiscountPercent(BigDecimal.ZERO); createReqDTO.setDepositPrice(BigDecimal.ZERO); createReqDTO.setSourceType(2); createReqDTO.setSourceNo(inquiry.getNo()); createReqDTO.setSourceId(inquiry.getId()); createReqDTO.setRemark("SRM询价自动生成,询价单号:" + inquiry.getNo()); createReqDTO.setItems(orderItems); Long purchaseOrderId; try { purchaseOrderId = erpPurchaseOrderApi.createPurchaseOrderDraft(createReqDTO).getCheckedData(); } catch (Exception ex) { log.error("[confirmInquiry] 询价单({}) 供应商({}) 生成采购订单失败", inquiry.getNo(), supplierId, ex); throw new ServiceException(ErrorCodeConstants.INQUIRY_ORDER_GENERATE_FAIL.getCode(), "询价单【" + inquiry.getNo() + "】为供应商【" + supplierName + "】生成采购订单失败:" + ex.getMessage() + "。请检查该供应商与产品资料后重试。"); } String purchaseOrderNo = null; try { ErpPurchaseOrderRespDTO order = erpPurchaseOrderApi.getPurchaseOrder(purchaseOrderId).getCheckedData(); purchaseOrderNo = order != null ? order.getNo() : null; } catch (Exception ex) { log.warn("[confirmInquiry] 获取采购订单号失败,purchaseOrderId={}", purchaseOrderId, ex); } inquiryOrderMapper.insert(SrmInquiryOrderDO.builder() .inquiryId(inquiry.getId()) .supplierId(supplierId) .purchaseOrderId(purchaseOrderId) .purchaseOrderNo(purchaseOrderNo) .build()); SrmInquiryConfirmRespVO.Order orderResult = new SrmInquiryConfirmRespVO.Order(); orderResult.setSupplierId(supplierId); orderResult.setSupplierName(supplierName); orderResult.setPurchaseOrderId(purchaseOrderId); orderResult.setPurchaseOrderNo(purchaseOrderNo); orders.add(orderResult); } // 4. 更新询价单为已转订单(状态条件更新,避免并发重复生成) int updated = inquiryMapper.updateByIdAndStatus(inquiry.getId(), inquiry.getStatus(), new SrmInquiryDO().setStatus(InquiryStatusEnum.CONFIRMED.getStatus())); if (updated == 0) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.INQUIRY_ALREADY_CONFIRMED); } SrmInquiryConfirmRespVO result = new SrmInquiryConfirmRespVO(); result.setInquiryId(inquiry.getId()); result.setOrders(orders); return result; } // ========== 组装 ========== private SrmInquiryRespVO buildInquiryResp(SrmInquiryDO inquiry) { SrmInquiryRespVO respVO = BeanUtils.toBean(inquiry, SrmInquiryRespVO.class); List items = inquiryItemMapper.selectListByInquiryId(inquiry.getId()); List participants = inquirySupplierMapper.selectListByInquiryId(inquiry.getId()); List quotes = inquiryQuoteMapper.selectListByInquiryId(inquiry.getId()); List inquiryOrders = inquiryOrderMapper.selectListByInquiryId(inquiry.getId()); Set supplierIds = new HashSet<>(); participants.forEach(p -> supplierIds.add(p.getSupplierId())); quotes.forEach(q -> supplierIds.add(q.getSupplierId())); inquiryOrders.forEach(o -> supplierIds.add(o.getSupplierId())); Map supplierMap = supplierService.getSupplierMap(supplierIds); respVO.setItems(BeanUtils.toBean(items, SrmInquiryRespVO.Item.class)); respVO.setQuotes(BeanUtils.toBean(quotes, SrmInquiryRespVO.Quote.class)); List supplierVOs = BeanUtils.toBean(participants, SrmInquiryRespVO.Supplier.class); supplierVOs.forEach(vo -> { SrmSupplierDO supplier = supplierMap.get(vo.getSupplierId()); if (supplier != null) { vo.setSupplierName(supplier.getName()); vo.setSupplierCode(supplier.getCode()); } }); respVO.setSuppliers(supplierVOs); List orderVOs = BeanUtils.toBean(inquiryOrders, SrmInquiryRespVO.Order.class); orderVOs.forEach(vo -> { SrmSupplierDO supplier = supplierMap.get(vo.getSupplierId()); if (supplier != null) { vo.setSupplierName(supplier.getName()); } }); respVO.setOrders(orderVOs); return respVO; } // ========== 保存辅助 ========== private void validateInquiryInput(SrmInquirySaveReqVO reqVO) { if (CollUtil.isEmpty(reqVO.getItems())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.INQUIRY_ITEM_EMPTY); } if (CollUtil.isEmpty(reqVO.getSuppliers())) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.INQUIRY_SUPPLIER_EMPTY); } Set productIds = new HashSet<>(); for (SrmInquirySaveReqVO.Item item : reqVO.getItems()) { if (!productIds.add(item.getProductId())) { throw new ServiceException(ErrorCodeConstants.INQUIRY_ITEM_EMPTY.getCode(), "产品ID=" + item.getProductId() + " 在询价单中重复添加,请为每个产品只保留一行。"); } if (item.getQuantity() == null || item.getQuantity().compareTo(BigDecimal.ZERO) <= 0) { throw new ServiceException(ErrorCodeConstants.INQUIRY_ITEM_EMPTY.getCode(), "产品【" + StrUtil.blankToDefault(item.getProductName(), "ID=" + item.getProductId()) + "】的采购数量必须大于 0,请填写数量后重新保存。"); } } Set supplierIds = new HashSet<>(); for (SrmInquirySaveReqVO.Supplier supplier : reqVO.getSuppliers()) { if (!supplierIds.add(supplier.getSupplierId())) { throw new ServiceException(ErrorCodeConstants.INQUIRY_SUPPLIER_DUPLICATE.getCode(), "供应商ID=" + supplier.getSupplierId() + " 在询价单中重复添加,请为每个供应商只保留一行。"); } } validateSuppliers(supplierIds); } private void validateSuppliers(Collection supplierIds) { List suppliers = supplierService.getSupplierList(supplierIds); Map map = suppliers.stream() .collect(Collectors.toMap(SrmSupplierDO::getId, Function.identity())); for (Long supplierId : supplierIds) { SrmSupplierDO supplier = map.get(supplierId); if (supplier == null) { throw new ServiceException(ErrorCodeConstants.SUPPLIER_NOT_EXISTS.getCode(), "供应商ID=" + supplierId + " 不存在,请刷新供应商列表后重新选择。"); } if (supplier.getStatus() != null && !CommonStatusEnum.ENABLE.getStatus().equals(supplier.getStatus())) { throw new ServiceException(ErrorCodeConstants.SUPPLIER_NOT_ENABLE.getCode(), "供应商【" + supplier.getName() + "】已停用,请先在供应商档案中启用后再参与询价。"); } } } private List saveItems(Long inquiryId, List itemVOs) { List savedItems = new ArrayList<>(); Set productIds = itemVOs.stream().map(SrmInquirySaveReqVO.Item::getProductId) .collect(Collectors.toSet()); Map itemMap = mdmItemApi.getItemList(productIds).getCheckedData().stream() .collect(Collectors.toMap(MdmItemRespDTO::getId, Function.identity())); Set unitIds = itemMap.values().stream().map(MdmItemRespDTO::getUnitMeasureId) .filter(Objects::nonNull).collect(Collectors.toSet()); Map unitMap = unitIds.isEmpty() ? Collections.emptyMap() : mdmUnitMeasureApi.getUnitMeasureMap(unitIds); for (int i = 0; i < itemVOs.size(); i++) { SrmInquirySaveReqVO.Item itemVO = itemVOs.get(i); MdmItemRespDTO mdmItem = itemMap.get(itemVO.getProductId()); if (mdmItem == null) { throw new ServiceException(ErrorCodeConstants.INQUIRY_ITEM_EMPTY.getCode(), "产品ID=" + itemVO.getProductId() + " 不存在,请重新选择产品。"); } SrmInquiryItemDO item = BeanUtils.toBean(itemVO, SrmInquiryItemDO.class); item.setId(null); item.setInquiryId(inquiryId); item.setProductCode(StrUtil.blankToDefault(itemVO.getProductCode(), mdmItem.getCode())); item.setProductName(StrUtil.blankToDefault(itemVO.getProductName(), mdmItem.getName())); item.setProductSpec(StrUtil.blankToDefault(itemVO.getProductSpec(), mdmItem.getSpecification())); Long productUnitId = itemVO.getProductUnitId() != null ? itemVO.getProductUnitId() : mdmItem.getUnitMeasureId(); item.setProductUnitId(productUnitId); if (StrUtil.isBlank(itemVO.getUnitName()) && productUnitId != null) { MdmUnitMeasureRespDTO unit = unitMap.get(productUnitId); item.setUnitName(unit != null ? unit.getName() : null); } item.setSort(i); inquiryItemMapper.insert(item); savedItems.add(item); } return savedItems; } /** * 保存产品行报价与中标选择(创建/编辑询价单时一并写入) */ private void saveItemQuotes(Long inquiryId, List items, List itemVOs, Set participantIds) { for (int i = 0; i < items.size(); i++) { SrmInquiryItemDO item = items.get(i); SrmInquirySaveReqVO.Item itemVO = itemVOs.get(i); String productLabel = StrUtil.blankToDefault(item.getProductName(), "ID=" + item.getProductId()); List quoteVOs = itemVO.getQuotes() == null ? Collections.emptyList() : itemVO.getQuotes(); Set quoteSupplierIds = new HashSet<>(); SrmInquiryQuoteDO selectedQuote = null; for (SrmInquirySaveReqVO.Quote quoteVO : quoteVOs) { if (!quoteSupplierIds.add(quoteVO.getSupplierId())) { throw new ServiceException(ErrorCodeConstants.INQUIRY_SUPPLIER_DUPLICATE.getCode(), "产品【" + productLabel + "】的供应商ID=" + quoteVO.getSupplierId() + " 报价重复,请为每个供应商只保留一条报价。"); } if (!participantIds.contains(quoteVO.getSupplierId())) { throw new ServiceException(ErrorCodeConstants.INQUIRY_SUPPLIER_NOT_PARTICIPANT.getCode(), "供应商ID=" + quoteVO.getSupplierId() + " 未参与询价单,无法录入产品【" + productLabel + "】的报价,请先在参与供应商中添加该供应商。"); } if (quoteVO.getPrice() == null || quoteVO.getPrice().compareTo(BigDecimal.ZERO) <= 0) { throw new ServiceException(ErrorCodeConstants.INQUIRY_QUOTE_PRICE_INVALID.getCode(), "产品【" + productLabel + "】供应商ID=" + quoteVO.getSupplierId() + " 的报价单价为【" + quoteVO.getPrice() + "】,必须大于 0,请修改后重新保存。"); } boolean selected = itemVO.getSelectedSupplierId() != null && itemVO.getSelectedSupplierId().equals(quoteVO.getSupplierId()); SrmInquiryQuoteDO quote = BeanUtils.toBean(quoteVO, SrmInquiryQuoteDO.class); quote.setInquiryId(inquiryId); quote.setInquiryItemId(item.getId()); quote.setIsSelected(selected); inquiryQuoteMapper.insert(quote); if (selected) { selectedQuote = quote; } } Long selectedSupplierId = itemVO.getSelectedSupplierId(); if (selectedSupplierId != null && selectedQuote == null) { throw new ServiceException(ErrorCodeConstants.INQUIRY_WINNER_QUOTE_MISMATCH.getCode(), "产品【" + productLabel + "】选择的中标供应商ID=" + selectedSupplierId + " 没有录入报价,请先填写该供应商的报价,或改选其他已报价供应商。"); } if (selectedQuote != null) { inquiryItemMapper.updateById(new SrmInquiryItemDO() .setId(item.getId()) .setSelectedSupplierId(selectedQuote.getSupplierId()) .setSelectedQuoteId(selectedQuote.getId()) .setSelectedPrice(selectedQuote.getPrice())); } } } private void saveSuppliers(Long inquiryId, List supplierVOs) { for (SrmInquirySaveReqVO.Supplier supplierVO : supplierVOs) { inquirySupplierMapper.insert(SrmInquirySupplierDO.builder() .inquiryId(inquiryId) .supplierId(supplierVO.getSupplierId()) .quoteStatus(0) .remark(supplierVO.getRemark()) .build()); } } // ========== 校验 ========== private SrmInquiryDO validateInquiryExists(Long id) { SrmInquiryDO inquiry = inquiryMapper.selectById(id); if (inquiry == null) { throw ServiceExceptionUtil.exception(ErrorCodeConstants.INQUIRY_NOT_EXISTS); } return inquiry; } private String generateInquiryNo() { String date = LocalDate.now().format(NO_DATE_FORMAT); for (int attempt = 0; attempt < 20; attempt++) { int seq = (int) (Math.random() * 9000) + 1000; String no = "XJ" + date + String.format("%04d", seq); if (inquiryMapper.selectByNo(no) == null) { return no; } } throw new ServiceException(ErrorCodeConstants.INQUIRY_NO_GENERATE_FAIL.getCode(), "询价单号在多次尝试后仍重复,请稍后重试。"); } private String statusName(Integer status) { for (InquiryStatusEnum e : InquiryStatusEnum.values()) { if (e.getStatus().equals(status)) { return e.getName(); } } return "未知"; } private static class QuoteWithItem { private final SrmInquiryItemDO item; private final SrmInquiryQuoteDO quote; private QuoteWithItem(SrmInquiryItemDO item, SrmInquiryQuoteDO quote) { this.item = item; this.quote = quote; } } }