chore: 保存盘点开发前改动
This commit is contained in:
@@ -26,6 +26,8 @@ import com.ruoyi.common.utils.ExceptionUtil;
|
||||
import com.ruoyi.common.utils.ServletUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.ip.IpUtils;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.constant.HttpStatus;
|
||||
|
||||
/**
|
||||
* 外部接口入站调用日志切面。
|
||||
@@ -71,7 +73,8 @@ public class ApiLogAspect
|
||||
logEntity.setSysCode(apiLog.sysCode());
|
||||
logEntity.setInterfaceName(apiLog.interfaceName());
|
||||
logEntity.setMsgType("JSON");
|
||||
logEntity.setStatus(e == null ? "SUCCESS" : "FAIL");
|
||||
boolean businessFailed = e == null && isBusinessFailed(jsonResult);
|
||||
logEntity.setStatus(e == null && !businessFailed ? "SUCCESS" : "FAIL");
|
||||
logEntity.setHttpStatus(e == null ? 200 : 500);
|
||||
logEntity.setCostMs(costMs());
|
||||
|
||||
@@ -91,6 +94,8 @@ public class ApiLogAspect
|
||||
}
|
||||
if (e != null) {
|
||||
logEntity.setErrorMsg(StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, PARAM_MAX_LENGTH));
|
||||
} else if (businessFailed) {
|
||||
logEntity.setErrorMsg(StringUtils.substring(extractBusinessErrorMessage(jsonResult), 0, PARAM_MAX_LENGTH));
|
||||
}
|
||||
apiLogService.record(logEntity);
|
||||
} catch (Exception ex) {
|
||||
@@ -172,4 +177,38 @@ public class ApiLogAspect
|
||||
{
|
||||
return StringUtils.isNotEmpty(first) ? first : second;
|
||||
}
|
||||
|
||||
private boolean isBusinessFailed(Object jsonResult)
|
||||
{
|
||||
Object code = extractResultCode(jsonResult);
|
||||
if (code == null) {
|
||||
return false;
|
||||
}
|
||||
String codeText = String.valueOf(code);
|
||||
return !String.valueOf(HttpStatus.SUCCESS).equals(codeText) && !"0".equals(codeText);
|
||||
}
|
||||
|
||||
private Object extractResultCode(Object jsonResult)
|
||||
{
|
||||
if (jsonResult instanceof AjaxResult) {
|
||||
return ((AjaxResult) jsonResult).get(AjaxResult.CODE_TAG);
|
||||
}
|
||||
if (jsonResult instanceof Map) {
|
||||
return ((Map<?, ?>) jsonResult).get(AjaxResult.CODE_TAG);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String extractBusinessErrorMessage(Object jsonResult)
|
||||
{
|
||||
if (jsonResult instanceof AjaxResult) {
|
||||
Object msg = ((AjaxResult) jsonResult).get(AjaxResult.MSG_TAG);
|
||||
return msg == null ? "" : String.valueOf(msg);
|
||||
}
|
||||
if (jsonResult instanceof Map) {
|
||||
Object msg = ((Map<?, ?>) jsonResult).get(AjaxResult.MSG_TAG);
|
||||
return msg == null ? "" : String.valueOf(msg);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ public class WcsClient extends BaseExternalClient {
|
||||
|
||||
public ApiResponse<String> sendPutawayTask(String stockinNo, String locationCode,
|
||||
String trayNo, String priority) {
|
||||
if (mockEnabled()) {
|
||||
return ApiResponse.ok("MOCK_WCS_PUTAWAY_OK");
|
||||
}
|
||||
String body = String.join("|", "PUTAWAY", String.valueOf(nextSeq()),
|
||||
stockinNo, locationCode, trayNo, priority);
|
||||
return post(url("sendPutawayTask"), body, "WCS-下发入库任务", this::parseOkNg);
|
||||
@@ -48,6 +51,9 @@ public class WcsClient extends BaseExternalClient {
|
||||
if (job == null) {
|
||||
return ApiResponse.fail("PARAM_ERROR", "入库作业不能为空");
|
||||
}
|
||||
if (mockEnabled()) {
|
||||
return ApiResponse.ok("MOCK_WCS_PUTAWAY_OK");
|
||||
}
|
||||
try {
|
||||
String body = objectMapper.writeValueAsString(job);
|
||||
return post(url("sendPutawayTask"), body, "WCS-下发入库任务", this::parseWcsAck, MsgTypeEnum.JSON);
|
||||
@@ -60,6 +66,9 @@ public class WcsClient extends BaseExternalClient {
|
||||
|
||||
public ApiResponse<String> sendRetrieveTask(String stockoutNo, String locationCode,
|
||||
String materialCode, String qty) {
|
||||
if (mockEnabled()) {
|
||||
return ApiResponse.ok("MOCK_WCS_RETRIEVE_OK");
|
||||
}
|
||||
String body = String.join("|", "RETRIEVE", String.valueOf(nextSeq()),
|
||||
stockoutNo, locationCode, materialCode, qty);
|
||||
return post(url("sendRetrieveTask"), body, "WCS-下发出库任务", this::parseOkNg);
|
||||
@@ -72,6 +81,9 @@ public class WcsClient extends BaseExternalClient {
|
||||
if (job == null) {
|
||||
return ApiResponse.fail("PARAM_ERROR", "出库作业不能为空");
|
||||
}
|
||||
if (mockEnabled()) {
|
||||
return ApiResponse.ok("MOCK_WCS_RETRIEVE_OK");
|
||||
}
|
||||
try {
|
||||
String body = objectMapper.writeValueAsString(job);
|
||||
return post(url("sendPutawayTask"), body, "WCS-下发出库任务", this::parseWcsAck, MsgTypeEnum.JSON);
|
||||
@@ -83,6 +95,14 @@ public class WcsClient extends BaseExternalClient {
|
||||
// ── 接口3:查询设备状态 ───────────────────────────────────
|
||||
|
||||
public ApiResponse<List<Map<String, String>>> queryDeviceStatus(String deviceType) {
|
||||
if (mockEnabled()) {
|
||||
Map<String, String> device = new LinkedHashMap<>();
|
||||
device.put("deviceId", deviceType == null || deviceType.isEmpty() ? "MOCK_DEVICE" : "MOCK_" + deviceType);
|
||||
device.put("status", "MOCK_ONLINE");
|
||||
device.put("running", "Y");
|
||||
device.put("error", "");
|
||||
return ApiResponse.ok(Collections.singletonList(device));
|
||||
}
|
||||
String body = "STATUS|" + nextSeq() + "|" + deviceType;
|
||||
return post(url("queryDeviceStatus"), body, "WCS-查询设备状态", rawBody -> {
|
||||
String[] lines = rawBody.trim().split("\\n");
|
||||
@@ -107,6 +127,9 @@ public class WcsClient extends BaseExternalClient {
|
||||
|
||||
public ApiResponse<String> sendCraneMove(String craneNo, String fromLocation,
|
||||
String toLocation, String taskType) {
|
||||
if (mockEnabled()) {
|
||||
return ApiResponse.ok("MOCK_WCS_CRANE_MOVE_OK");
|
||||
}
|
||||
String body = String.join("|", "MOVE", String.valueOf(nextSeq()),
|
||||
craneNo, fromLocation, toLocation, taskType);
|
||||
return post(url("sendCraneMove"), body, "WCS-堆垛机移动指令", this::parseOkNg);
|
||||
@@ -115,12 +138,24 @@ public class WcsClient extends BaseExternalClient {
|
||||
// ── 接口5:取消任务 ──────────────────────────────────────
|
||||
|
||||
public ApiResponse<String> cancelTask(String taskId) {
|
||||
if (mockEnabled()) {
|
||||
return ApiResponse.ok("MOCK_WCS_CANCEL_OK");
|
||||
}
|
||||
String body = "CANCEL|" + nextSeq() + "|" + taskId;
|
||||
return post(url("cancelTask"), body, "WCS-取消任务", this::parseOkNg);
|
||||
}
|
||||
|
||||
// ── 辅助 ─────────────────────────────────────────────────
|
||||
|
||||
private boolean mockEnabled() {
|
||||
ExternalProp prop = externalProp();
|
||||
if (prop == null) {
|
||||
return true;
|
||||
}
|
||||
ExternalProp.SystemConfig wcsConfig = prop.getSystem(sysEnum().getCode());
|
||||
return wcsConfig == null || wcsConfig.isMockEnabled();
|
||||
}
|
||||
|
||||
private String parseOkNg(String rawBody) {
|
||||
String[] parts = rawBody.trim().split("\\|", 2);
|
||||
if ("OK".equals(parts[0])) {
|
||||
@@ -141,11 +176,9 @@ public class WcsClient extends BaseExternalClient {
|
||||
Object success = result.get("success");
|
||||
Object code = result.get("code");
|
||||
Object message = result.get("message");
|
||||
boolean ok = Boolean.TRUE.equals(success)
|
||||
|| "true".equalsIgnoreCase(String.valueOf(success))
|
||||
|| "00000".equals(String.valueOf(code))
|
||||
|| "200".equals(String.valueOf(code))
|
||||
|| "OK".equalsIgnoreCase(String.valueOf(code));
|
||||
boolean ok = "0".equals(String.valueOf(code))
|
||||
|| (code == null && (Boolean.TRUE.equals(success)
|
||||
|| "true".equalsIgnoreCase(String.valueOf(success))));
|
||||
if (ok) {
|
||||
Object data = result.get("data");
|
||||
return data == null ? (message == null ? "OK" : String.valueOf(message)) : String.valueOf(data);
|
||||
|
||||
@@ -29,6 +29,8 @@ public class ExternalProp {
|
||||
private String token;
|
||||
private int connectTimeout = 5000;
|
||||
private int readTimeout = 10000;
|
||||
/** 是否启用本地模拟返回。当前仅 WCS 客户端使用,默认开启便于本地测试。 */
|
||||
private boolean mockEnabled = true;
|
||||
|
||||
/** 该系统的接口路径配置,key=接口名,value=路径 */
|
||||
private Map<String, String> apis;
|
||||
@@ -41,6 +43,8 @@ public class ExternalProp {
|
||||
public void setConnectTimeout(int connectTimeout) { this.connectTimeout = connectTimeout; }
|
||||
public int getReadTimeout() { return readTimeout; }
|
||||
public void setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; }
|
||||
public boolean isMockEnabled() { return mockEnabled; }
|
||||
public void setMockEnabled(boolean mockEnabled) { this.mockEnabled = mockEnabled; }
|
||||
public Map<String, String> getApis() { return apis; }
|
||||
public void setApis(Map<String, String> apis) { this.apis = apis; }
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.ruoyi.api.client.MesClient;
|
||||
import com.ruoyi.api.client.WcsClient;
|
||||
import com.ruoyi.api.domain.WcsInboundCompleteRequest;
|
||||
import com.ruoyi.api.domain.WcsInboundLocationRequest;
|
||||
import com.ruoyi.api.domain.WcsJobExceptionRequest;
|
||||
import com.ruoyi.api.domain.WcsOutboundCompleteRequest;
|
||||
import com.ruoyi.api.domain.ApiResponse;
|
||||
import com.ruoyi.common.annotation.ApiLog;
|
||||
@@ -91,7 +92,7 @@ public class ExternalApiController extends BaseController {
|
||||
@RequestParam String trayNo,
|
||||
@RequestParam(defaultValue = "NORMAL") String priority) {
|
||||
ApiResponse<String> resp = wcsClient.sendPutawayTask(stockinNo, locationCode, trayNo, priority);
|
||||
return resp.isSuccess() ? success(resp.getData()) : error(resp.getMessage());
|
||||
return wcsResult(resp);
|
||||
}
|
||||
|
||||
@PostMapping("/wcs/sendRetrieveTask")
|
||||
@@ -100,13 +101,13 @@ public class ExternalApiController extends BaseController {
|
||||
@RequestParam String materialCode,
|
||||
@RequestParam String qty) {
|
||||
ApiResponse<String> resp = wcsClient.sendRetrieveTask(stockoutNo, locationCode, materialCode, qty);
|
||||
return resp.isSuccess() ? success(resp.getData()) : error(resp.getMessage());
|
||||
return wcsResult(resp);
|
||||
}
|
||||
|
||||
@PostMapping("/wcs/queryDeviceStatus")
|
||||
public AjaxResult queryDeviceStatus(@RequestParam(defaultValue = "ALL") String deviceType) {
|
||||
ApiResponse<List<Map<String, String>>> resp = wcsClient.queryDeviceStatus(deviceType);
|
||||
return resp.isSuccess() ? success(resp.getData()) : error(resp.getMessage());
|
||||
return wcsResult(resp);
|
||||
}
|
||||
|
||||
@PostMapping("/wcs/sendCraneMove")
|
||||
@@ -115,13 +116,35 @@ public class ExternalApiController extends BaseController {
|
||||
@RequestParam String toLocation,
|
||||
@RequestParam(defaultValue = "NORMAL") String taskType) {
|
||||
ApiResponse<String> resp = wcsClient.sendCraneMove(craneNo, fromLocation, toLocation, taskType);
|
||||
return resp.isSuccess() ? success(resp.getData()) : error(resp.getMessage());
|
||||
return wcsResult(resp);
|
||||
}
|
||||
|
||||
@PostMapping("/wcs/cancelTask")
|
||||
public AjaxResult cancelWcsTask(@RequestParam String taskId) {
|
||||
ApiResponse<String> resp = wcsClient.cancelTask(taskId);
|
||||
return resp.isSuccess() ? success(resp.getData()) : error(resp.getMessage());
|
||||
return wcsResult(resp);
|
||||
}
|
||||
|
||||
/**
|
||||
* WCS 识别现场异常后回调 WMS。
|
||||
*/
|
||||
@ApiLog(interfaceName = "WCS-作业异常处理", sysCode = "WCS", direction = "INBOUND")
|
||||
@PostMapping("/wcs/exception")
|
||||
public AjaxResult handleWcsException(@RequestBody WcsJobExceptionRequest request) {
|
||||
String exceptionType = normalizeExceptionType(request);
|
||||
if (exceptionType == null) {
|
||||
return wcsError("异常类型不能为空,支持PICK_EMPTY/取货无货、PUT_OCCUPIED/放货有货");
|
||||
}
|
||||
|
||||
MydoJob job = resolveExceptionJob(request, exceptionType);
|
||||
if (job == null) {
|
||||
return wcsError("未找到对应作业");
|
||||
}
|
||||
|
||||
if ("PICK_EMPTY".equals(exceptionType)) {
|
||||
return wcsResult(mydoJobService.handleWcsPickEmptyException(job.getJobId()));
|
||||
}
|
||||
return wcsResult(mydoJobService.handleWcsPutOccupiedException(job.getJobId()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,14 +172,14 @@ public class ExternalApiController extends BaseController {
|
||||
job = mydoJobService.selectLatestInMydoJobByTrayCode(request.getTrayCode().trim());
|
||||
}
|
||||
if (job == null) {
|
||||
return error("未找到对应入库作业");
|
||||
return wcsError("未找到对应入库作业");
|
||||
}
|
||||
if (job.getCargoSpaceId() == null || job.getCargoSpaceId().trim().isEmpty()) {
|
||||
Map<String, Object> cargoSpace;
|
||||
try {
|
||||
cargoSpace = pdaService.lockInboundCargoSpace(job.getTrayCode(), job.getWarehouseId(), job.getRoadwayId());
|
||||
} catch (RuntimeException e) {
|
||||
return error(e.getMessage());
|
||||
return wcsError(e.getMessage());
|
||||
}
|
||||
job.setRoadwayId(String.valueOf(cargoSpace.get("roadway_id")));
|
||||
job.setCargoSpaceId(String.valueOf(cargoSpace.get("cargo_space_id")));
|
||||
@@ -164,11 +187,11 @@ public class ExternalApiController extends BaseController {
|
||||
int updateRows = mydoJobService.updateMydoJob(job);
|
||||
if (updateRows <= 0) {
|
||||
pdaService.releaseInboundCargoSpace(job.getTrayCode(), job.getWarehouseId());
|
||||
return error("入库货位回写作业失败");
|
||||
return wcsError("入库货位回写作业失败");
|
||||
}
|
||||
}
|
||||
|
||||
return success(job);
|
||||
return wcsSuccess(job);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,15 +219,19 @@ public class ExternalApiController extends BaseController {
|
||||
job = mydoJobService.selectLatestInMydoJobByTrayCode(request.getTrayCode().trim());
|
||||
}
|
||||
if (job == null) {
|
||||
return error("未找到对应入库作业");
|
||||
return wcsError("未找到对应入库作业");
|
||||
}
|
||||
return mydoJobService.completeInJobFromWcs(job.getJobId());
|
||||
if (!isExpectedJobType(job, true)) {
|
||||
return wcsError("非法入库作业");
|
||||
}
|
||||
return wcsResult(mydoJobService.completeInJobFromWcs(job.getJobId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* WCS 完成出库后回调 WMS。
|
||||
* <p>
|
||||
* 出库完成仍然复用当前出库作业完成过程,统一完成库存扣减、作业归档和出库单完成联动。
|
||||
* WCS 确认托盘已离开库位并到达出库口后,立即释放原货位为空货位;
|
||||
* PDA 扫码确认只负责后续库存扣减、作业归档、出库单完成和回流入库联动。
|
||||
*/
|
||||
@ApiLog(interfaceName = "WCS-完成出库作业", sysCode = "WCS", direction = "OUTBOUND")
|
||||
@PostMapping("/wcs/outbound/complete")
|
||||
@@ -212,9 +239,6 @@ public class ExternalApiController extends BaseController {
|
||||
MydoJob job = null;
|
||||
if (request != null && request.getJobId() != null && !request.getJobId().trim().isEmpty()) {
|
||||
job = mydoJobService.selectMydoJobByJobId(request.getJobId().trim());
|
||||
if (job != null && (job.getInOrOut() == null || job.getInOrOut() < 20)) {
|
||||
job = null;
|
||||
}
|
||||
}
|
||||
if (job == null && request != null && request.getTaskNo() != null && !request.getTaskNo().trim().isEmpty()) {
|
||||
job = mydoJobService.selectOutMydoJobByTaskNo(request.getTaskNo().trim());
|
||||
@@ -225,8 +249,112 @@ public class ExternalApiController extends BaseController {
|
||||
job = mydoJobService.selectLatestOutMydoJobByTrayCodeAndWarehouseId(request.getTrayCode().trim(), request.getWarehouseId().trim());
|
||||
}
|
||||
if (job == null) {
|
||||
return error("未找到对应出库作业");
|
||||
return wcsError("未找到对应出库作业");
|
||||
}
|
||||
return mydoJobService.completeOutJobFromWcs(job.getJobId());
|
||||
if (!isExpectedJobType(job, false)) {
|
||||
return wcsError("非法出库作业");
|
||||
}
|
||||
return wcsResult(mydoJobService.completeOutJobFromWcs(job.getJobId()));
|
||||
}
|
||||
|
||||
private <T> AjaxResult wcsResult(ApiResponse<T> response) {
|
||||
if (response != null && response.isSuccess()) {
|
||||
return wcsSuccess(response.getData());
|
||||
}
|
||||
return wcsError(response == null ? "WCS接口无返回" : response.getMessage());
|
||||
}
|
||||
|
||||
private AjaxResult wcsResult(AjaxResult result) {
|
||||
if (result != null && result.isSuccess()) {
|
||||
return wcsResponse(0, String.valueOf(result.getOrDefault(AjaxResult.MSG_TAG, "操作成功")),
|
||||
result.get(AjaxResult.DATA_TAG));
|
||||
}
|
||||
String message = result == null ? "操作失败" : String.valueOf(result.getOrDefault(AjaxResult.MSG_TAG, "操作失败"));
|
||||
return wcsError(message);
|
||||
}
|
||||
|
||||
private AjaxResult wcsSuccess(Object data) {
|
||||
return wcsResponse(0, "操作成功", data);
|
||||
}
|
||||
|
||||
private AjaxResult wcsError(String message) {
|
||||
return wcsResponse(1, message, null);
|
||||
}
|
||||
|
||||
private AjaxResult wcsResponse(int code, String message, Object data) {
|
||||
return new AjaxResult(code, message, data);
|
||||
}
|
||||
|
||||
private String normalizeExceptionType(WcsJobExceptionRequest request) {
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
String type = trimToNull(request.getExceptionType());
|
||||
if (type == null) {
|
||||
type = trimToNull(request.getType());
|
||||
}
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
if ("PICK_EMPTY".equalsIgnoreCase(type)
|
||||
|| "PICK_NO_GOODS".equalsIgnoreCase(type)
|
||||
|| "取货无货".equals(type)
|
||||
|| "1".equals(type)) {
|
||||
return "PICK_EMPTY";
|
||||
}
|
||||
if ("PUT_OCCUPIED".equalsIgnoreCase(type)
|
||||
|| "PUT_HAS_GOODS".equalsIgnoreCase(type)
|
||||
|| "放货有货".equals(type)
|
||||
|| "2".equals(type)) {
|
||||
return "PUT_OCCUPIED";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private MydoJob resolveExceptionJob(WcsJobExceptionRequest request, String exceptionType) {
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
boolean inbound = "PUT_OCCUPIED".equals(exceptionType);
|
||||
MydoJob job = null;
|
||||
String jobId = trimToNull(request.getJobId());
|
||||
if (jobId != null) {
|
||||
job = mydoJobService.selectMydoJobByJobId(jobId);
|
||||
if (!isExpectedJobType(job, inbound)) {
|
||||
job = null;
|
||||
}
|
||||
}
|
||||
String taskNo = trimToNull(request.getTaskNo());
|
||||
if (job == null && taskNo != null) {
|
||||
job = inbound
|
||||
? mydoJobService.selectInMydoJobByTaskNo(taskNo)
|
||||
: mydoJobService.selectOutMydoJobByTaskNo(taskNo);
|
||||
}
|
||||
String trayCode = trimToNull(request.getTrayCode());
|
||||
String warehouseId = trimToNull(request.getWarehouseId());
|
||||
if (job == null && trayCode != null && warehouseId != null) {
|
||||
job = inbound
|
||||
? mydoJobService.selectLatestInMydoJobByTrayCodeAndWarehouseId(trayCode, warehouseId)
|
||||
: mydoJobService.selectLatestOutMydoJobByTrayCodeAndWarehouseId(trayCode, warehouseId);
|
||||
}
|
||||
if (job == null && inbound && trayCode != null) {
|
||||
job = mydoJobService.selectLatestInMydoJobByTrayCode(trayCode);
|
||||
}
|
||||
return isExpectedJobType(job, inbound) ? job : null;
|
||||
}
|
||||
|
||||
private boolean isExpectedJobType(MydoJob job, boolean inbound) {
|
||||
if (job == null || job.getInOrOut() == null) {
|
||||
return false;
|
||||
}
|
||||
return inbound ? job.getInOrOut() < 20 : job.getInOrOut() >= 20;
|
||||
}
|
||||
|
||||
private String trimToNull(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = value.trim();
|
||||
return trimmed.isEmpty() ? null : trimmed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.ruoyi.api.domain;
|
||||
|
||||
/**
|
||||
* WCS 作业异常回调参数。
|
||||
*/
|
||||
public class WcsJobExceptionRequest {
|
||||
|
||||
/** 异常类型:PICK_EMPTY/取货无货,PUT_OCCUPIED/放货有货 */
|
||||
private String exceptionType;
|
||||
|
||||
/** 兼容字段:异常类型 */
|
||||
private String type;
|
||||
|
||||
/** WMS 作业ID */
|
||||
private String jobId;
|
||||
|
||||
/** WMS/WCS 协同任务号 */
|
||||
private String taskNo;
|
||||
|
||||
/** 托盘号 */
|
||||
private String trayCode;
|
||||
|
||||
/** 仓库编码 */
|
||||
private String warehouseId;
|
||||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
public String getExceptionType() {
|
||||
return exceptionType;
|
||||
}
|
||||
|
||||
public void setExceptionType(String exceptionType) {
|
||||
this.exceptionType = exceptionType;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getJobId() {
|
||||
return jobId;
|
||||
}
|
||||
|
||||
public void setJobId(String jobId) {
|
||||
this.jobId = jobId;
|
||||
}
|
||||
|
||||
public String getTaskNo() {
|
||||
return taskNo;
|
||||
}
|
||||
|
||||
public void setTaskNo(String taskNo) {
|
||||
this.taskNo = taskNo;
|
||||
}
|
||||
|
||||
public String getTrayCode() {
|
||||
return trayCode;
|
||||
}
|
||||
|
||||
public void setTrayCode(String trayCode) {
|
||||
this.trayCode = trayCode;
|
||||
}
|
||||
|
||||
public String getWarehouseId() {
|
||||
return warehouseId;
|
||||
}
|
||||
|
||||
public void setWarehouseId(String warehouseId) {
|
||||
this.warehouseId = warehouseId;
|
||||
}
|
||||
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark) {
|
||||
this.remark = remark;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.bas.cargospace.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.bas.cargospace.domain.BasCargoSpace;
|
||||
import com.ruoyi.in.job.domain.MydoJob;
|
||||
import com.ruoyi.kpi.domain.BasCargoSpaceStatus;
|
||||
|
||||
/**
|
||||
@@ -78,4 +79,68 @@ public interface BasCargoSpaceMapper
|
||||
* @return
|
||||
*/
|
||||
BasCargoSpace selectByCoordinates(BasCargoSpace basCargoSpace);
|
||||
|
||||
/**
|
||||
* 按库存主表ID将货位标记为出库已分配。
|
||||
*
|
||||
* @param stockId 库存ID
|
||||
* @return 结果
|
||||
*/
|
||||
int markOutboundAssignedByStockId(String stockId);
|
||||
|
||||
/**
|
||||
* 按库存ID或库存明细ID将货位标记为出库已分配。
|
||||
*
|
||||
* @param stockRef 库存ID或库存明细ID
|
||||
* @return 结果
|
||||
*/
|
||||
int markOutboundAssignedByStockRef(String stockRef);
|
||||
|
||||
/**
|
||||
* 按出库子单已生成的作业明细将货位标记为出库已分配。
|
||||
*
|
||||
* @param stockoutDetailId 出库子单ID
|
||||
* @return 结果
|
||||
*/
|
||||
int markOutboundAssignedByStockoutDetailId(String stockoutDetailId);
|
||||
|
||||
/**
|
||||
* 按货位ID将无库存货位标记为出库已分配。
|
||||
*
|
||||
* @param cargoSpaceId 货位ID
|
||||
* @return 结果
|
||||
*/
|
||||
int markOutboundAssignedByCargoSpaceId(String cargoSpaceId);
|
||||
|
||||
/**
|
||||
* 将出库作业对应货位释放为空货位。
|
||||
*
|
||||
* @param cargoSpaceId 货位ID
|
||||
* @return 结果
|
||||
*/
|
||||
int releaseOutboundCargoSpace(String cargoSpaceId);
|
||||
|
||||
/**
|
||||
* 将取货无货作业货位标记为出库需盘点。
|
||||
*
|
||||
* @param cargoSpaceId 货位ID
|
||||
* @return 结果
|
||||
*/
|
||||
int markOutboundNeedCheck(String cargoSpaceId);
|
||||
|
||||
/**
|
||||
* 将放货有货作业原货位标记为入库需盘点。
|
||||
*
|
||||
* @param cargoSpaceId 货位ID
|
||||
* @return 结果
|
||||
*/
|
||||
int markInboundNeedCheck(String cargoSpaceId);
|
||||
|
||||
/**
|
||||
* 查询同仓库同巷道的可用入库空货位。
|
||||
*
|
||||
* @param job 当前入库作业
|
||||
* @return 可用货位
|
||||
*/
|
||||
BasCargoSpace selectAvailableInboundCargoSpaceInRoadway(MydoJob job);
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ public class BasStationPoint extends BaseEntity
|
||||
@Excel(name = "点位类型", readConverterExp = "1=入库,2=出库,3=入出库")
|
||||
private String pointType;
|
||||
|
||||
/** 点位大小:1-大点位,2-小点位 */
|
||||
@Excel(name = "点位大小", readConverterExp = "1=大点位,2=小点位")
|
||||
/** 点位大小:1-大点位,2-小点位,3-通用点位 */
|
||||
@Excel(name = "点位大小", readConverterExp = "1=大点位,2=小点位,3=通用点位")
|
||||
private String pointSize;
|
||||
|
||||
/** 托盘类型,多个类型使用英文逗号分隔 */
|
||||
|
||||
@@ -115,6 +115,19 @@ public class BasStockController extends BaseController
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出库存物料明细列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('bas:stock:export')")
|
||||
@Log(title = "库存物料明细", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/detail/export")
|
||||
public void detailExport(HttpServletResponse response, BasStockDetail basStockDetail)
|
||||
{
|
||||
List<BasStockDetail> list = basStockService.selectBasStockDetailList(basStockDetail);
|
||||
ExcelUtil<BasStockDetail> util = new ExcelUtil<BasStockDetail>(BasStockDetail.class);
|
||||
util.exportExcel(response, list, "库存物料明细数据");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取库存物料明细
|
||||
*/
|
||||
|
||||
@@ -85,6 +85,10 @@ public class BasStockDetail extends BaseEntity
|
||||
@Excel(name = "仓库名称")
|
||||
private String warehouseName;
|
||||
|
||||
/** 托盘类型 */
|
||||
@Excel(name = "托盘类型")
|
||||
private String trayType;
|
||||
|
||||
/** 可用库存数量 */
|
||||
@Excel(name = "可用库存数量")
|
||||
private BigDecimal availableQuantity;
|
||||
@@ -269,6 +273,16 @@ public class BasStockDetail extends BaseEntity
|
||||
this.warehouseName = warehouseName;
|
||||
}
|
||||
|
||||
public String getTrayType()
|
||||
{
|
||||
return trayType;
|
||||
}
|
||||
|
||||
public void setTrayType(String trayType)
|
||||
{
|
||||
this.trayType = trayType;
|
||||
}
|
||||
|
||||
public BigDecimal getAvailableQuantity()
|
||||
{
|
||||
return availableQuantity;
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.bas.stock.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.bas.stock.domain.BasStockDetail;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 库存物料明细Mapper接口
|
||||
@@ -12,10 +13,14 @@ public interface BasStockDetailMapper
|
||||
|
||||
public List<BasStockDetail> selectBasStockDetailList(BasStockDetail basStockDetail);
|
||||
|
||||
public int countBasStockDetailByStockId(@Param("stockId") String stockId);
|
||||
|
||||
public int insertBasStockDetail(BasStockDetail basStockDetail);
|
||||
|
||||
public int updateBasStockDetail(BasStockDetail basStockDetail);
|
||||
|
||||
public int rollbackOutboundQuantityByJobId(String jobId);
|
||||
|
||||
public List<String> selectStockIdsByDetailIds(String[] detailIds);
|
||||
|
||||
public int deleteBasStockDetailByDetailId(String detailId);
|
||||
|
||||
@@ -51,6 +51,14 @@ public interface BasStockMapper
|
||||
*/
|
||||
public int refreshBasStockSummary(String stockId);
|
||||
|
||||
/**
|
||||
* 将取货无货作业关联库存标记为异常。
|
||||
*
|
||||
* @param jobId 作业ID
|
||||
* @return 结果
|
||||
*/
|
||||
public int markExceptionByJobId(String jobId);
|
||||
|
||||
/**
|
||||
* 删除库存信息
|
||||
*
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.ruoyi.bas.stock.service.impl;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import com.ruoyi.bas.stock.domain.BasStockDetail;
|
||||
import com.ruoyi.bas.stock.mapper.BasStockDetailMapper;
|
||||
@@ -31,6 +35,9 @@ import com.ruoyi.bas.stock.service.IBasStockService;
|
||||
@Service
|
||||
public class BasStockServiceImpl implements IBasStockService
|
||||
{
|
||||
private static final DateTimeFormatter STOCK_ID_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
|
||||
private static final AtomicLong LAST_STOCK_ID_MILLIS = new AtomicLong();
|
||||
|
||||
@Autowired
|
||||
private BasStockMapper basStockMapper;
|
||||
@Autowired
|
||||
@@ -77,7 +84,7 @@ public class BasStockServiceImpl implements IBasStockService
|
||||
{
|
||||
if (basStock.getStockId() == null || basStock.getStockId().isEmpty())
|
||||
{
|
||||
basStock.setStockId(UUID.randomUUID().toString().replace("-", ""));
|
||||
basStock.setStockId(generateStockId());
|
||||
}
|
||||
BasStockDetail initialDetail = buildInitialDetail(basStock);
|
||||
clearMasterMaterialFields(basStock);
|
||||
@@ -190,7 +197,7 @@ public class BasStockServiceImpl implements IBasStockService
|
||||
{
|
||||
if (basStockDetail.getDetailId() == null || basStockDetail.getDetailId().isEmpty())
|
||||
{
|
||||
basStockDetail.setDetailId(UUID.randomUUID().toString().replace("-", ""));
|
||||
basStockDetail.setDetailId(generateStockDetailId(basStockDetail.getStockId()));
|
||||
}
|
||||
basStockDetail.setCreateTime(DateUtils.getNowDate());
|
||||
int rows = basStockDetailMapper.insertBasStockDetail(basStockDetail);
|
||||
@@ -247,7 +254,7 @@ public class BasStockServiceImpl implements IBasStockService
|
||||
return null;
|
||||
}
|
||||
BasStockDetail detail = new BasStockDetail();
|
||||
detail.setDetailId(UUID.randomUUID().toString().replace("-", ""));
|
||||
detail.setDetailId(generateStockDetailId(basStock.getStockId()));
|
||||
detail.setStockId(basStock.getStockId());
|
||||
detail.setProductStatus(basStock.getProductStatus());
|
||||
detail.setLotNo(basStock.getLotNo());
|
||||
@@ -288,6 +295,23 @@ public class BasStockServiceImpl implements IBasStockService
|
||||
return value != null && !value.isEmpty();
|
||||
}
|
||||
|
||||
private String generateStockId()
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
long stockMillis = LAST_STOCK_ID_MILLIS.updateAndGet(last -> Math.max(now, last + 1));
|
||||
LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(stockMillis), ZoneId.systemDefault());
|
||||
return "KC" + STOCK_ID_FORMATTER.format(dateTime);
|
||||
}
|
||||
|
||||
private String generateStockDetailId(String stockId)
|
||||
{
|
||||
if (!hasText(stockId))
|
||||
{
|
||||
throw new RuntimeException("库存ID不能为空");
|
||||
}
|
||||
return stockId + "-" + (basStockDetailMapper.countBasStockDetailByStockId(stockId) + 1);
|
||||
}
|
||||
|
||||
private void refreshStockSummary(List<String> stockIds)
|
||||
{
|
||||
if (stockIds == null || stockIds.isEmpty())
|
||||
|
||||
@@ -4,8 +4,10 @@ import java.util.List;
|
||||
|
||||
import com.ruoyi.common.core.text.Convert;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.in.job.domain.MydoJob;
|
||||
import com.ruoyi.in.job.service.IMydoJobService;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -46,14 +48,39 @@ public class InMydoJobController extends BaseController
|
||||
{
|
||||
startPage();
|
||||
List<MydoJob> list;
|
||||
if(StringUtils.isEmpty(mydoJob.getStatus()) || Convert.toInt(mydoJob.getStatus())<4){
|
||||
list = mydoJobService.selectInMydoJobList(mydoJob);
|
||||
}else {
|
||||
if (isHistoryQuery(mydoJob) || (StringUtils.isNotEmpty(mydoJob.getStatus()) && Convert.toInt(mydoJob.getStatus()) >= 4)){
|
||||
list = mydoJobService.selectInMydoJobFinishList(mydoJob);
|
||||
}else {
|
||||
list = mydoJobService.selectInMydoJobList(mydoJob);
|
||||
}
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出入库作业信息列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('in:job:export')")
|
||||
@Log(title = "入库作业信息", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(HttpServletResponse response, MydoJob mydoJob)
|
||||
{
|
||||
List<MydoJob> list;
|
||||
if (isHistoryQuery(mydoJob) || (StringUtils.isNotEmpty(mydoJob.getStatus()) && Convert.toInt(mydoJob.getStatus()) >= 4)) {
|
||||
list = mydoJobService.selectInMydoJobFinishList(mydoJob);
|
||||
} else {
|
||||
list = mydoJobService.selectInMydoJobList(mydoJob);
|
||||
}
|
||||
ExcelUtil<MydoJob> util = new ExcelUtil<MydoJob>(MydoJob.class);
|
||||
util.exportExcel(response, list, "入库作业信息数据");
|
||||
}
|
||||
|
||||
private boolean isHistoryQuery(MydoJob mydoJob)
|
||||
{
|
||||
return mydoJob != null
|
||||
&& mydoJob.getParams() != null
|
||||
&& "true".equalsIgnoreCase(Convert.toStr(mydoJob.getParams().get("history")));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取作业信息详细信息
|
||||
*/
|
||||
|
||||
@@ -78,6 +78,22 @@ public interface MydoJobMapper
|
||||
*/
|
||||
List<String> selectStockinIdsByJobId(String jobId);
|
||||
|
||||
/**
|
||||
* 查询出库作业关联的库存主表ID。
|
||||
*
|
||||
* @param jobId 作业ID
|
||||
* @return 库存ID集合
|
||||
*/
|
||||
List<String> selectStockIdsByJobId(String jobId);
|
||||
|
||||
/**
|
||||
* 查询出库作业关联的出库子单ID。
|
||||
*
|
||||
* @param jobId 作业ID
|
||||
* @return 出库子单ID集合
|
||||
*/
|
||||
List<String> selectStockoutDetailIdsByJobId(String jobId);
|
||||
|
||||
/**
|
||||
* 查询作业信息列表
|
||||
*
|
||||
|
||||
@@ -141,12 +141,31 @@ public interface IMydoJobService
|
||||
|
||||
/**
|
||||
* WCS 回调完成出库作业。
|
||||
* <p>
|
||||
* 标记作业已到达出库口并释放原货位;不执行出库完成存储过程,
|
||||
* 库存扣减和作业归档仍由 PDA 扫码确认触发。
|
||||
*
|
||||
* @param jobId 作业ID
|
||||
* @return 处理结果
|
||||
*/
|
||||
AjaxResult completeOutJobFromWcs(String jobId);
|
||||
|
||||
/**
|
||||
* WCS 回调取货无货异常。
|
||||
*
|
||||
* @param jobId 出库作业ID
|
||||
* @return 处理结果
|
||||
*/
|
||||
AjaxResult handleWcsPickEmptyException(String jobId);
|
||||
|
||||
/**
|
||||
* WCS 回调放货有货异常。
|
||||
*
|
||||
* @param jobId 入库作业ID
|
||||
* @return 处理结果,data 为重新分配货位后的作业对象
|
||||
*/
|
||||
AjaxResult handleWcsPutOccupiedException(String jobId);
|
||||
|
||||
/**
|
||||
* 将未下发的出库作业推送到 WCS。
|
||||
*
|
||||
|
||||
@@ -5,6 +5,10 @@ import java.util.List;
|
||||
|
||||
import com.ruoyi.api.client.WcsClient;
|
||||
import com.ruoyi.api.domain.ApiResponse;
|
||||
import com.ruoyi.bas.cargospace.domain.BasCargoSpace;
|
||||
import com.ruoyi.bas.cargospace.mapper.BasCargoSpaceMapper;
|
||||
import com.ruoyi.bas.stock.mapper.BasStockDetailMapper;
|
||||
import com.ruoyi.bas.stock.mapper.BasStockMapper;
|
||||
import com.ruoyi.bas.stationpoint.service.IBasStationPointService;
|
||||
import com.ruoyi.common.annotation.WarehouseScope;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
@@ -12,12 +16,17 @@ import com.ruoyi.common.core.text.Convert;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.in.jobdetail.mapper.MydoJobDetailMapper;
|
||||
import com.ruoyi.in.stockin.mapper.InStockinMapper;
|
||||
import com.ruoyi.out.stockout.mapper.OutStockoutMapper;
|
||||
import com.ruoyi.out.stockoutdetail.mapper.OutStockoutDetailMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.in.job.mapper.MydoJobMapper;
|
||||
import com.ruoyi.in.job.domain.MydoJob;
|
||||
import com.ruoyi.in.job.service.IMydoJobService;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.interceptor.TransactionAspectSupport;
|
||||
|
||||
/**
|
||||
* 作业信息Service业务层处理
|
||||
@@ -37,6 +46,24 @@ public class MydoJobServiceImpl implements IMydoJobService
|
||||
@Autowired
|
||||
private IBasStationPointService basStationPointService;
|
||||
|
||||
@Autowired
|
||||
private BasCargoSpaceMapper basCargoSpaceMapper;
|
||||
|
||||
@Autowired
|
||||
private BasStockMapper basStockMapper;
|
||||
|
||||
@Autowired
|
||||
private BasStockDetailMapper basStockDetailMapper;
|
||||
|
||||
@Autowired
|
||||
private OutStockoutDetailMapper outStockoutDetailMapper;
|
||||
|
||||
@Autowired
|
||||
private OutStockoutMapper outStockoutMapper;
|
||||
|
||||
@Autowired
|
||||
private MydoJobDetailMapper mydoJobDetailMapper;
|
||||
|
||||
@Autowired
|
||||
private WcsClient wcsClient;
|
||||
|
||||
@@ -146,7 +173,8 @@ public class MydoJobServiceImpl implements IMydoJobService
|
||||
|
||||
MydoJob updateJob = new MydoJob();
|
||||
updateJob.setJobId(job.getJobId());
|
||||
updateJob.setStatus("2");
|
||||
updateJob.setStatus("3");
|
||||
updateJob.setExecutionPhase("1");
|
||||
updateJob.setStartTime(DateUtils.getTime());
|
||||
if (mydoJobMapper.updateMydoJob(updateJob) <= 0) {
|
||||
return AjaxResult.error("出库作业状态更新失败");
|
||||
@@ -227,6 +255,10 @@ public class MydoJobServiceImpl implements IMydoJobService
|
||||
public AjaxResult cancelJob(String jobId,String type)
|
||||
{
|
||||
MydoJob job = mydoJobMapper.selectMydoJobByJobId(jobId);
|
||||
AjaxResult wcsCancelResult = requestWcsCancelBeforeManualCancel(job);
|
||||
if (wcsCancelResult.isError()) {
|
||||
return wcsCancelResult;
|
||||
}
|
||||
if ("1".equals(type)){
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
map.put("v_job_id", jobId);
|
||||
@@ -251,6 +283,27 @@ public class MydoJobServiceImpl implements IMydoJobService
|
||||
return AjaxResult.error();
|
||||
}
|
||||
|
||||
private AjaxResult requestWcsCancelBeforeManualCancel(MydoJob job)
|
||||
{
|
||||
if (job == null) {
|
||||
return AjaxResult.error("作业不存在");
|
||||
}
|
||||
if (!"2".equals(job.getStatus()) && !"3".equals(job.getStatus())) {
|
||||
return AjaxResult.success();
|
||||
}
|
||||
String taskId = StringUtils.isEmpty(job.getTaskNo()) ? job.getJobId() : job.getTaskNo();
|
||||
try {
|
||||
ApiResponse<String> response = wcsClient.cancelTask(taskId);
|
||||
if (response == null || !response.isSuccess()) {
|
||||
String message = response == null ? "WCS接口无返回" : response.getMessage();
|
||||
return AjaxResult.error("WCS拒绝作废:" + message);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
return AjaxResult.error("WCS拒绝作废:" + e.getMessage());
|
||||
}
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动完成作业
|
||||
*
|
||||
@@ -306,6 +359,12 @@ public class MydoJobServiceImpl implements IMydoJobService
|
||||
public AjaxResult completeInJobFromWcs(String jobId)
|
||||
{
|
||||
MydoJob job = mydoJobMapper.selectMydoJobByJobId(jobId);
|
||||
if (job == null) {
|
||||
return AjaxResult.error("入库作业不存在");
|
||||
}
|
||||
if (!isInboundJob(job)) {
|
||||
return AjaxResult.error("非法入库作业");
|
||||
}
|
||||
List<String> stockinIds = mydoJobMapper.selectStockinIdsByJobId(jobId);
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
map.put("job_id", jobId);
|
||||
@@ -325,21 +384,163 @@ public class MydoJobServiceImpl implements IMydoJobService
|
||||
|
||||
/**
|
||||
* WCS 回调完成出库作业。
|
||||
* <p>
|
||||
* 此时托盘已离开库位,货位状态在 WCS 完成回调中释放;PDA 确认不再负责释放货位。
|
||||
*/
|
||||
@Override
|
||||
public AjaxResult completeOutJobFromWcs(String jobId)
|
||||
{
|
||||
MydoJob job = mydoJobMapper.selectMydoJobByJobId(jobId);
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
map.put("v_job_id", jobId);
|
||||
map.put("v_dept_id", 0);
|
||||
map.put("v_user_id", 0);
|
||||
mydoJobMapper.makeSure(map);
|
||||
if (isProcedureSuccess(map.get("v_result"))){
|
||||
releaseJobStationPoint(job);
|
||||
if (job == null) {
|
||||
return AjaxResult.error("出库作业不存在");
|
||||
}
|
||||
if (!isOutboundJob(job)) {
|
||||
return AjaxResult.error("非法出库作业");
|
||||
}
|
||||
|
||||
MydoJob updateJob = new MydoJob();
|
||||
updateJob.setJobId(job.getJobId());
|
||||
updateJob.setExecutionPhase("5");
|
||||
if (mydoJobMapper.updateMydoJob(updateJob) <= 0) {
|
||||
return AjaxResult.error("出库作业等待扫码确认状态更新失败");
|
||||
}
|
||||
AjaxResult releaseResult = releaseOutboundCargoSpace(job);
|
||||
if (releaseResult.isError()) {
|
||||
return releaseResult;
|
||||
}
|
||||
return AjaxResult.success("出库作业已到达出口,货位已释放,等待PDA扫码确认");
|
||||
}
|
||||
|
||||
private boolean isInboundJob(MydoJob job)
|
||||
{
|
||||
return job != null && job.getInOrOut() != null && job.getInOrOut() < 20;
|
||||
}
|
||||
|
||||
private boolean isOutboundJob(MydoJob job)
|
||||
{
|
||||
return job != null && job.getInOrOut() != null && job.getInOrOut() >= 20;
|
||||
}
|
||||
|
||||
/**
|
||||
* WCS 回调取货无货异常。
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public AjaxResult handleWcsPickEmptyException(String jobId)
|
||||
{
|
||||
if (StringUtils.isEmpty(jobId)) {
|
||||
return AjaxResult.error("作业ID不能为空");
|
||||
}
|
||||
MydoJob job = mydoJobMapper.selectMydoJobByJobId(jobId);
|
||||
if (job == null) {
|
||||
return AjaxResult.error("出库作业不存在");
|
||||
}
|
||||
if (job.getInOrOut() == null || job.getInOrOut() < 20) {
|
||||
return AjaxResult.error("请选择出库作业");
|
||||
}
|
||||
if (StringUtils.isEmpty(job.getCargoSpaceId())) {
|
||||
return AjaxResult.error("出库作业货位不能为空");
|
||||
}
|
||||
|
||||
List<String> stockIds = mydoJobMapper.selectStockIdsByJobId(jobId);
|
||||
List<String> stockoutDetailIds = mydoJobMapper.selectStockoutDetailIdsByJobId(jobId);
|
||||
if (basCargoSpaceMapper.markOutboundNeedCheck(job.getCargoSpaceId()) <= 0) {
|
||||
return rollbackError("出库作业货位异常状态更新失败");
|
||||
}
|
||||
boolean hasStockDetails = stockIds != null && !stockIds.isEmpty();
|
||||
boolean hasStockoutDetails = stockoutDetailIds != null && !stockoutDetailIds.isEmpty();
|
||||
if (hasStockoutDetails) {
|
||||
if (outStockoutDetailMapper.rollbackAllotByJobId(jobId) <= 0) {
|
||||
return rollbackError("出库单据分配数量回滚失败");
|
||||
}
|
||||
outStockoutMapper.reopenOutStockoutByExceptionJobId(jobId);
|
||||
}
|
||||
if (hasStockDetails) {
|
||||
String[] stockIdArray = stockIds.toArray(new String[0]);
|
||||
basStockDetailMapper.deleteBasStockDetailByStockIds(stockIdArray);
|
||||
if (basStockMapper.deleteBasStockByStockIds(stockIdArray) <= 0) {
|
||||
return rollbackError("库存删除失败");
|
||||
}
|
||||
}
|
||||
mydoJobDetailMapper.deleteMydoJobDetailByJobId(jobId);
|
||||
if (mydoJobMapper.deleteMydoJobByJobId(jobId) <= 0) {
|
||||
return rollbackError("作业作废失败");
|
||||
}
|
||||
releaseJobStationPoint(job);
|
||||
return AjaxResult.success("取货无货异常处理完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* WCS 回调放货有货异常。
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public AjaxResult handleWcsPutOccupiedException(String jobId)
|
||||
{
|
||||
if (StringUtils.isEmpty(jobId)) {
|
||||
return AjaxResult.error("作业ID不能为空");
|
||||
}
|
||||
MydoJob job = mydoJobMapper.selectMydoJobByJobId(jobId);
|
||||
if (job == null) {
|
||||
return AjaxResult.error("入库作业不存在");
|
||||
}
|
||||
if (job.getInOrOut() == null || job.getInOrOut() >= 20) {
|
||||
return AjaxResult.error("请选择入库作业");
|
||||
}
|
||||
if (StringUtils.isEmpty(job.getCargoSpaceId())) {
|
||||
return AjaxResult.error("入库作业货位不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(job.getWarehouseId()) || StringUtils.isEmpty(job.getRoadwayId())) {
|
||||
return AjaxResult.error("入库作业仓库或巷道不能为空");
|
||||
}
|
||||
|
||||
if (basCargoSpaceMapper.markInboundNeedCheck(job.getCargoSpaceId()) <= 0) {
|
||||
return rollbackError("入库作业原货位异常状态更新失败");
|
||||
}
|
||||
BasCargoSpace cargoSpace = basCargoSpaceMapper.selectAvailableInboundCargoSpaceInRoadway(job);
|
||||
if (cargoSpace == null) {
|
||||
return rollbackError("相同巷道没有可用入库货位");
|
||||
}
|
||||
|
||||
BasCargoSpace lockCargoSpace = new BasCargoSpace();
|
||||
lockCargoSpace.setCargoSpaceId(cargoSpace.getCargoSpaceId());
|
||||
lockCargoSpace.setStatus("3");
|
||||
lockCargoSpace.setTrayCode(job.getTrayCode());
|
||||
if (basCargoSpaceMapper.updateBasCargoSpace(lockCargoSpace) <= 0) {
|
||||
return rollbackError("新入库货位锁定失败");
|
||||
}
|
||||
|
||||
MydoJob updateJob = new MydoJob();
|
||||
updateJob.setJobId(job.getJobId());
|
||||
updateJob.setRoadwayId(cargoSpace.getRoadwayId());
|
||||
updateJob.setCargoSpaceId(cargoSpace.getCargoSpaceId());
|
||||
updateJob.setCargoSpaceName(cargoSpace.getCargoSpaceName());
|
||||
if (mydoJobMapper.updateMydoJob(updateJob) <= 0) {
|
||||
return rollbackError("入库作业货位更新失败");
|
||||
}
|
||||
|
||||
MydoJob updatedJob = mydoJobMapper.selectMydoJobByJobId(jobId);
|
||||
if (updatedJob == null) {
|
||||
updatedJob = updateJob;
|
||||
}
|
||||
return AjaxResult.success("放货有货异常处理完成", updatedJob);
|
||||
}
|
||||
|
||||
private AjaxResult releaseOutboundCargoSpace(MydoJob job)
|
||||
{
|
||||
if (job == null || StringUtils.isEmpty(job.getCargoSpaceId())) {
|
||||
return AjaxResult.success();
|
||||
}
|
||||
return AjaxResult.error(Convert.toStr(map.get("v_resultmsg")));
|
||||
if (basCargoSpaceMapper.releaseOutboundCargoSpace(job.getCargoSpaceId()) <= 0) {
|
||||
return AjaxResult.error("出库作业货位释放失败");
|
||||
}
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
private AjaxResult rollbackError(String message)
|
||||
{
|
||||
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return AjaxResult.error(message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -59,5 +59,7 @@ public interface MydoJobDetailMapper
|
||||
*/
|
||||
public int deleteMydoJobDetailByJobDetailIds(String[] jobDetailIds);
|
||||
|
||||
public int deleteMydoJobDetailByJobId(String jobId);
|
||||
|
||||
List<MydoJobDetail> selectMydoJobDetailFinishList(MydoJobDetail mydoJobDetail);
|
||||
}
|
||||
|
||||
@@ -50,10 +50,10 @@ public class InStockinController extends BaseController
|
||||
{
|
||||
startPage();
|
||||
List<InStockin> list;
|
||||
if (StringUtils.isEmpty(inStockin.getStatus()) || Convert.toInt(inStockin.getStatus())< 4){
|
||||
list = inStockinService.selectInStockinList(inStockin);
|
||||
}else {
|
||||
if (isHistoryQuery(inStockin) || (StringUtils.isNotEmpty(inStockin.getStatus()) && Convert.toInt(inStockin.getStatus()) >= 4)){
|
||||
list = inStockinService.selectInStockinFinishList(inStockin);
|
||||
}else {
|
||||
list = inStockinService.selectInStockinList(inStockin);
|
||||
}
|
||||
return getDataTable(list);
|
||||
}
|
||||
@@ -66,11 +66,23 @@ public class InStockinController extends BaseController
|
||||
@PostMapping("/export")
|
||||
public void export(HttpServletResponse response, InStockin inStockin)
|
||||
{
|
||||
List<InStockin> list = inStockinService.selectInStockinList(inStockin);
|
||||
List<InStockin> list;
|
||||
if (isHistoryQuery(inStockin) || (StringUtils.isNotEmpty(inStockin.getStatus()) && Convert.toInt(inStockin.getStatus()) >= 4)) {
|
||||
list = inStockinService.selectInStockinFinishList(inStockin);
|
||||
} else {
|
||||
list = inStockinService.selectInStockinList(inStockin);
|
||||
}
|
||||
ExcelUtil<InStockin> util = new ExcelUtil<InStockin>(InStockin.class);
|
||||
util.exportExcel(response, list, "入库单据主信息数据");
|
||||
}
|
||||
|
||||
private boolean isHistoryQuery(InStockin inStockin)
|
||||
{
|
||||
return inStockin != null
|
||||
&& inStockin.getParams() != null
|
||||
&& "true".equalsIgnoreCase(Convert.toStr(inStockin.getParams().get("history")));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取入库单据主信息详细信息
|
||||
*/
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.ruoyi.common.annotation.WarehouseScope;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.text.Convert;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.system.mapper.SysDeptMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -150,12 +151,26 @@ public class InStockinServiceImpl implements IInStockinService
|
||||
}
|
||||
// 关联主单ID
|
||||
detail.setStockinId(saveDTO.getStockinId());
|
||||
normalizeProductFields(detail);
|
||||
inStockinDetailMapper.insertInStockinDetail(detail);
|
||||
}
|
||||
}
|
||||
return saveDTO.getStockinId();
|
||||
}
|
||||
|
||||
private void normalizeProductFields(InStockinDetail detail)
|
||||
{
|
||||
if (detail == null) {
|
||||
return;
|
||||
}
|
||||
if (StringUtils.isNotEmpty(detail.getItemCod())) {
|
||||
detail.setProductId(detail.getItemCod());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(detail.getItemNam())) {
|
||||
detail.setProductName(detail.getItemNam());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动作废入库单据
|
||||
*
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.springframework.stereotype.Service;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* KPI Service实现
|
||||
@@ -45,6 +45,8 @@ public class KpiServiceImpl implements KpiService {
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private static final String UNASSIGNED_ROADWAY = "未分配";
|
||||
|
||||
@Override
|
||||
public Map<String, Object> selectInboundTrend(InAnalysisDataQuery query) {
|
||||
query.setStartTime(query.getStartTime()+" 00:00:00");
|
||||
@@ -55,40 +57,12 @@ public class KpiServiceImpl implements KpiService {
|
||||
// 2. 获取时间范围内所有日期(x轴)
|
||||
List<String> dateListBetween = DateUtils.getDateListBetween(query.getStartTime(), query.getEndTime());
|
||||
|
||||
// 3. 按 巷道ID 分组
|
||||
Map<String, List<InAnalysisData>> roadwayMap = list.stream()
|
||||
.collect(Collectors.groupingBy(InAnalysisData::getRoadwayId));
|
||||
|
||||
// 4. 组装 series
|
||||
List<Map<String, Object>> seriesList = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, List<InAnalysisData>> entry : roadwayMap.entrySet()) {
|
||||
String roadwayId = entry.getKey(); // 巷道ID
|
||||
List<InAnalysisData> dataList = entry.getValue();
|
||||
|
||||
// 把数据转成 Map<日期, 数量> 方便查找
|
||||
Map<String, Long> dateCountMap = dataList.stream()
|
||||
.collect(Collectors.toMap(InAnalysisData::getDateDay, InAnalysisData::getCount));
|
||||
|
||||
// 遍历所有日期,没有数据补 0
|
||||
List<Long> countList = new ArrayList<>();
|
||||
for (String date : dateListBetween) {
|
||||
countList.add(dateCountMap.getOrDefault(date, 0L));
|
||||
}
|
||||
|
||||
// 构造 series 项
|
||||
Map<String, Object> series = new HashMap<>();
|
||||
series.put("name", "巷道"+roadwayId); // 巷道1、巷道2...
|
||||
series.put("data", countList);
|
||||
seriesList.add(series);
|
||||
}
|
||||
|
||||
// 5. 组装最终返回结构
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("xAxis", dateListBetween);
|
||||
result.put("series", seriesList);
|
||||
|
||||
return result; // ← 这里返回,不是return null
|
||||
return buildRoadwayTrend(
|
||||
list,
|
||||
dateListBetween,
|
||||
InAnalysisData::getRoadwayId,
|
||||
InAnalysisData::getDateDay,
|
||||
InAnalysisData::getCount);
|
||||
}
|
||||
|
||||
|
||||
@@ -103,40 +77,58 @@ public class KpiServiceImpl implements KpiService {
|
||||
// 2. 获取时间范围内所有日期(x轴)
|
||||
List<String> dateListBetween = DateUtils.getDateListBetween(query.getStartTime(), query.getEndTime());
|
||||
|
||||
// 3. 按 巷道ID 分组
|
||||
Map<String, List<OutAnalysisData>> roadwayMap = list.stream()
|
||||
.collect(Collectors.groupingBy(OutAnalysisData::getRoadwayId));
|
||||
return buildRoadwayTrend(
|
||||
list,
|
||||
dateListBetween,
|
||||
OutAnalysisData::getRoadwayId,
|
||||
OutAnalysisData::getDateDay,
|
||||
OutAnalysisData::getCount);
|
||||
}
|
||||
|
||||
private <T> Map<String, Object> buildRoadwayTrend(List<T> list,
|
||||
List<String> dateListBetween,
|
||||
Function<T, String> roadwayGetter,
|
||||
Function<T, String> dateGetter,
|
||||
Function<T, Long> countGetter) {
|
||||
Map<String, Map<String, Long>> roadwayDateCountMap = new LinkedHashMap<>();
|
||||
if (list != null) {
|
||||
for (T item : list) {
|
||||
String dateDay = dateGetter.apply(item);
|
||||
if (dateDay == null || dateDay.trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
String roadwayId = normalizeRoadwayId(roadwayGetter.apply(item));
|
||||
Long count = countGetter.apply(item);
|
||||
roadwayDateCountMap
|
||||
.computeIfAbsent(roadwayId, key -> new LinkedHashMap<>())
|
||||
.merge(dateDay, count == null ? 0L : count, Long::sum);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 组装 series
|
||||
List<Map<String, Object>> seriesList = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, List<OutAnalysisData>> entry : roadwayMap.entrySet()) {
|
||||
String roadwayId = entry.getKey(); // 巷道ID
|
||||
List<OutAnalysisData> dataList = entry.getValue();
|
||||
|
||||
// 把数据转成 Map<日期, 数量> 方便查找
|
||||
Map<String, Long> dateCountMap = dataList.stream()
|
||||
.collect(Collectors.toMap(OutAnalysisData::getDateDay, OutAnalysisData::getCount));
|
||||
|
||||
// 遍历所有日期,没有数据补 0
|
||||
for (Map.Entry<String, Map<String, Long>> entry : roadwayDateCountMap.entrySet()) {
|
||||
List<Long> countList = new ArrayList<>();
|
||||
for (String date : dateListBetween) {
|
||||
countList.add(dateCountMap.getOrDefault(date, 0L));
|
||||
countList.add(entry.getValue().getOrDefault(date, 0L));
|
||||
}
|
||||
|
||||
// 构造 series 项
|
||||
Map<String, Object> series = new HashMap<>();
|
||||
series.put("name", "巷道"+roadwayId); // 巷道1、巷道2...
|
||||
series.put("name", "巷道" + entry.getKey());
|
||||
series.put("data", countList);
|
||||
seriesList.add(series);
|
||||
}
|
||||
|
||||
// 5. 组装最终返回结构
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("xAxis", dateListBetween);
|
||||
result.put("series", seriesList);
|
||||
return result;
|
||||
}
|
||||
|
||||
return result; // ← 这里返回,不是return null
|
||||
private String normalizeRoadwayId(String roadwayId) {
|
||||
if (roadwayId == null || roadwayId.trim().isEmpty()) {
|
||||
return UNASSIGNED_ROADWAY;
|
||||
}
|
||||
return roadwayId.trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,8 +4,10 @@ import java.util.List;
|
||||
|
||||
import com.ruoyi.common.core.text.Convert;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.in.job.domain.MydoJob;
|
||||
import com.ruoyi.in.job.service.IMydoJobService;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -44,14 +46,39 @@ public class OutMydoJobController extends BaseController
|
||||
{
|
||||
startPage();
|
||||
List<MydoJob> list;
|
||||
if (StringUtils.isEmpty(mydoJob.getStatus()) || Convert.toInt(mydoJob.getStatus())<4){
|
||||
list = mydoJobService.selectOutMydoJobList(mydoJob);
|
||||
}else {
|
||||
if (isHistoryQuery(mydoJob) || (StringUtils.isNotEmpty(mydoJob.getStatus()) && Convert.toInt(mydoJob.getStatus()) >= 4)){
|
||||
list = mydoJobService.selectOutMydoJobFinishList(mydoJob);
|
||||
}else {
|
||||
list = mydoJobService.selectOutMydoJobList(mydoJob);
|
||||
}
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出出库作业信息列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('out:job:export')")
|
||||
@Log(title = "出库作业信息", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(HttpServletResponse response, MydoJob mydoJob)
|
||||
{
|
||||
List<MydoJob> list;
|
||||
if (isHistoryQuery(mydoJob) || (StringUtils.isNotEmpty(mydoJob.getStatus()) && Convert.toInt(mydoJob.getStatus()) >= 4)) {
|
||||
list = mydoJobService.selectOutMydoJobFinishList(mydoJob);
|
||||
} else {
|
||||
list = mydoJobService.selectOutMydoJobList(mydoJob);
|
||||
}
|
||||
ExcelUtil<MydoJob> util = new ExcelUtil<MydoJob>(MydoJob.class);
|
||||
util.exportExcel(response, list, "出库作业信息数据");
|
||||
}
|
||||
|
||||
private boolean isHistoryQuery(MydoJob mydoJob)
|
||||
{
|
||||
return mydoJob != null
|
||||
&& mydoJob.getParams() != null
|
||||
&& "true".equalsIgnoreCase(Convert.toStr(mydoJob.getParams().get("history")));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取作业信息详细信息
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.ruoyi.out.location.controller;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.out.location.domain.LocationOutboundGenerateRequest;
|
||||
import com.ruoyi.out.location.domain.LocationOutboundStock;
|
||||
import com.ruoyi.out.location.service.ILocationOutboundService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 指定货位出库Controller。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/out/locationOutbound")
|
||||
public class LocationOutboundController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private ILocationOutboundService locationOutboundService;
|
||||
|
||||
/**
|
||||
* 查询货位库存列表。
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('out:locationOutbound:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(LocationOutboundStock query)
|
||||
{
|
||||
startPage();
|
||||
List<LocationOutboundStock> list = locationOutboundService.selectLocationOutboundStockList(query);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按选中货位整托生成出库作业。
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('out:locationOutbound:generate')")
|
||||
@Log(title = "指定货位出库", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/generate")
|
||||
public AjaxResult generate(@RequestBody LocationOutboundGenerateRequest request)
|
||||
{
|
||||
return success(locationOutboundService.generateWholeTrayOutboundJob(request));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.ruoyi.out.location.domain;
|
||||
|
||||
/**
|
||||
* 指定货位出库作业生成参数。
|
||||
*/
|
||||
public class LocationOutboundGenerateRequest
|
||||
{
|
||||
private String stockId;
|
||||
private String cargoSpaceId;
|
||||
private String exitId;
|
||||
|
||||
public String getStockId() {
|
||||
return stockId;
|
||||
}
|
||||
|
||||
public void setStockId(String stockId) {
|
||||
this.stockId = stockId;
|
||||
}
|
||||
|
||||
public String getCargoSpaceId() {
|
||||
return cargoSpaceId;
|
||||
}
|
||||
|
||||
public void setCargoSpaceId(String cargoSpaceId) {
|
||||
this.cargoSpaceId = cargoSpaceId;
|
||||
}
|
||||
|
||||
public String getExitId() {
|
||||
return exitId;
|
||||
}
|
||||
|
||||
public void setExitId(String exitId) {
|
||||
this.exitId = exitId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
package com.ruoyi.out.location.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.ruoyi.bas.stock.domain.BasStockDetail;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
|
||||
/**
|
||||
* 指定货位出库库存视图。
|
||||
*/
|
||||
public class LocationOutboundStock extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String cargoSpaceId;
|
||||
private String cargoSpaceName;
|
||||
private String cargoSpaceStatus;
|
||||
private String locked;
|
||||
private String warehouseId;
|
||||
private String warehouseName;
|
||||
private String roadwayId;
|
||||
private Long thePlatoon;
|
||||
private Long theColumn;
|
||||
private Long theFloor;
|
||||
private String theType;
|
||||
private String theTypeName;
|
||||
private String stockId;
|
||||
private String stockStatus;
|
||||
private String trayCode;
|
||||
private String trayType;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date inDate;
|
||||
private String stockinId;
|
||||
private String inType;
|
||||
private String supplierId;
|
||||
private String supplierName;
|
||||
private Integer detailCount;
|
||||
private BigDecimal quantity;
|
||||
private BigDecimal outQuantity;
|
||||
private BigDecimal availableQuantity;
|
||||
private String productId;
|
||||
private String productName;
|
||||
private String lotNo;
|
||||
private Boolean showEmpty;
|
||||
private List<BasStockDetail> details = new ArrayList<>();
|
||||
|
||||
public String getCargoSpaceId() {
|
||||
return cargoSpaceId;
|
||||
}
|
||||
|
||||
public void setCargoSpaceId(String cargoSpaceId) {
|
||||
this.cargoSpaceId = cargoSpaceId;
|
||||
}
|
||||
|
||||
public String getCargoSpaceName() {
|
||||
return cargoSpaceName;
|
||||
}
|
||||
|
||||
public void setCargoSpaceName(String cargoSpaceName) {
|
||||
this.cargoSpaceName = cargoSpaceName;
|
||||
}
|
||||
|
||||
public String getCargoSpaceStatus() {
|
||||
return cargoSpaceStatus;
|
||||
}
|
||||
|
||||
public void setCargoSpaceStatus(String cargoSpaceStatus) {
|
||||
this.cargoSpaceStatus = cargoSpaceStatus;
|
||||
}
|
||||
|
||||
public String getLocked() {
|
||||
return locked;
|
||||
}
|
||||
|
||||
public void setLocked(String locked) {
|
||||
this.locked = locked;
|
||||
}
|
||||
|
||||
public String getWarehouseId() {
|
||||
return warehouseId;
|
||||
}
|
||||
|
||||
public void setWarehouseId(String warehouseId) {
|
||||
this.warehouseId = warehouseId;
|
||||
}
|
||||
|
||||
public String getWarehouseName() {
|
||||
return warehouseName;
|
||||
}
|
||||
|
||||
public void setWarehouseName(String warehouseName) {
|
||||
this.warehouseName = warehouseName;
|
||||
}
|
||||
|
||||
public String getRoadwayId() {
|
||||
return roadwayId;
|
||||
}
|
||||
|
||||
public void setRoadwayId(String roadwayId) {
|
||||
this.roadwayId = roadwayId;
|
||||
}
|
||||
|
||||
public Long getThePlatoon() {
|
||||
return thePlatoon;
|
||||
}
|
||||
|
||||
public void setThePlatoon(Long thePlatoon) {
|
||||
this.thePlatoon = thePlatoon;
|
||||
}
|
||||
|
||||
public Long getTheColumn() {
|
||||
return theColumn;
|
||||
}
|
||||
|
||||
public void setTheColumn(Long theColumn) {
|
||||
this.theColumn = theColumn;
|
||||
}
|
||||
|
||||
public Long getTheFloor() {
|
||||
return theFloor;
|
||||
}
|
||||
|
||||
public void setTheFloor(Long theFloor) {
|
||||
this.theFloor = theFloor;
|
||||
}
|
||||
|
||||
public String getTheType() {
|
||||
return theType;
|
||||
}
|
||||
|
||||
public void setTheType(String theType) {
|
||||
this.theType = theType;
|
||||
}
|
||||
|
||||
public String getTheTypeName() {
|
||||
return theTypeName;
|
||||
}
|
||||
|
||||
public void setTheTypeName(String theTypeName) {
|
||||
this.theTypeName = theTypeName;
|
||||
}
|
||||
|
||||
public String getStockId() {
|
||||
return stockId;
|
||||
}
|
||||
|
||||
public void setStockId(String stockId) {
|
||||
this.stockId = stockId;
|
||||
}
|
||||
|
||||
public String getStockStatus() {
|
||||
return stockStatus;
|
||||
}
|
||||
|
||||
public void setStockStatus(String stockStatus) {
|
||||
this.stockStatus = stockStatus;
|
||||
}
|
||||
|
||||
public String getTrayCode() {
|
||||
return trayCode;
|
||||
}
|
||||
|
||||
public void setTrayCode(String trayCode) {
|
||||
this.trayCode = trayCode;
|
||||
}
|
||||
|
||||
public String getTrayType() {
|
||||
return trayType;
|
||||
}
|
||||
|
||||
public void setTrayType(String trayType) {
|
||||
this.trayType = trayType;
|
||||
}
|
||||
|
||||
public Date getInDate() {
|
||||
return inDate;
|
||||
}
|
||||
|
||||
public void setInDate(Date inDate) {
|
||||
this.inDate = inDate;
|
||||
}
|
||||
|
||||
public String getStockinId() {
|
||||
return stockinId;
|
||||
}
|
||||
|
||||
public void setStockinId(String stockinId) {
|
||||
this.stockinId = stockinId;
|
||||
}
|
||||
|
||||
public String getInType() {
|
||||
return inType;
|
||||
}
|
||||
|
||||
public void setInType(String inType) {
|
||||
this.inType = inType;
|
||||
}
|
||||
|
||||
public String getSupplierId() {
|
||||
return supplierId;
|
||||
}
|
||||
|
||||
public void setSupplierId(String supplierId) {
|
||||
this.supplierId = supplierId;
|
||||
}
|
||||
|
||||
public String getSupplierName() {
|
||||
return supplierName;
|
||||
}
|
||||
|
||||
public void setSupplierName(String supplierName) {
|
||||
this.supplierName = supplierName;
|
||||
}
|
||||
|
||||
public Integer getDetailCount() {
|
||||
return detailCount;
|
||||
}
|
||||
|
||||
public void setDetailCount(Integer detailCount) {
|
||||
this.detailCount = detailCount;
|
||||
}
|
||||
|
||||
public BigDecimal getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(BigDecimal quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public BigDecimal getOutQuantity() {
|
||||
return outQuantity;
|
||||
}
|
||||
|
||||
public void setOutQuantity(BigDecimal outQuantity) {
|
||||
this.outQuantity = outQuantity;
|
||||
}
|
||||
|
||||
public BigDecimal getAvailableQuantity() {
|
||||
return availableQuantity;
|
||||
}
|
||||
|
||||
public void setAvailableQuantity(BigDecimal availableQuantity) {
|
||||
this.availableQuantity = availableQuantity;
|
||||
}
|
||||
|
||||
public String getProductId() {
|
||||
return productId;
|
||||
}
|
||||
|
||||
public void setProductId(String productId) {
|
||||
this.productId = productId;
|
||||
}
|
||||
|
||||
public String getProductName() {
|
||||
return productName;
|
||||
}
|
||||
|
||||
public void setProductName(String productName) {
|
||||
this.productName = productName;
|
||||
}
|
||||
|
||||
public String getLotNo() {
|
||||
return lotNo;
|
||||
}
|
||||
|
||||
public void setLotNo(String lotNo) {
|
||||
this.lotNo = lotNo;
|
||||
}
|
||||
|
||||
public Boolean getShowEmpty() {
|
||||
return showEmpty;
|
||||
}
|
||||
|
||||
public void setShowEmpty(Boolean showEmpty) {
|
||||
this.showEmpty = showEmpty;
|
||||
}
|
||||
|
||||
public List<BasStockDetail> getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
public void setDetails(List<BasStockDetail> details) {
|
||||
this.details = details;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.ruoyi.out.location.mapper;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import com.ruoyi.bas.stock.domain.BasStockDetail;
|
||||
import com.ruoyi.out.location.domain.LocationOutboundStock;
|
||||
|
||||
/**
|
||||
* 指定货位出库Mapper。
|
||||
*/
|
||||
public interface LocationOutboundMapper
|
||||
{
|
||||
List<LocationOutboundStock> selectLocationOutboundStockList(LocationOutboundStock query);
|
||||
|
||||
List<BasStockDetail> selectLocationOutboundDetailsByStockIds(String[] stockIds);
|
||||
|
||||
LocationOutboundStock selectLocationOutboundStockForUpdate(String stockId);
|
||||
|
||||
LocationOutboundStock selectLocationOutboundLocationForUpdate(String cargoSpaceId);
|
||||
|
||||
List<BasStockDetail> selectAvailableDetailsForUpdate(String stockId);
|
||||
|
||||
int reserveStockDetailForOutbound(HashMap<String, Object> map);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ruoyi.out.location.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.ruoyi.out.location.domain.LocationOutboundGenerateRequest;
|
||||
import com.ruoyi.out.location.domain.LocationOutboundStock;
|
||||
|
||||
/**
|
||||
* 指定货位出库服务。
|
||||
*/
|
||||
public interface ILocationOutboundService
|
||||
{
|
||||
List<LocationOutboundStock> selectLocationOutboundStockList(LocationOutboundStock query);
|
||||
|
||||
String generateWholeTrayOutboundJob(LocationOutboundGenerateRequest request);
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
package com.ruoyi.out.location.service.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.ruoyi.bas.cargospace.mapper.BasCargoSpaceMapper;
|
||||
import com.ruoyi.bas.id.job.service.JobIdService;
|
||||
import com.ruoyi.bas.id.taskno.service.TaskNoService;
|
||||
import com.ruoyi.bas.stationpoint.domain.BasStationPoint;
|
||||
import com.ruoyi.bas.stationpoint.service.IBasStationPointService;
|
||||
import com.ruoyi.bas.stock.domain.BasStockDetail;
|
||||
import com.ruoyi.bas.stock.mapper.BasStockMapper;
|
||||
import com.ruoyi.common.annotation.WarehouseScope;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.text.Convert;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.in.job.domain.MydoJob;
|
||||
import com.ruoyi.in.job.service.IMydoJobService;
|
||||
import com.ruoyi.in.jobdetail.domain.MydoJobDetail;
|
||||
import com.ruoyi.in.jobdetail.service.IMydoJobDetailService;
|
||||
import com.ruoyi.out.location.domain.LocationOutboundGenerateRequest;
|
||||
import com.ruoyi.out.location.domain.LocationOutboundStock;
|
||||
import com.ruoyi.out.location.mapper.LocationOutboundMapper;
|
||||
import com.ruoyi.out.location.service.ILocationOutboundService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 指定货位出库服务实现。
|
||||
*/
|
||||
@Service
|
||||
public class LocationOutboundServiceImpl implements ILocationOutboundService
|
||||
{
|
||||
@Autowired
|
||||
private LocationOutboundMapper locationOutboundMapper;
|
||||
|
||||
@Autowired
|
||||
private BasCargoSpaceMapper basCargoSpaceMapper;
|
||||
|
||||
@Autowired
|
||||
private BasStockMapper basStockMapper;
|
||||
|
||||
@Autowired
|
||||
private IBasStationPointService basStationPointService;
|
||||
|
||||
@Autowired
|
||||
private IMydoJobService mydoJobService;
|
||||
|
||||
@Autowired
|
||||
private IMydoJobDetailService mydoJobDetailService;
|
||||
|
||||
@Autowired
|
||||
private JobIdService jobIdService;
|
||||
|
||||
@Autowired
|
||||
private TaskNoService taskNoService;
|
||||
|
||||
@Override
|
||||
@WarehouseScope(warehouseField = "bcs.warehouse_id")
|
||||
public List<LocationOutboundStock> selectLocationOutboundStockList(LocationOutboundStock query)
|
||||
{
|
||||
List<LocationOutboundStock> rows = locationOutboundMapper.selectLocationOutboundStockList(query);
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
return rows;
|
||||
}
|
||||
List<String> stockIds = rows.stream()
|
||||
.map(LocationOutboundStock::getStockId)
|
||||
.filter(StringUtils::isNotEmpty)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
if (stockIds.isEmpty()) {
|
||||
return rows;
|
||||
}
|
||||
List<BasStockDetail> details = locationOutboundMapper.selectLocationOutboundDetailsByStockIds(stockIds.toArray(new String[0]));
|
||||
Map<String, List<BasStockDetail>> detailMap = details == null ? new HashMap<>() :
|
||||
details.stream().collect(Collectors.groupingBy(BasStockDetail::getStockId));
|
||||
for (LocationOutboundStock row : rows) {
|
||||
if (StringUtils.isNotEmpty(row.getStockId())) {
|
||||
row.setDetails(detailMap.getOrDefault(row.getStockId(), new ArrayList<>()));
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String generateWholeTrayOutboundJob(LocationOutboundGenerateRequest request)
|
||||
{
|
||||
checkRequest(request);
|
||||
LocationOutboundStock stock = selectOutboundLocationForUpdate(request);
|
||||
boolean emptyLocation = StringUtils.isEmpty(stock.getStockId());
|
||||
if (emptyLocation) {
|
||||
checkEmptyLocation(stock);
|
||||
} else {
|
||||
checkStock(stock);
|
||||
}
|
||||
|
||||
BasStationPoint exitPoint = validateExitPoint(request.getExitId(), stock);
|
||||
String jobId = jobIdService.getId("CK");
|
||||
String taskNo = taskNoService.getTaskNo("20");
|
||||
MydoJob job = buildOutboundJob(jobId, taskNo, stock, exitPoint);
|
||||
if (mydoJobService.insertMydoJob(job) <= 0) {
|
||||
throw new RuntimeException("指定货位出库作业生成失败");
|
||||
}
|
||||
|
||||
if (emptyLocation) {
|
||||
if (basCargoSpaceMapper.markOutboundAssignedByCargoSpaceId(stock.getCargoSpaceId()) <= 0) {
|
||||
throw new RuntimeException("当前货位已被占用,请刷新后重试");
|
||||
}
|
||||
} else {
|
||||
List<BasStockDetail> details = locationOutboundMapper.selectAvailableDetailsForUpdate(stock.getStockId());
|
||||
if (details == null || details.isEmpty()) {
|
||||
throw new RuntimeException("当前托盘无可出库库存明细");
|
||||
}
|
||||
|
||||
int index = 1;
|
||||
BigDecimal totalQuantity = BigDecimal.ZERO;
|
||||
for (BasStockDetail detail : details) {
|
||||
BigDecimal availableQuantity = availableQuantity(detail);
|
||||
if (availableQuantity.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
continue;
|
||||
}
|
||||
String jobDetailId = jobId + "-" + index++;
|
||||
MydoJobDetail jobDetail = buildOutboundJobDetail(job, jobDetailId, stock, detail, availableQuantity);
|
||||
if (mydoJobDetailService.insertMydoJobDetail(jobDetail) <= 0) {
|
||||
throw new RuntimeException("指定货位出库作业明细生成失败");
|
||||
}
|
||||
HashMap<String, Object> reserveMap = new HashMap<>();
|
||||
reserveMap.put("stockDetailId", detail.getDetailId());
|
||||
reserveMap.put("stockId", detail.getStockId());
|
||||
reserveMap.put("jobDetailId", jobDetailId);
|
||||
reserveMap.put("quantity", availableQuantity);
|
||||
if (locationOutboundMapper.reserveStockDetailForOutbound(reserveMap) <= 0) {
|
||||
throw new RuntimeException("库存已被占用,请刷新后重试");
|
||||
}
|
||||
totalQuantity = totalQuantity.add(availableQuantity);
|
||||
}
|
||||
if (totalQuantity.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new RuntimeException("当前托盘无可出库库存明细");
|
||||
}
|
||||
|
||||
basStockMapper.refreshBasStockSummary(stock.getStockId());
|
||||
basCargoSpaceMapper.markOutboundAssignedByStockId(stock.getStockId());
|
||||
}
|
||||
|
||||
AjaxResult pushResult = mydoJobService.pushOutJobToWcs(jobId);
|
||||
if (pushResult == null || pushResult.isError()) {
|
||||
throw new RuntimeException(pushResult == null ? "WCS出库作业下发失败" : Convert.toStr(pushResult.get(AjaxResult.MSG_TAG)));
|
||||
}
|
||||
return "指定货位出库作业生成成功:" + jobId + ";" + Convert.toStr(pushResult.get(AjaxResult.MSG_TAG));
|
||||
}
|
||||
|
||||
private void checkRequest(LocationOutboundGenerateRequest request)
|
||||
{
|
||||
if (request == null || (StringUtils.isEmpty(request.getStockId()) && StringUtils.isEmpty(request.getCargoSpaceId()))) {
|
||||
throw new RuntimeException("请选择要出库的货位");
|
||||
}
|
||||
if (StringUtils.isEmpty(request.getExitId())) {
|
||||
throw new RuntimeException("请选择出库口");
|
||||
}
|
||||
}
|
||||
|
||||
private LocationOutboundStock selectOutboundLocationForUpdate(LocationOutboundGenerateRequest request)
|
||||
{
|
||||
LocationOutboundStock stock = StringUtils.isNotEmpty(request.getStockId())
|
||||
? locationOutboundMapper.selectLocationOutboundStockForUpdate(request.getStockId())
|
||||
: locationOutboundMapper.selectLocationOutboundLocationForUpdate(request.getCargoSpaceId());
|
||||
if (stock == null || StringUtils.isEmpty(stock.getCargoSpaceId())) {
|
||||
throw new RuntimeException("货位不存在或已出库");
|
||||
}
|
||||
return stock;
|
||||
}
|
||||
|
||||
private void checkStock(LocationOutboundStock stock)
|
||||
{
|
||||
if (stock == null || StringUtils.isEmpty(stock.getStockId())) {
|
||||
throw new RuntimeException("库存不存在或已出库");
|
||||
}
|
||||
if (!"2".equals(stock.getCargoSpaceStatus())) {
|
||||
if ("4".equals(stock.getCargoSpaceStatus())) {
|
||||
throw new RuntimeException("当前货位已生成出库作业");
|
||||
}
|
||||
throw new RuntimeException("当前货位不是库内有库存状态");
|
||||
}
|
||||
if (stock.getAvailableQuantity() == null || stock.getAvailableQuantity().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new RuntimeException("当前托盘无可出库库存");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkEmptyLocation(LocationOutboundStock stock)
|
||||
{
|
||||
if (stock == null || StringUtils.isEmpty(stock.getCargoSpaceId())) {
|
||||
throw new RuntimeException("货位不存在");
|
||||
}
|
||||
String status = stock.getCargoSpaceStatus();
|
||||
if ("4".equals(status)) {
|
||||
throw new RuntimeException("当前货位已生成出库作业");
|
||||
}
|
||||
if (!"1".equals(status) && !"6".equals(status)) {
|
||||
throw new RuntimeException("当前货位不是可生成作业的空货位");
|
||||
}
|
||||
}
|
||||
|
||||
private BasStationPoint validateExitPoint(String exitId, LocationOutboundStock stock)
|
||||
{
|
||||
BasStationPoint point = basStationPointService.selectBasStationPointByPointId(exitId);
|
||||
if (point == null) {
|
||||
throw new RuntimeException("出库口不存在");
|
||||
}
|
||||
if (StringUtils.isEmpty(stock.getWarehouseId()) || !stock.getWarehouseId().equals(point.getWarehouseId())) {
|
||||
throw new RuntimeException("出库口不属于当前仓库");
|
||||
}
|
||||
if (!"2".equals(point.getPointType()) && !"3".equals(point.getPointType())) {
|
||||
throw new RuntimeException("请选择出库或入出库点位");
|
||||
}
|
||||
if (!"0".equals(point.getStatus())) {
|
||||
throw new RuntimeException("出库口已禁用");
|
||||
}
|
||||
String pointStatus = StringUtils.isEmpty(point.getPointStatus()) ? "0" : point.getPointStatus();
|
||||
if (!"0".equals(pointStatus)) {
|
||||
throw new RuntimeException("出库口已被分配");
|
||||
}
|
||||
if (StringUtils.isNotEmpty(stock.getTrayType()) && StringUtils.isNotEmpty(point.getTrayType())
|
||||
&& !supportsTrayType(point.getTrayType(), stock.getTrayType())) {
|
||||
throw new RuntimeException("出库口不支持当前托盘类型");
|
||||
}
|
||||
return point;
|
||||
}
|
||||
|
||||
private boolean supportsTrayType(String pointTrayTypes, String trayType)
|
||||
{
|
||||
String[] values = pointTrayTypes.split(",");
|
||||
for (String value : values) {
|
||||
if (trayType.equalsIgnoreCase(value.trim())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private MydoJob buildOutboundJob(String jobId, String taskNo, LocationOutboundStock stock, BasStationPoint exitPoint)
|
||||
{
|
||||
MydoJob job = new MydoJob();
|
||||
job.setJobId(jobId);
|
||||
job.setTaskNo(StringUtils.isNotEmpty(taskNo) ? taskNo : jobId);
|
||||
job.setInOrOut(20L);
|
||||
job.setStatus("1");
|
||||
job.setExecutionPhase("4");
|
||||
job.setTrayCode(stock.getTrayCode());
|
||||
job.setWarehouseId(stock.getWarehouseId());
|
||||
job.setWarehouseName(stock.getWarehouseName());
|
||||
job.setRoadwayId(stock.getRoadwayId());
|
||||
job.setCargoSpaceId(stock.getCargoSpaceId());
|
||||
job.setCargoSpaceName(stock.getCargoSpaceName());
|
||||
job.setLineType("1");
|
||||
job.setPriority(10L);
|
||||
job.setHeight(BigDecimal.ZERO);
|
||||
job.setWeight(BigDecimal.ZERO);
|
||||
job.setExitId(exitPoint.getPointId());
|
||||
copyLoginUser(job);
|
||||
return job;
|
||||
}
|
||||
|
||||
private MydoJobDetail buildOutboundJobDetail(MydoJob job, String jobDetailId, LocationOutboundStock stock,
|
||||
BasStockDetail detail, BigDecimal availableQuantity)
|
||||
{
|
||||
MydoJobDetail jobDetail = new MydoJobDetail();
|
||||
jobDetail.setJobDetailId(jobDetailId);
|
||||
jobDetail.setJobId(job.getJobId());
|
||||
jobDetail.setStockId(detail.getStockId());
|
||||
jobDetail.setLotNo(detail.getLotNo());
|
||||
jobDetail.setInDate(DateUtils.getTime());
|
||||
jobDetail.setProductId(detail.getProductId());
|
||||
jobDetail.setProductName(detail.getProductName());
|
||||
jobDetail.setStockQuantity(availableQuantity);
|
||||
jobDetail.setOutQuantity(availableQuantity);
|
||||
jobDetail.setStockinDetailId(detail.getDetailId());
|
||||
jobDetail.setSupplierId(stock.getSupplierId());
|
||||
jobDetail.setSupplierName(stock.getSupplierName());
|
||||
return jobDetail;
|
||||
}
|
||||
|
||||
private BigDecimal availableQuantity(BasStockDetail detail)
|
||||
{
|
||||
if (detail.getAvailableQuantity() != null) {
|
||||
return detail.getAvailableQuantity();
|
||||
}
|
||||
BigDecimal quantity = detail.getQuantity() == null ? BigDecimal.ZERO : detail.getQuantity();
|
||||
BigDecimal outQuantity = detail.getOutQuantity() == null ? BigDecimal.ZERO : detail.getOutQuantity();
|
||||
return quantity.subtract(outQuantity);
|
||||
}
|
||||
|
||||
private void copyLoginUser(MydoJob job)
|
||||
{
|
||||
try {
|
||||
job.setUserId(SecurityUtils.getUserId());
|
||||
job.setDeptId(SecurityUtils.getDeptId());
|
||||
job.setUserName(SecurityUtils.getUsername());
|
||||
} catch (RuntimeException ignored) {
|
||||
// External callbacks and focused unit tests can run without login context.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import java.util.List;
|
||||
|
||||
import com.ruoyi.common.core.text.Convert;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.out.stockout.domain.OutStockoutAssignDTO;
|
||||
import com.ruoyi.out.stockout.domain.OutStockoutManualAssignDTO;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@@ -15,6 +16,7 @@ import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.annotation.WarehouseScope;
|
||||
@@ -50,10 +52,10 @@ public class OutStockoutController extends BaseController
|
||||
{
|
||||
startPage();
|
||||
List<OutStockout> list;
|
||||
if (StringUtils.isEmpty(outStockout.getStatus()) || Convert.toInt(outStockout.getStatus()) < 4){
|
||||
list = outStockoutService.selectOutStockoutList(outStockout);
|
||||
}else {
|
||||
if (isHistoryQuery(outStockout) || (StringUtils.isNotEmpty(outStockout.getStatus()) && Convert.toInt(outStockout.getStatus()) >= 4)){
|
||||
list = outStockoutService.selectOutStockoutFinishList(outStockout);
|
||||
}else {
|
||||
list = outStockoutService.selectOutStockoutList(outStockout);
|
||||
}
|
||||
return getDataTable(list);
|
||||
}
|
||||
@@ -66,11 +68,23 @@ public class OutStockoutController extends BaseController
|
||||
@PostMapping("/export")
|
||||
public void export(HttpServletResponse response, OutStockout outStockout)
|
||||
{
|
||||
List<OutStockout> list = outStockoutService.selectOutStockoutList(outStockout);
|
||||
List<OutStockout> list;
|
||||
if (isHistoryQuery(outStockout) || (StringUtils.isNotEmpty(outStockout.getStatus()) && Convert.toInt(outStockout.getStatus()) >= 4)) {
|
||||
list = outStockoutService.selectOutStockoutFinishList(outStockout);
|
||||
} else {
|
||||
list = outStockoutService.selectOutStockoutList(outStockout);
|
||||
}
|
||||
ExcelUtil<OutStockout> util = new ExcelUtil<OutStockout>(OutStockout.class);
|
||||
util.exportExcel(response, list, "出库单据主信息数据");
|
||||
}
|
||||
|
||||
private boolean isHistoryQuery(OutStockout outStockout)
|
||||
{
|
||||
return outStockout != null
|
||||
&& outStockout.getParams() != null
|
||||
&& "true".equalsIgnoreCase(Convert.toStr(outStockout.getParams().get("history")));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取出库单据主信息详细信息
|
||||
*/
|
||||
@@ -153,9 +167,17 @@ public class OutStockoutController extends BaseController
|
||||
@PreAuthorize("@ss.hasPermi('out:stockout:autoAssign')")
|
||||
@Log(title = "出库单据主信息", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/autoAssign/{stockoutId}")
|
||||
public AjaxResult autoAssign(@PathVariable("stockoutId") String stockoutId)
|
||||
public AjaxResult autoAssign(@PathVariable("stockoutId") String stockoutId,
|
||||
@RequestBody(required = false) OutStockoutAssignDTO assignDTO,
|
||||
@RequestParam(value = "exitId", required = false) String exitId)
|
||||
{
|
||||
return outStockoutService.autoAssignOutStockout(stockoutId);
|
||||
if (assignDTO == null) {
|
||||
assignDTO = new OutStockoutAssignDTO();
|
||||
}
|
||||
if (StringUtils.isNotEmpty(exitId)) {
|
||||
assignDTO.setExitId(exitId);
|
||||
}
|
||||
return outStockoutService.autoAssignOutStockout(stockoutId, assignDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.ruoyi.out.stockout.domain;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class OutStockoutAssignDTO {
|
||||
private String exitId;
|
||||
private Map<String, String> exitByTrayType;
|
||||
|
||||
public String getExitId() {
|
||||
return exitId;
|
||||
}
|
||||
|
||||
public void setExitId(String exitId) {
|
||||
this.exitId = exitId;
|
||||
}
|
||||
|
||||
public Map<String, String> getExitByTrayType() {
|
||||
return exitByTrayType;
|
||||
}
|
||||
|
||||
public void setExitByTrayType(Map<String, String> exitByTrayType) {
|
||||
this.exitByTrayType = exitByTrayType;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.ruoyi.out.stockout.domain;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class OutStockoutManualAssignDTO {
|
||||
private String stockoutDetailId;
|
||||
private String exitId;
|
||||
private Map<String, String> exitByTrayType;
|
||||
private Map<String, String> exitByStockId;
|
||||
private List<String> stockIdList;
|
||||
|
||||
public String getStockoutDetailId() {
|
||||
@@ -14,6 +18,30 @@ public class OutStockoutManualAssignDTO {
|
||||
this.stockoutDetailId = stockoutDetailId;
|
||||
}
|
||||
|
||||
public String getExitId() {
|
||||
return exitId;
|
||||
}
|
||||
|
||||
public void setExitId(String exitId) {
|
||||
this.exitId = exitId;
|
||||
}
|
||||
|
||||
public Map<String, String> getExitByTrayType() {
|
||||
return exitByTrayType;
|
||||
}
|
||||
|
||||
public void setExitByTrayType(Map<String, String> exitByTrayType) {
|
||||
this.exitByTrayType = exitByTrayType;
|
||||
}
|
||||
|
||||
public Map<String, String> getExitByStockId() {
|
||||
return exitByStockId;
|
||||
}
|
||||
|
||||
public void setExitByStockId(Map<String, String> exitByStockId) {
|
||||
this.exitByStockId = exitByStockId;
|
||||
}
|
||||
|
||||
public List<String> getStockIdList() {
|
||||
return stockIdList;
|
||||
}
|
||||
|
||||
@@ -82,4 +82,12 @@ public interface OutStockoutMapper
|
||||
* @return
|
||||
*/
|
||||
List<OutStockout> selectOutStockoutFinishList(OutStockout outStockout);
|
||||
|
||||
/**
|
||||
* 取货无货异常作废最后一个活动作业后,将出库主单退回已审核状态。
|
||||
*
|
||||
* @param jobId 作业ID
|
||||
* @return 结果
|
||||
*/
|
||||
int reopenOutStockoutByExceptionJobId(String jobId);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.out.stockout.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.out.stockout.domain.OutStockoutAssignDTO;
|
||||
import com.ruoyi.out.stockout.domain.OutStockout;
|
||||
import com.ruoyi.out.stockout.domain.OutStockoutManualAssignDTO;
|
||||
import com.ruoyi.out.stockout.domain.OutStockoutSaveDTO;
|
||||
@@ -90,9 +91,19 @@ public interface IOutStockoutService
|
||||
* 自动分配出库单据
|
||||
*
|
||||
* @param stockoutId 出库单据主键
|
||||
* @param exitId 出库口点位
|
||||
* @return 结果
|
||||
*/
|
||||
public AjaxResult autoAssignOutStockout(String stockoutId);
|
||||
public AjaxResult autoAssignOutStockout(String stockoutId, String exitId);
|
||||
|
||||
/**
|
||||
* 自动分配出库单据
|
||||
*
|
||||
* @param stockoutId 出库单据主键
|
||||
* @param assignDTO 出库口分配信息
|
||||
* @return 结果
|
||||
*/
|
||||
public AjaxResult autoAssignOutStockout(String stockoutId, OutStockoutAssignDTO assignDTO);
|
||||
|
||||
/**
|
||||
* 手动分配出库单据
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package com.ruoyi.out.stockout.service.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.ruoyi.common.annotation.WarehouseScope;
|
||||
import com.ruoyi.common.core.text.Convert;
|
||||
import com.ruoyi.bas.cargospace.mapper.BasCargoSpaceMapper;
|
||||
import com.ruoyi.in.job.domain.MydoJob;
|
||||
import com.ruoyi.in.job.service.IMydoJobService;
|
||||
import com.ruoyi.out.stockout.domain.OutStockoutAssignDTO;
|
||||
import com.ruoyi.out.stockout.domain.OutStockoutManualAssignDTO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -17,6 +21,7 @@ import com.ruoyi.bas.id.job.service.JobIdService;
|
||||
import com.ruoyi.common.OutStockoutStatusEnum;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.system.mapper.SysDeptMapper;
|
||||
import com.ruoyi.out.stockout.mapper.OutStockoutMapper;
|
||||
import com.ruoyi.out.stockout.domain.OutStockout;
|
||||
@@ -49,6 +54,9 @@ public class OutStockoutServiceImpl implements IOutStockoutService
|
||||
@Autowired
|
||||
private IMydoJobService mydoJobService;
|
||||
|
||||
@Autowired
|
||||
private BasCargoSpaceMapper basCargoSpaceMapper;
|
||||
|
||||
/**
|
||||
* 查询出库单据主信息
|
||||
*/
|
||||
@@ -159,16 +167,47 @@ public class OutStockoutServiceImpl implements IOutStockoutService
|
||||
* 自动分配出库单据
|
||||
*/
|
||||
@Override
|
||||
public AjaxResult autoAssignOutStockout(String stockoutId)
|
||||
public AjaxResult autoAssignOutStockout(String stockoutId, String exitId)
|
||||
{
|
||||
OutStockoutAssignDTO assignDTO = new OutStockoutAssignDTO();
|
||||
assignDTO.setExitId(exitId);
|
||||
return autoAssignOutStockout(stockoutId, assignDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动分配出库单据
|
||||
*/
|
||||
@Override
|
||||
public AjaxResult autoAssignOutStockout(String stockoutId, OutStockoutAssignDTO assignDTO)
|
||||
{
|
||||
if (assignDTO == null) {
|
||||
assignDTO = new OutStockoutAssignDTO();
|
||||
}
|
||||
StringBuilder str = new StringBuilder();
|
||||
List<OutStockoutDetail> details = outStockoutDetailMapper.selectOutStockoutDetailListByStockoutId(stockoutId);
|
||||
if (details == null || details.isEmpty()) {
|
||||
return AjaxResult.error("出库单据无明细");
|
||||
}
|
||||
List<String> allocatedDetailIds = new ArrayList<>();
|
||||
for (OutStockoutDetail detail : details) {
|
||||
String exitId = resolveExitId(assignDTO.getExitId(), assignDTO.getExitByTrayType(), detail.getTrayType());
|
||||
if (StringUtils.isEmpty(exitId)) {
|
||||
return AjaxResult.error(buildMissingExitMessage(detail.getTrayType()));
|
||||
}
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
map.put("v_stockout_detail_id", detail.getStockoutDetailId());
|
||||
map.put("v_exit_id", exitId);
|
||||
outStockoutMapper.autoAssignOutStockoutByProc(map);
|
||||
str.append(detail.getStockoutDetailId()).append(Convert.toStr(map.get("v_resultString"))).append("|");
|
||||
AjaxResult pushResult = pushPendingOutJobs(detail.getStockoutDetailId());
|
||||
String resultString = Convert.toStr(map.get("v_resultString"));
|
||||
str.append(detail.getStockoutDetailId()).append(resultString).append("|");
|
||||
if (!isProcedureSuccess(map)) {
|
||||
return AjaxResult.error(detail.getStockoutDetailId() + resultString);
|
||||
}
|
||||
basCargoSpaceMapper.markOutboundAssignedByStockoutDetailId(detail.getStockoutDetailId());
|
||||
allocatedDetailIds.add(detail.getStockoutDetailId());
|
||||
}
|
||||
for (String detailId : allocatedDetailIds) {
|
||||
AjaxResult pushResult = pushPendingOutJobs(detailId);
|
||||
if (pushResult.isError()) {
|
||||
return pushResult;
|
||||
}
|
||||
@@ -182,14 +221,35 @@ public class OutStockoutServiceImpl implements IOutStockoutService
|
||||
@Override
|
||||
public AjaxResult manualAssignOutStockout(OutStockoutManualAssignDTO outStockoutManualAssignDTO)
|
||||
{
|
||||
if (outStockoutManualAssignDTO == null) {
|
||||
return AjaxResult.error("请选择出库口");
|
||||
}
|
||||
if (outStockoutManualAssignDTO.getStockIdList() == null || outStockoutManualAssignDTO.getStockIdList().isEmpty()) {
|
||||
return AjaxResult.error("请选择要分配的库存");
|
||||
}
|
||||
StringBuilder str = new StringBuilder();
|
||||
List<String> allocatedDetailIds = new ArrayList<>();
|
||||
for (String stockId : outStockoutManualAssignDTO.getStockIdList()) {
|
||||
String exitId = resolveManualExitId(outStockoutManualAssignDTO, stockId);
|
||||
if (StringUtils.isEmpty(exitId)) {
|
||||
return AjaxResult.error("请选择出库口");
|
||||
}
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
map.put("v_stockout_detail_id", outStockoutManualAssignDTO.getStockoutDetailId());
|
||||
map.put("v_stock_id", stockId);
|
||||
map.put("v_exit_id", exitId);
|
||||
outStockoutMapper.manualAssignOutStockoutByProc(map);
|
||||
str.append(outStockoutManualAssignDTO.getStockoutDetailId()).append("|").append(stockId).append(Convert.toStr(map.get("v_resultString"))).append("|");
|
||||
AjaxResult pushResult = pushPendingOutJobs(outStockoutManualAssignDTO.getStockoutDetailId());
|
||||
String resultString = Convert.toStr(map.get("v_resultString"));
|
||||
str.append(outStockoutManualAssignDTO.getStockoutDetailId()).append("|").append(stockId).append(resultString).append("|");
|
||||
if (!isProcedureSuccess(map)) {
|
||||
return AjaxResult.error(outStockoutManualAssignDTO.getStockoutDetailId() + "|" + stockId + resultString);
|
||||
}
|
||||
basCargoSpaceMapper.markOutboundAssignedByStockRef(stockId);
|
||||
basCargoSpaceMapper.markOutboundAssignedByStockoutDetailId(outStockoutManualAssignDTO.getStockoutDetailId());
|
||||
allocatedDetailIds.add(outStockoutManualAssignDTO.getStockoutDetailId());
|
||||
}
|
||||
for (String detailId : allocatedDetailIds) {
|
||||
AjaxResult pushResult = pushPendingOutJobs(detailId);
|
||||
if (pushResult.isError()) {
|
||||
return pushResult;
|
||||
}
|
||||
@@ -235,6 +295,38 @@ public class OutStockoutServiceImpl implements IOutStockoutService
|
||||
return saveDTO.getStockoutId();
|
||||
}
|
||||
|
||||
private boolean isProcedureSuccess(Map<String, Object> map)
|
||||
{
|
||||
return "10".equals(Convert.toStr(map.get("v_result")));
|
||||
}
|
||||
|
||||
private String resolveManualExitId(OutStockoutManualAssignDTO assignDTO, String stockId)
|
||||
{
|
||||
if (assignDTO.getExitByStockId() != null && assignDTO.getExitByStockId().containsKey(stockId)) {
|
||||
return assignDTO.getExitByStockId().get(stockId);
|
||||
}
|
||||
return resolveExitId(assignDTO.getExitId(), assignDTO.getExitByTrayType(), null);
|
||||
}
|
||||
|
||||
private String resolveExitId(String defaultExitId, Map<String, String> exitByTrayType, String trayType)
|
||||
{
|
||||
if (StringUtils.isNotEmpty(trayType) && exitByTrayType != null && exitByTrayType.containsKey(trayType)) {
|
||||
return exitByTrayType.get(trayType);
|
||||
}
|
||||
if (StringUtils.isEmpty(defaultExitId) && exitByTrayType != null && exitByTrayType.size() == 1) {
|
||||
return exitByTrayType.values().iterator().next();
|
||||
}
|
||||
return defaultExitId;
|
||||
}
|
||||
|
||||
private String buildMissingExitMessage(String trayType)
|
||||
{
|
||||
if (StringUtils.isNotEmpty(trayType)) {
|
||||
return "请选择" + trayType + "类型托盘的出库口";
|
||||
}
|
||||
return "请选择出库口";
|
||||
}
|
||||
|
||||
@Override
|
||||
@WarehouseScope
|
||||
public List<OutStockout> selectOutStockoutFinishList(OutStockout outStockout) {
|
||||
|
||||
@@ -101,6 +101,9 @@ public class OutStockoutDetail extends BaseEntity
|
||||
/** 分配库存明细ID */
|
||||
private String stockDetailId;
|
||||
|
||||
/** 托盘类型 */
|
||||
private String trayType;
|
||||
|
||||
public void setStatus(String status)
|
||||
{
|
||||
this.status = status;
|
||||
@@ -131,6 +134,16 @@ public class OutStockoutDetail extends BaseEntity
|
||||
this.stockDetailId = stockDetailId;
|
||||
}
|
||||
|
||||
public String getTrayType()
|
||||
{
|
||||
return trayType;
|
||||
}
|
||||
|
||||
public void setTrayType(String trayType)
|
||||
{
|
||||
this.trayType = trayType;
|
||||
}
|
||||
|
||||
public void setStockoutDetailId(String stockoutDetailId)
|
||||
{
|
||||
this.stockoutDetailId = stockoutDetailId;
|
||||
@@ -345,6 +358,7 @@ public class OutStockoutDetail extends BaseEntity
|
||||
.append("remar", getRemar())
|
||||
.append("stockId", getStockId())
|
||||
.append("stockDetailId", getStockDetailId())
|
||||
.append("trayType", getTrayType())
|
||||
.append("remark", getRemark())
|
||||
.toString();
|
||||
}
|
||||
|
||||
@@ -72,4 +72,12 @@ public interface OutStockoutDetailMapper
|
||||
* @return
|
||||
*/
|
||||
List<OutStockoutDetail> selectOutStockoutDetailFinishList(OutStockoutDetail outStockoutDetail);
|
||||
|
||||
/**
|
||||
* 按取货无货作业回滚出库子单分配数量。
|
||||
*
|
||||
* @param jobId 作业ID
|
||||
* @return 结果
|
||||
*/
|
||||
int rollbackAllotByJobId(String jobId);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.ruoyi.in.stockin.domain.InStockin;
|
||||
import com.ruoyi.in.sotckindetail.domain.InStockinDetail;
|
||||
import com.ruoyi.pda.domain.AddInJobRequest;
|
||||
import com.ruoyi.pda.domain.BindTrayRequest;
|
||||
import com.ruoyi.pda.domain.EmptyTrayOutboundRequest;
|
||||
import com.ruoyi.pda.service.PdaService;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -151,6 +152,21 @@ public class PdaController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PDA 空托入库:选择仓库、扫描托盘号、选择入库点位后直接生成空托入库作业。
|
||||
*/
|
||||
@PostMapping("/emptyTrayInbound")
|
||||
@PreAuthorize("@ss.hasPermi('in:job:add')")
|
||||
@Log(title = "PDA空托入库并生成作业", businessType = BusinessType.INSERT)
|
||||
public AjaxResult emptyTrayInbound(@Valid @RequestBody AddInJobRequest request) {
|
||||
try {
|
||||
String res = pdaService.addEmptyTrayInboundJob(request);
|
||||
return AjaxResult.success(res);
|
||||
} catch (RuntimeException e) {
|
||||
return AjaxResult.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 入库绑盘-查询物料信息
|
||||
* @param detailId
|
||||
@@ -176,6 +192,47 @@ public class PdaController extends BaseController {
|
||||
return AjaxResult.success(pdaService.outboundTaskList(query));
|
||||
}
|
||||
|
||||
/**
|
||||
* PDA 查询当前仓库库内空托按条码头分组的数量。
|
||||
*/
|
||||
@GetMapping("/emptyTrayOutbound/count")
|
||||
@PreAuthorize("@ss.hasPermi('out:job:list')")
|
||||
public AjaxResult emptyTrayOutboundCount(String warehouseId) {
|
||||
try {
|
||||
return AjaxResult.success(pdaService.emptyTrayOutboundCount(warehouseId));
|
||||
} catch (RuntimeException e) {
|
||||
return AjaxResult.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PDA 库存查询:扫描托盘码后查看托盘绑定的库存物料。
|
||||
*/
|
||||
@GetMapping("/trayStock")
|
||||
@PreAuthorize("@ss.hasPermi('bas:stock:list')")
|
||||
public AjaxResult trayStock(String trayCode) {
|
||||
try {
|
||||
return AjaxResult.success(pdaService.trayStock(trayCode));
|
||||
} catch (RuntimeException e) {
|
||||
return AjaxResult.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PDA 空托出库:按仓库、条码头类型和数量生成空托出库作业并下发 WCS。
|
||||
*/
|
||||
@PostMapping("/emptyTrayOutbound")
|
||||
@PreAuthorize("@ss.hasPermi('out:job:list')")
|
||||
@Log(title = "PDA空托出库并下发WCS", businessType = BusinessType.INSERT)
|
||||
public AjaxResult emptyTrayOutbound(@Valid @RequestBody EmptyTrayOutboundRequest request) {
|
||||
try {
|
||||
String res = pdaService.addEmptyTrayOutboundJob(request);
|
||||
return AjaxResult.success(res);
|
||||
} catch (RuntimeException e) {
|
||||
return AjaxResult.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PDA 扫码查询出库任务明细。
|
||||
*/
|
||||
@@ -204,19 +261,4 @@ public class PdaController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PDA 出库异常提交。
|
||||
*/
|
||||
@PostMapping("/outboundException")
|
||||
@PreAuthorize("@ss.hasPermi('out:job:complete')")
|
||||
@Log(title = "PDA出库异常", businessType = BusinessType.UPDATE)
|
||||
public AjaxResult outboundException(@RequestBody Map<String, Object> data) {
|
||||
try {
|
||||
pdaService.submitOutboundException(data);
|
||||
return AjaxResult.success();
|
||||
} catch (RuntimeException e) {
|
||||
return AjaxResult.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.ruoyi.pda.domain;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
public class EmptyTrayOutboundRequest {
|
||||
|
||||
@NotNull
|
||||
private String warehouseId;
|
||||
|
||||
@NotBlank
|
||||
private String barcodePrefix;
|
||||
|
||||
@NotNull
|
||||
private Integer quantity;
|
||||
|
||||
public String getWarehouseId() {
|
||||
return warehouseId;
|
||||
}
|
||||
|
||||
public void setWarehouseId(String warehouseId) {
|
||||
this.warehouseId = warehouseId;
|
||||
}
|
||||
|
||||
public String getBarcodePrefix() {
|
||||
return barcodePrefix;
|
||||
}
|
||||
|
||||
public void setBarcodePrefix(String barcodePrefix) {
|
||||
this.barcodePrefix = barcodePrefix;
|
||||
}
|
||||
|
||||
public Integer getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(Integer quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package com.ruoyi.pda.mapper;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
public interface PdaMapper {
|
||||
void bindTray(HashMap<String, Object> map);
|
||||
|
||||
@@ -25,7 +27,7 @@ public interface PdaMapper {
|
||||
HashMap<String, Object> selectAvailableInboundRoadway(HashMap<String, Object> map);
|
||||
|
||||
/**
|
||||
* WCS 请求货位时,按已分配巷道从下到上、前到后锁定一个可入库空货位。
|
||||
* WCS 请求货位时,优先选择已分配巷道内空货位最多的排,再锁定一个可入库空货位。
|
||||
*
|
||||
* @param map 仓库、货位类型、托盘号、巷道
|
||||
* @return 锁定行数
|
||||
@@ -57,4 +59,44 @@ public interface PdaMapper {
|
||||
* PDA 扫码查询出库任务明细。
|
||||
*/
|
||||
HashMap<String, Object> selectOutboundDetail(HashMap<String, Object> map);
|
||||
|
||||
/**
|
||||
* 查询出库托盘上的作业物料明细。
|
||||
*/
|
||||
List<HashMap<String, Object>> selectOutboundTrayMaterials(HashMap<String, Object> map);
|
||||
|
||||
/**
|
||||
* 查询出库确认后托盘上仍有库存的物料。
|
||||
*/
|
||||
List<HashMap<String, Object>> selectResidualTrayMaterials(HashMap<String, Object> map);
|
||||
|
||||
/**
|
||||
* PDA 库存查询:按托盘码查询托盘绑定的库存物料。
|
||||
*/
|
||||
List<HashMap<String, Object>> selectTrayStockMaterials(@Param("trayCode") String trayCode);
|
||||
|
||||
/**
|
||||
* 按条码格式前缀查询指定仓库可出库空托数量。
|
||||
*/
|
||||
List<HashMap<String, Object>> selectAvailableEmptyTrayTypeCounts(@Param("warehouseId") String warehouseId);
|
||||
|
||||
/**
|
||||
* 查询指定仓库、指定条码格式前缀可出库空托数量。
|
||||
*/
|
||||
int countAvailableEmptyTrays(HashMap<String, Object> map);
|
||||
|
||||
/**
|
||||
* 锁定读取指定仓库可出库空托库存明细。
|
||||
*/
|
||||
List<HashMap<String, Object>> selectAvailableEmptyTrayStocks(HashMap<String, Object> map);
|
||||
|
||||
/**
|
||||
* 预占空托库存明细,避免重复生成出库作业。
|
||||
*/
|
||||
int reserveEmptyTrayStockDetail(HashMap<String, Object> map);
|
||||
|
||||
/**
|
||||
* 将空托所在货位标记为出库锁定。
|
||||
*/
|
||||
int lockEmptyTrayCargoSpace(HashMap<String, Object> map);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ import com.ruoyi.in.stockin.domain.InStockin;
|
||||
import com.ruoyi.in.sotckindetail.domain.InStockinDetail;
|
||||
import com.ruoyi.pda.domain.AddInJobRequest;
|
||||
import com.ruoyi.pda.domain.BindTrayRequest;
|
||||
import com.ruoyi.pda.domain.EmptyTrayOutboundRequest;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -71,6 +73,38 @@ public interface PdaService {
|
||||
*/
|
||||
String addInJob(AddInJobRequest request);
|
||||
|
||||
/**
|
||||
* PDA 生成空托入库作业。
|
||||
*
|
||||
* @param request 仓库、托盘号和入库点位
|
||||
* @return 作业生成和 WCS 下发结果
|
||||
*/
|
||||
String addEmptyTrayInboundJob(AddInJobRequest request);
|
||||
|
||||
/**
|
||||
* 查询指定仓库库内可出库空托按条码头分组的数量。
|
||||
*
|
||||
* @param warehouseId 仓库编码
|
||||
* @return 空托类型数量列表
|
||||
*/
|
||||
List<HashMap<String, Object>> emptyTrayOutboundCount(String warehouseId);
|
||||
|
||||
/**
|
||||
* PDA 库存查询:按托盘码查询托盘绑定的库存物料。
|
||||
*
|
||||
* @param trayCode 托盘码
|
||||
* @return 库存物料明细
|
||||
*/
|
||||
List<HashMap<String, Object>> trayStock(String trayCode);
|
||||
|
||||
/**
|
||||
* PDA 生成空托出库作业并下发 WCS。
|
||||
*
|
||||
* @param request 仓库、条码头类型和出库数量
|
||||
* @return 作业生成和 WCS 下发结果
|
||||
*/
|
||||
String addEmptyTrayOutboundJob(EmptyTrayOutboundRequest request);
|
||||
|
||||
/**
|
||||
* WCS 到达入库口请求货位时,按作业已分配巷道锁定一个具体货位。
|
||||
*
|
||||
@@ -112,10 +146,4 @@ public interface PdaService {
|
||||
*/
|
||||
void confirmOutbound(Map<String, Object> data);
|
||||
|
||||
/**
|
||||
* PDA 提交出库异常。
|
||||
*
|
||||
* @param data 异常数据
|
||||
*/
|
||||
void submitOutboundException(Map<String, Object> data);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import com.ruoyi.bas.warehouse.domain.BasWarehouse;
|
||||
import com.ruoyi.bas.warehouse.service.IBasWarehouseService;
|
||||
import com.ruoyi.api.client.WcsClient;
|
||||
import com.ruoyi.api.domain.ApiResponse;
|
||||
import com.ruoyi.bas.id.job.service.JobIdService;
|
||||
import com.ruoyi.bas.id.taskno.service.TaskNoService;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.text.Convert;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
@@ -20,6 +22,7 @@ import com.ruoyi.in.sotckindetail.domain.InStockinDetail;
|
||||
import com.ruoyi.in.sotckindetail.mapper.InStockinDetailMapper;
|
||||
import com.ruoyi.pda.domain.AddInJobRequest;
|
||||
import com.ruoyi.pda.domain.BindTrayRequest;
|
||||
import com.ruoyi.pda.domain.EmptyTrayOutboundRequest;
|
||||
import com.ruoyi.pda.mapper.PdaMapper;
|
||||
import com.ruoyi.pda.service.PdaService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -41,6 +44,13 @@ import java.util.Map;
|
||||
*/
|
||||
@Service
|
||||
public class PdaServiceImpl implements PdaService {
|
||||
private static final String EMPTY_TRAY_PRODUCT_ID = "KTP0001";
|
||||
private static final String EMPTY_TRAY_PRODUCT_NAME = "空容器";
|
||||
private static final long EMPTY_TRAY_IN_JOB_TYPE = 3L;
|
||||
private static final long EMPTY_TRAY_OUT_JOB_TYPE = 22L;
|
||||
private static final String EMPTY_TRAY_IN_TASK_NO_TYPE = "6";
|
||||
private static final String EMPTY_TRAY_OUT_TASK_NO_TYPE = "20";
|
||||
private static final BigDecimal ONE_EMPTY_TRAY = BigDecimal.ONE;
|
||||
|
||||
@Autowired
|
||||
private PdaMapper pdaMapper;
|
||||
@@ -66,6 +76,12 @@ public class PdaServiceImpl implements PdaService {
|
||||
@Autowired
|
||||
private WcsClient wcsClient;
|
||||
|
||||
@Autowired
|
||||
private JobIdService jobIdService;
|
||||
|
||||
@Autowired
|
||||
private TaskNoService taskNoService;
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// 入库待处理任务列表
|
||||
// ─────────────────────────────────────────────
|
||||
@@ -193,10 +209,18 @@ public class PdaServiceImpl implements PdaService {
|
||||
// 普通入库不使用 target_* 字段;生成作业阶段只写入口点位和作业巷道,不锁定具体货位。
|
||||
job.setEntranceId(stationPoint.getPointId());
|
||||
job.setRoadwayId(stationPoint.getRoadwayId());
|
||||
BasWarehouse warehouse = basWarehouseService.selectBasWarehouseByWarehouseId(request.getWarehouseId());
|
||||
if (warehouse != null) {
|
||||
job.setWarehouseName(warehouse.getWarehouseName());
|
||||
}
|
||||
job.setStatus("1");
|
||||
job.setExecutionPhase("0");
|
||||
// updateMydoJob 只更新非 null 字段,这里使用空字符串覆盖存储过程可能写入的作业货位。
|
||||
job.setCargoSpaceId("");
|
||||
job.setCargoSpaceName("");
|
||||
mydoJobService.updateMydoJob(job);
|
||||
if (mydoJobService.updateMydoJob(job) <= 0) {
|
||||
throw new RuntimeException("入库作业初始化状态更新失败");
|
||||
}
|
||||
|
||||
MydoJobDetail query = new MydoJobDetail();
|
||||
query.setJobId(job.getJobId());
|
||||
@@ -212,9 +236,241 @@ public class PdaServiceImpl implements PdaService {
|
||||
String message = wcsResponse == null ? "WCS接口无返回" : wcsResponse.getMessage();
|
||||
throw new RuntimeException("WCS入库作业下发失败:" + message);
|
||||
}
|
||||
markInJobDispatchedToWcs(job.getJobId());
|
||||
return resultMsg + ";WCS入库作业下发成功:" + Convert.toStr(wcsResponse.getData());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String addEmptyTrayInboundJob(AddInJobRequest request) {
|
||||
checkAddInJobRequest(request);
|
||||
BasStationPoint stationPoint = validateInboundStationPoint(request.getStationPointId(), request.getWarehouseId(), request.getTrayCode());
|
||||
ensureRoadwayHasEmptyCargoSpace(request.getTrayCode(), request.getWarehouseId(), stationPoint.getRoadwayId());
|
||||
int occupyRows = basStationPointService.occupyBasStationPoint(stationPoint.getPointId());
|
||||
if (occupyRows <= 0) {
|
||||
throw new RuntimeException("入库点位已被分配,请重新选择点位");
|
||||
}
|
||||
|
||||
String jobId = jobIdService.getId("RK");
|
||||
String taskNo = taskNoService.getTaskNo(EMPTY_TRAY_IN_TASK_NO_TYPE);
|
||||
MydoJob job = new MydoJob();
|
||||
job.setJobId(jobId);
|
||||
job.setTaskNo(StringUtils.hasText(taskNo) ? taskNo : jobId);
|
||||
job.setInOrOut(EMPTY_TRAY_IN_JOB_TYPE);
|
||||
job.setStatus("1");
|
||||
job.setExecutionPhase("0");
|
||||
job.setTrayCode(request.getTrayCode());
|
||||
job.setWarehouseId(request.getWarehouseId());
|
||||
BasWarehouse warehouse = basWarehouseService.selectBasWarehouseByWarehouseId(request.getWarehouseId());
|
||||
if (warehouse != null) {
|
||||
job.setWarehouseName(warehouse.getWarehouseName());
|
||||
}
|
||||
job.setEntranceId(stationPoint.getPointId());
|
||||
job.setRoadwayId(stationPoint.getRoadwayId());
|
||||
job.setPriority(10L);
|
||||
job.setHeight(BigDecimal.ZERO);
|
||||
job.setWeight(BigDecimal.ZERO);
|
||||
copyLoginUser(job);
|
||||
if (mydoJobService.insertMydoJob(job) <= 0) {
|
||||
throw new RuntimeException("空托入库作业生成失败");
|
||||
}
|
||||
|
||||
MydoJobDetail detail = new MydoJobDetail();
|
||||
detail.setJobDetailId(jobId + "-1");
|
||||
detail.setJobId(jobId);
|
||||
detail.setProductId(EMPTY_TRAY_PRODUCT_ID);
|
||||
detail.setProductName(EMPTY_TRAY_PRODUCT_NAME);
|
||||
detail.setInQuantity(ONE_EMPTY_TRAY);
|
||||
if (mydoJobDetailService.insertMydoJobDetail(detail) <= 0) {
|
||||
throw new RuntimeException("空托入库作业明细生成失败");
|
||||
}
|
||||
|
||||
ApiResponse<String> wcsResponse = wcsClient.sendPutawayTask(job);
|
||||
if (wcsResponse == null || !wcsResponse.isSuccess()) {
|
||||
String message = wcsResponse == null ? "WCS接口无返回" : wcsResponse.getMessage();
|
||||
throw new RuntimeException("WCS入库作业下发失败:" + message);
|
||||
}
|
||||
markInJobDispatchedToWcs(job.getJobId());
|
||||
return "空托入库作业生成成功:" + job.getJobId() + ";WCS入库作业下发成功:" + Convert.toStr(wcsResponse.getData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HashMap<String, Object>> emptyTrayOutboundCount(String warehouseId) {
|
||||
if (!StringUtils.hasText(warehouseId)) {
|
||||
throw new RuntimeException("仓库编码不能为空");
|
||||
}
|
||||
return pdaMapper.selectAvailableEmptyTrayTypeCounts(warehouseId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HashMap<String, Object>> trayStock(String trayCode) {
|
||||
if (!StringUtils.hasText(trayCode)) {
|
||||
throw new RuntimeException("托盘码不能为空");
|
||||
}
|
||||
return pdaMapper.selectTrayStockMaterials(trayCode.trim());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String addEmptyTrayOutboundJob(EmptyTrayOutboundRequest request) {
|
||||
checkEmptyTrayOutboundRequest(request);
|
||||
int quantity = request.getQuantity();
|
||||
HashMap<String, Object> countQuery = new HashMap<>();
|
||||
countQuery.put("warehouseId", request.getWarehouseId());
|
||||
countQuery.put("barcodePrefix", request.getBarcodePrefix());
|
||||
int availableCount = pdaMapper.countAvailableEmptyTrays(countQuery);
|
||||
if (availableCount < quantity) {
|
||||
throw new RuntimeException("库内空托盘数量不足,当前可用:" + availableCount);
|
||||
}
|
||||
|
||||
BasStationPoint exitPoint = selectDefaultOutboundStationPoint(request.getWarehouseId());
|
||||
HashMap<String, Object> stockQuery = new HashMap<>();
|
||||
stockQuery.put("warehouseId", request.getWarehouseId());
|
||||
stockQuery.put("barcodePrefix", request.getBarcodePrefix());
|
||||
stockQuery.put("limit", quantity);
|
||||
List<HashMap<String, Object>> emptyTrayStocks = pdaMapper.selectAvailableEmptyTrayStocks(stockQuery);
|
||||
if (emptyTrayStocks == null || emptyTrayStocks.size() < quantity) {
|
||||
throw new RuntimeException("库内空托盘数量不足,请刷新后重试");
|
||||
}
|
||||
|
||||
BasWarehouse warehouse = basWarehouseService.selectBasWarehouseByWarehouseId(request.getWarehouseId());
|
||||
StringBuilder result = new StringBuilder("空托出库作业生成成功:");
|
||||
for (int i = 0; i < quantity; i++) {
|
||||
HashMap<String, Object> stock = emptyTrayStocks.get(i);
|
||||
MydoJob job = buildEmptyTrayOutboundJob(request.getWarehouseId(), warehouse, exitPoint, stock);
|
||||
if (mydoJobService.insertMydoJob(job) <= 0) {
|
||||
throw new RuntimeException("空托出库作业生成失败");
|
||||
}
|
||||
|
||||
MydoJobDetail detail = buildEmptyTrayOutboundDetail(job, stock);
|
||||
if (mydoJobDetailService.insertMydoJobDetail(detail) <= 0) {
|
||||
throw new RuntimeException("空托出库作业明细生成失败");
|
||||
}
|
||||
|
||||
HashMap<String, Object> reserveMap = new HashMap<>();
|
||||
reserveMap.put("stockDetailId", detail.getStockinDetailId());
|
||||
reserveMap.put("stockId", detail.getStockId());
|
||||
reserveMap.put("jobDetailId", detail.getJobDetailId());
|
||||
reserveMap.put("quantity", detail.getOutQuantity());
|
||||
if (pdaMapper.reserveEmptyTrayStockDetail(reserveMap) <= 0) {
|
||||
throw new RuntimeException("空托库存已被占用,请刷新后重试");
|
||||
}
|
||||
HashMap<String, Object> cargoMap = new HashMap<>();
|
||||
cargoMap.put("stockId", detail.getStockId());
|
||||
cargoMap.put("cargoSpaceId", job.getCargoSpaceId());
|
||||
pdaMapper.lockEmptyTrayCargoSpace(cargoMap);
|
||||
|
||||
AjaxResult pushResult = mydoJobService.pushOutJobToWcs(job.getJobId());
|
||||
if (pushResult == null || pushResult.isError()) {
|
||||
throw new RuntimeException(pushResult == null ? "WCS出库作业下发失败" : Convert.toStr(pushResult.get(AjaxResult.MSG_TAG)));
|
||||
}
|
||||
result.append(job.getJobId()).append("|");
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private void markInJobDispatchedToWcs(String jobId) {
|
||||
MydoJob updateJob = new MydoJob();
|
||||
updateJob.setJobId(jobId);
|
||||
updateJob.setStatus("3");
|
||||
updateJob.setExecutionPhase("1");
|
||||
updateJob.setStartTime(DateUtils.getTime());
|
||||
if (mydoJobService.updateMydoJob(updateJob) <= 0) {
|
||||
throw new RuntimeException("入库作业WCS下发状态更新失败");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkEmptyTrayOutboundRequest(EmptyTrayOutboundRequest request) {
|
||||
if (request == null || !StringUtils.hasText(request.getWarehouseId())) {
|
||||
throw new RuntimeException("仓库编码不能为空");
|
||||
}
|
||||
if (request.getQuantity() == null || request.getQuantity() <= 0) {
|
||||
throw new RuntimeException("出库数量必须大于0");
|
||||
}
|
||||
if (!StringUtils.hasText(request.getBarcodePrefix())) {
|
||||
throw new RuntimeException("空托盘类型不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
private BasStationPoint selectDefaultOutboundStationPoint(String warehouseId) {
|
||||
BasStationPoint query = new BasStationPoint();
|
||||
query.setWarehouseId(warehouseId);
|
||||
query.setStatus("0");
|
||||
List<BasStationPoint> pointList = basStationPointService.selectBasStationPointList(query);
|
||||
if (pointList != null) {
|
||||
for (BasStationPoint point : pointList) {
|
||||
if (point != null && ("2".equals(point.getPointType()) || "3".equals(point.getPointType()))) {
|
||||
return point;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("当前仓库未配置可用出库点位");
|
||||
}
|
||||
|
||||
private MydoJob buildEmptyTrayOutboundJob(String warehouseId, BasWarehouse warehouse, BasStationPoint exitPoint, HashMap<String, Object> stock) {
|
||||
String jobId = jobIdService.getId("CK");
|
||||
String taskNo = taskNoService.getTaskNo(EMPTY_TRAY_OUT_TASK_NO_TYPE);
|
||||
MydoJob job = new MydoJob();
|
||||
job.setJobId(jobId);
|
||||
job.setTaskNo(StringUtils.hasText(taskNo) ? taskNo : jobId);
|
||||
job.setInOrOut(EMPTY_TRAY_OUT_JOB_TYPE);
|
||||
job.setStatus("1");
|
||||
job.setExecutionPhase("0");
|
||||
job.setTrayCode(mapString(stock, "trayCode"));
|
||||
job.setWarehouseId(warehouseId);
|
||||
String warehouseName = mapString(stock, "warehouseName");
|
||||
if (!StringUtils.hasText(warehouseName) && warehouse != null) {
|
||||
warehouseName = warehouse.getWarehouseName();
|
||||
}
|
||||
job.setWarehouseName(warehouseName);
|
||||
job.setRoadwayId(mapString(stock, "roadwayId"));
|
||||
job.setCargoSpaceId(mapString(stock, "cargoSpaceId"));
|
||||
job.setCargoSpaceName(mapString(stock, "cargoSpaceName"));
|
||||
job.setLineType("1");
|
||||
job.setPriority(10L);
|
||||
job.setHeight(BigDecimal.ZERO);
|
||||
job.setWeight(BigDecimal.ZERO);
|
||||
job.setExitId(exitPoint.getPointId());
|
||||
copyLoginUser(job);
|
||||
return job;
|
||||
}
|
||||
|
||||
private MydoJobDetail buildEmptyTrayOutboundDetail(MydoJob job, HashMap<String, Object> stock) {
|
||||
MydoJobDetail detail = new MydoJobDetail();
|
||||
detail.setJobDetailId(job.getJobId() + "-1");
|
||||
detail.setJobId(job.getJobId());
|
||||
detail.setStockId(mapString(stock, "stockId"));
|
||||
detail.setLotNo(mapString(stock, "lotNo"));
|
||||
detail.setInDate(DateUtils.getTime());
|
||||
detail.setProductId(EMPTY_TRAY_PRODUCT_ID);
|
||||
detail.setProductName(EMPTY_TRAY_PRODUCT_NAME);
|
||||
detail.setStockQuantity(toBigDecimal(mapValue(stock, "availableQuantity")));
|
||||
detail.setOutQuantity(ONE_EMPTY_TRAY);
|
||||
detail.setStockinDetailId(mapString(stock, "stockDetailId"));
|
||||
detail.setSupplierId(mapString(stock, "supplierId"));
|
||||
detail.setSupplierName(mapString(stock, "supplierName"));
|
||||
return detail;
|
||||
}
|
||||
|
||||
private Object mapValue(Map<String, Object> map, String key) {
|
||||
if (map == null || key == null) {
|
||||
return null;
|
||||
}
|
||||
if (map.containsKey(key)) {
|
||||
return map.get(key);
|
||||
}
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
if (key.equalsIgnoreCase(entry.getKey())) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String mapString(Map<String, Object> map, String key) {
|
||||
return Convert.toStr(mapValue(map, key));
|
||||
}
|
||||
|
||||
private void checkBindTrayRequest(BindTrayRequest request) {
|
||||
if (request == null || !StringUtils.hasText(request.getTrayCode())) {
|
||||
throw new RuntimeException("托盘号不能为空");
|
||||
@@ -407,6 +663,10 @@ public class PdaServiceImpl implements PdaService {
|
||||
if (detail == null || detail.isEmpty()) {
|
||||
throw new RuntimeException("未找到对应出库任务");
|
||||
}
|
||||
HashMap<String, Object> materialQuery = new HashMap<>();
|
||||
materialQuery.put("jobId", detail.get("jobId"));
|
||||
List<HashMap<String, Object>> materials = pdaMapper.selectOutboundTrayMaterials(materialQuery);
|
||||
detail.put("materials", materials == null ? new ArrayList<>() : materials);
|
||||
return detail;
|
||||
}
|
||||
|
||||
@@ -415,22 +675,84 @@ public class PdaServiceImpl implements PdaService {
|
||||
public void confirmOutbound(Map<String, Object> data) {
|
||||
Map<String, Object> detail = outboundDetail(data);
|
||||
BigDecimal expectedQuantity = toBigDecimal(detail.get("quantity"));
|
||||
BigDecimal actualQuantity = toBigDecimal(data == null ? null : data.get("quantity"));
|
||||
BigDecimal actualQuantity = applyOutboundMaterialQuantities(data, detail);
|
||||
if (actualQuantity.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new RuntimeException("实际数量必须大于0");
|
||||
}
|
||||
if (actualQuantity.compareTo(expectedQuantity) != 0) {
|
||||
throw new RuntimeException("实际数量必须等于出库任务数量");
|
||||
}
|
||||
AjaxResult result = mydoJobService.completeJob(Convert.toStr(detail.get("jobId")), "2");
|
||||
String jobId = Convert.toStr(detail.get("jobId"));
|
||||
MydoJob outboundJob = mydoJobService.selectMydoJobByJobId(jobId);
|
||||
AjaxResult result = mydoJobService.completeJob(jobId, "2");
|
||||
if (result == null || result.isError()) {
|
||||
throw new RuntimeException(result == null ? "出库确认失败" : Convert.toStr(result.get(AjaxResult.MSG_TAG)));
|
||||
}
|
||||
createReturnInboundJobIfNeeded(outboundJob);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submitOutboundException(Map<String, Object> data) {
|
||||
outboundDetail(data);
|
||||
@SuppressWarnings("unchecked")
|
||||
private BigDecimal applyOutboundMaterialQuantities(Map<String, Object> data, Map<String, Object> detail) {
|
||||
Object materialPayload = data == null ? null : data.get("materials");
|
||||
if (!(materialPayload instanceof List<?> requestMaterials) || requestMaterials.isEmpty()) {
|
||||
return toBigDecimal(data == null ? null : data.get("quantity"));
|
||||
}
|
||||
|
||||
List<HashMap<String, Object>> jobMaterials = detail.get("materials") instanceof List<?>
|
||||
? (List<HashMap<String, Object>>) detail.get("materials")
|
||||
: new ArrayList<>();
|
||||
if (jobMaterials.isEmpty()) {
|
||||
throw new RuntimeException("出库作业无物料明细");
|
||||
}
|
||||
|
||||
BigDecimal totalQuantity = BigDecimal.ZERO;
|
||||
for (Object item : requestMaterials) {
|
||||
if (!(item instanceof Map<?, ?>)) {
|
||||
throw new RuntimeException("出库物料确认明细格式错误");
|
||||
}
|
||||
Map<String, Object> requestMaterial = (Map<String, Object>) item;
|
||||
String jobDetailId = mapString(requestMaterial, "jobDetailId");
|
||||
String stockDetailId = mapString(requestMaterial, "stockDetailId");
|
||||
HashMap<String, Object> jobMaterial = findOutboundJobMaterial(jobMaterials, jobDetailId, stockDetailId);
|
||||
if (jobMaterial == null) {
|
||||
throw new RuntimeException("出库物料确认明细不存在");
|
||||
}
|
||||
|
||||
BigDecimal quantity = toBigDecimal(firstNotBlank(mapValue(requestMaterial, "actualQuantity"), mapValue(requestMaterial, "quantity")));
|
||||
if (quantity.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new RuntimeException("物料实际数量必须大于0");
|
||||
}
|
||||
BigDecimal stockQuantity = toBigDecimal(mapValue(jobMaterial, "stockQuantity"));
|
||||
if (stockQuantity.compareTo(BigDecimal.ZERO) > 0 && quantity.compareTo(stockQuantity) > 0) {
|
||||
throw new RuntimeException("物料实际数量不能大于库存数量");
|
||||
}
|
||||
|
||||
MydoJobDetail updateDetail = new MydoJobDetail();
|
||||
updateDetail.setJobDetailId(mapString(jobMaterial, "jobDetailId"));
|
||||
updateDetail.setOutQuantity(quantity);
|
||||
if (mydoJobDetailService.updateMydoJobDetail(updateDetail) <= 0) {
|
||||
throw new RuntimeException("出库物料确认数量更新失败");
|
||||
}
|
||||
totalQuantity = totalQuantity.add(quantity);
|
||||
}
|
||||
|
||||
return totalQuantity;
|
||||
}
|
||||
|
||||
private HashMap<String, Object> findOutboundJobMaterial(List<HashMap<String, Object>> jobMaterials, String jobDetailId, String stockDetailId) {
|
||||
for (HashMap<String, Object> material : jobMaterials) {
|
||||
if (StringUtils.hasText(jobDetailId) && jobDetailId.equals(mapString(material, "jobDetailId"))) {
|
||||
return material;
|
||||
}
|
||||
if (StringUtils.hasText(stockDetailId) && stockDetailId.equals(mapString(material, "stockDetailId"))) {
|
||||
return material;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Object firstNotBlank(Object first, Object second) {
|
||||
return StringUtils.hasText(Convert.toStr(first)) ? first : second;
|
||||
}
|
||||
|
||||
private BigDecimal toBigDecimal(Object value) {
|
||||
@@ -440,6 +762,84 @@ public class PdaServiceImpl implements PdaService {
|
||||
return new BigDecimal(Convert.toStr(value));
|
||||
}
|
||||
|
||||
private void createReturnInboundJobIfNeeded(MydoJob outboundJob) {
|
||||
if (outboundJob == null || !StringUtils.hasText(outboundJob.getTrayCode()) || !StringUtils.hasText(outboundJob.getWarehouseId())) {
|
||||
return;
|
||||
}
|
||||
HashMap<String, Object> residualQuery = new HashMap<>();
|
||||
residualQuery.put("trayCode", outboundJob.getTrayCode());
|
||||
residualQuery.put("warehouseId", outboundJob.getWarehouseId());
|
||||
List<HashMap<String, Object>> residualMaterials = pdaMapper.selectResidualTrayMaterials(residualQuery);
|
||||
if (residualMaterials == null || residualMaterials.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String returnJobId = jobIdService.getId("HL");
|
||||
String returnTaskNo = taskNoService.getTaskNo("5");
|
||||
MydoJob returnJob = new MydoJob();
|
||||
returnJob.setJobId(returnJobId);
|
||||
returnJob.setTaskNo(StringUtils.hasText(returnTaskNo) ? returnTaskNo : returnJobId);
|
||||
returnJob.setInOrOut(5L);
|
||||
returnJob.setStatus("1");
|
||||
returnJob.setExecutionPhase("0");
|
||||
returnJob.setTrayCode(outboundJob.getTrayCode());
|
||||
returnJob.setWarehouseId(outboundJob.getWarehouseId());
|
||||
returnJob.setWarehouseName(outboundJob.getWarehouseName());
|
||||
returnJob.setEntranceId(outboundJob.getExitId());
|
||||
returnJob.setPriority(10L);
|
||||
returnJob.setHeight(BigDecimal.ZERO);
|
||||
returnJob.setWeight(BigDecimal.ZERO);
|
||||
copyLoginUser(returnJob);
|
||||
if (StringUtils.hasText(outboundJob.getExitId())) {
|
||||
BasStationPoint exitPoint = basStationPointService.selectBasStationPointByPointId(outboundJob.getExitId());
|
||||
if (exitPoint != null) {
|
||||
returnJob.setRoadwayId(exitPoint.getRoadwayId());
|
||||
}
|
||||
}
|
||||
mydoJobService.insertMydoJob(returnJob);
|
||||
|
||||
int index = 1;
|
||||
for (HashMap<String, Object> material : residualMaterials) {
|
||||
BigDecimal quantity = toBigDecimal(material.get("quantity"));
|
||||
if (quantity.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
continue;
|
||||
}
|
||||
MydoJobDetail returnDetail = new MydoJobDetail();
|
||||
returnDetail.setJobDetailId(returnJobId + "-" + index++);
|
||||
returnDetail.setJobId(returnJobId);
|
||||
returnDetail.setStockId(Convert.toStr(material.get("stockId")));
|
||||
returnDetail.setLotNo(Convert.toStr(material.get("lotNo")));
|
||||
returnDetail.setInDate(DateUtils.getTime());
|
||||
returnDetail.setInOrOutId(outboundJob.getJobId());
|
||||
returnDetail.setInOrOutDId(Convert.toStr(material.get("stockDetailId")));
|
||||
returnDetail.setProductId(Convert.toStr(material.get("productId")));
|
||||
returnDetail.setProductName(Convert.toStr(material.get("productName")));
|
||||
returnDetail.setInQuantity(quantity);
|
||||
returnDetail.setStockQuantity(quantity);
|
||||
returnDetail.setStockinDetailId(Convert.toStr(material.get("stockDetailId")));
|
||||
returnDetail.setSupplierId(Convert.toStr(material.get("supplierId")));
|
||||
returnDetail.setSupplierName(Convert.toStr(material.get("supplierName")));
|
||||
mydoJobDetailService.insertMydoJobDetail(returnDetail);
|
||||
}
|
||||
|
||||
ApiResponse<String> wcsResponse = wcsClient.sendPutawayTask(returnJob);
|
||||
if (wcsResponse == null || !wcsResponse.isSuccess()) {
|
||||
String message = wcsResponse == null ? "WCS接口无返回" : wcsResponse.getMessage();
|
||||
throw new RuntimeException("WCS回流入库作业下发失败:" + message);
|
||||
}
|
||||
markInJobDispatchedToWcs(returnJobId);
|
||||
}
|
||||
|
||||
private void copyLoginUser(MydoJob job) {
|
||||
try {
|
||||
job.setUserId(SecurityUtils.getUserId());
|
||||
job.setDeptId(SecurityUtils.getDeptId());
|
||||
job.setUserName(SecurityUtils.getUsername());
|
||||
} catch (RuntimeException ignored) {
|
||||
// Unit tests and external callbacks may run without a Spring Security context.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 作业生成后将关联入库单置为执行状态,供完成单据过程按既有规则校验 scan_num 与 assign_num。
|
||||
*/
|
||||
|
||||
@@ -168,6 +168,8 @@ external:
|
||||
WCS:
|
||||
base-url: http://172.16.4.63:8084
|
||||
token: wcs-token
|
||||
# 本地测试默认启用模拟返回;联调或生产设为 false 即可恢复真实 WCS 调用
|
||||
mock-enabled: false
|
||||
apis:
|
||||
sendPutawayTask: /wcs/api/receiveWmsJob
|
||||
sendRetrieveTask: /wcs/api/receiveWmsJob
|
||||
|
||||
@@ -177,6 +177,100 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
where cargo_space_id = #{cargoSpaceId}
|
||||
</update>
|
||||
|
||||
<update id="markOutboundAssignedByStockId" parameterType="String">
|
||||
update bcs
|
||||
set bcs.status = '4',
|
||||
bcs.tray_code = bs.tray_code
|
||||
from bas_cargo_space bcs
|
||||
inner join bas_stock bs on bcs.cargo_space_id = bs.cargo_space_id
|
||||
where bs.stock_id = #{stockId}
|
||||
</update>
|
||||
|
||||
<update id="markOutboundAssignedByStockRef" parameterType="String">
|
||||
update bcs
|
||||
set bcs.status = '4',
|
||||
bcs.tray_code = bs.tray_code
|
||||
from bas_cargo_space bcs
|
||||
inner join bas_stock bs on bcs.cargo_space_id = bs.cargo_space_id
|
||||
where bs.stock_id = #{stockRef}
|
||||
or exists (
|
||||
select 1
|
||||
from bas_stock_detail bsd
|
||||
where bsd.stock_id = bs.stock_id
|
||||
and bsd.detail_id = #{stockRef}
|
||||
)
|
||||
</update>
|
||||
|
||||
<update id="markOutboundAssignedByStockoutDetailId" parameterType="String">
|
||||
update bcs
|
||||
set bcs.status = '4',
|
||||
bcs.tray_code = bs.tray_code
|
||||
from bas_cargo_space bcs
|
||||
inner join bas_stock bs on bcs.cargo_space_id = bs.cargo_space_id
|
||||
where exists (
|
||||
select 1
|
||||
from mydo_job_detail jd
|
||||
inner join mydo_job j on j.job_id = jd.job_id
|
||||
where jd.stock_id = bs.stock_id
|
||||
and jd.in_or_out_d_id = #{stockoutDetailId}
|
||||
and j.in_or_out >= 20
|
||||
and j.status in ('1', '2', '3')
|
||||
)
|
||||
</update>
|
||||
|
||||
<update id="markOutboundAssignedByCargoSpaceId" parameterType="String">
|
||||
update bas_cargo_space
|
||||
set status = '4'
|
||||
where cargo_space_id = #{cargoSpaceId}
|
||||
and status in ('1', '6')
|
||||
</update>
|
||||
|
||||
<update id="releaseOutboundCargoSpace" parameterType="String">
|
||||
update bas_cargo_space
|
||||
set status = '1',
|
||||
tray_code = null
|
||||
where cargo_space_id = #{cargoSpaceId}
|
||||
</update>
|
||||
|
||||
<update id="markOutboundNeedCheck" parameterType="String">
|
||||
update bas_cargo_space
|
||||
set status = '6'
|
||||
where cargo_space_id = #{cargoSpaceId}
|
||||
</update>
|
||||
|
||||
<update id="markInboundNeedCheck" parameterType="String">
|
||||
update bas_cargo_space
|
||||
set status = '5'
|
||||
where cargo_space_id = #{cargoSpaceId}
|
||||
</update>
|
||||
|
||||
<select id="selectAvailableInboundCargoSpaceInRoadway" parameterType="com.ruoyi.in.job.domain.MydoJob" resultMap="BasCargoSpaceResult">
|
||||
select top 1 cargo_space_id, warehouse_id, roadway_id, cargo_area_id, the_type, the_type_name, cargo_space_name, the_platoon, the_column, the_floor, status, locked, tray_code, iss_double, group_coding_no, group_coding, cargo_space_logo, this_cargo_area, dept_id, dept_name, the_weight, the_height
|
||||
from (
|
||||
select bcs.cargo_space_id, bcs.warehouse_id, bcs.roadway_id, bcs.cargo_area_id, bcs.the_type, bcs.the_type_name,
|
||||
bcs.cargo_space_name, bcs.the_platoon, bcs.the_column, bcs.the_floor, bcs.status, bcs.locked,
|
||||
bcs.tray_code, bcs.iss_double, bcs.group_coding_no, bcs.group_coding, bcs.cargo_space_logo,
|
||||
bcs.this_cargo_area, bcs.dept_id, bcs.dept_name, bcs.the_weight, bcs.the_height,
|
||||
count(1) over (partition by bcs.the_platoon) as platoon_empty_count
|
||||
from bas_cargo_space bcs
|
||||
where bcs.warehouse_id = #{warehouseId}
|
||||
and bcs.roadway_id = #{roadwayId}
|
||||
and bcs.status = '1'
|
||||
and ISNULL(bcs.locked, 'N') = 'N'
|
||||
and (#{cargoSpaceId} is null or bcs.cargo_space_id != #{cargoSpaceId})
|
||||
and bcs.the_type = (select top 1 btr.allowed_cargo_type
|
||||
from bas_tray_rule btr
|
||||
where btr.tray_type is not null
|
||||
and btr.tray_type != ''
|
||||
and #{trayCode} is not null
|
||||
and #{trayCode} != ''
|
||||
and left(#{trayCode}, len(btr.tray_type)) = btr.tray_type
|
||||
and charindex(',' + #{warehouseId} + ',', ',' + btr.allowed_warehouses + ',') > 0
|
||||
order by len(btr.tray_type) desc, btr.id)
|
||||
) candidate
|
||||
order by platoon_empty_count desc, the_platoon asc, the_floor asc, the_column asc, cargo_space_id asc
|
||||
</select>
|
||||
|
||||
<delete id="deleteBasCargoSpaceByCargoSpaceId" parameterType="String">
|
||||
delete from bas_cargo_space where cargo_space_id = #{cargoSpaceId}
|
||||
</delete>
|
||||
@@ -187,4 +281,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
#{cargoSpaceId}
|
||||
</foreach>
|
||||
</delete>
|
||||
</mapper>
|
||||
</mapper>
|
||||
|
||||
@@ -23,6 +23,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="trayCode" column="tray_code" />
|
||||
<result property="warehouseId" column="warehouse_id" />
|
||||
<result property="warehouseName" column="warehouse_name" />
|
||||
<result property="trayType" column="tray_type" />
|
||||
<result property="availableQuantity" column="available_quantity" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
@@ -33,17 +34,29 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
select bsd.detail_id, bsd.stock_id, bsd.product_status, bsd.lot_no, bsd.product_id, bsd.product_name,
|
||||
bsd.quantity, bsd.out_quantity, bsd.job_detail_id, bsd.stockin_detail_id,
|
||||
bs.cargo_space_id, bcs.cargo_space_name, bs.stock_status, bs.in_date, bs.back_date,
|
||||
bs.tray_code, bs.warehouse_id, bs.warehouse_name,
|
||||
bs.tray_code, bs.warehouse_id, ISNULL(NULLIF(bs.warehouse_name, ''), bw.warehouse_name) as warehouse_name,
|
||||
bbf.tray_type,
|
||||
ISNULL(bsd.quantity, 0) - ISNULL(bsd.out_quantity, 0) as available_quantity,
|
||||
bsd.create_time, bsd.update_time, bsd.remark
|
||||
from bas_stock_detail bsd
|
||||
left join bas_stock bs on bsd.stock_id = bs.stock_id
|
||||
left join bas_cargo_space bcs on bs.cargo_space_id = bcs.cargo_space_id
|
||||
left join bas_warehouse bw on bs.warehouse_id = bw.warehouse_id
|
||||
OUTER APPLY (
|
||||
select top 1 prefix as tray_type
|
||||
from bas_barcode_format bbf
|
||||
where bbf.prefix is not null
|
||||
and bbf.prefix != ''
|
||||
and bs.tray_code is not null
|
||||
and left(bs.tray_code, len(bbf.prefix)) = bbf.prefix
|
||||
order by len(bbf.prefix) desc
|
||||
) bbf
|
||||
</sql>
|
||||
|
||||
<select id="selectBasStockDetailList" parameterType="BasStockDetail" resultMap="BasStockDetailResult">
|
||||
<include refid="selectBasStockDetailVo" />
|
||||
<where>
|
||||
bcs.status = '2'
|
||||
<if test="detailId != null and detailId != ''"> and bsd.detail_id = #{detailId}</if>
|
||||
<if test="stockId != null and stockId != ''"> and bsd.stock_id = #{stockId}</if>
|
||||
<if test="productStatus != null and productStatus != ''"> and bsd.product_status = #{productStatus}</if>
|
||||
@@ -56,7 +69,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="stockStatus != null and stockStatus != ''"> and bs.stock_status = #{stockStatus}</if>
|
||||
<if test="trayCode != null and trayCode != ''"> and bs.tray_code = #{trayCode}</if>
|
||||
<if test="warehouseId != null and warehouseId != ''"> and bs.warehouse_id = #{warehouseId}</if>
|
||||
<if test="warehouseName != null and warehouseName != ''"> and bs.warehouse_name like concat('%', #{warehouseName}, '%')</if>
|
||||
<if test="warehouseName != null and warehouseName != ''"> and ISNULL(NULLIF(bs.warehouse_name, ''), bw.warehouse_name) like concat('%', #{warehouseName}, '%')</if>
|
||||
<if test="params.beginInDate != null and params.beginInDate != '' and params.endInDate != null and params.endInDate != ''"> and bs.in_date between #{params.beginInDate} and #{params.endInDate}</if>
|
||||
<if test="params.beginBackDate != null and params.beginBackDate != '' and params.endBackDate != null and params.endBackDate != ''"> and bs.back_date between #{params.beginBackDate} and #{params.endBackDate}</if>
|
||||
${params.warehouseScope}
|
||||
@@ -69,6 +82,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
where bsd.detail_id = #{detailId}
|
||||
</select>
|
||||
|
||||
<select id="countBasStockDetailByStockId" parameterType="String" resultType="int">
|
||||
select count(1)
|
||||
from bas_stock_detail with (UPDLOCK, HOLDLOCK)
|
||||
where stock_id = #{stockId}
|
||||
</select>
|
||||
|
||||
<select id="selectStockIdsByDetailIds" parameterType="String" resultType="String">
|
||||
select distinct stock_id from bas_stock_detail where detail_id in
|
||||
<foreach item="detailId" collection="array" open="(" separator="," close=")">
|
||||
@@ -128,6 +147,34 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
where detail_id = #{detailId}
|
||||
</update>
|
||||
|
||||
<update id="rollbackOutboundQuantityByJobId" parameterType="String">
|
||||
update bsd
|
||||
set bsd.out_quantity = case
|
||||
when ISNULL(bsd.out_quantity, 0) - ISNULL(jd.rollback_quantity, 0) < 0 then 0
|
||||
else ISNULL(bsd.out_quantity, 0) - ISNULL(jd.rollback_quantity, 0)
|
||||
end,
|
||||
bsd.job_detail_id = case
|
||||
when exists (
|
||||
select 1
|
||||
from mydo_job_detail x
|
||||
where x.job_id = #{jobId}
|
||||
and x.job_detail_id = bsd.job_detail_id
|
||||
) then null
|
||||
else bsd.job_detail_id
|
||||
end,
|
||||
bsd.update_time = SYSDATETIME()
|
||||
from bas_stock_detail bsd
|
||||
inner join (
|
||||
select stockin_detail_id as detail_id,
|
||||
sum(ISNULL(out_quantity, 0)) as rollback_quantity
|
||||
from mydo_job_detail
|
||||
where job_id = #{jobId}
|
||||
and stockin_detail_id is not null
|
||||
and stockin_detail_id != ''
|
||||
group by stockin_detail_id
|
||||
) jd on jd.detail_id = bsd.detail_id
|
||||
</update>
|
||||
|
||||
<delete id="deleteBasStockDetailByDetailId" parameterType="String">
|
||||
delete from bas_stock_detail where detail_id = #{detailId}
|
||||
</delete>
|
||||
|
||||
@@ -35,7 +35,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectBasStockVo">
|
||||
select stock_id, cargo_space_id, stock_status, product_status, lot_no, in_date, back_date, product_id, product_name, quantity, job_id, job_detail_id, tray_code, out_quantity, stockin_detail_id, stockin_id, in_type, warehouse_id, warehouse_name, user_id, user_name, dept_id, dept_name, supplier_id as suplier_id, supplier_name as suplier_name from bas_stock
|
||||
select stock_id, cargo_space_id, stock_status, product_status, lot_no, in_date, back_date, product_id, product_name, quantity, job_id, job_detail_id, tray_code, out_quantity, stockin_detail_id, stockin_id, in_type, warehouse_id,
|
||||
ISNULL(NULLIF(warehouse_name, ''), (select top 1 bw.warehouse_name from bas_warehouse bw where bw.warehouse_id = bas_stock.warehouse_id)) as warehouse_name,
|
||||
user_id, user_name, dept_id, dept_name, supplier_id as suplier_id, supplier_name as suplier_name from bas_stock
|
||||
</sql>
|
||||
|
||||
<select id="selectBasStockList" parameterType="BasStock" resultMap="BasStockResult">
|
||||
@@ -46,12 +48,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
bs.job_id, null as job_detail_id,
|
||||
bs.tray_code, ISNULL(bsa.out_quantity, 0) as out_quantity,
|
||||
null as stockin_detail_id,
|
||||
bs.stockin_id, bs.in_type, bs.warehouse_id, bs.warehouse_name,
|
||||
bs.stockin_id, bs.in_type, bs.warehouse_id, ISNULL(NULLIF(bs.warehouse_name, ''), bw.warehouse_name) as warehouse_name,
|
||||
bs.user_id, bs.user_name, bs.dept_id, bs.dept_name, bs.supplier_id as suplier_id, bs.supplier_name as suplier_name,
|
||||
ISNULL(bsa.detail_count, 0) as detail_count,
|
||||
ISNULL(bsa.quantity, 0) - ISNULL(bsa.out_quantity, 0) as available_quantity
|
||||
from bas_stock bs
|
||||
left join bas_cargo_space bcs on bs.cargo_space_id = bcs.cargo_space_id
|
||||
left join bas_warehouse bw on bs.warehouse_id = bw.warehouse_id
|
||||
left join (
|
||||
select stock_id,
|
||||
count(1) as detail_count,
|
||||
@@ -80,7 +83,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="stockinId != null and stockinId != ''"> and bs.stockin_id = #{stockinId}</if>
|
||||
<if test="inType != null and inType != ''"> and bs.in_type = #{inType}</if>
|
||||
<if test="warehouseId != null and warehouseId != ''"> and bs.warehouse_id = #{warehouseId}</if>
|
||||
<if test="warehouseName != null and warehouseName != ''"> and bs.warehouse_name like concat('%', #{warehouseName}, '%')</if>
|
||||
<if test="warehouseName != null and warehouseName != ''"> and ISNULL(NULLIF(bs.warehouse_name, ''), bw.warehouse_name) like concat('%', #{warehouseName}, '%')</if>
|
||||
<if test="userName != null and userName != ''"> and bs.user_name like concat('%', #{userName}, '%')</if>
|
||||
<if test="deptName != null and deptName != ''"> and bs.dept_name like concat('%', #{deptName}, '%')</if>
|
||||
<if test="suplierName != null and suplierName != ''"> and bs.supplier_name like concat('%', #{suplierName}, '%')</if>
|
||||
@@ -204,6 +207,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
where bs.stock_id = #{stockId}
|
||||
</update>
|
||||
|
||||
<update id="markExceptionByJobId" parameterType="String">
|
||||
update bs
|
||||
set bs.stock_status = '3'
|
||||
from bas_stock bs
|
||||
where exists (
|
||||
select 1
|
||||
from mydo_job_detail jd
|
||||
where jd.job_id = #{jobId}
|
||||
and jd.stock_id = bs.stock_id
|
||||
)
|
||||
</update>
|
||||
|
||||
<delete id="deleteBasStockByStockId" parameterType="String">
|
||||
delete from bas_stock where stock_id = #{stockId}
|
||||
</delete>
|
||||
|
||||
@@ -153,4 +153,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
#{jobDetailId}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<delete id="deleteMydoJobDetailByJobId" parameterType="String">
|
||||
delete from mydo_job_detail where job_id = #{jobId}
|
||||
</delete>
|
||||
</mapper>
|
||||
|
||||
@@ -39,10 +39,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectMydoJobVo">
|
||||
select job_id, status, task_no, in_or_out, execution_phase, tray_code, warehouse_id, warehouse_name, roadway_id, cargo_space_id, cargo_space_name, target_roadway_id, target_cargo_space_id, target_cargo_space_name, line_type, priority, height, weight, create_time, start_time, finish_time, entrance_id, exit_id, dept_id, dept_name, user_id, user_name, supplier_id, supplier_name, customer_id, customer_name from mydo_job
|
||||
select job_id, status, task_no, in_or_out, execution_phase, tray_code, warehouse_id,
|
||||
isnull(nullif(warehouse_name, ''), (select top 1 bw.warehouse_name from bas_warehouse bw where bw.warehouse_id = mydo_job.warehouse_id)) as warehouse_name,
|
||||
roadway_id, cargo_space_id, cargo_space_name, target_roadway_id, target_cargo_space_id, target_cargo_space_name, line_type, priority, height, weight, create_time, start_time, finish_time, entrance_id, exit_id, dept_id, dept_name, user_id, user_name, supplier_id, supplier_name, customer_id, customer_name from mydo_job
|
||||
</sql>
|
||||
<sql id="selectMydoJobFinishVo">
|
||||
select job_id, status, task_no, in_or_out, execution_phase, tray_code, warehouse_id, warehouse_name, roadway_id, cargo_space_id, cargo_space_name, target_roadway_id, target_cargo_space_id, target_cargo_space_name, line_type, priority, height, weight, create_time, start_time, finish_time, entrance_id, exit_id, dept_id, dept_name, user_id, user_name, supplier_id, supplier_name, customer_id, customer_name from mydo_job_finish
|
||||
select job_id, status, task_no, in_or_out, execution_phase, tray_code, warehouse_id,
|
||||
isnull(nullif(warehouse_name, ''), (select top 1 bw.warehouse_name from bas_warehouse bw where bw.warehouse_id = mydo_job_finish.warehouse_id)) as warehouse_name,
|
||||
roadway_id, cargo_space_id, cargo_space_name, target_roadway_id, target_cargo_space_id, target_cargo_space_name, line_type, priority, height, weight, create_time, start_time, finish_time, entrance_id, exit_id, dept_id, dept_name, user_id, user_name, supplier_id, supplier_name, customer_id, customer_name from mydo_job_finish
|
||||
</sql>
|
||||
|
||||
<select id="selectMydoJobList" parameterType="MydoJob" resultMap="MydoJobResult">
|
||||
@@ -53,6 +57,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="inOrOut != null "> and in_or_out = #{inOrOut}</if>
|
||||
<if test="executionPhase != null and executionPhase != ''"> and execution_phase = #{executionPhase}</if>
|
||||
<if test="trayCode != null and trayCode != ''"> and tray_code = #{trayCode}</if>
|
||||
<if test="warehouseId != null and warehouseId != ''"> and warehouse_id = #{warehouseId}</if>
|
||||
<if test="warehouseName != null and warehouseName != ''"> and warehouse_name like concat('%', #{warehouseName}, '%')</if>
|
||||
<if test="roadwayId != null and roadwayId != ''"> and roadway_id = #{roadwayId}</if>
|
||||
<if test="cargoSpaceId != null and cargoSpaceId != ''"> and cargo_space_id = #{cargoSpaceId}</if>
|
||||
@@ -84,7 +89,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
finish_time, entrance_id, exit_id, dept_id, dept_name, user_id, user_name, supplier_id,
|
||||
supplier_name, customer_id, customer_name
|
||||
from mydo_job
|
||||
where tray_code = #{trayCode} and in_or_out = 1
|
||||
where tray_code = #{trayCode} and in_or_out < 20
|
||||
order by create_time desc
|
||||
</select>
|
||||
|
||||
@@ -95,7 +100,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
finish_time, entrance_id, exit_id, dept_id, dept_name, user_id, user_name, supplier_id,
|
||||
supplier_name, customer_id, customer_name
|
||||
from mydo_job
|
||||
where task_no = #{taskNo} and in_or_out = 1
|
||||
where task_no = #{taskNo} and in_or_out < 20
|
||||
order by create_time desc
|
||||
</select>
|
||||
|
||||
@@ -117,7 +122,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
finish_time, entrance_id, exit_id, dept_id, dept_name, user_id, user_name, supplier_id,
|
||||
supplier_name, customer_id, customer_name
|
||||
from mydo_job
|
||||
where tray_code = #{trayCode} and warehouse_id = #{warehouseId} and in_or_out = 1
|
||||
where tray_code = #{trayCode} and warehouse_id = #{warehouseId} and in_or_out < 20
|
||||
order by create_time desc
|
||||
</select>
|
||||
|
||||
@@ -154,6 +159,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
and stockin_id is not null
|
||||
and stockin_id != ''
|
||||
</select>
|
||||
|
||||
<select id="selectStockIdsByJobId" parameterType="String" resultType="String">
|
||||
select distinct stock_id
|
||||
from mydo_job_detail
|
||||
where job_id = #{jobId}
|
||||
and stock_id is not null
|
||||
and stock_id != ''
|
||||
</select>
|
||||
|
||||
<select id="selectStockoutDetailIdsByJobId" parameterType="String" resultType="String">
|
||||
select distinct in_or_out_d_id
|
||||
from mydo_job_detail
|
||||
where job_id = #{jobId}
|
||||
and in_or_out_d_id is not null
|
||||
and in_or_out_d_id != ''
|
||||
</select>
|
||||
<select id="completeJob" parameterType="java.util.Map" statementType="CALLABLE">
|
||||
{call proc_complete_in_job(
|
||||
#{job_id,mode=IN,jdbcType=VARCHAR},
|
||||
@@ -313,6 +334,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="inOrOut != null "> and in_or_out = #{inOrOut}</if>
|
||||
<if test="executionPhase != null and executionPhase != ''"> and execution_phase = #{executionPhase}</if>
|
||||
<if test="trayCode != null and trayCode != ''"> and tray_code = #{trayCode}</if>
|
||||
<if test="warehouseId != null and warehouseId != ''"> and warehouse_id = #{warehouseId}</if>
|
||||
<if test="warehouseName != null and warehouseName != ''"> and warehouse_name like concat('%', #{warehouseName}, '%')</if>
|
||||
<if test="roadwayId != null and roadwayId != ''"> and roadway_id = #{roadwayId}</if>
|
||||
<if test="cargoSpaceId != null and cargoSpaceId != ''"> and cargo_space_id = #{cargoSpaceId}</if>
|
||||
@@ -339,6 +361,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="inOrOut != null "> and in_or_out = #{inOrOut}</if>
|
||||
<if test="executionPhase != null and executionPhase != ''"> and execution_phase = #{executionPhase}</if>
|
||||
<if test="trayCode != null and trayCode != ''"> and tray_code = #{trayCode}</if>
|
||||
<if test="warehouseId != null and warehouseId != ''"> and warehouse_id = #{warehouseId}</if>
|
||||
<if test="warehouseName != null and warehouseName != ''"> and warehouse_name like concat('%', #{warehouseName}, '%')</if>
|
||||
<if test="roadwayId != null and roadwayId != ''"> and roadway_id = #{roadwayId}</if>
|
||||
<if test="cargoSpaceId != null and cargoSpaceId != ''"> and cargo_space_id = #{cargoSpaceId}</if>
|
||||
@@ -365,6 +388,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="inOrOut != null "> and in_or_out = #{inOrOut}</if>
|
||||
<if test="executionPhase != null and executionPhase != ''"> and execution_phase = #{executionPhase}</if>
|
||||
<if test="trayCode != null and trayCode != ''"> and tray_code = #{trayCode}</if>
|
||||
<if test="warehouseId != null and warehouseId != ''"> and warehouse_id = #{warehouseId}</if>
|
||||
<if test="warehouseName != null and warehouseName != ''"> and warehouse_name like concat('%', #{warehouseName}, '%')</if>
|
||||
<if test="roadwayId != null and roadwayId != ''"> and roadway_id = #{roadwayId}</if>
|
||||
<if test="cargoSpaceId != null and cargoSpaceId != ''"> and cargo_space_id = #{cargoSpaceId}</if>
|
||||
@@ -392,6 +416,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="inOrOut != null "> and in_or_out = #{inOrOut}</if>
|
||||
<if test="executionPhase != null and executionPhase != ''"> and execution_phase = #{executionPhase}</if>
|
||||
<if test="trayCode != null and trayCode != ''"> and tray_code = #{trayCode}</if>
|
||||
<if test="warehouseId != null and warehouseId != ''"> and warehouse_id = #{warehouseId}</if>
|
||||
<if test="warehouseName != null and warehouseName != ''"> and warehouse_name like concat('%', #{warehouseName}, '%')</if>
|
||||
<if test="roadwayId != null and roadwayId != ''"> and roadway_id = #{roadwayId}</if>
|
||||
<if test="cargoSpaceId != null and cargoSpaceId != ''"> and cargo_space_id = #{cargoSpaceId}</if>
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.out.location.mapper.LocationOutboundMapper">
|
||||
|
||||
<resultMap type="LocationOutboundStock" id="LocationOutboundStockResult">
|
||||
<result property="cargoSpaceId" column="cargo_space_id" />
|
||||
<result property="cargoSpaceName" column="cargo_space_name" />
|
||||
<result property="cargoSpaceStatus" column="cargo_space_status" />
|
||||
<result property="locked" column="locked" />
|
||||
<result property="warehouseId" column="warehouse_id" />
|
||||
<result property="warehouseName" column="warehouse_name" />
|
||||
<result property="roadwayId" column="roadway_id" />
|
||||
<result property="thePlatoon" column="the_platoon" />
|
||||
<result property="theColumn" column="the_column" />
|
||||
<result property="theFloor" column="the_floor" />
|
||||
<result property="theType" column="the_type" />
|
||||
<result property="theTypeName" column="the_type_name" />
|
||||
<result property="stockId" column="stock_id" />
|
||||
<result property="stockStatus" column="stock_status" />
|
||||
<result property="trayCode" column="tray_code" />
|
||||
<result property="trayType" column="tray_type" />
|
||||
<result property="inDate" column="in_date" />
|
||||
<result property="stockinId" column="stockin_id" />
|
||||
<result property="inType" column="in_type" />
|
||||
<result property="supplierId" column="supplier_id" />
|
||||
<result property="supplierName" column="supplier_name" />
|
||||
<result property="detailCount" column="detail_count" />
|
||||
<result property="quantity" column="quantity" />
|
||||
<result property="outQuantity" column="out_quantity" />
|
||||
<result property="availableQuantity" column="available_quantity" />
|
||||
</resultMap>
|
||||
|
||||
<resultMap type="BasStockDetail" id="LocationOutboundDetailResult">
|
||||
<result property="detailId" column="detail_id" />
|
||||
<result property="stockId" column="stock_id" />
|
||||
<result property="productStatus" column="product_status" />
|
||||
<result property="lotNo" column="lot_no" />
|
||||
<result property="productId" column="product_id" />
|
||||
<result property="productName" column="product_name" />
|
||||
<result property="quantity" column="quantity" />
|
||||
<result property="outQuantity" column="out_quantity" />
|
||||
<result property="jobDetailId" column="job_detail_id" />
|
||||
<result property="stockinDetailId" column="stockin_detail_id" />
|
||||
<result property="cargoSpaceId" column="cargo_space_id" />
|
||||
<result property="cargoSpaceName" column="cargo_space_name" />
|
||||
<result property="stockStatus" column="stock_status" />
|
||||
<result property="inDate" column="in_date" />
|
||||
<result property="backDate" column="back_date" />
|
||||
<result property="trayCode" column="tray_code" />
|
||||
<result property="warehouseId" column="warehouse_id" />
|
||||
<result property="warehouseName" column="warehouse_name" />
|
||||
<result property="trayType" column="tray_type" />
|
||||
<result property="availableQuantity" column="available_quantity" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="stockAggregate">
|
||||
select stock_id,
|
||||
count(1) as detail_count,
|
||||
sum(isnull(quantity, 0)) as quantity,
|
||||
sum(isnull(out_quantity, 0)) as out_quantity,
|
||||
sum(isnull(quantity, 0) - isnull(out_quantity, 0)) as available_quantity
|
||||
from bas_stock_detail
|
||||
group by stock_id
|
||||
</sql>
|
||||
|
||||
<sql id="detailSelect">
|
||||
select bsd.detail_id, bsd.stock_id, bsd.product_status, bsd.lot_no, bsd.product_id, bsd.product_name,
|
||||
bsd.quantity, bsd.out_quantity, bsd.job_detail_id, bsd.stockin_detail_id,
|
||||
bs.cargo_space_id, bcs.cargo_space_name, bs.stock_status, bs.in_date, bs.back_date,
|
||||
bs.tray_code, bs.warehouse_id, isnull(nullif(bs.warehouse_name, ''), bw.warehouse_name) as warehouse_name,
|
||||
bbf.tray_type,
|
||||
isnull(bsd.quantity, 0) - isnull(bsd.out_quantity, 0) as available_quantity,
|
||||
bsd.create_time, bsd.update_time, bsd.remark
|
||||
from bas_stock_detail bsd
|
||||
inner join bas_stock bs on bsd.stock_id = bs.stock_id
|
||||
left join bas_cargo_space bcs on bs.cargo_space_id = bcs.cargo_space_id
|
||||
left join bas_warehouse bw on bs.warehouse_id = bw.warehouse_id
|
||||
outer apply (
|
||||
select top 1 prefix as tray_type
|
||||
from bas_barcode_format bbf
|
||||
where bbf.prefix is not null
|
||||
and bbf.prefix != ''
|
||||
and bs.tray_code is not null
|
||||
and left(bs.tray_code, len(bbf.prefix)) = bbf.prefix
|
||||
order by len(bbf.prefix) desc
|
||||
) bbf
|
||||
</sql>
|
||||
|
||||
<select id="selectLocationOutboundStockList" parameterType="LocationOutboundStock" resultMap="LocationOutboundStockResult">
|
||||
select bcs.cargo_space_id, bcs.cargo_space_name, bcs.status as cargo_space_status, bcs.locked,
|
||||
bcs.warehouse_id, isnull(nullif(bs.warehouse_name, ''), bw.warehouse_name) as warehouse_name,
|
||||
bcs.roadway_id, bcs.the_platoon, bcs.the_column, bcs.the_floor, bcs.the_type, bcs.the_type_name,
|
||||
bs.stock_id, bs.stock_status, bs.tray_code, bbf.tray_type, bs.in_date, bs.stockin_id, bs.in_type,
|
||||
bs.supplier_id, bs.supplier_name,
|
||||
isnull(bsa.detail_count, 0) as detail_count,
|
||||
isnull(bsa.quantity, 0) as quantity,
|
||||
isnull(bsa.out_quantity, 0) as out_quantity,
|
||||
isnull(bsa.available_quantity, 0) as available_quantity
|
||||
from bas_cargo_space bcs
|
||||
left join bas_stock bs on bs.cargo_space_id = bcs.cargo_space_id
|
||||
and bcs.status in ('2', '4')
|
||||
left join bas_warehouse bw on bcs.warehouse_id = bw.warehouse_id
|
||||
left join (
|
||||
<include refid="stockAggregate" />
|
||||
) bsa on bsa.stock_id = bs.stock_id
|
||||
outer apply (
|
||||
select top 1 prefix as tray_type
|
||||
from bas_barcode_format bbf
|
||||
where bbf.prefix is not null
|
||||
and bbf.prefix != ''
|
||||
and bs.tray_code is not null
|
||||
and left(bs.tray_code, len(bbf.prefix)) = bbf.prefix
|
||||
order by len(bbf.prefix) desc
|
||||
) bbf
|
||||
<where>
|
||||
<if test="showEmpty == null or showEmpty == false">
|
||||
and bcs.status = '2'
|
||||
and bs.stock_id is not null
|
||||
and isnull(bsa.available_quantity, 0) > 0
|
||||
</if>
|
||||
<if test="warehouseId != null and warehouseId != ''"> and bcs.warehouse_id = #{warehouseId}</if>
|
||||
<if test="warehouseName != null and warehouseName != ''"> and isnull(nullif(bs.warehouse_name, ''), bw.warehouse_name) like concat('%', #{warehouseName}, '%')</if>
|
||||
<if test="roadwayId != null and roadwayId != ''"> and bcs.roadway_id = #{roadwayId}</if>
|
||||
<if test="cargoSpaceId != null and cargoSpaceId != ''"> and bcs.cargo_space_id like concat('%', #{cargoSpaceId}, '%')</if>
|
||||
<if test="cargoSpaceName != null and cargoSpaceName != ''"> and bcs.cargo_space_name like concat('%', #{cargoSpaceName}, '%')</if>
|
||||
<if test="cargoSpaceStatus != null and cargoSpaceStatus != ''"> and bcs.status = #{cargoSpaceStatus}</if>
|
||||
<if test="stockStatus != null and stockStatus != ''"> and bs.stock_status = #{stockStatus}</if>
|
||||
<if test="trayCode != null and trayCode != ''"> and bs.tray_code like concat('%', #{trayCode}, '%')</if>
|
||||
<if test="productId != null and productId != ''"> and exists (select 1 from bas_stock_detail x where x.stock_id = bs.stock_id and x.product_id like concat('%', #{productId}, '%'))</if>
|
||||
<if test="productName != null and productName != ''"> and exists (select 1 from bas_stock_detail x where x.stock_id = bs.stock_id and x.product_name like concat('%', #{productName}, '%'))</if>
|
||||
<if test="lotNo != null and lotNo != ''"> and exists (select 1 from bas_stock_detail x where x.stock_id = bs.stock_id and x.lot_no like concat('%', #{lotNo}, '%'))</if>
|
||||
${params.warehouseScope}
|
||||
</where>
|
||||
order by bcs.warehouse_id, bcs.roadway_id, bcs.the_floor, bcs.the_platoon, bcs.the_column, bcs.cargo_space_id, bs.stock_id
|
||||
</select>
|
||||
|
||||
<select id="selectLocationOutboundDetailsByStockIds" parameterType="String" resultMap="LocationOutboundDetailResult">
|
||||
<include refid="detailSelect" />
|
||||
where bsd.stock_id in
|
||||
<foreach item="stockId" collection="array" open="(" separator="," close=")">
|
||||
#{stockId}
|
||||
</foreach>
|
||||
and isnull(bsd.quantity, 0) > 0
|
||||
order by bsd.stock_id, bsd.product_id, bsd.detail_id
|
||||
</select>
|
||||
|
||||
<select id="selectLocationOutboundStockForUpdate" parameterType="String" resultMap="LocationOutboundStockResult">
|
||||
select top 1 bcs.cargo_space_id, bcs.cargo_space_name, bcs.status as cargo_space_status, bcs.locked,
|
||||
bcs.warehouse_id, isnull(nullif(bs.warehouse_name, ''), bw.warehouse_name) as warehouse_name,
|
||||
bcs.roadway_id, bcs.the_platoon, bcs.the_column, bcs.the_floor, bcs.the_type, bcs.the_type_name,
|
||||
bs.stock_id, bs.stock_status, bs.tray_code, bbf.tray_type, bs.in_date, bs.stockin_id, bs.in_type,
|
||||
bs.supplier_id, bs.supplier_name,
|
||||
isnull(bsa.detail_count, 0) as detail_count,
|
||||
isnull(bsa.quantity, 0) as quantity,
|
||||
isnull(bsa.out_quantity, 0) as out_quantity,
|
||||
isnull(bsa.available_quantity, 0) as available_quantity
|
||||
from bas_stock bs with (UPDLOCK, HOLDLOCK)
|
||||
inner join bas_cargo_space bcs with (UPDLOCK, HOLDLOCK) on bs.cargo_space_id = bcs.cargo_space_id
|
||||
left join bas_warehouse bw on bs.warehouse_id = bw.warehouse_id
|
||||
left join (
|
||||
<include refid="stockAggregate" />
|
||||
) bsa on bsa.stock_id = bs.stock_id
|
||||
outer apply (
|
||||
select top 1 prefix as tray_type
|
||||
from bas_barcode_format bbf
|
||||
where bbf.prefix is not null
|
||||
and bbf.prefix != ''
|
||||
and bs.tray_code is not null
|
||||
and left(bs.tray_code, len(bbf.prefix)) = bbf.prefix
|
||||
order by len(bbf.prefix) desc
|
||||
) bbf
|
||||
where bs.stock_id = #{stockId}
|
||||
</select>
|
||||
|
||||
<select id="selectLocationOutboundLocationForUpdate" parameterType="String" resultMap="LocationOutboundStockResult">
|
||||
select top 1 bcs.cargo_space_id, bcs.cargo_space_name, bcs.status as cargo_space_status, bcs.locked,
|
||||
bcs.warehouse_id, isnull(nullif(bs.warehouse_name, ''), bw.warehouse_name) as warehouse_name,
|
||||
bcs.roadway_id, bcs.the_platoon, bcs.the_column, bcs.the_floor, bcs.the_type, bcs.the_type_name,
|
||||
bs.stock_id, bs.stock_status, bs.tray_code, bbf.tray_type, bs.in_date, bs.stockin_id, bs.in_type,
|
||||
bs.supplier_id, bs.supplier_name,
|
||||
isnull(bsa.detail_count, 0) as detail_count,
|
||||
isnull(bsa.quantity, 0) as quantity,
|
||||
isnull(bsa.out_quantity, 0) as out_quantity,
|
||||
isnull(bsa.available_quantity, 0) as available_quantity
|
||||
from bas_cargo_space bcs with (UPDLOCK, HOLDLOCK)
|
||||
left join bas_stock bs with (UPDLOCK, HOLDLOCK) on bs.cargo_space_id = bcs.cargo_space_id
|
||||
and bcs.status in ('2', '4')
|
||||
left join bas_warehouse bw on bcs.warehouse_id = bw.warehouse_id
|
||||
left join (
|
||||
<include refid="stockAggregate" />
|
||||
) bsa on bsa.stock_id = bs.stock_id
|
||||
outer apply (
|
||||
select top 1 prefix as tray_type
|
||||
from bas_barcode_format bbf
|
||||
where bbf.prefix is not null
|
||||
and bbf.prefix != ''
|
||||
and bs.tray_code is not null
|
||||
and left(bs.tray_code, len(bbf.prefix)) = bbf.prefix
|
||||
order by len(bbf.prefix) desc
|
||||
) bbf
|
||||
where bcs.cargo_space_id = #{cargoSpaceId}
|
||||
order by bs.in_date desc, bs.stock_id
|
||||
</select>
|
||||
|
||||
<select id="selectAvailableDetailsForUpdate" parameterType="String" resultMap="LocationOutboundDetailResult">
|
||||
select bsd.detail_id, bsd.stock_id, bsd.product_status, bsd.lot_no, bsd.product_id, bsd.product_name,
|
||||
bsd.quantity, bsd.out_quantity, bsd.job_detail_id, bsd.stockin_detail_id,
|
||||
bs.cargo_space_id, bcs.cargo_space_name, bs.stock_status, bs.in_date, bs.back_date,
|
||||
bs.tray_code, bs.warehouse_id, isnull(nullif(bs.warehouse_name, ''), bw.warehouse_name) as warehouse_name,
|
||||
bbf.tray_type,
|
||||
isnull(bsd.quantity, 0) - isnull(bsd.out_quantity, 0) as available_quantity,
|
||||
bsd.create_time, bsd.update_time, bsd.remark
|
||||
from bas_stock_detail bsd with (UPDLOCK, HOLDLOCK)
|
||||
inner join bas_stock bs with (UPDLOCK, HOLDLOCK) on bsd.stock_id = bs.stock_id
|
||||
left join bas_cargo_space bcs with (UPDLOCK, HOLDLOCK) on bs.cargo_space_id = bcs.cargo_space_id
|
||||
left join bas_warehouse bw on bs.warehouse_id = bw.warehouse_id
|
||||
outer apply (
|
||||
select top 1 prefix as tray_type
|
||||
from bas_barcode_format bbf
|
||||
where bbf.prefix is not null
|
||||
and bbf.prefix != ''
|
||||
and bs.tray_code is not null
|
||||
and left(bs.tray_code, len(bbf.prefix)) = bbf.prefix
|
||||
order by len(bbf.prefix) desc
|
||||
) bbf
|
||||
where bsd.stock_id = #{stockId}
|
||||
and isnull(bsd.quantity, 0) - isnull(bsd.out_quantity, 0) > 0
|
||||
order by bsd.product_id, bsd.detail_id
|
||||
</select>
|
||||
|
||||
<update id="reserveStockDetailForOutbound" parameterType="java.util.Map">
|
||||
update bas_stock_detail
|
||||
set out_quantity = isnull(out_quantity, 0) + #{quantity},
|
||||
job_detail_id = #{jobDetailId},
|
||||
update_time = sysdatetime()
|
||||
where detail_id = #{stockDetailId}
|
||||
and stock_id = #{stockId}
|
||||
and isnull(quantity, 0) - isnull(out_quantity, 0) >= #{quantity}
|
||||
</update>
|
||||
</mapper>
|
||||
@@ -28,56 +28,68 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="remar" column="remar" />
|
||||
<result property="stockId" column="stock_id" />
|
||||
<result property="stockDetailId" column="stock_detail_id" />
|
||||
<result property="trayType" column="tray_type" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectOutStockoutDetailVo">
|
||||
select stockout_detail_id, stockout_id, lot_no, product_id, product_name, quantity, allot_num, assign_num, remark, status,
|
||||
line_n, item_cod, item_nam, spec_mode, uni, actual_qt, warehous, locatio, inventory_, source_doc_n, remar,
|
||||
stock_id, stock_detail_id
|
||||
from out_stockout_detail
|
||||
select d.stockout_detail_id, d.stockout_id, d.lot_no, d.product_id, d.product_name, d.quantity, d.allot_num, d.assign_num, d.remark, d.status,
|
||||
d.line_n, d.item_cod, d.item_nam, d.spec_mode, d.uni, d.actual_qt, d.warehous, d.locatio, d.inventory_, d.source_doc_n, d.remar,
|
||||
d.stock_id, d.stock_detail_id, bbf.tray_type
|
||||
from out_stockout_detail d
|
||||
left join bas_stock_detail bsd on d.stock_detail_id = bsd.detail_id
|
||||
left join bas_stock bs on bs.stock_id = COALESCE(NULLIF(d.stock_id, ''), bsd.stock_id)
|
||||
OUTER APPLY (
|
||||
select top 1 prefix as tray_type
|
||||
from bas_barcode_format bbf
|
||||
where bbf.prefix is not null
|
||||
and bbf.prefix != ''
|
||||
and bs.tray_code is not null
|
||||
and left(bs.tray_code, len(bbf.prefix)) = bbf.prefix
|
||||
order by len(bbf.prefix) desc
|
||||
) bbf
|
||||
</sql>
|
||||
<sql id="selectOutStockoutDetailFinishVo">
|
||||
select stockout_detail_id, stockout_id, lot_no, product_id, product_name, quantity, allot_num, assign_num, remark, status,
|
||||
line_n, item_cod, item_nam, spec_mode, uni, actual_qt, warehous, locatio, inventory_, source_doc_n, remar,
|
||||
stock_id, stock_detail_id
|
||||
from out_stockout_detail_finish
|
||||
select d.stockout_detail_id, d.stockout_id, d.lot_no, d.product_id, d.product_name, d.quantity, d.allot_num, d.assign_num, d.remark, d.status,
|
||||
d.line_n, d.item_cod, d.item_nam, d.spec_mode, d.uni, d.actual_qt, d.warehous, d.locatio, d.inventory_, d.source_doc_n, d.remar,
|
||||
d.stock_id, d.stock_detail_id, null as tray_type
|
||||
from out_stockout_detail_finish d
|
||||
</sql>
|
||||
|
||||
<select id="selectOutStockoutDetailList" parameterType="OutStockoutDetail" resultMap="OutStockoutDetailResult">
|
||||
<include refid="selectOutStockoutDetailVo"/>
|
||||
<where>
|
||||
<if test="stockoutId != null and stockoutId != ''"> and stockout_id = #{stockoutId}</if>
|
||||
<if test="lotNo != null and lotNo != ''"> and lot_no = #{lotNo}</if>
|
||||
<if test="productId != null and productId != ''"> and product_id = #{productId}</if>
|
||||
<if test="productName != null and productName != ''"> and product_name like concat('%', #{productName}, '%')</if>
|
||||
<if test="quantity != null "> and quantity = #{quantity}</if>
|
||||
<if test="allotNum != null "> and allot_num = #{allotNum}</if>
|
||||
<if test="assignNum != null "> and assign_num = #{assignNum}</if>
|
||||
<if test="itemCod != null and itemCod != ''"> and item_cod like concat('%', #{itemCod}, '%')</if>
|
||||
<if test="itemNam != null and itemNam != ''"> and item_nam like concat('%', #{itemNam}, '%')</if>
|
||||
<if test="stockoutId != null and stockoutId != ''"> and d.stockout_id = #{stockoutId}</if>
|
||||
<if test="lotNo != null and lotNo != ''"> and d.lot_no = #{lotNo}</if>
|
||||
<if test="productId != null and productId != ''"> and d.product_id = #{productId}</if>
|
||||
<if test="productName != null and productName != ''"> and d.product_name like concat('%', #{productName}, '%')</if>
|
||||
<if test="quantity != null "> and d.quantity = #{quantity}</if>
|
||||
<if test="allotNum != null "> and d.allot_num = #{allotNum}</if>
|
||||
<if test="assignNum != null "> and d.assign_num = #{assignNum}</if>
|
||||
<if test="itemCod != null and itemCod != ''"> and d.item_cod like concat('%', #{itemCod}, '%')</if>
|
||||
<if test="itemNam != null and itemNam != ''"> and d.item_nam like concat('%', #{itemNam}, '%')</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<select id="selectOutStockoutDetailByStockoutDetailId" parameterType="String" resultMap="OutStockoutDetailResult">
|
||||
<include refid="selectOutStockoutDetailVo"/>
|
||||
where stockout_detail_id = #{stockoutDetailId}
|
||||
where d.stockout_detail_id = #{stockoutDetailId}
|
||||
</select>
|
||||
<select id="selectOutStockoutDetailListByStockoutId" parameterType="String" resultMap="OutStockoutDetailResult">
|
||||
<include refid="selectOutStockoutDetailVo"/>
|
||||
where stockout_id = #{stockoutDetailId}
|
||||
where d.stockout_id = #{stockoutDetailId}
|
||||
</select>
|
||||
<select id="selectOutStockoutDetailFinishList" parameterType="OutStockoutDetail" resultMap="OutStockoutDetailResult">
|
||||
<include refid="selectOutStockoutDetailFinishVo"/>
|
||||
<where>
|
||||
<if test="stockoutId != null and stockoutId != ''"> and stockout_id = #{stockoutId}</if>
|
||||
<if test="lotNo != null and lotNo != ''"> and lot_no = #{lotNo}</if>
|
||||
<if test="productId != null and productId != ''"> and product_id = #{productId}</if>
|
||||
<if test="productName != null and productName != ''"> and product_name like concat('%', #{productName}, '%')</if>
|
||||
<if test="quantity != null "> and quantity = #{quantity}</if>
|
||||
<if test="allotNum != null "> and allot_num = #{allotNum}</if>
|
||||
<if test="assignNum != null "> and assign_num = #{assignNum}</if>
|
||||
<if test="itemCod != null and itemCod != ''"> and item_cod like concat('%', #{itemCod}, '%')</if>
|
||||
<if test="itemNam != null and itemNam != ''"> and item_nam like concat('%', #{itemNam}, '%')</if>
|
||||
<if test="stockoutId != null and stockoutId != ''"> and d.stockout_id = #{stockoutId}</if>
|
||||
<if test="lotNo != null and lotNo != ''"> and d.lot_no = #{lotNo}</if>
|
||||
<if test="productId != null and productId != ''"> and d.product_id = #{productId}</if>
|
||||
<if test="productName != null and productName != ''"> and d.product_name like concat('%', #{productName}, '%')</if>
|
||||
<if test="quantity != null "> and d.quantity = #{quantity}</if>
|
||||
<if test="allotNum != null "> and d.allot_num = #{allotNum}</if>
|
||||
<if test="assignNum != null "> and d.assign_num = #{assignNum}</if>
|
||||
<if test="itemCod != null and itemCod != ''"> and d.item_cod like concat('%', #{itemCod}, '%')</if>
|
||||
<if test="itemNam != null and itemNam != ''"> and d.item_nam like concat('%', #{itemNam}, '%')</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
@@ -164,6 +176,28 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
where stockout_detail_id = #{stockoutDetailId}
|
||||
</update>
|
||||
|
||||
<update id="rollbackAllotByJobId" parameterType="String">
|
||||
update d
|
||||
set d.allot_num = case
|
||||
when ISNULL(d.allot_num, 0) - ISNULL(jd.rollback_quantity, 0) < 0 then 0
|
||||
else ISNULL(d.allot_num, 0) - ISNULL(jd.rollback_quantity, 0)
|
||||
end,
|
||||
d.status = case
|
||||
when ISNULL(d.allot_num, 0) - ISNULL(jd.rollback_quantity, 0) <= 0 then '0'
|
||||
else d.status
|
||||
end
|
||||
from out_stockout_detail d
|
||||
inner join (
|
||||
select in_or_out_d_id as stockout_detail_id,
|
||||
sum(ISNULL(out_quantity, 0)) as rollback_quantity
|
||||
from mydo_job_detail
|
||||
where job_id = #{jobId}
|
||||
and in_or_out_d_id is not null
|
||||
and in_or_out_d_id != ''
|
||||
group by in_or_out_d_id
|
||||
) jd on jd.stockout_detail_id = d.stockout_detail_id
|
||||
</update>
|
||||
|
||||
<delete id="deleteOutStockoutDetailByStockoutDetailId" parameterType="String">
|
||||
delete from out_stockout_detail where stockout_detail_id = #{stockoutDetailId}
|
||||
</delete>
|
||||
|
||||
@@ -108,6 +108,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<select id="autoAssignOutStockoutByProc" parameterType="java.util.Map" statementType="CALLABLE">
|
||||
{call proc_out_auto_allocation(
|
||||
#{v_stockout_detail_id,mode=IN,jdbcType=VARCHAR},
|
||||
#{v_exit_id,mode=IN,jdbcType=VARCHAR},
|
||||
#{v_result,mode=OUT,jdbcType=INTEGER},
|
||||
#{v_resultString,mode=OUT,jdbcType=VARCHAR}
|
||||
)}
|
||||
@@ -252,11 +253,32 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
{call proc_out_hand_allocation(
|
||||
#{v_stockout_detail_id,mode=IN,jdbcType=VARCHAR},
|
||||
#{v_stock_id,mode=IN,jdbcType=VARCHAR},
|
||||
#{v_exit_id,mode=IN,jdbcType=VARCHAR},
|
||||
#{v_result,mode=OUT,jdbcType=INTEGER},
|
||||
#{v_resultString,mode=OUT,jdbcType=VARCHAR}
|
||||
)}
|
||||
</select>
|
||||
|
||||
<update id="reopenOutStockoutByExceptionJobId" parameterType="String">
|
||||
update o
|
||||
set o.status = '2',
|
||||
o.start_date = null
|
||||
from out_stockout o
|
||||
where o.status = '3'
|
||||
and exists (
|
||||
select 1
|
||||
from mydo_job_detail jd
|
||||
where jd.job_id = #{jobId}
|
||||
and jd.in_or_out_id = o.stockout_id
|
||||
)
|
||||
and not exists (
|
||||
select 1
|
||||
from mydo_job_detail active_jd
|
||||
where active_jd.in_or_out_id = o.stockout_id
|
||||
and active_jd.job_id != #{jobId}
|
||||
)
|
||||
</update>
|
||||
|
||||
<delete id="deleteOutStockoutByStockoutId" parameterType="String">
|
||||
delete from out_stockout where stockout_id = #{stockoutId}
|
||||
</delete>
|
||||
|
||||
@@ -50,18 +50,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<update id="lockInboundCargoSpace" parameterType="java.util.Map">
|
||||
update bas_cargo_space
|
||||
set status = '3',
|
||||
locked = 'Y',
|
||||
tray_code = #{v_tray_code}
|
||||
where cargo_space_id = (
|
||||
select top 1 cargo_space_id
|
||||
from bas_cargo_space with (UPDLOCK, READPAST, ROWLOCK)
|
||||
where warehouse_id = #{v_warehouse_id}
|
||||
and the_type = #{v_cargo_type}
|
||||
and (#{v_roadway_id} is null or #{v_roadway_id} = '' or roadway_id = #{v_roadway_id})
|
||||
and status = '1'
|
||||
and locked = 'N'
|
||||
and (tray_code is null or tray_code = '')
|
||||
order by the_floor asc, the_platoon asc, the_column asc
|
||||
from (
|
||||
select bcs.cargo_space_id,
|
||||
bcs.the_platoon,
|
||||
bcs.the_column,
|
||||
bcs.the_floor,
|
||||
count(1) over (partition by bcs.the_platoon) as platoon_empty_count
|
||||
from bas_cargo_space bcs with (UPDLOCK, READPAST, ROWLOCK)
|
||||
where bcs.warehouse_id = #{v_warehouse_id}
|
||||
and bcs.the_type = #{v_cargo_type}
|
||||
and (#{v_roadway_id} is null or #{v_roadway_id} = '' or bcs.roadway_id = #{v_roadway_id})
|
||||
and bcs.status = '1'
|
||||
and bcs.locked = 'N'
|
||||
and (bcs.tray_code is null or bcs.tray_code = '')
|
||||
) candidate
|
||||
order by platoon_empty_count desc, the_platoon asc, the_floor asc, the_column asc, cargo_space_id asc
|
||||
)
|
||||
</update>
|
||||
|
||||
@@ -72,19 +78,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
and tray_code = #{v_tray_code}
|
||||
and (#{v_roadway_id} is null or #{v_roadway_id} = '' or roadway_id = #{v_roadway_id})
|
||||
and status = '3'
|
||||
and locked = 'Y'
|
||||
order by the_floor asc, the_platoon asc, the_column asc
|
||||
</select>
|
||||
|
||||
<update id="releaseInboundCargoSpace" parameterType="java.util.Map">
|
||||
update bas_cargo_space
|
||||
set status = '1',
|
||||
locked = 'N',
|
||||
tray_code = null
|
||||
where warehouse_id = #{v_warehouse_id}
|
||||
and tray_code = #{v_tray_code}
|
||||
and status = '3'
|
||||
and locked = 'Y'
|
||||
</update>
|
||||
|
||||
<select id="outboundTaskList" parameterType="java.util.Map" resultType="java.util.HashMap">
|
||||
@@ -99,16 +102,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
isnull(j.exit_id, '') as portName,
|
||||
convert(varchar(19), j.create_time, 120) as createTime,
|
||||
j.status as status,
|
||||
case j.status
|
||||
when '1' then '未执行'
|
||||
when '2' then '待执行'
|
||||
when '3' then '执行中'
|
||||
j.execution_phase as executionPhase,
|
||||
case
|
||||
when j.execution_phase = '5' then '待扫码确认'
|
||||
when j.status = '1' then '未执行'
|
||||
when j.status = '2' then '待执行'
|
||||
when j.status = '3' then '执行中'
|
||||
else '待确认'
|
||||
end as statusName,
|
||||
case j.status
|
||||
when '1' then 'pending'
|
||||
when '2' then 'pending'
|
||||
when '3' then 'processing'
|
||||
case
|
||||
when j.execution_phase = '5' then 'pending'
|
||||
when j.status = '1' then 'pending'
|
||||
when j.status = '2' then 'pending'
|
||||
when j.status = '3' then 'processing'
|
||||
else 'pending'
|
||||
end as statusClass
|
||||
from mydo_job j
|
||||
@@ -116,38 +122,192 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
where j.in_or_out >= 20
|
||||
and j.status in ('1', '2', '3')
|
||||
and (#{taskNo} is null or #{taskNo} = '' or j.task_no = #{taskNo})
|
||||
group by j.job_id, j.task_no, j.exit_id, j.create_time, j.status
|
||||
group by j.job_id, j.task_no, j.exit_id, j.create_time, j.status, j.execution_phase
|
||||
order by j.create_time desc
|
||||
</select>
|
||||
|
||||
<select id="selectOutboundDetail" parameterType="java.util.Map" resultType="java.util.HashMap">
|
||||
select top 1 j.job_id as jobId,
|
||||
j.task_no as taskNo,
|
||||
jd.job_detail_id as jobDetailId,
|
||||
jd.stock_id as stockId,
|
||||
jd.stockin_detail_id as stockDetailId,
|
||||
jd.product_id as materialCode,
|
||||
jd.product_name as materialName,
|
||||
jd.lot_no as lotNo,
|
||||
jd.out_quantity as quantity,
|
||||
j.tray_code as trayCode,
|
||||
j.cargo_space_id as locationCode,
|
||||
j.cargo_space_name as locationName,
|
||||
j.warehouse_id as warehouseId,
|
||||
j.warehouse_name as warehouseName,
|
||||
j.execution_phase as executionPhase,
|
||||
isnull(j.exit_id, '') as portName,
|
||||
sum(isnull(jd.out_quantity, 0)) as quantity,
|
||||
count(1) as materialCount,
|
||||
'' as spec
|
||||
from mydo_job j
|
||||
inner join mydo_job_detail jd on j.job_id = jd.job_id
|
||||
where j.in_or_out >= 20
|
||||
and j.status in ('1', '2', '3')
|
||||
and (#{taskNo} is null or #{taskNo} = '' or j.task_no = #{taskNo})
|
||||
and (
|
||||
#{barcode} is null or #{barcode} = ''
|
||||
or j.task_no = #{barcode}
|
||||
or j.tray_code = #{barcode}
|
||||
or jd.stock_id = #{barcode}
|
||||
or jd.stockin_detail_id = #{barcode}
|
||||
or jd.product_id = #{barcode}
|
||||
)
|
||||
order by j.create_time desc, jd.job_detail_id
|
||||
and (#{barcode} is null or #{barcode} = '' or j.tray_code = #{barcode})
|
||||
group by j.job_id, j.task_no, j.tray_code, j.cargo_space_id, j.cargo_space_name,
|
||||
j.warehouse_id, j.warehouse_name, j.execution_phase, j.exit_id, j.create_time
|
||||
order by j.create_time desc
|
||||
</select>
|
||||
|
||||
<select id="selectOutboundTrayMaterials" parameterType="java.util.Map" resultType="java.util.HashMap">
|
||||
select jd.job_detail_id as jobDetailId,
|
||||
jd.stock_id as stockId,
|
||||
jd.stockin_detail_id as stockDetailId,
|
||||
jd.product_id as materialCode,
|
||||
jd.product_name as materialName,
|
||||
jd.lot_no as lotNo,
|
||||
jd.stock_quantity as stockQuantity,
|
||||
jd.out_quantity as quantity,
|
||||
isnull(jd.stock_quantity, 0) - isnull(jd.out_quantity, 0) as residualQuantity
|
||||
from mydo_job_detail jd
|
||||
where jd.job_id = #{jobId}
|
||||
order by jd.job_detail_id
|
||||
</select>
|
||||
|
||||
<select id="selectResidualTrayMaterials" parameterType="java.util.Map" resultType="java.util.HashMap">
|
||||
select bsd.detail_id as stockDetailId,
|
||||
bsd.stock_id as stockId,
|
||||
bsd.product_id as productId,
|
||||
bsd.product_id as materialCode,
|
||||
bsd.product_name as productName,
|
||||
bsd.product_name as materialName,
|
||||
bsd.lot_no as lotNo,
|
||||
isnull(bsd.quantity, 0) as quantity,
|
||||
bs.tray_code as trayCode,
|
||||
bs.warehouse_id as warehouseId,
|
||||
bs.warehouse_name as warehouseName,
|
||||
bs.cargo_space_id as cargoSpaceId,
|
||||
bcs.cargo_space_name as cargoSpaceName,
|
||||
bcs.roadway_id as roadwayId,
|
||||
bs.supplier_id as supplierId,
|
||||
bs.supplier_name as supplierName
|
||||
from bas_stock_detail bsd
|
||||
inner join bas_stock bs on bsd.stock_id = bs.stock_id
|
||||
left join bas_cargo_space bcs on bs.cargo_space_id = bcs.cargo_space_id
|
||||
where bs.tray_code = #{trayCode}
|
||||
and bs.warehouse_id = #{warehouseId}
|
||||
and isnull(bsd.quantity, 0) > 0
|
||||
order by bsd.detail_id
|
||||
</select>
|
||||
|
||||
<select id="selectTrayStockMaterials" parameterType="String" resultType="java.util.HashMap">
|
||||
select bsd.detail_id as stockDetailId,
|
||||
bsd.stock_id as stockId,
|
||||
bsd.product_id as productId,
|
||||
bsd.product_id as materialCode,
|
||||
bsd.product_name as productName,
|
||||
bsd.product_name as materialName,
|
||||
bsd.lot_no as lotNo,
|
||||
isnull(bsd.quantity, 0) as quantity,
|
||||
isnull(bsd.out_quantity, 0) as outQuantity,
|
||||
isnull(bsd.quantity, 0) - isnull(bsd.out_quantity, 0) as availableQuantity,
|
||||
bs.tray_code as trayCode,
|
||||
bs.stock_status as stockStatus,
|
||||
bs.product_status as productStatus,
|
||||
bs.warehouse_id as warehouseId,
|
||||
isnull(nullif(bs.warehouse_name, ''), bw.warehouse_name) as warehouseName,
|
||||
bs.cargo_space_id as cargoSpaceId,
|
||||
bcs.cargo_space_name as cargoSpaceName,
|
||||
bcs.roadway_id as roadwayId,
|
||||
bs.supplier_id as supplierId,
|
||||
bs.supplier_name as supplierName,
|
||||
convert(varchar(19), bs.in_date, 120) as inDate
|
||||
from bas_stock bs
|
||||
inner join bas_stock_detail bsd on bs.stock_id = bsd.stock_id
|
||||
left join bas_cargo_space bcs on bs.cargo_space_id = bcs.cargo_space_id
|
||||
left join bas_warehouse bw on bs.warehouse_id = bw.warehouse_id
|
||||
where bs.tray_code = #{trayCode}
|
||||
and isnull(bsd.quantity, 0) > 0
|
||||
order by bsd.detail_id
|
||||
</select>
|
||||
|
||||
<select id="selectAvailableEmptyTrayTypeCounts" parameterType="String" resultType="java.util.HashMap">
|
||||
select bbf.id as formatId,
|
||||
bbf.format_name as formatName,
|
||||
bbf.prefix as barcodePrefix,
|
||||
count(1) as quantity
|
||||
from bas_stock_detail bsd
|
||||
inner join bas_stock bs on bsd.stock_id = bs.stock_id
|
||||
inner join bas_barcode_format bbf
|
||||
on bbf.prefix is not null
|
||||
and bbf.prefix != ''
|
||||
and left(bs.tray_code, len(bbf.prefix)) = bbf.prefix
|
||||
where bs.warehouse_id = #{warehouseId}
|
||||
and bsd.product_id = 'KTP0001'
|
||||
and isnull(bsd.quantity, 0) - isnull(bsd.out_quantity, 0) > 0
|
||||
and bs.tray_code is not null
|
||||
and bs.tray_code != ''
|
||||
and not exists (
|
||||
select 1
|
||||
from bas_barcode_format bbf2
|
||||
where bbf2.prefix is not null
|
||||
and bbf2.prefix != ''
|
||||
and left(bs.tray_code, len(bbf2.prefix)) = bbf2.prefix
|
||||
and len(bbf2.prefix) > len(bbf.prefix)
|
||||
)
|
||||
group by bbf.id, bbf.format_name, bbf.prefix
|
||||
order by bbf.prefix
|
||||
</select>
|
||||
|
||||
<select id="countAvailableEmptyTrays" parameterType="java.util.Map" resultType="int">
|
||||
select count(1)
|
||||
from bas_stock_detail bsd
|
||||
inner join bas_stock bs on bsd.stock_id = bs.stock_id
|
||||
where bs.warehouse_id = #{warehouseId}
|
||||
and left(bs.tray_code, len(#{barcodePrefix})) = #{barcodePrefix}
|
||||
and bsd.product_id = 'KTP0001'
|
||||
and isnull(bsd.quantity, 0) - isnull(bsd.out_quantity, 0) > 0
|
||||
and bs.tray_code is not null
|
||||
and bs.tray_code != ''
|
||||
</select>
|
||||
|
||||
<select id="selectAvailableEmptyTrayStocks" parameterType="java.util.Map" resultType="java.util.HashMap">
|
||||
select top (#{limit})
|
||||
bsd.detail_id as stockDetailId,
|
||||
bsd.stock_id as stockId,
|
||||
bsd.lot_no as lotNo,
|
||||
bsd.product_id as productId,
|
||||
bsd.product_name as productName,
|
||||
isnull(bsd.quantity, 0) - isnull(bsd.out_quantity, 0) as availableQuantity,
|
||||
bs.tray_code as trayCode,
|
||||
bs.warehouse_id as warehouseId,
|
||||
isnull(nullif(bs.warehouse_name, ''), (select top 1 bw.warehouse_name from bas_warehouse bw where bw.warehouse_id = bs.warehouse_id)) as warehouseName,
|
||||
bs.cargo_space_id as cargoSpaceId,
|
||||
bcs.cargo_space_name as cargoSpaceName,
|
||||
bcs.roadway_id as roadwayId,
|
||||
bs.supplier_id as supplierId,
|
||||
bs.supplier_name as supplierName
|
||||
from bas_stock_detail bsd with (UPDLOCK, HOLDLOCK)
|
||||
inner join bas_stock bs with (UPDLOCK, HOLDLOCK) on bsd.stock_id = bs.stock_id
|
||||
left join bas_cargo_space bcs with (UPDLOCK, HOLDLOCK) on bs.cargo_space_id = bcs.cargo_space_id
|
||||
where bs.warehouse_id = #{warehouseId}
|
||||
and left(bs.tray_code, len(#{barcodePrefix})) = #{barcodePrefix}
|
||||
and bsd.product_id = 'KTP0001'
|
||||
and isnull(bsd.quantity, 0) - isnull(bsd.out_quantity, 0) > 0
|
||||
and bs.tray_code is not null
|
||||
and bs.tray_code != ''
|
||||
order by bs.in_date asc, bs.cargo_space_id asc, bsd.detail_id asc
|
||||
</select>
|
||||
|
||||
<update id="reserveEmptyTrayStockDetail" parameterType="java.util.Map">
|
||||
update bas_stock_detail
|
||||
set out_quantity = isnull(out_quantity, 0) + #{quantity},
|
||||
job_detail_id = #{jobDetailId},
|
||||
update_time = sysdatetime()
|
||||
where detail_id = #{stockDetailId}
|
||||
and stock_id = #{stockId}
|
||||
and product_id = 'KTP0001'
|
||||
and isnull(quantity, 0) - isnull(out_quantity, 0) >= #{quantity}
|
||||
</update>
|
||||
|
||||
<update id="lockEmptyTrayCargoSpace" parameterType="java.util.Map">
|
||||
update bcs
|
||||
set status = '4',
|
||||
tray_code = bs.tray_code
|
||||
from bas_cargo_space bcs
|
||||
inner join bas_stock bs on bcs.cargo_space_id = bs.cargo_space_id
|
||||
where bs.stock_id = #{stockId}
|
||||
and bs.cargo_space_id = #{cargoSpaceId}
|
||||
</update>
|
||||
</mapper>
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.ruoyi.api.aspect;
|
||||
|
||||
import com.ruoyi.api.service.ApiLogService;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ApiLogAspectTest {
|
||||
|
||||
@Mock
|
||||
private ApiLogService apiLogService;
|
||||
|
||||
@Mock
|
||||
private JoinPoint joinPoint;
|
||||
|
||||
private ApiLogAspect aspect;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
aspect = new ApiLogAspect();
|
||||
ReflectionTestUtils.setField(aspect, "apiLogService", apiLogService);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/api/wcs/exception");
|
||||
request.addHeader("X-Trace-Id", "TRACE-001");
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
RequestContextHolder.resetRequestAttributes();
|
||||
}
|
||||
|
||||
@Test
|
||||
void recordsReturnedAjaxResultFailureAsFailedApiLog() {
|
||||
when(joinPoint.getArgs()).thenReturn(new Object[0]);
|
||||
com.ruoyi.common.annotation.ApiLog annotation = apiLogAnnotation();
|
||||
|
||||
aspect.doBefore(joinPoint, annotation);
|
||||
aspect.doAfterReturning(joinPoint, annotation, new AjaxResult(1, "未找到对应作业"));
|
||||
|
||||
ArgumentCaptor<com.ruoyi.api.domain.ApiLog> logCaptor =
|
||||
ArgumentCaptor.forClass(com.ruoyi.api.domain.ApiLog.class);
|
||||
verify(apiLogService).record(logCaptor.capture());
|
||||
com.ruoyi.api.domain.ApiLog apiLog = logCaptor.getValue();
|
||||
assertThat(apiLog.getStatus()).isEqualTo("FAIL");
|
||||
assertThat(apiLog.getHttpStatus()).isEqualTo(200);
|
||||
assertThat(apiLog.getErrorMsg()).isEqualTo("未找到对应作业");
|
||||
assertThat(apiLog.getResponseBody()).contains("未找到对应作业");
|
||||
}
|
||||
|
||||
private com.ruoyi.common.annotation.ApiLog apiLogAnnotation() {
|
||||
return new com.ruoyi.common.annotation.ApiLog() {
|
||||
@Override
|
||||
public String interfaceName() {
|
||||
return "WCS-作业异常处理";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sysCode() {
|
||||
return "WCS";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String direction() {
|
||||
return "INBOUND";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSaveRequestData() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSaveResponseData() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] excludeParamNames() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Annotation> annotationType() {
|
||||
return com.ruoyi.common.annotation.ApiLog.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,10 @@ package com.ruoyi.api.client;
|
||||
import com.ruoyi.api.config.ExternalProp;
|
||||
import com.ruoyi.api.constant.MsgTypeEnum;
|
||||
import com.ruoyi.api.domain.ApiResponse;
|
||||
import com.ruoyi.api.exception.BizException;
|
||||
import com.ruoyi.in.job.domain.MydoJob;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -16,8 +16,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
class WcsClientTest {
|
||||
|
||||
@Test
|
||||
void inboundAndOutboundJobPayloadsUseSameWcsReceiveEndpoint() {
|
||||
RecordingWcsClient client = new RecordingWcsClient();
|
||||
void wcsMethodsReturnMockValuesWithoutPostingToRealWcs() {
|
||||
NoPostWcsClient client = new NoPostWcsClient(true);
|
||||
|
||||
MydoJob inboundJob = new MydoJob();
|
||||
inboundJob.setJobId("RK001");
|
||||
@@ -27,31 +27,108 @@ class WcsClientTest {
|
||||
outboundJob.setJobId("CK001");
|
||||
outboundJob.setInOrOut(20L);
|
||||
|
||||
client.sendPutawayTask(inboundJob);
|
||||
client.sendRetrieveTask(outboundJob);
|
||||
|
||||
assertThat(client.urls).containsExactly(
|
||||
"http://wcs/wcs/api/receiveWmsJob",
|
||||
"http://wcs/wcs/api/receiveWmsJob");
|
||||
assertThat(client.messageTypes).containsExactly(MsgTypeEnum.JSON, MsgTypeEnum.JSON);
|
||||
assertThat(client.sendPutawayTask("RK001", "A0101", "TP001", "10").getData())
|
||||
.isEqualTo("MOCK_WCS_PUTAWAY_OK");
|
||||
assertThat(client.sendPutawayTask(inboundJob).getData())
|
||||
.isEqualTo("MOCK_WCS_PUTAWAY_OK");
|
||||
assertThat(client.sendRetrieveTask("CK001", "A0101", "M001", "1").getData())
|
||||
.isEqualTo("MOCK_WCS_RETRIEVE_OK");
|
||||
assertThat(client.sendRetrieveTask(outboundJob).getData())
|
||||
.isEqualTo("MOCK_WCS_RETRIEVE_OK");
|
||||
assertThat(client.sendCraneMove("CR01", "A0101", "A0102", "MOVE").getData())
|
||||
.isEqualTo("MOCK_WCS_CRANE_MOVE_OK");
|
||||
assertThat(client.cancelTask("TASK001").getData())
|
||||
.isEqualTo("MOCK_WCS_CANCEL_OK");
|
||||
assertThat(client.queryDeviceStatus("CRANE").getData()).hasSize(1);
|
||||
assertThat(client.queryDeviceStatus("CRANE").getData().get(0).get("status"))
|
||||
.isEqualTo("MOCK_ONLINE");
|
||||
}
|
||||
|
||||
private static class RecordingWcsClient extends WcsClient {
|
||||
private final ExternalProp externalProp;
|
||||
private final List<String> urls = new ArrayList<>();
|
||||
private final List<MsgTypeEnum> messageTypes = new ArrayList<>();
|
||||
@Test
|
||||
void wcsMethodsPostToConfiguredEndpointsWhenMockDisabled() {
|
||||
RecordingWcsClient client = new RecordingWcsClient(false);
|
||||
|
||||
private RecordingWcsClient() {
|
||||
externalProp = new ExternalProp();
|
||||
ExternalProp.SystemConfig wcs = new ExternalProp.SystemConfig();
|
||||
wcs.setBaseUrl("http://wcs");
|
||||
Map<String, String> apis = new HashMap<>();
|
||||
apis.put("sendPutawayTask", "/wcs/api/receiveWmsJob");
|
||||
apis.put("sendRetrieveTask", "/task/dispatch");
|
||||
wcs.setApis(apis);
|
||||
Map<String, ExternalProp.SystemConfig> systems = new HashMap<>();
|
||||
systems.put("WCS", wcs);
|
||||
externalProp.setSystems(systems);
|
||||
MydoJob inboundJob = new MydoJob();
|
||||
inboundJob.setJobId("RK001");
|
||||
inboundJob.setInOrOut(1L);
|
||||
|
||||
MydoJob outboundJob = new MydoJob();
|
||||
outboundJob.setJobId("CK001");
|
||||
outboundJob.setInOrOut(20L);
|
||||
|
||||
assertThat(client.sendPutawayTask("RK001", "A0101", "TP001", "10").getData())
|
||||
.isEqualTo("ACCEPTED");
|
||||
assertThat(client.sendPutawayTask(inboundJob).getData())
|
||||
.isEqualTo("ACCEPTED");
|
||||
assertThat(client.sendRetrieveTask("CK001", "A0101", "M001", "1").getData())
|
||||
.isEqualTo("ACCEPTED");
|
||||
assertThat(client.sendRetrieveTask(outboundJob).getData())
|
||||
.isEqualTo("ACCEPTED");
|
||||
assertThat(client.queryDeviceStatus("CRANE").getData().get(0).get("status"))
|
||||
.isEqualTo("ONLINE");
|
||||
assertThat(client.sendCraneMove("CR01", "A0101", "A0102", "MOVE").getData())
|
||||
.isEqualTo("ACCEPTED");
|
||||
assertThat(client.cancelTask("TASK001").getData())
|
||||
.isEqualTo("ACCEPTED");
|
||||
|
||||
assertThat(client.calls).extracting(Call::url).containsExactly(
|
||||
"http://wcs/wcs/api/receiveWmsJob",
|
||||
"http://wcs/wcs/api/receiveWmsJob",
|
||||
"http://wcs/task/dispatch",
|
||||
"http://wcs/wcs/api/receiveWmsJob",
|
||||
"http://wcs/device/status",
|
||||
"http://wcs/crane/move",
|
||||
"http://wcs/task/cancel");
|
||||
assertThat(client.calls).extracting(Call::messageType).containsExactly(
|
||||
MsgTypeEnum.TEXT,
|
||||
MsgTypeEnum.JSON,
|
||||
MsgTypeEnum.TEXT,
|
||||
MsgTypeEnum.JSON,
|
||||
MsgTypeEnum.TEXT,
|
||||
MsgTypeEnum.TEXT,
|
||||
MsgTypeEnum.TEXT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void jsonAckTreatsCodeZeroAsSuccess() {
|
||||
RecordingWcsClient client = new RecordingWcsClient(false,
|
||||
"{\"code\":0,\"message\":\"ok\",\"data\":\"ACCEPTED\"}");
|
||||
|
||||
ApiResponse<String> response = client.sendPutawayTask(inboundJob());
|
||||
|
||||
assertThat(response.isSuccess()).isTrue();
|
||||
assertThat(response.getData()).isEqualTo("ACCEPTED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void jsonAckTreatsCodeOneAsFailure() {
|
||||
RecordingWcsClient client = new RecordingWcsClient(false,
|
||||
"{\"code\":1,\"message\":\"任务拒绝\"}");
|
||||
|
||||
ApiResponse<String> response = client.sendPutawayTask(inboundJob());
|
||||
|
||||
assertThat(response.isSuccess()).isFalse();
|
||||
assertThat(response.getCode()).isEqualTo("BIZ_FAIL");
|
||||
assertThat(response.getMessage()).isEqualTo("WCS拒绝: 任务拒绝");
|
||||
}
|
||||
|
||||
@Test
|
||||
void jsonAckNoLongerTreatsLegacyCodeTwoHundredAsSuccess() {
|
||||
RecordingWcsClient client = new RecordingWcsClient(false,
|
||||
"{\"code\":200,\"message\":\"旧成功码\"}");
|
||||
|
||||
ApiResponse<String> response = client.sendPutawayTask(inboundJob());
|
||||
|
||||
assertThat(response.isSuccess()).isFalse();
|
||||
assertThat(response.getCode()).isEqualTo("BIZ_FAIL");
|
||||
assertThat(response.getMessage()).isEqualTo("WCS拒绝: 旧成功码");
|
||||
}
|
||||
|
||||
private static class NoPostWcsClient extends WcsClient {
|
||||
private final ExternalProp externalProp;
|
||||
|
||||
private NoPostWcsClient(boolean mockEnabled) {
|
||||
externalProp = testExternalProp(mockEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,9 +139,76 @@ class WcsClientTest {
|
||||
@Override
|
||||
protected <T> ApiResponse<T> post(String url, String body, String interfaceName,
|
||||
ResponseParser<T> parser, MsgTypeEnum messageType) {
|
||||
urls.add(url);
|
||||
messageTypes.add(messageType);
|
||||
return ApiResponse.ok(null);
|
||||
throw new AssertionError("WCS local mock must not post to real WCS");
|
||||
}
|
||||
}
|
||||
|
||||
private static class RecordingWcsClient extends WcsClient {
|
||||
private final ExternalProp externalProp;
|
||||
private final List<Call> calls = new java.util.ArrayList<>();
|
||||
private final String jsonAckBody;
|
||||
|
||||
private RecordingWcsClient(boolean mockEnabled) {
|
||||
this(mockEnabled, null);
|
||||
}
|
||||
|
||||
private RecordingWcsClient(boolean mockEnabled, String jsonAckBody) {
|
||||
externalProp = testExternalProp(mockEnabled);
|
||||
this.jsonAckBody = jsonAckBody;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExternalProp externalProp() {
|
||||
return externalProp;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> ApiResponse<T> post(String url, String body, String interfaceName,
|
||||
ResponseParser<T> parser, MsgTypeEnum messageType) {
|
||||
calls.add(new Call(url, body, interfaceName, messageType));
|
||||
try {
|
||||
String rawBody;
|
||||
if ("WCS-查询设备状态".equals(interfaceName)) {
|
||||
rawBody = "OK|CR01,ONLINE,Y,";
|
||||
} else if (jsonAckBody != null && MsgTypeEnum.JSON.equals(messageType)) {
|
||||
rawBody = jsonAckBody;
|
||||
} else {
|
||||
rawBody = "OK|ACCEPTED";
|
||||
}
|
||||
return ApiResponse.ok(parser.parse(rawBody));
|
||||
} catch (BizException e) {
|
||||
return ApiResponse.fail("BIZ_FAIL", e.getMessage());
|
||||
} catch (Exception e) {
|
||||
return ApiResponse.fail("PARSE_FAIL", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static MydoJob inboundJob() {
|
||||
MydoJob job = new MydoJob();
|
||||
job.setJobId("RK001");
|
||||
job.setInOrOut(1L);
|
||||
return job;
|
||||
}
|
||||
|
||||
private static ExternalProp testExternalProp(boolean mockEnabled) {
|
||||
ExternalProp externalProp = new ExternalProp();
|
||||
ExternalProp.SystemConfig wcs = new ExternalProp.SystemConfig();
|
||||
wcs.setBaseUrl("http://wcs");
|
||||
wcs.setMockEnabled(mockEnabled);
|
||||
Map<String, String> apis = new HashMap<>();
|
||||
apis.put("sendPutawayTask", "/wcs/api/receiveWmsJob");
|
||||
apis.put("sendRetrieveTask", "/task/dispatch");
|
||||
apis.put("queryDeviceStatus", "/device/status");
|
||||
apis.put("sendCraneMove", "/crane/move");
|
||||
apis.put("cancelTask", "/task/cancel");
|
||||
wcs.setApis(apis);
|
||||
Map<String, ExternalProp.SystemConfig> systems = new HashMap<>();
|
||||
systems.put("WCS", wcs);
|
||||
externalProp.setSystems(systems);
|
||||
return externalProp;
|
||||
}
|
||||
|
||||
private record Call(String url, String body, String interfaceName, MsgTypeEnum messageType) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.ruoyi.api.controller;
|
||||
|
||||
import com.ruoyi.api.client.WcsClient;
|
||||
import com.ruoyi.api.domain.ApiResponse;
|
||||
import com.ruoyi.api.domain.WcsInboundCompleteRequest;
|
||||
import com.ruoyi.api.domain.WcsJobExceptionRequest;
|
||||
import com.ruoyi.api.domain.WcsOutboundCompleteRequest;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.in.job.domain.MydoJob;
|
||||
import com.ruoyi.in.job.service.IMydoJobService;
|
||||
import com.ruoyi.pda.service.PdaService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ExternalApiControllerWcsCodeTest {
|
||||
|
||||
@Mock
|
||||
private WcsClient wcsClient;
|
||||
|
||||
@Mock
|
||||
private IMydoJobService mydoJobService;
|
||||
|
||||
@Mock
|
||||
private PdaService pdaService;
|
||||
|
||||
@InjectMocks
|
||||
private ExternalApiController controller;
|
||||
|
||||
@Test
|
||||
void wcsClientTriggerEndpointsReturnCodeZeroWhenWcsCallSucceeds() {
|
||||
when(wcsClient.sendPutawayTask("RK001", "A0101", "TP001", "NORMAL"))
|
||||
.thenReturn(ApiResponse.ok("PUTAWAY_OK"));
|
||||
when(wcsClient.sendRetrieveTask("CK001", "A0101", "M001", "2"))
|
||||
.thenReturn(ApiResponse.ok("RETRIEVE_OK"));
|
||||
when(wcsClient.queryDeviceStatus("ALL"))
|
||||
.thenReturn(ApiResponse.ok(List.of(Map.of("deviceId", "CR01", "status", "ONLINE"))));
|
||||
when(wcsClient.sendCraneMove("CR01", "A0101", "A0102", "MOVE"))
|
||||
.thenReturn(ApiResponse.ok("MOVE_OK"));
|
||||
when(wcsClient.cancelTask("TASK001"))
|
||||
.thenReturn(ApiResponse.ok("CANCEL_OK"));
|
||||
|
||||
assertWcsSuccess(controller.sendPutawayTask("RK001", "A0101", "TP001", "NORMAL"));
|
||||
assertWcsSuccess(controller.sendRetrieveTask("CK001", "A0101", "M001", "2"));
|
||||
assertWcsSuccess(controller.queryDeviceStatus("ALL"));
|
||||
assertWcsSuccess(controller.sendCraneMove("CR01", "A0101", "A0102", "MOVE"));
|
||||
assertWcsSuccess(controller.cancelWcsTask("TASK001"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void wcsClientTriggerEndpointsReturnCodeOneWhenWcsCallFails() {
|
||||
when(wcsClient.sendRetrieveTask("CK001", "A0101", "M001", "2"))
|
||||
.thenReturn(ApiResponse.fail("WCS_NG", "WCS拒绝任务"));
|
||||
|
||||
AjaxResult result = controller.sendRetrieveTask("CK001", "A0101", "M001", "2");
|
||||
|
||||
assertWcsError(result, "WCS拒绝任务");
|
||||
}
|
||||
|
||||
@Test
|
||||
void wcsCallbackEndpointsReturnCodeZeroWhenBusinessHandlingSucceeds() {
|
||||
MydoJob inboundJob = new MydoJob();
|
||||
inboundJob.setJobId("RK001");
|
||||
inboundJob.setInOrOut(1L);
|
||||
when(mydoJobService.selectMydoJobByJobId("RK001")).thenReturn(inboundJob);
|
||||
when(mydoJobService.completeInJobFromWcs("RK001")).thenReturn(AjaxResult.success("入库完成"));
|
||||
|
||||
MydoJob outboundJob = new MydoJob();
|
||||
outboundJob.setJobId("CK001");
|
||||
outboundJob.setInOrOut(20L);
|
||||
when(mydoJobService.selectMydoJobByJobId("CK001")).thenReturn(outboundJob);
|
||||
when(mydoJobService.completeOutJobFromWcs("CK001")).thenReturn(AjaxResult.success("出库完成"));
|
||||
|
||||
WcsInboundCompleteRequest inboundRequest = new WcsInboundCompleteRequest();
|
||||
inboundRequest.setJobId("RK001");
|
||||
WcsOutboundCompleteRequest outboundRequest = new WcsOutboundCompleteRequest();
|
||||
outboundRequest.setJobId("CK001");
|
||||
|
||||
assertWcsSuccess(controller.completeInbound(inboundRequest));
|
||||
assertWcsSuccess(controller.completeOutbound(outboundRequest));
|
||||
}
|
||||
|
||||
@Test
|
||||
void wcsCallbackEndpointsReturnCodeOneWhenBusinessHandlingFails() {
|
||||
MydoJob outboundJob = new MydoJob();
|
||||
outboundJob.setJobId("CK001");
|
||||
outboundJob.setInOrOut(20L);
|
||||
when(mydoJobService.selectMydoJobByJobId("CK001")).thenReturn(outboundJob);
|
||||
when(mydoJobService.completeOutJobFromWcs("CK001")).thenReturn(AjaxResult.error("出库完成失败"));
|
||||
|
||||
WcsOutboundCompleteRequest request = new WcsOutboundCompleteRequest();
|
||||
request.setJobId("CK001");
|
||||
|
||||
assertWcsError(controller.completeOutbound(request), "出库完成失败");
|
||||
}
|
||||
|
||||
@Test
|
||||
void wcsCallbackEndpointsRejectMismatchedJobTypes() {
|
||||
MydoJob outboundJob = new MydoJob();
|
||||
outboundJob.setJobId("CK001");
|
||||
outboundJob.setInOrOut(20L);
|
||||
when(mydoJobService.selectMydoJobByJobId("RK001")).thenReturn(outboundJob);
|
||||
|
||||
MydoJob inboundJob = new MydoJob();
|
||||
inboundJob.setJobId("RK002");
|
||||
inboundJob.setInOrOut(1L);
|
||||
when(mydoJobService.selectMydoJobByJobId("RK002")).thenReturn(inboundJob);
|
||||
|
||||
WcsInboundCompleteRequest inboundRequest = new WcsInboundCompleteRequest();
|
||||
inboundRequest.setJobId("RK001");
|
||||
WcsOutboundCompleteRequest outboundRequest = new WcsOutboundCompleteRequest();
|
||||
outboundRequest.setJobId("RK002");
|
||||
|
||||
assertWcsError(controller.completeInbound(inboundRequest), "非法入库作业");
|
||||
assertWcsError(controller.completeOutbound(outboundRequest), "非法出库作业");
|
||||
verify(mydoJobService, never()).completeInJobFromWcs("RK001");
|
||||
verify(mydoJobService, never()).completeOutJobFromWcs("RK002");
|
||||
}
|
||||
|
||||
@Test
|
||||
void wcsCallbackValidationErrorsReturnCodeOne() {
|
||||
WcsJobExceptionRequest request = new WcsJobExceptionRequest();
|
||||
|
||||
assertWcsError(controller.handleWcsException(request), "异常类型不能为空");
|
||||
}
|
||||
|
||||
private void assertWcsSuccess(AjaxResult result) {
|
||||
assertThat(result.get(AjaxResult.CODE_TAG)).isEqualTo(0);
|
||||
}
|
||||
|
||||
private void assertWcsError(AjaxResult result, String messagePart) {
|
||||
assertThat(result.get(AjaxResult.CODE_TAG)).isEqualTo(1);
|
||||
assertThat(String.valueOf(result.get(AjaxResult.MSG_TAG))).contains(messagePart);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.ruoyi.bas.cargospace.mapper;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class BasCargoSpaceMapperSqlTest {
|
||||
|
||||
@Test
|
||||
void putOccupiedReallocationUsesTrayRuleCargoTypeInsteadOfOriginalCargoSpaceType() throws Exception {
|
||||
String xml = Files.readString(resolveMapperXml());
|
||||
String selectSql = section(xml, "<select id=\"selectAvailableInboundCargoSpaceInRoadway\"", "</select>");
|
||||
|
||||
assertThat(selectSql)
|
||||
.contains("bas_tray_rule")
|
||||
.contains("allowed_cargo_type")
|
||||
.contains("left(#{trayCode}, len(btr.tray_type)) = btr.tray_type")
|
||||
.doesNotContain("cs2.the_type");
|
||||
}
|
||||
|
||||
@Test
|
||||
void automaticCargoSpaceStatusUpdatesDoNotChangeManualLockedFlag() throws Exception {
|
||||
String xml = Files.readString(resolveMapperXml());
|
||||
|
||||
for (String id : List.of(
|
||||
"markOutboundAssignedByStockId",
|
||||
"markOutboundAssignedByStockRef",
|
||||
"markOutboundAssignedByStockoutDetailId",
|
||||
"markOutboundAssignedByCargoSpaceId",
|
||||
"releaseOutboundCargoSpace",
|
||||
"markOutboundNeedCheck",
|
||||
"markInboundNeedCheck")) {
|
||||
String updateSql = section(xml, "<update id=\"" + id + "\"", "</update>");
|
||||
assertThat(updateSql)
|
||||
.doesNotContain("locked = 'Y'")
|
||||
.doesNotContain("locked = 'N'");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void putOccupiedReallocationChoosesPlatoonWithMostAvailableCargoSpaces() throws Exception {
|
||||
String xml = Files.readString(resolveMapperXml());
|
||||
String selectSql = section(xml, "<select id=\"selectAvailableInboundCargoSpaceInRoadway\"", "</select>");
|
||||
|
||||
assertThat(selectSql)
|
||||
.contains("count(1) over (partition by bcs.the_platoon) as platoon_empty_count")
|
||||
.contains("order by platoon_empty_count desc");
|
||||
}
|
||||
|
||||
private Path resolveMapperXml() {
|
||||
return Stream.of(
|
||||
Path.of("src/main/resources/mapper/bas/BasCargoSpaceMapper.xml"),
|
||||
Path.of("ruoyi-admin/src/main/resources/mapper/bas/BasCargoSpaceMapper.xml")
|
||||
)
|
||||
.filter(Files::exists)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
private String section(String text, String startMarker, String endMarker) {
|
||||
int start = text.indexOf(startMarker);
|
||||
assertThat(start).isGreaterThanOrEqualTo(0);
|
||||
int end = text.indexOf(endMarker, start);
|
||||
assertThat(end).isGreaterThan(start);
|
||||
return text.substring(start, end);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.ruoyi.bas.stationpoint;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class BasStationPointConfigurationTest {
|
||||
|
||||
@Test
|
||||
void pointSizeConfigurationIncludesCommonPointType() throws Exception {
|
||||
String domain = Files.readString(resolvePath(
|
||||
"src/main/java/com/ruoyi/bas/stationpoint/domain/BasStationPoint.java",
|
||||
"ruoyi-admin/src/main/java/com/ruoyi/bas/stationpoint/domain/BasStationPoint.java"));
|
||||
String vue = Files.readString(resolvePath(
|
||||
"../ruoyi-ui/src/views/bas/stationpoint/index.vue",
|
||||
"ruoyi-ui/src/views/bas/stationpoint/index.vue"));
|
||||
String sql = Files.readString(resolvePath(
|
||||
"../sql/bas_station_point.sql",
|
||||
"sql/bas_station_point.sql"));
|
||||
|
||||
assertThat(domain)
|
||||
.contains("1-大点位,2-小点位,3-通用点位")
|
||||
.contains("1=大点位,2=小点位,3=通用点位");
|
||||
assertThat(vue)
|
||||
.contains("<el-option label=\"通用点位\" value=\"3\" />")
|
||||
.contains("if (size === '3') return '通用点位'");
|
||||
assertThat(sql)
|
||||
.contains("1=大点位,2=小点位,3=通用点位");
|
||||
}
|
||||
|
||||
private Path resolvePath(String modulePath, String rootPath) {
|
||||
return Stream.of(Path.of(modulePath), Path.of(rootPath))
|
||||
.filter(Files::exists)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.ruoyi.bas.stock.service.impl;
|
||||
|
||||
import com.ruoyi.bas.stock.domain.BasStock;
|
||||
import com.ruoyi.bas.stock.domain.BasStockDetail;
|
||||
import com.ruoyi.bas.stock.mapper.BasStockDetailMapper;
|
||||
import com.ruoyi.bas.stock.mapper.BasStockMapper;
|
||||
import com.ruoyi.bas.stockoper.mapper.BasStockOperRecordMapper;
|
||||
import com.ruoyi.system.mapper.SysDeptMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class BasStockServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private BasStockMapper basStockMapper;
|
||||
|
||||
@Mock
|
||||
private BasStockDetailMapper basStockDetailMapper;
|
||||
|
||||
@Mock
|
||||
private BasStockOperRecordMapper basStockOperRecordMapper;
|
||||
|
||||
@Mock
|
||||
private SysDeptMapper sysDeptMapper;
|
||||
|
||||
@InjectMocks
|
||||
private BasStockServiceImpl service;
|
||||
|
||||
@Test
|
||||
void insertBasStockGeneratesKcMillisecondTimestampStockIdWhenMissing() {
|
||||
BasStock stock = new BasStock();
|
||||
stock.setWarehouseId("WH01");
|
||||
stock.setStockStatus("1");
|
||||
when(basStockMapper.insertBasStock(stock)).thenReturn(1);
|
||||
|
||||
int rows = service.insertBasStock(stock);
|
||||
|
||||
assertThat(rows).isEqualTo(1);
|
||||
ArgumentCaptor<BasStock> stockCaptor = ArgumentCaptor.forClass(BasStock.class);
|
||||
verify(basStockMapper).insertBasStock(stockCaptor.capture());
|
||||
assertThat(stockCaptor.getValue().getStockId()).matches("KC\\d{17}");
|
||||
verify(basStockDetailMapper, never()).insertBasStockDetail(org.mockito.ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void insertBasStockCreatesInitialDetailIdFromStockIdAndDetailSequence() {
|
||||
BasStock stock = new BasStock();
|
||||
stock.setStockId("KC20260624152234021");
|
||||
stock.setProductId("MAT001");
|
||||
stock.setProductName("物料1");
|
||||
stock.setQuantity(new BigDecimal("6"));
|
||||
when(basStockMapper.insertBasStock(stock)).thenReturn(1);
|
||||
|
||||
int rows = service.insertBasStock(stock);
|
||||
|
||||
assertThat(rows).isEqualTo(1);
|
||||
ArgumentCaptor<BasStockDetail> detailCaptor = ArgumentCaptor.forClass(BasStockDetail.class);
|
||||
verify(basStockDetailMapper).insertBasStockDetail(detailCaptor.capture());
|
||||
assertThat(detailCaptor.getValue().getDetailId()).isEqualTo("KC20260624152234021-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void insertBasStockDetailCreatesDetailIdFromStockIdAndDetailSequence() {
|
||||
BasStockDetail detail = new BasStockDetail();
|
||||
detail.setStockId("KC20260624152234021");
|
||||
detail.setProductId("MAT002");
|
||||
detail.setProductName("物料2");
|
||||
detail.setQuantity(new BigDecimal("4"));
|
||||
when(basStockDetailMapper.insertBasStockDetail(any(BasStockDetail.class))).thenReturn(1);
|
||||
|
||||
int rows = service.insertBasStockDetail(detail);
|
||||
|
||||
assertThat(rows).isEqualTo(1);
|
||||
ArgumentCaptor<BasStockDetail> detailCaptor = ArgumentCaptor.forClass(BasStockDetail.class);
|
||||
verify(basStockDetailMapper).insertBasStockDetail(detailCaptor.capture());
|
||||
assertThat(detailCaptor.getValue().getDetailId()).isEqualTo("KC20260624152234021-1");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.ruoyi.in.job.mapper;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class MydoJobMapperSqlTest {
|
||||
|
||||
@Test
|
||||
void inboundJobListFiltersByWarehouseIdSoWarehouseDropdownCanQueryReliably() throws Exception {
|
||||
String xml = Files.readString(resolveMapperXml());
|
||||
String selectInList = section(xml, "<select id=\"selectInMydoJobList\"", "</select>");
|
||||
|
||||
assertThat(selectInList)
|
||||
.contains("<if test=\"warehouseId != null and warehouseId != ''\"> and warehouse_id = #{warehouseId}</if>");
|
||||
}
|
||||
|
||||
@Test
|
||||
void inboundCompletionProcedureUsesMillisecondTimestampStockId() throws Exception {
|
||||
String script = Files.readString(resolveSqlPath()).replace("\r\n", "\n");
|
||||
String proc = section(script, "CREATE PROCEDURE [dbo].[proc_complete_in_job]", "END\nGO");
|
||||
|
||||
assertThat(proc)
|
||||
.contains("@now DATETIME2(3) = SYSDATETIME()")
|
||||
.contains("FORMAT(@now, 'yyyyMMddHHmmssfff')")
|
||||
.contains("SET @stock_id = 'KC' + FORMAT(@now, 'yyyyMMddHHmmssfff')");
|
||||
}
|
||||
|
||||
@Test
|
||||
void jobDictUpdateScriptAddsWaitingForPdaScanExecutionPhase() throws Exception {
|
||||
String script = Files.readString(resolveJobDictScriptPath());
|
||||
|
||||
assertThat(script)
|
||||
.contains("dict_type = N'mydo_job_execution_phase' AND dict_value = N'5'")
|
||||
.contains("N'等待扫码确认'")
|
||||
.contains("N'warning'")
|
||||
.doesNotContain("INSERT INTO sys_dict_data (dict_code");
|
||||
}
|
||||
|
||||
@Test
|
||||
void inboundCancelProcedureAllowsReturnInboundJobs() throws Exception {
|
||||
String script = Files.readString(resolveCancelInJobScriptPath()).replace("\r\n", "\n");
|
||||
String proc = section(script, "CREATE PROCEDURE [dbo].[proc_hand_delete_in_job]", "END\nGO");
|
||||
|
||||
assertThat(proc)
|
||||
.contains("AND in_or_out < 20")
|
||||
.doesNotContain("AND in_or_out = 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void inboundCompletionProcedureDoesNotChangeManualLockedFlag() throws Exception {
|
||||
String script = Files.readString(resolveSqlPath()).replace("\r\n", "\n");
|
||||
String proc = section(script, "CREATE PROCEDURE [dbo].[proc_complete_in_job]", "END\nGO");
|
||||
String cargoSpaceUpdate = section(proc, "UPDATE bas_cargo_space", "WHERE cargo_space_id = @cargo_space_id");
|
||||
|
||||
assertThat(cargoSpaceUpdate)
|
||||
.contains("SET status = '2'")
|
||||
.doesNotContain("locked =");
|
||||
}
|
||||
|
||||
@Test
|
||||
void inboundCancelProcedureDoesNotChangeManualLockedFlag() throws Exception {
|
||||
String script = Files.readString(resolveCancelInJobScriptPath()).replace("\r\n", "\n");
|
||||
String proc = section(script, "CREATE PROCEDURE [dbo].[proc_hand_delete_in_job]", "END\nGO");
|
||||
String cargoSpaceUpdate = section(proc, "UPDATE dbo.bas_cargo_space", "DELETE FROM dbo.mydo_job_detail");
|
||||
|
||||
assertThat(cargoSpaceUpdate)
|
||||
.contains("SET status = '1'")
|
||||
.contains("AND status = '3'")
|
||||
.doesNotContain("locked =")
|
||||
.doesNotContain("AND locked = 'Y'");
|
||||
}
|
||||
|
||||
private Path resolveMapperXml() {
|
||||
return Stream.of(
|
||||
Path.of("src/main/resources/mapper/in/MydoJobMapper.xml"),
|
||||
Path.of("ruoyi-admin/src/main/resources/mapper/in/MydoJobMapper.xml")
|
||||
)
|
||||
.filter(Files::exists)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
private Path resolveSqlPath() {
|
||||
return Stream.of(
|
||||
Path.of("../sql/bas_stock_master_detail.sql"),
|
||||
Path.of("sql/bas_stock_master_detail.sql")
|
||||
)
|
||||
.filter(Files::exists)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
private Path resolveJobDictScriptPath() {
|
||||
return Stream.of(
|
||||
Path.of("../sql/20260624_update_job_dicts.sql"),
|
||||
Path.of("sql/20260624_update_job_dicts.sql")
|
||||
)
|
||||
.filter(Files::exists)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
private Path resolveCancelInJobScriptPath() {
|
||||
return Stream.of(
|
||||
Path.of("../sql/proc_hand_delete_in_job.sql"),
|
||||
Path.of("sql/proc_hand_delete_in_job.sql")
|
||||
)
|
||||
.filter(Files::exists)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
private String section(String text, String startMarker, String endMarker) {
|
||||
int start = text.indexOf(startMarker);
|
||||
assertThat(start).isGreaterThanOrEqualTo(0);
|
||||
int end = text.indexOf(endMarker, start);
|
||||
assertThat(end).isGreaterThan(start);
|
||||
return text.substring(start, end);
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,37 @@ package com.ruoyi.in.job.service.impl;
|
||||
|
||||
import com.ruoyi.api.client.WcsClient;
|
||||
import com.ruoyi.api.domain.ApiResponse;
|
||||
import com.ruoyi.bas.cargospace.domain.BasCargoSpace;
|
||||
import com.ruoyi.bas.cargospace.mapper.BasCargoSpaceMapper;
|
||||
import com.ruoyi.bas.stock.mapper.BasStockDetailMapper;
|
||||
import com.ruoyi.bas.stock.mapper.BasStockMapper;
|
||||
import com.ruoyi.bas.stationpoint.service.IBasStationPointService;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
import com.ruoyi.in.job.domain.MydoJob;
|
||||
import com.ruoyi.in.job.mapper.MydoJobMapper;
|
||||
import com.ruoyi.in.jobdetail.mapper.MydoJobDetailMapper;
|
||||
import com.ruoyi.in.stockin.mapper.InStockinMapper;
|
||||
import com.ruoyi.out.stockout.mapper.OutStockoutMapper;
|
||||
import com.ruoyi.out.stockoutdetail.mapper.OutStockoutDetailMapper;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
@@ -33,12 +50,44 @@ class MydoJobServiceImplTest {
|
||||
@Mock
|
||||
private IBasStationPointService basStationPointService;
|
||||
|
||||
@Mock
|
||||
private BasCargoSpaceMapper basCargoSpaceMapper;
|
||||
|
||||
@Mock
|
||||
private BasStockMapper basStockMapper;
|
||||
|
||||
@Mock
|
||||
private BasStockDetailMapper basStockDetailMapper;
|
||||
|
||||
@Mock
|
||||
private OutStockoutDetailMapper outStockoutDetailMapper;
|
||||
|
||||
@Mock
|
||||
private OutStockoutMapper outStockoutMapper;
|
||||
|
||||
@Mock
|
||||
private MydoJobDetailMapper mydoJobDetailMapper;
|
||||
|
||||
@Mock
|
||||
private WcsClient wcsClient;
|
||||
|
||||
@InjectMocks
|
||||
private MydoJobServiceImpl service;
|
||||
|
||||
@BeforeEach
|
||||
void setUpSecurityContext() {
|
||||
SysUser user = new SysUser();
|
||||
user.setUserName("tester");
|
||||
LoginUser loginUser = new LoginUser(10L, 20L, user, Collections.emptySet());
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void clearSecurityContext() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
void pushOutJobToWcsRejectsInboundJob() {
|
||||
MydoJob job = new MydoJob();
|
||||
@@ -88,7 +137,276 @@ class MydoJobServiceImplTest {
|
||||
verify(mydoJobMapper).updateMydoJob(updateCaptor.capture());
|
||||
MydoJob update = updateCaptor.getValue();
|
||||
assertThat(update.getJobId()).isEqualTo("CK002");
|
||||
assertThat(update.getStatus()).isEqualTo("2");
|
||||
assertThat(update.getStatus()).isEqualTo("3");
|
||||
assertThat(update.getExecutionPhase()).isEqualTo("1");
|
||||
assertThat(update.getStartTime()).isNotBlank();
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancelOutJobAsksWcsBeforeCancellingPushedJob() {
|
||||
MydoJob job = new MydoJob();
|
||||
job.setJobId("CK003");
|
||||
job.setTaskNo("TASK003");
|
||||
job.setStatus("2");
|
||||
job.setInOrOut(20L);
|
||||
when(mydoJobMapper.selectMydoJobByJobId("CK003")).thenReturn(job);
|
||||
when(wcsClient.cancelTask("TASK003")).thenReturn(ApiResponse.ok("OK"));
|
||||
doAnswer(invocation -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
java.util.HashMap<String, Object> map = invocation.getArgument(0);
|
||||
map.put("v_result", "10");
|
||||
return null;
|
||||
}).when(mydoJobMapper).cancelOutJob(any(java.util.HashMap.class));
|
||||
|
||||
AjaxResult result = service.cancelJob("CK003", "2");
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
verify(wcsClient).cancelTask("TASK003");
|
||||
verify(mydoJobMapper).cancelOutJob(any(java.util.HashMap.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancelOutJobStopsWhenWcsRejectsPushedJobCancellation() {
|
||||
MydoJob job = new MydoJob();
|
||||
job.setJobId("CK004");
|
||||
job.setTaskNo("TASK004");
|
||||
job.setStatus("2");
|
||||
job.setInOrOut(20L);
|
||||
when(mydoJobMapper.selectMydoJobByJobId("CK004")).thenReturn(job);
|
||||
when(wcsClient.cancelTask("TASK004")).thenReturn(ApiResponse.fail("WCS_NG", "任务已执行不能取消"));
|
||||
|
||||
AjaxResult result = service.cancelJob("CK004", "2");
|
||||
|
||||
assertThat(result.isError()).isTrue();
|
||||
assertThat(result.get(AjaxResult.MSG_TAG)).isEqualTo("WCS拒绝作废:任务已执行不能取消");
|
||||
verify(wcsClient).cancelTask("TASK004");
|
||||
verify(mydoJobMapper, never()).cancelOutJob(any(java.util.HashMap.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void completeOutJobFromWcsMarksJobWaitingForPdaScanAndReleasesCargoSpace() {
|
||||
MydoJob job = new MydoJob();
|
||||
job.setJobId("CK005");
|
||||
job.setStatus("3");
|
||||
job.setInOrOut(20L);
|
||||
job.setCargoSpaceId("CS001");
|
||||
job.setExitId("OUT01");
|
||||
when(mydoJobMapper.selectMydoJobByJobId("CK005")).thenReturn(job);
|
||||
when(mydoJobMapper.updateMydoJob(any(MydoJob.class))).thenReturn(1);
|
||||
when(basCargoSpaceMapper.releaseOutboundCargoSpace("CS001")).thenReturn(1);
|
||||
|
||||
AjaxResult result = service.completeOutJobFromWcs("CK005");
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
ArgumentCaptor<MydoJob> updateCaptor = ArgumentCaptor.forClass(MydoJob.class);
|
||||
verify(mydoJobMapper).updateMydoJob(updateCaptor.capture());
|
||||
MydoJob update = updateCaptor.getValue();
|
||||
assertThat(update.getJobId()).isEqualTo("CK005");
|
||||
assertThat(update.getStatus()).isNull();
|
||||
assertThat(update.getExecutionPhase()).isEqualTo("5");
|
||||
assertThat(update.getFinishTime()).isNull();
|
||||
verify(basCargoSpaceMapper).releaseOutboundCargoSpace("CS001");
|
||||
verify(mydoJobMapper, never()).makeSure(any(java.util.HashMap.class));
|
||||
verifyNoInteractions(basStationPointService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void completeInJobFromWcsRejectsOutboundJobType() {
|
||||
MydoJob job = new MydoJob();
|
||||
job.setJobId("CK-WRONG");
|
||||
job.setInOrOut(20L);
|
||||
when(mydoJobMapper.selectMydoJobByJobId("CK-WRONG")).thenReturn(job);
|
||||
|
||||
AjaxResult result = service.completeInJobFromWcs("CK-WRONG");
|
||||
|
||||
assertThat(result.isError()).isTrue();
|
||||
assertThat(result.get(AjaxResult.MSG_TAG)).isEqualTo("非法入库作业");
|
||||
verify(mydoJobMapper, never()).selectStockinIdsByJobId("CK-WRONG");
|
||||
verify(mydoJobMapper, never()).completeJob(any(java.util.HashMap.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void completeOutJobFromWcsRejectsInboundJobType() {
|
||||
MydoJob job = new MydoJob();
|
||||
job.setJobId("RK-WRONG");
|
||||
job.setInOrOut(1L);
|
||||
when(mydoJobMapper.selectMydoJobByJobId("RK-WRONG")).thenReturn(job);
|
||||
|
||||
AjaxResult result = service.completeOutJobFromWcs("RK-WRONG");
|
||||
|
||||
assertThat(result.isError()).isTrue();
|
||||
assertThat(result.get(AjaxResult.MSG_TAG)).isEqualTo("非法出库作业");
|
||||
verify(mydoJobMapper, never()).updateMydoJob(any(MydoJob.class));
|
||||
verifyNoInteractions(basCargoSpaceMapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
void manualCompleteOutJobDoesNotReleaseCargoSpaceAfterProcedureCompletes() {
|
||||
MydoJob job = new MydoJob();
|
||||
job.setJobId("CK006");
|
||||
job.setStatus("3");
|
||||
job.setInOrOut(20L);
|
||||
job.setCargoSpaceId("CS002");
|
||||
job.setExitId("OUT02");
|
||||
when(mydoJobMapper.selectMydoJobByJobId("CK006")).thenReturn(job);
|
||||
doAnswer(invocation -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
java.util.HashMap<String, Object> map = invocation.getArgument(0);
|
||||
map.put("v_result", "10");
|
||||
return null;
|
||||
}).when(mydoJobMapper).makeSure(any(java.util.HashMap.class));
|
||||
|
||||
AjaxResult result = service.completeJob("CK006", "2");
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
verify(mydoJobMapper).makeSure(any(java.util.HashMap.class));
|
||||
verify(basCargoSpaceMapper, never()).releaseOutboundCargoSpace("CS002");
|
||||
verify(basStationPointService).releaseBasStationPoint("OUT02");
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleWcsPickEmptyExceptionDeletesAssociatedStockAndRollsBackDocumentAllocation() {
|
||||
MydoJob job = new MydoJob();
|
||||
job.setJobId("CK007");
|
||||
job.setStatus("3");
|
||||
job.setInOrOut(20L);
|
||||
job.setCargoSpaceId("CS003");
|
||||
job.setExitId("OUT03");
|
||||
when(mydoJobMapper.selectMydoJobByJobId("CK007")).thenReturn(job);
|
||||
when(mydoJobMapper.selectStockIdsByJobId("CK007")).thenReturn(Collections.singletonList("ST001"));
|
||||
when(mydoJobMapper.selectStockoutDetailIdsByJobId("CK007")).thenReturn(Collections.singletonList("CKD001-1"));
|
||||
when(basCargoSpaceMapper.markOutboundNeedCheck("CS003")).thenReturn(1);
|
||||
when(outStockoutDetailMapper.rollbackAllotByJobId("CK007")).thenReturn(1);
|
||||
when(basStockDetailMapper.deleteBasStockDetailByStockIds(any(String[].class))).thenReturn(1);
|
||||
when(basStockMapper.deleteBasStockByStockIds(any(String[].class))).thenReturn(1);
|
||||
when(outStockoutMapper.reopenOutStockoutByExceptionJobId("CK007")).thenReturn(1);
|
||||
when(mydoJobDetailMapper.deleteMydoJobDetailByJobId("CK007")).thenReturn(1);
|
||||
when(mydoJobMapper.deleteMydoJobByJobId("CK007")).thenReturn(1);
|
||||
|
||||
AjaxResult result = service.handleWcsPickEmptyException("CK007");
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.get(AjaxResult.MSG_TAG)).isEqualTo("取货无货异常处理完成");
|
||||
verify(basCargoSpaceMapper).markOutboundNeedCheck("CS003");
|
||||
verify(outStockoutDetailMapper).rollbackAllotByJobId("CK007");
|
||||
verify(basStockDetailMapper).deleteBasStockDetailByStockIds(argThat(ids ->
|
||||
java.util.Arrays.equals(ids, new String[] {"ST001"})));
|
||||
verify(basStockMapper).deleteBasStockByStockIds(argThat(ids ->
|
||||
java.util.Arrays.equals(ids, new String[] {"ST001"})));
|
||||
verify(basStockDetailMapper, never()).rollbackOutboundQuantityByJobId("CK007");
|
||||
verify(basStockMapper, never()).refreshBasStockSummary("ST001");
|
||||
verify(basStockMapper, never()).markExceptionByJobId("CK007");
|
||||
verify(outStockoutMapper).reopenOutStockoutByExceptionJobId("CK007");
|
||||
verify(mydoJobDetailMapper).deleteMydoJobDetailByJobId("CK007");
|
||||
verify(mydoJobMapper).deleteMydoJobByJobId("CK007");
|
||||
verify(basStationPointService).releaseBasStationPoint("OUT03");
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleWcsPickEmptyExceptionWithoutStockDetailsStillCompletes() {
|
||||
MydoJob job = new MydoJob();
|
||||
job.setJobId("CK008");
|
||||
job.setStatus("3");
|
||||
job.setInOrOut(20L);
|
||||
job.setCargoSpaceId("CS008");
|
||||
job.setExitId("OUT08");
|
||||
when(mydoJobMapper.selectMydoJobByJobId("CK008")).thenReturn(job);
|
||||
when(mydoJobMapper.selectStockIdsByJobId("CK008")).thenReturn(Collections.emptyList());
|
||||
when(basCargoSpaceMapper.markOutboundNeedCheck("CS008")).thenReturn(1);
|
||||
when(mydoJobDetailMapper.deleteMydoJobDetailByJobId("CK008")).thenReturn(1);
|
||||
when(mydoJobMapper.deleteMydoJobByJobId("CK008")).thenReturn(1);
|
||||
|
||||
AjaxResult result = service.handleWcsPickEmptyException("CK008");
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.get(AjaxResult.MSG_TAG)).isEqualTo("取货无货异常处理完成");
|
||||
verify(basCargoSpaceMapper).markOutboundNeedCheck("CS008");
|
||||
verify(mydoJobDetailMapper).deleteMydoJobDetailByJobId("CK008");
|
||||
verify(mydoJobMapper).deleteMydoJobByJobId("CK008");
|
||||
verify(basStationPointService).releaseBasStationPoint("OUT08");
|
||||
verifyNoInteractions(outStockoutDetailMapper);
|
||||
verifyNoInteractions(basStockDetailMapper);
|
||||
verifyNoInteractions(basStockMapper);
|
||||
verifyNoInteractions(outStockoutMapper);
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleWcsPickEmptyExceptionForLocationOutboundWithoutStockoutDocumentSkipsDocumentRollback() {
|
||||
MydoJob job = new MydoJob();
|
||||
job.setJobId("CK009");
|
||||
job.setStatus("3");
|
||||
job.setInOrOut(20L);
|
||||
job.setCargoSpaceId("CS009");
|
||||
job.setExitId("OUT09");
|
||||
when(mydoJobMapper.selectMydoJobByJobId("CK009")).thenReturn(job);
|
||||
when(mydoJobMapper.selectStockIdsByJobId("CK009")).thenReturn(Collections.singletonList("ST009"));
|
||||
when(basCargoSpaceMapper.markOutboundNeedCheck("CS009")).thenReturn(1);
|
||||
when(basStockDetailMapper.deleteBasStockDetailByStockIds(any(String[].class))).thenReturn(1);
|
||||
when(basStockMapper.deleteBasStockByStockIds(any(String[].class))).thenReturn(1);
|
||||
when(mydoJobDetailMapper.deleteMydoJobDetailByJobId("CK009")).thenReturn(1);
|
||||
when(mydoJobMapper.deleteMydoJobByJobId("CK009")).thenReturn(1);
|
||||
|
||||
AjaxResult result = service.handleWcsPickEmptyException("CK009");
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
verify(basStockDetailMapper).deleteBasStockDetailByStockIds(argThat(ids ->
|
||||
java.util.Arrays.equals(ids, new String[] {"ST009"})));
|
||||
verify(basStockMapper).deleteBasStockByStockIds(argThat(ids ->
|
||||
java.util.Arrays.equals(ids, new String[] {"ST009"})));
|
||||
verify(basStockDetailMapper, never()).rollbackOutboundQuantityByJobId("CK009");
|
||||
verify(basStockMapper, never()).refreshBasStockSummary("ST009");
|
||||
verify(basStockMapper, never()).markExceptionByJobId("CK009");
|
||||
verifyNoInteractions(outStockoutDetailMapper);
|
||||
verify(outStockoutMapper, never()).reopenOutStockoutByExceptionJobId("CK009");
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleWcsPutOccupiedExceptionMarksOldLocationAndReturnsReassignedInboundJob() {
|
||||
MydoJob job = new MydoJob();
|
||||
job.setJobId("RK002");
|
||||
job.setStatus("3");
|
||||
job.setInOrOut(10L);
|
||||
job.setTrayCode("TRAY002");
|
||||
job.setWarehouseId("WH01");
|
||||
job.setRoadwayId("R01");
|
||||
job.setCargoSpaceId("OLD-CS");
|
||||
job.setCargoSpaceName("旧货位");
|
||||
|
||||
MydoJob updatedJob = new MydoJob();
|
||||
updatedJob.setJobId("RK002");
|
||||
updatedJob.setStatus("3");
|
||||
updatedJob.setInOrOut(10L);
|
||||
updatedJob.setTrayCode("TRAY002");
|
||||
updatedJob.setWarehouseId("WH01");
|
||||
updatedJob.setRoadwayId("R01");
|
||||
updatedJob.setCargoSpaceId("NEW-CS");
|
||||
updatedJob.setCargoSpaceName("新货位");
|
||||
|
||||
BasCargoSpace newCargoSpace = new BasCargoSpace();
|
||||
newCargoSpace.setCargoSpaceId("NEW-CS");
|
||||
newCargoSpace.setCargoSpaceName("新货位");
|
||||
newCargoSpace.setRoadwayId("R01");
|
||||
|
||||
when(mydoJobMapper.selectMydoJobByJobId("RK002")).thenReturn(job, updatedJob);
|
||||
when(basCargoSpaceMapper.selectAvailableInboundCargoSpaceInRoadway(job)).thenReturn(newCargoSpace);
|
||||
when(basCargoSpaceMapper.updateBasCargoSpace(any(BasCargoSpace.class))).thenReturn(1);
|
||||
when(basCargoSpaceMapper.markInboundNeedCheck("OLD-CS")).thenReturn(1);
|
||||
when(mydoJobMapper.updateMydoJob(any(MydoJob.class))).thenReturn(1);
|
||||
|
||||
AjaxResult result = service.handleWcsPutOccupiedException("RK002");
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.get(AjaxResult.DATA_TAG)).isSameAs(updatedJob);
|
||||
verify(basCargoSpaceMapper).markInboundNeedCheck("OLD-CS");
|
||||
verify(basCargoSpaceMapper).updateBasCargoSpace(argThat(cargoSpace ->
|
||||
"NEW-CS".equals(cargoSpace.getCargoSpaceId())
|
||||
&& "3".equals(cargoSpace.getStatus())
|
||||
&& cargoSpace.getLocked() == null
|
||||
&& "TRAY002".equals(cargoSpace.getTrayCode())));
|
||||
verify(mydoJobMapper).updateMydoJob(argThat(update ->
|
||||
"RK002".equals(update.getJobId())
|
||||
&& "R01".equals(update.getRoadwayId())
|
||||
&& "NEW-CS".equals(update.getCargoSpaceId())
|
||||
&& "新货位".equals(update.getCargoSpaceName())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.ruoyi.in.stockin.service.impl;
|
||||
|
||||
import com.ruoyi.common.core.domain.entity.SysDept;
|
||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
import com.ruoyi.bas.id.job.service.JobIdService;
|
||||
import com.ruoyi.in.sotckindetail.domain.InStockinDetail;
|
||||
import com.ruoyi.in.sotckindetail.mapper.InStockinDetailMapper;
|
||||
import com.ruoyi.in.stockin.domain.InStockinSaveDTO;
|
||||
import com.ruoyi.in.stockin.mapper.InStockinMapper;
|
||||
import com.ruoyi.system.mapper.SysDeptMapper;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class InStockinServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private InStockinMapper inStockinMapper;
|
||||
|
||||
@Mock
|
||||
private InStockinDetailMapper inStockinDetailMapper;
|
||||
|
||||
@Mock
|
||||
private SysDeptMapper sysDeptMapper;
|
||||
|
||||
@Mock
|
||||
private JobIdService jobIdService;
|
||||
|
||||
@InjectMocks
|
||||
private InStockinServiceImpl service;
|
||||
|
||||
@BeforeEach
|
||||
void setUpSecurityContext() {
|
||||
SysUser user = new SysUser();
|
||||
user.setUserName("tester");
|
||||
LoginUser loginUser = new LoginUser(10L, 20L, user, Collections.emptySet());
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void clearSecurityContext() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveWithDetailsUsesItemCodeAndItemNameForProductFields() {
|
||||
when(jobIdService.getId("RKD")).thenReturn("RKD001");
|
||||
SysDept dept = new SysDept();
|
||||
dept.setDeptName("测试部门");
|
||||
when(sysDeptMapper.selectDeptById(20L)).thenReturn(dept);
|
||||
|
||||
InStockinDetail detail = new InStockinDetail();
|
||||
detail.setProductId("测试物料名称");
|
||||
detail.setProductName("测试物料名称");
|
||||
detail.setItemCod("MAT001");
|
||||
detail.setItemNam("测试物料名称");
|
||||
detail.setQuantity(new BigDecimal("6"));
|
||||
|
||||
InStockinSaveDTO saveDTO = new InStockinSaveDTO();
|
||||
saveDTO.setWarehouseId("WH01");
|
||||
saveDTO.setWarehouseName("一号仓");
|
||||
saveDTO.setDetails(List.of(detail));
|
||||
|
||||
String stockinId = service.saveInStockinWithDetails(saveDTO);
|
||||
|
||||
assertThat(stockinId).isEqualTo("RKD001");
|
||||
verify(inStockinMapper).insertInStockin(saveDTO);
|
||||
ArgumentCaptor<InStockinDetail> detailCaptor = ArgumentCaptor.forClass(InStockinDetail.class);
|
||||
verify(inStockinDetailMapper).insertInStockinDetail(detailCaptor.capture());
|
||||
InStockinDetail insertedDetail = detailCaptor.getValue();
|
||||
assertThat(insertedDetail.getStockinDetailId()).isEqualTo("RKD001-1");
|
||||
assertThat(insertedDetail.getStockinId()).isEqualTo("RKD001");
|
||||
assertThat(insertedDetail.getProductId()).isEqualTo("MAT001");
|
||||
assertThat(insertedDetail.getProductName()).isEqualTo("测试物料名称");
|
||||
assertThat(insertedDetail.getItemCod()).isEqualTo("MAT001");
|
||||
assertThat(insertedDetail.getItemNam()).isEqualTo("测试物料名称");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.ruoyi.kpi.service.impl;
|
||||
|
||||
import com.ruoyi.bas.cargospace.mapper.BasCargoSpaceMapper;
|
||||
import com.ruoyi.bas.stock.mapper.BasStockDetailMapper;
|
||||
import com.ruoyi.in.job.mapper.MydoJobMapper;
|
||||
import com.ruoyi.kpi.domain.InAnalysisData;
|
||||
import com.ruoyi.kpi.domain.InAnalysisDataQuery;
|
||||
import com.ruoyi.kpi.domain.OutAnalysisData;
|
||||
import com.ruoyi.kpi.domain.OutAnalysisDataQuery;
|
||||
import com.ruoyi.kpi.mapper.KpiMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class KpiServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private KpiMapper kpiMapper;
|
||||
|
||||
@Mock
|
||||
private BasCargoSpaceMapper basCargoSpaceMapper;
|
||||
|
||||
@Mock
|
||||
private BasStockDetailMapper basStockDetailMapper;
|
||||
|
||||
@Mock
|
||||
private MydoJobMapper mydoJobMapper;
|
||||
|
||||
@InjectMocks
|
||||
private KpiServiceImpl service;
|
||||
|
||||
@Test
|
||||
void inboundTrendGroupsNullRoadwayAsUnassignedAndSumsDuplicateDates() {
|
||||
InAnalysisData nullRoadway = new InAnalysisData();
|
||||
nullRoadway.setDateDay("2026-06-24");
|
||||
nullRoadway.setRoadwayId(null);
|
||||
nullRoadway.setCount(2L);
|
||||
|
||||
InAnalysisData blankRoadway = new InAnalysisData();
|
||||
blankRoadway.setDateDay("2026-06-24");
|
||||
blankRoadway.setRoadwayId(" ");
|
||||
blankRoadway.setCount(3L);
|
||||
|
||||
InAnalysisData roadwayA = new InAnalysisData();
|
||||
roadwayA.setDateDay("2026-06-25");
|
||||
roadwayA.setRoadwayId("A");
|
||||
roadwayA.setCount(4L);
|
||||
|
||||
InAnalysisData roadwayADuplicate = new InAnalysisData();
|
||||
roadwayADuplicate.setDateDay("2026-06-25");
|
||||
roadwayADuplicate.setRoadwayId("A");
|
||||
roadwayADuplicate.setCount(6L);
|
||||
|
||||
InAnalysisDataQuery query = new InAnalysisDataQuery();
|
||||
query.setStartTime("2026-06-24");
|
||||
query.setEndTime("2026-06-25");
|
||||
when(mydoJobMapper.selectInMydoJobFinishListGroupByWarehouseId(query))
|
||||
.thenReturn(List.of(nullRoadway, blankRoadway, roadwayA, roadwayADuplicate));
|
||||
|
||||
Map<String, Object> result = service.selectInboundTrend(query);
|
||||
|
||||
assertThat(result.get("xAxis")).isEqualTo(List.of("2026-06-24", "2026-06-25"));
|
||||
assertThat(result.get("series")).asList()
|
||||
.anySatisfy(series -> {
|
||||
Map<?, ?> item = (Map<?, ?>) series;
|
||||
assertThat(item.get("name")).isEqualTo("巷道未分配");
|
||||
assertThat(item.get("data")).isEqualTo(List.of(5L, 0L));
|
||||
})
|
||||
.anySatisfy(series -> {
|
||||
Map<?, ?> item = (Map<?, ?>) series;
|
||||
assertThat(item.get("name")).isEqualTo("巷道A");
|
||||
assertThat(item.get("data")).isEqualTo(List.of(0L, 10L));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void outboundTrendGroupsNullRoadwayAsUnassigned() {
|
||||
OutAnalysisData data = new OutAnalysisData();
|
||||
data.setDateDay("2026-06-25");
|
||||
data.setRoadwayId(null);
|
||||
data.setCount(7L);
|
||||
|
||||
OutAnalysisDataQuery query = new OutAnalysisDataQuery();
|
||||
query.setStartTime("2026-06-25");
|
||||
query.setEndTime("2026-06-25");
|
||||
when(mydoJobMapper.selectOutMydoJobFinishListGroupByWarehouseId(query))
|
||||
.thenReturn(List.of(data));
|
||||
|
||||
Map<String, Object> result = service.selectOutboundTrend(query);
|
||||
|
||||
assertThat(result.get("xAxis")).isEqualTo(List.of("2026-06-25"));
|
||||
assertThat(result.get("series")).asList()
|
||||
.singleElement()
|
||||
.satisfies(series -> {
|
||||
Map<?, ?> item = (Map<?, ?>) series;
|
||||
assertThat(item.get("name")).isEqualTo("巷道未分配");
|
||||
assertThat(item.get("data")).isEqualTo(List.of(7L));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package com.ruoyi.out.location.service.impl;
|
||||
|
||||
import com.ruoyi.bas.cargospace.mapper.BasCargoSpaceMapper;
|
||||
import com.ruoyi.bas.id.job.service.JobIdService;
|
||||
import com.ruoyi.bas.id.taskno.service.TaskNoService;
|
||||
import com.ruoyi.bas.stationpoint.domain.BasStationPoint;
|
||||
import com.ruoyi.bas.stationpoint.service.IBasStationPointService;
|
||||
import com.ruoyi.bas.stock.domain.BasStockDetail;
|
||||
import com.ruoyi.bas.stock.mapper.BasStockMapper;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.in.job.domain.MydoJob;
|
||||
import com.ruoyi.in.job.service.IMydoJobService;
|
||||
import com.ruoyi.in.jobdetail.domain.MydoJobDetail;
|
||||
import com.ruoyi.in.jobdetail.service.IMydoJobDetailService;
|
||||
import com.ruoyi.out.location.domain.LocationOutboundGenerateRequest;
|
||||
import com.ruoyi.out.location.domain.LocationOutboundStock;
|
||||
import com.ruoyi.out.location.mapper.LocationOutboundMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class LocationOutboundServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private LocationOutboundMapper locationOutboundMapper;
|
||||
|
||||
@Mock
|
||||
private BasCargoSpaceMapper basCargoSpaceMapper;
|
||||
|
||||
@Mock
|
||||
private BasStockMapper basStockMapper;
|
||||
|
||||
@Mock
|
||||
private IBasStationPointService basStationPointService;
|
||||
|
||||
@Mock
|
||||
private IMydoJobService mydoJobService;
|
||||
|
||||
@Mock
|
||||
private IMydoJobDetailService mydoJobDetailService;
|
||||
|
||||
@Mock
|
||||
private JobIdService jobIdService;
|
||||
|
||||
@Mock
|
||||
private TaskNoService taskNoService;
|
||||
|
||||
@InjectMocks
|
||||
private LocationOutboundServiceImpl service;
|
||||
|
||||
@Test
|
||||
void generateWholeTrayOutboundJobReservesEveryAvailableStockDetailAndLocksCargoSpace() {
|
||||
LocationOutboundStock stock = new LocationOutboundStock();
|
||||
stock.setStockId("KC001");
|
||||
stock.setCargoSpaceId("CS001");
|
||||
stock.setCargoSpaceName("01排001列01层");
|
||||
stock.setCargoSpaceStatus("2");
|
||||
stock.setTrayCode("A001");
|
||||
stock.setTrayType("A");
|
||||
stock.setWarehouseId("WH01");
|
||||
stock.setWarehouseName("一号仓");
|
||||
stock.setRoadwayId("R01");
|
||||
stock.setSupplierId("S001");
|
||||
stock.setSupplierName("供应商1");
|
||||
stock.setAvailableQuantity(new BigDecimal("12"));
|
||||
when(locationOutboundMapper.selectLocationOutboundStockForUpdate("KC001")).thenReturn(stock);
|
||||
|
||||
BasStockDetail detailA = stockDetail("SD001", "MAT001", "物料1", "L001", "8");
|
||||
BasStockDetail detailB = stockDetail("SD002", "MAT002", "物料2", "L002", "4");
|
||||
when(locationOutboundMapper.selectAvailableDetailsForUpdate("KC001")).thenReturn(List.of(detailA, detailB));
|
||||
|
||||
BasStationPoint exitPoint = new BasStationPoint();
|
||||
exitPoint.setPointId("OUT01");
|
||||
exitPoint.setWarehouseId("WH01");
|
||||
exitPoint.setPointType("2");
|
||||
exitPoint.setStatus("0");
|
||||
exitPoint.setPointStatus("0");
|
||||
exitPoint.setTrayType("A,B");
|
||||
when(basStationPointService.selectBasStationPointByPointId("OUT01")).thenReturn(exitPoint);
|
||||
when(jobIdService.getId("CK")).thenReturn("CK001");
|
||||
when(taskNoService.getTaskNo("20")).thenReturn("50001");
|
||||
when(mydoJobService.insertMydoJob(any(MydoJob.class))).thenReturn(1);
|
||||
when(mydoJobDetailService.insertMydoJobDetail(any(MydoJobDetail.class))).thenReturn(1);
|
||||
when(locationOutboundMapper.reserveStockDetailForOutbound(any(HashMap.class))).thenReturn(1);
|
||||
when(basStockMapper.refreshBasStockSummary("KC001")).thenReturn(1);
|
||||
when(basCargoSpaceMapper.markOutboundAssignedByStockId("KC001")).thenReturn(1);
|
||||
when(mydoJobService.pushOutJobToWcs("CK001")).thenReturn(AjaxResult.success("WCS出库作业下发成功:OK"));
|
||||
|
||||
LocationOutboundGenerateRequest request = new LocationOutboundGenerateRequest();
|
||||
request.setStockId("KC001");
|
||||
request.setExitId("OUT01");
|
||||
String result = service.generateWholeTrayOutboundJob(request);
|
||||
|
||||
assertThat(result).contains("CK001").contains("WCS出库作业下发成功");
|
||||
|
||||
ArgumentCaptor<MydoJob> jobCaptor = ArgumentCaptor.forClass(MydoJob.class);
|
||||
verify(mydoJobService).insertMydoJob(jobCaptor.capture());
|
||||
MydoJob job = jobCaptor.getValue();
|
||||
assertThat(job.getJobId()).isEqualTo("CK001");
|
||||
assertThat(job.getTaskNo()).isEqualTo("50001");
|
||||
assertThat(job.getInOrOut()).isEqualTo(20L);
|
||||
assertThat(job.getStatus()).isEqualTo("1");
|
||||
assertThat(job.getCargoSpaceId()).isEqualTo("CS001");
|
||||
assertThat(job.getExitId()).isEqualTo("OUT01");
|
||||
|
||||
ArgumentCaptor<MydoJobDetail> detailCaptor = ArgumentCaptor.forClass(MydoJobDetail.class);
|
||||
verify(mydoJobDetailService, org.mockito.Mockito.times(2)).insertMydoJobDetail(detailCaptor.capture());
|
||||
List<MydoJobDetail> jobDetails = detailCaptor.getAllValues();
|
||||
assertThat(jobDetails).extracting(MydoJobDetail::getJobDetailId).containsExactly("CK001-1", "CK001-2");
|
||||
assertThat(jobDetails).extracting(MydoJobDetail::getProductId).containsExactly("MAT001", "MAT002");
|
||||
assertThat(jobDetails).extracting(MydoJobDetail::getOutQuantity)
|
||||
.containsExactly(new BigDecimal("8"), new BigDecimal("4"));
|
||||
assertThat(jobDetails).extracting(MydoJobDetail::getStockinDetailId).containsExactly("SD001", "SD002");
|
||||
|
||||
ArgumentCaptor<HashMap> reserveCaptor = ArgumentCaptor.forClass(HashMap.class);
|
||||
verify(locationOutboundMapper, org.mockito.Mockito.times(2)).reserveStockDetailForOutbound(reserveCaptor.capture());
|
||||
assertThat(reserveCaptor.getAllValues()).extracting(map -> map.get("stockDetailId"))
|
||||
.containsExactly("SD001", "SD002");
|
||||
verify(basStockMapper).refreshBasStockSummary("KC001");
|
||||
verify(basCargoSpaceMapper).markOutboundAssignedByStockId("KC001");
|
||||
verify(mydoJobService).pushOutJobToWcs("CK001");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateWholeTrayOutboundJobAllowsEmptyAbnormalLocationWithoutStockDetails() {
|
||||
LocationOutboundStock location = new LocationOutboundStock();
|
||||
location.setCargoSpaceId("CS-EMPTY");
|
||||
location.setCargoSpaceName("02排001列01层");
|
||||
location.setCargoSpaceStatus("6");
|
||||
location.setWarehouseId("WH01");
|
||||
location.setWarehouseName("一号仓");
|
||||
location.setRoadwayId("R01");
|
||||
when(locationOutboundMapper.selectLocationOutboundLocationForUpdate("CS-EMPTY")).thenReturn(location);
|
||||
|
||||
BasStationPoint exitPoint = new BasStationPoint();
|
||||
exitPoint.setPointId("OUT01");
|
||||
exitPoint.setWarehouseId("WH01");
|
||||
exitPoint.setPointType("2");
|
||||
exitPoint.setStatus("0");
|
||||
exitPoint.setPointStatus("0");
|
||||
when(basStationPointService.selectBasStationPointByPointId("OUT01")).thenReturn(exitPoint);
|
||||
when(jobIdService.getId("CK")).thenReturn("CK002");
|
||||
when(taskNoService.getTaskNo("20")).thenReturn("50002");
|
||||
when(mydoJobService.insertMydoJob(any(MydoJob.class))).thenReturn(1);
|
||||
when(basCargoSpaceMapper.markOutboundAssignedByCargoSpaceId("CS-EMPTY")).thenReturn(1);
|
||||
when(mydoJobService.pushOutJobToWcs("CK002")).thenReturn(AjaxResult.success("WCS出库作业下发成功:OK"));
|
||||
|
||||
LocationOutboundGenerateRequest request = new LocationOutboundGenerateRequest();
|
||||
request.setCargoSpaceId("CS-EMPTY");
|
||||
request.setExitId("OUT01");
|
||||
String result = service.generateWholeTrayOutboundJob(request);
|
||||
|
||||
assertThat(result).contains("CK002").contains("WCS出库作业下发成功");
|
||||
|
||||
ArgumentCaptor<MydoJob> jobCaptor = ArgumentCaptor.forClass(MydoJob.class);
|
||||
verify(mydoJobService).insertMydoJob(jobCaptor.capture());
|
||||
MydoJob job = jobCaptor.getValue();
|
||||
assertThat(job.getJobId()).isEqualTo("CK002");
|
||||
assertThat(job.getTaskNo()).isEqualTo("50002");
|
||||
assertThat(job.getInOrOut()).isEqualTo(20L);
|
||||
assertThat(job.getCargoSpaceId()).isEqualTo("CS-EMPTY");
|
||||
assertThat(job.getCargoSpaceName()).isEqualTo("02排001列01层");
|
||||
assertThat(job.getTrayCode()).isNull();
|
||||
|
||||
verify(locationOutboundMapper, never()).selectAvailableDetailsForUpdate(any());
|
||||
verify(mydoJobDetailService, never()).insertMydoJobDetail(any(MydoJobDetail.class));
|
||||
verify(locationOutboundMapper, never()).reserveStockDetailForOutbound(any(HashMap.class));
|
||||
verify(basStockMapper, never()).refreshBasStockSummary(any());
|
||||
verify(basCargoSpaceMapper).markOutboundAssignedByCargoSpaceId("CS-EMPTY");
|
||||
verify(mydoJobService).pushOutJobToWcs("CK002");
|
||||
}
|
||||
|
||||
private BasStockDetail stockDetail(String detailId, String productId, String productName, String lotNo, String availableQuantity) {
|
||||
BasStockDetail detail = new BasStockDetail();
|
||||
detail.setDetailId(detailId);
|
||||
detail.setStockId("KC001");
|
||||
detail.setProductId(productId);
|
||||
detail.setProductName(productName);
|
||||
detail.setLotNo(lotNo);
|
||||
detail.setAvailableQuantity(new BigDecimal(availableQuantity));
|
||||
detail.setQuantity(new BigDecimal(availableQuantity));
|
||||
detail.setOutQuantity(BigDecimal.ZERO);
|
||||
return detail;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
package com.ruoyi.out.stockout.mapper;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class OutStockoutMapperSqlTest {
|
||||
|
||||
@Test
|
||||
void allocationProcedureCallsPassExitPointParameter() throws Exception {
|
||||
String xml = Files.readString(resolvePath(
|
||||
"src/main/resources/mapper/out/OutStockoutMapper.xml",
|
||||
"ruoyi-admin/src/main/resources/mapper/out/OutStockoutMapper.xml"));
|
||||
String script = Files.readString(resolvePath(
|
||||
"../sql/out_stockout_flow.sql",
|
||||
"sql/out_stockout_flow.sql"));
|
||||
|
||||
String autoCall = section(xml, "<select id=\"autoAssignOutStockoutByProc\"", "</select>");
|
||||
String manualCall = section(xml, "<select id=\"manualAssignOutStockoutByProc\"", "</select>");
|
||||
String autoProc = section(script, "CREATE PROCEDURE dbo.proc_out_auto_allocation", "AS");
|
||||
String manualProc = section(script, "CREATE PROCEDURE dbo.proc_out_hand_allocation", "AS");
|
||||
|
||||
assertThat(autoCall)
|
||||
.contains("proc_out_auto_allocation")
|
||||
.contains("#{v_exit_id,mode=IN,jdbcType=VARCHAR}");
|
||||
assertThat(manualCall)
|
||||
.contains("proc_out_hand_allocation")
|
||||
.contains("#{v_exit_id,mode=IN,jdbcType=VARCHAR}");
|
||||
assertThat(autoProc).contains("@v_exit_id varchar(80)");
|
||||
assertThat(manualProc).contains("@v_exit_id varchar(80)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void manualAllocationStockDetailListOnlyShowsNormalCargoSpaceStock() throws Exception {
|
||||
String xml = Files.readString(resolvePath(
|
||||
"src/main/resources/mapper/bas/BasStockDetailMapper.xml",
|
||||
"ruoyi-admin/src/main/resources/mapper/bas/BasStockDetailMapper.xml"));
|
||||
String selectList = section(xml, "<select id=\"selectBasStockDetailList\"", "</select>");
|
||||
|
||||
assertThat(selectList).contains("bcs.status = '2'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void stockListAndStockDetailListFallbackToWarehouseTableName() throws Exception {
|
||||
String stockXml = Files.readString(resolvePath(
|
||||
"src/main/resources/mapper/bas/BasStockMapper.xml",
|
||||
"ruoyi-admin/src/main/resources/mapper/bas/BasStockMapper.xml"));
|
||||
String stockDetailXml = Files.readString(resolvePath(
|
||||
"src/main/resources/mapper/bas/BasStockDetailMapper.xml",
|
||||
"ruoyi-admin/src/main/resources/mapper/bas/BasStockDetailMapper.xml"));
|
||||
|
||||
String stockSelect = section(stockXml, "<select id=\"selectBasStockList\"", "</select>");
|
||||
String detailSelectVo = section(stockDetailXml, "<sql id=\"selectBasStockDetailVo\">", "</sql>");
|
||||
|
||||
assertThat(stockSelect)
|
||||
.contains("left join bas_warehouse bw on bs.warehouse_id = bw.warehouse_id")
|
||||
.contains("ISNULL(NULLIF(bs.warehouse_name, ''), bw.warehouse_name) as warehouse_name");
|
||||
assertThat(detailSelectVo)
|
||||
.contains("left join bas_warehouse bw on bs.warehouse_id = bw.warehouse_id")
|
||||
.contains("ISNULL(NULLIF(bs.warehouse_name, ''), bw.warehouse_name) as warehouse_name");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outboundAllocationProcedureChecksExitTrayType() throws Exception {
|
||||
String script = Files.readString(resolvePath(
|
||||
"../sql/out_stockout_flow.sql",
|
||||
"sql/out_stockout_flow.sql"));
|
||||
String manualProc = section(script, "CREATE PROCEDURE dbo.proc_out_hand_allocation", "IF OBJECT_ID(N'dbo.proc_out_auto_allocation'");
|
||||
|
||||
assertThat(manualProc)
|
||||
.contains("@tray_type nvarchar(80)")
|
||||
.contains("bas_barcode_format")
|
||||
.contains("charindex(',' + @tray_type + ',', ',' + sp.tray_type + ',') > 0")
|
||||
.contains("出库口不支持当前托盘类型");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outboundAllocationProcedureReusesExistingTrayJobForSameStockout() throws Exception {
|
||||
String script = Files.readString(resolvePath(
|
||||
"../sql/out_stockout_flow.sql",
|
||||
"sql/out_stockout_flow.sql"));
|
||||
String manualProc = section(script, "CREATE PROCEDURE dbo.proc_out_hand_allocation", "IF OBJECT_ID(N'dbo.proc_out_auto_allocation'");
|
||||
|
||||
assertThat(manualProc)
|
||||
.contains("@existing_job_id nvarchar(80)")
|
||||
.contains("OR EXISTS (")
|
||||
.contains("INNER JOIN dbo.mydo_job_detail ejd ON ej.job_id = ejd.job_id")
|
||||
.contains("AND ejd.in_or_out_id = @stockout_id")
|
||||
.contains("SET @job_id = @existing_job_id")
|
||||
.contains("SET @job_detail_id = @job_id + '-' + CONVERT(nvarchar(10), @row_idx)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outboundAllocationMarksPickingJobWhenAllocatedQuantityIsLessThanTrayStock() throws Exception {
|
||||
String script = Files.readString(resolvePath(
|
||||
"../sql/out_stockout_flow.sql",
|
||||
"sql/out_stockout_flow.sql"));
|
||||
String manualProc = section(script, "CREATE PROCEDURE dbo.proc_out_hand_allocation", "IF OBJECT_ID(N'dbo.proc_out_auto_allocation'");
|
||||
|
||||
assertThat(manualProc)
|
||||
.contains("@job_type int")
|
||||
.contains("SET @job_type = CASE WHEN @alloc_qty < @available_qty THEN 21 ELSE 20 END")
|
||||
.contains("@job_id, '1', @task_no, @job_type")
|
||||
.contains("SET in_or_out = CASE WHEN @job_type = 21 THEN 21 ELSE in_or_out END");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outboundAllocationOnlyTreatsActiveOutboundJobDetailAsReserved() throws Exception {
|
||||
String script = Files.readString(resolvePath(
|
||||
"../sql/out_stockout_flow.sql",
|
||||
"sql/out_stockout_flow.sql"));
|
||||
String manualProc = section(script, "CREATE PROCEDURE dbo.proc_out_hand_allocation", "IF OBJECT_ID(N'dbo.proc_out_auto_allocation'");
|
||||
String autoProc = section(script, "CREATE PROCEDURE dbo.proc_out_auto_allocation", "IF OBJECT_ID(N'dbo.proc_out_makesure'");
|
||||
|
||||
assertThat(manualProc)
|
||||
.contains("OR NOT EXISTS (")
|
||||
.contains("INNER JOIN dbo.mydo_job lock_j ON lock_j.job_id = lock_jd.job_id")
|
||||
.contains("WHERE lock_jd.job_detail_id = bsd.job_detail_id")
|
||||
.contains("AND lock_j.in_or_out >= 20")
|
||||
.contains("AND lock_j.status IN ('1', '2', '3')");
|
||||
assertThat(autoProc)
|
||||
.contains("OR NOT EXISTS (")
|
||||
.contains("WHERE lock_jd.job_detail_id = bsd.job_detail_id")
|
||||
.contains("AND lock_j.in_or_out >= 20")
|
||||
.contains("OR EXISTS (")
|
||||
.contains("AND ejd.in_or_out_id = @stockout_id");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outboundConfirmationArchivesDeletedStockIntoFinishTables() throws Exception {
|
||||
String script = Files.readString(resolvePath(
|
||||
"../sql/out_stockout_flow.sql",
|
||||
"sql/out_stockout_flow.sql"));
|
||||
String makeSureProc = section(script, "CREATE PROCEDURE dbo.proc_out_makesure", "IF OBJECT_ID(N'dbo.proc_hand_delete_out_job'");
|
||||
|
||||
assertThat(script)
|
||||
.contains("CREATE TABLE dbo.bas_stock_finish")
|
||||
.contains("CREATE TABLE dbo.bas_stock_detail_finish")
|
||||
.contains("delete_time datetime2(7)");
|
||||
assertThat(makeSureProc)
|
||||
.contains("INSERT INTO dbo.bas_stock_detail_finish")
|
||||
.contains("INSERT INTO dbo.bas_stock_finish")
|
||||
.contains("SYSDATETIME() AS delete_time");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outboundConfirmationDoesNotReoccupyOriginalCargoSpaceAfterWcsRelease() throws Exception {
|
||||
String script = Files.readString(resolvePath(
|
||||
"../sql/out_stockout_flow.sql",
|
||||
"sql/out_stockout_flow.sql"));
|
||||
String makeSureProc = section(script, "CREATE PROCEDURE dbo.proc_out_makesure", "IF OBJECT_ID(N'dbo.proc_hand_delete_out_job'");
|
||||
String residualBranch = section(makeSureProc,
|
||||
"IF EXISTS (SELECT 1 FROM dbo.bas_stock_detail WHERE stock_id = @stock_id)",
|
||||
"INSERT INTO dbo.bas_stock_finish");
|
||||
|
||||
assertThat(residualBranch)
|
||||
.doesNotContain("UPDATE dbo.bas_cargo_space")
|
||||
.doesNotContain("SET status = '2'")
|
||||
.doesNotContain("tray_code = @tray_code");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outboundAllocationAndCancelDoNotChangeManualLockedFlag() throws Exception {
|
||||
String script = Files.readString(resolvePath(
|
||||
"../sql/out_stockout_flow.sql",
|
||||
"sql/out_stockout_flow.sql"));
|
||||
String manualProc = section(script, "CREATE PROCEDURE dbo.proc_out_hand_allocation", "IF OBJECT_ID(N'dbo.proc_out_auto_allocation'");
|
||||
String cancelProc = section(script, "CREATE PROCEDURE dbo.proc_hand_delete_out_job", "END\nGO");
|
||||
|
||||
assertThat(manualProc)
|
||||
.contains("UPDATE dbo.bas_cargo_space")
|
||||
.contains("SET status = '4'")
|
||||
.doesNotContain("locked = 'Y'")
|
||||
.doesNotContain("locked = 'N'");
|
||||
assertThat(cancelProc)
|
||||
.contains("UPDATE dbo.bas_cargo_space")
|
||||
.contains("SET status = '2'")
|
||||
.doesNotContain("locked = 'Y'")
|
||||
.doesNotContain("locked = 'N'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void locationOutboundListOnlyTreatsOccupiedCargoSpacesAsInStock() throws Exception {
|
||||
String xml = Files.readString(resolvePath(
|
||||
"src/main/resources/mapper/out/LocationOutboundMapper.xml",
|
||||
"ruoyi-admin/src/main/resources/mapper/out/LocationOutboundMapper.xml"));
|
||||
String selectList = section(xml, "<select id=\"selectLocationOutboundStockList\"", "</select>");
|
||||
|
||||
assertThat(selectList)
|
||||
.contains("left join bas_stock bs on bs.cargo_space_id = bcs.cargo_space_id\n and bcs.status in ('2', '4')")
|
||||
.contains("and bcs.status = '2'")
|
||||
.contains("and bs.stock_id is not null");
|
||||
}
|
||||
|
||||
private Path resolvePath(String modulePath, String rootPath) {
|
||||
return Stream.of(Path.of(modulePath), Path.of(rootPath))
|
||||
.filter(Files::exists)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
private String section(String text, String startMarker, String endMarker) {
|
||||
int start = text.indexOf(startMarker);
|
||||
assertThat(start).isGreaterThanOrEqualTo(0);
|
||||
int end = text.indexOf(endMarker, start);
|
||||
assertThat(end).isGreaterThan(start);
|
||||
return text.substring(start, end);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.ruoyi.out.stockout.service.impl;
|
||||
|
||||
import com.ruoyi.bas.id.job.service.JobIdService;
|
||||
import com.ruoyi.bas.cargospace.mapper.BasCargoSpaceMapper;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.in.job.service.IMydoJobService;
|
||||
import com.ruoyi.out.stockout.mapper.OutStockoutMapper;
|
||||
import com.ruoyi.out.stockoutdetail.domain.OutStockoutDetail;
|
||||
import com.ruoyi.out.stockoutdetail.mapper.OutStockoutDetailMapper;
|
||||
import com.ruoyi.system.mapper.SysDeptMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class OutStockoutServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private OutStockoutMapper outStockoutMapper;
|
||||
|
||||
@Mock
|
||||
private OutStockoutDetailMapper outStockoutDetailMapper;
|
||||
|
||||
@Mock
|
||||
private SysDeptMapper sysDeptMapper;
|
||||
|
||||
@Mock
|
||||
private JobIdService jobIdService;
|
||||
|
||||
@Mock
|
||||
private IMydoJobService mydoJobService;
|
||||
|
||||
@Mock
|
||||
private BasCargoSpaceMapper basCargoSpaceMapper;
|
||||
|
||||
@InjectMocks
|
||||
private OutStockoutServiceImpl service;
|
||||
|
||||
@Test
|
||||
void autoAssignReturnsErrorWhenProcedureReportsFailure() {
|
||||
OutStockoutDetail detail = new OutStockoutDetail();
|
||||
detail.setStockoutDetailId("CKD-1");
|
||||
when(outStockoutDetailMapper.selectOutStockoutDetailListByStockoutId("CKD"))
|
||||
.thenReturn(List.of(detail));
|
||||
doAnswer(invocation -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
HashMap<String, Object> map = invocation.getArgument(0);
|
||||
map.put("v_result", 2);
|
||||
map.put("v_resultString", "没有可分配库存明细");
|
||||
return null;
|
||||
}).when(outStockoutMapper).autoAssignOutStockoutByProc(any());
|
||||
|
||||
AjaxResult result = service.autoAssignOutStockout("CKD", "02-AU-01");
|
||||
|
||||
assertThat(result.isError()).isTrue();
|
||||
assertThat(result.get(AjaxResult.MSG_TAG).toString())
|
||||
.contains("CKD-1")
|
||||
.contains("没有可分配库存明细");
|
||||
verify(mydoJobService, never()).selectPendingOutMydoJobsByStockoutDetailId(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoAssignDoesNotPushAnyJobUntilAllDetailsAreAllocated() {
|
||||
OutStockoutDetail first = new OutStockoutDetail();
|
||||
first.setStockoutDetailId("CKD-1");
|
||||
OutStockoutDetail second = new OutStockoutDetail();
|
||||
second.setStockoutDetailId("CKD-2");
|
||||
when(outStockoutDetailMapper.selectOutStockoutDetailListByStockoutId("CKD"))
|
||||
.thenReturn(List.of(first, second));
|
||||
AtomicInteger callCount = new AtomicInteger();
|
||||
doAnswer(invocation -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
HashMap<String, Object> map = invocation.getArgument(0);
|
||||
if (callCount.incrementAndGet() == 1) {
|
||||
map.put("v_result", 10);
|
||||
map.put("v_resultString", "自动分配完成");
|
||||
} else {
|
||||
map.put("v_result", 2);
|
||||
map.put("v_resultString", "没有可分配库存明细");
|
||||
}
|
||||
return null;
|
||||
}).when(outStockoutMapper).autoAssignOutStockoutByProc(any());
|
||||
|
||||
AjaxResult result = service.autoAssignOutStockout("CKD", "02-AU-01");
|
||||
|
||||
assertThat(result.isError()).isTrue();
|
||||
assertThat(result.get(AjaxResult.MSG_TAG).toString())
|
||||
.contains("CKD-2")
|
||||
.contains("没有可分配库存明细");
|
||||
verify(mydoJobService, never()).selectPendingOutMydoJobsByStockoutDetailId(any());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.ruoyi.pda.mapper;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PdaMapperSqlTest {
|
||||
|
||||
@Test
|
||||
void emptyTrayOutboundCountWarehouseParameterIsNamedForMyBatisXml() throws Exception {
|
||||
Method method = PdaMapper.class.getMethod("selectAvailableEmptyTrayTypeCounts", String.class);
|
||||
|
||||
Param param = method.getParameters()[0].getAnnotation(Param.class);
|
||||
|
||||
assertThat(param).isNotNull();
|
||||
assertThat(param.value()).isEqualTo("warehouseId");
|
||||
}
|
||||
|
||||
@Test
|
||||
void trayStockQueryTrayCodeParameterIsNamedForMyBatisXml() throws Exception {
|
||||
Method method = PdaMapper.class.getMethod("selectTrayStockMaterials", String.class);
|
||||
|
||||
Param param = method.getParameters()[0].getAnnotation(Param.class);
|
||||
|
||||
assertThat(param).isNotNull();
|
||||
assertThat(param.value()).isEqualTo("trayCode");
|
||||
}
|
||||
|
||||
@Test
|
||||
void trayStockQueryUsesTrayCodeOnlyAndReturnsMaterialRows() throws Exception {
|
||||
String xml = Files.readString(resolveMapperXml());
|
||||
String trayStockSql = section(xml, "<select id=\"selectTrayStockMaterials\"", "</select>");
|
||||
|
||||
assertThat(trayStockSql)
|
||||
.contains("bs.tray_code = #{trayCode}")
|
||||
.contains("bsd.product_id as materialCode")
|
||||
.contains("bsd.product_name as materialName")
|
||||
.contains("isnull(bsd.quantity, 0) - isnull(bsd.out_quantity, 0) as availableQuantity")
|
||||
.doesNotContain("#{warehouseId}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyTrayOutboundCountsGroupByBarcodeFormatPrefix() throws Exception {
|
||||
String xml = Files.readString(resolveMapperXml());
|
||||
String typeCountSql = section(xml, "<select id=\"selectAvailableEmptyTrayTypeCounts\"", "</select>");
|
||||
String countSql = section(xml, "<select id=\"countAvailableEmptyTrays\"", "</select>");
|
||||
String stockSql = section(xml, "<select id=\"selectAvailableEmptyTrayStocks\"", "</select>");
|
||||
|
||||
assertThat(typeCountSql)
|
||||
.contains("inner join bas_barcode_format bbf")
|
||||
.contains("bbf.prefix as barcodePrefix")
|
||||
.contains("left(bs.tray_code, len(bbf.prefix)) = bbf.prefix")
|
||||
.contains("not exists")
|
||||
.doesNotContain("bsd.job_detail_id is null");
|
||||
assertThat(countSql)
|
||||
.contains("isnull(bsd.quantity, 0) - isnull(bsd.out_quantity, 0) > 0")
|
||||
.doesNotContain("bsd.job_detail_id is null");
|
||||
assertThat(stockSql)
|
||||
.contains("left(bs.tray_code, len(#{barcodePrefix})) = #{barcodePrefix}")
|
||||
.doesNotContain("bsd.job_detail_id is null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void inboundCargoSpaceLockChoosesPlatoonWithMostEmptySpacesFirst() throws Exception {
|
||||
String xml = Files.readString(resolveMapperXml());
|
||||
String lockSql = section(xml, "<update id=\"lockInboundCargoSpace\"", "</update>");
|
||||
|
||||
assertThat(lockSql)
|
||||
.contains("count(1) over (partition by bcs.the_platoon) as platoon_empty_count")
|
||||
.contains("order by platoon_empty_count desc");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outboundTaskListShowsExecutionPhaseFiveAsWaitingForPdaScan() throws Exception {
|
||||
String xml = Files.readString(resolveMapperXml());
|
||||
String taskListSql = section(xml, "<select id=\"outboundTaskList\"", "</select>");
|
||||
String detailSql = section(xml, "<select id=\"selectOutboundDetail\"", "</select>");
|
||||
|
||||
assertThat(taskListSql)
|
||||
.contains("j.execution_phase as executionPhase")
|
||||
.contains("when j.execution_phase = '5' then '待扫码确认'")
|
||||
.contains("and j.status in ('1', '2', '3')")
|
||||
.contains("group by j.job_id, j.task_no, j.exit_id, j.create_time, j.status, j.execution_phase");
|
||||
assertThat(detailSql)
|
||||
.contains("j.execution_phase as executionPhase")
|
||||
.contains("and j.status in ('1', '2', '3')");
|
||||
}
|
||||
|
||||
private Path resolveMapperXml() {
|
||||
return Stream.of(
|
||||
Path.of("src/main/resources/mapper/pda/PdaMapper.xml"),
|
||||
Path.of("ruoyi-admin/src/main/resources/mapper/pda/PdaMapper.xml")
|
||||
)
|
||||
.filter(Files::exists)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
private String section(String text, String startMarker, String endMarker) {
|
||||
int start = text.indexOf(startMarker);
|
||||
assertThat(start).isGreaterThanOrEqualTo(0);
|
||||
int end = text.indexOf(endMarker, start);
|
||||
assertThat(end).isGreaterThan(start);
|
||||
return text.substring(start, end);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,483 @@
|
||||
package com.ruoyi.pda.service.impl;
|
||||
|
||||
import com.ruoyi.api.client.WcsClient;
|
||||
import com.ruoyi.api.domain.ApiResponse;
|
||||
import com.ruoyi.bas.id.job.service.JobIdService;
|
||||
import com.ruoyi.bas.id.taskno.service.TaskNoService;
|
||||
import com.ruoyi.bas.stationpoint.domain.BasStationPoint;
|
||||
import com.ruoyi.bas.stationpoint.service.IBasStationPointService;
|
||||
import com.ruoyi.bas.warehouse.domain.BasWarehouse;
|
||||
import com.ruoyi.bas.warehouse.service.IBasWarehouseService;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
import com.ruoyi.in.job.domain.MydoJob;
|
||||
import com.ruoyi.in.job.service.IMydoJobService;
|
||||
import com.ruoyi.in.jobdetail.domain.MydoJobDetail;
|
||||
import com.ruoyi.in.jobdetail.service.IMydoJobDetailService;
|
||||
import com.ruoyi.in.stockin.mapper.InStockinMapper;
|
||||
import com.ruoyi.in.sotckindetail.mapper.InStockinDetailMapper;
|
||||
import com.ruoyi.pda.domain.AddInJobRequest;
|
||||
import com.ruoyi.pda.domain.EmptyTrayOutboundRequest;
|
||||
import com.ruoyi.pda.mapper.PdaMapper;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PdaServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private PdaMapper pdaMapper;
|
||||
|
||||
@Mock
|
||||
private InStockinDetailMapper inStockinDetailMapper;
|
||||
|
||||
@Mock
|
||||
private InStockinMapper inStockinMapper;
|
||||
|
||||
@Mock
|
||||
private IBasWarehouseService basWarehouseService;
|
||||
|
||||
@Mock
|
||||
private IBasStationPointService basStationPointService;
|
||||
|
||||
@Mock
|
||||
private IMydoJobService mydoJobService;
|
||||
|
||||
@Mock
|
||||
private IMydoJobDetailService mydoJobDetailService;
|
||||
|
||||
@Mock
|
||||
private WcsClient wcsClient;
|
||||
|
||||
@Mock
|
||||
private JobIdService jobIdService;
|
||||
|
||||
@Mock
|
||||
private TaskNoService taskNoService;
|
||||
|
||||
@InjectMocks
|
||||
private PdaServiceImpl service;
|
||||
|
||||
@BeforeEach
|
||||
void setUpSecurityContext() {
|
||||
SysUser user = new SysUser();
|
||||
user.setUserName("tester");
|
||||
LoginUser loginUser = new LoginUser(10L, 20L, user, Collections.emptySet());
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void clearSecurityContext() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
void addInJobMarksJobDispatchedAfterWcsAcceptsIt() {
|
||||
AddInJobRequest request = new AddInJobRequest();
|
||||
request.setTrayCode("A001");
|
||||
request.setWarehouseId("WH01");
|
||||
request.setStationPointId("IN01");
|
||||
MydoJob job = stubSuccessfulInboundJobSetup();
|
||||
when(wcsClient.sendPutawayTask(job)).thenReturn(ApiResponse.ok("OK"));
|
||||
|
||||
String result = service.addInJob(request);
|
||||
|
||||
assertThat(result).contains("WCS入库作业下发成功");
|
||||
verify(wcsClient).sendPutawayTask(job);
|
||||
ArgumentCaptor<MydoJob> updateCaptor = ArgumentCaptor.forClass(MydoJob.class);
|
||||
verify(mydoJobService, org.mockito.Mockito.times(2)).updateMydoJob(updateCaptor.capture());
|
||||
List<MydoJob> updates = updateCaptor.getAllValues();
|
||||
assertThat(updates.get(0).getStatus()).isEqualTo("1");
|
||||
assertThat(updates.get(0).getExecutionPhase()).isEqualTo("0");
|
||||
assertThat(updates.get(1).getJobId()).isEqualTo("RK001");
|
||||
assertThat(updates.get(1).getStatus()).isEqualTo("3");
|
||||
assertThat(updates.get(1).getExecutionPhase()).isEqualTo("1");
|
||||
assertThat(updates.get(1).getStartTime()).isNotBlank();
|
||||
}
|
||||
|
||||
@Test
|
||||
void addInJobDoesNotMarkDispatchedWhenWcsRejectsIt() {
|
||||
AddInJobRequest request = new AddInJobRequest();
|
||||
request.setTrayCode("A001");
|
||||
request.setWarehouseId("WH01");
|
||||
request.setStationPointId("IN01");
|
||||
MydoJob job = stubSuccessfulInboundJobSetup();
|
||||
when(wcsClient.sendPutawayTask(job)).thenReturn(ApiResponse.fail("WCS_NG", "下发失败"));
|
||||
|
||||
assertThatThrownBy(() -> service.addInJob(request))
|
||||
.isInstanceOf(RuntimeException.class)
|
||||
.hasMessage("WCS入库作业下发失败:下发失败");
|
||||
|
||||
verify(wcsClient).sendPutawayTask(job);
|
||||
ArgumentCaptor<MydoJob> updateCaptor = ArgumentCaptor.forClass(MydoJob.class);
|
||||
verify(mydoJobService).updateMydoJob(updateCaptor.capture());
|
||||
assertThat(updateCaptor.getValue().getStatus()).isEqualTo("1");
|
||||
verify(mydoJobService, never()).updateMydoJob(
|
||||
org.mockito.ArgumentMatchers.argThat(update -> "3".equals(update.getStatus())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void addEmptyTrayInboundJobCreatesEmptyContainerDetailAndDispatchesToWcs() {
|
||||
AddInJobRequest request = new AddInJobRequest();
|
||||
request.setTrayCode("A001");
|
||||
request.setWarehouseId("WH01");
|
||||
request.setStationPointId("IN01");
|
||||
stubInboundStationAndRoadway();
|
||||
when(jobIdService.getId("RK")).thenReturn("RK_EMPTY_1");
|
||||
when(taskNoService.getTaskNo("6")).thenReturn("1008");
|
||||
BasWarehouse warehouse = new BasWarehouse();
|
||||
warehouse.setWarehouseName("一号仓");
|
||||
when(basWarehouseService.selectBasWarehouseByWarehouseId("WH01")).thenReturn(warehouse);
|
||||
when(mydoJobService.insertMydoJob(any(MydoJob.class))).thenReturn(1);
|
||||
when(mydoJobDetailService.insertMydoJobDetail(any(MydoJobDetail.class))).thenReturn(1);
|
||||
when(mydoJobService.updateMydoJob(any(MydoJob.class))).thenReturn(1);
|
||||
when(wcsClient.sendPutawayTask(any(MydoJob.class))).thenReturn(ApiResponse.ok("OK"));
|
||||
|
||||
String result = service.addEmptyTrayInboundJob(request);
|
||||
|
||||
assertThat(result).contains("空托入库作业生成成功").contains("WCS入库作业下发成功");
|
||||
verify(pdaMapper, never()).addInJob(any(HashMap.class));
|
||||
ArgumentCaptor<MydoJob> jobCaptor = ArgumentCaptor.forClass(MydoJob.class);
|
||||
verify(mydoJobService).insertMydoJob(jobCaptor.capture());
|
||||
MydoJob insertedJob = jobCaptor.getValue();
|
||||
assertThat(insertedJob.getJobId()).isEqualTo("RK_EMPTY_1");
|
||||
assertThat(insertedJob.getTaskNo()).isEqualTo("1008");
|
||||
assertThat(insertedJob.getInOrOut()).isEqualTo(3L);
|
||||
assertThat(insertedJob.getStatus()).isEqualTo("1");
|
||||
assertThat(insertedJob.getExecutionPhase()).isEqualTo("0");
|
||||
assertThat(insertedJob.getTrayCode()).isEqualTo("A001");
|
||||
assertThat(insertedJob.getWarehouseId()).isEqualTo("WH01");
|
||||
assertThat(insertedJob.getWarehouseName()).isEqualTo("一号仓");
|
||||
assertThat(insertedJob.getEntranceId()).isEqualTo("IN01");
|
||||
assertThat(insertedJob.getRoadwayId()).isEqualTo("R01");
|
||||
|
||||
ArgumentCaptor<MydoJobDetail> detailCaptor = ArgumentCaptor.forClass(MydoJobDetail.class);
|
||||
verify(mydoJobDetailService).insertMydoJobDetail(detailCaptor.capture());
|
||||
MydoJobDetail detail = detailCaptor.getValue();
|
||||
assertThat(detail.getJobDetailId()).isEqualTo("RK_EMPTY_1-1");
|
||||
assertThat(detail.getJobId()).isEqualTo("RK_EMPTY_1");
|
||||
assertThat(detail.getProductId()).isEqualTo("KTP0001");
|
||||
assertThat(detail.getProductName()).isEqualTo("空容器");
|
||||
assertThat(detail.getInQuantity()).isEqualByComparingTo("1");
|
||||
|
||||
verify(wcsClient).sendPutawayTask(insertedJob);
|
||||
ArgumentCaptor<MydoJob> updateCaptor = ArgumentCaptor.forClass(MydoJob.class);
|
||||
verify(mydoJobService).updateMydoJob(updateCaptor.capture());
|
||||
assertThat(updateCaptor.getValue().getStatus()).isEqualTo("3");
|
||||
assertThat(updateCaptor.getValue().getExecutionPhase()).isEqualTo("1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyTrayOutboundCountRequiresWarehouseAndReturnsTrayTypeCounts() {
|
||||
HashMap<String, Object> typeA = new HashMap<>();
|
||||
typeA.put("formatName", "A型托盘");
|
||||
typeA.put("barcodePrefix", "A-");
|
||||
typeA.put("quantity", 3);
|
||||
HashMap<String, Object> typeB = new HashMap<>();
|
||||
typeB.put("formatName", "B型托盘");
|
||||
typeB.put("barcodePrefix", "B-");
|
||||
typeB.put("quantity", 2);
|
||||
when(pdaMapper.selectAvailableEmptyTrayTypeCounts("WH01")).thenReturn(List.of(typeA, typeB));
|
||||
|
||||
assertThat(service.emptyTrayOutboundCount("WH01"))
|
||||
.containsExactly(typeA, typeB);
|
||||
assertThatThrownBy(() -> service.emptyTrayOutboundCount(""))
|
||||
.isInstanceOf(RuntimeException.class)
|
||||
.hasMessage("仓库编码不能为空");
|
||||
}
|
||||
|
||||
@Test
|
||||
void trayStockRequiresTrayCodeAndReturnsMaterialsForTrimmedTrayCode() {
|
||||
HashMap<String, Object> material = new HashMap<>();
|
||||
material.put("stockId", "KC001");
|
||||
material.put("stockDetailId", "SD001");
|
||||
material.put("trayCode", "A001");
|
||||
material.put("materialCode", "MAT001");
|
||||
material.put("materialName", "物料1");
|
||||
material.put("quantity", "6");
|
||||
when(pdaMapper.selectTrayStockMaterials("A001")).thenReturn(List.of(material));
|
||||
|
||||
List<HashMap<String, Object>> result = service.trayStock(" A001 ");
|
||||
|
||||
assertThat(result).containsExactly(material);
|
||||
verify(pdaMapper).selectTrayStockMaterials("A001");
|
||||
assertThatThrownBy(() -> service.trayStock(" "))
|
||||
.isInstanceOf(RuntimeException.class)
|
||||
.hasMessage("托盘码不能为空");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addEmptyTrayOutboundJobCreatesOneJobPerRequestedTrayAndPushesWcs() {
|
||||
EmptyTrayOutboundRequest request = new EmptyTrayOutboundRequest();
|
||||
request.setWarehouseId("WH01");
|
||||
request.setBarcodePrefix("A-");
|
||||
request.setQuantity(2);
|
||||
BasWarehouse warehouse = new BasWarehouse();
|
||||
warehouse.setWarehouseName("一号仓");
|
||||
when(basWarehouseService.selectBasWarehouseByWarehouseId("WH01")).thenReturn(warehouse);
|
||||
when(pdaMapper.countAvailableEmptyTrays(any(HashMap.class))).thenReturn(2);
|
||||
BasStationPoint exitPoint = new BasStationPoint();
|
||||
exitPoint.setPointId("OUT01");
|
||||
exitPoint.setPointType("2");
|
||||
exitPoint.setStatus("0");
|
||||
exitPoint.setPointStatus("0");
|
||||
when(basStationPointService.selectBasStationPointList(any(BasStationPoint.class))).thenReturn(List.of(exitPoint));
|
||||
when(pdaMapper.selectAvailableEmptyTrayStocks(any(HashMap.class))).thenReturn(List.of(
|
||||
emptyTrayStock("SD001", "KC001", "A001", "CS001", "货位1"),
|
||||
emptyTrayStock("SD002", "KC002", "A002", "CS002", "货位2")
|
||||
));
|
||||
when(jobIdService.getId("CK")).thenReturn("CK_EMPTY_1", "CK_EMPTY_2");
|
||||
when(taskNoService.getTaskNo("20")).thenReturn("50001", "50002");
|
||||
when(mydoJobService.insertMydoJob(any(MydoJob.class))).thenReturn(1);
|
||||
when(mydoJobDetailService.insertMydoJobDetail(any(MydoJobDetail.class))).thenReturn(1);
|
||||
when(pdaMapper.reserveEmptyTrayStockDetail(any(HashMap.class))).thenReturn(1);
|
||||
when(mydoJobService.pushOutJobToWcs(anyString())).thenReturn(AjaxResult.success("WCS出库作业下发成功:OK"));
|
||||
|
||||
String result = service.addEmptyTrayOutboundJob(request);
|
||||
|
||||
assertThat(result).contains("空托出库作业生成成功").contains("CK_EMPTY_1").contains("CK_EMPTY_2");
|
||||
ArgumentCaptor<MydoJob> jobCaptor = ArgumentCaptor.forClass(MydoJob.class);
|
||||
verify(mydoJobService, times(2)).insertMydoJob(jobCaptor.capture());
|
||||
assertThat(jobCaptor.getAllValues()).extracting(MydoJob::getInOrOut).containsExactly(22L, 22L);
|
||||
assertThat(jobCaptor.getAllValues()).extracting(MydoJob::getStatus).containsExactly("1", "1");
|
||||
assertThat(jobCaptor.getAllValues()).extracting(MydoJob::getExecutionPhase).containsExactly("0", "0");
|
||||
assertThat(jobCaptor.getAllValues()).extracting(MydoJob::getExitId).containsExactly("OUT01", "OUT01");
|
||||
|
||||
ArgumentCaptor<MydoJobDetail> detailCaptor = ArgumentCaptor.forClass(MydoJobDetail.class);
|
||||
verify(mydoJobDetailService, times(2)).insertMydoJobDetail(detailCaptor.capture());
|
||||
assertThat(detailCaptor.getAllValues()).extracting(MydoJobDetail::getProductId).containsExactly("KTP0001", "KTP0001");
|
||||
assertThat(detailCaptor.getAllValues()).extracting(MydoJobDetail::getOutQuantity)
|
||||
.containsExactly(new BigDecimal("1"), new BigDecimal("1"));
|
||||
assertThat(detailCaptor.getAllValues()).extracting(MydoJobDetail::getStockinDetailId).containsExactly("SD001", "SD002");
|
||||
verify(mydoJobService).pushOutJobToWcs("CK_EMPTY_1");
|
||||
verify(mydoJobService).pushOutJobToWcs("CK_EMPTY_2");
|
||||
ArgumentCaptor<HashMap> stockQueryCaptor = ArgumentCaptor.forClass(HashMap.class);
|
||||
verify(pdaMapper).selectAvailableEmptyTrayStocks(stockQueryCaptor.capture());
|
||||
assertThat(stockQueryCaptor.getValue())
|
||||
.containsEntry("warehouseId", "WH01")
|
||||
.containsEntry("barcodePrefix", "A-")
|
||||
.containsEntry("limit", 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void confirmOutboundCreatesReturnInboundJobWithOutboundExitAsEntranceWhenTrayHasResidualMaterial() {
|
||||
HashMap<String, Object> outboundDetail = new HashMap<>();
|
||||
outboundDetail.put("jobId", "CK001");
|
||||
outboundDetail.put("taskNo", "TASK001");
|
||||
outboundDetail.put("trayCode", "A001");
|
||||
outboundDetail.put("quantity", "5");
|
||||
when(pdaMapper.selectOutboundDetail(any(HashMap.class))).thenReturn(outboundDetail);
|
||||
when(pdaMapper.selectOutboundTrayMaterials(any(HashMap.class))).thenReturn(Collections.emptyList());
|
||||
|
||||
MydoJob outboundJob = new MydoJob();
|
||||
outboundJob.setJobId("CK001");
|
||||
outboundJob.setTrayCode("A001");
|
||||
outboundJob.setWarehouseId("WH01");
|
||||
outboundJob.setWarehouseName("一号仓");
|
||||
outboundJob.setCargoSpaceId("CS01");
|
||||
outboundJob.setCargoSpaceName("一层一列");
|
||||
outboundJob.setExitId("EXIT01");
|
||||
when(mydoJobService.selectMydoJobByJobId("CK001")).thenReturn(outboundJob);
|
||||
when(mydoJobService.completeJob("CK001", "2")).thenReturn(AjaxResult.success());
|
||||
|
||||
HashMap<String, Object> residualMaterial = new HashMap<>();
|
||||
residualMaterial.put("stockId", "KC001");
|
||||
residualMaterial.put("stockDetailId", "SD001");
|
||||
residualMaterial.put("productId", "MAT001");
|
||||
residualMaterial.put("productName", "物料1");
|
||||
residualMaterial.put("lotNo", "L001");
|
||||
residualMaterial.put("quantity", "3");
|
||||
when(pdaMapper.selectResidualTrayMaterials(any(HashMap.class))).thenReturn(List.of(residualMaterial));
|
||||
when(jobIdService.getId("HL")).thenReturn("HL001");
|
||||
when(taskNoService.getTaskNo("5")).thenReturn("80001");
|
||||
BasStationPoint exitPoint = new BasStationPoint();
|
||||
exitPoint.setRoadwayId("R02");
|
||||
when(basStationPointService.selectBasStationPointByPointId("EXIT01")).thenReturn(exitPoint);
|
||||
when(wcsClient.sendPutawayTask(any(MydoJob.class))).thenReturn(ApiResponse.ok("OK"));
|
||||
when(mydoJobService.updateMydoJob(any(MydoJob.class))).thenReturn(1);
|
||||
|
||||
HashMap<String, Object> data = new HashMap<>();
|
||||
data.put("taskNo", "TASK001");
|
||||
data.put("barcode", "A001");
|
||||
data.put("quantity", "5");
|
||||
service.confirmOutbound(data);
|
||||
|
||||
ArgumentCaptor<MydoJob> jobCaptor = ArgumentCaptor.forClass(MydoJob.class);
|
||||
verify(mydoJobService).insertMydoJob(jobCaptor.capture());
|
||||
MydoJob returnJob = jobCaptor.getValue();
|
||||
assertThat(returnJob.getJobId()).isEqualTo("HL001");
|
||||
assertThat(returnJob.getTaskNo()).isEqualTo("80001");
|
||||
assertThat(returnJob.getInOrOut()).isEqualTo(5L);
|
||||
assertThat(returnJob.getEntranceId()).isEqualTo("EXIT01");
|
||||
assertThat(returnJob.getRoadwayId()).isEqualTo("R02");
|
||||
assertThat(returnJob.getCargoSpaceId()).isNull();
|
||||
assertThat(returnJob.getCargoSpaceName()).isNull();
|
||||
verify(wcsClient).sendPutawayTask(returnJob);
|
||||
|
||||
ArgumentCaptor<MydoJobDetail> detailCaptor = ArgumentCaptor.forClass(MydoJobDetail.class);
|
||||
verify(mydoJobDetailService).insertMydoJobDetail(detailCaptor.capture());
|
||||
assertThat(detailCaptor.getValue().getJobId()).isEqualTo("HL001");
|
||||
assertThat(detailCaptor.getValue().getStockQuantity()).isEqualByComparingTo("3");
|
||||
|
||||
ArgumentCaptor<MydoJob> updateCaptor = ArgumentCaptor.forClass(MydoJob.class);
|
||||
verify(mydoJobService).updateMydoJob(updateCaptor.capture());
|
||||
assertThat(updateCaptor.getValue().getJobId()).isEqualTo("HL001");
|
||||
assertThat(updateCaptor.getValue().getStatus()).isEqualTo("3");
|
||||
assertThat(updateCaptor.getValue().getExecutionPhase()).isEqualTo("1");
|
||||
assertThat(updateCaptor.getValue().getStartTime()).isNotBlank();
|
||||
}
|
||||
|
||||
@Test
|
||||
void confirmOutboundUpdatesEachJobDetailWithActualMaterialQuantityBeforeCompletion() {
|
||||
HashMap<String, Object> outboundDetail = new HashMap<>();
|
||||
outboundDetail.put("jobId", "CK002");
|
||||
outboundDetail.put("taskNo", "TASK002");
|
||||
outboundDetail.put("trayCode", "A002");
|
||||
outboundDetail.put("quantity", "7");
|
||||
when(pdaMapper.selectOutboundDetail(any(HashMap.class))).thenReturn(outboundDetail);
|
||||
|
||||
HashMap<String, Object> materialA = new HashMap<>();
|
||||
materialA.put("jobDetailId", "CK002-1");
|
||||
materialA.put("stockDetailId", "SD001");
|
||||
materialA.put("stockQuantity", "6");
|
||||
materialA.put("quantity", "6");
|
||||
HashMap<String, Object> materialB = new HashMap<>();
|
||||
materialB.put("jobDetailId", "CK002-2");
|
||||
materialB.put("stockDetailId", "SD002");
|
||||
materialB.put("stockQuantity", "2");
|
||||
materialB.put("quantity", "1");
|
||||
when(pdaMapper.selectOutboundTrayMaterials(any(HashMap.class))).thenReturn(List.of(materialA, materialB));
|
||||
|
||||
MydoJob outboundJob = new MydoJob();
|
||||
outboundJob.setJobId("CK002");
|
||||
outboundJob.setTrayCode("A002");
|
||||
outboundJob.setWarehouseId("WH01");
|
||||
when(mydoJobService.selectMydoJobByJobId("CK002")).thenReturn(outboundJob);
|
||||
when(mydoJobService.completeJob("CK002", "2")).thenReturn(AjaxResult.success());
|
||||
when(pdaMapper.selectResidualTrayMaterials(any(HashMap.class))).thenReturn(Collections.emptyList());
|
||||
when(mydoJobDetailService.updateMydoJobDetail(any(MydoJobDetail.class))).thenReturn(1);
|
||||
|
||||
HashMap<String, Object> requestMaterialA = new HashMap<>();
|
||||
requestMaterialA.put("jobDetailId", "CK002-1");
|
||||
requestMaterialA.put("stockDetailId", "SD001");
|
||||
requestMaterialA.put("quantity", "5");
|
||||
HashMap<String, Object> requestMaterialB = new HashMap<>();
|
||||
requestMaterialB.put("jobDetailId", "CK002-2");
|
||||
requestMaterialB.put("stockDetailId", "SD002");
|
||||
requestMaterialB.put("quantity", "2");
|
||||
HashMap<String, Object> data = new HashMap<>();
|
||||
data.put("taskNo", "TASK002");
|
||||
data.put("barcode", "A002");
|
||||
data.put("quantity", "7");
|
||||
data.put("materials", List.of(requestMaterialA, requestMaterialB));
|
||||
|
||||
service.confirmOutbound(data);
|
||||
|
||||
ArgumentCaptor<MydoJobDetail> detailCaptor = ArgumentCaptor.forClass(MydoJobDetail.class);
|
||||
verify(mydoJobDetailService, times(2)).updateMydoJobDetail(detailCaptor.capture());
|
||||
assertThat(detailCaptor.getAllValues()).extracting(MydoJobDetail::getJobDetailId)
|
||||
.containsExactly("CK002-1", "CK002-2");
|
||||
assertThat(detailCaptor.getAllValues()).extracting(MydoJobDetail::getOutQuantity)
|
||||
.containsExactly(new BigDecimal("5"), new BigDecimal("2"));
|
||||
verify(mydoJobService).completeJob("CK002", "2");
|
||||
}
|
||||
|
||||
private void stubInboundStationAndRoadway() {
|
||||
BasStationPoint point = new BasStationPoint();
|
||||
point.setPointId("IN01");
|
||||
point.setWarehouseId("WH01");
|
||||
point.setPointType("1");
|
||||
point.setStatus("0");
|
||||
point.setPointStatus("0");
|
||||
point.setTrayType("A");
|
||||
point.setRoadwayId("R01");
|
||||
when(basStationPointService.selectBasStationPointByPointId("IN01")).thenReturn(point);
|
||||
when(basStationPointService.occupyBasStationPoint("IN01")).thenReturn(1);
|
||||
|
||||
HashMap<String, Object> trayRule = new HashMap<>();
|
||||
trayRule.put("allowed_cargo_type", "A");
|
||||
when(pdaMapper.selectMatchedTrayRule(any(HashMap.class))).thenReturn(trayRule);
|
||||
HashMap<String, Object> roadway = new HashMap<>();
|
||||
roadway.put("roadway_id", "R01");
|
||||
when(pdaMapper.selectAvailableInboundRoadway(any(HashMap.class))).thenReturn(roadway);
|
||||
}
|
||||
|
||||
private HashMap<String, Object> emptyTrayStock(String detailId, String stockId, String trayCode, String cargoSpaceId, String cargoSpaceName) {
|
||||
HashMap<String, Object> stock = new HashMap<>();
|
||||
stock.put("stockDetailId", detailId);
|
||||
stock.put("stockId", stockId);
|
||||
stock.put("trayCode", trayCode);
|
||||
stock.put("warehouseId", "WH01");
|
||||
stock.put("warehouseName", "一号仓");
|
||||
stock.put("cargoSpaceId", cargoSpaceId);
|
||||
stock.put("cargoSpaceName", cargoSpaceName);
|
||||
stock.put("roadwayId", "R01");
|
||||
stock.put("availableQuantity", "1");
|
||||
return stock;
|
||||
}
|
||||
|
||||
private MydoJob stubSuccessfulInboundJobSetup() {
|
||||
BasStationPoint point = new BasStationPoint();
|
||||
point.setPointId("IN01");
|
||||
point.setWarehouseId("WH01");
|
||||
point.setPointType("1");
|
||||
point.setStatus("0");
|
||||
point.setPointStatus("0");
|
||||
point.setTrayType("A");
|
||||
point.setRoadwayId("R01");
|
||||
when(basStationPointService.selectBasStationPointByPointId("IN01")).thenReturn(point);
|
||||
when(basStationPointService.occupyBasStationPoint("IN01")).thenReturn(1);
|
||||
|
||||
HashMap<String, Object> trayRule = new HashMap<>();
|
||||
trayRule.put("allowed_cargo_type", "A");
|
||||
when(pdaMapper.selectMatchedTrayRule(any(HashMap.class))).thenReturn(trayRule);
|
||||
HashMap<String, Object> roadway = new HashMap<>();
|
||||
roadway.put("roadway_id", "R01");
|
||||
when(pdaMapper.selectAvailableInboundRoadway(any(HashMap.class))).thenReturn(roadway);
|
||||
doAnswer(invocation -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
HashMap<String, Object> map = invocation.getArgument(0);
|
||||
map.put("v_result", "1");
|
||||
map.put("v_resultmsg", "生成作业成功");
|
||||
return null;
|
||||
}).when(pdaMapper).addInJob(any(HashMap.class));
|
||||
|
||||
MydoJob job = new MydoJob();
|
||||
job.setJobId("RK001");
|
||||
job.setTaskNo("TASK001");
|
||||
job.setTrayCode("A001");
|
||||
job.setWarehouseId("WH01");
|
||||
when(mydoJobService.selectLatestInMydoJobByTrayCodeAndWarehouseId("A001", "WH01")).thenReturn(job);
|
||||
BasWarehouse warehouse = new BasWarehouse();
|
||||
warehouse.setWarehouseName("一号仓");
|
||||
when(basWarehouseService.selectBasWarehouseByWarehouseId("WH01")).thenReturn(warehouse);
|
||||
when(mydoJobService.updateMydoJob(any(MydoJob.class))).thenReturn(1);
|
||||
when(mydoJobDetailService.selectMydoJobDetailList(any())).thenReturn(Collections.emptyList());
|
||||
return job;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user