feat(srm): 新增序列号生成服务并完善IQC库存检测回调功能

- 新增BasdeSerialNumberControllerService提供带重试机制的序列号生成功能
- 在QmsController中实现IQC库存检测回调接口处理不合格品转移逻辑
- 集成Redisson分布式锁确保库存操作的并发安全
- 更新WmsApiService支持泛型返回类型提高接口调用灵活性
- 完善WMS库存检验任务回调服务增强异常处理和结果更新机制
This commit is contained in:
曹鹏飞 2026-06-25 19:14:46 +08:00
parent 828d33a724
commit 07720b4acf
4 changed files with 170 additions and 38 deletions

View File

@ -41,7 +41,7 @@ public class WmsApiService {
* @param <T> 请求体类型
* @return 响应结果
*/
public <T> ApiResult<Object> post(String path, T body, String bizDesc) {
public <T,R> ApiResult<R> post(String path, T body, String bizDesc) {
String fullUrl = host + path;
HttpHeaders headers = new HttpHeaders();
@ -50,7 +50,7 @@ public class WmsApiService {
log.info("{}URL: {},数据: {}", bizDesc, fullUrl, JSONUtil.toJsonStr(body));
ResponseEntity<ApiResult<Object>> response = restTemplate.exchange(
ResponseEntity<ApiResult<R>> response = restTemplate.exchange(
fullUrl,
HttpMethod.POST,
requestEntity,

View File

@ -2,6 +2,7 @@ package com.nflg.qms.admin.service;
import com.nflg.wms.common.constant.STATE;
import com.nflg.wms.common.exception.NflgException;
import com.nflg.wms.common.pojo.ApiResult;
import com.nflg.wms.common.pojo.dto.MaterialQrCodeDTO;
import com.nflg.wms.common.pojo.qo.WmsIncomingInspectionTaskCallbackQO;
import com.nflg.wms.common.pojo.qo.WmsInventoryInspectionTaskCallbackQO;
@ -17,6 +18,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import software.amazon.awssdk.core.document.VoidDocumentVisitor;
import java.util.List;
import java.util.Objects;
@ -142,45 +144,39 @@ public class WmsIncomingInspectionTaskCallbackService {
* 向WMS系统发送库存检验任务回调
*/
private void inventory(QmsIncomingInspectionTaskVO taskVO, Short processingResult) {
// WmsInventoryInspectionTaskCallbackQO qo = new WmsInventoryInspectionTaskCallbackQO()
// .setTaskNo(taskVO.getTaskNo())
// .setRequestNo(taskVO.getRequestNo())
// .setMaterialNo(taskVO.getMaterialNo())
// .setFactory(taskVO.getFactory())
// .setWarehouse(taskVO.getWarehouse())
// .setStorageLocation(taskVO.getStorageLocation())
// .setStorageDays(taskVO.getStorageDays())
// .setDetectionQty(taskVO.getDetectionQty())
// .setQualifiedQty(taskVO.getQualifiedQty())
// .setUnqualifiedQty(taskVO.getUnqualifiedQty())
// .setInspectionResult(taskVO.getInspectionResult())
// .setProcessingResult(processingResult);
//
// List<QmsIncomingInspectionTaskRecord> records = incomingInspectionTaskRecordService.lambdaQuery()
// .select(QmsIncomingInspectionTaskRecord::getMaterialUniqueNo, QmsIncomingInspectionTaskRecord::getQualified)
// .eq(QmsIncomingInspectionTaskRecord::getTaskId, taskVO.getId())
// .list();
// qo.setQualifiedMaterialUniqueNo(records.stream()
// .filter(QmsIncomingInspectionTaskRecord::getQualified)
// .map(QmsIncomingInspectionTaskRecord::getMaterialUniqueNo)
// .toList()
// );
// qo.setUnqualifiedMaterialUniqueNo(records.stream()
// .filter(record -> !record.getQualified())
// .map(QmsIncomingInspectionTaskRecord::getMaterialUniqueNo)
// .toList()
// );
WmsInventoryInspectionTaskCallbackQO qo = new WmsInventoryInspectionTaskCallbackQO()
.setTaskNo(taskVO.getTaskNo())
.setRequestNo(taskVO.getRequestNo())
.setMaterialNo(taskVO.getMaterialNo())
.setFactory(taskVO.getFactory())
.setWarehouse(taskVO.getWarehouse())
.setStorageLocation(taskVO.getStorageLocation())
.setStorageDays(taskVO.getStorageDays())
.setDetectionQty(taskVO.getDetectionQty())
.setQualifiedQty(taskVO.getQualifiedQty())
.setUnqualifiedQty(taskVO.getUnqualifiedQty())
.setInspectionResult(taskVO.getInspectionResult())
.setProcessingResult(processingResult);
List<QmsIncomingInspectionTaskRecord> records = incomingInspectionTaskRecordService.lambdaQuery()
.select(QmsIncomingInspectionTaskRecord::getMaterialUniqueNo, QmsIncomingInspectionTaskRecord::getQualified)
.eq(QmsIncomingInspectionTaskRecord::getTaskId, taskVO.getId())
.list();
qo.setQualifiedMaterialUniqueNo(records.stream()
.filter(QmsIncomingInspectionTaskRecord::getQualified)
.map(QmsIncomingInspectionTaskRecord::getMaterialUniqueNo)
.toList()
);
qo.setUnqualifiedMaterialUniqueNo(records.stream()
.filter(record -> !record.getQualified())
.map(QmsIncomingInspectionTaskRecord::getMaterialUniqueNo)
.toList()
);
// 注意本方法不可被 @Transactional 包裹否则下面"标记失败"的更新会随异常一起回滚导致失败状态丢失
try {
// wmsApiService.post(inventoryUrl, qo, "库存检验任务回调WMS");
inventoryService.lambdaUpdate()
.set(WmsInventory::getDetectionStatus, (short) 2)
.set(WmsInventory::getDetectionResults, taskVO.getInspectionResult())
.eq(WmsInventory::getId, Long.valueOf(taskVO.getRequestNo()))
.eq(WmsInventory::getDetectionStatus, (short) 1)
.update();
updateCallbackResult(taskVO.getId(), true);
ApiResult<Void> result = wmsApiService.post(inventoryUrl, qo, "库存检验任务回调WMS");
updateCallbackResult(taskVO.getId(), result.getCode() == 200);
} catch (Exception e) {
updateCallbackResult(taskVO.getId(), false);
throw new NflgException(STATE.BusinessError, "调用WMS接口失败" + e.getMessage());

View File

@ -1,25 +1,35 @@
package com.nflg.wms.srm.receive.controller;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.nflg.wms.common.constant.STATE;
import com.nflg.wms.common.exception.NflgException;
import com.nflg.wms.common.pojo.ApiResult;
import com.nflg.wms.common.pojo.dto.InventoryInDTO;
import com.nflg.wms.common.pojo.dto.MaterialQrCodeDTO;
import com.nflg.wms.common.pojo.dto.TransferOrderDTO;
import com.nflg.wms.common.pojo.qo.WmsIncomingInspectionTaskCallbackQO;
import com.nflg.wms.common.pojo.qo.WmsInventoryInspectionTaskCallbackQO;
import com.nflg.wms.common.pojo.vo.InventoryLockVO;
import com.nflg.wms.common.util.UserUtil;
import com.nflg.wms.common.util.VUtil;
import com.nflg.wms.repository.entity.*;
import com.nflg.wms.repository.service.*;
import com.nflg.wms.srm.receive.pojo.dto.ZWM3A18DTO;
import com.nflg.wms.srm.receive.pojo.dto.ZWM3A18Item1DTO;
import com.nflg.wms.common.pojo.qo.PdiInspectionTaskCallbackQO;
import com.nflg.wms.srm.receive.service.BasdeSerialNumberControllerService;
import com.nflg.wms.srm.receive.service.SapService;
import com.nflg.wms.srm.receive.util.NoUtil;
import com.nflg.wms.starter.BaseController;
import jakarta.annotation.Resource;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@ -31,6 +41,7 @@ import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* 接收QMS推送数据
@ -64,6 +75,18 @@ public class QmsController extends BaseController {
@Resource
private IWmsInProduceOrderItemService produceOrderItemService;
@Resource
private BasdeSerialNumberControllerService serialNumberControllerService;
@Resource
private IWmsTransferFactoryService transferFactoryService;
@Resource
private IWmsTransferFactoryItemService transferFactoryItemService;
@Resource
private RedissonClient redissonClient;
/**
* IQC来料检测回调
*/
@ -231,6 +254,61 @@ public class QmsController extends BaseController {
return ApiResult.success();
}
/**
* IQC库存检测回调
*/
@Transactional
@PostMapping("/iqc/inventory")
public ApiResult<Void> iqcIncoming(@RequestBody @NotNull WmsInventoryInspectionTaskCallbackQO qo) {
log.info("IQC检测回调, 请求单号: {}, 物料: {}, 结果: {}", qo.getRequestNo(), qo.getMaterialNo(), qo.getInspectionResult());
inventoryService.lambdaUpdate()
.set(WmsInventory::getDetectionStatus, (short) 2)
.set(WmsInventory::getDetectionResults, qo.getInspectionResult())
.eq(WmsInventory::getId, Long.valueOf(qo.getRequestNo()))
.eq(WmsInventory::getDetectionStatus, (short) 1)
.update();
if (!qo.getInspectionResult()) {
TransferOrderDTO dto = new TransferOrderDTO()
.setMatnr(qo.getMaterialNo())
.setWerks(qo.getFactory())
.setLgort(qo.getWarehouse())
.setLgpbe(qo.getStorageLocation())
.setUmlgo("9011")
.setNum(BigDecimal.valueOf(qo.getUnqualifiedQty()));
WmsTransferFactory order = Convert.convert(WmsTransferFactory.class, dto);
order.setNo(serialNumberControllerService.generateSerialNumber(18));
order.setCreateBy(UserUtil.getUserName());
order.setCreateTime(LocalDateTime.now());
transferFactoryService.save(order);
RLock lock = redissonClient.getLock(StrUtil.format("lock:inventory:{}:{}:{}", dto.getWerks(), dto.getLgort(), dto.getMatnr()));
try {
// 等待5秒获取锁10秒后自动释放
if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {
if (inventoryService.getNumOne(dto.getWerks(), dto.getLgort(), dto.getMatnr()).compareTo(dto.getNum()) < 0) {
List<InventoryLockVO> itemLocks = inventoryService.getLockList(dto.getWerks(), dto.getLgort(), dto.getMatnr());
VUtil.trueThrowBusinessError(CollectionUtil.isEmpty(itemLocks)).throwMessage("库存不足:" + dto.getMatnr());
} else {
WmsTransferFactoryItem data = Convert.convert(WmsTransferFactoryItem.class, dto);
data.setId(IdUtil.getSnowflakeNextId());
data.setOrderId(order.getId());
data.setLeft(data.getNum());
transferFactoryItemService.save(data);
}
} else {
throw new NflgException(STATE.BusinessError, "获取锁失败");
}
} catch (Exception e) {
log.error("保存出错", e);
throw new NflgException(STATE.BusinessError, e.getMessage());
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
return ApiResult.success();
}
/**
* PDI检测回调
*/

View File

@ -0,0 +1,58 @@
package com.nflg.wms.srm.receive.service;
import cn.hutool.core.util.StrUtil;
import com.nflg.wms.repository.entity.BasdeSerialNumber;
import com.nflg.wms.repository.service.IBasdeSerialNumberService;
import jakarta.annotation.Resource;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
@Component
public class BasdeSerialNumberControllerService {
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
private static final int MAX_RETRY = 5; // 最大重试次数
@Resource
private IBasdeSerialNumberService basdeSerialNumberService;
@Retryable(
maxAttempts = 5, // 最大重试次数包括第一次调用
backoff = @Backoff(delay = 1000) // 重试间隔1秒
)
public String generateSerialNumber(Integer businessType, Integer suffixLen) {
String currentDate = LocalDate.now().format(DATE_FORMATTER);
BasdeSerialNumber serialNumber = basdeSerialNumberService.lambdaQuery()
.eq(BasdeSerialNumber::getBusinessType, businessType)
.one();
if (Objects.isNull(serialNumber)) {
return null;
}
int nextSerial = 0;
if (StrUtil.equals(currentDate, serialNumber.getCurrentDateStr())) {
nextSerial = serialNumber.getMaxSerial();
}
nextSerial = nextSerial + 1;
serialNumber.setMaxSerial(nextSerial);
serialNumber.setCurrentDateStr(currentDate);
basdeSerialNumberService.updateById(serialNumber);
// 格式化为4位数字不足补零
return serialNumber.getBusinessPrefixNumber() + currentDate
+ StrUtil.padPre(String.valueOf(nextSerial), suffixLen, '0');
}
@Retryable(
maxAttempts = 5, // 最大重试次数包括第一次调用
backoff = @Backoff(delay = 1000) // 重试间隔1秒
)
public String generateSerialNumber(Integer businessType) {
return generateSerialNumber(businessType, 4);
}
}