xiaoyi
8 天以前 b5e1bceabb36fe1d1e5e88397280d12e7044c6dd
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.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.productsales.vo.MesWmProductSalesPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesImportExcelVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.line.MesWmProductSalesLineSaveReqVO;
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.crm.api.customer.dto.CrmCustomerRespDTO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesShippingReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.salesnotice.MesWmSalesNoticeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO;
@@ -38,7 +45,13 @@
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@@ -79,6 +92,10 @@
    @Resource
    private ErpSaleOrderApi saleOrderApi;
    @Resource
    @Lazy
    private MesMdItemService mdItemService;
    @Override
    public Long createProductSales(MesWmProductSalesSaveReqVO createReqVO) {
@@ -440,4 +457,137 @@
        productSalesMapper.updateById(new MesWmProductSalesDO().setId(id).setStatus(status));
    }
    // ==================== 批量导入 ====================
    @Override
    @Transactional(rollbackFor = Exception.class)
    public MesWmDocImportRespVO importProductSalesList(List<MesWmProductSalesImportExcelVO> importList) {
        MesWmDocImportRespVO respVO = MesWmDocImportRespVO.empty();
        if (CollUtil.isEmpty(importList)) {
            return respVO;
        }
        // 1. 按「出库单号」归并成单据 + 明细行
        Map<String, List<MesWmProductSalesImportExcelVO>> grouped = new LinkedHashMap<>();
        int index = 0;
        for (MesWmProductSalesImportExcelVO row : importList) {
            index++;
            String code = StrUtil.trim(row.getSalesCode());
            if (StrUtil.isBlank(code)) {
                respVO.getFailureCodes().put("第 " + index + " 行", "出库单号不能为空");
                continue;
            }
            grouped.computeIfAbsent(code, k -> new ArrayList<>()).add(row);
        }
        // 2. 预加载客户,按名称建索引
        Map<String, Long> clientMap = new HashMap<>();
        try {
            List<CrmCustomerRespDTO> customers = customerApi.getCustomerSimpleList();
            if (CollUtil.isNotEmpty(customers)) {
                customers.forEach(c -> {
                    if (StrUtil.isNotBlank(c.getName())) {
                        clientMap.put(c.getName().trim(), c.getId());
                    }
                });
            }
        } catch (Exception e) {
            log.warn("[importProductSalesList][加载客户失败]", e);
        }
        // 3. 逐张单据处理
        for (Map.Entry<String, List<MesWmProductSalesImportExcelVO>> entry : grouped.entrySet()) {
            String code = entry.getKey();
            List<MesWmProductSalesImportExcelVO> rows = entry.getValue();
            MesWmProductSalesImportExcelVO head = rows.get(0);
            try {
                // 3.1 校验客户
                String clientName = StrUtil.trim(head.getClientName());
                Long clientId = clientName == null ? null : clientMap.get(clientName);
                if (clientId == null) {
                    respVO.getFailureCodes().put(code, "客户[" + clientName + "]不存在");
                    continue;
                }
                // 3.2 校验日期与编码
                LocalDateTime salesDate = parseImportDateTime(head.getSalesDate());
                if (salesDate == null) {
                    respVO.getFailureCodes().put(code, "出库日期不能为空或格式不正确(支持 yyyy-MM-dd / yyyy-MM-dd HH:mm:ss)");
                    continue;
                }
                if (productSalesMapper.selectByCode(code) != null) {
                    respVO.getFailureCodes().put(code, "出库单号已存在");
                    continue;
                }
                // 3.3 创建单据头
                MesWmProductSalesSaveReqVO saveReqVO = new MesWmProductSalesSaveReqVO();
                saveReqVO.setCode(code);
                saveReqVO.setName(StrUtil.blankToDefault(StrUtil.trim(head.getSalesName()), code));
                saveReqVO.setClientId(clientId);
                saveReqVO.setSalesDate(salesDate);
                saveReqVO.setSalesOrderCode(StrUtil.trim(head.getSalesOrderCode()));
                saveReqVO.setContactName(StrUtil.trim(head.getContactName()));
                saveReqVO.setContactTelephone(StrUtil.trim(head.getContactTelephone()));
                saveReqVO.setContactAddress(StrUtil.trim(head.getContactAddress()));
                saveReqVO.setCarrier(StrUtil.trim(head.getCarrier()));
                saveReqVO.setShippingNumber(StrUtil.trim(head.getShippingNumber()));
                saveReqVO.setRemark(StrUtil.trim(head.getRemark()));
                Long salesId = createProductSales(saveReqVO);
                // 3.4 逐行创建明细
                for (MesWmProductSalesImportExcelVO row : rows) {
                    createProductSalesLineByImport(salesId, row, respVO, code);
                }
                respVO.getCreateCodes().add(code);
            } catch (Exception e) {
                respVO.getFailureCodes().put(code, e.getMessage() == null ? "导入失败" : e.getMessage());
            }
        }
        return respVO;
    }
    /**
     * 导入时创建单行明细;失败只记录不抛出,避免影响整张单据的其它行
     */
    private void createProductSalesLineByImport(Long salesId, MesWmProductSalesImportExcelVO row,
                                                MesWmDocImportRespVO respVO, String salesCode) {
        String itemCode = StrUtil.trim(row.getItemCode());
        MesMdItemDO item = itemCode == null ? null : mdItemService.getItemByCode(itemCode);
        if (item == null) {
            respVO.getFailureCodes().put(salesCode, "物料编码[" + itemCode + "]不存在,该行已跳过");
            return;
        }
        if (row.getQuantity() == null || row.getQuantity().signum() <= 0) {
            respVO.getFailureCodes().put(salesCode, "物料[" + itemCode + "]出库数量必须大于 0,该行已跳过");
            return;
        }
        try {
            MesWmProductSalesLineSaveReqVO lineReqVO = new MesWmProductSalesLineSaveReqVO();
            lineReqVO.setSalesId(salesId);
            lineReqVO.setItemId(item.getId());
            lineReqVO.setQuantity(row.getQuantity());
            lineReqVO.setBatchCode(StrUtil.trim(row.getBatchCode()));
            lineReqVO.setRemark(StrUtil.trim(row.getRemark()));
            productSalesLineService.createProductSalesLine(lineReqVO);
        } catch (Exception e) {
            respVO.getFailureCodes().put(salesCode, "物料[" + itemCode + "]明细创建失败:" + e.getMessage());
        }
    }
    /**
     * 解析导入日期,支持 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;
        }
    }
}