Merge branch 'dev-ty1.2' into dev
This commit is contained in:
+92
@@ -1176,6 +1176,98 @@ public class StockOutOrderApplicationService {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* OMS下发前库存校验:按库存ID聚合各明细计划数量,逐一比对WMS可用库存,
|
||||
* 全部比对完成后汇总所有不足项一次性抛错。用于OMS在下发推送前拦截
|
||||
* (此前单据先落库、分配才失败,会在WMS残留"已审核"状态的垃圾单)
|
||||
*/
|
||||
public void checkOmsAssignInventory(List<OutMaterialDetail> outMaterialDetails) {
|
||||
if (CollectionUtils.isEmpty(outMaterialDetails)) {
|
||||
return;
|
||||
}
|
||||
//重复下发保护:该单号在WMS已存在活动单据时直接拦截(继续下发会删改已分配明细、
|
||||
//失败补偿还会误删正在拣货的单和拣货单)。历史残留单据需先在OMS取消下发后再重试
|
||||
String outOrderNumber = outMaterialDetails.get(0).getOutOrderNumber();
|
||||
if (outOrderNumber != null && !outOrderNumber.isEmpty()) {
|
||||
StockOutOrder existOrder = stockOutOrderService.getOne(new QueryWrapper<StockOutOrder>().lambda()
|
||||
.eq(StockOutOrder::getOutOrderNumber, outOrderNumber)
|
||||
.eq(StockOutOrder::getDelFlag, 1), false);
|
||||
if (existOrder != null) {
|
||||
throw new ServiceException("WMS已存在该出库单(当前状态:" + stockOutOrderStatusName(existOrder.getStatus())
|
||||
+ "),请勿重复下发;若为历史残留单据,请先取消下发后重试");
|
||||
}
|
||||
}
|
||||
//同一库存被多条明细引用时聚合需求量再比较(单条比会漏判)
|
||||
Map<Long, BigDecimal> needByInventoryId = new LinkedHashMap<>();
|
||||
Map<Long, String> materialDescByInventoryId = new HashMap<>();
|
||||
for (OutMaterialDetail detail : outMaterialDetails) {
|
||||
if (detail.getMaterialInventoryId() == null) {
|
||||
continue;
|
||||
}
|
||||
BigDecimal need = detail.getQuantity() == null ? BigDecimal.ZERO : detail.getQuantity();
|
||||
needByInventoryId.merge(detail.getMaterialInventoryId(), need, BigDecimal::add);
|
||||
materialDescByInventoryId.putIfAbsent(detail.getMaterialInventoryId(), buildMaterialDesc(detail));
|
||||
}
|
||||
List<String> errors = new ArrayList<>();
|
||||
for (Map.Entry<Long, BigDecimal> entry : needByInventoryId.entrySet()) {
|
||||
Long materialInventoryId = entry.getKey();
|
||||
BigDecimal need = entry.getValue();
|
||||
String materialDesc = materialDescByInventoryId.getOrDefault(materialInventoryId, "库存ID=" + materialInventoryId);
|
||||
if (materialInventoryId <= 0) {
|
||||
errors.add("物料[" + materialDesc + "]未关联库存,合计需求数量:" + need.stripTrailingZeros().toPlainString());
|
||||
continue;
|
||||
}
|
||||
MaterialInventory materialInventory = materialInventoryMapper.selectById(materialInventoryId);
|
||||
if (materialInventory == null) {
|
||||
errors.add("物料[" + materialDesc + "]库存不存在,库存ID=" + materialInventoryId);
|
||||
continue;
|
||||
}
|
||||
BigDecimal available = materialInventory.getAllocationQuantity() == null
|
||||
? BigDecimal.ZERO : materialInventory.getAllocationQuantity();
|
||||
if (available.compareTo(need) < 0) {
|
||||
errors.add(String.format("物料[%s]可用库存不足:需要%s,当前可用%s",
|
||||
materialDesc, need.stripTrailingZeros().toPlainString(), available.stripTrailingZeros().toPlainString()));
|
||||
}
|
||||
}
|
||||
if (!errors.isEmpty()) {
|
||||
throw new ServiceException("下发前库存校验未通过(共" + errors.size() + "项):" + String.join(";", errors));
|
||||
}
|
||||
}
|
||||
|
||||
private String buildMaterialDesc(OutMaterialDetail detail) {
|
||||
if (detail.getMaterialCode() != null) {
|
||||
return detail.getMaterialCode() + (detail.getMaterialName() != null ? "-" + detail.getMaterialName() : "");
|
||||
}
|
||||
if (detail.getMaterialName() != null) {
|
||||
return detail.getMaterialName();
|
||||
}
|
||||
return "未知物料";
|
||||
}
|
||||
|
||||
/**
|
||||
* 出库单状态码转名称(用于校验提示),未知状态码原样返回
|
||||
*/
|
||||
private String stockOutOrderStatusName(Integer status) {
|
||||
if (status == null) {
|
||||
return "未知";
|
||||
}
|
||||
switch (status) {
|
||||
case 1: return "已创建";
|
||||
case 2: return "已审核";
|
||||
case 3: return "部分分配";
|
||||
case 4: return "已分配";
|
||||
case 5: return "波次中";
|
||||
case 6: return "拣货中";
|
||||
case 7: return "拣货完成";
|
||||
case 8: return "复核中";
|
||||
case 9: return "已出库";
|
||||
case 10: return "取消";
|
||||
case 11: return "关闭";
|
||||
case 12: return "已复核";
|
||||
default: return "状态码" + status;
|
||||
}
|
||||
}
|
||||
|
||||
/*public Boolean autoAssign(StockOutOrderDO stockOutOrderDO){
|
||||
StockOutOrderDO assembleStockOutOrderDO = assembleParamByOutOrder(stockOutOrderDO);
|
||||
return stockOutOrderDomainService.autoAssign(assembleStockOutOrderDO);
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.mhd.wms.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.mhd.common.core.jackson.FlexibleDateDeserializer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* JSON 日期反序列化兼容配置:Date 字段兼容 yyyy-MM-dd(纯日期)等格式。
|
||||
*
|
||||
* 背景:库存查询日期只保留年月日后,前端回传纯日期(如 createTime=2026-07-06)时,
|
||||
* 实体/DTO 上 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 严格格式反序列化报
|
||||
* InvalidFormatException。注册 FlexibleDateDeserializer 后优先按字段 @JsonFormat
|
||||
* 严格解析,失败时兼容纯日期等常见格式(与 OMS 库存查询一致)。
|
||||
*
|
||||
* Spring Boot 会自动把容器中的 Module Bean 注册进 MVC 使用的 ObjectMapper。
|
||||
*
|
||||
* @author rcx
|
||||
* @date 2026-09-05
|
||||
*/
|
||||
@Configuration
|
||||
public class DateCompatibilityConfig {
|
||||
|
||||
@Bean
|
||||
public Module flexibleDateModule() {
|
||||
SimpleModule module = new SimpleModule("wms-flexible-date-module");
|
||||
module.addDeserializer(Date.class, new FlexibleDateDeserializer());
|
||||
return module;
|
||||
}
|
||||
}
|
||||
+21
-9
@@ -2558,6 +2558,7 @@ public class StockOutOrderDomainService {
|
||||
stockOutOrderService.save(stockOutOrder);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void cancelExecutionOrder(StockOutOrder stockOutOrder) {
|
||||
String outOrderNumber = stockOutOrder.getOutOrderNumber();
|
||||
StockOutOrder stockOutOrderDB = stockOutOrderService.getOne(new LambdaQueryWrapper<StockOutOrder>()
|
||||
@@ -2566,23 +2567,33 @@ public class StockOutOrderDomainService {
|
||||
if (stockOutOrderDB == null) {
|
||||
throw new ServiceException("出库单不存在");
|
||||
}
|
||||
//逻辑删除配送明细(放在最前:所有清理路径——含下方无明细提前返回——统一先清配送明细,避免残留)
|
||||
deliveryDetailsLinkService.update(new UpdateWrapper<com.mhd.wms.domain.deliveryDetailsLink.entity.DeliveryDetailsLink>().lambda()
|
||||
.set(com.mhd.wms.domain.deliveryDetailsLink.entity.DeliveryDetailsLink::getDelFlag, 2)
|
||||
.eq(com.mhd.wms.domain.deliveryDetailsLink.entity.DeliveryDetailsLink::getOrderNumber, outOrderNumber)
|
||||
.eq(com.mhd.wms.domain.deliveryDetailsLink.entity.DeliveryDetailsLink::getDelFlag, 1));
|
||||
List<OutMaterialDetail> materialDetails = outMaterialDetailService.list(new LambdaQueryWrapper<OutMaterialDetail>()
|
||||
.eq(OutMaterialDetail::getOutOrderNumber, outOrderNumber)
|
||||
.eq(OutMaterialDetail::getDelFlag, 1));
|
||||
if (CollectionUtils.isEmpty(materialDetails)) {
|
||||
throw new ServiceException("出库单明细不存在");
|
||||
//无明细(下发中途失败的补偿清理场景):没有可还原的库存,直接逻辑删除单头即可
|
||||
stockOutOrderDB.setDelFlag(2);
|
||||
stockOutOrderService.updateById(stockOutOrderDB);
|
||||
return;
|
||||
}
|
||||
for (OutMaterialDetail materialDetail : materialDetails) {
|
||||
//还原库存
|
||||
BigDecimal allocationQuantity = materialDetail.getAllocationQuantity();
|
||||
Long materialInventoryId = materialDetail.getMaterialInventoryId();
|
||||
MaterialInventory materialInventory = materialInventoryMapper.selectById(materialInventoryId);
|
||||
if (materialInventory == null) {
|
||||
throw new ServiceException("库存信息不存在");
|
||||
//以下两种情况都无可还原的冻结量,跳过还原直接删单,保证此类单据也能被取消/补偿清理:
|
||||
//1.库存不存在(如OMS漏传库存ID的历史脏单);
|
||||
//2.未分配的明细(allocationQuantity为空或0,如下发失败后的补偿清理)
|
||||
if (materialInventory != null && allocationQuantity != null && allocationQuantity.compareTo(BigDecimal.ZERO) > 0) {
|
||||
materialInventory.setAllocationQuantity((materialInventory.getAllocationQuantity() == null ? BigDecimal.ZERO : materialInventory.getAllocationQuantity()).add(allocationQuantity));
|
||||
materialInventory.setFreezeQuantity((materialInventory.getFreezeQuantity() == null ? BigDecimal.ZERO : materialInventory.getFreezeQuantity()).subtract(allocationQuantity));
|
||||
materialInventoryMapper.updateById(materialInventory);
|
||||
}
|
||||
materialInventory.setAllocationQuantity(materialInventory.getAllocationQuantity().add(allocationQuantity));
|
||||
materialInventory.setFreezeQuantity(materialInventory.getFreezeQuantity().subtract(allocationQuantity));
|
||||
materialInventoryMapper.updateById(materialInventory);
|
||||
//逻辑删除库存调整记录
|
||||
Long uniqueId = materialDetail.getUniqueId();
|
||||
String relateNo = outOrderNumber+"#"+uniqueId;
|
||||
@@ -2593,9 +2604,9 @@ public class StockOutOrderDomainService {
|
||||
materialDetail.setDelFlag(2);
|
||||
outMaterialDetailService.updateById(materialDetail);
|
||||
}
|
||||
//逻辑删除出库单
|
||||
stockOutOrder.setDelFlag(2);
|
||||
stockOutOrderService.updateById(stockOutOrder);
|
||||
//逻辑删除出库单(用按单号查出的DB对象更新:OMS补偿清理只传单号,传入对象无主键,直接updateById不生效)
|
||||
stockOutOrderDB.setDelFlag(2);
|
||||
stockOutOrderService.updateById(stockOutOrderDB);
|
||||
|
||||
PickingOrder pickingOrder = pickingOrderService.getOne(new LambdaQueryWrapper<PickingOrder>()
|
||||
.eq(PickingOrder::getDelFlag, 1)
|
||||
@@ -2609,6 +2620,7 @@ public class StockOutOrderDomainService {
|
||||
pickingMaterialDetail.setDelFlag(2);
|
||||
pickingMaterialDetailService.updateById(pickingMaterialDetail);
|
||||
}
|
||||
pickingOrder.setDelFlag(2);
|
||||
pickingOrderService.updateById(pickingOrder);
|
||||
}
|
||||
}
|
||||
|
||||
+12
-1
@@ -544,10 +544,21 @@ public class StockOutOrderApi extends BaseController {
|
||||
stockOutOrderApplicationService.omsOrderAddDetail(outMaterialDetails);
|
||||
}
|
||||
|
||||
/**
|
||||
* oms下发前库存校验:不足时返回错误,OMS据此拦截下发,避免单据落库后分配失败残留垃圾单
|
||||
*/
|
||||
@ApiOperation("oms下发前库存校验")
|
||||
@PostMapping(value = "/checkOmsAssignInventory")
|
||||
public AjaxResult checkOmsAssignInventory(@RequestBody List<OutMaterialDetail> outMaterialDetails) {
|
||||
stockOutOrderApplicationService.checkOmsAssignInventory(outMaterialDetails);
|
||||
return AjaxResult.success("库存校验通过");
|
||||
}
|
||||
|
||||
@ApiOperation("oms下发单保存")
|
||||
@PostMapping(value = "/cancelExecutionOrder")
|
||||
public void omsCancelExecutionOrder(@RequestBody StockOutOrder stockOutOrder) {
|
||||
public AjaxResult omsCancelExecutionOrder(@RequestBody StockOutOrder stockOutOrder) {
|
||||
stockOutOrderApplicationService.cancelExecutionOrder(stockOutOrder);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user