xiaoyi
7 天以前 05ab6e3f69a5e73624b2573db043f5923e8f7d8b
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptServiceImpl.java
@@ -2,12 +2,19 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptImportExcelVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.line.MesWmItemReceiptLineSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.vo.MesWmDocImportRespVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptDetailDO;
@@ -34,12 +41,19 @@
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.validation.annotation.Validated;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -61,6 +75,8 @@
    @Resource
    private MesWmItemReceiptLineService itemReceiptLineService;
    @Resource
    private PlatformTransactionManager transactionManager;
    @Resource
    private MesWmItemReceiptDetailService itemReceiptDetailService;
    @Resource
    @Lazy
@@ -76,6 +92,10 @@
    @Resource
    private MesWmArrivalNoticeLineService arrivalNoticeLineService;
    @Resource
    @Lazy
    private MesMdItemService mdItemService;
    @Resource
    private BpmProcessInstanceApi processInstanceApi;
@@ -518,4 +538,188 @@
        return "IR" + System.currentTimeMillis();
    }
    // ==================== 批量导入 ====================
    @Override
    public MesWmDocImportRespVO importItemReceiptList(List<MesWmItemReceiptImportExcelVO> importList) {
        MesWmDocImportRespVO respVO = MesWmDocImportRespVO.empty();
        if (CollUtil.isEmpty(importList)) {
            return respVO;
        }
        // 1. 按「入库单编码」归并成单据 + 明细行
        Map<String, List<MesWmItemReceiptImportExcelVO>> grouped = new LinkedHashMap<>();
        int index = 0;
        for (MesWmItemReceiptImportExcelVO row : importList) {
            index++;
            String code = StrUtil.trim(row.getReceiptCode());
            if (StrUtil.isBlank(code)) {
                respVO.getFailureCodes().put("第 " + index + " 行", "入库单编码不能为空");
                continue;
            }
            grouped.computeIfAbsent(code, k -> new ArrayList<>()).add(row);
        }
        // 2. 预加载供应商,按名称建索引
        // 注意:getSupplierSimpleList() 只返回特定状态的供应商,可能缺少历史/停用供应商,
        // 因此这里以「供应商名称」逐行兜底查询(见 resolveVendorId)。
        Map<String, Long> vendorMap = new HashMap<>();
        try {
            List<SrmSupplierRespDTO> suppliers = srmSupplierApi.getSupplierSimpleList().getCheckedData();
            if (CollUtil.isNotEmpty(suppliers)) {
                suppliers.forEach(s -> {
                    if (StrUtil.isNotBlank(s.getName())) {
                        vendorMap.put(s.getName().trim(), s.getId());
                    }
                });
            }
        } catch (Exception e) {
            log.warn("[importItemReceiptList][加载供应商简洁列表失败,将逐行查询]", e);
        }
        // 3. 逐张单据处理
        for (Map.Entry<String, List<MesWmItemReceiptImportExcelVO>> entry : grouped.entrySet()) {
            String code = entry.getKey();
            List<MesWmItemReceiptImportExcelVO> rows = entry.getValue();
            MesWmItemReceiptImportExcelVO head = rows.get(0);
            try {
                // 3.1 校验供应商
                String vendorName = StrUtil.trim(head.getVendorName());
                Long vendorId = vendorName == null ? null : vendorMap.get(vendorName);
                if (vendorId == null && vendorName != null) {
                    // 兜底:简洁列表可能未覆盖该供应商,按名称查询
                    SrmSupplierRespDTO supplier = findSupplierByName(vendorName);
                    if (supplier != null) {
                        vendorId = supplier.getId();
                        vendorMap.put(vendorName, vendorId);
                    }
                }
                if (vendorId == null) {
                    respVO.getFailureCodes().put(code, "供应商[" + vendorName + "]不存在");
                    continue;
                }
                // 3.2 校验日期与编码
                LocalDateTime receiptDate = parseImportDateTime(head.getReceiptDate());
                if (receiptDate == null) {
                    respVO.getFailureCodes().put(code, "入库日期不能为空或格式不正确(支持 yyyy-MM-dd / yyyy-MM-dd HH:mm:ss)");
                    continue;
                }
                if (itemReceiptMapper.selectByCode(code) != null) {
                    respVO.getFailureCodes().put(code, "入库单编码已存在");
                    continue;
                }
                // 3.3 创建单据头
                MesWmItemReceiptSaveReqVO saveReqVO = new MesWmItemReceiptSaveReqVO();
                saveReqVO.setCode(code);
                saveReqVO.setName(StrUtil.blankToDefault(StrUtil.trim(head.getReceiptName()), code));
                saveReqVO.setVendorId(vendorId);
                saveReqVO.setReceiptDate(receiptDate);
                saveReqVO.setPurchaseOrderCode(StrUtil.trim(head.getPurchaseOrderCode()));
                saveReqVO.setRemark(StrUtil.trim(head.getRemark()));
                Long receiptId = createItemReceipt(saveReqVO);
                // 3.4 逐行创建明细
                int createdLines = 0;
                for (MesWmItemReceiptImportExcelVO row : rows) {
                    if (createItemReceiptLineByImport(receiptId, row, respVO, code)) {
                        createdLines++;
                    }
                }
                if (createdLines == 0) {
                    // 没有任何有效明细时不保留空单,也不报告导入成功。
                    itemReceiptMapper.deleteById(receiptId);
                } else {
                    respVO.getCreateCodes().add(code);
                }
            } catch (Exception e) {
                respVO.getFailureCodes().put(code, e.getMessage() == null ? "导入失败" : e.getMessage());
            }
        }
        return respVO;
    }
    /**
     * 导入时创建单行明细;失败只记录不抛出,避免影响整张单据的其它行
     */
    private boolean createItemReceiptLineByImport(Long receiptId, MesWmItemReceiptImportExcelVO row,
                                               MesWmDocImportRespVO respVO, String receiptCode) {
        String itemCode = StrUtil.trim(row.getItemCode());
        MesMdItemDO item = itemCode == null ? null : mdItemService.getItemByCode(itemCode);
        if (item == null) {
            respVO.getFailureCodes().put(receiptCode, "物料编码[" + itemCode + "]不存在,该行已跳过");
            return false;
        }
        if (row.getReceivedQuantity() == null || row.getReceivedQuantity().signum() <= 0) {
            respVO.getFailureCodes().put(receiptCode, "物料[" + itemCode + "]入库数量必须大于 0,该行已跳过");
            return false;
        }
        try {
            MesWmItemReceiptLineSaveReqVO lineReqVO = new MesWmItemReceiptLineSaveReqVO();
            lineReqVO.setReceiptId(receiptId);
            lineReqVO.setItemId(item.getId());
            lineReqVO.setReceivedQuantity(row.getReceivedQuantity());
            lineReqVO.setProductionDate(parseImportDateTime(row.getProductionDate()));
            lineReqVO.setExpireDate(parseImportDateTime(row.getExpireDate()));
            lineReqVO.setLotNumber(StrUtil.trim(row.getLotNumber()));
            lineReqVO.setRemark(StrUtil.trim(row.getRemark()));
            // 每行独立事务:批次校验失败时只回滚该行,避免污染整次导入事务。
            new TransactionTemplate(transactionManager).executeWithoutResult(
                    status -> itemReceiptLineService.createItemReceiptLine(lineReqVO));
            return true;
        } catch (Exception e) {
            respVO.getFailureCodes().put(receiptCode, "物料[" + itemCode + "]明细创建失败:" + e.getMessage());
            return false;
        }
    }
    /**
     * 按名称查找供应商(兜底)
     *
     * getSupplierSimpleList() 只返回已通过准入审批的供应商,
     * 历史导入或未走准入流程的供应商不在其中,因此退化为全量匹配。
     */
    private SrmSupplierRespDTO findSupplierByName(String name) {
        try {
            var simple = srmSupplierApi.getSupplierSimpleList();
            if (simple != null && CollUtil.isNotEmpty(simple.getCheckedData())) {
                SrmSupplierRespDTO hit = simple.getCheckedData().stream()
                        .filter(s -> name.equals(StrUtil.trim(s.getName())))
                        .findFirst().orElse(null);
                if (hit != null) {
                    return hit;
                }
            }
        } catch (Exception e) {
            log.warn("[findSupplierByName][简洁列表查询失败,改用全量]", e);
        }
        try {
            var all = srmSupplierApi.getSupplierAllList();
            if (all != null && CollUtil.isNotEmpty(all.getCheckedData())) {
                return all.getCheckedData().stream()
                        .filter(s -> name.equals(StrUtil.trim(s.getName())))
                        .findFirst().orElse(null);
            }
        } catch (Exception e) {
            log.warn("[findSupplierByName][全量列表查询失败]", e);
        }
        return null;
    }
    /**
     * 解析导入日期,支持 yyyy-MM-dd 与 yyyy-MM-dd HH:mm:ss,失败返回 null
     */
    private LocalDateTime parseImportDateTime(String text) {
        if (StrUtil.isBlank(text)) {
            return null;
        }
        String value = text.trim().replace('/', '-');
        try {
            if (value.length() <= 10) {
                return LocalDate.parse(value).atStartOfDay();
            }
            return LocalDateTime.parse(value.replace(' ', 'T'));
        } catch (Exception e) {
            return null;
        }
    }
}