| | |
| | | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | |
| | | @Resource |
| | | private ErpSaleOrderApi saleOrderApi; |
| | | |
| | | @Resource |
| | | @Lazy |
| | | private MesMdItemService mdItemService; |
| | | |
| | | @Override |
| | | public Long createProductSales(MesWmProductSalesSaveReqVO createReqVO) { |
| | |
| | | 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; |
| | | } |
| | | } |
| | | |
| | | } |
| | | |