From f6ee1a3eee82cf1b5c6e59092f3f98aa87d0541b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=A5=8E=E5=85=B4?= <2220574228@qq.com> Date: Fri, 31 Jul 2026 15:47:20 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A2=84=E7=BA=A6=E5=8D=95=E8=80=81=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E6=95=B0=E6=8D=AE=E5=AF=BC=E5=85=A5;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapper/ReserveStockInOrderMapper.java | 27 ++ .../repository/util/ExcelParseUtil.java | 48 ++++ .../vo/LegacyReserveInDetailVO.java | 77 ++++++ .../repository/vo/LegacyReserveInOrderVO.java | 93 +++++++ ...ReserveStockInOrderApplicationService.java | 258 ++++++++++++++++++ .../mapper/ReserveStockOutOrderMapper.java | 12 + .../repository/util/ExcelParseUtil.java | 52 ++++ .../vo/LegacyReserveOutDetailVO.java | 91 ++++++ .../vo/LegacyReserveOutOrderVO.java | 82 ++++++ ...eserveStockOutOrderApplicationService.java | 242 ++++++++++++++++ .../ReserveStockOutOrderApi.java | 57 ++++ .../ReserveStockInOrderApi.java | 66 +++++ .../ReserveStockInOrderMapper.xml | 43 +++ .../ReserveStockOutOrderMapper.xml | 20 ++ 14 files changed, 1168 insertions(+) create mode 100644 mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/repository/vo/LegacyReserveInDetailVO.java create mode 100644 mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/repository/vo/LegacyReserveInOrderVO.java create mode 100644 mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/repository/vo/LegacyReserveOutDetailVO.java create mode 100644 mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/repository/vo/LegacyReserveOutOrderVO.java diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/repository/mapper/ReserveStockInOrderMapper.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/repository/mapper/ReserveStockInOrderMapper.java index 412930a66..9bbc47a3d 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/repository/mapper/ReserveStockInOrderMapper.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/repository/mapper/ReserveStockInOrderMapper.java @@ -2,6 +2,8 @@ package com.mhd.oms.domain.reserveStockInOrder.repository.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; //import com.mhd.oms.domain.reservationInMaterialDetail.repository.po.ReserveInMaterialDetailPO; +import com.mhd.common.core.domain.po.UserPo; +import com.mhd.common.core.domain.po.WarehousePO; import com.mhd.oms.domain.reservationStockInOrder.repository.po.MaterialBaseInfoPO; import com.mhd.oms.domain.reservationStockInOrder.repository.po.MaterialGoodsRulePO; import com.mhd.oms.domain.reservationStockInOrder.repository.po.MaterialWarehouseControlPO; @@ -50,4 +52,29 @@ public interface ReserveStockInOrderMapper extends BaseMapper generateReserveOrderNumber1(@Param("prefix") String prefix); + + /** + * 根据货主名称查询用户(老系统数据导入用) + */ + List getShipperByName(@Param("name") String name); + + /** + * 根据仓库名称查询仓库(老系统数据导入用) + */ + List getWarehouseByName(@Param("name") String name); + + /** + * 根据仓库类型查询仓库(老系统数据导入用,1=干仓 2=冬仓) + */ + List getWarehouseByType(@Param("warehouseType") String warehouseType); + + /** + * 根据货主用户ID查询NC客户编码 + */ + String getCustomerNcCodeByUserId(@Param("id") Long id); + + /** + * 根据入库单号精确统计(导入防重用) + */ + int countByInOrderNumber(@Param("inOrderNumber") String inOrderNumber); } \ No newline at end of file diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/repository/util/ExcelParseUtil.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/repository/util/ExcelParseUtil.java index 3ae7db3e8..3d534da4f 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/repository/util/ExcelParseUtil.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/repository/util/ExcelParseUtil.java @@ -1,12 +1,16 @@ package com.mhd.oms.domain.reserveStockInOrder.repository.util; import com.alibaba.excel.EasyExcel; +import com.mhd.common.core.utils.StringUtils; import com.mhd.oms.domain.reserveStockInOrder.repository.listener.ReservedInventoryListener; +import com.mhd.oms.domain.reserveStockInOrder.repository.vo.LegacyReserveInDetailVO; +import com.mhd.oms.domain.reserveStockInOrder.repository.vo.LegacyReserveInOrderVO; import com.mhd.oms.domain.reserveStockInOrder.repository.vo.ReservedInventory; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.io.InputStream; import java.util.List; +import java.util.stream.Collectors; /** * Excel解析工具类 @@ -56,6 +60,50 @@ public class ExcelParseUtil { return dataList; } + /** + * 解析老系统《入库预约单列表》Excel(表头第1行,数据从第2行开始) + * @param file 上传的Excel文件 + * @return 老系统入库预约单主表行列表 + * @throws IOException 文件处理异常 + */ + public static List parseLegacyOrderExcel(MultipartFile file) throws IOException { + validateFile(file); + List dataList; + try (InputStream inputStream = file.getInputStream()) { + dataList = EasyExcel.read(inputStream) + .head(LegacyReserveInOrderVO.class) + .sheet(0) + .headRowNumber(1) + .doReadSync(); + } + // 过滤入库单号为空的无效行 + return dataList.stream() + .filter(row -> row != null && StringUtils.isNotBlank(row.getInOrderNumber())) + .collect(Collectors.toList()); + } + + /** + * 解析老系统《入库预约单明细列表》Excel(表头第1行,数据从第2行开始) + * @param file 上传的Excel文件 + * @return 老系统入库预约单明细行列表 + * @throws IOException 文件处理异常 + */ + public static List parseLegacyDetailExcel(MultipartFile file) throws IOException { + validateFile(file); + List dataList; + try (InputStream inputStream = file.getInputStream()) { + dataList = EasyExcel.read(inputStream) + .head(LegacyReserveInDetailVO.class) + .sheet(0) + .headRowNumber(1) + .doReadSync(); + } + // 过滤入库单号为空的无效行 + return dataList.stream() + .filter(row -> row != null && StringUtils.isNotBlank(row.getInOrderNumber())) + .collect(Collectors.toList()); + } + /** * 验证Excel文件合法性 */ diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/repository/vo/LegacyReserveInDetailVO.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/repository/vo/LegacyReserveInDetailVO.java new file mode 100644 index 000000000..42ec1de99 --- /dev/null +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/repository/vo/LegacyReserveInDetailVO.java @@ -0,0 +1,77 @@ +package com.mhd.oms.domain.reserveStockInOrder.repository.vo; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.mhd.oms.domain.reserveStockInOrder.repository.util.StringToBigDecimalConverter; +import com.mhd.oms.domain.reserveStockInOrder.repository.util.StringToDateConverter; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 老系统入库预约单明细行 + * 与老系统导出Excel《入库预约单明细列表》字段一一映射(表头第1行,数据从第2行开始) + */ +@Data +public class LegacyReserveInDetailVO { + + /** + * 入库单号(必填,与主表关联键) + */ + @ExcelProperty(index = 0, value = "入库单号") + private String inOrderNumber; + + /** + * 计划数量(必填) + */ + @ExcelProperty(index = 1, value = "计划数量", converter = StringToBigDecimalConverter.class) + private BigDecimal quantity; + + /** + * 物料名称(必填,用于匹配新系统物料基础信息) + */ + @ExcelProperty(index = 2, value = "物料名称") + private String materialName; + + /** + * 单位名称 + */ + @ExcelProperty(index = 3, value = "单位名称") + private String unitName; + + /** + * 创建时间(老系统) + */ + @ExcelProperty(index = 4, value = "创建时间", converter = StringToDateConverter.class) + private Date createTime; + + /** + * 修改时间(老系统) + */ + @ExcelProperty(index = 5, value = "修改时间", converter = StringToDateConverter.class) + private Date updateTime; + + /** + * 创建人名称(老系统) + */ + @ExcelProperty(index = 6, value = "创建人名称") + private String createByName; + + /** + * 修改人名称(老系统) + */ + @ExcelProperty(index = 7, value = "修改人名称") + private String updateByName; + + /** + * 总净重(KG) + */ + @ExcelProperty(index = 8, value = "总净重(KG)", converter = StringToBigDecimalConverter.class) + private BigDecimal totalNetWeight; + + /** + * 备注 + */ + @ExcelProperty(index = 9, value = "备注") + private String remark; +} diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/repository/vo/LegacyReserveInOrderVO.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/repository/vo/LegacyReserveInOrderVO.java new file mode 100644 index 000000000..4bbbca38d --- /dev/null +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/repository/vo/LegacyReserveInOrderVO.java @@ -0,0 +1,93 @@ +package com.mhd.oms.domain.reserveStockInOrder.repository.vo; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.mhd.oms.domain.reserveStockInOrder.repository.util.StringToDateConverter; +import lombok.Data; + +import java.util.Date; + +/** + * 老系统入库预约单主表行 + * 与老系统导出Excel《入库预约单列表》字段一一映射(表头第1行,数据从第2行开始) + */ +@Data +public class LegacyReserveInOrderVO { + + /** + * 入库单号(必填,与明细表关联键) + */ + @ExcelProperty(index = 0, value = "入库单号") + private String inOrderNumber; + + /** + * 入仓时间 + */ + @ExcelProperty(index = 1, value = "入仓时间", converter = StringToDateConverter.class) + private Date warehouseDate; + + /** + * 制单人 + */ + @ExcelProperty(index = 2, value = "制单人") + private String documentCreator; + + /** + * 制单时间 + */ + @ExcelProperty(index = 3, value = "制单时间", converter = StringToDateConverter.class) + private Date documentDate; + + /** + * 货主名称(必填,用于匹配新系统用户) + */ + @ExcelProperty(index = 4, value = "货主名称") + private String shipperName; + + /** + * 备注 + */ + @ExcelProperty(index = 5, value = "备注") + private String remark; + + /** + * 创建时间(老系统) + */ + @ExcelProperty(index = 6, value = "创建时间", converter = StringToDateConverter.class) + private Date createTime; + + /** + * 修改时间(老系统) + */ + @ExcelProperty(index = 7, value = "修改时间", converter = StringToDateConverter.class) + private Date updateTime; + + /** + * 创建人名称(老系统) + */ + @ExcelProperty(index = 8, value = "创建人名称") + private String createByName; + + /** + * 修改人名称(老系统) + */ + @ExcelProperty(index = 9, value = "修改人名称") + private String updateByName; + + /** + * 老系统仓库id(跨系统id不可用,仅做占位) + */ + @ExcelProperty(index = 10, value = "仓库id") + private String oldWarehouseId; + + /** + * 仓库名称(用于匹配新系统仓库) + */ + @ExcelProperty(index = 11, value = "仓库名称") + private String warehouseName; + + /** + * 仓库类型(1=干仓 2=冬仓,用于仓库名称匹配兜底) + */ + @ExcelProperty(index = 12, value = "仓库类型") + private String warehouseType; +} diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/service/ReserveStockInOrderApplicationService.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/service/ReserveStockInOrderApplicationService.java index a33be77fa..e0c259eff 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/service/ReserveStockInOrderApplicationService.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockInOrder/service/ReserveStockInOrderApplicationService.java @@ -5,6 +5,7 @@ import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSONObject; import com.mhd.common.core.domain.po.UserPo; +import com.mhd.common.core.domain.po.WarehousePO; import com.mhd.common.core.enums.DictCode; import com.mhd.common.core.exception.ServiceException; import com.mhd.common.core.utils.OrderSequence; @@ -16,14 +17,19 @@ import com.mhd.oms.domain.reservationMaterialInventory.repository.todo.MaterialB import com.mhd.oms.domain.reservationStockInOrder.repository.mapper.ReservationStockInOrderMapper; import com.mhd.oms.domain.reservationStockInOrder.repository.po.MaterialBaseInfoPO; import com.mhd.oms.domain.reserveInMaterialDetail.entity.ReserveInMaterialDetail; +import com.mhd.oms.domain.reserveInMaterialDetail.repository.facade.IReserveInMaterialDetailService; import com.mhd.oms.domain.reserveInMaterialDetail.repository.po.ReserveInMaterialDetailPO; import com.mhd.oms.domain.reserveInMaterialDetail.repository.todo.ReserveInMaterialDetailDO; import com.mhd.oms.domain.reserveStockInOrder.entity.ReserveStockInOrder; +import com.mhd.oms.domain.reserveStockInOrder.repository.facade.IReserveStockInOrderService; +import com.mhd.oms.domain.reserveStockInOrder.repository.listener.ValidationException; import com.mhd.oms.domain.reserveStockInOrder.repository.mapper.ReserveStockInOrderMapper; import com.mhd.oms.domain.reserveStockInOrder.repository.po.ReserveStockInOrderPO; import com.mhd.oms.domain.reserveStockInOrder.repository.todo.ReserveStockInOrderDO; import com.mhd.oms.domain.reserveStockInOrder.repository.todo.ReserveStockInOrderDTO; import com.mhd.oms.domain.reserveStockInOrder.repository.vo.InAndOutListVo; +import com.mhd.oms.domain.reserveStockInOrder.repository.vo.LegacyReserveInDetailVO; +import com.mhd.oms.domain.reserveStockInOrder.repository.vo.LegacyReserveInOrderVO; import com.mhd.oms.domain.reserveStockInOrder.repository.vo.ReservedInventory; import com.mhd.system.api.SystemServiceFeign; import com.mhd.system.api.UserServiceFeign; @@ -38,6 +44,7 @@ import org.apache.poi.ss.usermodel.DateUtil; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.text.ParseException; @@ -70,6 +77,10 @@ public class ReserveStockInOrderApplicationService { private WmsServiceFeign wmsServiceFeign; @Autowired private ReservationStockInOrderMapper reservationStockInOrderMapper; + @Autowired + private IReserveStockInOrderService stockInOrderService; + @Autowired + private IReserveInMaterialDetailService materialDetailService; // @Autowired // private IMaterialBaseInfoService materialBaseInfoService; @@ -2039,6 +2050,253 @@ public class ReserveStockInOrderApplicationService { insert(reserveStockInOrder); } + /** + * 老系统入库预约单数据导入(主表+明细两个Excel,多单批量导入) + * 老系统的id类字段不可用,货主、仓库、物料均按名称匹配新系统基础数据 + */ + @Transactional(rollbackFor = Exception.class) + public Map importLegacyData(List orderList, List detailList) { + if (orderList == null || orderList.isEmpty()) { + throw new ValidationException("入库预约单列表文件中没有有效数据"); + } + if (detailList == null || detailList.isEmpty()) { + throw new ValidationException("入库预约单明细列表文件中没有有效数据"); + } + + // 明细按入库单号分组 + Map> detailGroupMap = new LinkedHashMap<>(); + for (LegacyReserveInDetailVO detailRow : detailList) { + detailGroupMap.computeIfAbsent(detailRow.getInOrderNumber().trim(), k -> new ArrayList<>()).add(detailRow); + } + + List errorList = new ArrayList<>(); + // 名称匹配结果缓存,避免重复查库 + Map shipperCache = new HashMap<>(); + Map warehouseCache = new HashMap<>(); + // 仓库类型 -> 仓库 缓存(用于名称匹配失败时的兜底) + Map warehouseTypeCache = new HashMap<>(); + Map materialCache = new HashMap<>(); + + Set orderNumberSet = new HashSet<>(); + List stockInOrderList = new ArrayList<>(); + int lotIndex = 1; + for (LegacyReserveInOrderVO orderRow : orderList) { + String orderNumber = orderRow.getInOrderNumber().trim(); + if (!orderNumberSet.add(orderNumber)) { + errorList.add("入库单号【" + orderNumber + "】在主表文件中重复"); + continue; + } + if (reserveStockInOrderMapper.countByInOrderNumber(orderNumber) > 0) { + errorList.add("入库单号【" + orderNumber + "】在系统中已存在,请勿重复导入"); + continue; + } + // 按货主名称匹配用户(查不到则用Excel中的名称兜底,不阻断导入) + UserPo shipper = null; + String shipperName = StringUtils.trimToEmpty(orderRow.getShipperName()); + if (StringUtils.isBlank(shipperName)) { + errorList.add("入库单号【" + orderNumber + "】货主名称为空"); + } else { + shipper = shipperCache.get(shipperName); + if (shipper == null) { + List userPoList = reserveStockInOrderMapper.getShipperByName(shipperName); + if (userPoList == null || userPoList.isEmpty()) { + errorList.add("入库单号【" + orderNumber + "】货主【" + shipperName + "】未找到对应用户,将仅以名称导入"); + } else { + shipper = userPoList.get(0); + shipperCache.put(shipperName, shipper); + } + } + } + + // 按仓库名称匹配仓库(查不到则按仓库类型兜底,再查不到则用名称兜底,不阻断导入) + WarehousePO warehouse = null; + String warehouseName = StringUtils.trimToEmpty(orderRow.getWarehouseName()); + if (StringUtils.isNotBlank(warehouseName)) { + warehouse = warehouseCache.get(warehouseName); + if (warehouse == null) { + List warehousePOList = reserveStockInOrderMapper.getWarehouseByName(warehouseName); + if (warehousePOList == null || warehousePOList.isEmpty()) { + // 名称匹配失败,尝试按仓库类型兜底匹配 + String warehouseType = StringUtils.trimToEmpty(orderRow.getWarehouseType()); + if (StringUtils.isNotBlank(warehouseType)) { + if (warehouseTypeCache.containsKey(warehouseType)) { + warehouse = warehouseTypeCache.get(warehouseType); + } else { + List typeList = reserveStockInOrderMapper.getWarehouseByType(warehouseType); + if (typeList != null && !typeList.isEmpty()) { + warehouse = typeList.get(0); + warehouseTypeCache.put(warehouseType, warehouse); + } else { + warehouseTypeCache.put(warehouseType, null); + } + } + } + if (warehouse == null) { + errorList.add("入库单号【" + orderNumber + "】仓库【" + warehouseName + "】未找到对应仓库,将仅以名称导入"); + } + } else { + warehouse = warehousePOList.get(0); + warehouseCache.put(warehouseName, warehouse); + } + } + } + + // 明细匹配 + List detailRows = detailGroupMap.get(orderNumber); + if (detailRows == null || detailRows.isEmpty()) { + errorList.add("入库单号【" + orderNumber + "】在明细文件中没有明细数据"); + continue; + } + List materialDetailList = new ArrayList<>(); + for (LegacyReserveInDetailVO detailRow : detailRows) { + String materialName = StringUtils.trimToEmpty(detailRow.getMaterialName()); + if (StringUtils.isBlank(materialName)) { + errorList.add("入库单号【" + orderNumber + "】存在物料名称为空的明细行"); + continue; + } + // 按物料名称匹配物料基础信息(查不到则用Excel中的名称兜底,不阻断导入) + MaterialBaseInfoPO materialBaseInfoPO = materialCache.get(materialName); + if (materialBaseInfoPO == null) { + List materialBaseInfoPOS = reserveStockInOrderMapper.getByName(materialName); + if (materialBaseInfoPOS == null || materialBaseInfoPOS.isEmpty()) { + errorList.add("入库单号【" + orderNumber + "】物料【" + materialName + "】未找到对应物料,将仅以名称导入"); + } else { + materialBaseInfoPO = materialBaseInfoPOS.get(0); + materialCache.put(materialName, materialBaseInfoPO); + } + } + ReserveInMaterialDetailDO materialDetail = new ReserveInMaterialDetailDO(); + if (materialBaseInfoPO != null) { + materialDetail.setMaterialBaseInfoId(materialBaseInfoPO.getMaterialBaseInfoId()); + materialDetail.setMaterialCode(materialBaseInfoPO.getMaterialCode()); + materialDetail.setUnitCode(materialBaseInfoPO.getUnitCode()); + materialDetail.setUnitName(StringUtils.isNotBlank(materialBaseInfoPO.getUnitName()) ? materialBaseInfoPO.getUnitName() : detailRow.getUnitName()); + } else { + // 未匹配到物料基础信息,仅以Excel中的名称作为物料名称 + materialDetail.setUnitName(detailRow.getUnitName()); + } + materialDetail.setMaterialName(materialName); + materialDetail.setReserveMaterialName(materialName); + materialDetail.setQuantity(detailRow.getQuantity()); + materialDetail.setReserveQuantity(detailRow.getQuantity()); + materialDetail.setTotalNetWeight(detailRow.getTotalNetWeight()); + materialDetail.setTotalGrossWeight(detailRow.getTotalNetWeight()); + materialDetail.setRemark(detailRow.getRemark()); + materialDetail.setContractFee(BigDecimal.ZERO); + materialDetail.setLotNo(generateDateSerialNumber() + "-" + lotIndex); + lotIndex++; +// if (shipper != null) { + materialDetail.setOrganizationId(2841L); + materialDetail.setOrganizationName("仓码"); + materialDetail.setTopOrganizationId(2827L); +// } + // 创建人/修改人名称取老系统Excel中的值,不使用当前登录人 + materialDetail.setCreateTime(detailRow.getCreateTime() != null ? detailRow.getCreateTime() : new Date()); + materialDetail.setCreateByName(detailRow.getCreateByName()); + materialDetail.setUpdateTime(detailRow.getUpdateTime()); + materialDetail.setUpdateByName(detailRow.getUpdateByName()); + materialDetailList.add(materialDetail); + } + + // 组装入库预约单 + ReserveStockInOrderDO stockInOrderDO = new ReserveStockInOrderDO(); + stockInOrderDO.setInOrderNumber(orderNumber); + stockInOrderDO.setBusinessOrderNo(generateDateTimeWithRandom()); + // 老系统历史数据,状态直接置为最终状态:已入库、审核通过、已完成 + stockInOrderDO.setStatus(5); + stockInOrderDO.setAuditStatus(3); + stockInOrderDO.setIssueStatus(3); + stockInOrderDO.setOrderStatus("已确认"); + stockInOrderDO.setStorageTime(orderRow.getWarehouseDate()); + stockInOrderDO.setNeedTransport(0); + stockInOrderDO.setNeedCustomsDeclaration(0); + stockInOrderDO.setWarehouseDate(orderRow.getWarehouseDate()); + stockInOrderDO.setExpectTime(orderRow.getWarehouseDate() != null ? orderRow.getWarehouseDate() : new Date()); + stockInOrderDO.setDocumentDate(orderRow.getDocumentDate() != null ? orderRow.getDocumentDate() : new Date()); + stockInOrderDO.setDocumentCreator(orderRow.getDocumentCreator()); + stockInOrderDO.setRemark(orderRow.getRemark()); + if (shipper != null) { + stockInOrderDO.setShipperId(shipper.getUserId()); + stockInOrderDO.setShipperName(shipper.getUserName()); + stockInOrderDO.setCustomerId(shipper.getUserId()); + stockInOrderDO.setCustomerName(shipper.getUserName()); + stockInOrderDO.setSalesmanId(String.valueOf(shipper.getUserId())); + stockInOrderDO.setSalesmanName(shipper.getUserName()); + stockInOrderDO.setCustomerCode(reserveStockInOrderMapper.getCustomerNcCodeByUserId(shipper.getUserId())); +// stockInOrderDO.setOrganizationId(shipper.getOrganizationId()); +// stockInOrderDO.setOrganizationName(shipper.getOrganizationName()); +// stockInOrderDO.setTopOrganizationId(shipper.getTopOrganizationId()); + } else { + // 货主未匹配到,仅以Excel中的名称作为名称字段,id/组织留空 + stockInOrderDO.setShipperName(shipperName); + stockInOrderDO.setCustomerName(shipperName); + stockInOrderDO.setSalesmanName(shipperName); + } + stockInOrderDO.setOrganizationId(2841L); + stockInOrderDO.setOrganizationName("仓码"); + stockInOrderDO.setTopOrganizationId(2827L); + if (warehouse != null) { + stockInOrderDO.setWarehouseId(warehouse.getWarehouseId()); + stockInOrderDO.setWarehouseCode(warehouse.getWarehouseCode()); + stockInOrderDO.setWarehouseName(warehouse.getWarehouseName()); + } else if (StringUtils.isNotBlank(warehouseName)) { + // 仓库未匹配到,仅以Excel中的名称作为仓库名称,id/编码留空 + stockInOrderDO.setWarehouseName(warehouseName); + } + // 创建人/修改人名称取老系统Excel中的值,不使用当前登录人 + stockInOrderDO.setCreateTime(orderRow.getCreateTime() != null ? orderRow.getCreateTime() : new Date()); + stockInOrderDO.setCreateByName(orderRow.getCreateByName()); + stockInOrderDO.setUpdateTime(orderRow.getUpdateTime()); + stockInOrderDO.setUpdateByName(orderRow.getUpdateByName()); + stockInOrderDO.setMaterialDetailList(materialDetailList); + stockInOrderList.add(stockInOrderDO); + } + + // 明细文件中存在但主表文件中不存在的单号 + for (String detailOrderNumber : detailGroupMap.keySet()) { + if (!orderNumberSet.contains(detailOrderNumber)) { + errorList.add("明细文件中入库单号【" + detailOrderNumber + "】在主表文件中不存在"); + } + } + + // 逐单入库(即使有未匹配到基础数据的警告也不阻断,错误信息随结果返回) + // 直接走 MyBatis-Plus 入库,绕过领域服务/应用服务层的参数补全逻辑,避免货主/物料未匹配等场景报错 + int detailCount = 0; + for (ReserveStockInOrderDO stockInOrderDO : stockInOrderList) { + // 主表:DO -> 实体 -> save + ReserveStockInOrder stockInOrder = new ReserveStockInOrder(); + BeanUtils.copyProperties(stockInOrderDO, stockInOrder); + stockInOrder.setMaterialQuantity(stockInOrderDO.getMaterialDetailList().size()); + BigDecimal totalQuantity = stockInOrderDO.getMaterialDetailList().stream() + .map(ReserveInMaterialDetailDO::getQuantity).filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + stockInOrder.setQuantity(totalQuantity); + stockInOrder.setReserveQuantity(totalQuantity); + stockInOrderService.save(stockInOrder); + + // 明细:DO -> 实体 -> saveBatch + List detailEntities = new ArrayList<>(); + for (ReserveInMaterialDetailDO detailDO : stockInOrderDO.getMaterialDetailList()) { + ReserveInMaterialDetail detail = new ReserveInMaterialDetail(); + BeanUtils.copyProperties(detailDO, detail); + detail.setInOrderNumber(stockInOrderDO.getInOrderNumber()); + detail.setLevel(1); + detailEntities.add(detail); + } + if (!detailEntities.isEmpty()) { + materialDetailService.saveBatch(detailEntities); + } + detailCount += detailEntities.size(); + } + Map result = new HashMap<>(); + result.put("orderCount", stockInOrderList.size()); + result.put("detailCount", detailCount); + if (!errorList.isEmpty()) { + result.put("warnings", errorList); + } + return result; + } + public static String generateDateTimeWithRandom() { // 1. 获取当前时间,格式化:yyMMddHHmmss LocalDateTime now = LocalDateTime.now(); diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/repository/mapper/ReserveStockOutOrderMapper.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/repository/mapper/ReserveStockOutOrderMapper.java index ed8bc8f50..128716b68 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/repository/mapper/ReserveStockOutOrderMapper.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/repository/mapper/ReserveStockOutOrderMapper.java @@ -2,6 +2,7 @@ package com.mhd.oms.domain.reserveStockOutOrder.repository.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; //import com.mhd.oms.domain.reservationOutMaterialDetail.repository.po.ReserveOutMaterialDetailPO; +import com.mhd.common.core.domain.po.WarehousePO; import com.mhd.oms.domain.reserveOutMaterialDetail.repository.po.ReserveOutMaterialDetailPO; import com.mhd.oms.domain.reserveStockOutOrder.entity.ReserveStockOutOrder; import com.mhd.oms.domain.reserveStockOutOrder.repository.po.ReserveStockOutOrderPO; @@ -38,4 +39,15 @@ public interface ReserveStockOutOrderMapper extends BaseMapper generateReserveOrderNumber1(@Param("prefix") String prefix); + + /** + * 老系统数据导入:根据出库单号精确统计(防重) + */ + int countByOutOrderNumber(@Param("outOrderNumber") String outOrderNumber); + + /** + * 老系统数据导入:根据仓库类型查询仓库 + * @param warehouseType 仓库类型编码(如 "1"/"2") + */ + List getWarehouseByType(@Param("warehouseType") String warehouseType); } \ No newline at end of file diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/repository/util/ExcelParseUtil.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/repository/util/ExcelParseUtil.java index 693b30aba..7deeddc83 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/repository/util/ExcelParseUtil.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/repository/util/ExcelParseUtil.java @@ -1,7 +1,10 @@ package com.mhd.oms.domain.reserveStockOutOrder.repository.util; import com.alibaba.excel.EasyExcel; +import com.mhd.common.core.utils.StringUtils; import com.mhd.oms.domain.reserveStockOutOrder.repository.listener.ReservedInventoryOutListener; +import com.mhd.oms.domain.reserveStockOutOrder.repository.vo.LegacyReserveOutDetailVO; +import com.mhd.oms.domain.reserveStockOutOrder.repository.vo.LegacyReserveOutOrderVO; import com.mhd.oms.domain.reserveStockOutOrder.repository.vo.ReservedInventoryOut; import org.springframework.web.multipart.MultipartFile; @@ -55,6 +58,55 @@ public class ExcelParseUtil { return dataList; } + /** + * 解析老系统《出库预约单列表》Excel(表头第1行,数据从第2行开始) + */ + public static List parseLegacyOrderExcel(MultipartFile file) throws IOException { + validateFile(file); + List dataList; + try (InputStream inputStream = file.getInputStream()) { + dataList = EasyExcel.read(inputStream) + .head(LegacyReserveOutOrderVO.class) + .sheet(0) + .headRowNumber(1) + .doReadSync(); + } + // 过滤出库单号为空的无效行 + return dataList.stream() + .filter(row -> row != null && StringUtils.isNotBlank(row.getOutOrderNumber())) + .collect(java.util.stream.Collectors.toList()); + } + + /** + * 解析老系统《出库预约单明细列表》Excel(表头第1行,数据从第2行开始) + * 物料名称取物料名称1和物料名称2中有值的那一个 + */ + public static List parseLegacyDetailExcel(MultipartFile file) throws IOException { + validateFile(file); + List dataList; + try (InputStream inputStream = file.getInputStream()) { + dataList = EasyExcel.read(inputStream) + .head(LegacyReserveOutDetailVO.class) + .sheet(0) + .headRowNumber(1) + .doReadSync(); + } + // 过滤出库单号为空的无效行,同时把 materialName1/materialName2 中有值的赋给 materialName + return dataList.stream() + .filter(row -> row != null && StringUtils.isNotBlank(row.getOutOrderNumber())) + .map(row -> { + if (StringUtils.isBlank(row.getMaterialName())) { + if (StringUtils.isNotBlank(row.getMaterialName1())) { + row.setMaterialName(row.getMaterialName1()); + } else if (StringUtils.isNotBlank(row.getMaterialName2())) { + row.setMaterialName(row.getMaterialName2()); + } + } + return row; + }) + .collect(java.util.stream.Collectors.toList()); + } + /** * 验证Excel文件合法性 */ diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/repository/vo/LegacyReserveOutDetailVO.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/repository/vo/LegacyReserveOutDetailVO.java new file mode 100644 index 000000000..84dc95dff --- /dev/null +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/repository/vo/LegacyReserveOutDetailVO.java @@ -0,0 +1,91 @@ +package com.mhd.oms.domain.reserveStockOutOrder.repository.vo; + +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.excel.annotation.ExcelProperty; +import com.mhd.oms.domain.reserveStockInOrder.repository.util.StringToBigDecimalConverter; +import com.mhd.oms.domain.reserveStockInOrder.repository.util.StringToDateConverter; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 老系统出库预约单明细行 + * 与老系统导出Excel《出库预约单明细列表》字段一一映射(表头第1行,数据从第2行开始) + * 物料名称取物料名称1和物料名称2中有值的那一个 + */ +@Data +public class LegacyReserveOutDetailVO { + + /** + * 老系统明细id(跨系统id不可用,仅做占位) + */ + @ExcelProperty(index = 0, value = "id") + private String oldDetailId; + + /** + * 出库单号(必填,关联主表) + */ + @ExcelProperty(index = 1, value = "出库单号") + private String outOrderNumber; + + /** + * 出库数量 + */ + @ExcelProperty(index = 2, value = "出库数量", converter = StringToBigDecimalConverter.class) + private BigDecimal quantity; + + /** + * 入库单号(老系统关联的入库单号,保留到备注或来源单号字段) + */ + @ExcelProperty(index = 3, value = "入库单号") + private String inOrderNumber; + + /** + * 物料名称1 + */ + @ExcelProperty(index = 4, value = "物料名称1") + private String materialName1; + + /** + * 物料名称2 + */ + @ExcelProperty(index = 5, value = "物料名称2") + private String materialName2; + + /** + * 重量(KG) + */ + @ExcelProperty(index = 6, value = "重量", converter = StringToBigDecimalConverter.class) + private BigDecimal weight; + + /** + * 创建时间(老系统) + */ + @ExcelProperty(index = 7, value = "创建时间", converter = StringToDateConverter.class) + private Date createTime; + + /** + * 修改时间(老系统) + */ + @ExcelProperty(index = 8, value = "修改时间", converter = StringToDateConverter.class) + private Date updateTime; + + /** + * 创建人名称(老系统) + */ + @ExcelProperty(index = 9, value = "创建人名称") + private String createByName; + + /** + * 修改人名称(老系统) + */ + @ExcelProperty(index = 10, value = "修改人名称") + private String updateByName; + + /** + * 取物料名称1和物料名称2中有值的那一个(不映射Excel列,运行时计算) + */ + @ExcelIgnore + private String materialName; +} diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/repository/vo/LegacyReserveOutOrderVO.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/repository/vo/LegacyReserveOutOrderVO.java new file mode 100644 index 000000000..59adf9bca --- /dev/null +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/repository/vo/LegacyReserveOutOrderVO.java @@ -0,0 +1,82 @@ +package com.mhd.oms.domain.reserveStockOutOrder.repository.vo; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.mhd.oms.domain.reserveStockInOrder.repository.util.StringToDateConverter; +import lombok.Data; + +import java.util.Date; + +/** + * 老系统出库预约单主表行 + * 与老系统导出Excel《出库预约单列表》字段一一映射(表头第1行,数据从第2行开始) + */ +@Data +public class LegacyReserveOutOrderVO { + + /** + * 出库单号(必填,与明细表关联键) + */ + @ExcelProperty(index = 0, value = "出库单号") + private String outOrderNumber; + + /** + * 出库时间 + */ + @ExcelProperty(index = 1, value = "出库时间", converter = StringToDateConverter.class) + private Date outboundDate; + + /** + * 制单人名称 + */ + @ExcelProperty(index = 2, value = "制单人名称") + private String documentCreator; + + /** + * 制单时间 + */ + @ExcelProperty(index = 3, value = "制单时间", converter = StringToDateConverter.class) + private Date documentDate; + + /** + * 货主名称(必填,用于匹配新系统用户) + */ + @ExcelProperty(index = 4, value = "货主名称") + private String shipperName; + + /** + * 备注 + */ + @ExcelProperty(index = 5, value = "备注") + private String remark; + + /** + * 创建时间(老系统) + */ + @ExcelProperty(index = 6, value = "创建时间", converter = StringToDateConverter.class) + private Date createTime; + + /** + * 修改时间(老系统) + */ + @ExcelProperty(index = 7, value = "修改时间", converter = StringToDateConverter.class) + private Date updateTime; + + /** + * 创建人名称(老系统) + */ + @ExcelProperty(index = 8, value = "创建人名称") + private String createByName; + + /** + * 修改人名称(老系统) + */ + @ExcelProperty(index = 9, value = "修改人名称") + private String updateByName; + + /** + * 仓库类型:1-干仓 2-冬仓 + * 用于按类型匹配新系统仓库(老系统没有仓库名称,只有仓库类型) + */ + @ExcelProperty(index = 10, value = "仓库类型") + private String warehouseType; +} diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/service/ReserveStockOutOrderApplicationService.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/service/ReserveStockOutOrderApplicationService.java index 13f113cda..3d69b9552 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/service/ReserveStockOutOrderApplicationService.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reserveStockOutOrder/service/ReserveStockOutOrderApplicationService.java @@ -5,7 +5,9 @@ import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.mhd.common.core.constant.SnowFlakeConstants; import com.mhd.common.core.domain.po.UserPo; +import com.mhd.common.core.domain.po.WarehousePO; import com.mhd.common.core.enums.DictCode; import com.mhd.common.core.exception.ServiceException; import com.mhd.common.core.utils.StringUtils; @@ -23,14 +25,20 @@ import com.mhd.oms.domain.reserveInMaterialDetail.repository.todo.ReserveInMater import com.mhd.oms.domain.reserveOutMaterialDetail.repository.facade.IReserveOutMaterialDetailService; import com.mhd.oms.domain.reserveOutMaterialDetail.repository.po.ReserveOutMaterialDetailPO; import com.mhd.oms.domain.reserveOutMaterialDetail.repository.todo.ReserveOutMaterialDetailDO; +import com.mhd.oms.domain.reserveStockInOrder.repository.listener.ValidationException; import com.mhd.oms.domain.reserveStockInOrder.repository.mapper.ReserveStockInOrderMapper; import com.mhd.oms.domain.reserveStockInOrder.repository.todo.ReserveStockInOrderDO; import com.mhd.oms.domain.reserveStockInOrder.repository.vo.ReservedInventory; +import com.mhd.oms.domain.reserveStockOutOrder.entity.ReserveStockOutOrder; import com.mhd.oms.domain.reserveStockOutOrder.repository.facade.IReserveStockOutOrderService; +import com.mhd.oms.domain.reserveStockOutOrder.repository.mapper.ReserveStockOutOrderMapper; import com.mhd.oms.domain.reserveStockOutOrder.repository.po.ReserveStockOutOrderPO; import com.mhd.oms.domain.reserveStockOutOrder.repository.todo.ReserveStockOutOrderDO; import com.mhd.oms.domain.reserveStockOutOrder.repository.todo.ReserveStockOutOrderDTO; +import com.mhd.oms.domain.reserveStockOutOrder.repository.vo.LegacyReserveOutDetailVO; +import com.mhd.oms.domain.reserveStockOutOrder.repository.vo.LegacyReserveOutOrderVO; import com.mhd.oms.domain.reserveStockOutOrder.repository.vo.ReservedInventoryOut; +import com.mhd.oms.domain.reserveOutMaterialDetail.entity.ReserveOutMaterialDetail; import com.mhd.system.api.SystemServiceFeign; import com.mhd.system.api.UserServiceFeign; import com.mhd.system.api.domain.*; @@ -38,6 +46,7 @@ import com.mhd.system.api.model.LoginUser; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import java.lang.reflect.Field; @@ -68,6 +77,8 @@ public class ReserveStockOutOrderApplicationService { @Autowired private ReserveStockInOrderMapper reserveStockInOrderMapper; @Autowired + private ReserveStockOutOrderMapper reserveStockOutOrderMapper; + @Autowired private ReservationMaterialInventoryMapper reservationMaterialInventoryMapper; @Autowired private UserServiceFeign userServiceFeign; @@ -1112,6 +1123,237 @@ public class ReserveStockOutOrderApplicationService { return stockOutOrderDomainService.queryOutStockNoticePrintDetail(stockOutOrderDO); } + /** + * 老系统出库预约单数据导入(主表+明细两个Excel,多单批量导入) + * 老系统的id类字段不可用,货主按名称匹配新系统用户,仓库按仓库类型(1=干仓 2=冬仓)匹配新系统仓库,物料按名称匹配 + * 明细表物料名称取物料名称1和物料名称2中有值的那一个 + */ + @Transactional(rollbackFor = Exception.class) + public Map importLegacyData(List orderList, List detailList) { + if (orderList == null || orderList.isEmpty()) { + throw new ValidationException("出库预约单列表文件中没有有效数据"); + } + if (detailList == null || detailList.isEmpty()) { + throw new ValidationException("出库预约单明细列表文件中没有有效数据"); + } + + // 明细按出库单号分组 + Map> detailGroupMap = new LinkedHashMap<>(); + for (LegacyReserveOutDetailVO detailRow : detailList) { + detailGroupMap.computeIfAbsent(detailRow.getOutOrderNumber().trim(), k -> new ArrayList<>()).add(detailRow); + } + + List errorList = new ArrayList<>(); + // 名称匹配结果缓存,避免重复查库 + Map shipperCache = new HashMap<>(); + // 仓库类型 -> 仓库 缓存(老系统只有仓库类型,没有仓库名称) + Map warehouseTypeCache = new HashMap<>(); + Map materialCache = new HashMap<>(); + + Set orderNumberSet = new HashSet<>(); + List stockOutOrderList = new ArrayList<>(); + int lotIndex = 1; + for (LegacyReserveOutOrderVO orderRow : orderList) { + String orderNumber = orderRow.getOutOrderNumber().trim(); + if (!orderNumberSet.add(orderNumber)) { + errorList.add("出库单号【" + orderNumber + "】在主表文件中重复"); + continue; + } + if (reserveStockOutOrderMapper.countByOutOrderNumber(orderNumber) > 0) { + errorList.add("出库单号【" + orderNumber + "】在系统中已存在,请勿重复导入"); + continue; + } + + // 按货主名称匹配用户(查不到则用Excel中的名称兜底,不阻断导入) + UserPo shipper = null; + String shipperName = StringUtils.trimToEmpty(orderRow.getShipperName()); + if (StringUtils.isBlank(shipperName)) { + errorList.add("出库单号【" + orderNumber + "】货主名称为空"); + } else { + shipper = shipperCache.get(shipperName); + if (shipper == null) { + List userPoList = reserveStockInOrderMapper.getShipperByName(shipperName); + if (userPoList == null || userPoList.isEmpty()) { + errorList.add("出库单号【" + orderNumber + "】货主【" + shipperName + "】未找到对应用户,将仅以名称导入"); + } else { + shipper = userPoList.get(0); + shipperCache.put(shipperName, shipper); + } + } + } + + // 按仓库类型匹配仓库(老系统仓库类型 1=干仓 2=冬仓,查不到则用类型名兜底) + WarehousePO warehouse = null; + String warehouseType = StringUtils.trimToEmpty(orderRow.getWarehouseType()); + if (StringUtils.isNotBlank(warehouseType)) { + warehouse = warehouseTypeCache.get(warehouseType); + if (warehouse == null) { + List warehousePOList = reserveStockOutOrderMapper.getWarehouseByType(warehouseType); + if (warehousePOList == null || warehousePOList.isEmpty()) { + String fallbackName = "1".equals(warehouseType) ? "干仓" : ("2".equals(warehouseType) ? "冻仓" : ""); + errorList.add("出库单号【" + orderNumber + "】仓库类型【" + warehouseType + "】未找到对应仓库,将仅以名称导入"); + warehouseTypeCache.put(warehouseType, null); + warehouse = null; + } else { + warehouse = warehousePOList.get(0); + warehouseTypeCache.put(warehouseType, warehouse); + } + } + } + + // 明细匹配 + List detailRows = detailGroupMap.get(orderNumber); + if (detailRows == null || detailRows.isEmpty()) { + errorList.add("出库单号【" + orderNumber + "】在明细文件中没有明细数据"); + continue; + } + List materialDetailList = new ArrayList<>(); + for (LegacyReserveOutDetailVO detailRow : detailRows) { + String materialName = StringUtils.trimToEmpty(detailRow.getMaterialName()); + if (StringUtils.isBlank(materialName)) { + errorList.add("出库单号【" + orderNumber + "】存在物料名称为空的明细行"); + continue; + } + // 按物料名称匹配物料基础信息(查不到则用Excel中的名称兜底,不阻断导入) + MaterialBaseInfoPO materialBaseInfoPO = materialCache.get(materialName); + if (materialBaseInfoPO == null) { + List materialBaseInfoPOS = reserveStockInOrderMapper.getByName(materialName); + if (materialBaseInfoPOS == null || materialBaseInfoPOS.isEmpty()) { + errorList.add("出库单号【" + orderNumber + "】物料【" + materialName + "】未找到对应物料,将仅以名称导入"); + } else { + materialBaseInfoPO = materialBaseInfoPOS.get(0); + materialCache.put(materialName, materialBaseInfoPO); + } + } + ReserveOutMaterialDetailDO materialDetail = new ReserveOutMaterialDetailDO(); + if (materialBaseInfoPO != null) { + materialDetail.setMaterialBaseInfoId(materialBaseInfoPO.getMaterialBaseInfoId()); + materialDetail.setMaterialCode(materialBaseInfoPO.getMaterialCode()); + materialDetail.setUnitCode(materialBaseInfoPO.getUnitCode()); + materialDetail.setUnitName(materialBaseInfoPO.getUnitName()); + } + materialDetail.setMaterialName(materialName); + materialDetail.setReserveMaterialName(materialName); + materialDetail.setQuantity(detailRow.getQuantity()); + materialDetail.setOutboundQuantity(detailRow.getQuantity()); + materialDetail.setTotalNetWeight(detailRow.getWeight()); + materialDetail.setTotalGrossWeight(detailRow.getWeight()); + // 保留老系统关联的入库单号到备注 + if (StringUtils.isNotBlank(detailRow.getInOrderNumber())) { + materialDetail.setRemark("老系统入库单号:" + detailRow.getInOrderNumber()); + } + materialDetail.setContractFee(BigDecimal.ZERO); + materialDetail.setLotNo(generateDateSerialNumber() + "-" + lotIndex); + lotIndex++; + // 组织字段硬编码(与入库导入保持一致) + materialDetail.setOrganizationId(2841L); + materialDetail.setOrganizationName("仓码"); + materialDetail.setTopOrganizationId(2827L); + // 创建人/修改人名称取老系统Excel中的值,不使用当前登录人 + materialDetail.setCreateTime(detailRow.getCreateTime() != null ? detailRow.getCreateTime() : new Date()); + materialDetail.setCreateByName(detailRow.getCreateByName()); + materialDetail.setUpdateTime(detailRow.getUpdateTime()); + materialDetail.setUpdateByName(detailRow.getUpdateByName()); + materialDetail.setUniqueId(idGenerator.snowflakeId(SnowFlakeConstants.wmsId)); + materialDetailList.add(materialDetail); + } + + // 组装出库预约单 + ReserveStockOutOrderDO stockOutOrderDO = new ReserveStockOutOrderDO(); + stockOutOrderDO.setOutOrderNumber(orderNumber); + stockOutOrderDO.setBusinessOrderNo(generateDateTimeWithRandom()); + // 老系统历史数据,状态直接置为最终状态:已出库、审核通过 + stockOutOrderDO.setStatus(9); + stockOutOrderDO.setAuditStatus(3); + stockOutOrderDO.setIssueStatus(3); + stockOutOrderDO.setOutboundDate(orderRow.getOutboundDate()); + stockOutOrderDO.setNeedTransportFlag("0"); + stockOutOrderDO.setNeedDeclareFlag("0"); + stockOutOrderDO.setExpectTime(orderRow.getOutboundDate() != null ? orderRow.getOutboundDate() : new Date()); + stockOutOrderDO.setDocumentDate(orderRow.getDocumentDate() != null ? orderRow.getDocumentDate() : new Date()); + stockOutOrderDO.setDocumentCreator(orderRow.getDocumentCreator()); + stockOutOrderDO.setRemark(orderRow.getRemark()); + if (shipper != null) { + stockOutOrderDO.setShipperId(shipper.getUserId()); + stockOutOrderDO.setShipperName(shipper.getUserName()); + stockOutOrderDO.setCustomerId(shipper.getUserId()); + stockOutOrderDO.setCustomerName(shipper.getUserName()); + stockOutOrderDO.setSalesmanId(String.valueOf(shipper.getUserId())); + stockOutOrderDO.setSalesmanName(shipper.getUserName()); + stockOutOrderDO.setCustomerCode(reserveStockInOrderMapper.getCustomerNcCodeByUserId(shipper.getUserId())); + } else { + // 货主未匹配到,仅以Excel中的名称作为名称字段,id留空 + stockOutOrderDO.setShipperName(shipperName); + stockOutOrderDO.setCustomerName(shipperName); + stockOutOrderDO.setSalesmanName(shipperName); + } + // 组织字段硬编码(与入库导入保持一致) + stockOutOrderDO.setOrganizationId(2841L); + stockOutOrderDO.setOrganizationName("仓码"); + stockOutOrderDO.setTopOrganizationId(2827L); + if (warehouse != null) { + stockOutOrderDO.setWarehouseId(warehouse.getWarehouseId()); + stockOutOrderDO.setWarehouseCode(warehouse.getWarehouseCode()); + stockOutOrderDO.setWarehouseName(warehouse.getWarehouseName()); + } else if (StringUtils.isNotBlank(warehouseType)) { + // 仓库未匹配到,仅以仓库类型名作为仓库名称,id/编码留空 + String fallbackName = "1".equals(warehouseType) ? "干仓" : ("2".equals(warehouseType) ? "冻仓" : ""); + if (StringUtils.isNotBlank(fallbackName)) { + stockOutOrderDO.setWarehouseName(fallbackName); + } + } + // 创建人/修改人名称取老系统Excel中的值,不使用当前登录人 + stockOutOrderDO.setCreateTime(orderRow.getCreateTime() != null ? orderRow.getCreateTime() : new Date()); + stockOutOrderDO.setCreateByName(orderRow.getCreateByName()); + stockOutOrderDO.setUpdateTime(orderRow.getUpdateTime()); + stockOutOrderDO.setUpdateByName(orderRow.getUpdateByName()); + stockOutOrderDO.setMaterialDetailList(materialDetailList); + stockOutOrderList.add(stockOutOrderDO); + } + + // 明细文件中存在但主表文件中不存在的单号 + for (String detailOrderNumber : detailGroupMap.keySet()) { + if (!orderNumberSet.contains(detailOrderNumber)) { + errorList.add("明细文件中出库单号【" + detailOrderNumber + "】在主表文件中不存在"); + } + } + + // 逐单入库(即使有未匹配到基础数据的警告也不阻断,错误信息随结果返回) + // 直接走 MyBatis-Plus 入库,绕过领域服务/应用服务层的参数补全逻辑 + int detailCount = 0; + for (ReserveStockOutOrderDO stockOutOrderDO : stockOutOrderList) { + // 主表:DO -> 实体 -> save + ReserveStockOutOrder stockOutOrder = new ReserveStockOutOrder(); + BeanUtils.copyProperties(stockOutOrderDO, stockOutOrder); + stockOutOrder.setMaterialQuantity(stockOutOrderDO.getMaterialDetailList().size()); + BigDecimal totalQuantity = stockOutOrderDO.getMaterialDetailList().stream() + .map(ReserveOutMaterialDetailDO::getQuantity).filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + stockOutOrder.setQuantity(totalQuantity); + stockOutOrderService.save(stockOutOrder); + + // 明细:DO -> 实体 -> saveBatch + List detailEntities = new ArrayList<>(); + for (ReserveOutMaterialDetailDO detailDO : stockOutOrderDO.getMaterialDetailList()) { + ReserveOutMaterialDetail detail = new ReserveOutMaterialDetail(); + BeanUtils.copyProperties(detailDO, detail); + detail.setOutOrderNumber(stockOutOrderDO.getOutOrderNumber()); + detailEntities.add(detail); + } + if (!detailEntities.isEmpty()) { + outMaterialDetailService.saveBatch(detailEntities); + } + detailCount += detailEntities.size(); + } + Map result = new HashMap<>(); + result.put("orderCount", stockOutOrderList.size()); + result.put("detailCount", detailCount); + if (!errorList.isEmpty()) { + result.put("warnings", errorList); + } + return result; + } + public void importInData(List dataList) { LoginUser loginUser = SecurityUtils.getLoginUser(); ReserveStockOutOrderDO reserveStockOutOrder = new ReserveStockOutOrderDO(); diff --git a/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/ReserveStockOutOrder/ReserveStockOutOrderApi.java b/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/ReserveStockOutOrder/ReserveStockOutOrderApi.java index a3dae8373..14e6d1f9e 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/ReserveStockOutOrder/ReserveStockOutOrderApi.java +++ b/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/ReserveStockOutOrder/ReserveStockOutOrderApi.java @@ -15,6 +15,8 @@ import com.mhd.oms.domain.reserveStockOutOrder.repository.facade.IReserveStockOu import com.mhd.oms.domain.reserveStockOutOrder.repository.po.ReserveStockOutOrderPO; import com.mhd.oms.domain.reserveStockOutOrder.repository.todo.ReserveStockOutOrderDO; import com.mhd.oms.domain.reserveStockOutOrder.repository.todo.ReserveStockOutOrderDTO; +import com.mhd.oms.domain.reserveStockOutOrder.repository.vo.LegacyReserveOutDetailVO; +import com.mhd.oms.domain.reserveStockOutOrder.repository.vo.LegacyReserveOutOrderVO; import com.mhd.oms.domain.reserveStockOutOrder.repository.vo.ReservedInventoryOut; import com.mhd.oms.domain.reserveStockOutOrder.service.ReserveStockOutOrderApplicationService; import com.mhd.oms.domain.reserveStockOutOrder.service.ReserveStockOutOrderAssembler; @@ -303,4 +305,59 @@ public class ReserveStockOutOrderApi extends BaseController { return AjaxResult.success(stockOutOrderApplicationService.generateReserveOrderNumber()); } + /** + * 老系统出库预约单数据导入(两个文件:主表+明细) + * 通过货主名称、仓库类型、物料名称等匹配基础数据,id类字段不使用 + * 仓库类型:1=干仓 2=冬仓 + * 明细物料名称:取物料名称1和物料名称2中有值的那一个 + */ + @ApiOperation("老系统出库预约单数据导入") + @PostMapping("/importLegacy") + public ResponseEntity> importLegacyData( + @RequestParam("orderFile") MultipartFile orderFile, + @RequestParam("detailFile") MultipartFile detailFile) { + try { + // 1. 解析两个Excel文件 + List orderList = ExcelParseUtil.parseLegacyOrderExcel(orderFile); + List detailList = ExcelParseUtil.parseLegacyDetailExcel(detailFile); + + // 2. 调用应用服务执行导入 + Map result = stockOutOrderApplicationService.importLegacyData(orderList, detailList); + + Map successResponse = new HashMap<>(); + successResponse.put("code", 200); + successResponse.put("message", "老系统出库预约单数据导入成功"); + successResponse.put("orderCount", result.get("orderCount")); + successResponse.put("detailCount", result.get("detailCount")); + if (result.containsKey("warnings")) { + successResponse.put("warnings", result.get("warnings")); + } + return ResponseEntity.ok(successResponse); + + } catch (ValidationException e) { + log.error("老系统出库预约单数据验证失败: {}", e.getMessage(), e); + Map errorResponse = new HashMap<>(); + errorResponse.put("code", 400); + errorResponse.put("message", "数据验证失败"); + errorResponse.put("errorDetails", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse); + + } catch (IOException e) { + log.error("老系统出库预约单文件处理失败: {}", e.getMessage(), e); + Map errorResponse = new HashMap<>(); + errorResponse.put("code", 500); + errorResponse.put("message", "文件处理失败"); + errorResponse.put("errorDetails", e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse); + + } catch (Exception e) { + log.error("老系统出库预约单导入失败: {}", e.getMessage(), e); + Map errorResponse = new HashMap<>(); + errorResponse.put("code", 500); + errorResponse.put("message", "导入失败"); + errorResponse.put("errorDetails", e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse); + } + } + } \ No newline at end of file diff --git a/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/reserveStockInOrder/ReserveStockInOrderApi.java b/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/reserveStockInOrder/ReserveStockInOrderApi.java index ccd1b2d88..f1526fe43 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/reserveStockInOrder/ReserveStockInOrderApi.java +++ b/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/reserveStockInOrder/ReserveStockInOrderApi.java @@ -10,12 +10,15 @@ import com.mhd.oms.domain.reserveStockInOrder.repository.todo.ReserveStockInOrde import com.mhd.oms.domain.reserveStockInOrder.repository.todo.ReserveStockInOrderDTO; import com.mhd.oms.domain.reserveStockInOrder.repository.util.ExcelParseUtil; import com.mhd.oms.domain.reserveStockInOrder.repository.vo.InAndOutListVo; +import com.mhd.oms.domain.reserveStockInOrder.repository.vo.LegacyReserveInDetailVO; +import com.mhd.oms.domain.reserveStockInOrder.repository.vo.LegacyReserveInOrderVO; import com.mhd.oms.domain.reserveStockInOrder.repository.vo.ReservedInventory; import com.mhd.oms.domain.reserveStockInOrder.service.ReserveStockInOrderApplicationService; import com.mhd.oms.domain.reserveStockInOrder.service.ReserveStockInOrderAssembler; import com.mhd.system.api.domain.InMaterialDetailTZPD; import com.mhd.system.api.domain.StockInOrderTZPD; import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -34,6 +37,7 @@ import java.util.Map; * @author gen * @date 2024-05-21 */ +@Slf4j @RestController @RequestMapping("/reserveStockInOrderApi") public class ReserveStockInOrderApi extends BaseController { @@ -256,6 +260,68 @@ public class ReserveStockInOrderApi extends BaseController { } } + /** + * 老系统入库预约单数据导入(主表+明细两个Excel文件批量导入,按名称匹配基础数据) + */ + @ApiOperation("老系统入库预约单数据导入") + @PostMapping("/importLegacy") + public ResponseEntity> importLegacyExcel( + @RequestParam("orderFile") MultipartFile orderFile, + @RequestParam("detailFile") MultipartFile detailFile) { + try { + List orderList = ExcelParseUtil.parseLegacyOrderExcel(orderFile); + List detailList = ExcelParseUtil.parseLegacyDetailExcel(detailFile); + + Map importResult = stockInOrderApplicationService.importLegacyData(orderList, detailList); + + Map successResponse = new HashMap<>(); + successResponse.put("code", 200); + successResponse.put("message", "老系统数据导入成功"); + successResponse.put("orderCount", importResult.get("orderCount")); + successResponse.put("detailCount", importResult.get("detailCount")); + if (importResult.containsKey("warnings")) { + successResponse.put("warnings", importResult.get("warnings")); + } + return ResponseEntity.ok(successResponse); + + } catch (ValidationException e) { + // 数据验证失败响应 + log.warn("老系统数据导入校验失败: {}", e.getMessage()); + Map errorResponse = new HashMap<>(); + errorResponse.put("code", 400); + errorResponse.put("message", "数据验证失败"); + errorResponse.put("errorDetails", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse); + + } catch (IOException e) { + // 文件处理失败响应 + log.error("老系统数据导入文件处理失败", e); + Map errorResponse = new HashMap<>(); + errorResponse.put("code", 500); + errorResponse.put("message", "文件处理失败"); + errorResponse.put("errorDetails", e.getClass().getSimpleName() + ": " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse); + + } catch (Exception e) { + // 其他未知错误响应:打印完整堆栈,把真正的报错信息报出来 + log.error("老系统数据导入发生未知异常", e); + String errorType = e.getClass().getSimpleName(); + String errorMsg = e.getMessage() != null ? e.getMessage() : "无详细信息"; + // 如果异常带有 cause,则再追加 cause 信息,方便定位根因 + Throwable cause = e.getCause(); + StringBuilder detail = new StringBuilder(); + detail.append(errorType).append(": ").append(errorMsg); + if (cause != null && cause != e) { + detail.append(" | 根因: ").append(cause.getClass().getSimpleName()).append(": ").append(cause.getMessage()); + } + Map errorResponse = new HashMap<>(); + errorResponse.put("code", 500); + errorResponse.put("message", detail.toString()); + errorResponse.put("errorDetails", detail.toString()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse); + } + } + /** * 生成入库预约单号 */ diff --git a/mhd_oms/src/main/resources/mapper/reserveStockInOrder/ReserveStockInOrderMapper.xml b/mhd_oms/src/main/resources/mapper/reserveStockInOrder/ReserveStockInOrderMapper.xml index 873ed5d8e..ffdb7fdd4 100644 --- a/mhd_oms/src/main/resources/mapper/reserveStockInOrder/ReserveStockInOrderMapper.xml +++ b/mhd_oms/src/main/resources/mapper/reserveStockInOrder/ReserveStockInOrderMapper.xml @@ -788,4 +788,47 @@ WHERE in_order_number LIKE concat(#{prefix}, '%') + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mhd_oms/src/main/resources/mapper/reserveStockOutOrder/ReserveStockOutOrderMapper.xml b/mhd_oms/src/main/resources/mapper/reserveStockOutOrder/ReserveStockOutOrderMapper.xml index a6559f786..8e0a52a6b 100644 --- a/mhd_oms/src/main/resources/mapper/reserveStockOutOrder/ReserveStockOutOrderMapper.xml +++ b/mhd_oms/src/main/resources/mapper/reserveStockOutOrder/ReserveStockOutOrderMapper.xml @@ -528,4 +528,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" WHERE out_order_number LIKE concat(#{prefix}, '%') + + + + + + \ No newline at end of file