Merge branch 'dev' into feature/dev-0729
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
+4
-20
@@ -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
|
||||
#线上正式环境
|
||||
|
||||
+86
-7
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+32
@@ -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,如果使用默认分组可以不配置
|
||||
|
||||
+16
@@ -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"
|
||||
|
||||
+62
-17
@@ -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<>();
|
||||
}
|
||||
|
||||
+274
@@ -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;
|
||||
}
|
||||
}
|
||||
-3
@@ -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()));
|
||||
}
|
||||
|
||||
+6
-1
@@ -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());
|
||||
|
||||
+7
-3
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user