预约单老系统数据导入;

This commit is contained in:
王奎兴
2026-07-31 15:47:20 +08:00
parent d4ba4f0c0b
commit f6ee1a3eee
14 changed files with 1168 additions and 0 deletions
@@ -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<ReserveStockInOrde
int countByOrderNumberPrefix(@Param("prefix") String prefix);
List<String> generateReserveOrderNumber1(@Param("prefix") String prefix);
/**
* 根据货主名称查询用户(老系统数据导入用)
*/
List<UserPo> getShipperByName(@Param("name") String name);
/**
* 根据仓库名称查询仓库(老系统数据导入用)
*/
List<WarehousePO> getWarehouseByName(@Param("name") String name);
/**
* 根据仓库类型查询仓库(老系统数据导入用,1=干仓 2=冬仓)
*/
List<WarehousePO> getWarehouseByType(@Param("warehouseType") String warehouseType);
/**
* 根据货主用户ID查询NC客户编码
*/
String getCustomerNcCodeByUserId(@Param("id") Long id);
/**
* 根据入库单号精确统计(导入防重用)
*/
int countByInOrderNumber(@Param("inOrderNumber") String inOrderNumber);
}
@@ -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<LegacyReserveInOrderVO> parseLegacyOrderExcel(MultipartFile file) throws IOException {
validateFile(file);
List<LegacyReserveInOrderVO> 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<LegacyReserveInDetailVO> parseLegacyDetailExcel(MultipartFile file) throws IOException {
validateFile(file);
List<LegacyReserveInDetailVO> 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文件合法性
*/
@@ -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;
}
@@ -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;
}
@@ -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<String, Object> importLegacyData(List<LegacyReserveInOrderVO> orderList, List<LegacyReserveInDetailVO> detailList) {
if (orderList == null || orderList.isEmpty()) {
throw new ValidationException("入库预约单列表文件中没有有效数据");
}
if (detailList == null || detailList.isEmpty()) {
throw new ValidationException("入库预约单明细列表文件中没有有效数据");
}
// 明细按入库单号分组
Map<String, List<LegacyReserveInDetailVO>> detailGroupMap = new LinkedHashMap<>();
for (LegacyReserveInDetailVO detailRow : detailList) {
detailGroupMap.computeIfAbsent(detailRow.getInOrderNumber().trim(), k -> new ArrayList<>()).add(detailRow);
}
List<String> errorList = new ArrayList<>();
// 名称匹配结果缓存,避免重复查库
Map<String, UserPo> shipperCache = new HashMap<>();
Map<String, WarehousePO> warehouseCache = new HashMap<>();
// 仓库类型 -> 仓库 缓存(用于名称匹配失败时的兜底)
Map<String, WarehousePO> warehouseTypeCache = new HashMap<>();
Map<String, MaterialBaseInfoPO> materialCache = new HashMap<>();
Set<String> orderNumberSet = new HashSet<>();
List<ReserveStockInOrderDO> 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<UserPo> 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<WarehousePO> 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<WarehousePO> 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<LegacyReserveInDetailVO> detailRows = detailGroupMap.get(orderNumber);
if (detailRows == null || detailRows.isEmpty()) {
errorList.add("入库单号【" + orderNumber + "】在明细文件中没有明细数据");
continue;
}
List<ReserveInMaterialDetailDO> 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<MaterialBaseInfoPO> 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<ReserveInMaterialDetail> 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<String, Object> 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();
@@ -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<ReserveStockOutOr
int countByOrderNumberPrefix(@Param("prefix") String prefix);
List<String> generateReserveOrderNumber1(@Param("prefix") String prefix);
/**
* 老系统数据导入:根据出库单号精确统计(防重)
*/
int countByOutOrderNumber(@Param("outOrderNumber") String outOrderNumber);
/**
* 老系统数据导入:根据仓库类型查询仓库
* @param warehouseType 仓库类型编码(如 "1"/"2"
*/
List<WarehousePO> getWarehouseByType(@Param("warehouseType") String warehouseType);
}
@@ -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<LegacyReserveOutOrderVO> parseLegacyOrderExcel(MultipartFile file) throws IOException {
validateFile(file);
List<LegacyReserveOutOrderVO> 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<LegacyReserveOutDetailVO> parseLegacyDetailExcel(MultipartFile file) throws IOException {
validateFile(file);
List<LegacyReserveOutDetailVO> 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文件合法性
*/
@@ -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;
}
@@ -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;
}
@@ -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<String, Object> importLegacyData(List<LegacyReserveOutOrderVO> orderList, List<LegacyReserveOutDetailVO> detailList) {
if (orderList == null || orderList.isEmpty()) {
throw new ValidationException("出库预约单列表文件中没有有效数据");
}
if (detailList == null || detailList.isEmpty()) {
throw new ValidationException("出库预约单明细列表文件中没有有效数据");
}
// 明细按出库单号分组
Map<String, List<LegacyReserveOutDetailVO>> detailGroupMap = new LinkedHashMap<>();
for (LegacyReserveOutDetailVO detailRow : detailList) {
detailGroupMap.computeIfAbsent(detailRow.getOutOrderNumber().trim(), k -> new ArrayList<>()).add(detailRow);
}
List<String> errorList = new ArrayList<>();
// 名称匹配结果缓存避免重复查库
Map<String, UserPo> shipperCache = new HashMap<>();
// 仓库类型 -> 仓库 缓存老系统只有仓库类型没有仓库名称
Map<String, WarehousePO> warehouseTypeCache = new HashMap<>();
Map<String, MaterialBaseInfoPO> materialCache = new HashMap<>();
Set<String> orderNumberSet = new HashSet<>();
List<ReserveStockOutOrderDO> 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<UserPo> 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<WarehousePO> 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<LegacyReserveOutDetailVO> detailRows = detailGroupMap.get(orderNumber);
if (detailRows == null || detailRows.isEmpty()) {
errorList.add("出库单号【" + orderNumber + "】在明细文件中没有明细数据");
continue;
}
List<ReserveOutMaterialDetailDO> 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<MaterialBaseInfoPO> 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<ReserveOutMaterialDetail> 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<String, Object> 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<ReservedInventoryOut> dataList) {
LoginUser loginUser = SecurityUtils.getLoginUser();
ReserveStockOutOrderDO reserveStockOutOrder = new ReserveStockOutOrderDO();
@@ -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<Map<String, Object>> importLegacyData(
@RequestParam("orderFile") MultipartFile orderFile,
@RequestParam("detailFile") MultipartFile detailFile) {
try {
// 1. 解析两个Excel文件
List<LegacyReserveOutOrderVO> orderList = ExcelParseUtil.parseLegacyOrderExcel(orderFile);
List<LegacyReserveOutDetailVO> detailList = ExcelParseUtil.parseLegacyDetailExcel(detailFile);
// 2. 调用应用服务执行导入
Map<String, Object> result = stockOutOrderApplicationService.importLegacyData(orderList, detailList);
Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> errorResponse = new HashMap<>();
errorResponse.put("code", 500);
errorResponse.put("message", "导入失败");
errorResponse.put("errorDetails", e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
}
}
@@ -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<Map<String, Object>> importLegacyExcel(
@RequestParam("orderFile") MultipartFile orderFile,
@RequestParam("detailFile") MultipartFile detailFile) {
try {
List<LegacyReserveInOrderVO> orderList = ExcelParseUtil.parseLegacyOrderExcel(orderFile);
List<LegacyReserveInDetailVO> detailList = ExcelParseUtil.parseLegacyDetailExcel(detailFile);
Map<String, Object> importResult = stockInOrderApplicationService.importLegacyData(orderList, detailList);
Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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);
}
}
/**
* 生成入库预约单号
*/
@@ -788,4 +788,47 @@
WHERE in_order_number LIKE concat(#{prefix}, '%')
</select>
<!-- 老系统数据导入:根据货主名称查询用户 -->
<select id="getShipperByName" parameterType="java.lang.String" resultType="com.mhd.common.core.domain.po.UserPo">
select USER_ID, USER_NAME, ORGANIZATION_ID, ORGANIZATION_NAME, TOP_ORGANIZATION_ID
from NGWL_TEST_USER."USER"
where DEL_FLAG = 1 and USER_NAME = #{name}
</select>
<!-- 老系统数据导入:根据仓库名称查询仓库(支持模糊匹配,如Excel中"凍倉-陳美玲"匹配系统中"凍倉" -->
<select id="getWarehouseByName" parameterType="java.lang.String" resultType="com.mhd.common.core.domain.po.WarehousePO">
select WAREHOUSE_ID, WAREHOUSE_CODE, WAREHOUSE_NAME
from "NGWL_TEST_SYSTEM".WAREHOUSE
where DEL_FLAG = 1
and (
WAREHOUSE_NAME = #{name}
or #{name} LIKE ('%' || WAREHOUSE_NAME || '%')
)
order by case when WAREHOUSE_NAME = #{name} then 0 else 1 end,
length(WAREHOUSE_NAME) asc
</select>
<!-- 老系统数据导入:根据仓库类型查询仓库(1=干仓 2=冬仓) -->
<select id="getWarehouseByType" parameterType="java.lang.String" resultType="com.mhd.common.core.domain.po.WarehousePO">
select WAREHOUSE_ID, WAREHOUSE_CODE, WAREHOUSE_NAME
from "NGWL_TEST_SYSTEM".WAREHOUSE
where DEL_FLAG = 1
and (
WAREHOUSE_TYPE = #{warehouseType}
or WAREHOUSE_TYPE_NAME = case when #{warehouseType} = '1' then '干仓' when #{warehouseType} = '2' then '冬仓' else null end
)
</select>
<!-- 老系统数据导入:根据货主用户ID查询NC客户编码 -->
<select id="getCustomerNcCodeByUserId" parameterType="java.lang.Long" resultType="java.lang.String">
select CUSTOMER_NC_CODE from "NGWL_TEST_USER".USER_SHIPPER where DEL_FLAG = 1 and USER_ID = #{id} limit 1
</select>
<!-- 老系统数据导入:根据入库单号精确统计(防重) -->
<select id="countByInOrderNumber" resultType="java.lang.Integer" parameterType="java.lang.String">
SELECT COUNT(1)
FROM reserve_stock_in_order
WHERE in_order_number = #{inOrderNumber}
</select>
</mapper>
@@ -528,4 +528,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
WHERE out_order_number LIKE concat(#{prefix}, '%')
</select>
<!-- 老系统数据导入:根据出库单号精确统计(防重) -->
<select id="countByOutOrderNumber" resultType="java.lang.Integer" parameterType="java.lang.String">
SELECT COUNT(1)
FROM reserve_stock_out_order
WHERE out_order_number = #{outOrderNumber}
</select>
<!-- 老系统数据导入:根据仓库类型查询仓库
老系统仓库类型 1=干仓 2=冬仓;新系统 WAREHOUSE_TYPE 为数据字典编码
同时尝试匹配 WAREHOUSE_TYPE 编码和 WAREHOUSE_TYPE_NAME 名称,兼容字典值与老系统编码一致或不一致两种情况 -->
<select id="getWarehouseByType" parameterType="java.lang.String" resultType="com.mhd.common.core.domain.po.WarehousePO">
select WAREHOUSE_ID, WAREHOUSE_CODE, WAREHOUSE_NAME
from "NGWL_TEST_SYSTEM".WAREHOUSE
where DEL_FLAG = 1
and (
WAREHOUSE_TYPE = #{warehouseType}
or WAREHOUSE_TYPE_NAME = case when #{warehouseType} = '1' then '干仓' when #{warehouseType} = '2' then '冬仓' else null end
)
</select>
</mapper>