feat(oms): 出/入仓委托单新增导入接口(委托单模板与W出入库管理导入一致)

出仓: POST /outEntrustOrderApi/import, EasyExcel两遍解析(前11行表头+第12行起明细), 客户名称解析货主, 明细按料号>SKU>品名匹配货主物料档案(料号不存在/规格/SKU不一致报错), 柜号/运输方式/监管方式字典转码, 校验通过复用新增链路落库(生成CKWT单号, 待审核/未下发); 入仓: POST /reservationStockInOrderApi/importData, 恢复原注释W式导入并按现行类型重写——POI标签扫描前12行表头+ExcelUtil读13行列名明细, 按委托编号NO分组建单, 料号必填且须在该货主下存在, 规格/SKU与档案一致性校验, 数量空行/容器行跳过, 计划数量=入库数量之和, 订单来源=数据导入; OutEntrustOrderMapper新增跨库物料直查(NGWL_TEST_WMS.MATERIAL_BASE_INFO, 货主+一级组织过滤)
This commit is contained in:
rcx
2026-09-14 13:55:10 +08:00
parent e60f22979c
commit 888a293ebd
10 changed files with 1806 additions and 11 deletions
@@ -11,6 +11,7 @@ import com.mhd.oms.domain.outEntrustOrder.repository.po.OutEntrustOrderPO;
import com.mhd.oms.domain.outEntrustOrder.repository.todo.OutEntrustOrderDO;
import com.mhd.oms.domain.outEntrustOrder.repository.todo.OutEntrustOrderDetailDO;
import com.mhd.oms.domain.outEntrustOrder.service.OutEntrustOrderDomainService;
import com.mhd.oms.domain.outEntrustOrder.repository.listener.OutEntrustExcelParseService;
import com.mhd.system.api.SystemServiceFeign;
import com.mhd.system.api.WmsServiceFeign;
import com.mhd.system.api.domain.NoticeMaterialDetailDTO;
@@ -20,8 +21,10 @@ import com.mhd.system.api.model.LoginUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
@@ -52,6 +55,9 @@ public class OutEntrustOrderApplicationService {
@Resource
private WmsServiceFeign wmsServiceFeign;
@Resource
private OutEntrustExcelParseService outEntrustExcelParseService;
/**
* 分页查询出仓委托单列表(不做默认状态过滤,显示全部数据)
*/
@@ -108,6 +114,45 @@ public class OutEntrustOrderApplicationService {
return outEntrustOrderDomainService.insert(outEntrustOrderDO);
}
/**
* 导入出仓委托单(模板与WMS出库管理导入一致:前11行表头+第12行起明细)
* 解析校验通过后复用新增链路落库:生成CKWT委托单号,状态待审核/未下发
*
* @return 校验错误信息;空串表示导入成功
*/
public StringBuilder importOutEntrustOrder(MultipartFile file, Long warehouseId, String warehouseCode, String warehouseName) throws IOException {
//解析+校验(客户解析/物料匹配/字典转换/必填项)
OutEntrustExcelParseService.ParseResult parseResult =
outEntrustExcelParseService.parseOutEntrustOrderExcel(file, warehouseName, warehouseCode, warehouseId);
if (parseResult.getErrorMsg().length() > 0) {
return parseResult.getErrorMsg();
}
OutEntrustOrderDO outEntrustOrderDO = parseResult.getOrder();
//审计信息(与WMS出库导入一致,导入人=当前登录人)
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser != null) {
String userRealName;
if (loginUser.getUserPo() != null && loginUser.getUserPo().getUserName() != null
&& !loginUser.getUserPo().getUserName().trim().isEmpty()) {
userRealName = loginUser.getUserPo().getUserName();
} else if (loginUser.getRealname() != null && !loginUser.getRealname().trim().isEmpty()) {
userRealName = loginUser.getRealname();
} else {
userRealName = loginUser.getUsername();
}
Date now = new Date();
outEntrustOrderDO.setCreateBy(loginUser.getUserid());
outEntrustOrderDO.setCreateByName(userRealName);
outEntrustOrderDO.setCreateTime(now);
outEntrustOrderDO.setUpdateBy(loginUser.getUserid());
outEntrustOrderDO.setUpdateByName(userRealName);
outEntrustOrderDO.setUpdateTime(now);
}
//复用新增链路:必填校验→默认普通出库→仓库校验→组织信息→汇总申报数量→生成CKWT委托单号(待审核/未下发)
insert(outEntrustOrderDO);
return new StringBuilder();
}
/**
* 修改出仓委托单(仅待审核/已驳回且未下发可修改;已驳回单编辑后状态重置为待审核)
*/
@@ -0,0 +1,58 @@
package com.mhd.oms.domain.outEntrustOrder.repository.listener;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
/**
* 自定义转换器,不进行任何类型转换,直接返回原始值
* 覆盖EasyExcel默认的StringNumberConverter,避免数字格式化错误
*
* @author rcx
* @date 2026-09-14
*/
public class NoConvertConverter implements Converter<String> {
@Override
public Class<?> supportJavaTypeKey() {
return String.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
// 关键:返回NUMBER类型,覆盖StringNumberConverter
return CellDataTypeEnum.NUMBER;
}
@Override
public String convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
// 不进行任何格式化,直接返回原始值的字符串表示
if (cellData == null) {
return "";
}
// 根据单元格类型获取原始值
try {
CellDataTypeEnum type = cellData.getType();
if (type == CellDataTypeEnum.STRING) {
return cellData.getStringValue() != null ? cellData.getStringValue() : "";
} else if (type == CellDataTypeEnum.NUMBER) {
// 直接转换数字为字符串,不进行格式化
return cellData.getNumberValue() != null ? cellData.getNumberValue().toString() : "";
} else if (type == CellDataTypeEnum.BOOLEAN) {
return cellData.getBooleanValue() != null ? cellData.getBooleanValue().toString() : "";
} else if (type == CellDataTypeEnum.EMPTY) {
return "";
} else {
// 其他类型尝试获取字符串值
return cellData.getStringValue() != null ? cellData.getStringValue() : "";
}
} catch (Exception e) {
// 如果获取失败,返回空字符串
return "";
}
}
}
@@ -0,0 +1,481 @@
package com.mhd.oms.domain.outEntrustOrder.repository.listener;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.mhd.common.core.domain.po.SysDictDataVo;
import com.mhd.common.core.domain.po.UserPo;
import com.mhd.common.core.exception.ServiceException;
import com.mhd.common.core.utils.StringUtils;
import com.mhd.common.core.web.domain.AjaxResult;
import com.mhd.common.security.utils.SecurityUtils;
import com.mhd.oms.domain.outEntrustOrder.repository.mapper.OutEntrustOrderMapper;
import com.mhd.oms.domain.outEntrustOrder.repository.todo.OutEntrustOrderDO;
import com.mhd.oms.domain.outEntrustOrder.repository.todo.OutEntrustOrderDetailDO;
import com.mhd.oms.domain.reservationStockInOrder.repository.po.MaterialBaseInfoPO;
import com.mhd.system.api.SystemServiceFeign;
import com.mhd.system.api.UserServiceFeign;
import com.mhd.system.api.model.LoginUser;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 出仓委托单Excel解析服务
* 模板与WMS出库管理导入一致:前11行为表头,第12行起为明细
*
* @author rcx
* @date 2026-09-14
*/
@Slf4j
@Service
public class OutEntrustExcelParseService {
@Resource
private SystemServiceFeign systemServiceFeign;
@Resource
private UserServiceFeign userServiceFeign;
@Autowired
private OutEntrustOrderMapper outEntrustOrderMapper;
/**
* 解析结果:委托单对象 + 校验错误信息(空串表示通过)
*/
@Getter
public static class ParseResult {
private final OutEntrustOrderDO order;
private final StringBuilder errorMsg;
public ParseResult(OutEntrustOrderDO order, StringBuilder errorMsg) {
this.order = order;
this.errorMsg = errorMsg;
}
}
/**
* 解析出仓委托单Excel(不落库,落库由应用服务走新增链路)
*
* @param file 上传的Excel文件
* @param warehouseName 仓库名称(导入弹窗选择)
* @param warehouseCode 仓库编码
* @param warehouseId 仓库ID
* @return 解析结果:委托单对象 + 校验错误信息;错误信息非空时不应落库
*/
public ParseResult parseOutEntrustOrderExcel(MultipartFile file, String warehouseName, String warehouseCode, Long warehouseId) throws IOException {
OutEntrustHeaderListener headerListener = new OutEntrustHeaderListener();
ReadSheet headerSheet = EasyExcel.readSheet(0)
.headRowNumber(0)
.build();
ExcelReader headerReader = EasyExcel.read(file.getInputStream())
.autoCloseStream(false)
.registerReadListener(headerListener)
.registerConverter(new NoConvertConverter())
.headRowNumber(0)
.build();
// 通过监听器控制只读取前11行
headerReader.read(headerSheet);
InputStream itemInputStream = file.getInputStream();
OutEntrustItemListener itemListener = new OutEntrustItemListener();
// 商品明细需要跳过前11行,从第12行开始读取
ExcelReader itemReader = EasyExcel.read(itemInputStream)
.autoCloseStream(false)
.registerReadListener(itemListener)
.registerConverter(new NoConvertConverter())
.headRowNumber(12)
.build();
ReadSheet itemSheet = EasyExcel.readSheet(0).build();
itemReader.read(itemSheet);
itemReader.finish();
List<OutEntrustOrderDetailDO> itemList = itemListener.getItemList();
OutEntrustOrderDO outEntrustOrder = headerListener.getOutEntrustOrder();
// 提前获取货主ID,用于查询物料基础信息时过滤
String customerName = outEntrustOrder.getShipperName();
UserPo shipperUser = resolveCustomerShipperUserPoForImport(customerName);
Long shipperId = shipperUser != null ? shipperUser.getUserId() : null;
LoginUser loginUser = SecurityUtils.getLoginUser();
Long topOrganizationId = loginUser != null && loginUser.getUserPo() != null
? loginUser.getUserPo().getTopOrganizationId() : null;
initList(itemList, shipperId, topOrganizationId);
outEntrustOrder.setMaterialDetailList(itemList);
outEntrustOrder.setWarehouseName(warehouseName);
outEntrustOrder.setWarehouseCode(warehouseCode);
outEntrustOrder.setWarehouseId(warehouseId);
//业务员默认当前登录人
if (loginUser != null) {
outEntrustOrder.setSalesmanId(String.valueOf(loginUser.getUserid()));
outEntrustOrder.setSalesmanName(loginUser.getUserPo() != null ? loginUser.getUserPo().getUserName() : loginUser.getUsername());
}
//货主信息回填(名称/编号以用户中心为准)
if (shipperUser != null) {
outEntrustOrder.setShipperId(shipperUser.getUserId());
outEntrustOrder.setShipperCode(shipperUser.getUserMemberCode());
outEntrustOrder.setShipperName(shipperUser.getUserName());
}
initDict(outEntrustOrder);
//校验必填字段
StringBuilder errorMsg = check(outEntrustOrder);
// 日期单元格有值但解析失败:与物料一致性错误同等对待,阻断导入并明确提示,杜绝静默丢日期
for (String dateError : headerListener.getDateParseErrors()) {
errorMsg.append(dateError).append("; ");
}
return new ParseResult(outEntrustOrder, errorMsg);
}
/**
* 校验导入明细是否已在当前货主下维护物料基础信息
*/
private StringBuilder validateShipperMaterialDetails(OutEntrustOrderDO outEntrustOrder) {
StringBuilder sb = new StringBuilder();
List<OutEntrustOrderDetailDO> details = outEntrustOrder.getMaterialDetailList();
if (details == null || details.isEmpty()) {
return sb;
}
Long shipperId = outEntrustOrder.getShipperId();
for (OutEntrustOrderDetailDO row : details) {
if (row.getMaterialBaseInfoId() != null) {
continue;
}
boolean hasIdentifier = StringUtils.isNotBlank(row.getMaterialNo())
|| StringUtils.isNotBlank(row.getSkucode())
|| StringUtils.isNotBlank(row.getCommodityName());
if (!hasIdentifier) {
continue;
}
String desc = buildImportMaterialRowDesc(row);
if (ObjectUtil.isEmpty(shipperId)) {
sb.append("请先确认客户(货主)信息;无法校验以下物料是否已在货主下维护:").append(desc).append("; ");
} else {
sb.append("当前货主下未维护以下物料:").append(desc).append("; ");
}
}
return sb;
}
/**
* 提示文案只强调 Excel 导入列「商品名称」;商品名称为空时用导入料号/SKU 辅助说明。
*/
private String buildImportMaterialRowDesc(OutEntrustOrderDetailDO row) {
String commodityName = StringUtils.trimToEmpty(row.getCommodityName());
if (StringUtils.isNotBlank(commodityName)) {
return "商品名称「" + commodityName + "";
}
String materialNo = StringUtils.trimToEmpty(row.getMaterialNo());
String sku = StringUtils.trimToEmpty(row.getSkucode());
StringBuilder b = new StringBuilder("导入商品名称为空");
if (StringUtils.isNotBlank(materialNo)) {
b.append(",料号「").append(materialNo).append("");
}
if (StringUtils.isNotBlank(sku)) {
b.append("SKU「").append(sku).append("");
}
return b.toString();
}
private StringBuilder check(OutEntrustOrderDO outEntrustOrder) {
StringBuilder errorMsg = new StringBuilder();
errorMsg.append(validateShipperMaterialDetails(outEntrustOrder));
if (outEntrustOrder.getMaterialDetailList() == null || outEntrustOrder.getMaterialDetailList().isEmpty()) {
errorMsg.append("物料明细列表不能为空或匹配不上物料信息; ");
}
if (StringUtils.isEmpty(outEntrustOrder.getToReceiver())) errorMsg.append("To不能为空; ");
if (StringUtils.isEmpty(outEntrustOrder.getCarrier())) errorMsg.append("承运商不能为空; ");
if (StringUtils.isEmpty(outEntrustOrder.getTransportModeCode())) errorMsg.append("运输方式不能为空; ");
if (StringUtils.isEmpty(outEntrustOrder.getSupervisionModeCode())) errorMsg.append("监督方式不能为空; ");
if (StringUtils.isEmpty(outEntrustOrder.getDepartureArrivalCountry())) errorMsg.append("出发/到达国家不能为空; ");
if (StringUtils.isEmpty(outEntrustOrder.getContactPerson())) errorMsg.append("联系人不能为空; ");
if (ObjectUtil.isEmpty(outEntrustOrder.getShipperId())) errorMsg.append("客户名称不能为空或匹配不上客户信息; ");
if (StringUtils.isEmpty(outEntrustOrder.getDeclareCustoms())) errorMsg.append("申报地关区不能为空; ");
if (StringUtils.isEmpty(outEntrustOrder.getEntryExitCustoms())) errorMsg.append("进出境关别不能为空; ");
return errorMsg;
}
/**
* @description 初始化物料明细列表,查询物料基础信息并设置物料基础信息ID(与WMS出库管理导入逻辑一致:料号 > SKU码 > 商品名称)
* @param itemList 出仓委托单明细列表
* @param shipperId 货主ID(用于过滤物料基础信息,确保查询到的是该货主绑定的物料)
* @param topOrganizationId 一级组织ID(用于过滤物料基础信息)
*/
private void initList(List<OutEntrustOrderDetailDO> itemList, Long shipperId, Long topOrganizationId) {
for (OutEntrustOrderDetailDO detail : itemList) {
// 未识别货主时不查物料,避免匹配到其他货主的物料基础信息
if (shipperId == null) {
continue;
}
// 根据明细表中的字段查询物料基础信息(优先:料号 > 商品SKU码 > 商品名称)
MaterialBaseInfoPO materialBaseInfoPO = null;
// 优先使用料号查询(materialNo对应materialCode
String materialNo = detail.getMaterialNo();
if (StringUtils.isNotBlank(materialNo)) {
List<MaterialBaseInfoPO> materialList = outEntrustOrderMapper.getMaterialByMaterialCode(materialNo, shipperId, topOrganizationId);
if (materialList != null && !materialList.isEmpty()) {
materialBaseInfoPO = materialList.get(0);
// ==== 料号与规格型号/SKU码一致性校验:任何一项不符直接报对应错误 ====
// 双方(trim后)必须完全一致:Excel留空而档案有值、或Excel填了值而档案为空,均视为不一致报错
String excelSpec = StringUtils.trimToEmpty(detail.getSpecificationModel());
String dbSpec = StringUtils.trimToEmpty(materialBaseInfoPO.getSpecificationModel());
if (!excelSpec.equals(dbSpec)) {
throw new ServiceException(String.format("导入失败:料号「%s」规格型号与物料档案不一致(Excel:%s / 档案:%s",
materialNo, excelSpec.isEmpty() ? "" : excelSpec, dbSpec.isEmpty() ? "" : dbSpec));
}
String excelSku = StringUtils.trimToEmpty(detail.getSkucode());
String dbSku = StringUtils.trimToEmpty(materialBaseInfoPO.getBarCode());
if (!excelSku.equals(dbSku)) {
throw new ServiceException(String.format("导入失败:料号「%s」SKU码与物料档案不一致(Excel:%s / 档案:%s",
materialNo, excelSku.isEmpty() ? "" : excelSku, dbSku.isEmpty() ? "" : dbSku));
}
} else {
// 料号已填写但在该货主下查不到 → 直接报错(与WMS出库导入一致),不再被SKU码/商品名称静默兜底
throw new ServiceException(String.format("导入失败:料号「%s」在当前客户(货主)下不存在,请核对料号或在物料管理中维护", materialNo));
}
}
// 如果料号查询不到,使用商品SKU码查询(skucode对应barCode
if (materialBaseInfoPO == null && StringUtils.isNotBlank(detail.getSkucode())) {
List<MaterialBaseInfoPO> materialList = outEntrustOrderMapper.getMaterialByBarCode(detail.getSkucode(), shipperId, topOrganizationId);
if (materialList != null && !materialList.isEmpty()) {
materialBaseInfoPO = materialList.get(0);
}
}
// 如果料号和SKU码都查询不到,使用商品名称查询(commodityName对应materialName
if (materialBaseInfoPO == null && StringUtils.isNotBlank(detail.getCommodityName())) {
List<MaterialBaseInfoPO> materialList = outEntrustOrderMapper.getMaterialByMaterialName(detail.getCommodityName(), shipperId, topOrganizationId);
if (materialList != null && !materialList.isEmpty()) {
materialBaseInfoPO = materialList.get(0);
}
}
// 设置物料基础信息ID,并从物料基础信息中回填料号、物料编码、物料名称等信息
if (materialBaseInfoPO != null) {
detail.setMaterialBaseInfoId(materialBaseInfoPO.getMaterialBaseInfoId());
// 单价统一从物料档案回填
detail.setUnitPrice(materialBaseInfoPO.getUnitPrice());
// 如果Excel中没有料号,从物料基础信息中回填
if (StringUtils.isBlank(detail.getMaterialNo())) {
detail.setMaterialNo(materialBaseInfoPO.getMaterialCode());
}
// 回填物料编码
if (StringUtils.isBlank(detail.getMaterialCode())) {
detail.setMaterialCode(materialBaseInfoPO.getMaterialCode());
}
// 回填商品编号
if (StringUtils.isBlank(detail.getCommodityNo())) {
detail.setCommodityNo(materialBaseInfoPO.getMaterialCode());
}
// 回填物料名称(如果Excel中的商品名称为空,使用物料基础信息中的名称)
if (StringUtils.isBlank(detail.getCommodityName())) {
detail.setCommodityName(materialBaseInfoPO.getMaterialName());
}
// 回填物料名称到materialName字段
detail.setMaterialName(materialBaseInfoPO.getMaterialName());
// 回填条形码
detail.setBarCode(materialBaseInfoPO.getBarCode());
// 如果Excel中没有SKU码,从物料基础信息中回填
if (StringUtils.isBlank(detail.getSkucode())) {
detail.setSkucode(materialBaseInfoPO.getBarCode());
}
}
// 单位:优先使用Excel中的"单位"字段,如果为空则使用"申报单位",如果还为空则从物料主数据获取(与WMS出库导入处理一致)
String unit = StringUtils.isNotBlank(detail.getUnit()) ? detail.getUnit() : null;
if (StringUtils.isBlank(unit) && StringUtils.isNotBlank(detail.getDeclareUnit())) {
unit = detail.getDeclareUnit();
}
if (StringUtils.isBlank(unit) && materialBaseInfoPO != null && StringUtils.isNotBlank(materialBaseInfoPO.getUnitName())) {
unit = materialBaseInfoPO.getUnitName();
}
detail.setUnit(unit);
}
}
private void initDict(OutEntrustOrderDO outEntrustOrder) {
if (ObjectUtil.isNotNull(outEntrustOrder.getContainerNoName())) {
AjaxResult containerNo = systemServiceFeign.selectListByDictType("cabinetNo");
if (ObjectUtil.isNotNull(containerNo) && "200".equals(String.valueOf(containerNo.get("code")))) {
List<SysDictDataVo> containerNoList = JSON.parseArray(JSONObject.toJSONString(containerNo.get("data")), SysDictDataVo.class);
for (SysDictDataVo sysDictDataVo : containerNoList) {
if (outEntrustOrder.getContainerNoName().equals(sysDictDataVo.getDictLabel())) {
outEntrustOrder.setContainerNo(sysDictDataVo.getDictValue());
break;
}
}
}
}
if (ObjectUtil.isNotNull(outEntrustOrder.getTransportModeName())) {
AjaxResult transportMode = systemServiceFeign.selectListByDictType("transportMode");
if (ObjectUtil.isNotNull(transportMode) && "200".equals(String.valueOf(transportMode.get("code")))) {
List<SysDictDataVo> transportModeList = JSON.parseArray(JSONObject.toJSONString(transportMode.get("data")), SysDictDataVo.class);
for (SysDictDataVo sysDictDataVo : transportModeList) {
if (outEntrustOrder.getTransportModeName().equals(sysDictDataVo.getDictLabel())) {
outEntrustOrder.setTransportModeCode(sysDictDataVo.getDictValue());
break;
}
}
}
}
if (ObjectUtil.isNotNull(outEntrustOrder.getSupervisionModeName())) {
AjaxResult supervisionMode = systemServiceFeign.selectListByDictType("controlType");
if (ObjectUtil.isNotNull(supervisionMode) && "200".equals(String.valueOf(supervisionMode.get("code")))) {
List<SysDictDataVo> supervisionModeList = JSON.parseArray(JSONObject.toJSONString(supervisionMode.get("data")), SysDictDataVo.class);
for (SysDictDataVo sysDictDataVo : supervisionModeList) {
if (outEntrustOrder.getSupervisionModeName().equals(sysDictDataVo.getDictLabel())) {
outEntrustOrder.setSupervisionModeCode(sysDictDataVo.getDictValue());
break;
}
}
}
}
}
/**
* 导入出库单:解析出的货主用户是否属于当前登录用户同一组织(organizationId)。
* 仅当登录用户带有 organizationId 时启用过滤;否则保持与旧行为一致。
*/
private boolean shipperMatchesImporterOrganization(UserPo userPo, Long loginOrganizationId) {
if (loginOrganizationId == null) {
return true;
}
if (userPo == null) {
return false;
}
Long oid = userPo.getOrganizationId();
return oid != null && loginOrganizationId.equals(oid);
}
/**
* 按客户名称或数字ID解析货主用户,逻辑与WMS出库管理导入一致:getInfoByName → 货主列表兜底,均做 organizationId 校验。
*/
private UserPo resolveCustomerShipperUserPoForImport(String customerName) {
if (StringUtils.isEmpty(customerName)) {
return null;
}
Long loginOrganizationId = null;
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser != null && loginUser.getUserPo() != null) {
loginOrganizationId = loginUser.getUserPo().getOrganizationId();
}
String shipperName = customerName.trim();
if (StringUtils.isEmpty(shipperName)) {
return null;
}
// 纯数字:按用户ID查询
if (shipperName.matches("\\d+")) {
try {
Long possibleId = Long.valueOf(shipperName);
AjaxResult ajaxResult = userServiceFeign.getInfo(possibleId);
if ("200".equals(String.valueOf(ajaxResult.get("code"))) && ajaxResult.get("data") != null) {
UserPo userPo = JSON.parseObject(JSONObject.toJSONString(ajaxResult.get("data")), UserPo.class);
if (userPo != null && shipperMatchesImporterOrganization(userPo, loginOrganizationId)) {
return userPo;
}
}
} catch (Exception e) {
// ignore
}
return null;
}
// 按名称查询(用户中心)
try {
AjaxResult ajaxResult = userServiceFeign.getInfoByName(shipperName);
if ("200".equals(String.valueOf(ajaxResult.get("code"))) && ajaxResult.get("data") != null) {
UserPo userPo = JSON.parseObject(JSONObject.toJSONString(ajaxResult.get("data")), UserPo.class);
if (userPo != null && shipperMatchesImporterOrganization(userPo, loginOrganizationId)) {
return userPo;
}
}
} catch (Exception e) {
// 继续货主列表兜底
}
// 货主列表兜底(与WMS出库导入一致)
try {
AjaxResult shipperListResult = userServiceFeign.userShipperListAll();
if ("200".equals(String.valueOf(shipperListResult.get("code"))) && shipperListResult.get("data") != null) {
Object data = shipperListResult.get("data");
List<Map<String, Object>> shipperList = null;
if (data instanceof List) {
List<Object> rawList = (List<Object>) data;
shipperList = new ArrayList<>();
for (Object item : rawList) {
if (item instanceof Map) {
Map<String, Object> mapItem = (Map<String, Object>) item;
shipperList.add(mapItem);
} else {
Map<String, Object> mapItem = JSON.parseObject(JSONObject.toJSONString(item), new TypeReference<Map<String, Object>>() {
});
shipperList.add(mapItem);
}
}
} else {
String jsonStr = JSONObject.toJSONString(data);
shipperList = JSON.parseObject(jsonStr, new TypeReference<List<Map<String, Object>>>() {
});
}
if (shipperList != null && !shipperList.isEmpty()) {
for (Map<String, Object> shipper : shipperList) {
Object userId = shipper.get("userId");
Object userName = shipper.get("userName");
if (userName == null) {
userName = shipper.get("name");
}
if (userName == null) {
userName = shipper.get("shipperName");
}
boolean matched = false;
if (StringUtils.isNotEmpty(shipperName) && userName != null) {
String normalizedUserName = String.valueOf(userName).trim();
if (shipperName.equals(normalizedUserName) || normalizedUserName.contains(shipperName)) {
matched = true;
}
}
if (matched && userId != null) {
Long candidateId = Long.valueOf(String.valueOf(userId));
AjaxResult infoRes = userServiceFeign.getInfo(candidateId);
if ("200".equals(String.valueOf(infoRes.get("code"))) && infoRes.get("data") != null) {
UserPo up = JSON.parseObject(JSONObject.toJSONString(infoRes.get("data")), UserPo.class);
if (shipperMatchesImporterOrganization(up, loginOrganizationId)) {
return up;
}
}
}
}
}
}
} catch (Exception e) {
// ignore
}
return null;
}
}
@@ -0,0 +1,201 @@
package com.mhd.oms.domain.outEntrustOrder.repository.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.mhd.oms.domain.outEntrustOrder.repository.todo.OutEntrustOrderDO;
import lombok.Getter;
import org.apache.poi.ss.usermodel.DateUtil; // Excel序列号转日期,POI已随EasyExcel引入
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 出仓委托单导入-表头信息监听器(处理前11行,与WMS出库管理导入模板一致)
*
* @author rcx
* @date 2026-09-14
*/
@Getter
public class OutEntrustHeaderListener extends AnalysisEventListener<Map<Integer, Object>> {
private final OutEntrustOrderDO outEntrustOrder = new OutEntrustOrderDO();
private int rowNum = 0;
/** 日期单元格有值但格式无法识别的收集器(类上已有@Getter,自动生成getDateParseErrors() */
private final List<String> dateParseErrors = new ArrayList<>();
private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Override
public void invoke(Map<Integer, Object> data, AnalysisContext context) {
// 将Map<Integer, Object>转换为Map<Integer, String>
Map<Integer, String> stringData = convertToStringMap(data);
// 将Map转换为List再转为数组
List<String> dataList = mapToList(stringData);
Object[] rowData = listToArray(dataList);
String stringValue1 = getStringValue(rowData[0]);
if (stringValue1 != null && !stringValue1.isEmpty()) {
if ("备注:".equals(stringValue1)) {
outEntrustOrder.setRemark(getStringValue(rowData[1]));
}
}
// 根据行号提取对应信息
switch (rowNum) {
case 4: // 第5行:to、仓库联系人、车型及车牌、委托编码
outEntrustOrder.setToReceiver(getStringValue(rowData[2]));
outEntrustOrder.setContactPerson(getStringValue(rowData[5]));
outEntrustOrder.setVehicleModelPlate(getStringValue(rowData[12]));
outEntrustOrder.setEntrustNo(getStringValue(rowData[16]));
break;
case 5: // 第6行:承运方、tel、柜号、司机签名
outEntrustOrder.setCarrier(getStringValue(rowData[2]));
outEntrustOrder.setTel(getStringValue(rowData[5]));
outEntrustOrder.setContainerNoName(getStringValue(rowData[12]));
outEntrustOrder.setDriverSign(getStringValue(rowData[16]));
break;
case 6: // 第7行:出仓日期、fax、运输方式、申报地关区
Date outboundDate = parseDateSmart(getStringValue(rowData[2]), "出仓日期");
if (outboundDate != null) {
outEntrustOrder.setOutboundDate(outboundDate);
}
outEntrustOrder.setFax(getStringValue(rowData[5]));
outEntrustOrder.setTransportModeName(getStringValue(rowData[12]));
outEntrustOrder.setDeclareCustoms(getStringValue(rowData[16]));
break;
case 7: // 第8行:生产商、客户名称、报关姓名及联系方式、完成时间
outEntrustOrder.setManufacturer(getStringValue(rowData[2]));
outEntrustOrder.setShipperName(getStringValue(rowData[5]));
outEntrustOrder.setCustomsDeclarerInfo(getStringValue(rowData[12]));
Date completeTime = parseDateSmart(getStringValue(rowData[16]), "完成时间");
if (completeTime != null) {
outEntrustOrder.setCompleteTime(completeTime);
}
break;
case 8: // 第9行:监管方式、送货地址、海关是否查验货物、进出境关别
outEntrustOrder.setSupervisionModeName(getStringValue(rowData[2]));
outEntrustOrder.setDeliveryAddr(getStringValue(rowData[5]));
String stringValue = getStringValue(rowData[12]);
outEntrustOrder.setCustomsInspectionFlag(stringValue);
outEntrustOrder.setEntryExitCustoms(getStringValue(rowData[16]));
break;
case 9: // 第10行:下单日期、起运国
Date orderDate = parseDateSmart(getStringValue(rowData[2]), "下单日期");
if (orderDate != null) {
outEntrustOrder.setOrderDate(orderDate);
}
outEntrustOrder.setDepartureArrivalCountry(getStringValue(rowData[5]));
break;
}
rowNum++;
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 解析完成后的处理
}
/**
* 将List转换为数组
*/
private Object[] listToArray(List<String> list) {
// 创建一个足够大的数组来容纳最多30列数据
Object[] array = new Object[30];
for (int i = 0; i < 30 && i < list.size(); i++) {
array[i] = list.get(i);
}
return array;
}
/**
* 将Object的Map转换为String的Map
*/
private Map<Integer, String> convertToStringMap(Map<Integer, Object> dataMap) {
if (dataMap == null) {
return new HashMap<>();
}
Map<Integer, String> stringMap = new HashMap<>();
for (Map.Entry<Integer, Object> entry : dataMap.entrySet()) {
Object value = entry.getValue();
String stringValue = value != null ? value.toString() : "";
stringMap.put(entry.getKey(), stringValue);
}
return stringMap;
}
/**
* 将Map转换为List
*/
private List<String> mapToList(Map<Integer, String> map) {
if (map == null) {
return new ArrayList<>();
}
// 找到最大索引以确定列表大小
int maxSize = map.keySet().stream().mapToInt(Integer::intValue).max().orElse(-1) + 1;
maxSize = Math.max(maxSize, 30); // 至少30个位置
List<String> list = new ArrayList<>(Collections.nCopies(maxSize, ""));
for (Map.Entry<Integer, String> entry : map.entrySet()) {
if (entry.getKey() >= 0 && entry.getKey() < maxSize) {
String value = entry.getValue();
list.set(entry.getKey(), value != null ? value : "");
}
}
return list;
}
/**
* 转换为字符串,处理null值
*/
private String getStringValue(Object value) {
return value == null ? "" : value.toString().trim();
}
/**
* 智能解析日期:兼容 Excel 日期单元格序列号 + 常见文本格式。
* 解析失败记入 dateParseErrors,由解析服务汇总返回给用户,不再静默丢失。
* @param raw 单元格原始字符串
* @param fieldLabel 字段中文名(用于错误提示)
*/
private Date parseDateSmart(String raw, String fieldLabel) {
String s = raw == null ? "" : raw.trim();
if (s.isEmpty()) {
return null; // 单元格为空:保持原行为,不算错误
}
// 1) Excel 日期序列号:NoConvertConverter 会把真日期单元格转成纯数字字符串(如 46234 = 2026-08-20
// 区间 20000~60000 对应 1954~2064 年,避免把普通数字误判成日期
if (s.matches("^\\d{1,5}(\\.\\d+)?$")) {
try {
double serial = Double.parseDouble(s);
if (serial >= 20000 && serial <= 60000) {
return DateUtil.getJavaDate(serial);
}
} catch (NumberFormatException ignore) {
}
}
// 2) 常见文本格式逐个尝试
String[] patterns = {
"yyyy-MM-dd", "yyyy/M/d", "yyyy.M.d", "yyyy年M月d日",
"yyyy-MM-dd HH:mm:ss", "yyyy/M/d H:mm:ss", "yyyy-MM-dd H:mm"
};
for (String p : patterns) {
try {
SimpleDateFormat sdf = new SimpleDateFormat(p);
sdf.setLenient(false);
return sdf.parse(s);
} catch (ParseException ignore) {
}
}
dateParseErrors.add(fieldLabel + "" + s + "」格式无法识别,请改为日期格式或 2026-08-20 / 2026/8/20 / 2026年8月20日 等格式");
return null;
}
}
@@ -0,0 +1,282 @@
package com.mhd.oms.domain.outEntrustOrder.repository.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.mhd.oms.domain.outEntrustOrder.repository.todo.OutEntrustOrderDetailDO;
import lombok.Getter;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 出仓委托单导入-商品明细监听器(从第12行开始,与WMS出库管理导入模板一致)
*
* @author rcx
* @date 2026-09-14
*/
@Getter
public class OutEntrustItemListener extends AnalysisEventListener<Map<Integer, Object>> {
private final List<OutEntrustOrderDetailDO> itemList = new ArrayList<>();
@Override
public void invoke(Map<Integer, Object> data, AnalysisContext context) {
// 将Map<Integer, Object>转换为Map<Integer, String>
Map<Integer, String> stringData = convertToStringMap(data);
// 将Map转换为List再转为数组
List<String> dataList = mapToList(stringData);
Object[] rowData = listToArray(dataList);
// 提取序号(第一列),过滤空行和标题行
String serialNoStr = getStringValue(rowData[0]);
String commodityName = getStringValue(rowData[1]);
String materialNo = getStringValue(rowData[2]);
if (commodityName.isEmpty() && materialNo.isEmpty()) {
return;
}
if (serialNoStr.isEmpty() || "序号".equals(serialNoStr) || !isSequenceNumber(serialNoStr)) {
return;
}
OutEntrustOrderDetailDO item = new OutEntrustOrderDetailDO();
// 填充商品明细数据
//商品名称 料号 规格型号/ITEM 商品SKU码 Invoice No. 申报数量 申报单位 升 箱数 件数 尺寸 出库数量 "总净重
//(KG)" 总毛重 (KG) "总体积
//(CBM)" 总面积(SQM) Batch Ref NO's Sheet Ref NO's 原产国 箱号/卡板号 总价 币制 备注
item.setCommodityName(getStringValue(rowData[1]));
item.setMaterialNo(getStringValue(rowData[2]));
item.setSpecificationModel(getStringValue(rowData[3]));
item.setSkucode(getStringValue(rowData[4]));
item.setInvoiceNo(getStringValue(rowData[5]));
item.setDeclareQuantity(parseBigDecimal(rowData[6]));
item.setDeclareUnit(getStringValue(rowData[7]));
item.setLiter(normalizeNumeric(getStringValue(rowData[8])));
item.setCaseCount(parseBigDecimal(rowData[9]));
item.setPieceCount(parseBigDecimal(rowData[10]));
item.setSize(getStringValue(rowData[11]));
item.setOutboundQuantity(parseBigDecimal(rowData[12]));
item.setTotalNetWeight(parseBigDecimal(rowData[13]));
item.setTotalGrossWeight(parseBigDecimal(rowData[14]));
item.setTotalVolume(parseBigDecimal(rowData[15]));
item.setTotalArea(parseBigDecimal(rowData[16]));
item.setBatchRefNo(getStringValue(rowData[17]));
item.setSheetRefNo(getStringValue(rowData[18]));
item.setCountryOfOrigin(getStringValue(rowData[19]));
item.setBoxPalletNo(getStringValue(rowData[20]));
item.setTotalAmount(parseBigDecimal(rowData[21]));
item.setCurrency(getStringValue(rowData[22]));
item.setRemark(getStringValue(rowData[23]));
itemList.add(item);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 解析完成后的处理
}
/**
* 将List转换为数组
*/
private Object[] listToArray(List<String> list) {
// 创建一个足够大的数组来容纳最多30列数据
Object[] array = new Object[30];
for (int i = 0; i < 30 && i < list.size(); i++) {
array[i] = list.get(i);
}
return array;
}
/**
* 将Object的Map转换为String的Map
*/
private Map<Integer, String> convertToStringMap(Map<Integer, Object> dataMap) {
if (dataMap == null) {
return new HashMap<>();
}
Map<Integer, String> stringMap = new HashMap<>();
for (Map.Entry<Integer, Object> entry : dataMap.entrySet()) {
Object value = entry.getValue();
String stringValue = convertToString(value);
stringMap.put(entry.getKey(), stringValue);
}
return stringMap;
}
/**
* 将Object转换为String,处理BigDecimal等数字类型,去除小数点后的零
*/
private String convertToString(Object value) {
if (value == null) {
return "";
}
// 如果是BigDecimal类型,需要特殊处理,去除小数点后的零
if (value instanceof BigDecimal) {
BigDecimal bd = (BigDecimal) value;
// 如果小数部分为0,转换为整数形式
if (bd.scale() > 0 && bd.stripTrailingZeros().scale() <= 0) {
return String.valueOf(bd.longValue());
}
// 否则去除尾部零
return bd.stripTrailingZeros().toPlainString();
}
// 如果是Double或Float类型,也需要处理
if (value instanceof Double) {
Double d = (Double) value;
if (d == d.longValue()) {
return String.valueOf(d.longValue());
}
return d.toString();
}
if (value instanceof Float) {
Float f = (Float) value;
if (f == f.longValue()) {
return String.valueOf(f.longValue());
}
return f.toString();
}
if (value instanceof String) {
String str = value.toString().trim();
if (str.isEmpty()) {
return "";
}
return str;
}
return value.toString().trim();
}
/**
* 将Map转换为List
*/
private List<String> mapToList(Map<Integer, String> map) {
if (map == null) {
return new ArrayList<>();
}
// 找到最大索引以确定列表大小
int maxSize = map.keySet().stream().mapToInt(Integer::intValue).max().orElse(-1) + 1;
maxSize = Math.max(maxSize, 30); // 至少30个位置
List<String> list = new ArrayList<>(Collections.nCopies(maxSize, ""));
for (Map.Entry<Integer, String> entry : map.entrySet()) {
if (entry.getKey() >= 0 && entry.getKey() < maxSize) {
String value = entry.getValue();
list.set(entry.getKey(), value != null ? value : "");
}
}
return list;
}
private String getStringValue(Object value) {
if (value == null) {
return "";
}
// 如果是BigDecimal类型,需要特殊处理,去除小数点后的零
if (value instanceof BigDecimal) {
BigDecimal bd = (BigDecimal) value;
// 如果小数部分为0,转换为整数形式
if (bd.scale() > 0 && bd.stripTrailingZeros().scale() <= 0) {
return String.valueOf(bd.longValue());
}
// 否则去除尾部零
return bd.stripTrailingZeros().toPlainString();
}
// 如果是Double或Float类型,也需要处理
if (value instanceof Double) {
Double d = (Double) value;
if (d == d.longValue()) {
return String.valueOf(d.longValue());
}
return d.toString();
}
if (value instanceof Float) {
Float f = (Float) value;
if (f == f.longValue()) {
return String.valueOf(f.longValue());
}
return f.toString();
}
if (value instanceof String) {
String str = value.toString().trim();
if (str.isEmpty()) {
return "";
}
// 尝试解析为数字,如果是整数形式的小数(如 "123.0"),转换为整数
try {
BigDecimal bd = new BigDecimal(str);
// 如果小数部分为0,转换为整数形式
if (bd.scale() > 0 && bd.stripTrailingZeros().scale() <= 0) {
return String.valueOf(bd.longValue());
}
// 否则去除尾部零
return bd.stripTrailingZeros().toPlainString();
} catch (NumberFormatException e) {
// 不是数字字符串,直接返回
return str;
}
}
return value.toString().trim();
}
/**
* 解析BigDecimal,失败返回0
*/
private BigDecimal parseBigDecimal(Object value) {
try {
String strValue = getStringValue(value);
if (strValue.isEmpty()) {
return BigDecimal.ZERO;
}
return new BigDecimal(strValue);
} catch (Exception e) {
return BigDecimal.ZERO;
}
}
/**
* 升列归一化:去千分位逗号、去空白;空值统一存 null(避免前端合计时对空串求和);
* 纯数字归一为无多余零的字符串;非数字内容原样保留。
*/
private String normalizeNumeric(String raw) {
if (raw == null) {
return null;
}
String s = raw.trim().replace(",", "");
if (s.isEmpty()) {
return null;
}
try {
return new BigDecimal(s).stripTrailingZeros().toPlainString();
} catch (NumberFormatException e) {
return s;
}
}
public static boolean isSequenceNumber(String str) {
if (str == null || str.trim().isEmpty()) {
return false;
}
str = str.trim();
return str.matches("^[1-9]\\d*$") || str.matches("^[1-9]\\d*\\.\\d+$") || str.matches("^0\\.\\d+$");
}
}
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mhd.oms.domain.outEntrustOrder.entity.OutEntrustOrder;
import com.mhd.oms.domain.outEntrustOrder.repository.po.OutEntrustOrderPO;
import com.mhd.oms.domain.outEntrustOrder.repository.todo.OutEntrustOrderDO;
import com.mhd.oms.domain.reservationStockInOrder.repository.po.MaterialBaseInfoPO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@@ -25,4 +26,25 @@ public interface OutEntrustOrderMapper extends BaseMapper<OutEntrustOrder> {
* 根据编号集合查询出仓委托单列表
*/
List<OutEntrustOrderPO> getInfoByIds(@Param("idList") List<Long> idList);
/**
* 导入用:按料号查询货主下的物料基础信息(跨库WMS物料表)
*/
List<MaterialBaseInfoPO> getMaterialByMaterialCode(@Param("materialCode") String materialCode,
@Param("shipperId") Long shipperId,
@Param("topOrganizationId") Long topOrganizationId);
/**
* 导入用:按SKU码(条形码)查询货主下的物料基础信息(跨库WMS物料表)
*/
List<MaterialBaseInfoPO> getMaterialByBarCode(@Param("barCode") String barCode,
@Param("shipperId") Long shipperId,
@Param("topOrganizationId") Long topOrganizationId);
/**
* 导入用:按物料名称查询货主下的物料基础信息(跨库WMS物料表)
*/
List<MaterialBaseInfoPO> getMaterialByMaterialName(@Param("materialName") String materialName,
@Param("shipperId") Long shipperId,
@Param("topOrganizationId") Long topOrganizationId);
}
@@ -10,6 +10,7 @@ import com.mhd.common.core.domain.po.UserPo;
import com.mhd.common.core.enums.DictCode;
import com.mhd.common.core.exception.ServiceException;
import com.mhd.common.core.utils.StringUtils;
import com.mhd.common.core.utils.poi.ExcelUtil;
import com.mhd.common.core.web.domain.AjaxResult;
import com.mhd.common.security.utils.SecurityUtils;
import com.mhd.oms.domain.erpCountry.entity.ErpCountry;
@@ -18,6 +19,7 @@ import com.mhd.oms.domain.gwLog.repository.mapper.GwLogMapper;
import com.mhd.oms.domain.reservationInMaterialDetail.entity.ReservationInMaterialDetail;
import com.mhd.oms.domain.reservationInMaterialDetail.repository.mapper.ReservationInMaterialDetailMapper;
import com.mhd.oms.domain.reservationInMaterialDetail.repository.po.ReservationInMaterialDetailPO;
import com.mhd.oms.domain.reservationInMaterialDetail.repository.todo.ReservationInMaterialDetailDO;
import com.mhd.oms.domain.reservationMaterialInventory.repository.facade.IReservationMaterialInventoryService;
import com.mhd.oms.domain.reservationMaterialInventory.repository.mapper.ReservationMaterialInventoryMapper;
import com.mhd.oms.domain.reservationStockInOrder.entity.ReservationStockInOrder;
@@ -26,6 +28,7 @@ import com.mhd.oms.domain.reservationStockInOrder.repository.po.MaterialBaseInfo
import com.mhd.oms.domain.reservationStockInOrder.repository.po.ReservationStockInOrderPO;
import com.mhd.oms.domain.reservationStockInOrder.repository.todo.ReservationStockInOrderDO;
import com.mhd.oms.domain.reservationStockInOrder.repository.todo.ReservationStockInOrderDTO;
import com.mhd.oms.domain.reservationStockInOrder.repository.todo.ReservationStockInOrderImportRowDTO;
import com.mhd.oms.domain.reservationStockInOrder.repository.vo.GwInDetail;
import com.mhd.oms.domain.reservationStockInOrder.repository.vo.GwStockInOrder;
import com.mhd.oms.interfaces.dto.erpCountry.ErpCountryDTO;
@@ -47,6 +50,7 @@ import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
@@ -1590,6 +1594,635 @@ public class ReservationStockInOrderApplicationService {
// }
// }
/**
* 导入入仓委托单(模板与WMS入库管理导入一致:第1-12行表头,第13行列名,第14行起明细)
* 以【委托编号NO】作为分组键,同一委托编号的多条明细归为一张入库单
*/
public Boolean importData(MultipartFile file, Long warehouseId, String warehouseCode, String warehouseName) {
if (file == null || file.isEmpty()) {
throw new ServiceException("导入文件不能为空");
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser == null) {
throw new ServiceException("登录已失效,请重新登录");
}
Long topOrganizationId = loginUser.getUserPo() != null ? loginUser.getUserPo().getTopOrganizationId() : null;
try (InputStream is = file.getInputStream()) {
// 明细表的列名在第13行(索引12),数据从第14行开始
int detailTitleRowIndex = 12;
// 1. 读取表头信息(第1-12行),按标签扫描提取字段
ReservationStockInOrderImportRowDTO headerInfo = new ReservationStockInOrderImportRowDTO();
try (InputStream isForHeader = file.getInputStream()) {
Workbook wb = WorkbookFactory.create(isForHeader);
Sheet sheet = wb.getSheetAt(0);
fillHeaderInfoByLabels(sheet, headerInfo);
} catch (Exception e) {
log.error("读取Excel表头信息失败", e);
// 不抛出异常,继续尝试从明细行读取
}
log.info("表头信息读取完成 - 委托编号NO: {}, 客户名称: {}, To: {}, 联系人: {}, 承运商: {}, 入仓日期: {}, 完成时间: {}, 下单日期: {}",
headerInfo.getConsignmentNo(), headerInfo.getShipperName(), headerInfo.getTo(),
headerInfo.getContactPerson(), headerInfo.getCarrier(), headerInfo.getWarehouseDate(),
headerInfo.getCompletionTime(), headerInfo.getOrderDate());
// 2. 读取明细数据(ExcelUtil从第13行列名开始)
ExcelUtil<ReservationStockInOrderImportRowDTO> util = new ExcelUtil<>(ReservationStockInOrderImportRowDTO.class);
List<ReservationStockInOrderImportRowDTO> rows = util.importExcel(is, detailTitleRowIndex);
if (rows == null || rows.isEmpty()) {
throw new ServiceException("导入数据为空。请检查Excel第13行是否为明细表列名(包含'商品名称'列),第14行开始是否为数据行");
}
log.info("成功读取 {} 行明细数据", rows.size());
// 3. 将表头信息应用到每一行(明细行值为空时使用表头值)
applyHeaderToRows(headerInfo, rows);
// 4. 过滤掉商品名称为空的空行
List<ReservationStockInOrderImportRowDTO> validRows = rows.stream()
.filter(r -> r != null && StringUtils.isNotBlank(r.getMaterialName()))
.collect(Collectors.toList());
if (validRows.isEmpty()) {
throw new ServiceException("导入数据为空(可能全为空行)。共读取 " + rows.size()
+ " 行数据,但商品名称都为空。请检查Excel第13行是否为明细表列名(包含'商品名称'列),第14行开始是否为数据行");
}
// 5. 仓库必填校验(与出库单导入保持一致,仓库由导入弹窗选择传入)
if (warehouseId == null) {
throw new ServiceException("导入失败:仓库ID不能为空");
}
if (StringUtils.isBlank(warehouseCode)) {
throw new ServiceException("导入失败:仓库编码不能为空");
}
if (StringUtils.isBlank(warehouseName)) {
throw new ServiceException("导入失败:仓库名称不能为空");
}
// 6. 以委托编号NO分组;若为空则归到同一组
Map<String, List<ReservationStockInOrderImportRowDTO>> groupMap = validRows.stream()
.collect(Collectors.groupingBy(r -> StringUtils.isBlank(r.getConsignmentNo()) ? "__EMPTY_CONSIGNMENT__" : r.getConsignmentNo()));
List<ReservationStockInOrderDO> orderList = new ArrayList<>();
for (Map.Entry<String, List<ReservationStockInOrderImportRowDTO>> entry : groupMap.entrySet()) {
ReservationStockInOrderDO order = buildStockInOrderFromImport(
entry.getValue(), headerInfo, loginUser, topOrganizationId,
warehouseId, warehouseCode, warehouseName);
if (order != null) {
orderList.add(order);
}
}
// 如果所有订单都被跳过(orderList为空),抛出异常提示用户
if (orderList.isEmpty()) {
throw new ServiceException("导入失败:所有入库单的明细行都因数据不完整(数量为空或物料基础信息缺失)被跳过,请检查Excel数据");
}
return stockInOrderDomainService.batchInsert(orderList);
} catch (ServiceException e) {
throw e;
} catch (Exception e) {
log.error("导入入库单失败", e);
throw new ServiceException("导入入库单失败:" + e.getMessage());
}
}
/**
* 单个委托编号分组的入库单组装(解析+校验,不落库)
* 明细行数量为空/容器类型行跳过;料号必填且必须在当前货主下存在,规格型号/SKU码与物料档案不一致直接报错
*
* @return 组装完成的入库单;该组明细全部被跳过时返回null
*/
private ReservationStockInOrderDO buildStockInOrderFromImport(List<ReservationStockInOrderImportRowDTO> groupRows,
ReservationStockInOrderImportRowDTO headerInfo,
LoginUser loginUser, Long topOrganizationId,
Long warehouseId, String warehouseCode, String warehouseName) {
ReservationStockInOrderImportRowDTO h = groupRows.get(0);
// 客户名称优先使用表头,表头为空时使用明细行
String customerName = StringUtils.isNotBlank(headerInfo.getShipperName()) ? headerInfo.getShipperName() : h.getShipperName();
if (StringUtils.isBlank(customerName)) {
throw new ServiceException("导入失败:客户名称不能为空(委托编号NO=" + h.getConsignmentNo()
+ ")。请确保Excel表头(第1-12行)中包含'客户名称'字段");
}
ReservationStockInOrderDO order = new ReservationStockInOrderDO();
// 组织/审计字段
if (loginUser.getUserPo() != null) {
order.setOrganizationId(loginUser.getUserPo().getOrganizationId());
order.setOrganizationName(loginUser.getUserPo().getOrganizationName());
order.setTopOrganizationId(loginUser.getUserPo().getTopOrganizationId());
}
order.setCreateBy(loginUser.getUserid());
order.setCreateByName(resolveUserRealName(loginUser));
order.setCreateTime(new Date());
// 仓库(导入弹窗选择传入)
order.setWarehouseId(warehouseId);
order.setWarehouseCode(warehouseCode);
order.setWarehouseName(warehouseName);
// 货主信息:名称/编号解析货主ID
String shipperCode = StringUtils.isNotBlank(headerInfo.getShipperCode()) ? headerInfo.getShipperCode() : h.getShipperCode();
Long shipperId = getShipperIdByNameOrCode(customerName, shipperCode);
order.setShipperId(shipperId);
if (StringUtils.isNotBlank(customerName)) {
order.setShipperName(customerName);
}
if (StringUtils.isNotBlank(shipperCode)) {
order.setShipperCode(shipperCode);
}
order.setConsignmentNo(h.getConsignmentNo());
order.setRemark(h.getRemark());
// 导入入库单时,如果入库单类型为空,默认设置为普通入库
order.setOrderTypeCode("pu_tong_ru_ku");
order.setOrderTypeName("普通入库单");
// 订单来源:数据导入(与入库业务单多页签导入保持一致)
order.setReservationOrderSource("数据导入");
// 报关表头字段(优先使用表头字段,表头为空则使用明细行字段)
order.setTo(firstNotBlank(headerInfo.getTo(), h.getTo()));
order.setContactPerson(firstNotBlank(headerInfo.getContactPerson(), h.getContactPerson()));
order.setVehicleInfo(firstNotBlank(headerInfo.getVehicleInfo(), h.getVehicleInfo()));
order.setTel(firstNotBlank(headerInfo.getTel(), h.getTel()));
order.setDriverSignature(firstNotBlank(headerInfo.getDriverSignature(), h.getDriverSignature()));
order.setFax(firstNotBlank(headerInfo.getFax(), h.getFax()));
order.setDeclarationArea(firstNotBlank(headerInfo.getDeclarationArea(), h.getDeclarationArea()));
order.setManufacturer(firstNotBlank(headerInfo.getManufacturer(), h.getManufacturer()));
order.setCustomsBrokerInfo(firstNotBlank(headerInfo.getCustomsBrokerInfo(), h.getCustomsBrokerInfo()));
order.setDeliveryAddress(firstNotBlank(headerInfo.getDeliveryAddress(), h.getDeliveryAddress()));
order.setEntryExitCustoms(firstNotBlank(headerInfo.getEntryExitCustoms(), h.getEntryExitCustoms()));
order.setOriginDestinationCountry(firstNotBlank(headerInfo.getOriginDestinationCountry(), h.getOriginDestinationCountry()));
order.setCarrier(firstNotBlank(headerInfo.getCarrier(), h.getCarrier()));
order.setContainerNo(firstNotBlank(headerInfo.getContainerNo(), h.getContainerNo()));
order.setTransportMethod(firstNotBlank(headerInfo.getTransportMethod(), h.getTransportMethod()));
order.setSupervisionMethod(firstNotBlank(headerInfo.getSupervisionMethod(), h.getSupervisionMethod()));
// 报关字段默认值(优先使用表头字段)
Integer customsInspection = headerInfo.getCustomsInspection() != null ? headerInfo.getCustomsInspection() : h.getCustomsInspection();
order.setCustomsInspection(customsInspection == null ? 0 : customsInspection); // 默认:否
Integer needCustomsDeclaration = headerInfo.getNeedCustomsDeclaration() != null ? headerInfo.getNeedCustomsDeclaration() : h.getNeedCustomsDeclaration();
order.setNeedCustomsDeclaration(needCustomsDeclaration == null ? 1 : needCustomsDeclaration); // 默认:是
Integer needTransport = headerInfo.getNeedTransport() != null ? headerInfo.getNeedTransport() : h.getNeedTransport();
order.setNeedTransport(needTransport == null ? 0 : needTransport); // 默认:否
// 日期字段(优先使用表头字段)
String warehouseDateStr = firstNotBlank(headerInfo.getWarehouseDate(), h.getWarehouseDate());
if (StringUtils.isNotBlank(warehouseDateStr)) {
Date warehouseDate = parseDateString(warehouseDateStr);
if (warehouseDate != null) {
order.setWarehouseDate(warehouseDate);
}
}
String completionTimeStr = firstNotBlank(headerInfo.getCompletionTime(), h.getCompletionTime());
if (StringUtils.isNotBlank(completionTimeStr)) {
Date completionTime = parseDateString(completionTimeStr);
if (completionTime != null) {
order.setCompletionTime(completionTime);
}
}
String orderDateStr = firstNotBlank(headerInfo.getOrderDate(), h.getOrderDate());
if (StringUtils.isNotBlank(orderDateStr)) {
Date orderDate = parseDateString(orderDateStr);
if (orderDate != null) {
order.setOrderDate(orderDate);
}
}
// 明细
List<ReservationInMaterialDetailDO> details = new ArrayList<>();
for (ReservationStockInOrderImportRowDTO r : groupRows) {
// 先检查数量(仓库数量优先,入库数量兜底),数量为空直接跳过该明细行
BigDecimal quantity = r.getWarehouseQuantity();
if (quantity == null) {
quantity = r.getInboundQuantity();
}
if (quantity == null) {
log.warn("跳过明细行:仓库数量或入库数量不能为空 - 委托编号NO: [{}], 商品名称: [{}], 料号: [{}]",
h.getConsignmentNo(), r.getMaterialName(), r.getMaterialCode());
continue;
}
// 检查是否为容器类型等非物料信息,如果是则跳过
String materialName = r.getMaterialName();
if (StringUtils.isNotBlank(materialName)) {
String lowerName = materialName.trim();
if (lowerName.contains("尺柜") || lowerName.contains("尺集装箱")
|| lowerName.contains("Container") || lowerName.contains("container")
|| (lowerName.contains("") && (lowerName.contains("20") || lowerName.contains("40") || lowerName.contains("45")))) {
log.info("跳过明细行:识别为容器类型信息,非实际物料 - 委托编号NO: [{}], 商品名称: [{}]",
h.getConsignmentNo(), materialName);
continue;
}
}
// 导入文件中必须填写料号,根据料号获取商品信息,必须是货主下有的才可以匹配
if (StringUtils.isBlank(r.getMaterialCode())) {
throw new ServiceException("导入失败:请在导入文件中填写正确的料号");
}
// 根据料号跨库查询物料基础信息(getByCode按组织过滤),查到后需在Java侧核对货主归属
MaterialBaseInfoPO materialBaseInfoPO = safeGetMaterialByCode(r.getMaterialCode(), loginUser);
if (materialBaseInfoPO != null && shipperId != null
&& (materialBaseInfoPO.getShipperId() == null || !materialBaseInfoPO.getShipperId().equals(shipperId))) {
// 查到但不属于该货主,等同于不存在,不允许导入
materialBaseInfoPO = null;
}
if (materialBaseInfoPO == null) {
// 如果提供了料号但查询不到(或不属于该货主),直接报错,不允许导入
throw new ServiceException(String.format("导入失败:该货主下不存在该料号(货主名称=%s,料号=%s)。请确保该料号已在物料管理中创建,且属于该货主",
order.getShipperName(), r.getMaterialCode()));
}
// 校验料号与规格型号/SKU码一致性(与WMS入库管理导入一致):任何一项不符直接报对应错误
String excelSpec = StringUtils.trimToEmpty(r.getSpecificationModel());
String dbSpec = StringUtils.trimToEmpty(materialBaseInfoPO.getSpecificationModel());
if (!excelSpec.equals(dbSpec)) {
throw new ServiceException(String.format("导入失败:料号[%s](商品名称:%s)规格型号与物料档案不一致(Excel:%s / 档案:%s",
r.getMaterialCode(), r.getMaterialName(),
excelSpec.isEmpty() ? "" : excelSpec, dbSpec.isEmpty() ? "" : dbSpec));
}
String excelSku = StringUtils.trimToEmpty(r.getSkuCode());
String dbSku = StringUtils.trimToEmpty(materialBaseInfoPO.getBarCode());
if (!excelSku.equals(dbSku)) {
throw new ServiceException(String.format("导入失败:料号[%s](商品名称:%s)SKU码与物料档案不一致(Excel:%s / 档案:%s",
r.getMaterialCode(), r.getMaterialName(),
excelSku.isEmpty() ? "" : excelSku, dbSku.isEmpty() ? "" : dbSku));
}
ReservationInMaterialDetailDO d = new ReservationInMaterialDetailDO();
d.setMaterialBaseInfoId(materialBaseInfoPO.getMaterialBaseInfoId());
// 从物料档案获取名称(与WMS入库导入一致)
d.setMaterialName(materialBaseInfoPO.getMaterialName());
d.setMaterialCode(r.getMaterialCode());
d.setQuantity(quantity);
d.setSpecificationModel(r.getSpecificationModel());
d.setSkuCode(r.getSkuCode());
d.setInvoiceNo(r.getInvoiceNo());
d.setDeclaredQuantity(r.getDeclaredQuantity());
d.setDeclarationUnit(r.getDeclarationUnit());
d.setLiter(r.getLiter());
d.setBoxCount(r.getBoxCount());
d.setPieceCount(r.getPieceCount());
d.setDimensions(r.getDimensions());
// 入库数量:优先使用入库数量,如果为空则使用仓库数量
BigDecimal inboundQuantity = r.getInboundQuantity();
if (inboundQuantity == null) {
inboundQuantity = r.getWarehouseQuantity();
}
d.setInboundQuantity(inboundQuantity);
// 单位:优先使用Excel中的"单位"字段,如果为空则使用"申报单位",如果还为空则从物料主数据获取
String unit = StringUtils.isNotBlank(r.getUnit()) ? r.getUnit() : null;
if (StringUtils.isBlank(unit) && StringUtils.isNotBlank(r.getDeclarationUnit())) {
unit = r.getDeclarationUnit();
}
if (StringUtils.isBlank(unit) && StringUtils.isNotBlank(materialBaseInfoPO.getUnitName())) {
unit = materialBaseInfoPO.getUnitName();
}
d.setUnit(unit);
// 总净重/总毛重/总体积/总面积:优先使用主字段,如果为空则使用备用字段
d.setTotalNetWeight(r.getTotalNetWeight() != null ? r.getTotalNetWeight() : r.getTotalNetWeight2());
d.setTotalGrossWeight(r.getTotalGrossWeight() != null ? r.getTotalGrossWeight() : r.getTotalGrossWeight2());
d.setTotalVolume(r.getTotalVolume() != null ? r.getTotalVolume() : r.getTotalVolume2());
d.setTotalArea(r.getTotalArea() != null ? r.getTotalArea() : r.getTotalArea2());
d.setBatchRefNo(r.getBatchRefNo());
d.setSheetRefNo(r.getSheetRefNo());
// 原产国:优先使用Excel中的数据,如果为空则从物料主数据获取
String originCountry = StringUtils.isNotBlank(r.getOriginCountry()) ? r.getOriginCountry() : null;
if (StringUtils.isBlank(originCountry) && StringUtils.isNotBlank(materialBaseInfoPO.getOriginCountry())) {
originCountry = materialBaseInfoPO.getOriginCountry();
}
d.setOriginCountry(originCountry);
d.setBoxPalletNo(r.getBoxPalletNo());
d.setTotalPrice(r.getTotalPrice());
// 币制:优先使用Excel中的数据,如果为空则从物料主数据获取
String currency = StringUtils.isNotBlank(r.getCurrency()) ? r.getCurrency() : null;
if (StringUtils.isBlank(currency) && StringUtils.isNotBlank(materialBaseInfoPO.getCurrency())) {
currency = materialBaseInfoPO.getCurrency();
}
d.setCurrency(currency);
d.setRemark(r.getDetailRemark());
details.add(d);
}
// 如果该订单的所有明细行都被跳过(details为空),跳过该订单
if (details.isEmpty()) {
log.warn("跳过入库单:委托编号NO [{}] 的所有明细行都因数据不完整被跳过,该入库单将不会被导入", h.getConsignmentNo());
return null;
}
order.setMaterialDetailList(details);
// 导入入库单时,计划数量取所有明细行的入库数量之和
BigDecimal totalInboundQuantity = details.stream()
.map(detail -> detail.getInboundQuantity() != null ? detail.getInboundQuantity() : BigDecimal.ZERO)
.reduce(BigDecimal.ZERO, BigDecimal::add);
order.setQuantity(totalInboundQuantity);
// 导入时补全货主/供应商/数据字典信息(与手动新增链路一致)
try {
shipperParam(order);
} catch (Exception e) {
log.error("导入入库单时设置货主信息失败,委托编号NO:{},货主名称:{},错误:{}",
h.getConsignmentNo(), order.getShipperName(), e.getMessage());
throw new ServiceException("导入失败:设置货主信息失败(委托编号NO=" + h.getConsignmentNo()
+ ",货主名称=" + order.getShipperName() + ")。" + e.getMessage());
}
supplierParam(order);
setDataDict(order);
return order;
}
/**
* 导入用:按料号跨库查询物料基础信息(查询结果需在Java侧核对货主归属)
*/
private MaterialBaseInfoPO safeGetMaterialByCode(String materialCode, LoginUser loginUser) {
try {
Long orgId = loginUser.getUserPo() != null ? loginUser.getUserPo().getOrganizationId() : null;
return reservationStockInOrderMapper.getByCode(materialCode, orgId);
} catch (Exception e) {
log.warn("按料号查询物料基础信息失败,料号: [{}], 错误: {}", materialCode, e.getMessage());
return null;
}
}
/**
* 将表头信息应用到每一行明细(明细行值为空时使用表头值;客户名称/货主编号表头有值时强制应用)
*/
private void applyHeaderToRows(ReservationStockInOrderImportRowDTO headerInfo, List<ReservationStockInOrderImportRowDTO> rows) {
for (ReservationStockInOrderImportRowDTO row : rows) {
if (row == null) {
continue;
}
// 应用表头中的委托编号NO
if (StringUtils.isBlank(row.getConsignmentNo()) && StringUtils.isNotBlank(headerInfo.getConsignmentNo())) {
row.setConsignmentNo(headerInfo.getConsignmentNo());
}
// 表头有客户名称/货主编号时,强制应用到所有明细行
if (StringUtils.isNotBlank(headerInfo.getShipperName())) {
row.setShipperName(headerInfo.getShipperName());
}
if (StringUtils.isNotBlank(headerInfo.getShipperCode())) {
row.setShipperCode(headerInfo.getShipperCode());
}
// 应用其他表头字段(如果明细行为空,则使用表头值)
row.setTo(firstNotBlank(row.getTo(), headerInfo.getTo()));
row.setContactPerson(firstNotBlank(row.getContactPerson(), headerInfo.getContactPerson()));
row.setVehicleInfo(firstNotBlank(row.getVehicleInfo(), headerInfo.getVehicleInfo()));
row.setCarrier(firstNotBlank(row.getCarrier(), headerInfo.getCarrier()));
row.setTel(firstNotBlank(row.getTel(), headerInfo.getTel()));
row.setDriverSignature(firstNotBlank(row.getDriverSignature(), headerInfo.getDriverSignature()));
row.setFax(firstNotBlank(row.getFax(), headerInfo.getFax()));
row.setDeclarationArea(firstNotBlank(row.getDeclarationArea(), headerInfo.getDeclarationArea()));
row.setManufacturer(firstNotBlank(row.getManufacturer(), headerInfo.getManufacturer()));
row.setCustomsBrokerInfo(firstNotBlank(row.getCustomsBrokerInfo(), headerInfo.getCustomsBrokerInfo()));
row.setDeliveryAddress(firstNotBlank(row.getDeliveryAddress(), headerInfo.getDeliveryAddress()));
row.setEntryExitCustoms(firstNotBlank(row.getEntryExitCustoms(), headerInfo.getEntryExitCustoms()));
row.setOriginDestinationCountry(firstNotBlank(row.getOriginDestinationCountry(), headerInfo.getOriginDestinationCountry()));
row.setContainerNo(firstNotBlank(row.getContainerNo(), headerInfo.getContainerNo()));
row.setTransportMethod(firstNotBlank(row.getTransportMethod(), headerInfo.getTransportMethod()));
row.setSupervisionMethod(firstNotBlank(row.getSupervisionMethod(), headerInfo.getSupervisionMethod()));
if (row.getCustomsInspection() == null) {
row.setCustomsInspection(headerInfo.getCustomsInspection());
}
if (row.getNeedCustomsDeclaration() == null) {
row.setNeedCustomsDeclaration(headerInfo.getNeedCustomsDeclaration());
}
if (row.getNeedTransport() == null) {
row.setNeedTransport(headerInfo.getNeedTransport());
}
row.setWarehouseDate(firstNotBlank(row.getWarehouseDate(), headerInfo.getWarehouseDate()));
row.setCompletionTime(firstNotBlank(row.getCompletionTime(), headerInfo.getCompletionTime()));
row.setOrderDate(firstNotBlank(row.getOrderDate(), headerInfo.getOrderDate()));
row.setRemark(firstNotBlank(row.getRemark(), headerInfo.getRemark()));
}
}
/**
* 扫描表头区域(第1-12行)按标签提取字段值:
* 值优先取标签同行右侧最近的合法单元格,其次取下一行同列,最后取标签文本冒号后的内容;
* 看起来仍是标签文本(含冒号或命中标签关键词)的值会被跳过
*/
private void fillHeaderInfoByLabels(Sheet sheet, ReservationStockInOrderImportRowDTO headerInfo) {
for (int rowIdx = 0; rowIdx < 12; rowIdx++) {
Row row = sheet.getRow(rowIdx);
if (row == null) {
continue;
}
for (int colIdx = 0; colIdx < row.getPhysicalNumberOfCells(); colIdx++) {
Cell labelCell = row.getCell(colIdx);
if (labelCell == null) {
continue;
}
String labelValue = getCellValueAsString(labelCell);
if (StringUtils.isBlank(labelValue)) {
continue;
}
// 标签文本冒号后的内容(兜底值)
String extractedFromSameCell = null;
if (labelValue.contains(":")) {
String[] parts = labelValue.split(":", 2);
if (parts.length > 1) {
extractedFromSameCell = parts[1].trim();
}
}
String value = findValueRightwards(row, colIdx);
if (StringUtils.isBlank(value)) {
value = findValueBelow(sheet, rowIdx, colIdx);
}
if (StringUtils.isBlank(value)) {
value = extractedFromSameCell;
}
if (StringUtils.isBlank(value)) {
continue;
}
dispatchHeaderField(labelValue, value, headerInfo);
}
}
}
/**
* 在同行右侧(最多10列)查找第一个合法值
*/
private String findValueRightwards(Row row, int colIdx) {
for (int searchColIdx = colIdx + 1; searchColIdx < colIdx + 10 && searchColIdx < row.getPhysicalNumberOfCells(); searchColIdx++) {
Cell valueCell = row.getCell(searchColIdx);
if (valueCell == null) {
continue;
}
String cellValue = getCellValueAsString(valueCell);
if (StringUtils.isNotBlank(cellValue)) {
return cellValue;
}
}
return null;
}
/**
* 在下一行同列查找合法值(模板变体:值写在标签下一行)
*/
private String findValueBelow(Sheet sheet, int rowIdx, int colIdx) {
Row nextRow = sheet.getRow(rowIdx + 1);
if (nextRow == null) {
return null;
}
Cell nextRowCell = nextRow.getCell(colIdx);
if (nextRowCell == null) {
return null;
}
String nextRowValue = getCellValueAsString(nextRowCell);
return StringUtils.isNotBlank(nextRowValue) ? nextRowValue : null;
}
/**
* 按标签关键词分发到表头字段
*/
private void dispatchHeaderField(String labelValue, String value, ReservationStockInOrderImportRowDTO headerInfo) {
// 备注:值内容任意,不做标签过滤
if (labelValue.contains("备注")) {
if (StringUtils.isBlank(headerInfo.getRemark())) {
headerInfo.setRemark(value);
}
return;
}
// 委托编号NO(支持"委托编号NO"、"委托编号"等格式,排除标签本身)
if (labelValue.contains("委托编号")) {
if (StringUtils.isBlank(headerInfo.getConsignmentNo())
&& !value.contains("委托编号") && !value.contains("NO")
&& !"委托编号NO".equals(value) && !"委托编号".equals(value)) {
headerInfo.setConsignmentNo(value);
}
return;
}
// 客户名称
if (labelValue.contains("客户名称")) {
if (StringUtils.isBlank(headerInfo.getShipperName())
&& !value.contains("客户") && !"客户名称".equals(value)) {
headerInfo.setShipperName(value);
}
return;
}
// To(需要精确匹配,避免误匹配)
if (isToLabel(labelValue)) {
if (StringUtils.isBlank(headerInfo.getTo()) && !isToLabel(value) && !looksLikeLabel(value)) {
headerInfo.setTo(value);
}
return;
}
// 值是标签文本(含冒号或命中标签关键词)时跳过
if (value.contains("") || value.contains(":") || looksLikeLabel(value)) {
return;
}
if (labelValue.contains("联系人") && StringUtils.isBlank(headerInfo.getContactPerson())) {
headerInfo.setContactPerson(value);
} else if ((labelValue.contains("车型") || labelValue.contains("车牌")) && StringUtils.isBlank(headerInfo.getVehicleInfo())) {
headerInfo.setVehicleInfo(value);
} else if ((labelValue.contains("承运方") || labelValue.contains("承运商")) && StringUtils.isBlank(headerInfo.getCarrier())) {
headerInfo.setCarrier(value);
} else if (labelValue.contains("Tel") && StringUtils.isBlank(headerInfo.getTel())) {
headerInfo.setTel(value);
} else if (labelValue.contains("司机签名") && StringUtils.isBlank(headerInfo.getDriverSignature())) {
headerInfo.setDriverSignature(value);
} else if (labelValue.contains("入仓日期") && StringUtils.isBlank(headerInfo.getWarehouseDate())) {
headerInfo.setWarehouseDate(value);
} else if (labelValue.contains("Fax") && StringUtils.isBlank(headerInfo.getFax())) {
headerInfo.setFax(value);
} else if (labelValue.contains("申报地关区") && StringUtils.isBlank(headerInfo.getDeclarationArea())) {
headerInfo.setDeclarationArea(value);
} else if (labelValue.contains("生产商") && StringUtils.isBlank(headerInfo.getManufacturer())) {
headerInfo.setManufacturer(value);
} else if (labelValue.contains("报关员") && StringUtils.isBlank(headerInfo.getCustomsBrokerInfo())) {
headerInfo.setCustomsBrokerInfo(value);
} else if (labelValue.contains("送货地址") && StringUtils.isBlank(headerInfo.getDeliveryAddress())) {
headerInfo.setDeliveryAddress(value);
} else if (labelValue.contains("进出境关别") && StringUtils.isBlank(headerInfo.getEntryExitCustoms())) {
headerInfo.setEntryExitCustoms(value);
} else if ((labelValue.contains("起运") || labelValue.contains("运抵国")) && StringUtils.isBlank(headerInfo.getOriginDestinationCountry())) {
headerInfo.setOriginDestinationCountry(value);
} else if (labelValue.contains("柜号") && StringUtils.isBlank(headerInfo.getContainerNo())) {
headerInfo.setContainerNo(value);
} else if (labelValue.contains("运输方式") && StringUtils.isBlank(headerInfo.getTransportMethod())) {
headerInfo.setTransportMethod(value);
} else if (labelValue.contains("监管方式") && StringUtils.isBlank(headerInfo.getSupervisionMethod())) {
headerInfo.setSupervisionMethod(value);
} else if (labelValue.contains("海关是否查验货物") && headerInfo.getCustomsInspection() == null) {
headerInfo.setCustomsInspection(parseYesNo(value));
} else if (labelValue.contains("是否需要报关") && headerInfo.getNeedCustomsDeclaration() == null) {
headerInfo.setNeedCustomsDeclaration(parseYesNo(value));
} else if (labelValue.contains("是否需要运输") && headerInfo.getNeedTransport() == null) {
headerInfo.setNeedTransport(parseYesNo(value));
} else if (labelValue.contains("完成时间") && StringUtils.isBlank(headerInfo.getCompletionTime())) {
headerInfo.setCompletionTime(value);
} else if (labelValue.contains("下单日期") && StringUtils.isBlank(headerInfo.getOrderDate())) {
headerInfo.setOrderDate(value);
}
}
/**
* 是否为To标签(精确匹配,避免把Value/Total等英文词误认为To
*/
private boolean isToLabel(String text) {
if (text == null) {
return false;
}
String t = text.trim();
return "To".equals(t) || "To:".equals(t) || "To".equals(t) || t.startsWith("To:") || t.startsWith("To");
}
/**
* 值是否看起来仍是标签文本(命中常见标签关键词)
*/
private boolean looksLikeLabel(String value) {
if (value == null) {
return true;
}
String v = value.trim();
return v.contains("委托编号") || v.contains("司机签名") || v.contains("完成时间") || v.contains("下单日期")
|| v.contains("入仓日期") || v.contains("联系人") || v.contains("Tel") || v.contains("Fax")
|| v.contains("客户") || v.contains("地址") || v.contains("日期") || v.contains("方式")
|| v.contains("关区") || v.contains("关别") || v.contains("报关员") || v.contains("生产商")
|| v.contains("承运方") || v.contains("承运商") || v.contains("起运") || v.contains("运抵国")
|| v.contains("地区") || v.contains("车型") || v.contains("车牌") || v.contains("柜号")
|| v.contains("海关是否查验货物") || v.contains("是否需要报关") || v.contains("是否需要运输");
}
/**
* 是/否(或1/0)转Integer;无法识别返回null
*/
private Integer parseYesNo(String value) {
if (value == null) {
return null;
}
String v = value.trim();
if (v.contains("") || "1".equals(v)) {
return 1;
}
if (v.contains("") || "0".equals(v)) {
return 0;
}
return null;
}
/**
* 取第一个非空字符串
*/
private String firstNotBlank(String a, String b) {
return StringUtils.isNotBlank(a) ? a : b;
}
/**
* 获取登录人姓名(优先UserPo.userName,其次realname,最后username兜底)
*/
private String resolveUserRealName(LoginUser loginUser) {
if (loginUser.getUserPo() != null && StringUtils.isNotBlank(loginUser.getUserPo().getUserName())) {
return loginUser.getUserPo().getUserName();
}
if (StringUtils.isNotBlank(loginUser.getRealname())) {
return loginUser.getRealname();
}
return loginUser.getUsername();
}
/**
* 解析日期字符串为Date对象
* 支持多种日期格式:yyyy-MM-dd, yyyy/MM/dd, yyyy年MM月dd日, Date.toString()格式等
@@ -12,8 +12,10 @@ import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
@@ -80,6 +82,42 @@ public class OutEntrustOrderApi extends BaseController {
return AjaxResult.success(outEntrustOrderApplicationService.getInfo(id));
}
/**
* 上传并解析出仓委托单Excel(模板与WMS出库管理导入一致:前11行表头+第12行起明细)
* 解析校验通过后落库为出仓委托单(生成CKWT委托单号,状态待审核/未下发)
*/
@ApiOperation("导入出仓委托单")
@PostMapping("/import")
public AjaxResult importOutEntrustOrder(
@RequestParam("file") MultipartFile file,
@RequestParam("warehouseId") Long warehouseId,
@RequestParam("warehouseCode") String warehouseCode,
@RequestParam("warehouseName") String warehouseName) {
// 验证文件
if (file.isEmpty()) {
return error("文件为空");
}
// 验证文件类型
String fileName = file.getOriginalFilename();
if (fileName == null || !fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
return error("文件类型错误");
}
try {
// 解析Excel并返回结果
StringBuilder result = outEntrustOrderApplicationService.importOutEntrustOrder(file, warehouseId, warehouseCode, warehouseName);
if (result.length() > 0) {
return AjaxResult.error(result.toString());
} else {
return AjaxResult.success(result);
}
} catch (IOException e) {
return error("解析失败");
}
}
/**
* 批量审核:待审核/已驳回 → 已审核(通过)或已驳回
* 请求体:{"idList":[1,2],"orderStatus":3,"auditRemark":"备注"}orderStatus2-审核通过 3-驳回(auditRemark选填)
@@ -99,18 +99,28 @@ public class ReservationStockInOrderApi extends BaseController {
}
/**
* 导入入库单
* 导入入仓委托单(模板与WMS入库管理导入一致:第1-12行表头,第13行列名,第14行起明细)
* 按【委托编号NO】分组,同一委托编号的多条明细归为一张入库单
*/
// @ApiOperation("导入入库单")
// @PostMapping(value = "/importData")
// public AjaxResult importData(
// @RequestParam("file") MultipartFile file,
// @RequestParam("warehouseId") Long warehouseId,
// @RequestParam("warehouseCode") String warehouseCode,
// @RequestParam("warehouseName") String warehouseName)
// {
// return AjaxResult.success(stockInOrderApplicationService.importData(file, warehouseId, warehouseCode, warehouseName));
// }
@ApiOperation("导入入仓委托单")
@PostMapping(value = "/importData")
public AjaxResult importData(
@RequestParam("file") MultipartFile file,
@RequestParam("warehouseId") Long warehouseId,
@RequestParam("warehouseCode") String warehouseCode,
@RequestParam("warehouseName") String warehouseName)
{
// 验证文件
if (file.isEmpty()) {
return error("文件为空");
}
// 验证文件类型
String fileName = file.getOriginalFilename();
if (fileName == null || !fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) {
return error("文件类型错误");
}
return AjaxResult.success(stockInOrderApplicationService.importData(file, warehouseId, warehouseCode, warehouseName));
}
/**
* 审核入库单
@@ -178,4 +178,29 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</where>
</select>
<!-- 导入用:跨库查询WMS物料基础信息(与reservationStockInOrder模块的直查模式一致),按货主+一级组织过滤 -->
<sql id="selectImportMaterialBaseInfo">
select * from "NGWL_TEST_WMS".MATERIAL_BASE_INFO
where DEL_FLAG = 1
and SHIPPER_ID = #{shipperId}
<if test="topOrganizationId != null">
and TOP_ORGANIZATION_ID = #{topOrganizationId}
</if>
</sql>
<select id="getMaterialByMaterialCode" resultType="com.mhd.oms.domain.reservationStockInOrder.repository.po.MaterialBaseInfoPO">
<include refid="selectImportMaterialBaseInfo"/>
and MATERIAL_CODE = #{materialCode}
</select>
<select id="getMaterialByBarCode" resultType="com.mhd.oms.domain.reservationStockInOrder.repository.po.MaterialBaseInfoPO">
<include refid="selectImportMaterialBaseInfo"/>
and BAR_CODE = #{barCode}
</select>
<select id="getMaterialByMaterialName" resultType="com.mhd.oms.domain.reservationStockInOrder.repository.po.MaterialBaseInfoPO">
<include refid="selectImportMaterialBaseInfo"/>
and MATERIAL_NAME = #{materialName}
</select>
</mapper>