From 46855633ce1809df905efa8e0b03ca94a49a227b Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期六, 31 十月 2026 13:48:21 +0800
Subject: [PATCH] feat(mes): 销售出库单支持 Excel 导入
---
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java | 259 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 259 insertions(+), 0 deletions(-)
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
index 9338217..79e4404 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
@@ -2,13 +2,26 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
+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.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.crm.api.customer.dto.CrmCustomerRespDTO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesImportExcelVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesImportRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesShippingReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.line.MesWmProductSalesLineSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
+import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
+import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
+import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.salesnotice.MesWmSalesNoticeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDetailDO;
@@ -34,11 +47,20 @@
import lombok.extern.slf4j.Slf4j;
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 java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+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 +101,15 @@
@Resource
private ErpSaleOrderApi saleOrderApi;
+
+ @Resource
+ private MdmItemApi mdmItemApi;
+ @Resource
+ private MesWmBatchService batchService;
+ @Resource
+ private MesMdAutoCodeRecordService autoCodeRecordService;
+ @Resource
+ private PlatformTransactionManager transactionManager;
@Override
public Long createProductSales(MesWmProductSalesSaveReqVO createReqVO) {
@@ -441,4 +472,232 @@
productSalesMapper.updateById(new MesWmProductSalesDO().setId(id).setStatus(status));
}
+ // ==================== 瀵煎叆 ====================
+
+ @Override
+ public MesWmProductSalesImportRespVO importProductSalesList(List<MesWmProductSalesImportExcelVO> importList) {
+ MesWmProductSalesImportRespVO respVO = MesWmProductSalesImportRespVO.builder()
+ .createList(new ArrayList<>())
+ .failureList(new LinkedHashMap<>())
+ .build();
+ if (CollUtil.isEmpty(importList)) {
+ return respVO;
+ }
+
+ // 1. 棰勭儹鈥滃鎴峰悕绉� -> 瀹㈡埛缂栧彿鈥濇槧灏勶紙瀹㈡埛涓绘暟鎹湪 CRM 妯″潡锛�
+ Map<String, Long> customerIdMap = new HashMap<>();
+ List<CrmCustomerRespDTO> customers = customerApi.getCustomerSimpleList();
+ if (CollUtil.isNotEmpty(customers)) {
+ customers.forEach(customer -> {
+ if (customer != null && StrUtil.isNotBlank(customer.getName())) {
+ customerIdMap.putIfAbsent(customer.getName().trim(), customer.getId());
+ }
+ });
+ }
+
+ // 2. 鎸夆�滃嚭搴撳崟鍙封�濆垎缁勶細鍚屽崟鍙峰琛� = 涓�寮犲嚭搴撳崟鐨勫鏉℃槑缁嗭紱鍗曞彿涓虹┖鍒欐瘡琛岀嫭绔嬫垚鍗�
+ Map<String, List<ImportRow>> orderGroups = new LinkedHashMap<>();
+ for (int i = 0; i < importList.size(); i++) {
+ MesWmProductSalesImportExcelVO row = importList.get(i);
+ if (row == null || isEmptyImportRow(row)) {
+ continue; // 璺宠繃绌鸿
+ }
+ String key = StrUtil.isNotBlank(row.getCode()) ? row.getCode().trim() : "__row_" + (i + 2);
+ orderGroups.computeIfAbsent(key, k -> new ArrayList<>()).add(new ImportRow(row, i + 2));
+ }
+
+ // 3. 閫愬崟瀵煎叆锛氭瘡寮犲崟鐙珛浜嬪姟锛屽け璐ョ殑鍗曟暣鍗曞洖婊氾紝涓嶅奖鍝嶅叾瀹冨崟
+ TransactionTemplate importTxTemplate = new TransactionTemplate(transactionManager);
+ for (Map.Entry<String, List<ImportRow>> entry : orderGroups.entrySet()) {
+ List<ImportRow> rows = entry.getValue();
+ String label = StrUtil.isNotBlank(rows.get(0).data().getCode())
+ ? rows.get(0).data().getCode().trim() : "绗� " + rows.get(0).rowNum() + " 琛�";
+ try {
+ String code = importTxTemplate.execute(status -> createProductSalesByImport(rows, customerIdMap));
+ respVO.getCreateList().add(code);
+ } catch (Exception ex) {
+ respVO.getFailureList().put(label, resolveImportErrorMessage(ex));
+ }
+ }
+ return respVO;
+ }
+
+ /**
+ * 渚濇嵁鍚屼竴鍑哄簱鍗曞彿鐨勫琛屽鍏ユ暟鎹紝鍒涘缓涓�寮犻攢鍞嚭搴撳崟锛堝崟澶� + 鏄庣粏锛�
+ *
+ * @return 鍑哄簱鍗曞彿
+ */
+ private String createProductSalesByImport(List<ImportRow> rows, Map<String, Long> customerIdMap) {
+ ImportRow headRow = rows.get(0);
+ MesWmProductSalesImportExcelVO head = headRow.data();
+ String rowPrefix = "绗� " + headRow.rowNum() + " 琛岋細";
+
+ // 1. 瀹㈡埛锛堝彇璇ョ粍棣栬锛�
+ if (StrUtil.isBlank(head.getClientName())) {
+ throw new IllegalArgumentException(rowPrefix + "瀹㈡埛鍚嶇О涓嶈兘涓虹┖");
+ }
+ String clientName = head.getClientName().trim();
+ Long clientId = customerIdMap.get(clientName);
+ if (clientId == null) {
+ throw new IllegalArgumentException(rowPrefix + "瀹㈡埛鍚嶇О[" + clientName
+ + "]鍦� CRM 涓笉瀛樺湪锛岃鏍稿鍚嶇О鏄惁涓庣郴缁熷鎴峰畬鍏ㄤ竴鑷达紙鍚叏鍗婅銆佺┖鏍硷級");
+ }
+
+ // 2. 鍑哄簱鏃ユ湡锛堢暀绌哄彇褰撳ぉ锛�
+ LocalDateTime salesDate = parseImportSalesDate(head.getSalesDate(), rowPrefix);
+
+ // 3. 鍑哄簱鍗曞彿锛氬~浜嗗氨鐢紙鏍¢獙鍞竴锛夛紝娌″~鑷姩鐢熸垚
+ String code = StrUtil.isNotBlank(head.getCode()) ? head.getCode().trim()
+ : autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.WM_PRODUCT_SALES_CODE.getCode());
+ if (StrUtil.isBlank(code)) {
+ throw new IllegalArgumentException(rowPrefix
+ + "鍑哄簱鍗曞彿鑷姩鐢熸垚澶辫触锛岃绋嶅悗閲嶈瘯锛屾垨鍦ㄨ鍒楁墜宸ュ~鍐欏嚭搴撳崟鍙�");
+ }
+ if (productSalesMapper.selectByCode(code) != null) {
+ throw new IllegalArgumentException(rowPrefix + "鍑哄簱鍗曞彿[" + code
+ + "]宸插瓨鍦紝璇蜂慨鏀硅鍒楁垨娓呯┖璇ュ垪鐢辩郴缁熻嚜鍔ㄧ敓鎴�");
+ }
+
+ // 4. 鍒涘缓鍗曞ご锛堣崏绋跨姸鎬侊紝涓嶆彁浜ゃ�佷笉鍗犵敤搴撳瓨锛�
+ MesWmProductSalesSaveReqVO saveReqVO = new MesWmProductSalesSaveReqVO();
+ saveReqVO.setCode(code);
+ saveReqVO.setName(StrUtil.isNotBlank(head.getName()) ? head.getName().trim()
+ : clientName + "-" + salesDate.toLocalDate() + " 鍑哄簱");
+ saveReqVO.setClientId(clientId);
+ saveReqVO.setSalesDate(salesDate);
+ saveReqVO.setSalesOrderCode(StrUtil.trimToNull(head.getSalesOrderCode()));
+ saveReqVO.setContactName(StrUtil.trimToNull(head.getContactName()));
+ saveReqVO.setContactTelephone(StrUtil.trimToNull(head.getContactTelephone()));
+ saveReqVO.setContactAddress(StrUtil.trimToNull(head.getContactAddress()));
+ saveReqVO.setRemark(StrUtil.trimToNull(head.getRemark()));
+ Long salesId = createProductSales(saveReqVO);
+
+ // 5. 閫愯鍒涘缓鏄庣粏
+ for (ImportRow row : rows) {
+ createProductSalesLineByImport(salesId, row);
+ }
+ return code;
+ }
+
+ /**
+ * 渚濇嵁瀵煎叆琛屽垱寤轰竴鏉¢攢鍞嚭搴撳崟琛�
+ */
+ private void createProductSalesLineByImport(Long salesId, ImportRow row) {
+ MesWmProductSalesImportExcelVO data = row.data();
+ String rowPrefix = "绗� " + row.rowNum() + " 琛岋細";
+
+ // 1. 鐗╂枡缂栫爜锛堝繀濉紝涓斿繀椤诲瓨鍦ㄣ�佸惎鐢級
+ if (StrUtil.isBlank(data.getItemCode())) {
+ throw new IllegalArgumentException(rowPrefix + "鐗╂枡缂栫爜涓嶈兘涓虹┖");
+ }
+ String itemCode = data.getItemCode().trim();
+ MdmItemRespDTO item = mdmItemApi.getItemByCode(itemCode).getCheckedData();
+ if (item == null) {
+ throw new IllegalArgumentException(rowPrefix + "鐗╂枡缂栫爜[" + itemCode
+ + "]涓嶅瓨鍦紝璇峰厛鍒般�愬熀纭�鏁版嵁-鐗╂枡涓绘暟鎹�戜腑缁存姢璇ョ墿鏂�");
+ }
+ if (!CommonStatusEnum.ENABLE.getStatus().equals(item.getStatus())) {
+ throw new IllegalArgumentException(rowPrefix + "鐗╂枡缂栫爜[" + itemCode
+ + "-" + item.getName() + "]宸插仠鐢紝璇峰厛鍦ㄧ墿鏂欎富鏁版嵁涓惎鐢ㄥ悗鍐嶅鍏�");
+ }
+
+ // 2. 鍑哄簱鏁伴噺锛堝繀濉紝涓诲崟浣嶅彛寰勶級
+ if (data.getQuantity() == null) {
+ throw new IllegalArgumentException(rowPrefix + "鍑哄簱鏁伴噺涓嶈兘涓虹┖锛堢墿鏂橻" + itemCode + "]锛�");
+ }
+ if (data.getQuantity().compareTo(BigDecimal.ZERO) <= 0) {
+ throw new IllegalArgumentException(rowPrefix + "鍑哄簱鏁伴噺蹇呴』澶т簬 0锛屽綋鍓嶅~鍐欎负["
+ + data.getQuantity().toPlainString() + "]锛堢墿鏂橻" + itemCode + "]锛�");
+ }
+
+ // 3. 鎵规鍙凤紙鍙┖锛涘~浜嗗繀椤绘槸宸插瓨鍦ㄤ笖褰掑睘璇ョ墿鏂欑殑鎵规锛�
+ String batchCode = StrUtil.trimToNull(data.getBatchCode());
+ if (batchCode != null) {
+ MesWmBatchDO batch = batchService.getBatchByCode(batchCode);
+ if (batch == null) {
+ throw new IllegalArgumentException(rowPrefix + "鎵规鍙穂" + batchCode
+ + "]涓嶅瓨鍦紝璇峰厛鍦ㄣ�愭壒娆$鐞嗐�戜腑鍒涘缓锛屾垨娓呯┖璇ヨ鐨勬壒娆″彿");
+ }
+ if (batch.getItemId() != null && !ObjUtil.equal(batch.getItemId(), item.getId())) {
+ String batchItemText = resolveItemText(batch.getItemId());
+ throw new IllegalArgumentException(rowPrefix + "鎵规鍙穂" + batchCode + "]灞炰簬鐗╂枡["
+ + batchItemText + "]锛屼笌褰撳墠琛岀墿鏂橻" + itemCode + "-" + item.getName()
+ + "]涓嶄竴鑷达紝璇锋牳瀵规壒娆″彿鎴栫墿鏂欑紪鐮�");
+ }
+ }
+
+ // 4. 鍒涘缓琛岋紙鑽夌鐘舵�侊紱鐗╂枡瀛樺湪鎬�/鍚敤銆佹壒娆¤В鏋愮瓑鏍¢獙鐢辫鏈嶅姟缁熶竴瀹屾垚锛�
+ MesWmProductSalesLineSaveReqVO lineVO = new MesWmProductSalesLineSaveReqVO();
+ lineVO.setSalesId(salesId);
+ lineVO.setItemId(item.getId());
+ lineVO.setQuantity(data.getQuantity());
+ lineVO.setBatchCode(batchCode);
+ lineVO.setOqcCheckFlag(parseImportYesNo(data.getOqcCheckFlag()));
+ lineVO.setRemark(StrUtil.trimToNull(data.getRemark()));
+ productSalesLineService.createProductSalesLine(lineVO);
+ }
+
+ private boolean isEmptyImportRow(MesWmProductSalesImportExcelVO row) {
+ return StrUtil.isBlank(row.getCode()) && StrUtil.isBlank(row.getClientName())
+ && StrUtil.isBlank(row.getItemCode()) && row.getQuantity() == null
+ && StrUtil.isBlank(row.getName()) && StrUtil.isBlank(row.getSalesDate());
+ }
+
+ private LocalDateTime parseImportSalesDate(String text, String rowPrefix) {
+ if (StrUtil.isBlank(text)) {
+ return LocalDate.now().atStartOfDay();
+ }
+ String value = text.trim();
+ for (String pattern : new String[]{"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm"}) {
+ try {
+ return LocalDateTime.parse(value, DateTimeFormatter.ofPattern(pattern));
+ } catch (Exception ignored) {
+ // 缁х画灏濊瘯涓嬩竴涓牸寮�
+ }
+ }
+ for (String pattern : new String[]{"yyyy-MM-dd", "yyyy/M/d"}) {
+ try {
+ return LocalDate.parse(value, DateTimeFormatter.ofPattern(pattern)).atStartOfDay();
+ } catch (Exception ignored) {
+ // 缁х画灏濊瘯涓嬩竴涓牸寮�
+ }
+ }
+ throw new IllegalArgumentException(rowPrefix + "鍑哄簱鏃ユ湡[" + text
+ + "]鏍煎紡涓嶆纭紝璇蜂娇鐢� yyyy-MM-dd锛屽 2026-10-31");
+ }
+
+ private Boolean parseImportYesNo(String text) {
+ if (StrUtil.isBlank(text)) {
+ return false;
+ }
+ String value = text.trim();
+ return "鏄�".equals(value) || "1".equals(value) || "true".equalsIgnoreCase(value)
+ || "yes".equalsIgnoreCase(value) || "闇�瑕�".equals(value);
+ }
+
+ private String resolveItemText(Long itemId) {
+ try {
+ MdmItemRespDTO item = mdmItemApi.getItem(itemId).getCheckedData();
+ if (item != null) {
+ return item.getCode() + "-" + item.getName();
+ }
+ } catch (Exception ex) {
+ log.warn("[resolveItemText][鐗╂枡({}) 鍚嶇О瑙f瀽澶辫触]", itemId, ex);
+ }
+ return "ID=" + itemId;
+ }
+
+ private String resolveImportErrorMessage(Exception ex) {
+ if (ex instanceof ServiceException serviceException) {
+ return serviceException.getMessage();
+ }
+ return StrUtil.blankToDefault(ex.getMessage(), "瀵煎叆澶辫触");
+ }
+
+ /**
+ * 瀵煎叆琛岋紙淇濈暀 Excel 琛屽彿锛屼究浜庢彁绀衡�滅 N 琛屸�濓級
+ */
+ private record ImportRow(MesWmProductSalesImportExcelVO data, int rowNum) {
+ }
+
}
--
Gitblit v1.9.3