From 6b58811f4cd99b69cabf0e5042732c99a12d359f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=A6=E9=B8=BF=E5=B1=95?= <18031053041@163.com> Date: Fri, 29 May 2026 10:02:26 +0800 Subject: [PATCH] =?UTF-8?q?=E7=89=A9=E6=96=99=E5=AF=BC=E5=85=A5=E7=B9=81?= =?UTF-8?q?=E4=BD=93=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MaterialBaseInfoApplicationService.java | 188 +++++++++++++----- .../materialBaseInfo/MaterialBaseInfoDTO.java | 16 +- .../util/MaterialBaseInfoImportExcelUtil.java | 60 ++++++ .../com/mhd/wms/util/ZhTextNormalizeUtil.java | 82 ++++++++ 4 files changed, 295 insertions(+), 51 deletions(-) create mode 100644 mhd_wms/src/main/java/com/mhd/wms/util/MaterialBaseInfoImportExcelUtil.java create mode 100644 mhd_wms/src/main/java/com/mhd/wms/util/ZhTextNormalizeUtil.java diff --git a/mhd_wms/src/main/java/com/mhd/wms/application/service/materialBaseInfo/MaterialBaseInfoApplicationService.java b/mhd_wms/src/main/java/com/mhd/wms/application/service/materialBaseInfo/MaterialBaseInfoApplicationService.java index 3cb25b386..bdd968c8b 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/application/service/materialBaseInfo/MaterialBaseInfoApplicationService.java +++ b/mhd_wms/src/main/java/com/mhd/wms/application/service/materialBaseInfo/MaterialBaseInfoApplicationService.java @@ -7,7 +7,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.mhd.common.core.domain.po.UserPo; import com.mhd.common.core.exception.ServiceException; import com.mhd.common.core.enums.DictCode; -import com.mhd.common.core.utils.poi.ExcelUtil; +import com.mhd.wms.util.ZhTextNormalizeUtil; import com.mhd.common.core.web.domain.AjaxResult; import com.mhd.common.core.web.page.TableDataInfo; import com.mhd.common.security.utils.SecurityUtils; @@ -42,6 +42,7 @@ import com.mhd.wms.domain.materialWarehouseControl.repository.po.MaterialWarehou import com.mhd.wms.domain.materialWarehouseControl.repository.todo.MaterialWarehouseControlDO; import com.mhd.wms.domain.materialWarehouseControl.service.MaterialWarehouseControlDomainService; import com.mhd.wms.interfaces.dto.materialBaseInfo.MaterialBaseInfoDTO; +import com.mhd.wms.util.MaterialBaseInfoImportExcelUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; @@ -54,6 +55,7 @@ import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.io.InputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -398,8 +400,7 @@ public class MaterialBaseInfoApplicationService { final List rows; try (InputStream is = file.getInputStream()) { - ExcelUtil util = new ExcelUtil<>(MaterialBaseInfoDTO.class); - rows = util.importExcel(is); + rows = MaterialBaseInfoImportExcelUtil.importRows(is); } catch (Exception e) { throw new ServiceException("导入模板解析失败:" + e.getMessage()); } @@ -434,10 +435,10 @@ public class MaterialBaseInfoApplicationService { continue; } - if (row.getOrganizationId() == null && loginOrgId != null) { + if (!isValidOrgId(row.getOrganizationId()) && isValidOrgId(loginOrgId)) { row.setOrganizationId(loginOrgId); } - if (row.getTopOrganizationId() == null && loginTopOrgId != null) { + if (row.getTopOrganizationId() == null && isValidOrgId(loginTopOrgId)) { row.setTopOrganizationId(loginTopOrgId); } if (row.getNullify() == null) { @@ -451,6 +452,9 @@ public class MaterialBaseInfoApplicationService { } // Excel 表头错位或重复时,HS 可能被读入「物料条形码」列;与 HS 完全相同时视为误填,保留 hsCode、清空条码 normalizeImportBarCodeWhenDuplicateOfHs(row); + // 先解析货主并补全组织,再做重复/存在性校验(组织维度依赖 organizationId) + fillIdsFromExistingMasterData(row); + fillOrganizationFromShipperIfNeeded(row); fillOrganizationName(row, loginUser); String shipperCode = StringUtils.defaultString(row.getShipperCode(), ""); @@ -473,8 +477,6 @@ public class MaterialBaseInfoApplicationService { errors.append("第").append(rowNum).append("行:物料已存在(组织+货主编码+物料编码);"); continue; } - // 根据导入的名称/编码回填现有ID,避免要求模板传ID - fillIdsFromExistingMasterData(row); fillClassifyTypeAndUnit(row); Object result = systemServiceFeign.getPackList(null, row.getPackName(), row.getOrganizationId()); @@ -551,23 +553,9 @@ public class MaterialBaseInfoApplicationService { if (StringUtils.isBlank(row.getPackName()) && StringUtils.isNotBlank(row.getPackImport())) { row.setPackName(row.getPackImport().trim()); } - // 货主:先按本组织已有物料反查,再用用户中心按「货主编码/货主名称」解析 userId(与入库单 import 一致;纯数字也可作名称,例如「1449」) + // 货主:先按本组织已有物料反查,再用用户中心按「货主编码/货主名称」解析 userId(支持繁体名称) if (row.getShipperId() == null && (StringUtils.isNotBlank(row.getShipperCode()) || StringUtils.isNotBlank(row.getShipperName()))) { - MaterialBaseInfo matched = materialBaseInfoService.getOne(new LambdaQueryWrapper() - .eq(MaterialBaseInfo::getDelFlag, 1) - .eq(MaterialBaseInfo::getOrganizationId, row.getOrganizationId()) - .and(w -> { - if (StringUtils.isNotBlank(row.getShipperCode())) { - w.eq(MaterialBaseInfo::getShipperCode, row.getShipperCode()); - } - if (StringUtils.isNotBlank(row.getShipperName())) { - if (StringUtils.isNotBlank(row.getShipperCode())) { - w.or(); - } - w.eq(MaterialBaseInfo::getShipperName, row.getShipperName()); - } - }) - .last("limit 1")); + MaterialBaseInfo matched = findExistingMaterialByShipper(row); if (matched != null) { row.setShipperId(matched.getShipperId()); if (StringUtils.isBlank(row.getShipperCode())) { @@ -642,6 +630,38 @@ public class MaterialBaseInfoApplicationService { } } + /** + * 按货主编码/名称在本组织已有物料中反查(名称支持简繁体归一化匹配)。 + */ + private MaterialBaseInfo findExistingMaterialByShipper(MaterialBaseInfoDTO row) { + if (row.getOrganizationId() == null) { + return null; + } + if (StringUtils.isNotBlank(row.getShipperCode())) { + MaterialBaseInfo byCode = materialBaseInfoService.getOne(new LambdaQueryWrapper() + .eq(MaterialBaseInfo::getDelFlag, 1) + .eq(MaterialBaseInfo::getOrganizationId, row.getOrganizationId()) + .eq(MaterialBaseInfo::getShipperCode, row.getShipperCode().trim()) + .last("limit 1")); + if (byCode != null) { + return byCode; + } + } + if (StringUtils.isNotBlank(row.getShipperName())) { + for (String nameVariant : ZhTextNormalizeUtil.nameLookupVariants(row.getShipperName())) { + MaterialBaseInfo byName = materialBaseInfoService.getOne(new LambdaQueryWrapper() + .eq(MaterialBaseInfo::getDelFlag, 1) + .eq(MaterialBaseInfo::getOrganizationId, row.getOrganizationId()) + .eq(MaterialBaseInfo::getShipperName, nameVariant) + .last("limit 1")); + if (byName != null) { + return byName; + } + } + } + return null; + } + /** * 导入时按货主名称/编号从用户中心解析货主ID(与 {@link com.mhd.wms.application.service.stockInOrder.StockInOrderApplicationService#getShipperIdByNameOrCode} 一致)。 * 不将纯数字的「货主名称」当作用户ID或主键,避免与业务上以数字作为企业简称/名称的货主(如「1449」)冲突。 @@ -658,17 +678,19 @@ public class MaterialBaseInfoApplicationService { String shipperCode = StringUtils.isNotBlank(row.getShipperCode()) ? row.getShipperCode().trim() : null; if (StringUtils.isNotBlank(shipperName)) { - 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 && shipperUserMatchesImportOrganization(userPo, importOrgId)) { - applyUserPoToMaterialImportRow(row, userPo); - return; + for (String nameVariant : ZhTextNormalizeUtil.nameLookupVariants(shipperName)) { + try { + AjaxResult ajaxResult = userServiceFeign.getInfoByName(nameVariant); + 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 && shipperUserMatchesImportOrganization(userPo, importOrgId)) { + applyUserPoToMaterialImportRow(row, userPo); + return; + } } + } catch (Exception e) { + log.debug("物料导入: getInfoByName 货主失败 name={}: {}", nameVariant, e.getMessage()); } - } catch (Exception e) { - log.debug("物料导入: getInfoByName 货主失败: {}", e.getMessage()); } } @@ -730,7 +752,8 @@ public class MaterialBaseInfoApplicationService { continue; } String s = String.valueOf(cand).trim(); - if (normalizedName.equals(s) || s.contains(normalizedName)) { + if (ZhTextNormalizeUtil.textEquals(normalizedName, s) + || ZhTextNormalizeUtil.textContains(s, normalizedName)) { matched = true; break; } @@ -763,6 +786,15 @@ public class MaterialBaseInfoApplicationService { return; } row.setShipperId(userPo.getUserId()); + if (!isValidOrgId(row.getOrganizationId()) && isValidOrgId(userPo.getOrganizationId())) { + row.setOrganizationId(userPo.getOrganizationId()); + } + if (row.getTopOrganizationId() == null && isValidOrgId(userPo.getTopOrganizationId())) { + row.setTopOrganizationId(userPo.getTopOrganizationId()); + } + if (StringUtils.isBlank(row.getOrganizationName()) && StringUtils.isNotBlank(userPo.getOrganizationName())) { + row.setOrganizationName(userPo.getOrganizationName()); + } if (StringUtils.isBlank(row.getShipperCode()) && StringUtils.isNotBlank(userPo.getUserMemberCode())) { row.setShipperCode(userPo.getUserMemberCode()); } @@ -771,6 +803,29 @@ public class MaterialBaseInfoApplicationService { } } + /** + * Excel 已填货主 ID、但未填组织时,从货主用户补全组织信息。 + */ + private void fillOrganizationFromShipperIfNeeded(MaterialBaseInfoDTO row) { + if (isValidOrgId(row.getOrganizationId()) || row.getShipperId() == null) { + return; + } + try { + AjaxResult infoRes = userServiceFeign.getInfo(row.getShipperId()); + if (!"200".equals(String.valueOf(infoRes.get("code"))) || infoRes.get("data") == null) { + return; + } + UserPo userPo = JSON.parseObject(JSONObject.toJSONString(infoRes.get("data")), UserPo.class); + applyUserPoToMaterialImportRow(row, userPo); + } catch (Exception e) { + log.debug("物料导入: 按货主ID补全组织失败 shipperId={}: {}", row.getShipperId(), e.getMessage()); + } + } + + private boolean isValidOrgId(Long organizationId) { + return organizationId != null && organizationId > 0; + } + private boolean shipperUserMatchesImportOrganization(UserPo userPo, Long importOrganizationId) { if (importOrganizationId == null) { return true; @@ -831,12 +886,7 @@ public class MaterialBaseInfoApplicationService { } private void fillClassifyTypeAndUnit(MaterialBaseInfoDTO row) { - // 物料分类:模板常用“物料分类”单列,回填到名称列表 - if (CollectionUtils.isEmpty(row.getMaterialClassifyName()) && StringUtils.isNotBlank(row.getMaterialClassifyImport())) { - List names = new ArrayList<>(); - names.add(row.getMaterialClassifyImport().trim()); - row.setMaterialClassifyName(names); - } + normalizeMaterialClassifyImportFields(row); // 按组织从 system 主数据反查分类编码,与名称列表对齐 fillMaterialClassifyCodesFromSystem(row); @@ -910,35 +960,81 @@ public class MaterialBaseInfoApplicationService { } } + /** + * Excel 导入列绑定 String 字段,再转为 List(List 字段仅用于导出/持久化)。 + */ + private void normalizeMaterialClassifyImportFields(MaterialBaseInfoDTO row) { + if (CollectionUtils.isEmpty(row.getMaterialClassifyCode()) + && StringUtils.isNotBlank(row.getMaterialClassifyCodeImport())) { + row.setMaterialClassifyCode(splitImportList(row.getMaterialClassifyCodeImport())); + } + if (CollectionUtils.isEmpty(row.getMaterialClassifyName())) { + if (StringUtils.isNotBlank(row.getMaterialClassifyNameImport())) { + row.setMaterialClassifyName(splitImportList(row.getMaterialClassifyNameImport())); + } else if (StringUtils.isNotBlank(row.getMaterialClassifyImport())) { + row.setMaterialClassifyName(splitImportList(row.getMaterialClassifyImport())); + } + } + } + + private List splitImportList(String raw) { + if (StringUtils.isBlank(raw)) { + return null; + } + return Arrays.stream(raw.split("[,,]")) + .map(String::trim) + .filter(StringUtils::isNotBlank) + .collect(Collectors.toList()); + } + /** * 导入时已有分类名称但缺少分类编码时,走 system 接口按组织+名称/编码/名称路径匹配并回填. * Excel 已填写的编码列优先保留。 */ private void fillMaterialClassifyCodesFromSystem(MaterialBaseInfoDTO row) { - if (CollectionUtils.isEmpty(row.getMaterialClassifyName()) || row.getOrganizationId() == null) { + if (row.getOrganizationId() == null) { return; } List names = row.getMaterialClassifyName(); List existing = row.getMaterialClassifyCode(); + if (CollectionUtils.isEmpty(names) && CollectionUtils.isEmpty(existing)) { + return; + } + int size = Math.max(names != null ? names.size() : 0, existing != null ? existing.size() : 0); List newCodes = new ArrayList<>(); - for (int i = 0; i < names.size(); i++) { + for (int i = 0; i < size; i++) { String fromExcel = (existing != null && i < existing.size()) ? existing.get(i) : null; if (StringUtils.isNotBlank(fromExcel)) { newCodes.add(fromExcel.trim()); continue; } - String n = StringUtils.trimToNull(names.get(i)); + String n = (names != null && i < names.size()) ? StringUtils.trimToNull(names.get(i)) : null; if (n == null) { - newCodes.add(null); - } else { - newCodes.add(resolveClassifyCodeByFeign(n, row.getOrganizationId())); + continue; + } + String resolved = resolveClassifyCodeByFeign(n, row.getOrganizationId()); + if (StringUtils.isNotBlank(resolved)) { + newCodes.add(resolved); } } - row.setMaterialClassifyCode(newCodes); + row.setMaterialClassifyCode(CollectionUtils.isEmpty(newCodes) ? null : newCodes); } /** 取 system 返回的 material_classify.code,写入本表 MATERIAL_CLASSIFY_CODE(List 中一项) */ private String resolveClassifyCodeByFeign(String keyword, Long organizationId) { + if (StringUtils.isBlank(keyword) || organizationId == null) { + return null; + } + for (String variant : ZhTextNormalizeUtil.nameLookupVariants(keyword)) { + String code = resolveClassifyCodeByFeignOnce(variant, organizationId); + if (StringUtils.isNotBlank(code)) { + return code; + } + } + return null; + } + + private String resolveClassifyCodeByFeignOnce(String keyword, Long organizationId) { if (StringUtils.isBlank(keyword) || organizationId == null) { return null; } diff --git a/mhd_wms/src/main/java/com/mhd/wms/interfaces/dto/materialBaseInfo/MaterialBaseInfoDTO.java b/mhd_wms/src/main/java/com/mhd/wms/interfaces/dto/materialBaseInfo/MaterialBaseInfoDTO.java index 8a6fa978d..ffbd5e729 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/interfaces/dto/materialBaseInfo/MaterialBaseInfoDTO.java +++ b/mhd_wms/src/main/java/com/mhd/wms/interfaces/dto/materialBaseInfo/MaterialBaseInfoDTO.java @@ -31,7 +31,7 @@ public class MaterialBaseInfoDTO extends BaseVOEntity { private Long materialBaseInfoId; @ApiModelProperty("组织表ID") - @Excel(name = "组织表ID") + @Excel(name = "组织表id") private Long organizationId; @ApiModelProperty("组织名称") @@ -39,7 +39,7 @@ public class MaterialBaseInfoDTO extends BaseVOEntity { private String organizationName; @ApiModelProperty("一级组织表ID") - @Excel(name = "一级组织表ID") + @Excel(name = "一级组织表id") private Long topOrganizationId; @ApiModelProperty("货主id") @@ -67,16 +67,22 @@ public class MaterialBaseInfoDTO extends BaseVOEntity { private String barCode; @ApiModelProperty("物料分类编码") - @Excel(name = "物料分类编码") + @Excel(name = "物料分类编码", type = Excel.Type.EXPORT) @TableField(typeHandler = ListTypeHandler.class) private List materialClassifyCode; @ApiModelProperty("物料分类名称") - @Excel(name = "物料分类名称") + @Excel(name = "物料分类名称", type = Excel.Type.EXPORT) @TableField(typeHandler = ListTypeHandler.class) private List materialClassifyName; + @ApiModelProperty("物料分类编码(导入)") + @Excel(name = "物料分类编码", type = Excel.Type.IMPORT) + private String materialClassifyCodeImport; + @ApiModelProperty("物料分类名称(导入)") + @Excel(name = "物料分类名称", type = Excel.Type.IMPORT) + private String materialClassifyNameImport; @ApiModelProperty("物料分类(导入)") - @Excel(name = "物料分类") + @Excel(name = "物料分类", type = Excel.Type.IMPORT) private String materialClassifyImport; @ApiModelProperty("物料种类编码 数据字典") diff --git a/mhd_wms/src/main/java/com/mhd/wms/util/MaterialBaseInfoImportExcelUtil.java b/mhd_wms/src/main/java/com/mhd/wms/util/MaterialBaseInfoImportExcelUtil.java new file mode 100644 index 000000000..beb1bff5a --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/util/MaterialBaseInfoImportExcelUtil.java @@ -0,0 +1,60 @@ +package com.mhd.wms.util; + +import com.mhd.common.core.utils.poi.ExcelUtil; +import com.mhd.wms.interfaces.dto.materialBaseInfo.MaterialBaseInfoDTO; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.DataFormatter; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.util.List; + +/** + * 物料主数据导入:支持繁体中文表头的 Excel 解析。 + */ +public final class MaterialBaseInfoImportExcelUtil { + + private MaterialBaseInfoImportExcelUtil() { + } + + public static List importRows(InputStream is) throws Exception { + Workbook workbook = WorkbookFactory.create(is); + try { + normalizeHeaderRow(workbook.getSheetAt(0), 0); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + workbook.write(bos); + ExcelUtil util = new ExcelUtil<>(MaterialBaseInfoDTO.class); + return util.importExcel(new ByteArrayInputStream(bos.toByteArray())); + } finally { + workbook.close(); + } + } + + private static void normalizeHeaderRow(Sheet sheet, int titleNum) { + if (sheet == null) { + return; + } + Row headerRow = sheet.getRow(titleNum); + if (headerRow == null) { + return; + } + DataFormatter formatter = new DataFormatter(); + for (int i = 0; i < headerRow.getLastCellNum(); i++) { + Cell cell = headerRow.getCell(i); + if (cell == null) { + continue; + } + String value = formatter.formatCellValue(cell); + if (StringUtils.isBlank(value)) { + continue; + } + cell.setCellValue(ZhTextNormalizeUtil.normalizeExcelHeader(value)); + } + } +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/util/ZhTextNormalizeUtil.java b/mhd_wms/src/main/java/com/mhd/wms/util/ZhTextNormalizeUtil.java new file mode 100644 index 000000000..d610b746c --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/util/ZhTextNormalizeUtil.java @@ -0,0 +1,82 @@ +package com.mhd.wms.util; + +import com.github.houbb.opencc4j.util.ZhConverterUtil; +import org.apache.commons.lang3.StringUtils; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * 简繁体文本归一化,用于 Excel 导入表头及货主名称匹配。 + */ +public final class ZhTextNormalizeUtil { + + private ZhTextNormalizeUtil() { + } + + public static String toSimple(String text) { + if (StringUtils.isBlank(text)) { + return text; + } + return ZhConverterUtil.toSimple(text.trim()); + } + + public static String toTraditional(String text) { + if (StringUtils.isBlank(text)) { + return text; + } + return ZhConverterUtil.toTraditional(text.trim()); + } + + /** + * 导入表头:繁体转简体,并将末尾 ID 统一为小写 id(与 @Excel name 一致)。 + */ + public static String normalizeExcelHeader(String header) { + if (StringUtils.isBlank(header)) { + return header; + } + String simple = toSimple(header.trim()); + if (simple.endsWith("ID")) { + return simple.substring(0, simple.length() - 2) + "id"; + } + return simple; + } + + public static boolean textEquals(String a, String b) { + if (a == null && b == null) { + return true; + } + if (a == null || b == null) { + return false; + } + return toSimple(a).equals(toSimple(b)); + } + + public static boolean textContains(String haystack, String needle) { + if (StringUtils.isBlank(haystack) || StringUtils.isBlank(needle)) { + return false; + } + return toSimple(haystack).contains(toSimple(needle)); + } + + /** + * 名称查询候选:原文、简体、繁体(去重,保持顺序)。 + */ + public static Set nameLookupVariants(String name) { + Set variants = new LinkedHashSet<>(); + if (StringUtils.isBlank(name)) { + return variants; + } + String trimmed = name.trim(); + variants.add(trimmed); + String simple = toSimple(trimmed); + if (StringUtils.isNotBlank(simple)) { + variants.add(simple); + } + String traditional = toTraditional(trimmed); + if (StringUtils.isNotBlank(traditional)) { + variants.add(traditional); + } + return variants; + } +}