package com.vincent.rsf.server.manager.service.impl;
|
|
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSONObject;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.vincent.rsf.framework.common.R;
|
import com.vincent.rsf.framework.exception.CoolException;
|
import com.vincent.rsf.server.api.controller.erp.params.TaskInParam;
|
import com.vincent.rsf.server.api.entity.dto.InTaskMsgDto;
|
import com.vincent.rsf.server.api.service.WcsService;
|
import com.vincent.rsf.server.manager.controller.params.PakinItem;
|
import com.vincent.rsf.server.manager.controller.params.RcsTestParams;
|
import com.vincent.rsf.server.manager.controller.params.WaitPakinParam;
|
import com.vincent.rsf.server.manager.controller.params.LocToTaskParams;
|
import com.vincent.rsf.server.manager.entity.*;
|
import com.vincent.rsf.server.manager.enums.*;
|
import com.vincent.rsf.server.manager.mapper.RcsTestConfigMapper;
|
import com.vincent.rsf.server.manager.service.*;
|
import com.vincent.rsf.server.common.constant.Constants;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.commons.lang3.StringUtils;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.util.*;
|
import java.util.Random;
|
|
@Slf4j
|
@Service
|
public class RcsTestServiceImpl extends ServiceImpl<RcsTestConfigMapper, RcsTestConfig> implements RcsTestService {
|
|
@Autowired
|
private MatnrService matnrService;
|
|
@Autowired
|
private WaitPakinService waitPakinService;
|
|
@Autowired
|
private WcsService wcsService;
|
|
@Autowired
|
private TaskService taskService;
|
|
@Autowired
|
private LocService locService;
|
|
@Autowired
|
private LocItemService locItemService;
|
|
@Autowired
|
private DeviceSiteService deviceSiteService;
|
|
@Autowired
|
private BasStationService basStationService;
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public R executeRcsTest(RcsTestParams params, Long userId) {
|
log.info("========== 开始执行RCS全流程自动测试 ==========");
|
Map<String, Object> result = new HashMap<>();
|
List<String> steps = new ArrayList<>();
|
|
try {
|
// 1. 加载配置(如果提供了configId)
|
RcsTestConfig config = null;
|
if (params.getConfigId() != null) {
|
config = this.getById(params.getConfigId());
|
if (config == null) {
|
throw new CoolException("配置不存在,配置ID:" + params.getConfigId());
|
}
|
// 使用配置中的参数覆盖请求参数
|
if (StringUtils.isBlank(params.getInboundStation()) && StringUtils.isNotBlank(config.getInboundStation())) {
|
params.setInboundStation(config.getInboundStation());
|
}
|
if (StringUtils.isBlank(params.getOutboundStation()) && StringUtils.isNotBlank(config.getOutboundStation())) {
|
params.setOutboundStation(config.getOutboundStation());
|
}
|
// 加载入库库位号数组
|
if ((params.getInboundLocNos() == null || params.getInboundLocNos().isEmpty())
|
&& StringUtils.isNotBlank(config.getInboundLocNos())) {
|
try {
|
params.setInboundLocNos(JSON.parseArray(config.getInboundLocNos(), String.class));
|
} catch (Exception e) {
|
log.warn("解析入库库位号数组失败: {}", e.getMessage());
|
}
|
}
|
// 兼容旧数据:如果inboundLocNos为空但locNo有值,使用locNo
|
if ((params.getInboundLocNos() == null || params.getInboundLocNos().isEmpty())
|
&& StringUtils.isNotBlank(config.getLocNo())) {
|
params.setInboundLocNos(Collections.singletonList(config.getLocNo()));
|
}
|
// 加载出库库位号数组
|
if ((params.getOutboundLocNos() == null || params.getOutboundLocNos().isEmpty())
|
&& StringUtils.isNotBlank(config.getOutboundLocNos())) {
|
try {
|
params.setOutboundLocNos(JSON.parseArray(config.getOutboundLocNos(), String.class));
|
} catch (Exception e) {
|
log.warn("解析出库库位号数组失败: {}", e.getMessage());
|
}
|
}
|
if (params.getCheckStock() == null && config.getCheckStock() != null) {
|
params.setCheckStock(config.getCheckStock() == 1);
|
}
|
if (StringUtils.isBlank(params.getInboundApiType()) && StringUtils.isNotBlank(config.getInboundApiType())) {
|
params.setInboundApiType(config.getInboundApiType());
|
}
|
if (params.getRandomMaterialCount() == null && config.getRandomMaterialCount() != null) {
|
params.setRandomMaterialCount(config.getRandomMaterialCount());
|
}
|
if (params.getAutoOutbound() == null && config.getAutoOutbound() != null) {
|
params.setAutoOutbound(config.getAutoOutbound() == 1);
|
}
|
if (params.getMatnrCodes() == null || params.getMatnrCodes().isEmpty()) {
|
if (StringUtils.isNotBlank(config.getMatnrCodes())) {
|
params.setMatnrCodes(JSON.parseArray(config.getMatnrCodes(), String.class));
|
}
|
}
|
}
|
|
// 2. 验证参数
|
// 出库测试不需要物料编号,只有入库测试或全流程测试才需要物料编号
|
// 如果 autoOutbound 为 true 且没有物料编号,说明是纯出库测试,跳过物料验证
|
boolean isPureOutboundTest = Boolean.TRUE.equals(params.getAutoOutbound())
|
&& (params.getMatnrCodes() == null || params.getMatnrCodes().isEmpty());
|
|
if (!isPureOutboundTest && (params.getMatnrCodes() == null || params.getMatnrCodes().isEmpty())) {
|
throw new CoolException("物料编号组不能为空!");
|
}
|
|
// 3. 处理入库流程(仅当非纯出库测试时)
|
String barcode = null;
|
String locNo = null;
|
String taskCode = null;
|
String requestedLocNo = null;
|
WaitPakin waitPakin = null;
|
|
if (!isPureOutboundTest) {
|
// 3.1 随机选择物料
|
List<String> selectedMatnrCodes = selectRandomMaterials(params.getMatnrCodes(), params.getRandomMaterialCount());
|
steps.add("随机选择物料:" + selectedMatnrCodes);
|
log.info("随机选择的物料:{}", selectedMatnrCodes);
|
|
// 3.2 查询物料信息
|
List<Matnr> matnrs = matnrService.list(new LambdaQueryWrapper<Matnr>()
|
.in(Matnr::getCode, selectedMatnrCodes));
|
if (matnrs.isEmpty()) {
|
throw new CoolException("未找到物料信息!");
|
}
|
steps.add("查询到物料数量:" + matnrs.size());
|
|
// 3.3 生成随机托盘码
|
barcode = generateBarcode();
|
steps.add("生成托盘码:" + barcode);
|
log.info("生成的托盘码:{}", barcode);
|
|
// 3.4 自动组托
|
WaitPakinParam waitPakinParam = new WaitPakinParam();
|
waitPakinParam.setBarcode(barcode);
|
List<PakinItem> pakinItems = new ArrayList<>();
|
for (Matnr matnr : matnrs) {
|
PakinItem item = new PakinItem();
|
item.setMatnrId(matnr.getId());
|
item.setReceiptQty(10.0 + Math.random() * 90); // 随机数量10-100
|
pakinItems.add(item);
|
}
|
waitPakinParam.setItems(pakinItems);
|
|
waitPakin = waitPakinService.mergeItems(waitPakinParam, userId);
|
if (waitPakin == null) {
|
throw new CoolException("组托失败,返回结果为空!");
|
}
|
steps.add("组托成功,组托编码:" + waitPakin.getCode());
|
log.info("组托成功,组托编码:{},托盘码:{}", waitPakin.getCode(), barcode);
|
|
// 3.5 自动入库
|
String inboundStation = params.getInboundStation();
|
if (StringUtils.isBlank(inboundStation)) {
|
// 获取默认入库站点
|
List<DeviceSite> deviceSites = deviceSiteService.list(new LambdaQueryWrapper<DeviceSite>()
|
.eq(DeviceSite::getType, TaskType.TASK_TYPE_IN.type)
|
.last("limit 1"));
|
if (deviceSites.isEmpty()) {
|
throw new CoolException("未找到入库站点!");
|
}
|
inboundStation = deviceSites.get(0).getSite();
|
}
|
|
// 处理入库库位号:如果指定了多个,随机选择一个
|
List<String> inboundLocNos = params.getInboundLocNos();
|
if (inboundLocNos != null && !inboundLocNos.isEmpty()) {
|
// 验证库位是否存在
|
List<String> validLocNos = new ArrayList<>();
|
for (String locCode : inboundLocNos) {
|
Loc loc = locService.getOne(new LambdaQueryWrapper<Loc>()
|
.eq(Loc::getCode, locCode)
|
.eq(Loc::getDeleted, 0));
|
if (loc != null) {
|
validLocNos.add(locCode);
|
} else {
|
steps.add("警告:指定的入库库位号不存在:" + locCode);
|
}
|
}
|
|
if (!validLocNos.isEmpty()) {
|
// 随机选择一个库位
|
Collections.shuffle(validLocNos);
|
requestedLocNo = validLocNos.get(0);
|
if (validLocNos.size() > 1) {
|
steps.add("从" + validLocNos.size() + "个入库库位中随机选择:" + requestedLocNo);
|
} else {
|
steps.add("使用指定的入库库位号:" + requestedLocNo);
|
}
|
}
|
}
|
|
// 兼容旧参数:如果inboundLocNos为空但locNo有值
|
if (requestedLocNo == null && StringUtils.isNotBlank(params.getLocNo())) {
|
requestedLocNo = params.getLocNo();
|
}
|
|
if ("location_allocate".equals(params.getInboundApiType())) {
|
// 使用 location_allocate 接口(内部调用createInTask)
|
R allocateResult = wcsService.allocateLocation(barcode, inboundStation, 1);
|
if (allocateResult != null) {
|
Object dataObj = allocateResult.get("data");
|
if (dataObj != null) {
|
if (dataObj instanceof JSONObject) {
|
JSONObject data = (JSONObject) dataObj;
|
locNo = data.getString("locNo");
|
} else if (dataObj instanceof Map) {
|
@SuppressWarnings("unchecked")
|
Map<String, Object> data = (Map<String, Object>) dataObj;
|
locNo = (String) data.get("locNo");
|
}
|
}
|
}
|
if (StringUtils.isNotBlank(requestedLocNo) && !requestedLocNo.equals(locNo)) {
|
steps.add("使用location_allocate接口申请入库,系统分配库位号:" + locNo + "(用户指定:" + requestedLocNo + ")");
|
} else {
|
steps.add("使用location_allocate接口申请入库,库位号:" + (locNo != null ? locNo : "未分配"));
|
}
|
} else {
|
// 使用 create_in_task 接口
|
TaskInParam taskInParam = new TaskInParam();
|
taskInParam.setBarcode(barcode);
|
taskInParam.setSourceStaNo(inboundStation);
|
taskInParam.setIoType(TaskType.TASK_TYPE_IN.type);
|
taskInParam.setLocType1(1);
|
taskInParam.setUser(userId);
|
|
InTaskMsgDto inTaskMsgDto = wcsService.createInTask(taskInParam);
|
locNo = inTaskMsgDto.getLocNo();
|
taskCode = inTaskMsgDto.getWorkNo();
|
if (StringUtils.isNotBlank(requestedLocNo) && locNo != null && !requestedLocNo.equals(locNo)) {
|
steps.add("使用create_in_task接口创建入库任务,系统分配库位号:" + locNo + "(用户指定:" + requestedLocNo + "),任务编码:" + taskCode);
|
} else {
|
steps.add("使用create_in_task接口创建入库任务,库位号:" + (locNo != null ? locNo : "未分配") + ",任务编码:" + taskCode);
|
}
|
}
|
} else {
|
steps.add("纯出库测试,跳过物料选择、组托和入库流程");
|
}
|
|
result.put("barcode", barcode);
|
result.put("locNo", locNo);
|
result.put("requestedLocNo", requestedLocNo); // 记录用户指定的库位号
|
if (waitPakin != null) {
|
result.put("waitPakinCode", waitPakin.getCode());
|
}
|
if (taskCode != null) {
|
result.put("inboundTaskCode", taskCode);
|
}
|
|
// 8. 自动出库(如果配置了)
|
if (params.getAutoOutbound() != null && params.getAutoOutbound()) {
|
String outboundStation = params.getOutboundStation();
|
if (StringUtils.isBlank(outboundStation)) {
|
// 获取默认出库站点
|
List<DeviceSite> deviceSites = deviceSiteService.list(new LambdaQueryWrapper<DeviceSite>()
|
.eq(DeviceSite::getType, TaskType.TASK_TYPE_OUT.type)
|
.last("limit 1"));
|
if (!deviceSites.isEmpty()) {
|
outboundStation = deviceSites.get(0).getSite();
|
}
|
}
|
|
if (StringUtils.isNotBlank(outboundStation)) {
|
List<String> outboundLocNos = params.getOutboundLocNos();
|
List<Loc> selectedOutboundLocs = new ArrayList<>();
|
|
if (outboundLocNos != null && !outboundLocNos.isEmpty()) {
|
// 使用用户指定的出库库位号
|
for (String locCode : outboundLocNos) {
|
Loc loc = locService.getOne(new LambdaQueryWrapper<Loc>()
|
.eq(Loc::getCode, locCode)
|
.eq(Loc::getDeleted, 0));
|
if (loc != null) {
|
// 如果检查库存,验证是否有库存
|
if (params.getCheckStock() != null && params.getCheckStock()) {
|
List<LocItem> locItems = locItemService.list(new LambdaQueryWrapper<LocItem>()
|
.eq(LocItem::getLocId, loc.getId()));
|
if (!locItems.isEmpty()) {
|
selectedOutboundLocs.add(loc);
|
}
|
} else {
|
// 不检查库存,直接添加
|
selectedOutboundLocs.add(loc);
|
}
|
}
|
}
|
|
if (selectedOutboundLocs.isEmpty()) {
|
steps.add("指定的出库库位号均无库存或不存在,跳过自动出库");
|
log.warn("指定的出库库位号均无库存或不存在,跳过自动出库");
|
}
|
} else {
|
// 没有指定出库库位号,查找有库存的库位
|
if (params.getCheckStock() != null && params.getCheckStock()) {
|
// 检查库存
|
List<Loc> locsWithStock = locService.list(new LambdaQueryWrapper<Loc>()
|
.eq(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_F.type)
|
.last("limit 10"));
|
|
for (Loc loc : locsWithStock) {
|
List<LocItem> locItems = locItemService.list(new LambdaQueryWrapper<LocItem>()
|
.eq(LocItem::getLocId, loc.getId()));
|
if (!locItems.isEmpty()) {
|
selectedOutboundLocs.add(loc);
|
}
|
}
|
|
if (selectedOutboundLocs.isEmpty()) {
|
steps.add("未找到有库存的库位,跳过自动出库");
|
log.warn("未找到有库存的库位,跳过自动出库");
|
}
|
} else {
|
// 不检查库存,随机选择一个库位(后续会使用固定模拟物料000267)
|
List<Loc> locs = locService.list(new LambdaQueryWrapper<Loc>()
|
.eq(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_F.type)
|
.last("limit 10"));
|
if (!locs.isEmpty()) {
|
Collections.shuffle(locs);
|
selectedOutboundLocs.add(locs.get(0));
|
steps.add("不检查库存,随机选择库位:" + locs.get(0).getCode() + ",将使用固定模拟物料000267");
|
}
|
}
|
}
|
|
// 处理出库:随机选择单个或多个库位组合
|
if (!selectedOutboundLocs.isEmpty()) {
|
Random random = new Random();
|
// 随机决定:单库位出库 或 多库位组合出库(最多3个库位)
|
boolean useMultipleLocs = random.nextBoolean() && selectedOutboundLocs.size() > 1;
|
int locCount = useMultipleLocs
|
? Math.min(random.nextInt(selectedOutboundLocs.size()) + 1, 3)
|
: 1;
|
|
// 随机选择库位
|
Collections.shuffle(selectedOutboundLocs);
|
List<Loc> finalOutboundLocs = selectedOutboundLocs.subList(0, Math.min(locCount, selectedOutboundLocs.size()));
|
|
if (finalOutboundLocs.size() == 1) {
|
steps.add("随机选择单库位出库:" + finalOutboundLocs.get(0).getCode());
|
} else {
|
steps.add("随机选择" + finalOutboundLocs.size() + "个库位组合出库:" +
|
finalOutboundLocs.stream().map(Loc::getCode).collect(java.util.stream.Collectors.joining(", ")));
|
}
|
|
// 为每个库位生成出库任务
|
List<String> outboundTaskCodes = new ArrayList<>();
|
List<String> matnrCodes = params.getMatnrCodes();
|
boolean hasMatnrFilter = matnrCodes != null && !matnrCodes.isEmpty();
|
boolean checkStockFlag = params.getCheckStock() != null && params.getCheckStock();
|
|
for (Loc outboundLoc : finalOutboundLocs) {
|
try {
|
LocToTaskParams taskParams = new LocToTaskParams();
|
List<LocItem> locItems;
|
|
if (hasMatnrFilter && checkStockFlag) {
|
// 如果指定了物料组且检查库存,只查找包含这些物料的库存
|
List<String> matnrIds = new ArrayList<>();
|
for (String matnrCode : matnrCodes) {
|
Matnr matnr = matnrService.getOne(new LambdaQueryWrapper<Matnr>()
|
.eq(Matnr::getCode, matnrCode)
|
.eq(Matnr::getDeleted, 0)
|
.last("limit 1"));
|
if (matnr != null) {
|
matnrIds.add(String.valueOf(matnr.getId()));
|
}
|
}
|
|
if (!matnrIds.isEmpty()) {
|
// 将String列表转换为Long列表
|
List<Long> matnrIdLongs = new ArrayList<>();
|
for (String matnrIdStr : matnrIds) {
|
try {
|
matnrIdLongs.add(Long.parseLong(matnrIdStr));
|
} catch (NumberFormatException e) {
|
log.warn("物料ID格式错误: {}", matnrIdStr);
|
}
|
}
|
|
if (!matnrIdLongs.isEmpty()) {
|
locItems = locItemService.list(new LambdaQueryWrapper<LocItem>()
|
.eq(LocItem::getLocId, outboundLoc.getId())
|
.in(LocItem::getMatnrId, matnrIdLongs)
|
.last("limit 1"));
|
} else {
|
locItems = new ArrayList<>();
|
}
|
} else {
|
locItems = new ArrayList<>();
|
}
|
} else if (!checkStockFlag) {
|
// 不检查库存,使用固定模拟物料000267
|
Matnr fixedMatnr = matnrService.getOne(new LambdaQueryWrapper<Matnr>()
|
.eq(Matnr::getCode, "000267")
|
.eq(Matnr::getDeleted, 0)
|
.last("limit 1"));
|
|
if (fixedMatnr != null) {
|
// 创建一个模拟的LocItem用于生成任务
|
locItems = new ArrayList<>();
|
LocItem mockLocItem = new LocItem();
|
mockLocItem.setLocId(outboundLoc.getId());
|
mockLocItem.setMatnrId(fixedMatnr.getId());
|
mockLocItem.setQty(1.0); // 默认数量
|
locItems.add(mockLocItem);
|
steps.add("不检查库存,使用固定模拟物料:000267");
|
} else {
|
// 如果找不到000267,尝试从库位中获取任意一个库存项
|
locItems = locItemService.list(new LambdaQueryWrapper<LocItem>()
|
.eq(LocItem::getLocId, outboundLoc.getId())
|
.last("limit 1"));
|
if (locItems.isEmpty()) {
|
steps.add("警告:库位 " + outboundLoc.getCode() + " 无库存,且未找到固定模拟物料000267");
|
}
|
}
|
} else {
|
// 检查库存但没有指定物料组,查找任意库存
|
locItems = locItemService.list(new LambdaQueryWrapper<LocItem>()
|
.eq(LocItem::getLocId, outboundLoc.getId())
|
.last("limit 1"));
|
}
|
|
if (!locItems.isEmpty()) {
|
taskParams.setItems(locItems);
|
taskParams.setSiteNo(outboundStation);
|
taskParams.setType(Constants.TASK_TYPE_OUT_STOCK);
|
taskParams.setTarLoc(outboundLoc.getCode());
|
|
locItemService.generateTask(TaskResouceType.TASK_RESOUCE_ORDER_TYPE.val, taskParams, userId);
|
steps.add("生成出库任务成功,源库位:" + outboundLoc.getCode() + ",目标站点:" + outboundStation);
|
log.info("生成出库任务成功,源库位:{},目标站点:{}", outboundLoc.getCode(), outboundStation);
|
|
// 查询刚生成的任务并下发到RCS
|
List<Task> outTasks = taskService.list(new LambdaQueryWrapper<Task>()
|
.eq(Task::getOrgLoc, outboundLoc.getCode())
|
.eq(Task::getTargSite, outboundStation)
|
.eq(Task::getTaskStatus, TaskStsType.GENERATE_OUT.id)
|
.orderByDesc(Task::getCreateTime)
|
.last("limit 1"));
|
|
if (!outTasks.isEmpty()) {
|
taskService.pubTaskToWcs(outTasks);
|
outboundTaskCodes.add(outTasks.get(0).getTaskCode());
|
steps.add("出库任务已下发到RCS,任务编码:" + outTasks.get(0).getTaskCode());
|
}
|
} else {
|
steps.add("库位 " + outboundLoc.getCode() + " 无符合条件的库存,跳过生成出库任务");
|
}
|
} catch (Exception e) {
|
log.error("生成出库任务失败,库位:{}", outboundLoc.getCode(), e);
|
steps.add("生成出库任务失败,库位:" + outboundLoc.getCode() + ",错误:" + e.getMessage());
|
}
|
}
|
|
if (!outboundTaskCodes.isEmpty()) {
|
result.put("outboundTaskCodes", outboundTaskCodes);
|
result.put("outboundTaskCode", outboundTaskCodes.get(0)); // 兼容旧字段
|
}
|
}
|
}
|
}
|
|
result.put("steps", steps);
|
result.put("success", true);
|
log.info("========== RCS全流程自动测试完成 ==========");
|
return R.ok(result);
|
|
} catch (Exception e) {
|
log.error("========== RCS全流程自动测试失败 ==========", e);
|
steps.add("测试失败:" + e.getMessage());
|
result.put("steps", steps);
|
result.put("success", false);
|
result.put("error", e.getMessage());
|
return R.error("测试失败:" + e.getMessage()).add(result);
|
}
|
}
|
|
/**
|
* 随机选择物料
|
*/
|
private List<String> selectRandomMaterials(List<String> matnrCodes, Integer count) {
|
if (matnrCodes == null || matnrCodes.isEmpty()) {
|
return new ArrayList<>();
|
}
|
|
List<String> shuffled = new ArrayList<>(matnrCodes);
|
Collections.shuffle(shuffled);
|
|
int selectCount = Math.min(count != null ? count : 1, shuffled.size());
|
return shuffled.subList(0, selectCount);
|
}
|
|
/**
|
* 生成随机托盘码
|
*/
|
private String generateBarcode() {
|
return "TEST_" + System.currentTimeMillis() + "_" + (int)(Math.random() * 10000);
|
}
|
}
|