10 Commits
Author SHA1 Message Date
rcx e60f22979c fix(wms): 补提交收货/上架单已反审归档详情接口(此前遗漏未入库)
getInfo 增加 archive 重载(归档主单已软删用getById放行+校验已反审标记,明细放行软删行)+ getReverseAuditInfo 接口,补齐对接文档承诺的4个归档详情接口中缺失的收货/上架两个;随附上架默认库位注释微调
2026-09-12 11:21:33 +08:00
rcx 84ff45b43d fix(oms): 批量审核运输业务单号重复(goods_number取号三层防线)
根因: 取号为「COUNT当日单量+1+查重后移」, 非原子、无锁、无唯一索引。
批量审核并发创建运输业务单时, 多请求在同一查重窗口取到同一序号并全部
插入成功, 且将同一单号回填到出库单business_number, 页面所见单号重复。

- GoodsNumberGenerator: 取号改为当日MAX+1+存在性校验后移; 按yyMMdd分段的
  JVM锁串行化"取号+插单"; 插单后自检同号自动改号自愈; 兼容唯一索引撞号重试
- BusinessDocumentOrderDomainService: 主创建入口改走原子"取号+插单",
  出库/入库单号回填移至插单成功之后
- BusinessDocumentOrderImpl: 审核拆单子单路径同改
- Mapper: countByGoodsNumberPrefix改为selectGoodsNumbersByPrefix
2026-09-12 10:09:11 +08:00
rcx 892cc30999 fix(oms): 出入库业务单下发执行单时订单来源继承业务单(数据导入/手动创建/系统生成)不再写死"客户预约"; 手动创建业务单来源为空兜底"手动创建"; 出库下发明细过滤del_flag, 修复编辑后软删旧明细参与仓库分组导致多生成执行单且数量叠加 2026-09-12 10:00:45 +08:00
rcx da76816533 fix(oms): 反审运输链路按拆单子单全量软删——执行单挂子单下(businessDocumentId=子单id), 主单+子单+执行单+TMS链全删; 执行单列表默认过滤归档行 2026-09-12 09:53:13 +08:00
rcx b86ab75dcf fix(oms,wms): WMS出库明细uniqueId撞键修复+推单返回值校验
- WMS反审/取消下发软删出库明细时置空uniqueId(表唯一约束不含del_flag)
- omsOrderAddDetail推单前自愈: 按本次推送uniqueId值跨执行单清归档行, 反审重发不再撞键
- OMS推单三处(单头/明细/配送明细)校验返回值, 失败报WMS真实原因, 不再静默吞掉后伪装成可用库存为0
- WmsServiceFeign三方法void改AjaxResult, 控制器与降级工厂同步
2026-09-11 16:07:21 +08:00
rcx 796bb99a8b fix(wms): 出库异常列表不再默认过滤已软删出库单, 反审生成的异常记录可见 2026-09-11 14:28:55 +08:00
rcx f9a50ae353 fix(oms): 出库反审核心改动(下发自愈/运输链路软删/issueStatus已驳回/uniqueId置空/仓库兜底切换) 2026-09-11 14:17:17 +08:00
rcx 2094ea31cd fix(oms): 仓库信息本地兜底查询去掉STOCK_LOCK_MODE列, 兜底不依赖冻结功能列, 修复无该列环境的下发报错 2026-09-11 13:52:28 +08:00
rcx 788bae9a16 fix(oms,wms): 出库反审补全——重新下发自愈/仓库信息本地兜底/运输链路反审联动/归档查询修复
- 反审后 issueStatus 置 3-已驳回(与入库侧同口径), 清空执行单号/货源单号
- 反审/取消下发软删执行明细时置空 uniqueId, 修复重新下发撞唯一约束
- 新增 WarehouseInfoApplicationService(Feign优先+本地跨库兜底), 出库侧8处调用点接入
- 反审联动软删运输链路: 运输业务单/执行单本地软删 + TMS deleteChainByExecuteOrderNumber
- WMS归档查询修复: 交接归档详情丢行/拣货归档数量恒0/复核列表归档汇总/OMS执行单已反审tab
2026-09-11 11:42:42 +08:00
rcx a65cca1cfa fix(oms,wms): 入库反审三项修复:反审记录不可见、反审后重新下发无法收货、异常列表JOIN过滤
1. 入库异常三个生成器(收货异常/上架异常/反审记录)插入时未显式设置delFlag,MyBatis-Plus insert不含该列,列无默认值落库为NULL,被列表del_flag=1过滤导致反审记录永远不可见;现显式setDelFlag(1)

2. 反审重置业务单status=1:首轮完成上架经WMS回传把业务单status写成5(已入库),不重置则重新下发时copy给执行单→W入库单→收货单,收货单带已完成状态导致前端不给收货按钮

3. 入库异常列表LEFT JOIN stock_in_order固定b.DEL_FLAG=1,反审记录关联的入库单已软删导致匹配不上,仓库等基于b表的过滤把行全部过滤掉、列表恒为空;改为按a.TYPE切换关联状态(type=3关联已软删入库单);收货/上架已反审tab同号多张入库单JOIN出重复行,限定只关联同状态最新一张(IN_ORDER_ID取max)
2026-09-11 09:41:21 +08:00
41 changed files with 635 additions and 126 deletions
@@ -539,6 +539,13 @@ public interface WlhyServiceFeign {
@GetMapping("/feign/order/cancelIssueDocuments")
public AjaxResult cancelIssueDocuments(@RequestParam(name="executeOrderId") String executeOrderId);
/**
* OMS出库业务单反审联动: 按执行单货源编号软删TMS整条链路(货源单->运单->运输作业单, is_delete=1), 幂等,
* 与 cancelIssueDocuments(仅关闭货源单且有有效运单即拒绝)不同, 反审口径不留TMS数据
*/
@PostMapping("/feign/order/deleteChainByExecuteOrderNumber")
public AjaxResult deleteChainByExecuteOrderNumber(@RequestParam(name="executeOrderNumber") String executeOrderNumber);
@GetMapping(value = "/ntocc/tmsSettlementExchangeRate/queryList")
public AjaxResult<?> queryList(@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
@@ -74,21 +74,21 @@ public interface WmsServiceFeign {
*/
@ApiOperation("oms下发单保存")
@PostMapping(value = "/stockOutOrderApi/omsOutOrderAdd")
public void omsOutOrderAdd(@RequestBody StockOutOrder stockOutOrder);
public AjaxResult omsOutOrderAdd(@RequestBody StockOutOrder stockOutOrder);
/**
* oms下发单明细保存
*/
@ApiOperation("oms下发单明细保存")
@PostMapping(value = "/stockOutOrderApi/omsOutOrderAddDetail")
public void omsOutOrderAddDetail(@RequestBody List<OutMaterialDetail> outMaterialDetails);
public AjaxResult omsOutOrderAddDetail(@RequestBody List<OutMaterialDetail> outMaterialDetails);
/**
* oms下发单明细保存
*/
@ApiOperation("oms下发单明细保存")
@PostMapping(value = "/stockOutOrderApi/omsOutOrderAddDeliveryDetailsLink")
public void omsOutOrderAddDeliveryDetailsLink(@RequestBody List<DeliveryDetailsLink> deliveryDetailsLinks);
public AjaxResult omsOutOrderAddDeliveryDetailsLink(@RequestBody List<DeliveryDetailsLink> deliveryDetailsLinks);
/**
@@ -551,6 +551,12 @@ public class RemoteWlhyFallbackFactory implements FallbackFactory<WlhyServiceFei
return AjaxResult.error(cause.getMessage());
}
@Override
public AjaxResult deleteChainByExecuteOrderNumber(String executeOrderNumber) {
//返回错误而非静默成功, 让OMS反审事务能感知失败并整体回滚
return AjaxResult.error("TMS反审联动软删链路失败(服务降级):" + cause.getMessage());
}
@Override
public AjaxResult<?> queryList(Integer pageNo, Integer pageSize, String pushStatus, String organizationId) {
return AjaxResult.error(cause.getMessage());
@@ -57,16 +57,19 @@ public class RemoteWmsFallbackFactory implements FallbackFactory<WmsServiceFeign
}
@Override
public void omsOutOrderAdd(StockOutOrder stockOutOrder) {
public AjaxResult omsOutOrderAdd(StockOutOrder stockOutOrder) {
//返回错误而非静默成功: 降级吞掉会让OMS继续走分配, 残留"有单无明细"的垃圾单
return AjaxResult.error("WMS下发单保存失败(服务降级):" + cause.getMessage());
}
@Override
public void omsOutOrderAddDetail(List<OutMaterialDetail> outMaterialDetails) {
public AjaxResult omsOutOrderAddDetail(List<OutMaterialDetail> outMaterialDetails) {
return AjaxResult.error("WMS下发单明细保存失败(服务降级):" + cause.getMessage());
}
@Override
public void omsOutOrderAddDeliveryDetailsLink(List<DeliveryDetailsLink> deliveryDetailsLinks) {
public AjaxResult omsOutOrderAddDeliveryDetailsLink(List<DeliveryDetailsLink> deliveryDetailsLinks) {
return AjaxResult.error("WMS下发单配送明细保存失败(服务降级):" + cause.getMessage());
}
@Override
@@ -0,0 +1,58 @@
package com.mhd.oms.application.service;
import com.mhd.common.core.exception.ServiceException;
import com.mhd.common.core.web.domain.AjaxResult;
import com.mhd.oms.domain.reservationStockInOrder.repository.mapper.ReservationStockInOrderMapper;
import com.mhd.system.api.SystemServiceFeign;
import com.mhd.system.api.domain.WarehouseFeignPO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 仓库信息获取(带本地兜底): 下发/下单冻结/保存等流程原先直接 Feign 调 system 服务取仓库信息,
* system 服务未启动/降级/返回非200时整个流程被"获取仓库信息失败"阻断。
* 本组件统一收口: 优先 Feign, 失败时回退本地跨库直查 "NGWL_TEST_SYSTEM".WAREHOUSE(与 Feign 同库同表, OMS 库账号可读),
* 两侧都取不到才抛错并带出 Feign 真实返回原因。
*
* @date 2026/9/11
*/
@Slf4j
@Service
public class WarehouseInfoApplicationService {
@Autowired
private SystemServiceFeign systemServiceFeign;
@Autowired
private ReservationStockInOrderMapper reservationStockInOrderMapper;
/**
* 获取仓库信息: Feign 优先, 失败走本地兜底; 两边都取不到抛 ServiceException
*
* @param warehouseId 仓库id
* @return 仓库信息(含编码/名称/单号生成规则/库存锁定模式等)
*/
public WarehouseFeignPO getWarehouseInfo(Long warehouseId) {
if (warehouseId == null) {
throw new ServiceException("仓库ID不能为空");
}
AjaxResult ajaxResult = null;
try {
ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(warehouseId);
} catch (Exception e) {
log.warn("获取仓库信息Feign调用异常, 走本地兜底查询, warehouseId={}, error={}", warehouseId, e.getMessage());
}
if (ajaxResult != null && "200".equals(String.valueOf(ajaxResult.get("code"))) && ajaxResult.get("data") != null) {
return com.alibaba.fastjson.JSON.parseObject(
com.alibaba.fastjson2.JSONObject.toJSONString(ajaxResult.get("data")), WarehouseFeignPO.class);
}
String remoteMsg = ajaxResult == null ? "Feign调用异常" : String.valueOf(ajaxResult.get("msg"));
log.warn("获取仓库信息Feign失败(返回: {}), 走本地兜底查询, warehouseId={}", remoteMsg, warehouseId);
WarehouseFeignPO local = reservationStockInOrderMapper.getWarehouseInfoFallback(warehouseId);
if (local == null) {
throw new ServiceException("获取仓库信息失败(Feign返回:" + remoteMsg + "), 且本地未查到仓库[warehouseId="
+ warehouseId + "], 请检查仓库是否存在/已启用");
}
return local;
}
}
@@ -46,6 +46,9 @@ public class OutEntrustOrderApplicationService {
@Resource
private SystemServiceFeign systemServiceFeign;
@Resource
private com.mhd.oms.application.service.WarehouseInfoApplicationService warehouseInfoApplicationService;
@Resource
private WmsServiceFeign wmsServiceFeign;
@@ -352,14 +355,10 @@ public class OutEntrustOrderApplicationService {
}
/**
* 仓库信息校验并回填编码/名称
* 仓库信息校验并回填编码/名称 (Feign失败走本地跨库兜底)
*/
private void setWarehouseInfo(OutEntrustOrderDO outEntrustOrderDO) {
AjaxResult ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(outEntrustOrderDO.getWarehouseId());
if (ajaxResult == null || !"200".equals(String.valueOf(ajaxResult.get("code")))) {
throw new ServiceException("获取仓库信息失败,请检查仓库是否有效");
}
WarehouseFeignPO warehouseFeignPO = JSONUtil.toBean(JSONUtil.toJsonStr(ajaxResult.get("data")), WarehouseFeignPO.class);
WarehouseFeignPO warehouseFeignPO = warehouseInfoApplicationService.getWarehouseInfo(outEntrustOrderDO.getWarehouseId());
if (warehouseFeignPO == null || Objects.isNull(warehouseFeignPO.getWarehouseId())) {
throw new ServiceException("仓库不存在,请重新选择");
}
@@ -39,8 +39,8 @@ public interface BusinessDocumentOrderMapper extends BaseMapper<BusinessDocument
String getPushVerifyByOrgId(@Param("organizationId") Long organizationId);
/**
* 统计指定日期前缀(yyMMdd)下已生成的新格式业务单号数量
* 仅统计新格式(长度<=12),过滤历史16位旧数据,保证序号每日从001正确累计
* 查询指定日期前缀(yyMMdd)下已生成的新格式业务单号列表
* 仅新格式(长度<=12),过滤历史16位旧数据,供取号器解析当日最大序号
*/
int countByGoodsNumberPrefix(@Param("prefix") String prefix);
List<String> selectGoodsNumbersByPrefix(@Param("prefix") String prefix);
}
@@ -237,11 +237,8 @@ public class BusinessDocumentOrderImpl extends ServiceImpl<BusinessDocumentOrder
subOrder.setRecipientName(user.getUserName());
//生成子单货源单号(yyMMdd + 当日顺序号)
String orderCode = goodsNumberGenerator.generateUniqueGoodsNumber(subOrder.getDocumentDate());
subOrder.setGoodsNumber(orderCode);
businessDocumentOrderMapper.insert(subOrder);
//生成子单货源单号(yyMMdd + 当日顺序号)并落库, 撞唯一索引自动重新取号重试
goodsNumberGenerator.insertWithUniqueGoodsNumber(subOrder);
subOrderCount++; // 增加子单计数
// 保存子单的地址信息到BUSINESS_DOCUMENT_ADDRESS_MULTI表
@@ -285,24 +285,9 @@ public class BusinessDocumentOrderDomainService {
}
}
//生成货源单号(yyMMdd + 当日顺序号,每日重置)
String orderCode = goodsNumberGenerator.generateUniqueGoodsNumber(tmsOrder.getDocumentDate());
tmsOrder.setGoodsNumber(orderCode);
ReservationStockOutOrder reservationStockOutOrder = reservationStockOutOrderMapper.selectOne(new LambdaQueryWrapper<ReservationStockOutOrder>()
.eq(ReservationStockOutOrder::getOutOrderNumber, tmsOrder.getInOrderNumber())
.eq(ReservationStockOutOrder::getDelFlag, 1));
if (reservationStockOutOrder != null){
reservationStockOutOrder.setBusinessNumber(tmsOrder.getGoodsNumber());
reservationStockOutOrderMapper.updateById(reservationStockOutOrder);
}
ReservationStockInOrder reservationStockInOrder = reservationStockInOrderMapper.selectOne(new LambdaQueryWrapper<ReservationStockInOrder>()
.eq(ReservationStockInOrder::getInOrderNumber, tmsOrder.getInOrderNumber())
.eq(ReservationStockInOrder::getDelFlag, 1));
if (reservationStockInOrder != null){
reservationStockInOrder.setBusinessNumber(tmsOrder.getGoodsNumber());
reservationStockInOrderMapper.updateById(reservationStockInOrder);
}
//生成货源单号并落库见下方 insertWithUniqueGoodsNumber:
//取号与插单合并为原子重试(撞goods_number唯一索引自动重新取号),
//出库/入库业务单的单号回填也移至插单成功之后,避免插单失败留下脏回填
List<BusinessDocumentAddressMultiDTO> loadingAddressList = businessDocumentOrderDO.getLoadingAddressList();//装货地信息集合
List<BusinessDocumentAddressMultiDTO> unloadAddressList = businessDocumentOrderDO.getUnloadAddressList();//卸货地信息集合
@@ -426,7 +411,24 @@ public class BusinessDocumentOrderDomainService {
//一天后
tmsOrder.setDocumentDate(businessDocumentOrderDO.getDocumentDate());
boolean istrue = businessDocumentOrderMapper.insert(tmsOrder) > 0;
//生成货源单号(yyMMdd + 当日顺序号,每日重置)并落库, 撞唯一索引自动重新取号重试
boolean istrue = goodsNumberGenerator.insertWithUniqueGoodsNumber(tmsOrder);
//落库成功后将运输业务单号回填至关联的出库/入库业务单
ReservationStockOutOrder reservationStockOutOrder = reservationStockOutOrderMapper.selectOne(new LambdaQueryWrapper<ReservationStockOutOrder>()
.eq(ReservationStockOutOrder::getOutOrderNumber, tmsOrder.getInOrderNumber())
.eq(ReservationStockOutOrder::getDelFlag, 1));
if (reservationStockOutOrder != null){
reservationStockOutOrder.setBusinessNumber(tmsOrder.getGoodsNumber());
reservationStockOutOrderMapper.updateById(reservationStockOutOrder);
}
ReservationStockInOrder reservationStockInOrder = reservationStockInOrderMapper.selectOne(new LambdaQueryWrapper<ReservationStockInOrder>()
.eq(ReservationStockInOrder::getInOrderNumber, tmsOrder.getInOrderNumber())
.eq(ReservationStockInOrder::getDelFlag, 1));
if (reservationStockInOrder != null){
reservationStockInOrder.setBusinessNumber(tmsOrder.getGoodsNumber());
reservationStockInOrderMapper.updateById(reservationStockInOrder);
}
if (hasDetails) {
for (BusinessDeliveryDetailsLinkDTO businessDeliveryDetailsLinkDTO : businessDeliveryDetailsLinkDTOList) {
@@ -5,23 +5,44 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.mhd.common.core.exception.ServiceException;
import com.mhd.oms.domain.businessDocumentOrder.entity.BusinessDocumentOrder;
import com.mhd.oms.domain.businessDocumentOrder.repository.mapper.BusinessDocumentOrderMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 业务单据订单(goodsNumber)单号生成器
* 规则: yyMMdd(取自单据日期) + 当日顺序号(每日重置, 3位起步, 超999自动扩位)
* 示例: 260804001、260804999、2608041000
*
* 唯一性保障(不依赖唯一索引, 三层防线):
* 1) 取号: 当日已生成单号 MAX+1 作为基线 + 候选号存在性校验后移收敛
* (对齐入库单号修复先例 ReservationStockInOrderDomainService#generateReserveOrderNumber)
* 2) 落库: 按日期前缀分段的JVM锁串行化"取号+插单", 消除本实例内并发审核/
* 批量取号的check-then-act竞态(多实例部署时本层只覆盖各自实例)
* 3) 自检自愈: 插单后立即校验同号数量, >1 说明与并发插入撞号, 给本单重新
* 取号改号, 兜住跨实例残余竞态
* 若日后补建goods_number唯一索引(见 sql/20260906_oms_business_goods_number_unique.sql),
* 撞号将抛DuplicateKeyException由重试逻辑接住, 与上述防线叠加互不冲突
*/
@Slf4j
@Component
public class GoodsNumberGenerator {
private static final int MAX_RETRY = 100;
/**
* 取号+插单分段锁: 按单号日期前缀(yyMMdd)分段, 同日串行、跨日并行,
* 锁对象常驻(每日新增1个, 增量可忽略)
*/
private static final Map<String, Object> LOCK_STRIPES = new ConcurrentHashMap<>();
@Autowired
private BusinessDocumentOrderMapper businessDocumentOrderMapper;
@@ -33,8 +54,21 @@ public class GoodsNumberGenerator {
Date date = (documentDate == null) ? new Date() : documentDate;
String datePart = DateUtil.format(date, "yyMMdd");
// count 只查一次作为序号基线
long seq = (long) businessDocumentOrderMapper.countByGoodsNumberPrefix(datePart) + 1;
// 当日已生成单号的最大序号作为基线(MAX+1而非COUNT+1, 防脏数据/序号错位导致回退复用)
List<String> existNumbers = businessDocumentOrderMapper.selectGoodsNumbersByPrefix(datePart);
long seq = 0;
for (String existNumber : existNumbers) {
if (existNumber == null || existNumber.length() <= datePart.length()) {
continue;
}
String numStr = existNumber.substring(datePart.length());
if (!numStr.matches("\\d+")) {
continue; // 防脏数据(非纯数字后缀)导致解析失败
}
seq = Math.max(seq, Long.parseLong(numStr));
}
seq = seq + 1;
int retryCount = 0;
while (retryCount < MAX_RETRY) {
String orderCode = datePart + String.format("%03d", seq);
@@ -46,8 +80,60 @@ public class GoodsNumberGenerator {
if (CollectionUtils.isEmpty(exist)) {
return orderCode;
}
seq++; // ← 关键:查重失败,序号往后递增找空位,而不是重新count
seq++; // ← 关键:查重失败,序号往后递增找空位,而不是重新取基线
}
throw new ServiceException("生成唯一业务单号失败,已达到最大重试次数:" + MAX_RETRY);
}
}
/**
* 生成唯一业务单号并落库: JVM分段锁内完成"取号+插单",
* 插入撞goods_number唯一索引时自动重新取号重试(索引为可选兜底),
* 插单成功后自检同号并自愈改号。
* 供业务单创建入口(手动/审核新增、审核拆单、预约确认)统一使用。
* @return 是否插入成功(mapper.insert结果)
*/
public boolean insertWithUniqueGoodsNumber(BusinessDocumentOrder order) {
Date date = (order.getDocumentDate() == null) ? new Date() : order.getDocumentDate();
String lockKey = DateUtil.format(date, "yyMMdd");
synchronized (LOCK_STRIPES.computeIfAbsent(lockKey, k -> new Object())) {
int retryCount = 0;
while (retryCount < MAX_RETRY) {
retryCount++;
order.setGoodsNumber(generateUniqueGoodsNumber(order.getDocumentDate()));
try {
boolean inserted = businessDocumentOrderMapper.insert(order) > 0;
// 插单后自检自愈: 无唯一索引时跨实例并发窗口内的撞号双方都能落库,
// 同号数量>1说明本单撞号, 给本单重新取号改号, 防止单号重复扩散
healIfDuplicated(order);
return inserted;
} catch (DuplicateKeyException e) {
// 已建唯一索引时的兜底: 仅业务单号撞唯一索引时重试, 其他唯一约束冲突原样抛出
String message = e.getMessage();
if (message == null || !message.toLowerCase().contains("goods_number")) {
throw e;
}
log.warn("业务单号[{}]撞号, 自动重新取号重试(第{}次): {}", order.getGoodsNumber(), retryCount, message);
}
}
throw new ServiceException("生成唯一业务单号失败,已达到最大重试次数:" + MAX_RETRY);
}
}
/**
* 插单后自检自愈: 查同号数量, >1 说明有并发插入撞号(本单为后插入方),
* 保持先落库单据单号不变, 重新取号给本单改号
*/
private void healIfDuplicated(BusinessDocumentOrder order) {
List<BusinessDocumentOrder> sameNumber = businessDocumentOrderMapper.selectList(
new QueryWrapper<BusinessDocumentOrder>().eq("goods_number", order.getGoodsNumber())
);
if (sameNumber != null && sameNumber.size() > 1) {
String oldNumber = order.getGoodsNumber();
String newNumber = generateUniqueGoodsNumber(order.getDocumentDate());
order.setGoodsNumber(newNumber);
businessDocumentOrderMapper.updateById(order);
log.warn("业务单号[{}]与并发插入撞号(无唯一索引兜底), 已自愈改号为[{}], orderId={}",
oldNumber, newNumber, order.getId());
}
}
}
@@ -29,6 +29,8 @@ public class ExecutionStockOutOrderPO extends BaseVOEntity {
@ApiModelProperty("出库单id")
private Long outOrderId;
@ApiModelProperty("反审标记: 1-已反审(反审软删归档, 【已反审】tab按 delFlag=2+reverseFlag=1 查询)")
private Integer reverseFlag;
@ApiModelProperty("总价")
@Excel(name = "总价")
private BigDecimal amount;
@@ -29,6 +29,9 @@ public class ExecutionStockOutOrderDO extends BaseVOEntity {
@ApiModelProperty("出库单id")
private Long outOrderId;
@ApiModelProperty("反审标记查询: 1-已反审(与delFlag=2成对传, 查【已反审】tab归档数据)")
private Integer reverseFlag;
@ApiModelProperty("组织表ID")
@Excel(name = "组织表ID")
private Long organizationId;
@@ -27,6 +27,9 @@ public class ExecutionStockOutOrderDTO extends ExecutionStockOutOrderBaseDTO {
@ApiModelProperty("出库单id")
private Long outOrderId;
@ApiModelProperty("反审标记查询: 1-已反审(与delFlag=2成对传, 查【已反审】tab归档数据)")
private Integer reverseFlag;
@ApiModelProperty("组织表ID")
@Excel(name = "组织表ID")
private Long organizationId;
@@ -66,6 +66,8 @@ import java.util.stream.Collectors;
public class ExecutionStockOutOrderApplicationService {
@Autowired
private ExecutionStockOutOrderDomainService stockOutOrderDomainService;
@Autowired
private com.mhd.oms.application.service.WarehouseInfoApplicationService warehouseInfoApplicationService;
// @Autowired
// private ExecutionOutMaterialDetailDomainService outMaterialDetailDomainService;
// @Autowired
@@ -862,11 +864,8 @@ public class ExecutionStockOutOrderApplicationService {
if (stockOutOrderDO.getWarehouseId() == null) {
throw new ServiceException("仓库ID不能为空");
}
AjaxResult ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(stockOutOrderDO.getWarehouseId());
if(!"200".equals(String.valueOf(ajaxResult.get("code")))){
throw new ServiceException("获取仓库信息失败");
}
WarehouseFeignPO warehouseFeignPO = JSON.parseObject(JSONObject.toJSONString(ajaxResult.get("data")), WarehouseFeignPO.class);
// Feign失败走本地跨库兜底, 避免保存被"获取仓库信息失败"阻断
WarehouseFeignPO warehouseFeignPO = warehouseInfoApplicationService.getWarehouseInfo(stockOutOrderDO.getWarehouseId());
stockOutOrderDO.setWarehouseCode(warehouseFeignPO.getWarehouseCode());
stockOutOrderDO.setWarehouseName(warehouseFeignPO.getWarehouseName());
}
@@ -71,6 +71,8 @@ public class ExecutionStockOutOrderDomainService {
@Autowired
private IExecutionStockOutOrderService stockOutOrderService;
@Autowired
private com.mhd.oms.application.service.WarehouseInfoApplicationService warehouseInfoApplicationService;
@Autowired
private IExecutionOutMaterialDetailService outMaterialDetailService;
@Autowired
private IReservationDeliveryDetailsLinkService deliveryDetailsLinkService;
@@ -148,14 +150,9 @@ public class ExecutionStockOutOrderDomainService {
public Boolean insert(ExecutionStockOutOrderDO stockOutOrderDO) {
//建单校验明细数量必须大于00数量单下发WMS后自动分配必失败出库单会卡死在已审核状态
validateMaterialDetailQuantity(stockOutOrderDO.getMaterialDetailList());
//设置单号
//设置单号(仓库信息: Feign优先, system服务失败/降级时本地跨库兜底)
Long warehouseId = stockOutOrderDO.getWarehouseId();
AjaxResult ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(warehouseId);
if (!"200".equals(String.valueOf(ajaxResult.get("code")))) {
throw new ServiceException("获取仓库信息失败");
}
//仓库信息
WarehouseFeignPO warehouseFeignPO = com.alibaba.fastjson.JSON.parseObject(com.alibaba.fastjson2.JSONObject.toJSONString(ajaxResult.get("data")), WarehouseFeignPO.class);
WarehouseFeignPO warehouseFeignPO = warehouseInfoApplicationService.getWarehouseInfo(warehouseId);
String orderNoGenerateRule = warehouseFeignPO.getOrderNoGenerateRule();
String prefix = warehouseFeignPO.getOutOrderNoPrefix();
if ("yang_pin_chu_ku".equals(stockOutOrderDO.getBusinessType())) {
@@ -218,14 +215,9 @@ public class ExecutionStockOutOrderDomainService {
@Transactional(rollbackFor = Exception.class)
public Boolean batchInsert(List<ExecutionStockOutOrderDO> stockOutOrderDOList) {
for (ExecutionStockOutOrderDO stockOutOrderDO : stockOutOrderDOList){
//设置单号
//设置单号(仓库信息: Feign优先, system服务失败/降级时本地跨库兜底)
Long warehouseId = stockOutOrderDO.getWarehouseId();
AjaxResult ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(warehouseId);
if (!"200".equals(String.valueOf(ajaxResult.get("code")))) {
throw new ServiceException("获取仓库信息失败");
}
//仓库信息
WarehouseFeignPO warehouseFeignPO = com.alibaba.fastjson.JSON.parseObject(com.alibaba.fastjson2.JSONObject.toJSONString(ajaxResult.get("data")), WarehouseFeignPO.class);
WarehouseFeignPO warehouseFeignPO = warehouseInfoApplicationService.getWarehouseInfo(warehouseId);
String orderNoGenerateRule = warehouseFeignPO.getOrderNoGenerateRule();
String prefix = warehouseFeignPO.getOutOrderNoPrefix();
if ("yang_pin_chu_ku".equals(stockOutOrderDO.getBusinessType())) {
@@ -542,7 +534,12 @@ public class ExecutionStockOutOrderDomainService {
stockOutOrder.setOrganizationId(orgId);
stockOutOrder.setOrganizationName(orgName);
stockOutOrder.setTopOrganizationId(topOrgId);
wmsServiceFeign.omsOutOrderAdd(stockOutOrder);
//单头/配送明细推送同样校验返回值: 降级吞掉会残留"有单无明细"等半截单
AjaxResult orderAddResult = wmsServiceFeign.omsOutOrderAdd(stockOutOrder);
if (orderAddResult == null || !"200".equals(String.valueOf(orderAddResult.get("code")))) {
throw new ServiceException("出库单[" + currentOrderNumber + "]WMS出库单推送失败:"
+ (orderAddResult == null ? "WMS服务不可用或已降级" : orderAddResult.get("msg")));
}
List<ReservationDeliveryDetailsLink> list = deliveryDetailsLinkService.list(new QueryWrapper<ReservationDeliveryDetailsLink>().lambda().eq(ReservationDeliveryDetailsLink::getOrderNumber, byId.getOutOrderNumber()));
List<DeliveryDetailsLink> deliveryDetailsLinkList = new ArrayList<>();
@@ -552,7 +549,11 @@ public class ExecutionStockOutOrderDomainService {
deliveryDetailsLink.setOmsId(reservationDeliveryDetailsLink.getId());
deliveryDetailsLinkList.add(deliveryDetailsLink);
}
wmsServiceFeign.omsOutOrderAddDeliveryDetailsLink(deliveryDetailsLinkList);
AjaxResult linkAddResult = wmsServiceFeign.omsOutOrderAddDeliveryDetailsLink(deliveryDetailsLinkList);
if (linkAddResult == null || !"200".equals(String.valueOf(linkAddResult.get("code")))) {
throw new ServiceException("出库单[" + currentOrderNumber + "]WMS配送明细推送失败:"
+ (linkAddResult == null ? "WMS服务不可用或已降级" : linkAddResult.get("msg")));
}
String inOrderNumber = stockOutOrder.getOutOrderNumber();
@@ -566,7 +567,12 @@ public class ExecutionStockOutOrderDomainService {
outMaterialDetail.setTopOrganizationId(topOrgId);
outMaterialDetailList.add(outMaterialDetail);
}
wmsServiceFeign.omsOutOrderAddDetail(outMaterialDetailList);
//明细推送必须校验返回值: 失败被降级吞掉时WMS会残留"有单无明细"的出库单, 自动分配报"可用库存为0"难定位
AjaxResult detailAddResult = wmsServiceFeign.omsOutOrderAddDetail(outMaterialDetailList);
if (detailAddResult == null || !"200".equals(String.valueOf(detailAddResult.get("code")))) {
throw new ServiceException("出库单[" + currentOrderNumber + "]WMS出库明细推送失败:"
+ (detailAddResult == null ? "WMS服务不可用或已降级" : detailAddResult.get("msg")));
}
String outOrderNumber = byId.getOutOrderNumber();
com.mhd.system.api.domain.StockOutOrderDTO stockOutOrderDTO = new com.mhd.system.api.domain.StockOutOrderDTO();
@@ -54,6 +54,13 @@ public interface ReservationStockInOrderMapper extends BaseMapper<ReservationSto
public String getWarehouseName(@Param("id") Long id);
/**
* 本地跨库直查仓库信息(编码/名称/单号生成规则),
* 供下发等流程在 system 服务 Feign 失败/降级时兜底, getWarehouseInfoByWarehouseId Feign 接口同数据源
* 不含 STOCK_LOCK_MODE(冻结功能列, 各环境库结构不一致), 兜底时 stockLockMode null 即默认分配库存冻结模式
*/
public com.mhd.system.api.domain.WarehouseFeignPO getWarehouseInfoFallback(@Param("id") Long id);
List<ReservationStockInOrderPO> queryNoticePrint(ReservationStockInOrderDO stockInOrderDO);
List<ReservationInMaterialDetailPO> queryNoticePrintDetail(ReservationStockInOrderDO stockInOrderDO);
public void updateByTransportationNumber(@Param("transportationNumber") String transportationNumber);
@@ -176,6 +176,10 @@ public class ReservationStockInOrderDomainService {
// //统计总体积
// BigDecimal volumeLimit = stockInOrderDO.getMaterialDetailList().stream().map(ReservationInMaterialDetailDO::getVolumeLimit).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
// stockInOrderDO.setVolumeLimit(volumeLimit);
// 订单来源为空时兜底为手动创建导入/预约下发等路径已显式设置不受影响下发执行单时随业务单继承
if (stockInOrderDO.getReservationOrderSource() == null || stockInOrderDO.getReservationOrderSource().isEmpty()) {
stockInOrderDO.setReservationOrderSource("手动创建");
}
stockInOrderService.insert(stockInOrderDO);
return materialDetailService.batchInsert(stockInOrderDO.getInOrderNumber(), stockInOrderDO.getMaterialDetailList());
}
@@ -614,7 +618,6 @@ public class ReservationStockInOrderDomainService {
reservationStockInOrder.setOrderTypeName("普通入库");
reservationStockInOrder.setBusinessType("pu_tong_ru_ku");
}
reservationStockInOrder.setReservationOrderSource("客户预约");
reservationStockInOrder.setWarehouseId(warehouseId);
if (warehouseId == null || warehouseId == 0L){
reservationStockInOrder.setWarehouseId(byId.getWarehouseId());
@@ -897,6 +900,10 @@ public class ReservationStockInOrderDomainService {
.set(ReservationStockInOrder::getIssueStatus, 3)
//清空下发/执行时间前端"是否执行"列按执行时间判断不清会仍显示已执行与取消下发 cancelIssueOrder 同口径
.set(ReservationStockInOrder::getIssueTime, null)
//入库进度状态(1-已创建 2-已审核 3-收货中 4-上架中 5-已入库)首轮"完成上架"会经WMS回传写成5
//不重置的话重新下发时copy给执行单W入库单收货单收货单带着"已完成"状态导致前端不给收货按钮
//重置为1(已创建)与手工新单首次下发链路一致(W入库单/收货单起点=1-待收货)
.set(ReservationStockInOrder::getStatus, 1)
.set(ReservationStockInOrder::getInQuantity, BigDecimal.ZERO)
.set(ReservationStockInOrder::getStorageTime, null)
.set(ReservationStockInOrder::getExecutionInOrderNumber, null)
@@ -1038,7 +1045,6 @@ public class ReservationStockInOrderDomainService {
reservationStockInOrder.setOrderTypeName("普通入库");
reservationStockInOrder.setBusinessType("pu_tong_ru_ku");
}
reservationStockInOrder.setReservationOrderSource("客户预约");
reservationStockInOrder.setWarehouseId(warehouseId);
// reservationStockInOrder.setOrganizationId(orgId);
// reservationStockInOrder.setOrganizationName(orgName);
@@ -85,6 +85,8 @@ import java.util.stream.Collectors;
public class ReservationStockOutOrderApplicationService {
@Autowired
private ReservationStockOutOrderDomainService stockOutOrderDomainService;
@Autowired
private com.mhd.oms.application.service.WarehouseInfoApplicationService warehouseInfoApplicationService;
// @Autowired
// private ReservationOutMaterialDetailDomainService outMaterialDetailDomainService;
// @Autowired
@@ -913,11 +915,8 @@ public class ReservationStockOutOrderApplicationService {
if (stockOutOrderDO.getWarehouseId() == null) {
throw new ServiceException("仓库ID不能为空");
}
AjaxResult ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(stockOutOrderDO.getWarehouseId());
if(!"200".equals(String.valueOf(ajaxResult.get("code")))){
throw new ServiceException("获取仓库信息失败");
}
WarehouseFeignPO warehouseFeignPO = JSON.parseObject(JSONObject.toJSONString(ajaxResult.get("data")), WarehouseFeignPO.class);
// Feign失败走本地跨库兜底, 避免新增/编辑保存被"获取仓库信息失败"阻断
WarehouseFeignPO warehouseFeignPO = warehouseInfoApplicationService.getWarehouseInfo(stockOutOrderDO.getWarehouseId());
stockOutOrderDO.setWarehouseCode(warehouseFeignPO.getWarehouseCode());
stockOutOrderDO.setWarehouseName(warehouseFeignPO.getWarehouseName());
}
@@ -18,6 +18,10 @@ import com.mhd.oms.domain.businessDeliveryDetailsLink.repository.facade.IBusines
import com.mhd.oms.domain.businessDeliveryDetailsLink.repository.mapper.BusinessDeliveryDetailsLinkMapper;
import com.mhd.oms.domain.businessDocumentOrder.entity.BusinessDocumentOrder;
import com.mhd.oms.domain.businessDocumentOrder.repository.mapper.BusinessDocumentOrderMapper;
import com.mhd.oms.domain.businessDocumentCargoMulti.entity.BusinessDocumentCargoMulti;
import com.mhd.oms.domain.businessDocumentCargoMulti.repository.mapper.BusinessDocumentCargoMultiMapper;
import com.mhd.oms.domain.businessDocumentAddressMulti.entity.BusinessDocumentAddressMulti;
import com.mhd.oms.domain.businessDocumentAddressMulti.repository.mapper.BusinessDocumentAddressMultiMapper;
import com.mhd.oms.domain.executeOrder.entity.ExecuteOrder;
import com.mhd.oms.domain.executeOrder.repository.mapper.ExecuteOrderMapper;
import com.mhd.oms.domain.executionInMaterialDetail.entity.ExecutionInMaterialDetail;
@@ -57,6 +61,7 @@ import com.mhd.oms.domain.reservationStockOutOrder.repository.todo.ReservationSt
import com.mhd.oms.domain.reserveStockOutOrder.repository.mapper.ReserveStockOutOrderMapper;
import com.mhd.system.api.SystemServiceFeign;
import com.mhd.system.api.WmsServiceFeign;
import com.mhd.system.api.WlhyServiceFeign;
import com.mhd.system.api.domain.*;
import com.mhd.system.api.model.LoginUser;
import lombok.extern.slf4j.Slf4j;
@@ -122,6 +127,14 @@ public class ReservationStockOutOrderDomainService {
private MaterialBaseInfoResolveService materialBaseInfoResolveService;
@Resource
private ReservationInventoryAdjustmentRecordMapper inventoryAdjustmentRecordMapper;
@Resource
private WlhyServiceFeign wlhyServiceFeign;
@Autowired
private BusinessDocumentCargoMultiMapper businessDocumentCargoMultiMapper;
@Autowired
private BusinessDocumentAddressMultiMapper businessDocumentAddressMultiMapper;
@Autowired
private com.mhd.oms.application.service.WarehouseInfoApplicationService warehouseInfoApplicationService;
/**
* 分页查询出库单列表
@@ -219,6 +232,10 @@ public class ReservationStockOutOrderDomainService {
if (stockOutOrderDO.getCheckTaskDistribution() == null) {
stockOutOrderDO.setCheckTaskDistribution(2);
}
// 订单来源为空时兜底为手动创建导入/预约下发等路径已显式设置不受影响下发执行单时随业务单继承
if (stockOutOrderDO.getReservationOrderSource() == null || stockOutOrderDO.getReservationOrderSource().isEmpty()) {
stockOutOrderDO.setReservationOrderSource("手动创建");
}
stockOutOrderService.insert(stockOutOrderDO);
List<ReservationDeliveryDetailsLink> reservationDeliveryDetailsLinkList = stockOutOrderDO.getReservationDeliveryDetailsLinkList();
if (reservationDeliveryDetailsLinkList != null && !reservationDeliveryDetailsLinkList.isEmpty()){
@@ -491,6 +508,125 @@ public class ReservationStockOutOrderDomainService {
}
}
/**
* 重新下发前自愈清理: 历史版本反审/取消下发软删的执行明细(del_flag=2)仍占用unique_id
* (EXECUTION_OUT_MATERIAL_DETAIL 唯一约束不含del_flag), 重新下发从预留明细复制同一unique_id会撞键;
* 归档行的unique_id无功能用途(仅活动明细展示反查批号用到), 统一置空释放键位, 幂等可重复执行
*/
private void releaseArchivedDetailUniqueId(String businessOutOrderNumber) {
List<String> executionOrderNumbers = executionStockOutOrderService.list(new QueryWrapper<ExecutionStockOutOrder>().lambda()
.eq(ExecutionStockOutOrder::getBusinessOutOrderNumber, businessOutOrderNumber))
.stream()
.map(ExecutionStockOutOrder::getOutOrderNumber)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (executionOrderNumbers.isEmpty()) {
return;
}
executionOutMaterialDetailService.update(null, new UpdateWrapper<ExecutionOutMaterialDetail>().lambda()
.set(ExecutionOutMaterialDetail::getUniqueId, null)
.eq(ExecutionOutMaterialDetail::getDelFlag, 2)
.in(ExecutionOutMaterialDetail::getOutOrderNumber, executionOrderNumbers));
}
/**
* 反审联动软删运输链路(编辑"是否运输"= 且审核通过后生成的链), 真实层级:
* 运输业务单-主单(splicingOrderType=4, business_document_order)
* -> 审核运输业务单按配送明细生成子单(splicingOrderType=3, mainOrderId=主单id, 每明细一单)
* -> 下发每个子单各生成一个运输执行单(execute_order.businessDocumentId=子单id, goodsNumber=子单货源号, 主单下无直接执行单)
* -> 每个执行单各推一单TMS(tms_order.execute_order_number=执行单货源号) -> TMS运单/运输作业单(wlhy外部服务)
* 定位运输业务单: 出库单.businessNumber(创建运输业务单时回填的货源单号) inOrderNumber=出库单号兜底
* (businessNumber缺失/已清空场景); 均不滤del_flag此前被旧版反审漏删或手工删过的主单,
* 其子单/执行单/TMS链可能仍存活, 同样要清理(幂等)配送明细链的invoiceNumber存的是送货单号而非出库单号, 不能作为定位条件
* 执行单逐个调 TMS deleteChainByExecuteOrderNumber 软删TMS侧整链(未下发过则TMS无数据, 幂等空操作), 失败抛异常整批回滚;
* 本地软删执行单/主单及子单及其货品/地址明细/配送明细链; 无关联运输单时直接跳过
*/
private void reverseTransportChain(ReservationStockOutOrder byId) {
String outOrderNumber = byId.getOutOrderNumber();
Set<Long> transportOrderIds = new HashSet<>();
if (StringUtils.isNotBlank(byId.getBusinessNumber())) {
businessDocumentOrderMapper.selectList(new LambdaQueryWrapper<BusinessDocumentOrder>()
.eq(BusinessDocumentOrder::getGoodsNumber, byId.getBusinessNumber()))
.forEach(order -> transportOrderIds.add(order.getId()));
}
businessDocumentOrderMapper.selectList(new LambdaQueryWrapper<BusinessDocumentOrder>()
.eq(BusinessDocumentOrder::getInOrderNumber, outOrderNumber))
.forEach(order -> transportOrderIds.add(order.getId()));
if (transportOrderIds.isEmpty()) {
return;
}
for (Long transportOrderId : transportOrderIds) {
BusinessDocumentOrder transportOrder = businessDocumentOrderMapper.selectById(transportOrderId);
if (transportOrder == null) {
continue;
}
// 0. 拆单主单自身无执行单: 收集主单+全部子单id, 执行单/货品/地址明细按全量id清理
List<BusinessDocumentOrder> subOrders = businessDocumentOrderMapper.selectList(new LambdaQueryWrapper<BusinessDocumentOrder>()
.eq(BusinessDocumentOrder::getMainOrderId, String.valueOf(transportOrderId))
.eq(BusinessDocumentOrder::getDelFlag, 1));
Set<Long> chainDocIds = new HashSet<>();
chainDocIds.add(transportOrderId);
subOrders.forEach(subOrder -> chainDocIds.add(subOrder.getId()));
// 1. 执行单: 不滤del_flag历史周期已归档执行单的TMS链也幂等补清("执行单已删TMS残留"),
// 先逐个软删TMS侧整链(货源单->运单->作业单), 再软删本地执行单(已归档的跳过更新)
List<ExecuteOrder> executeOrders = executeOrderMapper.selectList(new LambdaQueryWrapper<ExecuteOrder>()
.in(ExecuteOrder::getBusinessDocumentId, chainDocIds));
for (ExecuteOrder executeOrder : executeOrders) {
if (StringUtils.isNotBlank(executeOrder.getGoodsNumber())) {
AjaxResult tmsResult = wlhyServiceFeign.deleteChainByExecuteOrderNumber(executeOrder.getGoodsNumber());
if (tmsResult == null || !"200".equals(String.valueOf(tmsResult.get("code")))) {
throw new ServiceException("出库单[" + outOrderNumber + "]反审失败: 运输执行单货源单号["
+ executeOrder.getGoodsNumber() + "]TMS链路软删失败: "
+ (tmsResult == null ? "TMS服务不可用" : tmsResult.get("msg")));
}
}
if (!Integer.valueOf(2).equals(executeOrder.getDelFlag())) {
executeOrder.setDelFlag(2);
executeOrderMapper.updateById(executeOrder);
}
}
// 2. 软删运输业务单(主单+子单)及其货品/地址明细(主单已软删的只补清理子单/明细, 不重复更新主单)
if (!Integer.valueOf(2).equals(transportOrder.getDelFlag())) {
transportOrder.setDelFlag(2);
businessDocumentOrderMapper.updateById(transportOrder);
}
for (BusinessDocumentOrder subOrder : subOrders) {
subOrder.setDelFlag(2);
businessDocumentOrderMapper.updateById(subOrder);
}
List<String> chainDocIdStrings = chainDocIds.stream().map(String::valueOf).collect(Collectors.toList());
businessDocumentCargoMultiMapper.update(null, new LambdaUpdateWrapper<BusinessDocumentCargoMulti>()
.set(BusinessDocumentCargoMulti::getDelFlag, 2)
.in(BusinessDocumentCargoMulti::getOrderId, chainDocIdStrings)
.eq(BusinessDocumentCargoMulti::getDelFlag, 1));
businessDocumentAddressMultiMapper.update(null, new LambdaUpdateWrapper<BusinessDocumentAddressMulti>()
.set(BusinessDocumentAddressMulti::getDelFlag, 2)
.in(BusinessDocumentAddressMulti::getOrderId, chainDocIdStrings)
.eq(BusinessDocumentAddressMulti::getDelFlag, 1));
}
// 3. 配送明细链软删: 链的orderId固定指向运输业务单-主单, 按本次命中的主单id清理(含历史周期残留链)
businessDeliveryDetailsLinkService.update(null, new LambdaUpdateWrapper<BusinessDeliveryDetailsLink>()
.set(BusinessDeliveryDetailsLink::getDelFlag, 2)
.in(BusinessDeliveryDetailsLink::getOrderId, transportOrderIds)
.eq(BusinessDeliveryDetailsLink::getDelFlag, 1));
}
/**
* 获取仓库信息(Feign优先, system服务失败/降级时本地跨库兜底), 统一委托共享组件
*/
private WarehouseFeignPO getWarehouseInfoWithFallback(Long warehouseId) {
return warehouseInfoApplicationService.getWarehouseInfo(warehouseId);
}
/**
* 获取仓库单号生成规则(下发生成执行单号用): 优先 Feign system 服务;
* Feign 异常/降级/返回非200时回退本地跨库直查 NGWL_TEST_SYSTEM.WAREHOUSE(同库同表, getWarehouseCode 等查询同数据源),
* 避免 system 服务抖动阻断下发; 两侧都取不到才报错并带出 Feign 真实返回原因
*/
private WarehouseFeignPO getWarehouseOrderNoRule(Long warehouseId) {
return getWarehouseInfoWithFallback(warehouseId);
}
/**
* @description 审核出库单
* @author ZhouGY
@@ -531,8 +667,14 @@ public class ReservationStockOutOrderDomainService {
if (activeExecutionCount > 0) {
throw new ServiceException("出库单[" + inOrderNumber + "]已下发,请勿重复下发;如需重新下发请先取消下发");
}
// 自愈: 历史版本反审/取消下发的归档明细可能仍占用unique_id, 先置空释放键位再重新下发
releaseArchivedDetailUniqueId(inOrderNumber);
List<String> executionInOrderNumbers = new ArrayList<>();
List<ReservationOutMaterialDetail> materialDetailList = outMaterialDetailService.list(new QueryWrapper<ReservationOutMaterialDetail>().lambda().eq(ReservationOutMaterialDetail::getOutOrderNumber, inOrderNumber));
// 下发明细必须过滤软删行(del_flag=1)编辑对账软删的旧行仍留表内
// 不滤会参与按仓库分组/数量汇总多生成执行单且数量叠加(入库侧同查询已过滤)
List<ReservationOutMaterialDetail> materialDetailList = outMaterialDetailService.list(new QueryWrapper<ReservationOutMaterialDetail>().lambda()
.eq(ReservationOutMaterialDetail::getOutOrderNumber, inOrderNumber)
.eq(ReservationOutMaterialDetail::getDelFlag, 1));
// 按照 warehouseId 分组
Map<Long, List<ReservationOutMaterialDetail>> materialDetailGroupByWarehouse = materialDetailList.stream()
@@ -550,7 +692,6 @@ public class ReservationStockOutOrderDomainService {
reservationStockInOrder.setOrderTypeCode("pu_tong_chu_ku");
reservationStockInOrder.setOrderTypeName("普通出库");
reservationStockInOrder.setBusinessType("pu_tong_chu_ku");
reservationStockInOrder.setReservationOrderSource("客户预约");
reservationStockInOrder.setWarehouseId(warehouseId);
reservationStockInOrder.setWarehouseCode(warehouseCode);
reservationStockInOrder.setWarehouseName(warehouseName);
@@ -566,14 +707,8 @@ public class ReservationStockOutOrderDomainService {
reservationStockInOrder.setBusinessOutOrderNumber(inOrderNumber);
// reservationStockInOrder.setOutOrderNumber(generateExecutionOrderNumber(warehouseCode));
//设置单号
Long warehouseId1 = reservationStockInOrder.getWarehouseId();
AjaxResult ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(warehouseId1);
if (!"200".equals(String.valueOf(ajaxResult.get("code")))) {
throw new ServiceException("获取仓库信息失败");
}
//仓库信息
WarehouseFeignPO warehouseFeignPO = com.alibaba.fastjson.JSON.parseObject(com.alibaba.fastjson2.JSONObject.toJSONString(ajaxResult.get("data")), WarehouseFeignPO.class);
//设置单号(仓库单号规则: Feign优先, system服务失败/降级时本地跨库兜底)
WarehouseFeignPO warehouseFeignPO = getWarehouseOrderNoRule(reservationStockInOrder.getWarehouseId());
String orderNoGenerateRule = warehouseFeignPO.getOrderNoGenerateRule();
String prefix = warehouseFeignPO.getOutOrderNoPrefix();
if ("yang_pin_chu_ku".equals(stockOutOrderDO.getBusinessType())) {
@@ -662,6 +797,8 @@ public class ReservationStockOutOrderDomainService {
materialDetails.forEach(executionOutMaterialDetail -> {
executionOutMaterialDetailService.update(null, new UpdateWrapper<ExecutionOutMaterialDetail>().lambda()
.set(ExecutionOutMaterialDetail::getDelFlag, 2)
// 归档行置空uniqueId: 明细表唯一约束不含del_flag, 保留原值会与重新下发时从预留明细复制的同一uniqueId撞键
.set(ExecutionOutMaterialDetail::getUniqueId, null)
.eq(ExecutionOutMaterialDetail::getMaterialDetailId, executionOutMaterialDetail.getMaterialDetailId()));
});
executionStockOutOrderService.update(null, new UpdateWrapper<ExecutionStockOutOrder>().lambda()
@@ -676,7 +813,8 @@ public class ReservationStockOutOrderDomainService {
* @description 反审出库业务单(按前端展示字段 issueStatus 判断可反审范围: 2-已审核(未出库) / 4-已出库 均可反审;
* 0-已创建无需反审3-已驳回可直接编辑; status 字段在出库完成后不一定同步为9, 故不作为判断依据,
* 仅兜底排除 10-已取消/11-已关闭)
* 反审将单据回退到最原始状态(1-已创建, issueStatus=0)并打上反审标记:
* 反审将单据回退到已创建(status=1)且审核状态置为已驳回(issueStatus=3, 与入库侧反审同口径, 可编辑重新审核重新下发)
* 并打上反审标记:
* WMS 侧按出库单实际状态分支还原库存(已出库: 实物库存还原+推OMS; 未出库: 仅还原分配/冻结占用)
* 清理执行单据(出库单/明细/拣货/复核/交接/配送, 软删+reverseFlag=1 划归已反审tab)
* 每个执行出库单生成1条出库异常记录反审记录(out_order_abnormal, abnormalType=3);
@@ -738,6 +876,8 @@ public class ReservationStockOutOrderDomainService {
executionOutMaterialDetailService.update(null, new UpdateWrapper<ExecutionOutMaterialDetail>().lambda()
.set(ExecutionOutMaterialDetail::getDelFlag, 2)
.set(ExecutionOutMaterialDetail::getReverseFlag, 1)
// 归档行置空uniqueId: 明细表唯一约束不含del_flag, 保留原值会与重新下发时从预留明细复制的同一uniqueId撞键
.set(ExecutionOutMaterialDetail::getUniqueId, null)
.eq(ExecutionOutMaterialDetail::getMaterialDetailId, executionOutMaterialDetail.getMaterialDetailId()));
});
executionStockOutOrderService.update(null, new UpdateWrapper<ExecutionStockOutOrder>().lambda()
@@ -750,15 +890,20 @@ public class ReservationStockOutOrderDomainService {
.eq(ReservationInventoryAdjustmentRecord::getExecutionOrderNumber, executionOutOrderNumber)
.eq(ReservationInventoryAdjustmentRecord::getDelFlag, 1));
}
// 反审联动: 软删关联运输链路(运输业务单/运输执行单/TMS货源单-运单-作业单), 无关联运输单时跳过
reverseTransportChain(byId);
}
// 主单回退到已创建并打反审标记; 更新条件带 issueStatus in (2,4) + 排除已取消/已关闭 兜底防并发重复反审
// issueStatus 回退到 0-创建(前端展示映射: 0-已创建 2-已审核 3-已驳回 4-已出库);
// issueStatus 回退到 3-驳回(前端展示映射: 0-已创建 2-已审核 3-已驳回 4-已出库; 与入库侧反审同口径,
// 已驳回态可编辑重新审核重新下发, 重新审核通过时 reverse_status 自动复位 0 离开已反审tab);
// 清空执行出库单号执行单已随反审软删, 前端"是否执行"列由该字段有无值推导, 不清空会仍显示已执行
return stockOutOrderService.update(null, new UpdateWrapper<ReservationStockOutOrder>().lambda()
.set(ReservationStockOutOrder::getStatus, 1)
.set(ReservationStockOutOrder::getIssueStatus, 0)
.set(ReservationStockOutOrder::getIssueStatus, 3)
.set(ReservationStockOutOrder::getExecutionOutOrderNumber, null)
// 运输业务单已随反审软删, 货源单号一并清空, 重新审核后重建运输链路时自动回填
.set(ReservationStockOutOrder::getBusinessNumber, null)
.set(ReservationStockOutOrder::getIssueId, null)
.set(ReservationStockOutOrder::getIssueName, null)
.set(ReservationStockOutOrder::getIssueTime, null)
@@ -816,7 +961,13 @@ public class ReservationStockOutOrderDomainService {
String warehouseCode1 = byId.getWarehouseCode();
String warehouseName1 = byId.getWarehouseName();
String inOrderNumber = byId.getOutOrderNumber();
List<ReservationOutMaterialDetail> materialDetailList = outMaterialDetailService.list(new QueryWrapper<ReservationOutMaterialDetail>().lambda().eq(ReservationOutMaterialDetail::getOutOrderNumber, inOrderNumber));
// 自愈: 历史版本反审/取消下发的归档明细可能仍占用unique_id, 先置空释放键位再重新下发
releaseArchivedDetailUniqueId(inOrderNumber);
// 下发明细必须过滤软删行(del_flag=1)编辑对账软删的旧行仍留表内
// 不滤会参与按仓库分组/数量汇总多生成执行单且数量叠加(入库侧同查询已过滤)
List<ReservationOutMaterialDetail> materialDetailList = outMaterialDetailService.list(new QueryWrapper<ReservationOutMaterialDetail>().lambda()
.eq(ReservationOutMaterialDetail::getOutOrderNumber, inOrderNumber)
.eq(ReservationOutMaterialDetail::getDelFlag, 1));
// 按照 warehouseId 分组
Map<Long, List<ReservationOutMaterialDetail>> materialDetailGroupByWarehouse = materialDetailList.stream()
@@ -880,7 +1031,6 @@ public class ReservationStockOutOrderDomainService {
reservationStockInOrder.setOrderTypeCode("pu_tong_chu_ku");
reservationStockInOrder.setOrderTypeName("普通出库");
reservationStockInOrder.setBusinessType("pu_tong_chu_ku");
reservationStockInOrder.setReservationOrderSource("客户预约");
reservationStockInOrder.setWarehouseId(warehouseId);
reservationStockInOrder.setOrganizationId(orgId);
reservationStockInOrder.setOrganizationName(orgName);
@@ -890,14 +1040,8 @@ public class ReservationStockOutOrderDomainService {
reservationStockInOrder.setBusinessOutOrderNumber(inOrderNumber);
//设置单号
// Long warehouseId1 = reservationStockInOrder.getWarehouseId();
AjaxResult ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(warehouseId);
if (!"200".equals(String.valueOf(ajaxResult.get("code")))) {
throw new ServiceException("获取仓库信息失败");
}
//仓库信息
WarehouseFeignPO warehouseFeignPO = com.alibaba.fastjson.JSON.parseObject(com.alibaba.fastjson2.JSONObject.toJSONString(ajaxResult.get("data")), WarehouseFeignPO.class);
//设置单号(仓库单号规则: Feign优先, system服务失败/降级时本地跨库兜底)
WarehouseFeignPO warehouseFeignPO = getWarehouseOrderNoRule(warehouseId);
String orderNoGenerateRule = warehouseFeignPO.getOrderNoGenerateRule();
String prefix = warehouseFeignPO.getOutOrderNoPrefix();
if ("yang_pin_chu_ku".equals(stockOutOrderDO.getBusinessType())) {
@@ -68,6 +68,8 @@ import java.util.concurrent.ThreadLocalRandom;
public class ReserveStockOutOrderApplicationService {
@Autowired
private ReserveStockOutOrderDomainService stockOutOrderDomainService;
@Autowired
private com.mhd.oms.application.service.WarehouseInfoApplicationService warehouseInfoApplicationService;
// @Autowired
// private ReserveOutMaterialDetailDomainService outMaterialDetailDomainService;
// @Autowired
@@ -895,11 +897,8 @@ public class ReserveStockOutOrderApplicationService {
if (stockOutOrderDO.getWarehouseId() == null) {
throw new ServiceException("仓库ID不能为空");
}
AjaxResult ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(stockOutOrderDO.getWarehouseId());
if(!"200".equals(String.valueOf(ajaxResult.get("code")))){
throw new ServiceException("获取仓库信息失败");
}
WarehouseFeignPO warehouseFeignPO = JSON.parseObject(JSONObject.toJSONString(ajaxResult.get("data")), WarehouseFeignPO.class);
// Feign失败走本地跨库兜底, 避免保存被"获取仓库信息失败"阻断
WarehouseFeignPO warehouseFeignPO = warehouseInfoApplicationService.getWarehouseInfo(stockOutOrderDO.getWarehouseId());
stockOutOrderDO.setWarehouseCode(warehouseFeignPO.getWarehouseCode());
stockOutOrderDO.setWarehouseName(warehouseFeignPO.getWarehouseName());
}
@@ -1278,8 +1278,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
select PUSH_VERIFY from NGWL_TEST_PRODUCT.SYS_ORGANIZATION where ORGANIZATION_ID = #{organizationId}
</select>
<select id="countByGoodsNumberPrefix" resultType="java.lang.Integer">
SELECT COUNT(1)
<select id="selectGoodsNumbersByPrefix" resultType="java.lang.String">
SELECT goods_number
FROM business_document_order
WHERE goods_number LIKE concat(#{prefix}, '%')
AND LENGTH(goods_number) &lt;= 12
@@ -42,6 +42,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<sql id="selectExecuteOrderPo1">
<where>
<!-- 默认只查正常单(del_flag=1); 反审/删除的归档行需显式传delFlag=2(已反审tab) -->
<choose>
<when test="delFlag != null">and eo.DEL_FLAG = #{delFlag}</when>
<otherwise>and eo.DEL_FLAG = 1</otherwise>
</choose>
<if test="createStartTime != null">
and eo.CREATE_TIME &gt;= #{createStartTime}
</if>
@@ -52,6 +52,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="updateBy" column="update_by" />
<result property="updateByName" column="update_by_name" />
<result property="delFlag" column="del_flag" />
<result property="reverseFlag" column="reverse_flag" />
<result property="overStockId" column="over_stock_id" />
<result property="overStockStatus" column="over_stock_status" />
<result property="overStockType" column="over_stock_type" />
@@ -127,7 +128,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
a.cancel_time, a.close_order, a.close_remark, a.close_by, a.close_by_name, a.close_time, a.check_status,a.BUSINESS_ORDER_NO,
a.check_task_distribution, a.check_task_distribution_time, a.check_operators_by, a.check_operators_name, a.mobilize_code,
a.remark, a.create_time, a.create_by, a.create_by_name,a.business_number,a.train_no,a.amount,a.business_out_order_number,
a.update_time, a.update_by, a.update_by_name, a.del_flag, a.over_stock_id, a.over_stock_status, a.over_stock_type,a."TO",
a.update_time, a.update_by, a.update_by_name, a.del_flag, a.reverse_flag, a.over_stock_id, a.over_stock_status, a.over_stock_type,a."TO",
a.CONTACT_PERSON,a.VEHICLE_MODEL_PLATE,a.ENTRUST_NO,a.TEL,a.DRIVER_SIGN,a.FAX,a.DECLARE_CUSTOMS,a.MANUFACTURER,
a.CUSTOMS_DECLARER_INFO,a.DELIVERY_ADDR,a.ENTRY_EXIT_CUSTOMS,a.DEPARTURE_ARRIVAL_COUNTRY,a.CARRIER,a.CONTAINER_NO,a.CONTAINER_NO_NAME,a.OUTBOUND_DATE,
a.COMPLETE_TIME,a.ORDER_DATE,a.TRANSPORT_MODE_CODE,a.TRANSPORT_MODE_NAME,a.SUPERVISION_MODE_CODE,a.SUPERVISION_MODE_NAME,
@@ -401,6 +402,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<when test="delFlag != null"> and a.del_flag = #{delFlag} </when>
<otherwise> and a.del_flag = 1 </otherwise>
</choose>
<!-- 反审标记查询: OMS执行单【已反审】tab 前端传 delFlag=2 + reverseFlag=1 (与WMS四列表同口径, 两参数须成对传) -->
<if test="reverseFlag != null"> and a.reverse_flag = #{reverseFlag} </if>
</sql>
<select id="queryList" parameterType="com.mhd.oms.domain.executionStockOutOrder.repository.todo.ExecutionStockOutOrderDO" resultMap="StockOutOrderResult">
@@ -592,6 +592,19 @@
select WAREHOUSE_CODE from "NGWL_TEST_SYSTEM".WAREHOUSE where 1 = 1 and DEL_FLAG = 1 and WAREHOUSE_ID = #{id} limit 1
</select>
<!-- 本地跨库直查仓库信息: 下发/保存等流程 Feign 获取仓库信息失败/降级时兜底(与 system 服务同库同表)。
注意不含 STOCK_LOCK_MODE(冻结功能列, 各环境库结构不一致, 兜底路径不依赖冻结功能的列;
stockLockMode 走 Feign 正常路径获取, 兜底时为 null 即默认"分配库存冻结"模式) -->
<select id="getWarehouseInfoFallback" parameterType="java.lang.Long" resultType="com.mhd.system.api.domain.WarehouseFeignPO">
select WAREHOUSE_CODE as warehouseCode,
WAREHOUSE_NAME as warehouseName,
ORDER_NO_GENERATE_RULE as orderNoGenerateRule,
OUT_ORDER_NO_PREFIX as outOrderNoPrefix,
SAMPLE_OUT_ORDER_NO_PREFIX as sampleOutOrderNoPrefix
from "NGWL_TEST_SYSTEM".WAREHOUSE
where 1 = 1 and DEL_FLAG = 1 and WAREHOUSE_ID = #{id} limit 1
</select>
<!-- 入仓通知单打印-表头查询 -->
<select id="queryNoticePrint" resultMap="StockInOrderResult">
@@ -201,9 +201,22 @@ public class StockReceiptOrderApplicationService {
*/
public StockReceiptOrderPO getInfo(Long receiptOrderId)
{
StockReceiptOrderPO stockReceiptOrderPO = stockReceiptOrderDomainService.getInfo(receiptOrderId);
return getInfo(receiptOrderId, false);
}
/**
* 获取收货单详情
* @param archive true-查询已反审tab 归档数据已软删+已反审标记明细放行软删行
*/
public StockReceiptOrderPO getInfo(Long receiptOrderId, boolean archive)
{
StockReceiptOrderPO stockReceiptOrderPO = stockReceiptOrderDomainService.getInfo(receiptOrderId, archive);
ReceiptMaterialDetailDO receiptMaterialDetailDO = new ReceiptMaterialDetailDO();
receiptMaterialDetailDO.setReceiptOrderNumber(stockReceiptOrderPO.getReceiptOrderNumber());
if (archive) {
//归档明细已随主单软删放行软删行
receiptMaterialDetailDO.setDelFlag(2);
}
List<ReceiptMaterialDetailPO> receiptMaterialDetailPOList = receiptMaterialDetailDomainService.queryListChildren(receiptMaterialDetailDO);
// 性能优化批量获取批次属性避免N+1查询问题
@@ -306,6 +319,14 @@ public class StockReceiptOrderApplicationService {
totalNetWeight, totalGrossWeight, totalVolume, totalArea);
return stockReceiptOrderPO;
}
/**
* 查询收货单已反审tab 归档详情已软删+已反审标记的数据含软删明细
*/
public StockReceiptOrderPO getReverseAuditInfo(Long receiptOrderId)
{
return getInfo(receiptOrderId, true);
}
/**
* 根据物料基础信息ID获取批次属性更多属性只返回isDisplay=1的属性
@@ -134,12 +134,12 @@ public class StockShelfOrderApplicationService {
private static final String VERIFY = "ESLuxAmB6VXCgbqP1eFzxTPVRagkQ62BUkfhElrgQ6XmE/g2reHVflY/SiCSU7JD";
/** 仅这些仓库的上架单在PC上传页默认填充上架库位按仓库名匹配需放开其他仓库时在此追加
* 生产环境维他奶仓warehouseId=14已对照线上库位树验证库区id=20库位id=33测试环境为 青洲仓warehouseId=13需测试时切换 */
private static final Set<String> DEFAULT_STORAGE_LOCATION_WAREHOUSE_NAMES = new HashSet<>(Collections.singletonList("维他奶"));
/** 默认上架库位指定库区名库位名精确匹配须与基础资料名称完全一致注意含繁体字
* 生产环境維他奶2层冷凍A區 2层冷凍A區A01测试环境为 4楼A区 4A-01-01需测试时切换 */
private static final String DEFAULT_SHELF_STORAGE_NAME = "維他奶2层冷凍A區";
private static final String DEFAULT_SHELF_STORAGE_LOCATION_NAME = "2层冷凍A區A01";
* 测试环境青洲仓warehouseId=13生产环境为 维他奶仓warehouseId=14已对照线上库位树验证库区id=20库位id=33发版前需切换 */
private static final Set<String> DEFAULT_STORAGE_LOCATION_WAREHOUSE_NAMES = new HashSet<>(Collections.singletonList("青洲"));
/** 默认上架库位指定库区名库位名精确匹配须与基础资料名称完全一致
* 测试环境4楼A区 4A-01-01生产环境为 維他奶2层冷凍A區 2层冷凍A區A01含繁体字发版前需切换 */
private static final String DEFAULT_SHELF_STORAGE_NAME = "4楼A区";
private static final String DEFAULT_SHELF_STORAGE_LOCATION_NAME = "4A-01-01";
/** 仅这些组织的上架单在PC上传页默认填充上架库位(按组织id精确匹配;测试与生产均为 谷丰=2842,若不一致需按环境切换) */
private static final Set<Long> DEFAULT_STORAGE_LOCATION_ORGANIZATION_IDS = new HashSet<>(Collections.singletonList(2842L));
@@ -243,6 +243,13 @@ public class StockShelfOrderApplicationService {
return getInfo(shelfOrderId, false);
}
/**
* 查询上架单已反审tab 归档详情已软删+已反审标记的数据含软删明细
*/
public StockShelfOrderPO getReverseAuditInfo(Long shelfOrderId) {
return stockShelfOrderDomainService.getInfo(shelfOrderId, true);
}
/**
* 获取上架单详细信息
*
@@ -187,6 +187,8 @@ public class InOrderAbnormalImpl extends ServiceImpl<InOrderAbnormalMapper, InOr
inOrderAbnormal.setAbnormalNumber(OrderSequence.getOrderCode("FSYC"));
inOrderAbnormal.setType(3);
inOrderAbnormal.setAbnormalType(6);
//del_flag 不显式置1时 insert 不含该列列无默认值则落库为NULL会被列表的 del_flag=1 过滤掉导致记录永远不可见
inOrderAbnormal.setDelFlag(1);
if (inOrderAbnormal.getCreateTime() == null) {
inOrderAbnormal.setCreateTime(new Date());
}
@@ -242,6 +244,8 @@ public class InOrderAbnormalImpl extends ServiceImpl<InOrderAbnormalMapper, InOr
inOrderAbnormal.setCreateByName(userRealName);
inOrderAbnormal.setCreateTime(new Date());
inOrderAbnormal.setType(2);
//del_flag 不显式置1时 insert 不含该列列无默认值则落库为NULL会被列表的 del_flag=1 过滤掉导致记录永远不可见
inOrderAbnormal.setDelFlag(1);
inOrderAbnormalList.add(inOrderAbnormal);
}
}
@@ -287,6 +291,8 @@ public class InOrderAbnormalImpl extends ServiceImpl<InOrderAbnormalMapper, InOr
inOrderAbnormal.setCreateByName(userRealName);
inOrderAbnormal.setCreateTime(new Date());
inOrderAbnormal.setType(1);
//del_flag 不显式置1时 insert 不含该列列无默认值则落库为NULL会被列表的 del_flag=1 过滤掉导致记录永远不可见
inOrderAbnormal.setDelFlag(1);
inOrderAbnormalList.add(inOrderAbnormal);
}
}
@@ -26,6 +26,12 @@ public interface PickingMaterialDetailMapper extends BaseMapper<PickingMaterialD
public PickingMaterialDetailPO queryTotalsByOutNumberAndBaseId(@Param("outUniqueId") Long outUniqueId);
/**
* 归档汇总(已反审): queryTotalsByOutNumberAndBaseId, 但按 DEL_FLAG=2 统计软删归档的拣货明细,
* 供交接单归档详情 getReverseInfo 使用(反审时拣货明细均已软删, del_flag=1 统计恒为0会丢行)
*/
public PickingMaterialDetailPO queryTotalsByOutNumberAndBaseIdReverse(@Param("outUniqueId") Long outUniqueId);
/**
* 按出库单号汇总拣货完成数量实际下发/拣货完成的数量
*/
@@ -2632,6 +2632,8 @@ public class StockOutOrderDomainService {
.eq(InventoryAdjustmentRecord::getRelateNo, relateNo));
//逻辑删除出库单明细
materialDetail.setDelFlag(2);
// 归档行置空uniqueId: 明细表唯一约束不含del_flag, 保留原值会与重新下发推单时同一uniqueId的新明细撞键
materialDetail.setUniqueId(null);
outMaterialDetailService.updateById(materialDetail);
}
//逻辑删除出库单用按单号查出的DB对象更新OMS补偿清理只传单号传入对象无主键直接updateById不生效
@@ -2772,6 +2774,8 @@ public class StockOutOrderDomainService {
}
//逻辑删除出库单明细
materialDetail.setDelFlag(2);
// 归档行置空uniqueId: 明细表唯一约束不含del_flag, 保留原值会与重新下发推单时同一uniqueId的新明细撞键
materialDetail.setUniqueId(null);
materialDetail.setReverseFlag(1);
outMaterialDetailService.updateById(materialDetail);
}
@@ -2845,11 +2849,25 @@ public class StockOutOrderDomainService {
if (CollectionUtils.isEmpty(outMaterialDetails)) {
return;
}
//幂等保护重复下发时先逻辑删除同单号已存在的活动明细避免重复插入导致分配/拣货数据错乱
//幂等保护重复下发时本单已有活动明细统一软删(12)并置空uniqueId
outMaterialDetailService.update(new UpdateWrapper<OutMaterialDetail>().lambda()
.set(OutMaterialDetail::getDelFlag, 2)
.set(OutMaterialDetail::getUniqueId, null)
.eq(OutMaterialDetail::getOutOrderNumber, outMaterialDetails.get(0).getOutOrderNumber())
.eq(OutMaterialDetail::getDelFlag, 1));
//自愈反审/取消下发的历史归档明细(del_flag=2, 含其他执行单残留)若仍占用本次推送的uniqueId, 置空释放键位
//(表唯一约束不含del_flag; 反审后重新下发会复用同一库存行的uniqueId, 跨执行单也要清, 否则明细插入撞唯一约束)
List<Long> pushedUniqueIds = outMaterialDetails.stream()
.map(OutMaterialDetail::getUniqueId)
.filter(java.util.Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (!pushedUniqueIds.isEmpty()) {
outMaterialDetailService.update(new UpdateWrapper<OutMaterialDetail>().lambda()
.set(OutMaterialDetail::getUniqueId, null)
.eq(OutMaterialDetail::getDelFlag, 2)
.in(OutMaterialDetail::getUniqueId, pushedUniqueIds));
}
outMaterialDetailService.saveBatch(outMaterialDetails);
autoAuditOmsOutOrder(outMaterialDetails.get(0).getOutOrderNumber());
}
@@ -261,7 +261,26 @@ public class StockReceiptOrderDomainService {
* 获取收货单详细信息
*/
public StockReceiptOrderPO getInfo(Long receiptOrderId) {
return stockReceiptOrderService.getInfo(receiptOrderId);
return getInfo(receiptOrderId, false);
}
/**
* 获取收货单详情
* @param archive true-查询已反审tab 归档数据已软删+已反审标记
*/
public StockReceiptOrderPO getInfo(Long receiptOrderId, boolean archive) {
if (!archive) {
return stockReceiptOrderService.getInfo(receiptOrderId);
}
//归档主单已软删 MP getById不过滤 del_flag并校验已反审标记
StockReceiptOrder order = stockReceiptOrderService.getById(receiptOrderId);
if (order == null || order.getDelFlag() == null || order.getDelFlag() != 2
|| !Integer.valueOf(1).equals(order.getReverseAuditStatus())) {
throw new ServiceException("收货单非已反审归档数据,无法查看归档详情");
}
StockReceiptOrderPO po = new StockReceiptOrderPO();
org.springframework.beans.BeanUtils.copyProperties(order, po);
return po;
}
/**
@@ -243,9 +243,34 @@ public class StockShelfOrderDomainService {
*/
public StockShelfOrderPO getInfo(Long shelfOrderId)
{
StockShelfOrderPO stockShelfOrderPO = stockShelfOrderService.getInfo(shelfOrderId);
return getInfo(shelfOrderId, false);
}
/**
* 获取上架单详情
* @param archive true-查询已反审tab 归档数据已软删+已反审标记明细放行软删行
*/
public StockShelfOrderPO getInfo(Long shelfOrderId, boolean archive)
{
StockShelfOrderPO stockShelfOrderPO;
if (archive) {
//归档主单已软删 MP getById不过滤 del_flag并校验已反审标记
StockShelfOrder order = stockShelfOrderService.getById(shelfOrderId);
if (order == null || order.getDelFlag() == null || order.getDelFlag() != 2
|| !Integer.valueOf(1).equals(order.getReverseAuditStatus())) {
throw new ServiceException("上架单非已反审归档数据,无法查看归档详情");
}
stockShelfOrderPO = new StockShelfOrderPO();
org.springframework.beans.BeanUtils.copyProperties(order, stockShelfOrderPO);
} else {
stockShelfOrderPO = stockShelfOrderService.getInfo(shelfOrderId);
}
ShelfMaterialDetailDO shelfMaterialDetailDO = new ShelfMaterialDetailDO();
shelfMaterialDetailDO.setShelfOrderNumber(stockShelfOrderPO.getShelfOrderNumber());
if (archive) {
//归档明细已随主单软删放行软删行
shelfMaterialDetailDO.setDelFlag(2);
}
List<ShelfMaterialDetailPO> inMaterialDetailPOList = shelfMaterialDetailService.queryListChildren(shelfMaterialDetailDO);
stockShelfOrderPO.setMaterialDetailList(inMaterialDetailPOList);
return stockShelfOrderPO;
@@ -521,8 +521,9 @@ public class StockOutOrderApi extends BaseController {
*/
@ApiOperation("oms下发单保存")
@PostMapping(value = "/omsOutOrderAdd")
public void omsOutOrderAdd(@RequestBody StockOutOrder stockOutOrder) {
public AjaxResult omsOutOrderAdd(@RequestBody StockOutOrder stockOutOrder) {
stockOutOrderApplicationService.omsOrderAdd(stockOutOrder);
return AjaxResult.success();
}
/**
@@ -550,8 +551,9 @@ public class StockOutOrderApi extends BaseController {
*/
@ApiOperation("oms下发单明细保存")
@PostMapping(value = "/omsOutOrderAddDetail")
public void omsOutOrderAddDetail(@RequestBody List<OutMaterialDetail> outMaterialDetails) {
public AjaxResult omsOutOrderAddDetail(@RequestBody List<OutMaterialDetail> outMaterialDetails) {
stockOutOrderApplicationService.omsOrderAddDetail(outMaterialDetails);
return AjaxResult.success();
}
/**
@@ -587,8 +589,9 @@ public class StockOutOrderApi extends BaseController {
*/
@ApiOperation("oms下发单配送明细保存")
@PostMapping(value = "/omsOutOrderAddDeliveryDetailsLink")
public void omsOutOrderAddDeliveryDetailsLink(@RequestBody List<DeliveryDetailsLink> deliveryDetailLinks) {
public AjaxResult omsOutOrderAddDeliveryDetailsLink(@RequestBody List<DeliveryDetailsLink> deliveryDetailLinks) {
stockOutOrderApplicationService.omsOrderAddDeliveryDetailsLink(deliveryDetailLinks);
return AjaxResult.success();
}
@ApiOperation("出库单提货单打印")
@@ -64,6 +64,16 @@ public class StockReceiptOrderApi extends BaseController {
return AjaxResult.success(stockReceiptOrderApplicationService.getInfo(receiptOrderId));
}
/**
* 查询收货单已反审tab 归档详情已软删+已反审标记的数据含软删明细
*/
@ApiOperation("查询收货单已反审归档详情")
@GetMapping(value = "/getReverseAuditInfo/{receiptOrderId}")
public AjaxResult getReverseAuditInfo(@PathVariable("receiptOrderId") Long receiptOrderId)
{
return AjaxResult.success(stockReceiptOrderApplicationService.getReverseAuditInfo(receiptOrderId));
}
@ApiOperation("分配月台")
@PostMapping("/allocationPlatform")
public AjaxResult allocationPlatform(@RequestBody StockReceiptOrderDTO stockReceiptOrderDTO) {
@@ -64,6 +64,16 @@ public class StockShelfOrderApi extends BaseController {
return AjaxResult.success(stockShelfOrderApplicationService.getInfo(shelfOrderId, false));
}
/**
* 查询上架单已反审tab 归档详情已软删+已反审标记的数据含软删明细
*/
@ApiOperation("查询上架单已反审归档详情")
@GetMapping(value = "/getReverseAuditInfo/{shelfOrderId}")
public AjaxResult getReverseAuditInfo(@PathVariable("shelfOrderId") Long shelfOrderId)
{
return AjaxResult.success(stockShelfOrderApplicationService.getReverseAuditInfo(shelfOrderId));
}
@ApiOperation("任务下发")
@PostMapping("/taskDistribution")
public AjaxResult taskDistribution(@RequestBody StockShelfOrderDTO stockShelfOrderDTO) {
@@ -91,7 +91,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<include refid="selectInOrderAbnormalPo"/>
<include refid="com.mhd.wms.domain.stockInOrder.repository.mapper.StockInOrderMapper.joinStockInOrderPo"/>
from in_order_abnormal a
LEFT JOIN stock_in_order b ON a.in_order_number = b.in_order_number and a.DEL_FLAG = 1 and b.DEL_FLAG = 1
<!-- 反审记录(a.TYPE=3)关联的W入库单已被反审软删(del_flag=2),仍按 b.DEL_FLAG=1 关联会匹配不上:
LEFT JOIN 下 b 全为 NULL,仓库/货主等基于 b 的过滤条件(warehouseId等)随之恒为 false,反审记录列表恒为空;
故按 a.TYPE 切换关联状态:type=3 关联已软删的入库单,其余(收货/上架异常)关联正常入库单。
b.IN_ORDER_ID 取同状态最新一张:历史重复下发会产生同号多张入库单,不限定会JOIN出重复行 -->
LEFT JOIN stock_in_order b ON a.in_order_number = b.in_order_number
and a.DEL_FLAG = 1
and b.DEL_FLAG = (case when a.TYPE = 3 then 2 else 1 end)
and b.IN_ORDER_ID = (select max(c.IN_ORDER_ID) from stock_in_order c
where c.IN_ORDER_NUMBER = a.in_order_number
and c.DEL_FLAG = (case when a.TYPE = 3 then 2 else 1 end))
<where>
<include refid="selectInOrderAbnormalPo1"/>
<include refid="com.mhd.wms.domain.stockInOrder.repository.mapper.StockInOrderMapper.joinStockInOrderPoWhere"/>
@@ -101,10 +101,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="orderTypeCode != null and orderTypeCode != ''">
and b.order_type_code = #{orderTypeCode}
</if>
<choose>
<when test="delFlag != null"> and b.del_flag = #{delFlag} </when>
<otherwise> and b.del_flag = 1 </otherwise>
</choose>
<!-- 不默认过滤 b.del_flag: 反审后出库单已软删(del_flag=2), 默认 b.del_flag=1 会让反审生成的异常记录在列表中消失;
仅当前端显式传 delFlag 时才过滤 -->
<if test="delFlag != null"> and b.del_flag = #{delFlag} </if>
</sql>
<select id="queryList" parameterType="com.mhd.wms.domain.outOrderAbnormal.repository.todo.OutOrderAbnormalDO" resultMap="OutOrderAbnormalResult">
@@ -112,7 +111,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<include refid="selectOutOrderAbnormalPo11"/>
<include refid="com.mhd.wms.domain.stockOutOrder.repository.mapper.StockOutOrderMapper.joinStockOutOrderPoYc"/>
from out_order_abnormal a
left join stock_out_order b on a.out_order_number = b.out_order_number and b.del_flag = 1
<!-- 不按 b.del_flag 限定: 出库单号唯一, 关联活动单或反审归档单均按单号取数, 保证反审异常记录能带出出库单信息 -->
left join stock_out_order b on a.out_order_number = b.out_order_number
<where>
<include refid="selectOutOrderAbnormalPo1"/>
<include refid="joinStockOutOrderPoWhere"/>
@@ -248,6 +248,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND b.DEL_FLAG = 1
</select>
<!-- 归档汇总(已反审): 反审时拣货明细已软删(del_flag=2), 按 del_flag=1 统计恒为0会丢行, 供交接单归档详情使用 -->
<select id="queryTotalsByOutNumberAndBaseIdReverse" resultType="com.mhd.wms.domain.pickingMaterialDetail.repository.po.PickingMaterialDetailPO">
SELECT
COALESCE(SUM(b.PICKING_QUANTITY), 0) AS picking_quantity,
COALESCE(SUM(b.TOTAL_AREA), 0) AS total_area,
COALESCE(SUM(b.TOTAL_VOLUME), 0) AS total_volume,
COALESCE(SUM(b.TOTAL_NET_WEIGHT), 0) AS total_net_weight,
COALESCE(SUM(b.TOTAL_GROSS_WEIGHT), 0) AS total_gross_weight
FROM
PICKING_MATERIAL_DETAIL b
WHERE
b.OUT_UNIQUE_ID = #{outUniqueId}
AND b.DEL_FLAG = 2
</select>
<select id="sumPickingQuantityByOutOrderNumber" resultType="java.math.BigDecimal">
SELECT COALESCE(SUM(pmd.PICKING_QUANTITY), 0)
FROM picking_material_detail pmd
@@ -46,13 +46,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<sql id="selectPickingOrderPo">
a.picking_order_id, a.organization_id, a.organization_name, a.top_organization_id, a.order_number, a.picking_order_number,
a.status, a.type, a.warehouse_id, a.warehouse_code, a.warehouse_name, a.shipper_id, a.shipper_name, a.quantity, a.weight_limit, a.volume_limit, a.material_quantity,
IFNULL((SELECT SUM(b.picking_quantity) FROM picking_material_detail b WHERE b.picking_order_number = a.picking_order_number AND b.del_flag = 1), 0) AS picking_quantity,
<!-- 拣货数量/货物类型按行自身生命周期聚合(del_flag=a.del_flag): 活动行统计活动明细, 已反审归档行(del_flag=2)统计归档明细;
固定 del_flag=1 会导致【已反审】tab 及归档详情(queryOutOrderByIdReverse 复用本片段)拣货数量恒为0 -->
IFNULL((SELECT SUM(b.picking_quantity) FROM picking_material_detail b WHERE b.picking_order_number = a.picking_order_number AND b.del_flag = a.del_flag), 0) AS picking_quantity,
a.picking_material_quantity, a.abnormal, a.task_distribution, a.remark, a.create_time, a.create_by, a.create_by_name, a.update_time,b.out_order_id,
a.update_by, a.update_by_name, a.del_flag, a.reverse_flag, a.out_order_number, a.business_order_no, a.order_type_code, a.order_type_name, a.priority_level_code, a.priority_level_name
,a.push_status,a.push_time,a.push_by,a.push_by_name,
(SELECT m.goods_type FROM PICKING_MATERIAL_DETAIL d
JOIN material_base_info m ON d.material_base_info_id = m.material_base_info_id
WHERE d.PICKING_ORDER_NUMBER = a.PICKING_ORDER_NUMBER and d.del_flag = 1
WHERE d.PICKING_ORDER_NUMBER = a.PICKING_ORDER_NUMBER and d.del_flag = a.del_flag
LIMIT 1) AS goods_type
</sql>
@@ -204,7 +204,8 @@
sum(ifnull(d.material_plan_qty, ifnull(d.pending_review_qty, 0) + ifnull(d.reviewed_qty, 0))) as total_plan_qty,
sum(ifnull(d.reviewed_qty, 0)) as total_reviewed_qty
from review_material_detail d
where d.del_flag = 1
<!-- 不滤 del_flag: 复核明细仅随整单反审统一软删(全库唯一软删路径), 不滤才能让【已反审】tab 行汇总到归档明细;
活动单明细恒为 del_flag=1, 汇总口径不变 -->
group by d.review_order_id
) agg on a.review_order_id = agg.review_order_id
<where>
@@ -173,9 +173,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<choose>
<when test="reverseAuditStatus != null">
and a.DEL_FLAG = 2 and b.DEL_FLAG = 2
<!-- 历史重复下发会产生同号多张入库单,限定只关联同状态最新一张,防止列表出现重复行 -->
and b.IN_ORDER_ID = (select max(c.IN_ORDER_ID) from stock_in_order c
where c.IN_ORDER_NUMBER = a.in_order_number and c.DEL_FLAG = 2)
</when>
<otherwise>
and a.DEL_FLAG = 1 and b.DEL_FLAG = 1
and b.IN_ORDER_ID = (select max(c.IN_ORDER_ID) from stock_in_order c
where c.IN_ORDER_NUMBER = a.in_order_number and c.DEL_FLAG = 1)
</otherwise>
</choose>
<where>
@@ -164,9 +164,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<choose>
<when test="reverseAuditStatus != null">
and a.DEL_FLAG = 2 and b.DEL_FLAG = 2
<!-- 历史重复下发会产生同号多张入库单,限定只关联同状态最新一张,防止列表出现重复行 -->
and b.IN_ORDER_ID = (select max(c.IN_ORDER_ID) from stock_in_order c
where c.IN_ORDER_NUMBER = a.in_order_number and c.DEL_FLAG = 2)
</when>
<otherwise>
and a.DEL_FLAG = 1 and b.DEL_FLAG = 1
and b.IN_ORDER_ID = (select max(c.IN_ORDER_ID) from stock_in_order c
where c.IN_ORDER_NUMBER = a.in_order_number and c.DEL_FLAG = 1)
</otherwise>
</choose>
<where>