feat: prepare dense blockers during outbound allocation
This commit is contained in:
@@ -28,9 +28,14 @@ import com.ruoyi.out.location.domain.LocationOutboundGenerateRequest;
|
|||||||
import com.ruoyi.out.location.domain.LocationOutboundStock;
|
import com.ruoyi.out.location.domain.LocationOutboundStock;
|
||||||
import com.ruoyi.out.location.mapper.LocationOutboundMapper;
|
import com.ruoyi.out.location.mapper.LocationOutboundMapper;
|
||||||
import com.ruoyi.out.location.service.ILocationOutboundService;
|
import com.ruoyi.out.location.service.ILocationOutboundService;
|
||||||
|
import com.ruoyi.out.dispatch.domain.DispatchTrigger;
|
||||||
|
import com.ruoyi.out.dispatch.service.IOutboundDispatchService;
|
||||||
|
import com.ruoyi.out.relocation.service.IRelocationJobService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronization;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 指定货位出库服务实现。
|
* 指定货位出库服务实现。
|
||||||
@@ -62,6 +67,12 @@ public class LocationOutboundServiceImpl implements ILocationOutboundService
|
|||||||
@Autowired
|
@Autowired
|
||||||
private TaskNoService taskNoService;
|
private TaskNoService taskNoService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IRelocationJobService relocationJobService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IOutboundDispatchService dispatchService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@WarehouseScope(warehouseField = "bcs.warehouse_id")
|
@WarehouseScope(warehouseField = "bcs.warehouse_id")
|
||||||
public List<LocationOutboundStock> selectLocationOutboundStockList(LocationOutboundStock query)
|
public List<LocationOutboundStock> selectLocationOutboundStockList(LocationOutboundStock query)
|
||||||
@@ -150,11 +161,12 @@ public class LocationOutboundServiceImpl implements ILocationOutboundService
|
|||||||
basCargoSpaceMapper.markOutboundAssignedByStockId(stock.getStockId());
|
basCargoSpaceMapper.markOutboundAssignedByStockId(stock.getStockId());
|
||||||
}
|
}
|
||||||
|
|
||||||
AjaxResult pushResult = mydoJobService.pushOutJobToWcs(jobId);
|
List<String> relocationIds = relocationJobService.createBlockerRelocations(jobId);
|
||||||
if (pushResult == null || pushResult.isError()) {
|
AjaxResult pushResult = dispatchAfterCommit(jobId);
|
||||||
throw new RuntimeException(pushResult == null ? "WCS出库作业下发失败" : Convert.toStr(pushResult.get(AjaxResult.MSG_TAG)));
|
String relocationMessage = relocationIds == null || relocationIds.isEmpty()
|
||||||
}
|
? "" : ";已生成" + relocationIds.size() + "个遮挡移库作业";
|
||||||
return "指定货位出库作业生成成功:" + jobId + ";" + Convert.toStr(pushResult.get(AjaxResult.MSG_TAG));
|
return "指定货位出库作业生成成功:" + jobId + relocationMessage
|
||||||
|
+ ";" + Convert.toStr(pushResult.get(AjaxResult.MSG_TAG));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkRequest(LocationOutboundGenerateRequest request)
|
private void checkRequest(LocationOutboundGenerateRequest request)
|
||||||
@@ -308,4 +320,21 @@ public class LocationOutboundServiceImpl implements ILocationOutboundService
|
|||||||
// External callbacks and focused unit tests can run without login context.
|
// External callbacks and focused unit tests can run without login context.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AjaxResult dispatchAfterCommit(String jobId)
|
||||||
|
{
|
||||||
|
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||||
|
AjaxResult result = dispatchService.dispatch(jobId, DispatchTrigger.INITIAL);
|
||||||
|
return result == null ? AjaxResult.error("WCS统一下发服务无返回") : result;
|
||||||
|
}
|
||||||
|
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void afterCommit()
|
||||||
|
{
|
||||||
|
dispatchService.dispatch(jobId, DispatchTrigger.INITIAL);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return AjaxResult.success("作业已生成,事务提交后下发WCS");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ import com.ruoyi.in.job.domain.MydoJob;
|
|||||||
import com.ruoyi.in.job.service.IMydoJobService;
|
import com.ruoyi.in.job.service.IMydoJobService;
|
||||||
import com.ruoyi.out.stockout.domain.OutStockoutAssignDTO;
|
import com.ruoyi.out.stockout.domain.OutStockoutAssignDTO;
|
||||||
import com.ruoyi.out.stockout.domain.OutStockoutManualAssignDTO;
|
import com.ruoyi.out.stockout.domain.OutStockoutManualAssignDTO;
|
||||||
|
import com.ruoyi.out.dispatch.domain.DispatchTrigger;
|
||||||
|
import com.ruoyi.out.dispatch.service.IOutboundDispatchService;
|
||||||
|
import com.ruoyi.out.relocation.service.IRelocationJobService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -57,6 +60,12 @@ public class OutStockoutServiceImpl implements IOutStockoutService
|
|||||||
@Autowired
|
@Autowired
|
||||||
private BasCargoSpaceMapper basCargoSpaceMapper;
|
private BasCargoSpaceMapper basCargoSpaceMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IRelocationJobService relocationJobService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IOutboundDispatchService dispatchService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询出库单据主信息
|
* 查询出库单据主信息
|
||||||
*/
|
*/
|
||||||
@@ -334,7 +343,7 @@ public class OutStockoutServiceImpl implements IOutStockoutService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将新生成的出库作业推送到 WCS。推送成功后把作业状态从未执行改为待执行,避免重复下发。
|
* 为新生成作业准备密集库遮挡移库,并统一通过 Outbox 下发 WCS。
|
||||||
*/
|
*/
|
||||||
private AjaxResult pushPendingOutJobs(String stockoutDetailId)
|
private AjaxResult pushPendingOutJobs(String stockoutDetailId)
|
||||||
{
|
{
|
||||||
@@ -344,11 +353,16 @@ public class OutStockoutServiceImpl implements IOutStockoutService
|
|||||||
}
|
}
|
||||||
StringBuilder message = new StringBuilder();
|
StringBuilder message = new StringBuilder();
|
||||||
for (MydoJob job : jobs) {
|
for (MydoJob job : jobs) {
|
||||||
AjaxResult pushResult = mydoJobService.pushOutJobToWcs(job.getJobId());
|
List<String> relocationIds = relocationJobService.createBlockerRelocations(job.getJobId());
|
||||||
|
AjaxResult pushResult = dispatchService.dispatch(job.getJobId(), DispatchTrigger.INITIAL);
|
||||||
if (pushResult == null || pushResult.isError()) {
|
if (pushResult == null || pushResult.isError()) {
|
||||||
return AjaxResult.error(pushResult == null ? "WCS出库作业下发失败" : Convert.toStr(pushResult.get(AjaxResult.MSG_TAG)));
|
return AjaxResult.error(pushResult == null ? "WCS出库作业下发失败" : Convert.toStr(pushResult.get(AjaxResult.MSG_TAG)));
|
||||||
}
|
}
|
||||||
message.append(job.getJobId()).append(":").append(Convert.toStr(pushResult.get(AjaxResult.MSG_TAG))).append("|");
|
message.append(job.getJobId()).append(":");
|
||||||
|
if (relocationIds != null && !relocationIds.isEmpty()) {
|
||||||
|
message.append("已生成").append(relocationIds.size()).append("个遮挡移库作业;");
|
||||||
|
}
|
||||||
|
message.append(Convert.toStr(pushResult.get(AjaxResult.MSG_TAG))).append("|");
|
||||||
}
|
}
|
||||||
return AjaxResult.success(message.toString());
|
return AjaxResult.success(message.toString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ import com.ruoyi.in.jobdetail.service.IMydoJobDetailService;
|
|||||||
import com.ruoyi.out.location.domain.LocationOutboundGenerateRequest;
|
import com.ruoyi.out.location.domain.LocationOutboundGenerateRequest;
|
||||||
import com.ruoyi.out.location.domain.LocationOutboundStock;
|
import com.ruoyi.out.location.domain.LocationOutboundStock;
|
||||||
import com.ruoyi.out.location.mapper.LocationOutboundMapper;
|
import com.ruoyi.out.location.mapper.LocationOutboundMapper;
|
||||||
|
import com.ruoyi.out.dispatch.domain.DispatchTrigger;
|
||||||
|
import com.ruoyi.out.dispatch.service.IOutboundDispatchService;
|
||||||
|
import com.ruoyi.out.relocation.service.IRelocationJobService;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
@@ -59,6 +62,12 @@ class LocationOutboundServiceImplTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private TaskNoService taskNoService;
|
private TaskNoService taskNoService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IRelocationJobService relocationJobService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IOutboundDispatchService dispatchService;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private LocationOutboundServiceImpl service;
|
private LocationOutboundServiceImpl service;
|
||||||
|
|
||||||
@@ -98,14 +107,16 @@ class LocationOutboundServiceImplTest {
|
|||||||
when(locationOutboundMapper.reserveStockDetailForOutbound(any(HashMap.class))).thenReturn(1);
|
when(locationOutboundMapper.reserveStockDetailForOutbound(any(HashMap.class))).thenReturn(1);
|
||||||
when(basStockMapper.refreshBasStockSummary("KC001")).thenReturn(1);
|
when(basStockMapper.refreshBasStockSummary("KC001")).thenReturn(1);
|
||||||
when(basCargoSpaceMapper.markOutboundAssignedByStockId("KC001")).thenReturn(1);
|
when(basCargoSpaceMapper.markOutboundAssignedByStockId("KC001")).thenReturn(1);
|
||||||
when(mydoJobService.pushOutJobToWcs("CK001")).thenReturn(AjaxResult.success("WCS出库作业下发成功:OK"));
|
when(relocationJobService.createBlockerRelocations("CK001")).thenReturn(List.of("MOVE-1"));
|
||||||
|
when(dispatchService.dispatch("CK001", DispatchTrigger.INITIAL))
|
||||||
|
.thenReturn(AjaxResult.success("作业暂未下发WCS:等待移库"));
|
||||||
|
|
||||||
LocationOutboundGenerateRequest request = new LocationOutboundGenerateRequest();
|
LocationOutboundGenerateRequest request = new LocationOutboundGenerateRequest();
|
||||||
request.setStockId("KC001");
|
request.setStockId("KC001");
|
||||||
request.setExitId("OUT01");
|
request.setExitId("OUT01");
|
||||||
String result = service.generateWholeTrayOutboundJob(request);
|
String result = service.generateWholeTrayOutboundJob(request);
|
||||||
|
|
||||||
assertThat(result).contains("CK001").contains("WCS出库作业下发成功");
|
assertThat(result).contains("CK001").contains("等待移库");
|
||||||
|
|
||||||
ArgumentCaptor<MydoJob> jobCaptor = ArgumentCaptor.forClass(MydoJob.class);
|
ArgumentCaptor<MydoJob> jobCaptor = ArgumentCaptor.forClass(MydoJob.class);
|
||||||
verify(mydoJobService).insertMydoJob(jobCaptor.capture());
|
verify(mydoJobService).insertMydoJob(jobCaptor.capture());
|
||||||
@@ -132,7 +143,9 @@ class LocationOutboundServiceImplTest {
|
|||||||
.containsExactly("SD001", "SD002");
|
.containsExactly("SD001", "SD002");
|
||||||
verify(basStockMapper).refreshBasStockSummary("KC001");
|
verify(basStockMapper).refreshBasStockSummary("KC001");
|
||||||
verify(basCargoSpaceMapper).markOutboundAssignedByStockId("KC001");
|
verify(basCargoSpaceMapper).markOutboundAssignedByStockId("KC001");
|
||||||
verify(mydoJobService).pushOutJobToWcs("CK001");
|
verify(relocationJobService).createBlockerRelocations("CK001");
|
||||||
|
verify(dispatchService).dispatch("CK001", DispatchTrigger.INITIAL);
|
||||||
|
verify(mydoJobService, never()).pushOutJobToWcs(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -157,7 +170,9 @@ class LocationOutboundServiceImplTest {
|
|||||||
when(taskNoService.getTaskNo("20")).thenReturn("50002");
|
when(taskNoService.getTaskNo("20")).thenReturn("50002");
|
||||||
when(mydoJobService.insertMydoJob(any(MydoJob.class))).thenReturn(1);
|
when(mydoJobService.insertMydoJob(any(MydoJob.class))).thenReturn(1);
|
||||||
when(basCargoSpaceMapper.markOutboundAssignedByCargoSpaceId("CS-EMPTY")).thenReturn(1);
|
when(basCargoSpaceMapper.markOutboundAssignedByCargoSpaceId("CS-EMPTY")).thenReturn(1);
|
||||||
when(mydoJobService.pushOutJobToWcs("CK002")).thenReturn(AjaxResult.success("WCS出库作业下发成功:OK"));
|
when(relocationJobService.createBlockerRelocations("CK002")).thenReturn(List.of());
|
||||||
|
when(dispatchService.dispatch("CK002", DispatchTrigger.INITIAL))
|
||||||
|
.thenReturn(AjaxResult.success("WCS出库作业下发成功:OK"));
|
||||||
|
|
||||||
LocationOutboundGenerateRequest request = new LocationOutboundGenerateRequest();
|
LocationOutboundGenerateRequest request = new LocationOutboundGenerateRequest();
|
||||||
request.setCargoSpaceId("CS-EMPTY");
|
request.setCargoSpaceId("CS-EMPTY");
|
||||||
@@ -181,7 +196,9 @@ class LocationOutboundServiceImplTest {
|
|||||||
verify(locationOutboundMapper, never()).reserveStockDetailForOutbound(any(HashMap.class));
|
verify(locationOutboundMapper, never()).reserveStockDetailForOutbound(any(HashMap.class));
|
||||||
verify(basStockMapper, never()).refreshBasStockSummary(any());
|
verify(basStockMapper, never()).refreshBasStockSummary(any());
|
||||||
verify(basCargoSpaceMapper).markOutboundAssignedByCargoSpaceId("CS-EMPTY");
|
verify(basCargoSpaceMapper).markOutboundAssignedByCargoSpaceId("CS-EMPTY");
|
||||||
verify(mydoJobService).pushOutJobToWcs("CK002");
|
verify(relocationJobService).createBlockerRelocations("CK002");
|
||||||
|
verify(dispatchService).dispatch("CK002", DispatchTrigger.INITIAL);
|
||||||
|
verify(mydoJobService, never()).pushOutJobToWcs(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
private BasStockDetail stockDetail(String detailId, String productId, String productName, String lotNo, String availableQuantity) {
|
private BasStockDetail stockDetail(String detailId, String productId, String productName, String lotNo, String availableQuantity) {
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import com.ruoyi.bas.id.job.service.JobIdService;
|
|||||||
import com.ruoyi.bas.cargospace.mapper.BasCargoSpaceMapper;
|
import com.ruoyi.bas.cargospace.mapper.BasCargoSpaceMapper;
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
import com.ruoyi.in.job.service.IMydoJobService;
|
import com.ruoyi.in.job.service.IMydoJobService;
|
||||||
|
import com.ruoyi.in.job.domain.MydoJob;
|
||||||
|
import com.ruoyi.out.dispatch.domain.DispatchTrigger;
|
||||||
|
import com.ruoyi.out.dispatch.service.IOutboundDispatchService;
|
||||||
|
import com.ruoyi.out.relocation.service.IRelocationJobService;
|
||||||
import com.ruoyi.out.stockout.mapper.OutStockoutMapper;
|
import com.ruoyi.out.stockout.mapper.OutStockoutMapper;
|
||||||
import com.ruoyi.out.stockoutdetail.domain.OutStockoutDetail;
|
import com.ruoyi.out.stockoutdetail.domain.OutStockoutDetail;
|
||||||
import com.ruoyi.out.stockoutdetail.mapper.OutStockoutDetailMapper;
|
import com.ruoyi.out.stockoutdetail.mapper.OutStockoutDetailMapper;
|
||||||
@@ -46,6 +50,12 @@ class OutStockoutServiceImplTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private BasCargoSpaceMapper basCargoSpaceMapper;
|
private BasCargoSpaceMapper basCargoSpaceMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IRelocationJobService relocationJobService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IOutboundDispatchService dispatchService;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private OutStockoutServiceImpl service;
|
private OutStockoutServiceImpl service;
|
||||||
|
|
||||||
@@ -102,4 +112,35 @@ class OutStockoutServiceImplTest {
|
|||||||
.contains("没有可分配库存明细");
|
.contains("没有可分配库存明细");
|
||||||
verify(mydoJobService, never()).selectPendingOutMydoJobsByStockoutDetailId(any());
|
verify(mydoJobService, never()).selectPendingOutMydoJobsByStockoutDetailId(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void successfulAllocationCreatesDenseBlockersAndUsesUnifiedDispatch()
|
||||||
|
{
|
||||||
|
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", 10);
|
||||||
|
map.put("v_resultString", "自动分配完成");
|
||||||
|
return null;
|
||||||
|
}).when(outStockoutMapper).autoAssignOutStockoutByProc(any());
|
||||||
|
MydoJob job = new MydoJob();
|
||||||
|
job.setJobId("OUT-1");
|
||||||
|
when(mydoJobService.selectPendingOutMydoJobsByStockoutDetailId("CKD-1"))
|
||||||
|
.thenReturn(List.of(job));
|
||||||
|
when(relocationJobService.createBlockerRelocations("OUT-1"))
|
||||||
|
.thenReturn(List.of("MOVE-1"));
|
||||||
|
when(dispatchService.dispatch("OUT-1", DispatchTrigger.INITIAL))
|
||||||
|
.thenReturn(AjaxResult.success("作业暂未下发WCS:等待移库"));
|
||||||
|
|
||||||
|
AjaxResult result = service.autoAssignOutStockout("CKD", "02-AU-01");
|
||||||
|
|
||||||
|
assertThat(result.isSuccess()).isTrue();
|
||||||
|
verify(relocationJobService).createBlockerRelocations("OUT-1");
|
||||||
|
verify(dispatchService).dispatch("OUT-1", DispatchTrigger.INITIAL);
|
||||||
|
verify(mydoJobService, never()).pushOutJobToWcs(any());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user