feat: observe outbound allocation without changing legacy
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
package com.ruoyi.out.outStockout.service.impl;
|
package com.ruoyi.out.outStockout.service.impl;
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.ruoyi.algorithm.shadow.OutboundShadowObservation;
|
||||||
|
import com.ruoyi.algorithm.shadow.ShadowObservationGateway;
|
||||||
import com.ruoyi.api.service.IToWcsService;
|
import com.ruoyi.api.service.IToWcsService;
|
||||||
import com.ruoyi.bas.basJobId.service.IBasJobIdService;
|
import com.ruoyi.bas.basJobId.service.IBasJobIdService;
|
||||||
import com.ruoyi.common.annotation.DataScope;
|
import com.ruoyi.common.annotation.DataScope;
|
||||||
@@ -14,6 +16,7 @@ import com.ruoyi.out.outStockout.service.IOutStockoutService;
|
|||||||
import com.ruoyi.out.outStockoutDetail.domain.OutStockoutDetail;
|
import com.ruoyi.out.outStockoutDetail.domain.OutStockoutDetail;
|
||||||
import com.ruoyi.out.outStockoutDetail.mapper.OutStockoutDetailMapper;
|
import com.ruoyi.out.outStockoutDetail.mapper.OutStockoutDetailMapper;
|
||||||
import com.ruoyi.out.outStockoutDetail.service.IOutStockoutDetailService;
|
import com.ruoyi.out.outStockoutDetail.service.IOutStockoutDetailService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -32,6 +35,7 @@ import java.util.List;
|
|||||||
* @date 2025-05-20
|
* @date 2025-05-20
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
public class OutStockoutServiceImpl implements IOutStockoutService
|
public class OutStockoutServiceImpl implements IOutStockoutService
|
||||||
{
|
{
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -45,6 +49,8 @@ public class OutStockoutServiceImpl implements IOutStockoutService
|
|||||||
private IOutStockoutDetailService outStockoutDetailService;
|
private IOutStockoutDetailService outStockoutDetailService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IToWcsService toWcsService;
|
private IToWcsService toWcsService;
|
||||||
|
@Autowired
|
||||||
|
private ShadowObservationGateway shadowObservationGateway;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询出库主单
|
* 查询出库主单
|
||||||
@@ -182,7 +188,10 @@ public class OutStockoutServiceImpl implements IOutStockoutService
|
|||||||
map.put("v_stockout_detail_id", detail.getStockoutDetailId());
|
map.put("v_stockout_detail_id", detail.getStockoutDetailId());
|
||||||
map.put("v_result", null);
|
map.put("v_result", null);
|
||||||
map.put("v_resultmsg", null);
|
map.put("v_resultmsg", null);
|
||||||
|
OutboundShadowObservation shadowObservation =
|
||||||
|
planProjectOutboundShadow(detail.getStockoutDetailId());
|
||||||
outStockoutMapper.autoAllot(map);
|
outStockoutMapper.autoAllot(map);
|
||||||
|
compareOutboundShadow(shadowObservation, map.get("v_result"));
|
||||||
if (!"10".equals(map.get("v_result"))){
|
if (!"10".equals(map.get("v_result"))){
|
||||||
stringBuilder.append(",").append(detail.getStockoutDetailId()+map.get("v_resultmsg"));
|
stringBuilder.append(",").append(detail.getStockoutDetailId()+map.get("v_resultmsg"));
|
||||||
}
|
}
|
||||||
@@ -209,7 +218,10 @@ public class OutStockoutServiceImpl implements IOutStockoutService
|
|||||||
for (String stock : stockList) {
|
for (String stock : stockList) {
|
||||||
map.put("v_stockout_detail_id", stockoutDetailId);
|
map.put("v_stockout_detail_id", stockoutDetailId);
|
||||||
map.put("v_stock_id", stock);
|
map.put("v_stock_id", stock);
|
||||||
|
OutboundShadowObservation shadowObservation =
|
||||||
|
planExactStockOutboundShadow(stockoutDetailId, stock);
|
||||||
outStockoutMapper.manualAllot(map);
|
outStockoutMapper.manualAllot(map);
|
||||||
|
compareOutboundShadow(shadowObservation, map.get("v_result"));
|
||||||
//toWcsService.pushOutMydoJob();
|
//toWcsService.pushOutMydoJob();
|
||||||
if (!"10".equals(map.get("v_result"))){
|
if (!"10".equals(map.get("v_result"))){
|
||||||
stringBuilder.append(",").append(stockoutDetailId+map.get("v_resultString"));
|
stringBuilder.append(",").append(stockoutDetailId+map.get("v_resultString"));
|
||||||
@@ -221,6 +233,35 @@ public class OutStockoutServiceImpl implements IOutStockoutService
|
|||||||
return AjaxResult.success();
|
return AjaxResult.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private OutboundShadowObservation planProjectOutboundShadow(String stockoutDetailId) {
|
||||||
|
try {
|
||||||
|
return shadowObservationGateway.planProjectOutbound(stockoutDetailId);
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
log.warn("Project outbound shadow observation failed open for detail {}: {}",
|
||||||
|
stockoutDetailId, exception.getMessage());
|
||||||
|
return OutboundShadowObservation.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private OutboundShadowObservation planExactStockOutboundShadow(String stockoutDetailId, String stockId) {
|
||||||
|
try {
|
||||||
|
return shadowObservationGateway.planExactStockOutbound(stockoutDetailId, stockId);
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
log.warn("Exact-stock shadow observation failed open for detail {} stock {}: {}",
|
||||||
|
stockoutDetailId, stockId, exception.getMessage());
|
||||||
|
return OutboundShadowObservation.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void compareOutboundShadow(OutboundShadowObservation observation, String legacyResultCode) {
|
||||||
|
try {
|
||||||
|
shadowObservationGateway.compareOutbound(observation, legacyResultCode);
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
log.warn("Outbound shadow comparison failed open for request {}: {}",
|
||||||
|
observation == null ? null : observation.requestId(), exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateOutStockouts(OutStockout mainOut) {
|
public void updateOutStockouts(OutStockout mainOut) {
|
||||||
outStockoutMapper.updateOutStockouts(mainOut);
|
outStockoutMapper.updateOutStockouts(mainOut);
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package com.ruoyi.out.outStockout.service.impl;
|
||||||
|
|
||||||
|
import com.ruoyi.algorithm.shadow.OutboundShadowObservation;
|
||||||
|
import com.ruoyi.algorithm.shadow.ShadowObservationGateway;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.out.outStockout.domain.OutStockout;
|
||||||
|
import com.ruoyi.out.outStockout.mapper.OutStockoutMapper;
|
||||||
|
import com.ruoyi.out.outStockoutDetail.domain.OutStockoutDetail;
|
||||||
|
import com.ruoyi.out.outStockoutDetail.service.IOutStockoutDetailService;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InOrder;
|
||||||
|
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.doAnswer;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class OutStockoutServiceImplShadowTest {
|
||||||
|
@Mock
|
||||||
|
private OutStockoutMapper outStockoutMapper;
|
||||||
|
@Mock
|
||||||
|
private IOutStockoutDetailService outStockoutDetailService;
|
||||||
|
@Mock
|
||||||
|
private ShadowObservationGateway shadowObservationGateway;
|
||||||
|
@InjectMocks
|
||||||
|
private OutStockoutServiceImpl service;
|
||||||
|
|
||||||
|
private OutStockoutDetail pendingDetail;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
pendingDetail = new OutStockoutDetail();
|
||||||
|
pendingDetail.setStockoutDetailId("DETAIL-1");
|
||||||
|
pendingDetail.setQuantity(BigDecimal.TEN);
|
||||||
|
pendingDetail.setAllotQuantity(BigDecimal.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void missingShadowProjectStillRunsAutoAllotAndPreservesLegacyWarning() {
|
||||||
|
prepareAutoAllot();
|
||||||
|
OutboundShadowObservation missingProject = OutboundShadowObservation.unavailable(
|
||||||
|
"W01", "DETAIL-1", "SKIPPED_MISSING_PROJECT", "missing project");
|
||||||
|
when(shadowObservationGateway.planProjectOutbound("DETAIL-1")).thenReturn(missingProject);
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
HashMap<String, String> parameters = invocation.getArgument(0);
|
||||||
|
parameters.put("v_result", "20");
|
||||||
|
parameters.put("v_resultmsg", "LEGACY-WARNING");
|
||||||
|
return null;
|
||||||
|
}).when(outStockoutMapper).autoAllot(any());
|
||||||
|
|
||||||
|
AjaxResult result = service.autoAllot("OUT-1");
|
||||||
|
|
||||||
|
assertThat(result.get(AjaxResult.MSG_TAG)).isEqualTo(",DETAIL-1LEGACY-WARNING");
|
||||||
|
InOrder order = inOrder(shadowObservationGateway, outStockoutMapper);
|
||||||
|
order.verify(shadowObservationGateway).planProjectOutbound("DETAIL-1");
|
||||||
|
order.verify(outStockoutMapper).autoAllot(any());
|
||||||
|
order.verify(shadowObservationGateway).compareOutbound(missingProject, "20");
|
||||||
|
verify(outStockoutMapper, times(1)).autoAllot(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shadowExceptionStillRunsAutoAllotAndPreservesLegacySuccess() {
|
||||||
|
prepareAutoAllot();
|
||||||
|
when(shadowObservationGateway.planProjectOutbound("DETAIL-1"))
|
||||||
|
.thenThrow(new IllegalStateException("shadow unavailable"));
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
HashMap<String, String> parameters = invocation.getArgument(0);
|
||||||
|
parameters.put("v_result", "10");
|
||||||
|
parameters.put("v_resultmsg", "ok");
|
||||||
|
return null;
|
||||||
|
}).when(outStockoutMapper).autoAllot(any());
|
||||||
|
|
||||||
|
AjaxResult result = service.autoAllot("OUT-1");
|
||||||
|
|
||||||
|
assertThat(result.isSuccess()).isTrue();
|
||||||
|
verify(shadowObservationGateway).planProjectOutbound("DETAIL-1");
|
||||||
|
verify(outStockoutMapper, times(1)).autoAllot(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void missingShadowStockStillRunsManualAllotOncePerSelectedStock() {
|
||||||
|
OutboundShadowObservation missingStock = OutboundShadowObservation.unavailable(
|
||||||
|
"W01", "DETAIL-1", "SKIPPED_MISSING_STOCK", "missing stock");
|
||||||
|
when(shadowObservationGateway.planExactStockOutbound("DETAIL-1", "STOCK-1"))
|
||||||
|
.thenReturn(missingStock);
|
||||||
|
when(shadowObservationGateway.planExactStockOutbound("DETAIL-1", "STOCK-2"))
|
||||||
|
.thenReturn(missingStock);
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
HashMap<String, String> parameters = invocation.getArgument(0);
|
||||||
|
parameters.put("v_result", "10");
|
||||||
|
parameters.put("v_resultString", "ok");
|
||||||
|
return null;
|
||||||
|
}).when(outStockoutMapper).manualAllot(any());
|
||||||
|
|
||||||
|
AjaxResult result = service.manualAllot(new String[]{"STOCK-1", "STOCK-2"}, "DETAIL-1");
|
||||||
|
|
||||||
|
assertThat(result.isSuccess()).isTrue();
|
||||||
|
verify(outStockoutMapper, times(2)).manualAllot(any());
|
||||||
|
verify(shadowObservationGateway).planExactStockOutbound("DETAIL-1", "STOCK-1");
|
||||||
|
verify(shadowObservationGateway).planExactStockOutbound("DETAIL-1", "STOCK-2");
|
||||||
|
verify(shadowObservationGateway, times(2)).compareOutbound(missingStock, "10");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareAutoAllot() {
|
||||||
|
OutStockout bill = new OutStockout();
|
||||||
|
bill.setStatus("2");
|
||||||
|
when(outStockoutMapper.selectOutStockoutByStockoutId("OUT-1")).thenReturn(bill);
|
||||||
|
when(outStockoutDetailService.selectOutStockoutDetailList(any()))
|
||||||
|
.thenReturn(List.of(pendingDetail));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user