Merge branch 'dev' into feature/dev-0729

This commit is contained in:
王奎兴
2026-07-29 18:09:54 +08:00
17 changed files with 508 additions and 64 deletions
@@ -79,6 +79,7 @@ public class SsoAuthorizeController {
// 5. 重定向到前端页面,token通过URL参数传递
// 前端页面需读取URL中的token参数并存入localStorage
log.info("SSO单点登录成功, account={}", account);
log.info("SSO单点登录成功, account={}, token={} , redirectUrl={}", account, accessToken, redirectUrl);
response.sendRedirect(redirectUrl + "?token=" + URLEncoder.encode(accessToken, StandardCharsets.UTF_8.name()));
} catch (Exception e) {
@@ -50,7 +50,7 @@ public class SsoAuthorizeService {
@Value("${sso.public-key}")
private String publicKeyStr;
@Value("https://diglog.namkwong.com.mo/main_app/sso")
@Value("${sso.redirect-url}")
private String redirectUrl;
@Resource
@@ -550,11 +550,11 @@ public class SysLoginService {
throw new ServiceException("请输入手机号和短信验证码");
}
//调用工具类校验,校验手机号格式错误
if (!RegexUtil.isPhoneLegal(userPhone)) {
recordLogininfor(userPhone, Constants.LOGIN_FAIL, UserError.USER_PHONE_REG_ERROR.errmsg(), 1);
throw new DigitalLogisticsException(UserError.USER_PHONE_REG_ERROR);
}
// //调用工具类校验,校验手机号格式错误
// if (!RegexUtil.isPhoneLegal(userPhone)) {
// recordLogininfor(userPhone, Constants.LOGIN_FAIL, UserError.USER_PHONE_REG_ERROR.errmsg(), 1);
// throw new DigitalLogisticsException(UserError.USER_PHONE_REG_ERROR);
// }
//根据域名获取一级组织ID
AjaxResult topOrganizationIdR = organizationServiceFeign.getOrganizationByPath(path, SecurityConstants.INNER);
+3 -3
View File
@@ -15,7 +15,7 @@ spring:
# server-addr: 127.0.0.1:8848
# 线上测试环境配置 容器名+端口号
# server-addr: 10.33.0.129:6010
server-addr: 10.102.192.30:6848
server-addr: 10.102.192.31:6848
username: nacos
password: manhuoda@2023
#线上正式环境
@@ -24,7 +24,7 @@ spring:
# password: manhuoda@2023
config:
# server-addr: 127.0.0.1:8848
server-addr: 10.102.192.30:6848
server-addr: 10.102.192.31:6848
# 测试环境配置 容器名+端口号
# server-addr: 10.33.0.129:6010
# 线上测试环境配置 容器名+端口号
@@ -52,4 +52,4 @@ sso:
AwIDAQAB
-----END PUBLIC KEY-----
# SSO登录成功后跳转的前端页面地址
redirect-url: "/"
redirect-url: "https://diglog.namkwong.com.mo/main_app/sso"
@@ -91,31 +91,15 @@ public class StorageSectionDomainService {
public Boolean update(StorageSectionDO storageSectionDO) {
LoginUser loginUser = SecurityUtils.getLoginUser();
Long topOrganizationId = loginUser.getUserPo().getTopOrganizationId();
AssociationWarehouseCacheDO associationWarehouseCacheDO = SystemServiceCacheUtil.getAssociationWarehouseCache(loginUser.getUserid());
if (topOrganizationId == null || topOrganizationId == 0 || associationWarehouseCacheDO == null) {
throw new ServiceException("获取当前登陆人组织与登陆仓库失败!");
if (topOrganizationId == null || topOrganizationId == 0) {
throw new ServiceException("获取当前登陆人组织失败!");
}
checkStorageSectionDuplicate(topOrganizationId, associationWarehouseCacheDO.getWarehouseId(),
// 直接用记录自身的 warehouseId 做唯一校验,不再依赖缓存仓库
checkStorageSectionDuplicate(topOrganizationId, storageSectionDO.getWarehouseId(),
storageSectionDO.getStorageCode(), storageSectionDO.getStorageName(),
storageSectionDO.getStorageSectionId());
//设置仓库
setWarehouseInfo(storageSectionDO);
String storageCode = storageSectionDO.getStorageCode();
String storageName = storageSectionDO.getStorageName();
Long warehouseId = storageSectionDO.getWarehouseId();
List<StorageSection> codelist = storageSectionService.list(new QueryWrapper<StorageSection>().lambda()
.eq(StorageSection::getStorageCode, storageCode)
.eq(StorageSection::getWarehouseId, warehouseId)
.ne(StorageSection::getStorageSectionId, storageSectionDO.getStorageSectionId())
.eq(StorageSection::getDelFlag, 1));
List<StorageSection> namelist = storageSectionService.list(new QueryWrapper<StorageSection>().lambda()
.eq(StorageSection::getStorageName, storageName)
.eq(StorageSection::getWarehouseId, warehouseId)
.ne(StorageSection::getStorageSectionId, storageSectionDO.getStorageSectionId())
.eq(StorageSection::getDelFlag, 1));
if ((codelist != null && codelist.size() > 0) || (namelist != null && namelist.size() > 0)) {
throw new ServiceException("库区编码或名称已存在");
}
return storageSectionService.update(storageSectionDO);
}
@@ -16,7 +16,7 @@ spring:
# server-addr: 127.0.0.1:8848
# server-addr: 10.33.0.129:6010
# 线上测试环境配置 容器名+端口号
server-addr: 10.102.192.31:6848
server-addr: 10.102.192.30:6848
username: nacos
password: manhuoda@2023
#线上正式环境
@@ -25,7 +25,7 @@ spring:
# server-addr: 127.0.0.1:8848
# server-addr: 10.33.0.129:6010
# 线上测试环境配置 容器名+端口号
server-addr: 10.102.192.31:6848
server-addr: 10.102.192.30:6848
username: nacos
password: manhuoda@2023
#线上正式环境
@@ -1,12 +1,22 @@
package com.mhd.user.application.service;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mhd.common.security.utils.SecurityUtils;
import com.mhd.user.domain.userAggregate.entity.AgreementDetails;
import com.mhd.user.domain.userAggregate.entity.UserShipperEntity;
import com.mhd.user.domain.userAggregate.repository.mapper.AgreementDetailsMapper;
import com.mhd.user.domain.userAggregate.repository.mapper.UserShipperMapper;
import com.mhd.user.domain.userAggregate.repository.po.AgreementDetailsPo;
import com.mhd.system.api.model.LoginUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
@@ -19,18 +29,87 @@ public class AgreementDetailsApplicationService {
@Resource
private AgreementDetailsMapper agreementDetailsMapper;
@Resource
private UserShipperMapper userShipperMapper;
/**
* 根据货主ID查询所有协议明细
* 根据货主ID查询当前登录用户组织下的协议明细
* 从user_shipper表的orgCode字段解析JSON,匹配当前登录用户组织code,
* 返回该组织下的agreedaydatas列表
*
* @param shipperId 货主ID
* @return 协议明细列表
*/
public List<AgreementDetails> listByShipperId(Long shipperId) {
return agreementDetailsMapper.selectList(
new LambdaQueryWrapper<AgreementDetails>()
.eq(AgreementDetails::getShipperId, shipperId)
.orderByDesc(AgreementDetails::getCreateTime)
);
public List<AgreementDetailsPo> listByShipperId(Long shipperId) {
// 1. 获取当前登录用户组织ID
LoginUser loginUser = SecurityUtils.getLoginUser();
if (ObjectUtil.isNull(loginUser) || ObjectUtil.isNull(loginUser.getUserPo())) {
log.warn("未获取到登录用户信息");
return Collections.emptyList();
}
Long organizationId = loginUser.getUserPo().getOrganizationId();
if (ObjectUtil.isNull(organizationId)) {
log.warn("当前登录用户无组织信息");
return Collections.emptyList();
}
// 2. 根据shipperId查询货主信息,获取orgCode字段
UserShipperEntity shipperEntity = userShipperMapper.selectById(shipperId);
if (ObjectUtil.isNull(shipperEntity)) {
log.warn("未找到货主信息,shipperId={}", shipperId);
return Collections.emptyList();
}
String orgCodeJson = shipperEntity.getOrgCode();
if (ObjectUtil.isNull(orgCodeJson) || orgCodeJson.isEmpty()) {
log.warn("货主orgCode为空,shipperId={}", shipperId);
return Collections.emptyList();
}
// 3. 解析orgCode JSON数组,匹配当前登录用户组织code
JSONArray orgCodeArray = JSONArray.parseArray(orgCodeJson);
if (orgCodeArray == null || orgCodeArray.isEmpty()) {
return Collections.emptyList();
}
JSONObject matchedOrg = null;
for (int i = 0; i < orgCodeArray.size(); i++) {
JSONObject orgItem = orgCodeArray.getJSONObject(i);
Long code = orgItem.getLong("code");
if (organizationId.equals(code)) {
matchedOrg = orgItem;
break;
}
}
if (matchedOrg == null) {
log.info("未匹配到当前组织的协议信息,shipperId={}organizationId={}", shipperId, organizationId);
return Collections.emptyList();
}
// 4. 获取匹配组织的agreedaydatas
JSONArray agreedaydatas = matchedOrg.getJSONArray("agreedaydatas");
if (agreedaydatas == null || agreedaydatas.isEmpty()) {
log.info("当前组织下无协议天数数据,shipperId={}organizationId={}", shipperId, organizationId);
return Collections.emptyList();
}
// 5. 转换为AgreementDetailsPo列表
List<AgreementDetailsPo> result = new ArrayList<>();
for (int i = 0; i < agreedaydatas.size(); i++) {
JSONObject item = agreedaydatas.getJSONObject(i);
AgreementDetailsPo po = new AgreementDetailsPo();
po.setCode(item.getString("code"));
po.setOrgcode(item.getString("orgcode"));
po.setPtypename(item.getString("ptypename"));
po.setPtype(item.getString("ptype"));
po.setPcode(item.getString("pcode"));
po.setPname(item.getString("pname"));
po.setPday(item.getString("pday"));
result.add(po);
}
log.info("查询协议明细成功,shipperId={}organizationId={},共{}条", shipperId, organizationId, result.size());
return result;
}
/**
@@ -0,0 +1,32 @@
package com.mhd.user.domain.userAggregate.repository.po;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 协议天数PO
*/
@Data
public class AgreementDetailsPo {
@ApiModelProperty("客户编码")
private String code;
@ApiModelProperty("协议组织编码")
private String orgcode;
@ApiModelProperty("协议类型名称")
private String ptypename;
@ApiModelProperty("协议类型:1-付款协议天数,2-收款协议天数")
private String ptype;
@ApiModelProperty("财务系统协议编码")
private String pcode;
@ApiModelProperty("协议天数名称")
private String pname;
@ApiModelProperty("协议天数")
private String pday;
}
@@ -21,14 +21,14 @@ spring:
# server-addr: 127.0.0.1:8848
# 线上测试环境配置 容器名+端口号
# server-addr: 10.33.0.129:6010
server-addr: 10.102.192.30:6848
server-addr: 10.102.192.31:6848
username: nacos
password: manhuoda@2023
config:
# server-addr: 127.0.0.1:8848
# 线上测试环境配置 容器名+端口号
# server-addr: 10.33.0.129:6010
server-addr: 10.102.192.30:6848
server-addr: 10.102.192.31:6848
username: nacos
password: manhuoda@2023
group: DEFAULT_GROUP # 默认分组就是DEFAULT_GROUP,如果使用默认分组可以不配置
@@ -271,9 +271,25 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
Long materialInventoryId = reservationMaterialInventoryPO.getMaterialInventoryId();
String batchNumber = reservationMaterialInventoryPO.getBatchNumber();
String lotNumber = reservationMaterialInventoryPO.getLotNumber();
Long warehouseId = reservationMaterialInventoryPO.getWarehouseId();
String warehouseCode = reservationMaterialInventoryPO.getWarehouseCode();
String warehouseName = reservationMaterialInventoryPO.getWarehouseName();
String inOrderNumber = reservationMaterialInventoryPO.getInOrderNumber();
BigDecimal inventoryQuantity = reservationMaterialInventoryPO.getInventoryQuantity();
String unit = reservationMaterialInventoryPO.getUnit();
String unitName = reservationMaterialInventoryPO.getUnitName();
reservationOutMaterialDetailDO.setMaterialInventoryId(materialInventoryId);
reservationOutMaterialDetailDO.setBatchNumber(batchNumber);
reservationOutMaterialDetailDO.setLotNo(lotNumber);
reservationOutMaterialDetailDO.setWarehouseId(warehouseId);
reservationOutMaterialDetailDO.setWarehouseCode(warehouseCode);
reservationOutMaterialDetailDO.setWarehouseName(warehouseName);
reservationOutMaterialDetailDO.setInOrderNumber(inOrderNumber);
reservationOutMaterialDetailDO.setStockQuantity(inventoryQuantity);
reservationOutMaterialDetailDO.setUnit(unit);
reservationOutMaterialDetailDO.setStockUnit(unitName);
reservationOutMaterialDetailDO.setInventoryQuantity(inventoryQuantity);
reservationOutMaterialDetailDO.setUnitName(unitName);
} else {
throw new RuntimeException("物料批次库存数量少于出库数量!");
}
@@ -7,12 +7,14 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableRyFeignClients
@EnableDiscoveryClient
@MapperScan("com.mhd.wms.domain.**.mapper")
@EnableMethodCache(basePackages = "com.mhd.platform.domain.**.cache")
@EnableAsync
@ComponentScan(basePackages = {
"com.mhd.wms",
"com.mhd.system.api.factory"
@@ -49,12 +49,15 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;
@@ -100,6 +103,10 @@ public class MaterialBaseInfoApplicationService {
@Resource
private MaterialBaseInfoMapper materialBaseInfoMapper;
@Autowired
private MaterialBaseInfoAsyncService materialBaseInfoAsyncService;
/**
* 分页查询物料基础信息列表
*/
@@ -436,8 +443,47 @@ public class MaterialBaseInfoApplicationService {
return materialBaseInfoDomainService.getInfoByNoOrderReceipt(materialBaseInfoDO);
}
/**
* 导入物料主数据(仅导入主表字段
* 导入物料主数据(异步入口
*
* <p>接口层调用此方法,将文件保存到临时目录后立即返回"异步处理中",
* 实际导入由 {@link #doImportData(File, String, LoginUser)} 异步执行,
* 避免大数据量时接口超时。</p>
*/
public AjaxResult importDataAsync(MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new ServiceException("上传文件不能为空");
}
String fileName = file.getOriginalFilename();
if (org.apache.commons.lang3.StringUtils.isBlank(fileName)
|| (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx"))) {
throw new ServiceException("请上传xls或xlsx格式文件");
}
// 流式写入临时文件,避免大文件 getBytes() 导致内存溢出和接口超时
String tmpDir = System.getProperty("java.io.tmpdir") + File.separator + "material_import";
File dir = new File(tmpDir);
if (!dir.exists()) {
dir.mkdirs();
}
String tmpFileName = System.currentTimeMillis() + "_" + fileName;
File tmpFile = new File(dir, tmpFileName);
try {
file.transferTo(tmpFile);
} catch (Exception e) {
throw new ServiceException("文件保存失败:" + e.getMessage());
}
LoginUser loginUser = SecurityUtils.getLoginUser();
// 通过独立的异步 Service 执行导入(避免类内部 self-invocation 导致 @Async 失效)
materialBaseInfoAsyncService.doImportData(tmpFile, fileName, loginUser);
return AjaxResult.success("导入任务已提交,正在后台异步处理,请稍后查看导入结果日志");
}
/**
* 导入物料主数据(仅导入主表字段)- 原同步方法保留,供其他场景调用
*/
@Transactional(rollbackFor = Exception.class)
public AjaxResult importData(MultipartFile file) {
@@ -496,7 +542,6 @@ public class MaterialBaseInfoApplicationService {
row.setHsCode(row.getHsCodeImport().trim());
}
normalizeImportBarCodeWhenDuplicateOfHs(row);
// 客户模板通常只有「货主名称」无「货主id」:须先按名称解析货主,再回填组织;勿提前写入登录组织以免组织校验拦截货主匹配
fillIdsFromExistingMasterData(row);
fillOrganizationFromShipperIfNeeded(row);
if (!isValidOrgId(row.getOrganizationId()) && isValidOrgId(loginOrgId)) {
@@ -567,25 +612,20 @@ public class MaterialBaseInfoApplicationService {
materialBaseInfoDO.setMaterialCommon(null);
materialBaseInfoDO.setMaterialWarehouseControl(null);
materialBaseInfoDO.setMaterialBarCodeList(buildImportBarCodeList(row));
// 商品规则由 buildImportMaterialGoodsRule + insert() 统一写入,勿再单独 insert,避免 MATERIAL_GOODS_RULE 重复
insert(materialBaseInfoDO);
Long materialBaseInfoId = materialBaseInfoDO.getMaterialBaseInfoId();
//公共信息
MaterialCommonDO materialCommonDO = materialBaseInfoDO.getMaterialCommon();
if (materialCommonDO != null) {
materialCommonDO.setMaterialBaseInfoId(materialBaseInfoId);
materialCommonDomainService.insert(materialCommonDO);
}
//仓库控制信息
MaterialWarehouseControlDO materialWarehouseControlDO = materialBaseInfoDO.getMaterialWarehouseControl();
if (materialWarehouseControlDO != null) {
materialWarehouseControlDO.setMaterialBaseInfoId(materialBaseInfoId);
materialWarehouseControlDomainService.insert(materialWarehouseControlDO);
}
//库存预警
materialInventoryWarningDomainService.batchInsertOrUpdate(materialBaseInfoDO);
//条码信息
materialBarCodeDomainService.batchInsertOrUpdate(materialBaseInfoDO);
successCount++;
}
@@ -596,10 +636,11 @@ public class MaterialBaseInfoApplicationService {
return AjaxResult.success("导入成功,共" + successCount + "");
}
/**
* 使用已存在主数据按名称/编码反查并回填ID
*/
private void fillIdsFromExistingMasterData(MaterialBaseInfoDTO row) {
public void fillIdsFromExistingMasterData(MaterialBaseInfoDTO row) {
if (StringUtils.isBlank(row.getPackName()) && StringUtils.isNotBlank(row.getPackImport())) {
row.setPackName(row.getPackImport().trim());
}
@@ -818,10 +859,14 @@ public class MaterialBaseInfoApplicationService {
Object userCode = shipper.get("userCode");
Object userMemberCode = shipper.get("userMemberCode");
Object entName = shipper.get("shipperEnterpriseName");
if (userName == null) {
// userName 可能为空字符串而非 null,需同时判断空字符串
if (userName == null || String.valueOf(userName).trim().isEmpty()) {
userName = shipper.get("userNameShipper");
}
if (userName == null || String.valueOf(userName).trim().isEmpty()) {
userName = shipper.get("name");
}
if (userName == null) {
if (userName == null || String.valueOf(userName).trim().isEmpty()) {
userName = shipper.get("shipperName");
}
String normalizedName = StringUtils.isNotBlank(shipperName) ? shipperName : "";
@@ -919,7 +964,7 @@ public class MaterialBaseInfoApplicationService {
/**
* Excel 已填货主 ID、但未填组织时,从货主用户补全组织信息。
*/
private void fillOrganizationFromShipperIfNeeded(MaterialBaseInfoDTO row) {
public void fillOrganizationFromShipperIfNeeded(MaterialBaseInfoDTO row) {
if (isValidOrgId(row.getOrganizationId()) || row.getShipperId() == null) {
return;
}
@@ -978,7 +1023,7 @@ public class MaterialBaseInfoApplicationService {
}
}
private void fillOrganizationName(MaterialBaseInfoDTO row, LoginUser loginUser) {
public void fillOrganizationName(MaterialBaseInfoDTO row, LoginUser loginUser) {
if (StringUtils.isNotBlank(row.getOrganizationName())) {
return;
}
@@ -998,7 +1043,7 @@ public class MaterialBaseInfoApplicationService {
}
}
private void fillClassifyTypeAndUnit(MaterialBaseInfoDTO row) {
public void fillClassifyTypeAndUnit(MaterialBaseInfoDTO row) {
normalizeMaterialClassifyImportFields(row);
// 按组织从 system 主数据反查分类编码,与名称列表对齐
fillMaterialClassifyCodesFromSystem(row);
@@ -1179,7 +1224,7 @@ public class MaterialBaseInfoApplicationService {
* 导入约定:「HS码/HS编码」与「物料条形码」为不同列。模板表头错位或两列误填相同 HS 时,
* barCode 会与 hsCode 相同;此时保留 hsCode,清空主表/子表条码,避免 HS 写入 bar_code。
*/
private void normalizeImportBarCodeWhenDuplicateOfHs(MaterialBaseInfoDTO row) {
public void normalizeImportBarCodeWhenDuplicateOfHs(MaterialBaseInfoDTO row) {
if (row == null) {
return;
}
@@ -1194,7 +1239,7 @@ public class MaterialBaseInfoApplicationService {
}
}
private List<MaterialBarCodeDO> buildImportBarCodeList(MaterialBaseInfoDTO row) {
public List<MaterialBarCodeDO> buildImportBarCodeList(MaterialBaseInfoDTO row) {
if (StringUtils.isBlank(row.getBarCode())) {
return null;
}
@@ -1234,7 +1279,7 @@ public class MaterialBaseInfoApplicationService {
return idObj == null ? null : Long.valueOf(String.valueOf(idObj));
}
private MaterialGoodsRuleDO buildImportMaterialGoodsRule(MaterialBaseInfoDTO row) {
public MaterialGoodsRuleDO buildImportMaterialGoodsRule(MaterialBaseInfoDTO row) {
String batchName = StringUtils.isNotBlank(row.getBatchRuleNameImport())
? row.getBatchRuleNameImport().trim()
: (StringUtils.isNotBlank(row.getBatchName())
@@ -1309,7 +1354,7 @@ public class MaterialBaseInfoApplicationService {
.trim();
}
private List<Map<String, Object>> extractTableDataRows(Map<String, Object> resultMap) {
public List<Map<String, Object>> extractTableDataRows(Map<String, Object> resultMap) {
if (resultMap == null) {
return new ArrayList<>();
}
@@ -0,0 +1,274 @@
package com.mhd.wms.application.service.materialBaseInfo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mhd.common.core.domain.po.UserPo;
import com.mhd.common.core.web.domain.AjaxResult;
import com.mhd.system.api.SystemServiceFeign;
import com.mhd.system.api.UserServiceFeign;
import com.mhd.system.api.model.LoginUser;
import com.mhd.wms.domain.materialBarCode.service.MaterialBarCodeDomainService;
import com.mhd.wms.domain.materialBaseInfo.entity.MaterialBaseInfo;
import com.mhd.wms.domain.materialBaseInfo.repository.facade.IMaterialBaseInfoService;
import com.mhd.wms.domain.materialBaseInfo.repository.mapper.MaterialBaseInfoMapper;
import com.mhd.wms.domain.materialBaseInfo.repository.todo.MaterialBaseInfoDO;
import com.mhd.wms.domain.materialBaseInfo.service.MaterialBaseInfoDomainService;
import com.mhd.wms.domain.materialGoodsRule.service.MaterialGoodsRuleDomainService;
import com.mhd.wms.domain.materialCommon.service.MaterialCommonDomainService;
import com.mhd.wms.domain.materialCommon.repository.todo.MaterialCommonDO;
import com.mhd.wms.domain.materialInventoryWarning.service.MaterialInventoryWarningDomainService;
import com.mhd.wms.domain.materialWarehouseControl.service.MaterialWarehouseControlDomainService;
import com.mhd.wms.domain.materialWarehouseControl.repository.todo.MaterialWarehouseControlDO;
import com.mhd.wms.interfaces.dto.materialBaseInfo.MaterialBaseInfoDTO;
import com.mhd.wms.util.MaterialBaseInfoImportExcelUtil;
import com.mhd.wms.util.ZhTextNormalizeUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;
/**
* 物料主数据异步导入服务
*/
@Slf4j
@Service
public class MaterialBaseInfoAsyncService {
@Autowired
private IMaterialBaseInfoService materialBaseInfoService;
@Autowired
private MaterialBaseInfoMapper materialBaseInfoMapper;
@Autowired
private SystemServiceFeign systemServiceFeign;
@Autowired
private UserServiceFeign userServiceFeign;
@Autowired
private MaterialCommonDomainService materialCommonDomainService;
@Autowired
private MaterialWarehouseControlDomainService materialWarehouseControlDomainService;
@Autowired
private MaterialInventoryWarningDomainService materialInventoryWarningDomainService;
@Autowired
private MaterialBarCodeDomainService materialBarCodeDomainService;
@Autowired
private MaterialBaseInfoDomainService materialBaseInfoDomainService;
@Autowired
private com.mhd.wms.domain.materialGoodsRule.service.MaterialGoodsRuleDomainService materialGoodsRuleDomainService;
@org.springframework.context.annotation.Lazy
@Autowired
private MaterialBaseInfoApplicationService materialBaseInfoApplicationService;
@Async
public void doImportData(File tmpFile, String fileName, LoginUser loginUser) {
long startTime = System.currentTimeMillis();
log.info("=== 物料主数据异步导入开始,文件:{},大小:{}KB ===", fileName, tmpFile.length() / 1024);
try {
List<MaterialBaseInfoDTO> rows;
try (InputStream is = new FileInputStream(tmpFile)) {
rows = MaterialBaseInfoImportExcelUtil.importRows(is);
} catch (Exception e) {
log.error("物料主数据异步导入失败 - 文件解析异常:{}", e.getMessage(), e);
return;
}
if (CollectionUtils.isEmpty(rows)) {
log.warn("物料主数据异步导入结束 - 导入数据为空");
return;
}
Long loginOrgId = null;
Long loginTopOrgId = null;
if (loginUser != null && loginUser.getUserPo() != null) {
loginOrgId = loginUser.getUserPo().getOrganizationId();
loginTopOrgId = loginUser.getUserPo().getTopOrganizationId();
}
StringBuilder errors = new StringBuilder();
Set<String> duplicateInFile = new HashSet<>();
int successCount = 0;
int totalCount = rows.size();
Map<String, Map<String, Object>> packCache = new HashMap<>();
for (int i = 0; i < rows.size(); i++) {
MaterialBaseInfoDTO row = rows.get(i);
int rowNum = i + 2;
if (row == null) {
continue;
}
try {
if (StringUtils.isBlank(row.getMaterialCode())) {
errors.append("").append(rowNum).append("行:物料编码不能为空;");
continue;
}
if (StringUtils.isBlank(row.getMaterialName())) {
errors.append("").append(rowNum).append("行:物料名称不能为空;");
continue;
}
if (row.getNullify() == null) {
row.setNullify(2);
}
if (row.getCommon() == null) {
row.setCommon(2);
}
if (StringUtils.isBlank(row.getHsCode()) && StringUtils.isNotBlank(row.getHsCodeImport())) {
row.setHsCode(row.getHsCodeImport().trim());
}
materialBaseInfoApplicationService.normalizeImportBarCodeWhenDuplicateOfHs(row);
materialBaseInfoApplicationService.fillIdsFromExistingMasterData(row);
materialBaseInfoApplicationService.fillOrganizationFromShipperIfNeeded(row);
if (!isValidOrgId(row.getOrganizationId()) && isValidOrgId(loginOrgId)) {
row.setOrganizationId(loginOrgId);
}
if (row.getTopOrganizationId() == null && isValidOrgId(loginTopOrgId)) {
row.setTopOrganizationId(loginTopOrgId);
}
materialBaseInfoApplicationService.fillOrganizationName(row, loginUser);
String shipperCode = StringUtils.defaultString(row.getShipperCode(), "");
String duplicateKey = row.getOrganizationId() + "|" + shipperCode + "|" + row.getMaterialCode();
if (!duplicateInFile.add(duplicateKey)) {
errors.append("").append(rowNum).append("行:文件内存在重复物料;");
continue;
}
LambdaQueryWrapper<MaterialBaseInfo> wrapper = new LambdaQueryWrapper<MaterialBaseInfo>()
.eq(MaterialBaseInfo::getDelFlag, 1)
.eq(MaterialBaseInfo::getOrganizationId, row.getOrganizationId())
.eq(MaterialBaseInfo::getMaterialCode, row.getMaterialCode());
if (StringUtils.isBlank(row.getShipperCode())) {
wrapper.and(w -> w.isNull(MaterialBaseInfo::getShipperCode).or().eq(MaterialBaseInfo::getShipperCode, ""));
} else {
wrapper.eq(MaterialBaseInfo::getShipperCode, row.getShipperCode());
}
if (materialBaseInfoService.count(wrapper) > 0) {
errors.append("").append(rowNum).append("行:物料已存在;");
continue;
}
materialBaseInfoApplicationService.fillClassifyTypeAndUnit(row);
// 包装规格查询(带缓存)
String packCacheKey = row.getOrganizationId() + "|" + StringUtils.defaultString(row.getPackName());
Map<String, Object> cachedPack = packCache.get(packCacheKey);
if (cachedPack == null) {
Object result = systemServiceFeign.getPackList(null, row.getPackName(), row.getOrganizationId());
if (result == null) {
packCache.put(packCacheKey, Collections.emptyMap());
errors.append("").append(rowNum).append("行:包装规格查询无返回;");
continue;
}
Map<String, Object> resultMap = JSON.parseObject(JSONObject.toJSONString(result), new TypeReference<Map<String, Object>>() {});
List<Map<String, Object>> rowsPack = extractTableDataRows(resultMap);
if (CollectionUtils.isEmpty(rowsPack)) {
packCache.put(packCacheKey, Collections.emptyMap());
errors.append("").append(rowNum).append("行:包装规格不存在;");
continue;
}
cachedPack = rowsPack.get(0);
packCache.put(packCacheKey, cachedPack);
}
if (cachedPack.isEmpty()) {
errors.append("").append(rowNum).append("行:包装规格不存在(缓存);");
continue;
}
String packCode = cachedPack.get("packCode") == null ? "" : String.valueOf(cachedPack.get("packCode"));
row.setPackCode(packCode);
String packId = cachedPack.get("packId") == null ? "" : String.valueOf(cachedPack.get("packId"));
if (StringUtils.isBlank(packId) || !StringUtils.isNumeric(packId)) {
errors.append("").append(rowNum).append("行:包装规格数据异常;");
continue;
}
row.setPackId(Long.valueOf(packId));
String unitName = cachedPack.get("unitName") == null ? "" : String.valueOf(cachedPack.get("unitName"));
row.setUnitCode("EA");
row.setUnitName(unitName);
MaterialBaseInfoDO materialBaseInfoDO = new MaterialBaseInfoDO();
BeanUtils.copyProperties(row, materialBaseInfoDO);
materialBaseInfoDO.setMaterialGoodsRule(materialBaseInfoApplicationService.buildImportMaterialGoodsRule(row));
materialBaseInfoDO.setMaterialCommon(null);
materialBaseInfoDO.setMaterialWarehouseControl(null);
materialBaseInfoDO.setMaterialBarCodeList(materialBaseInfoApplicationService.buildImportBarCodeList(row));
// 补全创建人信息(异步线程没有 SecurityContext,用 loginUser
if (loginUser != null) {
materialBaseInfoDO.setCreateBy(loginUser.getUserid());
materialBaseInfoDO.setCreateByName(loginUser.getUsername());
}
materialBaseInfoDO.setCreateTime(new Date());
// 调用原 ApplicationService 的 insert,走完整的插入逻辑(含商品规则、公共信息、NC编码等)
materialBaseInfoApplicationService.insert(materialBaseInfoDO);
successCount++;
} catch (Exception e) {
log.error("物料主数据异步导入 - 第{}行处理异常", rowNum, e);
errors.append("").append(rowNum).append("行:处理异常-").append(e.getMessage()).append("");
}
}
long costTime = System.currentTimeMillis() - startTime;
if (errors.length() > 0) {
log.warn("=== 物料主数据异步导入完成,文件:{},总数:{},成功:{},失败:{},耗时:{}ms,失败原因:{} ===",
fileName, totalCount, successCount, totalCount - successCount, costTime, errors);
} else {
log.info("=== 物料主数据异步导入完成,文件:{},总数:{},成功:{},耗时:{}ms ===",
fileName, totalCount, successCount, costTime);
}
} finally {
try {
if (tmpFile.exists()) {
tmpFile.delete();
}
} catch (Exception e) {
log.warn("物料主数据异步导入 - 临时文件清理失败:{}", e.getMessage());
}
}
}
// ====== 以下工具方法 ======
private List<Map<String, Object>> extractTableDataRows(Map<String, Object> resultMap) {
if (resultMap == null) {
return Collections.emptyList();
}
Object data = resultMap.get("data");
if (data == null) {
data = resultMap.get("rows");
}
if (data instanceof List) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> list = (List<Map<String, Object>>) data;
return list;
}
if (data instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> dataMap = (Map<String, Object>) data;
Object innerData = dataMap.get("data");
if (innerData instanceof List) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> list = (List<Map<String, Object>>) innerData;
return list;
}
Object rows = dataMap.get("rows");
if (rows instanceof List) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> list = (List<Map<String, Object>>) rows;
return list;
}
}
return Collections.emptyList();
}
private boolean isValidOrgId(Long id) {
return id != null && id > 0;
}
}
@@ -282,9 +282,6 @@ public class StockOutOrderApplicationService {
stockOutOrderPO.setMaterialQuantity(cnt);
}
BigDecimal checkSum = (mag != null && mag.getCheckQuantitySum() != null)
? mag.getCheckQuantitySum() : BigDecimal.ZERO;
stockOutOrderPO.setCheckQuantity(checkSum);
stockOutOrderPO.setReceiptOrderAccountList(
accountByOrderId.getOrDefault(stockOutOrderPO.getOutOrderId(), Collections.emptyList()));
}
@@ -819,7 +819,12 @@ public class MaterialInventoryImpl extends ServiceImpl<MaterialInventoryMapper,
materialInventory.setNetWeight(netWeight);
materialInventory.setMaterialBaseInfoId(materialInventoryParamDO.getMaterialBaseInfoId());
materialInventory.setInOrderNumber(materialInventoryParamDO.getInOrderNumber());
if (materialBaseInfo != null) {
// 优先使用上游传入的货主信息如来自上架单否则再从物料基础信息兜底
if (materialInventoryParamDO.getShipperId() != null) {
materialInventory.setShipperId(materialInventoryParamDO.getShipperId());
materialInventory.setShipperName(materialInventoryParamDO.getShipperName());
materialInventory.setShipperCode(materialInventoryParamDO.getShipperCode());
} else if (materialBaseInfo != null) {
materialInventory.setShipperId(materialBaseInfo.getShipperId());
materialInventory.setShipperName(materialBaseInfo.getShipperName());
materialInventory.setShipperCode(materialBaseInfo.getShipperCode());
@@ -106,12 +106,16 @@ public class MaterialBaseInfoApi extends BaseController {
}
/**
* 导入物料主数据
* 导入物料主数据异步执行
*
* <p>数据量大时同步导入会导致接口超时改为异步模式
* 接口立即返回"已提交异步处理"后台线程执行实际导入
* 导入结果通过日志输出</p>
*/
@ApiOperation("导入物料主数据")
@ApiOperation("导入物料主数据(异步)")
@PostMapping("/importData")
public AjaxResult importData(@RequestParam("file") MultipartFile file) {
return materialBaseInfoApplicationService.importData(file);
return materialBaseInfoApplicationService.importDataAsync(file);
}
@ApiOperation("物料管理更新推送信息")
@@ -36,6 +36,11 @@ public final class MaterialBaseInfoImportExcelUtil {
HEADER_ALIASES.put("淨重(kg)", "净重 单位:千克");
HEADER_ALIASES.put("体积(m³)", "体积 单位:立方米");
HEADER_ALIASES.put("體積(m³)", "体积 单位:立方米");
HEADER_ALIASES.put("", "物料大小-长 单位:米");
HEADER_ALIASES.put("", "物料大小-宽 单位:米");
HEADER_ALIASES.put("", "物料大小-高 单位:米");
HEADER_ALIASES.put("公司名称", "货主名称");
HEADER_ALIASES.put("公司名稱", "货主名称");
}
public static List<MaterialBaseInfoDTO> importRows(InputStream is) throws Exception {