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<SrmInquiryItemDO> 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<SrmInquiryItemDO> 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<SrmInquiryDO> getInquiryPage(SrmInquiryPageReqVO pageReqVO) {
|
return inquiryMapper.selectPage(pageReqVO);
|
}
|
|
@Override
|
public List<SrmInquiryDO> 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<SrmInquiryItemDO> items = inquiryItemMapper.selectListByInquiryId(inquiry.getId());
|
Map<Long, SrmInquiryItemDO> itemMap = items.stream()
|
.collect(Collectors.toMap(SrmInquiryItemDO::getId, Function.identity()));
|
List<SrmInquirySupplierDO> participants = inquirySupplierMapper.selectListByInquiryId(inquiry.getId());
|
Set<Long> participantIds = participants.stream()
|
.map(SrmInquirySupplierDO::getSupplierId).collect(Collectors.toSet());
|
|
Set<Long> 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<SrmInquiryQuoteDO>()
|
.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<SrmInquiryItemDO> items = inquiryItemMapper.selectListByInquiryId(inquiry.getId());
|
if (CollUtil.isEmpty(items)) {
|
throw ServiceExceptionUtil.exception(ErrorCodeConstants.INQUIRY_ITEM_EMPTY);
|
}
|
Map<Long, SrmInquiryItemDO> itemMap = items.stream()
|
.collect(Collectors.toMap(SrmInquiryItemDO::getId, Function.identity()));
|
Set<Long> participantIds = inquirySupplierMapper.selectListByInquiryId(inquiry.getId()).stream()
|
.map(SrmInquirySupplierDO::getSupplierId).collect(Collectors.toSet());
|
List<SrmInquiryQuoteDO> allQuotes = inquiryQuoteMapper.selectListByInquiryId(inquiry.getId());
|
Map<Long, SrmInquiryQuoteDO> quoteMap = allQuotes.stream()
|
.collect(Collectors.toMap(SrmInquiryQuoteDO::getId, Function.identity()));
|
|
// 1. 校验每个产品恰好选择一条有效报价
|
// 未显式传 selections 时,使用询价单中已保存的中标选择(创建/编辑时一并录入)
|
List<SrmInquiryConfirmReqVO.Selection> 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<Long, SrmInquiryQuoteDO> 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<Long, SrmInquiryQuoteDO> 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<Long, List<QuoteWithItem>> winnersBySupplier = new LinkedHashMap<>();
|
for (Map.Entry<Long, SrmInquiryQuoteDO> entry : selectedByItem.entrySet()) {
|
QuoteWithItem pair = new QuoteWithItem(itemMap.get(entry.getKey()), entry.getValue());
|
winnersBySupplier.computeIfAbsent(entry.getValue().getSupplierId(), k -> new ArrayList<>()).add(pair);
|
}
|
Map<Long, SrmSupplierDO> supplierMap = supplierService.getSupplierMap(winnersBySupplier.keySet());
|
List<SrmInquiryConfirmRespVO.Order> orders = new ArrayList<>();
|
for (Map.Entry<Long, List<QuoteWithItem>> entry : winnersBySupplier.entrySet()) {
|
Long supplierId = entry.getKey();
|
SrmSupplierDO supplier = supplierMap.get(supplierId);
|
String supplierName = supplier != null ? supplier.getName() : "ID=" + supplierId;
|
|
List<ErpPurchaseOrderCreateReqDTO.Item> 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.setPaymentDate(confirmReqVO.getPaymentDate().atStartOfDay());
|
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<SrmInquiryItemDO> items = inquiryItemMapper.selectListByInquiryId(inquiry.getId());
|
List<SrmInquirySupplierDO> participants = inquirySupplierMapper.selectListByInquiryId(inquiry.getId());
|
List<SrmInquiryQuoteDO> quotes = inquiryQuoteMapper.selectListByInquiryId(inquiry.getId());
|
List<SrmInquiryOrderDO> inquiryOrders = inquiryOrderMapper.selectListByInquiryId(inquiry.getId());
|
|
Set<Long> 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<Long, SrmSupplierDO> supplierMap = supplierService.getSupplierMap(supplierIds);
|
|
respVO.setItems(BeanUtils.toBean(items, SrmInquiryRespVO.Item.class));
|
respVO.setQuotes(BeanUtils.toBean(quotes, SrmInquiryRespVO.Quote.class));
|
|
List<SrmInquiryRespVO.Supplier> 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<SrmInquiryRespVO.Order> 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<Long> 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<Long> 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<Long> supplierIds) {
|
List<SrmSupplierDO> suppliers = supplierService.getSupplierList(supplierIds);
|
Map<Long, SrmSupplierDO> 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<SrmInquiryItemDO> saveItems(Long inquiryId, List<SrmInquirySaveReqVO.Item> itemVOs) {
|
List<SrmInquiryItemDO> savedItems = new ArrayList<>();
|
|
Set<Long> productIds = itemVOs.stream().map(SrmInquirySaveReqVO.Item::getProductId)
|
.collect(Collectors.toSet());
|
Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemList(productIds).getCheckedData().stream()
|
.collect(Collectors.toMap(MdmItemRespDTO::getId, Function.identity()));
|
Set<Long> unitIds = itemMap.values().stream().map(MdmItemRespDTO::getUnitMeasureId)
|
.filter(Objects::nonNull).collect(Collectors.toSet());
|
Map<Long, MdmUnitMeasureRespDTO> 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<SrmInquiryItemDO> items,
|
List<SrmInquirySaveReqVO.Item> itemVOs, Set<Long> 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<SrmInquirySaveReqVO.Quote> quoteVOs = itemVO.getQuotes() == null
|
? Collections.emptyList() : itemVO.getQuotes();
|
Set<Long> 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<SrmInquirySaveReqVO.Supplier> 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;
|
}
|
}
|
|
}
|