1463 WMS系统钢构包采购业务功能优化

This commit is contained in:
10001392 2026-05-11 15:34:48 +08:00
parent bd0da952ce
commit cf9c38a19e
12 changed files with 737 additions and 2 deletions

View File

@ -861,8 +861,7 @@ public class StructuralPackageOrderController extends BaseController {
VUtil.trueThrowBusinessError(Objects.isNull(tray)).throwMessage("托盘不存在");
VUtil.trueThrowBusinessError(!Objects.equals(tray.getState(), OrderState.Unpackaged.getState())).throwMessage("请勿重复打包");
DeliverStructuralPackageOrderVO order = structuralPackageOrderService.getInfo(tray.getOrderId());
// 获取订单下的所有物料项而不是特定托盘的物料项
List<WmsStructuralPackageOrderTrayItem> trayItems = structuralPackageOrderTrayItemService.getList(tray.getOrderId());
List<WmsStructuralPackageOrderTrayItem> trayItems = structuralPackageOrderTrayItemService.getListByTrayId(tray.getId());
List<String> materialNos = new ArrayList<>();
List<StructuralPackageMaterialQRCodeContentDTO> qrCodeDTOs = new ArrayList<>();
trayItems.forEach(it -> {
@ -1677,4 +1676,336 @@ public class StructuralPackageOrderController extends BaseController {
return ApiResult.success();
}
/**
* 钢构件打包提交 PDA使用
*/
@Transactional
@PostMapping("/packTraySubmit")
public ApiResult<PackTraySubmitResultVO> packTraySubmit(@Valid @RequestBody @NotNull PackTraySubmitQO request) {
// 1. 根据orderNo查询订单信息
List<DeliverStructuralPackageOrderVO> orders = structuralPackageOrderService.getList(request.getOrderNo());
VUtil.trueThrowBusinessError(CollectionUtil.isEmpty(orders)).throwMessage("订单不存在");
DeliverStructuralPackageOrderVO order = orders.get(0);
Long orderId = order.getId();
// 2. 获取该订单下的所有托盘项用于对比计划数量和实际数量
List<WmsStructuralPackageOrderTrayItem> allTrayItems = structuralPackageOrderTrayItemService.getList(orderId);
// 3. 计算每个物料的计划出货数量按物料编号汇总
Map<String, BigDecimal> plannedQtyMap = allTrayItems.stream()
.collect(Collectors.groupingBy(
WmsStructuralPackageOrderTrayItem::getMaterialNo,
Collectors.reducing(BigDecimal.ZERO, WmsStructuralPackageOrderTrayItem::getShipmentNum, BigDecimal::add)
));
// 4. 计算实际提交的数量按物料编号汇总
Map<String, Integer> actualQtyMap = request.getMaterialSummary().stream()
.collect(Collectors.toMap(
MaterialSummaryQO::getMaterialNo,
MaterialSummaryQO::getTotalQty,
(v1, v2) -> v1
));
// 5. 对比计划数量和实际数量找出差异
List<DiffItemVO> diffList = new ArrayList<>();
for (Map.Entry<String, BigDecimal> entry : plannedQtyMap.entrySet()) {
String materialNo = entry.getKey();
BigDecimal plannedQty = entry.getValue();
Integer actualQty = actualQtyMap.getOrDefault(materialNo, 0);
if (plannedQty.compareTo(new BigDecimal(actualQty)) != 0) {
// 找到物料描述
String materialDesc = allTrayItems.stream()
.filter(item -> StrUtil.equals(item.getMaterialNo(), materialNo))
.map(WmsStructuralPackageOrderTrayItem::getMaterialDesc)
.findFirst()
.orElse("");
DiffItemVO diffItem = new DiffItemVO();
diffItem.setMaterialNo(materialNo);
diffItem.setMaterialDesc(materialDesc);
diffItem.setShipmentNum(plannedQty.intValue());
diffItem.setActualQty(actualQty);
diffItem.setDiffQty(plannedQty.intValue() - actualQty);
diffList.add(diffItem);
}
}
// 6. 如果有差异返回差异信息不进行打包
if (CollectionUtil.isNotEmpty(diffList)) {
PackTraySubmitResultVO result = new PackTraySubmitResultVO();
result.setHasDiff(true);
result.setDiffList(diffList);
result.setMessage("存在数量差异请使用packTraySubmitWithDiff接口提交差异原因");
return ApiResult.success(result);
}
// 7. 无差异执行打包逻辑
List<WmsStructuralPackageOrderTray> trays = structuralPackageOrderTrayService.getList(orderId);
VUtil.trueThrowBusinessError(CollectionUtil.isEmpty(trays)).throwMessage("该订单没有托盘数据");
// 8. 校验所有托盘状态是否为未打包
for (TraySubmitQO trayQO : request.getTrays()) {
WmsStructuralPackageOrderTray tray = trays.stream()
.filter(t -> StrUtil.equals(t.getNo(), trayQO.getTrayNo()))
.findFirst()
.orElse(null);
VUtil.trueThrowBusinessError(Objects.isNull(tray)).throwMessage("托盘" + trayQO.getTrayNo() + "不存在");
VUtil.trueThrowBusinessError(!Objects.equals(tray.getState(), OrderState.Unpackaged.getState()))
.throwMessage("托盘" + trayQO.getTrayNo() + "请勿重复打包");
}
// 9. 处理每个托盘的物料
List<StructuralPackageMaterialQRCodeContentDTO> allQrCodeDTOs = new ArrayList<>();
for (TraySubmitQO trayQO : request.getTrays()) {
WmsStructuralPackageOrderTray tray = trays.stream()
.filter(t -> StrUtil.equals(t.getNo(), trayQO.getTrayNo()))
.findFirst()
.orElse(null);
// 获取托盘项
List<WmsStructuralPackageOrderTrayItem> trayItems = structuralPackageOrderTrayItemService.getListByTrayId(tray.getId());
// 处理托盘中的每个物料
for (TrayMaterialQO materialQO : trayQO.getItems()) {
WmsStructuralPackageOrderTrayItem trayItem = trayItems.stream()
.filter(item -> StrUtil.equals(item.getMaterialNo(), materialQO.getMaterialNo()))
.findFirst()
.orElse(null);
VUtil.trueThrowBusinessError(Objects.isNull(trayItem))
.throwMessage("托盘" + trayQO.getTrayNo() + "中物料" + materialQO.getMaterialNo() + "不存在");
// 更新托盘项的trayId
trayItem.setTrayId(tray.getId());
// 解析二维码
if (CollectionUtil.isNotEmpty(materialQO.getQrCodes())) {
List<StructuralPackageMaterialQRCodeContentDTO> dtos = materialQO.getQrCodes().stream()
.map(NoUtil::getStructuralPackageMaterialQRCodeContent)
.toList();
// 验证数量是否一致
BigDecimal scannedQty = dtos.stream()
.map(StructuralPackageMaterialQRCodeContentDTO::getNum)
.reduce(BigDecimal.ZERO, BigDecimal::add);
if (trayItem.getShipmentNum().compareTo(scannedQty) != 0) {
throw new NflgException(STATE.BusinessError, "托盘" + trayQO.getTrayNo() + "中物料" + materialQO.getMaterialNo()
+ "的数量不一致,应为:" + trayItem.getShipmentNum() + ",实际扫码:" + scannedQty);
}
// 设置二维码相关信息
dtos.forEach(dto -> {
dto.setPoNum(order.getExternalOrderNo());
dto.setPackageNo(order.getPackageNo());
dto.setTrayItemId(trayItem.getId());
});
allQrCodeDTOs.addAll(dtos);
}
}
// 批量更新托盘项的trayId
structuralPackageOrderTrayItemService.updateBatchById(trayItems);
// 更新托盘状态为已打包
VUtil.trueThrowBusinessError(!structuralPackageOrderTrayService.lambdaUpdate()
.set(WmsStructuralPackageOrderTray::getState, OrderState.Packaged.getState())
.set(WmsStructuralPackageOrderTray::getPackBy, UserUtil.getUserName())
.set(WmsStructuralPackageOrderTray::getPackTime, LocalDateTime.now())
.eq(WmsStructuralPackageOrderTray::getState, OrderState.Unpackaged.getState())
.eq(WmsStructuralPackageOrderTray::getId, tray.getId())
.update()).throwMessage("托盘" + trayQO.getTrayNo() + "打包失败,请重试");
}
// 10. 保存二维码扫描记录
saveMaterialQrCodeScanRecord(allQrCodeDTOs, 1);
// 11. 构建返回结果
PackTraySubmitResultVO result = new PackTraySubmitResultVO();
result.setHasDiff(false);
result.setDiffList(new ArrayList<>());
result.setMessage("打包成功");
return ApiResult.success(result);
}
/**
* 钢构件打包提交带差异原因PDA使用
*/
@Transactional
@PostMapping("/packTraySubmitWithDiff")
public ApiResult<PackTraySubmitWithDiffVO> packTraySubmitWithDiff(@Valid @RequestBody @NotNull PackTraySubmitWithDiffQO request) {
// 1. 校验差异列表中的remark必填
for (DiffItemQO diffItem : request.getDiffList()) {
VUtil.trueThrowBusinessError(StrUtil.isBlank(diffItem.getRemark()))
.throwMessage("物料" + diffItem.getMaterialNo() + "的差异原因不能为空");
}
// 2. 根据orderNo查询订单信息
List<DeliverStructuralPackageOrderVO> orders = structuralPackageOrderService.getList(request.getOrderNo());
VUtil.trueThrowBusinessError(CollectionUtil.isEmpty(orders)).throwMessage("订单不存在");
DeliverStructuralPackageOrderVO order = orders.get(0);
Long orderId = order.getId();
// 3. 获取该订单下的所有托盘
List<WmsStructuralPackageOrderTray> trays = structuralPackageOrderTrayService.getList(orderId);
VUtil.trueThrowBusinessError(CollectionUtil.isEmpty(trays)).throwMessage("该订单没有托盘数据");
// 4. 校验所有托盘状态是否为未打包
for (TraySubmitQO trayQO : request.getTrays()) {
WmsStructuralPackageOrderTray tray = trays.stream()
.filter(t -> StrUtil.equals(t.getNo(), trayQO.getTrayNo()))
.findFirst()
.orElse(null);
VUtil.trueThrowBusinessError(Objects.isNull(tray)).throwMessage("托盘" + trayQO.getTrayNo() + "不存在");
VUtil.trueThrowBusinessError(!Objects.equals(tray.getState(), OrderState.Unpackaged.getState()))
.throwMessage("托盘" + trayQO.getTrayNo() + "请勿重复打包");
}
// 5. 处理每个托盘的物料
List<StructuralPackageMaterialQRCodeContentDTO> allQrCodeDTOs = new ArrayList<>();
for (TraySubmitQO trayQO : request.getTrays()) {
WmsStructuralPackageOrderTray tray = trays.stream()
.filter(t -> StrUtil.equals(t.getNo(), trayQO.getTrayNo()))
.findFirst()
.orElse(null);
// 获取托盘项
List<WmsStructuralPackageOrderTrayItem> trayItems = structuralPackageOrderTrayItemService.getListByTrayId(tray.getId());
// 处理托盘中的每个物料
for (TrayMaterialQO materialQO : trayQO.getItems()) {
WmsStructuralPackageOrderTrayItem trayItem = trayItems.stream()
.filter(item -> StrUtil.equals(item.getMaterialNo(), materialQO.getMaterialNo()))
.findFirst()
.orElse(null);
VUtil.trueThrowBusinessError(Objects.isNull(trayItem))
.throwMessage("托盘" + trayQO.getTrayNo() + "中物料" + materialQO.getMaterialNo() + "不存在");
// 判断该物料是否在差异列表中
boolean hasDiff = request.getDiffList().stream()
.anyMatch(diff -> StrUtil.equals(diff.getMaterialNo(), materialQO.getMaterialNo()));
// 解析二维码
if (CollectionUtil.isNotEmpty(materialQO.getQrCodes())) {
List<StructuralPackageMaterialQRCodeContentDTO> dtos = materialQO.getQrCodes().stream()
.map(NoUtil::getStructuralPackageMaterialQRCodeContent)
.toList();
// 只有不在差异列表中的物料才需要校验数量一致性
if (!hasDiff) {
// 验证数量是否一致
BigDecimal scannedQty = dtos.stream()
.map(StructuralPackageMaterialQRCodeContentDTO::getNum)
.reduce(BigDecimal.ZERO, BigDecimal::add);
if (trayItem.getShipmentNum().compareTo(scannedQty) != 0) {
throw new NflgException(STATE.BusinessError, "托盘" + trayQO.getTrayNo() + "中物料" + materialQO.getMaterialNo()
+ "的数量不一致,应为:" + trayItem.getShipmentNum() + ",实际扫码:" + scannedQty);
}
}
// 设置二维码相关信息
dtos.forEach(dto -> {
dto.setPoNum(order.getExternalOrderNo());
dto.setPackageNo(order.getPackageNo());
dto.setTrayItemId(trayItem.getId());
});
allQrCodeDTOs.addAll(dtos);
}
}
// 更新托盘状态为已打包
VUtil.trueThrowBusinessError(!structuralPackageOrderTrayService.lambdaUpdate()
.set(WmsStructuralPackageOrderTray::getState, OrderState.Packaged.getState())
.set(WmsStructuralPackageOrderTray::getPackBy, UserUtil.getUserName())
.set(WmsStructuralPackageOrderTray::getPackTime, LocalDateTime.now())
.eq(WmsStructuralPackageOrderTray::getState, OrderState.Unpackaged.getState())
.eq(WmsStructuralPackageOrderTray::getId, tray.getId())
.update()).throwMessage("托盘" + trayQO.getTrayNo() + "打包失败,请重试");
}
// 6. 处理差异项保存到托盘项的备注中
List<WmsStructuralPackageOrderTrayItem> allTrayItems = structuralPackageOrderTrayItemService.getList(orderId);
for (DiffItemQO diffItem : request.getDiffList()) {
// 找到对应的托盘项
WmsStructuralPackageOrderTrayItem trayItem = allTrayItems.stream()
.filter(item -> StrUtil.equals(item.getMaterialNo(), diffItem.getMaterialNo()))
.findFirst()
.orElse(null);
if (Objects.nonNull(trayItem)) {
// 保存差异原因到托盘项的offlineRemark字段
trayItem.setOfflineRemark(diffItem.getRemark());
trayItem.setOfflineReceived(false);
}
}
// 批量更新托盘项
structuralPackageOrderTrayItemService.updateBatchById(allTrayItems.stream()
.filter(item -> StrUtil.isNotBlank(item.getOfflineRemark()))
.toList());
// 7. 如果有差异项标记订单为缺料
if (CollectionUtil.isNotEmpty(request.getDiffList())) {
structuralPackageOrderService.lambdaUpdate()
.set(WmsStructuralPackageOrder::getQuehuo, true)
.eq(WmsStructuralPackageOrder::getId, orderId)
.update();
}
// 8. 保存二维码扫描记录
saveMaterialQrCodeScanRecord(allQrCodeDTOs, 1);
// 9. 构建返回结果
PackTraySubmitWithDiffVO result = new PackTraySubmitWithDiffVO();
result.setOrderNo(request.getOrderNo());
// 转换托盘列表
List<TrayItemVO2> trayVOs = new ArrayList<>();
for (TraySubmitQO trayQO : request.getTrays()) {
TrayItemVO2 trayVO = new TrayItemVO2();
trayVO.setTrayNo(trayQO.getTrayNo());
trayVO.setOrderNo(trayQO.getOrderNo());
List<TrayMaterialVO> materialVOs = new ArrayList<>();
for (TrayMaterialQO materialQO : trayQO.getItems()) {
TrayMaterialVO materialVO = new TrayMaterialVO();
materialVO.setMaterialNo(materialQO.getMaterialNo());
materialVO.setMaterialDesc(materialQO.getMaterialDesc());
materialVO.setQrCodes(materialQO.getQrCodes());
materialVO.setTotalQty(materialQO.getTotalQty());
materialVOs.add(materialVO);
}
trayVO.setItems(materialVOs);
trayVOs.add(trayVO);
}
result.setTrays(trayVOs);
// 转换差异列表
List<DiffItemVO> diffVOs = new ArrayList<>();
for (DiffItemQO diffQO : request.getDiffList()) {
DiffItemVO diffVO = new DiffItemVO();
diffVO.setMaterialNo(diffQO.getMaterialNo());
diffVO.setMaterialDesc(diffQO.getMaterialDesc());
diffVO.setShipmentNum(diffQO.getShipmentNum());
diffVO.setActualQty(diffQO.getActualQty());
diffVO.setDiffQty(diffQO.getDiffQty());
diffVO.setRemark(diffQO.getRemark());
diffVOs.add(diffVO);
}
result.setDiffList(diffVOs);
return ApiResult.success(result);
}
}

View File

@ -0,0 +1,52 @@
package com.nflg.wms.common.pojo.qo;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serializable;
/**
* 差异项QO
*/
@Data
public class DiffItemQO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 物料编号
*/
@NotBlank(message = "物料编号不能为空")
private String materialNo;
/**
* 物料描述
*/
@NotBlank(message = "物料描述不能为空")
private String materialDesc;
/**
* 出货数量
*/
@NotNull(message = "出货数量不能为空")
private Integer shipmentNum;
/**
* 已装数量
*/
@NotNull(message = "已装数量不能为空")
private Integer actualQty;
/**
* 差异数量
*/
@NotNull(message = "差异数量不能为空")
private Integer diffQty;
/**
* 差异原因描述必填
*/
@NotBlank(message = "差异原因不能为空")
private String remark;
}

View File

@ -0,0 +1,34 @@
package com.nflg.wms.common.pojo.qo;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serializable;
/**
* 物料汇总QO按物料编号统计
*/
@Data
public class MaterialSummaryQO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 物料编号
*/
@NotBlank(message = "物料编号不能为空")
private String materialNo;
/**
* 物料描述
*/
@NotBlank(message = "物料描述不能为空")
private String materialDesc;
/**
* 所有托盘该物料的总数量
*/
@NotNull(message = "物料总数量不能为空")
private Integer totalQty;
}

View File

@ -0,0 +1,38 @@
package com.nflg.wms.common.pojo.qo;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 钢构件打包提交QO
*/
@Data
public class PackTraySubmitQO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 采购单号/钢构件订单号
*/
@NotBlank(message = "订单号不能为空")
private String orderNo;
/**
* 托盘列表
*/
@NotEmpty(message = "托盘列表不能为空")
@Valid
private List<TraySubmitQO> trays;
/**
* 物料汇总列表按物料编号统计的总数量
*/
@NotEmpty(message = "物料汇总列表不能为空")
@Valid
private List<MaterialSummaryQO> materialSummary;
}

View File

@ -0,0 +1,38 @@
package com.nflg.wms.common.pojo.qo;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 钢构件打包提交带差异原因QO
*/
@Data
public class PackTraySubmitWithDiffQO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 采购单号/钢构件订单号
*/
@NotBlank(message = "订单号不能为空")
private String orderNo;
/**
* 托盘列表
*/
@NotEmpty(message = "托盘列表不能为空")
@Valid
private List<TraySubmitQO> trays;
/**
* 差异列表
*/
@NotEmpty(message = "差异列表不能为空")
@Valid
private List<DiffItemQO> diffList;
}

View File

@ -0,0 +1,40 @@
package com.nflg.wms.common.pojo.qo;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 托盘物料QO
*/
@Data
public class TrayMaterialQO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 物料编号
*/
@NotBlank(message = "物料编号不能为空")
private String materialNo;
/**
* 物料描述
*/
@NotBlank(message = "物料描述不能为空")
private String materialDesc;
/**
* 二维码列表该物料的所有条码
*/
private List<String> qrCodes;
/**
* 该物料的总数量
*/
@NotNull(message = "物料数量不能为空")
private Integer totalQty;
}

View File

@ -0,0 +1,37 @@
package com.nflg.wms.common.pojo.qo;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 托盘提交QO
*/
@Data
public class TraySubmitQO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 托盘号
*/
@NotBlank(message = "托盘号不能为空")
private String trayNo;
/**
* 订单号
*/
@NotBlank(message = "订单号不能为空")
private String orderNo;
/**
* 托盘内物料明细
*/
@NotEmpty(message = "物料明细不能为空")
@Valid
private List<TrayMaterialQO> items;
}

View File

@ -0,0 +1,43 @@
package com.nflg.wms.common.pojo.vo;
import lombok.Data;
import java.io.Serializable;
/**
* 差异项VO
*/
@Data
public class DiffItemVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 物料编号
*/
private String materialNo;
/**
* 物料描述
*/
private String materialDesc;
/**
* 出货数量
*/
private Integer shipmentNum;
/**
* 已装数量
*/
private Integer actualQty;
/**
* 差异数量
*/
private Integer diffQty;
/**
* 差异原因描述必填
*/
private String remark;
}

View File

@ -0,0 +1,29 @@
package com.nflg.wms.common.pojo.vo;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 打包提交返回结果VO
*/
@Data
public class PackTraySubmitResultVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 是否有差异
*/
private Boolean hasDiff;
/**
* 差异列表如果有差异
*/
private List<DiffItemVO> diffList;
/**
* 提示信息
*/
private String message;
}

View File

@ -0,0 +1,29 @@
package com.nflg.wms.common.pojo.vo;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 钢构件打包提交带差异原因VO
*/
@Data
public class PackTraySubmitWithDiffVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 采购单号/钢构件订单号
*/
private String orderNo;
/**
* 托盘列表
*/
private List<TrayItemVO2> trays;
/**
* 差异列表
*/
private List<DiffItemVO> diffList;
}

View File

@ -0,0 +1,30 @@
package com.nflg.wms.common.pojo.vo;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 托盘项VO
*/
@Data
public class TrayItemVO2 implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 托盘号
*/
private String trayNo;
/**
* 订单号
*/
private String orderNo;
/**
* 托盘内物料明细
*/
private List<TrayMaterialVO> items;
}

View File

@ -0,0 +1,34 @@
package com.nflg.wms.common.pojo.vo;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 托盘物料VO
*/
@Data
public class TrayMaterialVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 物料编号
*/
private String materialNo;
/**
* 物料描述
*/
private String materialDesc;
/**
* 二维码列表该物料的所有条码
*/
private List<String> qrCodes;
/**
* 该物料的总数量
*/
private Integer totalQty;
}