feat:参照 WMS 入库单规则,通宇「入仓委托单」需新增「入库仓库」字段,并设置为必填项,设置「入库仓库」校验具体

fix:修复【入库管理-收货管理】中批量设置货物参数(数量/面积/体积/尺寸)时,部分字段留空导致已有数据被重置丢失的问题。
This commit is contained in:
hjx
2026-08-05 09:49:19 +08:00
parent d268718f9b
commit b80668145a
4 changed files with 188 additions and 33 deletions
@@ -181,17 +181,9 @@ public class ReservationStockInOrderApplicationService {
* 新增入库单
*/
public Boolean insert(ReservationStockInOrderDO stockInOrderDO) {
// 如果仓库ID为空,从当前登录用户的关联仓库中获取
// 仓库为必填项,前端必须选择入库仓库
if (stockInOrderDO.getWarehouseId() == null) {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser == null) {
throw new ServiceException("登录已失效,请重新登录");
}
AssociationWarehouseCacheDO associationWarehouseCacheDO = SystemServiceCacheUtil.getAssociationWarehouseCache(loginUser.getUserid());
if (associationWarehouseCacheDO == null || associationWarehouseCacheDO.getWarehouseId() == null) {
throw new ServiceException("当前用户未配置关联仓库,请先配置仓库");
}
stockInOrderDO.setWarehouseId(associationWarehouseCacheDO.getWarehouseId());
throw new ServiceException("请选择入库仓库");
}
//设置仓库
setWarehouseInfo(stockInOrderDO);
@@ -25,6 +25,7 @@ import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
@@ -127,6 +128,17 @@ public class StatementStatisticsService {
}
}
List<ImmediateInventoryPO> immediateInventoryPOS = materialInventoryDomainService.queryImmediateInventoryList(immediateInventoryDO);
// 库存数量合计:与列表同筛选条件、不分页统计全部库存数量,回填到每行
if (!CollectionUtils.isEmpty(immediateInventoryPOS)) {
BigDecimal totalInventoryQuantity = materialInventoryDomainService.sumImmediateInventoryQuantity(immediateInventoryDO);
if (totalInventoryQuantity == null) {
totalInventoryQuantity = BigDecimal.ZERO;
}
BigDecimal finalTotal = totalInventoryQuantity;
immediateInventoryPOS.forEach(po -> po.setTotalInventoryQuantity(finalTotal));
}
// 性能优化:批量获取批次属性,避免N+1查询问题
// 1. 收集所有唯一的materialBaseInfoId
Set<Long> materialBaseInfoIdSet = immediateInventoryPOS.stream()
@@ -2220,24 +2220,31 @@ public class StockReceiptOrderApplicationService {
}
/**
* PDA 抄码收货不采集毛重、体积、面积:在装配前将对应请求字段置 0,避免 getInfo 带回的累计展示值被当作本次增量。
* PDA 抄码收货不采集毛重、体积、面积:在装配前将对应请求字段置 null
* 利用 MyBatis-Plus NOT_NULL 策略跳过这些字段,避免覆盖已有累计数据。
* @param lines 收货明细列表
*/
private static void normalizePdaCopyReceiptGrossVolumeAreaToZero(List<ReceiptMaterialDetailDO> lines) {
// 没有明细时,直接返回
if (CollectionUtils.isEmpty(lines)) {
return;
}
// 有明细时,遍历
for (ReceiptMaterialDetailDO line : lines) {
// 跳过 line 为 null
if (line == null) {
continue;
}
// 在装配之前将字段置为 null
if (isPdaCopyReceiptLine(line)) {
line.setReceiptGrossWeight(BigDecimal.ZERO);
line.setTotalGrossWeight(BigDecimal.ZERO);
line.setReceiptVolume(BigDecimal.ZERO);
line.setTotalVolume(BigDecimal.ZERO);
line.setReceiptArea(BigDecimal.ZERO);
line.setTotalArea(BigDecimal.ZERO);
line.setReceiptGrossWeight(null);
line.setTotalGrossWeight(null);
line.setReceiptVolume(null);
line.setTotalVolume(null);
line.setReceiptArea(null);
line.setTotalArea(null);
}
// 递归,确保每一个子行都会独立通过 isPdaCopyReceiptLine 判断是否需要清字段
normalizePdaCopyReceiptGrossVolumeAreaToZero(line.getChildren());
}
}
@@ -459,6 +459,7 @@ public class ReceiptMaterialDetailImpl extends ServiceImpl<ReceiptMaterialDetail
ReceiptMaterialDetail receiptMaterialDetailChild = new ReceiptMaterialDetail();
BeanUtils.copyProperties(receiptMaterialDetailDOChild, receiptMaterialDetailChild);
alignPersistedReceiptQuantityToCumulative(receiptMaterialDetailDOChild, receiptMaterialDetailChild);
normalizeZeroMetricsToNull(receiptMaterialDetailChild);
receiptMaterialDetailChild.setCreateBy(loginUser.getUserid());
receiptMaterialDetailChild.setCreateByName(loginUser.getUsername());
receiptMaterialDetailChild.setCreateTime(new Date());
@@ -474,9 +475,12 @@ public class ReceiptMaterialDetailImpl extends ServiceImpl<ReceiptMaterialDetail
}
}
}
ReceiptMaterialDetail receiptMaterialDetail = new ReceiptMaterialDetail();
BeanUtils.copyProperties(receiptMaterialDetailDO, receiptMaterialDetail);
alignPersistedReceiptQuantityToCumulative(receiptMaterialDetailDO, receiptMaterialDetail);
// total* 字段为 0 或空字符串时置为 null避免 MyBatis-Plus NOT_NULL 策略下覆盖已有数据
normalizeZeroMetricsToNull(receiptMaterialDetail);
receiptMaterialDetail.setUpdateBy(loginUser.getUserid());
receiptMaterialDetail.setUpdateByName(loginUser.getUsername());
receiptMaterialDetail.setUpdateTime(new Date());
@@ -488,23 +492,25 @@ public class ReceiptMaterialDetailImpl extends ServiceImpl<ReceiptMaterialDetail
}
//保存物料明细信息
saveOrUpdateBatch(receiptMaterialDetailList);
//批量新增/更新物料更多属性保留已有值仅更新有值的属性不再先删后插
mergeMaterialMoreDetail(materialMoreDetailList, stockReceiptOrderDO, receiptMaterialDetailDOList);
//批量新增/更新物料更多属性先删除该收货单下所有明细的旧批次属性再插入新值避免重复插入导致更新失效
if (!CollectionUtils.isEmpty(materialMoreDetailList)){
String receiptOrderNumber = stockReceiptOrderDO.getReceiptOrderNumber();
if (StringUtils.isBlank(receiptOrderNumber) && !CollectionUtils.isEmpty(receiptMaterialDetailDOList)) {
receiptOrderNumber = receiptMaterialDetailDOList.get(0).getReceiptOrderNumber();
}
if (StringUtils.isNotBlank(receiptOrderNumber)) {
java.util.Set<Long> uniqueIds = collectReceiptDetailUniqueIds(receiptMaterialDetailDOList);
if (!uniqueIds.isEmpty()) {
IMaterialMoreDetailService.remove(new QueryWrapper<MaterialMoreDetail>()
.eq("order_number", receiptOrderNumber)
.in("material_detail_unique_id", uniqueIds)
.eq("del_flag", 1));
}
}
IMaterialMoreDetailService.saveBatch(materialMoreDetailList);
}
// if (!CollectionUtils.isEmpty(materialMoreDetailList)){
// String receiptOrderNumber = stockReceiptOrderDO.getReceiptOrderNumber();
// if (StringUtils.isBlank(receiptOrderNumber) && !CollectionUtils.isEmpty(receiptMaterialDetailDOList)) {
// receiptOrderNumber = receiptMaterialDetailDOList.get(0).getReceiptOrderNumber();
// }
// if (StringUtils.isNotBlank(receiptOrderNumber)) {
// java.util.Set<Long> uniqueIds = collectReceiptDetailUniqueIds(receiptMaterialDetailDOList);
// if (!uniqueIds.isEmpty()) {
// IMaterialMoreDetailService.remove(new QueryWrapper<MaterialMoreDetail>()
// .eq("order_number", receiptOrderNumber)
// .in("material_detail_unique_id", uniqueIds)
// .eq("del_flag", 1));
// }
// }
// IMaterialMoreDetailService.saveBatch(materialMoreDetailList);
// }
//批量新增序列号
if (!CollectionUtils.isEmpty(materialDetailSerialNumberList)){
inMaterialDetailSerialNumberService.saveBatch(materialDetailSerialNumberList);
@@ -513,6 +519,144 @@ public class ReceiptMaterialDetailImpl extends ServiceImpl<ReceiptMaterialDetail
updateInMaterialDetailFromAttributeOption(receiptMaterialDetailDOList);
}
/**
* 批量合并物料更多属性有值则更新无值跳过保留旧值不存在则新增
* 替代原先 DELETE 全部再 INSERT的策略避免未填写字段丢失已有数据
* @param materialMoreDetailList 本次收货提交的批次属性列表
* @param stockReceiptOrderDO 收货单信息用于获取收货单号
* @param receiptMaterialDetailDOList 收货明细列表用于提取 uniqueId 集合
*/
private void mergeMaterialMoreDetail(List<MaterialMoreDetail> materialMoreDetailList,
StockReceiptOrderDO stockReceiptOrderDO,
List<ReceiptMaterialDetailDO> receiptMaterialDetailDOList) {
// 无批次属性则直接返回
if (CollectionUtils.isEmpty(materialMoreDetailList)) {
return;
}
// 无收货单号无法做增量更新退回全量插入
String receiptOrderNumber = stockReceiptOrderDO.getReceiptOrderNumber();
if (StringUtils.isBlank(receiptOrderNumber) && !CollectionUtils.isEmpty(receiptMaterialDetailDOList)) {
receiptOrderNumber = receiptMaterialDetailDOList.get(0).getReceiptOrderNumber();
}
// 无收货单号无法做增量更新退回全量插入
if (StringUtils.isBlank(receiptOrderNumber)) {
IMaterialMoreDetailService.saveBatch(materialMoreDetailList);
return;
}
// 无明细唯一ID无法做增量更新退回全量插入
java.util.Set<Long> uniqueIds = collectReceiptDetailUniqueIds(receiptMaterialDetailDOList);
if (uniqueIds.isEmpty()) {
IMaterialMoreDetailService.saveBatch(materialMoreDetailList);
return;
}
// 查询已存在的批次属性记录
List<MaterialMoreDetail> existingList = IMaterialMoreDetailService.list(
new QueryWrapper<MaterialMoreDetail>()
.eq("order_number", receiptOrderNumber) // 收货单号
.in("material_detail_unique_id", uniqueIds) // 明细唯一ID
.eq("del_flag", 1)); // 删除标志
// materialDetailUniqueId + batchDetailId 构建索引
Map<String, MaterialMoreDetail> existingMap = new HashMap<>();
for (MaterialMoreDetail existing : existingList) {
String key = buildMaterialMoreDetailKey(existing.getMaterialDetailUniqueId(), existing.getBatchDetailId());
existingMap.put(key, existing);
}
// 分离需要更新的 以及 需要新增的跳过空值保留旧数据
List<MaterialMoreDetail> toUpdate = new ArrayList<>();
List<MaterialMoreDetail> toInsert = new ArrayList<>();
for (MaterialMoreDetail incoming : materialMoreDetailList) {
// attributeValue 为空/空白时跳过不覆盖已有数据
if (StringUtils.isBlank(incoming.getAttributeValue())) {
continue;
}
// materialDetailUniqueId + batchDetailId 构建索引
String key = buildMaterialMoreDetailKey(incoming.getMaterialDetailUniqueId(), incoming.getBatchDetailId());
// 已存在的批次属性记录
MaterialMoreDetail existing = existingMap.get(key);
// 存在则更新 不存在则新增
if (existing != null) {
// 设置主键走 update保留已有记录的 id
incoming.setMaterialMoreDetailId(existing.getMaterialMoreDetailId());
// 更新现有记录
toUpdate.add(incoming);
} else {
// 插入新的记录
toInsert.add(incoming);
}
}
// 判断需更新的记录是否存在存在则更新
if (!toUpdate.isEmpty()) {
IMaterialMoreDetailService.updateBatchById(toUpdate);
}
// 判断是否存在需新增的记录存在则批量插入
if (!toInsert.isEmpty()) {
IMaterialMoreDetailService.saveBatch(toInsert);
}
}
/**
* 构建批次属性索引
* @param materialDetailUniqueId 明细唯一ID
* @param batchDetailId 批次属性ID
* @return 批次属性索引
*/
private static String buildMaterialMoreDetailKey(Long materialDetailUniqueId, Long batchDetailId) {
return (materialDetailUniqueId != null ? materialDetailUniqueId : "null") + "_"
+ (batchDetailId != null ? batchDetailId : "null");
}
/**
* total* 指标字段中值为 0 的置为 null
* MyBatis-Plus 默认 NOT_NULL 策略下null 不参与 SET 子句已有数据得以保留
* 注意仅对未填写场景生效用户确实需要填 0 的极端场景不受影响因前端通常传 null 表示未填
* @param receiptMaterialDetail 收货明细
* 需保证 total* 字段已赋值
* 需保证 alreadyReceipt* 字段已赋值
* 静态方法修改 receiptMaterialDetail 参数
**/
private static void normalizeZeroMetricsToNull(ReceiptMaterialDetail receiptMaterialDetail) {
// entity null 时直接返回
if (receiptMaterialDetail == null) {
return;
}
// receiptMaterialDetail totalNetWeight 字段为 0 时置为 null
if (isZeroBigDecimal(receiptMaterialDetail.getTotalNetWeight())) {
receiptMaterialDetail.setTotalNetWeight(null);
}
// receiptMaterialDetail totalGrossWeight 字段为 0 时置为 null
if (isZeroBigDecimal(receiptMaterialDetail.getTotalGrossWeight())) {
receiptMaterialDetail.setTotalGrossWeight(null);
}
// receiptMaterialDetail totalVolume 字段为 0 时置为 null
if (isZeroBigDecimal(receiptMaterialDetail.getTotalVolume())) {
receiptMaterialDetail.setTotalVolume(null);
}
// receiptMaterialDetail totalArea 字段为 0 时置为 null
if (isZeroBigDecimal(receiptMaterialDetail.getTotalArea())) {
receiptMaterialDetail.setTotalArea(null);
}
// receiptMaterialDetail alreadyReceiptNetWeight 字段为 0 时置为 null
if (isZeroBigDecimal(receiptMaterialDetail.getAlreadyReceiptNetWeight())) {
receiptMaterialDetail.setAlreadyReceiptNetWeight(null);
}
// receiptMaterialDetail alreadyReceiptGrossWeight 字段为 0 时置为 null
if (isZeroBigDecimal(receiptMaterialDetail.getAlreadyReceiptGrossWeight())) {
receiptMaterialDetail.setAlreadyReceiptGrossWeight(null);
}
// receiptMaterialDetail alreadyReceiptVolume 字段为 0 时置为 null
if (isZeroBigDecimal(receiptMaterialDetail.getAlreadyReceiptVolume())) {
receiptMaterialDetail.setAlreadyReceiptVolume(null);
}
// receiptMaterialDetail alreadyReceiptArea 字段为 0 时置为 null
if (isZeroBigDecimal(receiptMaterialDetail.getAlreadyReceiptArea())) {
receiptMaterialDetail.setAlreadyReceiptArea(null);
}
}
private static boolean isZeroBigDecimal(BigDecimal val) {
return val != null && val.compareTo(BigDecimal.ZERO) == 0;
}
private void setMore(ReceiptMaterialDetailDO receiptMaterialDetailDO) {
List<MaterialMoreDetailDO> materialMoreDetailList = receiptMaterialDetailDO.getMaterialMoreDetailList();
if (!CollectionUtils.isEmpty(materialMoreDetailList)) {