diff --git a/ruoyi-admin/src/main/java/com/ruoyi/out/location/service/impl/LocationOutboundServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/out/location/service/impl/LocationOutboundServiceImpl.java index 2aff6b1..19367a0 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/out/location/service/impl/LocationOutboundServiceImpl.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/out/location/service/impl/LocationOutboundServiceImpl.java @@ -28,9 +28,14 @@ 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 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.stereotype.Service; 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 private TaskNoService taskNoService; + @Autowired + private IRelocationJobService relocationJobService; + + @Autowired + private IOutboundDispatchService dispatchService; + @Override @WarehouseScope(warehouseField = "bcs.warehouse_id") public List selectLocationOutboundStockList(LocationOutboundStock query) @@ -150,11 +161,12 @@ public class LocationOutboundServiceImpl implements ILocationOutboundService 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)); + List relocationIds = relocationJobService.createBlockerRelocations(jobId); + AjaxResult pushResult = dispatchAfterCommit(jobId); + String relocationMessage = relocationIds == null || relocationIds.isEmpty() + ? "" : ";已生成" + relocationIds.size() + "个遮挡移库作业"; + return "指定货位出库作业生成成功:" + jobId + relocationMessage + + ";" + Convert.toStr(pushResult.get(AjaxResult.MSG_TAG)); } 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. } } + + 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"); + } } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/out/stockout/service/impl/OutStockoutServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/out/stockout/service/impl/OutStockoutServiceImpl.java index bc438a5..a9a9a27 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/out/stockout/service/impl/OutStockoutServiceImpl.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/out/stockout/service/impl/OutStockoutServiceImpl.java @@ -14,6 +14,9 @@ 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 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.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -57,6 +60,12 @@ public class OutStockoutServiceImpl implements IOutStockoutService @Autowired 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) { @@ -344,11 +353,16 @@ public class OutStockoutServiceImpl implements IOutStockoutService } StringBuilder message = new StringBuilder(); for (MydoJob job : jobs) { - AjaxResult pushResult = mydoJobService.pushOutJobToWcs(job.getJobId()); + List relocationIds = relocationJobService.createBlockerRelocations(job.getJobId()); + AjaxResult pushResult = dispatchService.dispatch(job.getJobId(), DispatchTrigger.INITIAL); if (pushResult == null || pushResult.isError()) { 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()); } diff --git a/ruoyi-admin/src/test/java/com/ruoyi/out/location/service/impl/LocationOutboundServiceImplTest.java b/ruoyi-admin/src/test/java/com/ruoyi/out/location/service/impl/LocationOutboundServiceImplTest.java index b54754d..f03149a 100644 --- a/ruoyi-admin/src/test/java/com/ruoyi/out/location/service/impl/LocationOutboundServiceImplTest.java +++ b/ruoyi-admin/src/test/java/com/ruoyi/out/location/service/impl/LocationOutboundServiceImplTest.java @@ -15,6 +15,9 @@ 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.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.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -59,6 +62,12 @@ class LocationOutboundServiceImplTest { @Mock private TaskNoService taskNoService; + @Mock + private IRelocationJobService relocationJobService; + + @Mock + private IOutboundDispatchService dispatchService; + @InjectMocks private LocationOutboundServiceImpl service; @@ -98,14 +107,16 @@ class LocationOutboundServiceImplTest { 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")); + when(relocationJobService.createBlockerRelocations("CK001")).thenReturn(List.of("MOVE-1")); + when(dispatchService.dispatch("CK001", DispatchTrigger.INITIAL)) + .thenReturn(AjaxResult.success("作业暂未下发WCS:等待移库")); LocationOutboundGenerateRequest request = new LocationOutboundGenerateRequest(); request.setStockId("KC001"); request.setExitId("OUT01"); String result = service.generateWholeTrayOutboundJob(request); - assertThat(result).contains("CK001").contains("WCS出库作业下发成功"); + assertThat(result).contains("CK001").contains("等待移库"); ArgumentCaptor jobCaptor = ArgumentCaptor.forClass(MydoJob.class); verify(mydoJobService).insertMydoJob(jobCaptor.capture()); @@ -132,7 +143,9 @@ class LocationOutboundServiceImplTest { .containsExactly("SD001", "SD002"); verify(basStockMapper).refreshBasStockSummary("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 @@ -157,7 +170,9 @@ class LocationOutboundServiceImplTest { 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")); + when(relocationJobService.createBlockerRelocations("CK002")).thenReturn(List.of()); + when(dispatchService.dispatch("CK002", DispatchTrigger.INITIAL)) + .thenReturn(AjaxResult.success("WCS出库作业下发成功:OK")); LocationOutboundGenerateRequest request = new LocationOutboundGenerateRequest(); request.setCargoSpaceId("CS-EMPTY"); @@ -181,7 +196,9 @@ class LocationOutboundServiceImplTest { verify(locationOutboundMapper, never()).reserveStockDetailForOutbound(any(HashMap.class)); verify(basStockMapper, never()).refreshBasStockSummary(any()); 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) { diff --git a/ruoyi-admin/src/test/java/com/ruoyi/out/stockout/service/impl/OutStockoutServiceImplTest.java b/ruoyi-admin/src/test/java/com/ruoyi/out/stockout/service/impl/OutStockoutServiceImplTest.java index f1b4f08..b882eb6 100644 --- a/ruoyi-admin/src/test/java/com/ruoyi/out/stockout/service/impl/OutStockoutServiceImplTest.java +++ b/ruoyi-admin/src/test/java/com/ruoyi/out/stockout/service/impl/OutStockoutServiceImplTest.java @@ -4,6 +4,10 @@ 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.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.stockoutdetail.domain.OutStockoutDetail; import com.ruoyi.out.stockoutdetail.mapper.OutStockoutDetailMapper; @@ -46,6 +50,12 @@ class OutStockoutServiceImplTest { @Mock private BasCargoSpaceMapper basCargoSpaceMapper; + @Mock + private IRelocationJobService relocationJobService; + + @Mock + private IOutboundDispatchService dispatchService; + @InjectMocks private OutStockoutServiceImpl service; @@ -102,4 +112,35 @@ class OutStockoutServiceImplTest { .contains("没有可分配库存明细"); 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 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()); + } }