云
2026-10-31 bf5f52656e128f578ed42c7979c31cf4b476ce46
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java
@@ -522,6 +522,7 @@
    @Override
    public ErpPurchaseRequestImportRespVO importPurchaseRequestList(List<ErpPurchaseRequestImportExcelVO> importRequests,
                                                                    ErpPurchaseRequestImportReqVO importReqVO) {
        if (Boolean.TRUE.equals(importReqVO.getUpdateSupport())) throw exception(PURCHASE_REQUEST_IMPORT_INVALID, "仅支持新增,不支持覆盖");
        if (CollUtil.isEmpty(importRequests)) {
            throw exception(PURCHASE_REQUEST_IMPORT_LIST_IS_EMPTY);
        }
@@ -543,11 +544,6 @@
            if (isBlankRow(row)) {
                continue;
            }
            // 1.2 物料编码为空:业务上无法定位物料,跳过并明确告知用户
            if (StrUtil.isBlank(row.getProductCode())) {
                respVO.getSkippedRows().put(rowLabel, "物料编码为空,该行未导入");
                continue;
            }
            validRows.add(new ImportRow(excelRowNo, row));
        }
        respVO.setTotalRows(validRows.size() + respVO.getSkippedRows().size());
@@ -566,7 +562,8 @@
        Map<String, Integer> groupIndexMap = new LinkedHashMap<>();
        for (ImportRow row : validRows) {
            String no = row.data.getNo();
            String groupKey = StrUtil.isNotBlank(no) ? no : ("__row_" + row.excelRowNo);
            if (StrUtil.isBlank(no)) { groups.add(new ArrayList<>(Collections.singletonList(row))); continue; }
            String groupKey = no.trim();
            Integer index = groupIndexMap.get(groupKey);
            if (index == null) {
                index = groups.size();
@@ -583,9 +580,8 @@
            String groupKey = StrUtil.isNotBlank(firstRow.data.getNo())
                    ? firstRow.data.getNo() : "第" + firstRow.excelRowNo + "行";
            try {
                ErpPurchaseRequestSaveReqVO createReqVO = buildPurchaseRequestImportSaveReqVO(
                        convertList(group, ImportRow::getData));
                Long requestId = createPurchaseRequest(createReqVO);
                Long requestId = cn.hutool.extra.spring.SpringUtil.getBean(ErpPurchaseRequestServiceImpl.class)
                        .createImportedPurchaseRequest(convertList(group, ImportRow::getData));
                respVO.getCreatePurchaseRequestNos().add(purchaseRequestMapper.selectById(requestId).getNo());
                respVO.setSuccessRows(respVO.getSuccessRows() == null
                        ? group.size() : respVO.getSuccessRows() + group.size());
@@ -597,7 +593,7 @@
                // 记录为失败并继续,保证其它能导的行正常入库
                log.error("[importPurchaseRequestList] 导入行({}) 发生非业务异常", groupKey, ex);
                respVO.getFailurePurchaseRequestNos().put(groupKey,
                        "导入失败:" + (ex instanceof NullPointerException ? "数据解析异常,请检查物料/单位编码" : ex.getMessage()));
                        "导入失败,请检查基础资料和申请明细");
            }
        }
        return respVO;
@@ -607,12 +603,14 @@
     * 判断一行是否完全空白(所有业务列都没有值)
     */
    private boolean isBlankRow(ErpPurchaseRequestImportExcelVO row) {
        return StrUtil.isBlank(row.getNo())
        return row == null || StrUtil.isBlank(row.getNo())
                && StrUtil.isBlank(row.getProductCode())
                && StrUtil.isBlank(row.getProductUnitCode())
                && row.getRequestTime() == null
                && row.getCount() == null
                && row.getProductPrice() == null
                && row.getDiscountPercent() == null && row.getDepositPrice() == null && row.getTaxPercent() == null
                && row.getDemandTime() == null && row.getQcCheckFlag() == null
                && StrUtil.isBlank(row.getSupplierName())
                && StrUtil.isBlank(row.getRequestReason())
                && StrUtil.isBlank(row.getRemark())
@@ -639,11 +637,34 @@
    /**
     * 将同一张采购申请的多行明细转换成创建 Request VO
     */
    @Transactional(rollbackFor = Exception.class)
    public Long createImportedPurchaseRequest(List<ErpPurchaseRequestImportExcelVO> group) {
        return createPurchaseRequest(buildPurchaseRequestImportSaveReqVO(group));
    }
    private ErpPurchaseRequestSaveReqVO buildPurchaseRequestImportSaveReqVO(List<ErpPurchaseRequestImportExcelVO> group) {
        ErpPurchaseRequestImportExcelVO first = group.get(0);
        // 1. 校验申请时间
        if (first.getRequestTime() == null) {
            throw exception(PURCHASE_REQUEST_IMPORT_REQUEST_TIME_REQUIRED);
        }
        if (first.getDiscountPercent() != null && (first.getDiscountPercent().signum() < 0 || first.getDiscountPercent().compareTo(new BigDecimal("100")) > 0)
                || first.getDepositPrice() != null && first.getDepositPrice().signum() < 0) {
            throw exception(PURCHASE_REQUEST_IMPORT_INVALID, "优惠率须在 0 至 100 之间,定金不能为负数");
        }
        for (ErpPurchaseRequestImportExcelVO row : group) {
            if (row.getRequestTime() != null && !row.getRequestTime().equals(first.getRequestTime())
                    || StrUtil.isNotBlank(row.getSupplierName()) && !StrUtil.equals(first.getSupplierName(), row.getSupplierName())
                    || StrUtil.isNotBlank(row.getRequestReason()) && !StrUtil.equals(first.getRequestReason(), row.getRequestReason())
                    || row.getDiscountPercent() != null && (first.getDiscountPercent() == null || row.getDiscountPercent().compareTo(first.getDiscountPercent()) != 0)
                    || row.getDepositPrice() != null && (first.getDepositPrice() == null || row.getDepositPrice().compareTo(first.getDepositPrice()) != 0)
                    || StrUtil.isNotBlank(row.getRemark()) && !StrUtil.equals(first.getRemark(), row.getRemark())) {
                throw exception(PURCHASE_REQUEST_IMPORT_INVALID, "同一申请单的主表信息不一致");
            }
            if (StrUtil.isBlank(row.getProductCode()) || StrUtil.isBlank(row.getProductUnitCode()) || row.getCount() == null || row.getCount().signum() <= 0
                    || row.getProductPrice() == null || row.getProductPrice().signum() < 0
                    || row.getTaxPercent() != null && (row.getTaxPercent().signum() < 0 || row.getTaxPercent().compareTo(new BigDecimal("100")) > 0)) {
                throw exception(PURCHASE_REQUEST_IMPORT_INVALID, "每行须填写物料、单位、正数数量、非负单价;税率须在 0 至 100 之间");
            }
        }
        // 2. 解析供应商(可选)
        Long supplierId = null;
@@ -657,14 +678,17 @@
        // 3. 组装明细
        List<ErpPurchaseRequestSaveReqVO.Item> items = new ArrayList<>();
        for (ErpPurchaseRequestImportExcelVO row : group) {
            MdmItemRespDTO product = mdmItemApi.getItemByCode(row.getProductCode()).getCheckedData();
            MdmItemRespDTO product = mdmItemApi.getItemByCode(row.getProductCode().trim()).getCheckedData();
            if (product == null) {
                throw exception(PURCHASE_REQUEST_IMPORT_PRODUCT_NOT_EXISTS, row.getProductCode());
            }
            MdmUnitMeasureRespDTO unit = mdmUnitMeasureApi.getUnitMeasureByCode(row.getProductUnitCode()).getCheckedData();
            MdmUnitMeasureRespDTO unit = mdmUnitMeasureApi.getUnitMeasureByCode(row.getProductUnitCode().trim()).getCheckedData();
            if (unit == null) {
                throw exception(PURCHASE_REQUEST_IMPORT_UNIT_NOT_EXISTS, row.getProductUnitCode());
            }
            if (product.getStatus() != null && product.getStatus() != 0 || unit.getStatus() != null && unit.getStatus() != 0) {
                throw exception(PURCHASE_REQUEST_IMPORT_INVALID, "物料或计量单位已停用");
            }
            ErpPurchaseRequestSaveReqVO.Item item = new ErpPurchaseRequestSaveReqVO.Item()
                    .setProductId(product.getId())
                    .setProductUnitId(unit.getId())