feat: add manual WCS dispatch action

This commit is contained in:
zgw
2026-07-16 11:05:16 +08:00
parent 4641bddcea
commit 10ce28f8ac
5 changed files with 202 additions and 19 deletions

View File

@@ -74,7 +74,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="priority != null "> and priority = #{priority}</if>
<if test="height != null "> and height = #{height}</if>
<if test="weight != null "> and weight = #{weight}</if>
<if test="params.beginCreateTime != null and params.beginCreateTime != '' and params.endCreateTime != null and params.endCreateTime != ''"> and create_time between #{params.beginCreateTime} and #{params.endCreateTime}</if>
<if test="params.beginCreateTime != null and params.beginCreateTime != '' and params.endCreateTime != null and params.endCreateTime != ''"> and mydo_job.create_time between #{params.beginCreateTime} and #{params.endCreateTime}</if>
<if test="entranceId != null and entranceId != ''"> and entrance_id = #{entranceId}</if>
<if test="exitId != null and exitId != ''"> and exit_id = #{exitId}</if>
<if test="deptName != null and deptName != ''"> and dept_name like concat('%', #{deptName}, '%')</if>
@@ -430,7 +430,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="priority != null "> and priority = #{priority}</if>
<if test="height != null "> and height = #{height}</if>
<if test="weight != null "> and weight = #{weight}</if>
<if test="params.beginCreateTime != null and params.beginCreateTime != '' and params.endCreateTime != null and params.endCreateTime != ''"> and create_time between #{params.beginCreateTime} and #{params.endCreateTime}</if>
<if test="params.beginCreateTime != null and params.beginCreateTime != '' and params.endCreateTime != null and params.endCreateTime != ''"> and mydo_job.create_time between #{params.beginCreateTime} and #{params.endCreateTime}</if>
<if test="entranceId != null and entranceId != ''"> and entrance_id = #{entranceId}</if>
<if test="exitId != null and exitId != ''"> and exit_id = #{exitId}</if>
<if test="deptName != null and deptName != ''"> and dept_name like concat('%', #{deptName}, '%')</if>
@@ -484,7 +484,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="priority != null "> and priority = #{priority}</if>
<if test="height != null "> and height = #{height}</if>
<if test="weight != null "> and weight = #{weight}</if>
<if test="params.beginCreateTime != null and params.beginCreateTime != '' and params.endCreateTime != null and params.endCreateTime != ''"> and create_time between #{params.beginCreateTime} and #{params.endCreateTime}</if>
<if test="params.beginCreateTime != null and params.beginCreateTime != '' and params.endCreateTime != null and params.endCreateTime != ''"> and mydo_job.create_time between #{params.beginCreateTime} and #{params.endCreateTime}</if>
<if test="entranceId != null and entranceId != ''"> and entrance_id = #{entranceId}</if>
<if test="exitId != null and exitId != ''"> and exit_id = #{exitId}</if>
<if test="deptName != null and deptName != ''"> and dept_name like concat('%', #{deptName}, '%')</if>
@@ -594,7 +594,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="priority != null "> and priority = #{priority}</if>
<if test="height != null "> and height = #{height}</if>
<if test="weight != null "> and weight = #{weight}</if>
<if test="params.beginCreateTime != null and params.beginCreateTime != '' and params.endCreateTime != null and params.endCreateTime != ''"> and create_time between #{params.beginCreateTime} and #{params.endCreateTime}</if>
<if test="params.beginCreateTime != null and params.beginCreateTime != '' and params.endCreateTime != null and params.endCreateTime != ''"> and mydo_job.create_time between #{params.beginCreateTime} and #{params.endCreateTime}</if>
<if test="entranceId != null and entranceId != ''"> and entrance_id = #{entranceId}</if>
<if test="exitId != null and exitId != ''"> and exit_id = #{exitId}</if>
<if test="deptName != null and deptName != ''"> and dept_name like concat('%', #{deptName}, '%')</if>
@@ -622,7 +622,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="priority != null "> and priority = #{priority}</if>
<if test="height != null "> and height = #{height}</if>
<if test="weight != null "> and weight = #{weight}</if>
<if test="params.beginCreateTime != null and params.beginCreateTime != '' and params.endCreateTime != null and params.endCreateTime != ''"> and create_time between #{params.beginCreateTime} and #{params.endCreateTime}</if>
<if test="params.beginCreateTime != null and params.beginCreateTime != '' and params.endCreateTime != null and params.endCreateTime != ''"> and mydo_job.create_time between #{params.beginCreateTime} and #{params.endCreateTime}</if>
<if test="entranceId != null and entranceId != ''"> and entrance_id = #{entranceId}</if>
<if test="exitId != null and exitId != ''"> and exit_id = #{exitId}</if>
<if test="deptName != null and deptName != ''"> and dept_name like concat('%', #{deptName}, '%')</if>

View File

@@ -88,6 +88,19 @@ class MydoJobMapperSqlTest {
.contains("<if test=\"warehouseId != null and warehouseId != ''\"> and warehouse_id = #{warehouseId}</if>");
}
@Test
void outboundListQualifiesCreateTimeAfterJoiningDispatchOutbox() throws Exception {
String xml = Files.readString(resolveMapperXml());
for (String selectId : new String[] {"selectMydoJobList", "selectInMydoJobList",
"selectInventoryInMydoJobList", "selectOutMydoJobList", "selectInventoryOutMydoJobList"}) {
String activeList = section(xml, "<select id=\"" + selectId + "\"", "</select>");
assertThat(activeList)
.as(selectId)
.contains("mydo_job.create_time between #{params.beginCreateTime} and #{params.endCreateTime}")
.doesNotContain("> and create_time between #{params.beginCreateTime}");
}
}
@Test
void denseInboundJobClaimIsAtomicAndLimitedToPendingWarehouse03Jobs() throws Exception {
String xml = Files.readString(resolveMapperXml());

View File

@@ -180,6 +180,21 @@ class OutboundDispatchServiceImplTest
verify(wcsClient, never()).sendRetrieveTask(any(MydoJob.class));
}
@Test
void completedJobCannotBeManuallyDispatched()
{
MydoJob job = jobOfType("CK-COMPLETED", 21L);
job.setStatus("4");
when(jobMapper.selectMydoJobByJobId("CK-COMPLETED")).thenReturn(job);
AjaxResult result = service.dispatch("CK-COMPLETED", DispatchTrigger.MANUAL);
assertThat(result.isSuccess()).isFalse();
assertThat(result.get("msg")).asString().contains("状态不允许");
verify(dispatchMapper, never()).insertIfAbsent(any(String.class));
verify(wcsClient, never()).sendRetrieveTask(any(MydoJob.class));
}
@Test
void blockedDenseJobDoesNotConsumeRetryOrCallWcs()
{

View File

@@ -0,0 +1,62 @@
package com.ruoyi.out.job.controller;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.out.dispatch.domain.DispatchTrigger;
import com.ruoyi.out.dispatch.service.IOutboundDispatchService;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class OutMydoJobDispatchControllerTest
{
@Test
void manualEndpointUsesDedicatedPermissionAndManualTrigger() throws Exception
{
Method endpoint = OutMydoJobController.class.getMethod("pushWcs", String.class);
assertThat(endpoint.getAnnotation(PreAuthorize.class).value())
.isEqualTo("@ss.hasPermi('out:job:pushWcs')");
IOutboundDispatchService dispatchService = mock(IOutboundDispatchService.class);
when(dispatchService.dispatch("CK-1", DispatchTrigger.MANUAL))
.thenReturn(AjaxResult.success("已下发"));
OutMydoJobController controller = new OutMydoJobController();
ReflectionTestUtils.setField(controller, "outboundDispatchService", dispatchService);
assertThat(controller.pushWcs("CK-1").isSuccess()).isTrue();
verify(dispatchService).dispatch("CK-1", DispatchTrigger.MANUAL);
}
@Test
void jobPageShowsDispatchStateAndRestrictsManualAction() throws Exception
{
String vue = Files.readString(resolve("ruoyi-ui/src/views/out/job/index.vue"));
String api = Files.readString(resolve("ruoyi-ui/src/api/out/job.js"));
assertThat(vue)
.contains("prop=\"dispatchStatus\"")
.contains("prop=\"autoRetryCount\"")
.contains("lastDispatchError")
.contains("out:job:pushWcs")
.contains("canPushWcs")
.contains("手动下发 WCS")
.doesNotContain("v-hasPermi=\"['out:job:edit']\"");
assertThat(api).contains("/out/job/pushWcs/");
}
private Path resolve(String path)
{
return Stream.of(Path.of(path), Path.of("..", path))
.filter(Files::exists)
.findFirst()
.orElseThrow();
}
}