fix: improve shadow diagnostic matching
This commit is contained in:
@@ -237,6 +237,8 @@ WHERE created_at >= '<测试开始时间>'
|
|||||||
ORDER BY created_at, decision_id;
|
ORDER BY created_at, decision_id;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
出库对比记录的 `reason` 会带出 `detail`、`project`、`product`、`tray`、`stock`、`snapshotSync`、`legacyMatches` 和 `shadowMatches`。其中 `legacyMatches` 是按 Legacy 有效库存条件计算的实时数量,`shadowMatches` 是影子快照中按项目/产品条件计算的数量;两者不一致时先检查快照同步时间,再判断业务映射。
|
||||||
|
|
||||||
交付分析的数据包至少包括:
|
交付分析的数据包至少包括:
|
||||||
|
|
||||||
- 决策日志导出文件,保持原始列名和时间字段;
|
- 决策日志导出文件,保持原始列名和时间字段;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.ruoyi.algorithm.shadow;
|
|||||||
|
|
||||||
public class ShadowInboundReference {
|
public class ShadowInboundReference {
|
||||||
private String requestId;
|
private String requestId;
|
||||||
|
private String warehouseId;
|
||||||
private String trayNo;
|
private String trayNo;
|
||||||
private String projectNo;
|
private String projectNo;
|
||||||
private String productId;
|
private String productId;
|
||||||
@@ -15,6 +16,14 @@ public class ShadowInboundReference {
|
|||||||
this.requestId = requestId;
|
this.requestId = requestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getWarehouseId() {
|
||||||
|
return warehouseId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWarehouseId(String warehouseId) {
|
||||||
|
this.warehouseId = warehouseId;
|
||||||
|
}
|
||||||
|
|
||||||
public String getTrayNo() {
|
public String getTrayNo() {
|
||||||
return trayNo;
|
return trayNo;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,28 +41,30 @@ public class ShadowPlanningEngine {
|
|||||||
public OutboundShadowObservation planProjectOutbound(String stockoutDetailId) {
|
public OutboundShadowObservation planProjectOutbound(String stockoutDetailId) {
|
||||||
return transactions.execute(status -> {
|
return transactions.execute(status -> {
|
||||||
ShadowStockReference reference = inputMapper.selectProjectOutboundReference(stockoutDetailId);
|
ShadowStockReference reference = inputMapper.selectProjectOutboundReference(stockoutDetailId);
|
||||||
String warehouseId = warehouseId();
|
String warehouseId = warehouseId(reference == null ? null : reference.getWarehouseId());
|
||||||
if (reference == null || isBlank(reference.getProjectNo())) {
|
if (reference == null || isBlank(reference.getProjectNo())) {
|
||||||
return OutboundShadowObservation.unavailable(warehouseId, stockoutDetailId,
|
return OutboundShadowObservation.unavailable(warehouseId, stockoutDetailId,
|
||||||
"SKIPPED_MISSING_PROJECT", "No project number was available for shadow planning");
|
"SKIPPED_MISSING_PROJECT", "No project number was available for shadow planning");
|
||||||
}
|
}
|
||||||
OutboundRequest request = new OutboundRequest(stockoutDetailId, null,
|
OutboundRequest request = new OutboundRequest(stockoutDetailId, null,
|
||||||
reference.getProjectNo(), reference.getProductId(), null, null, null, true);
|
reference.getProjectNo(), reference.getProductId(), null, null, null, true);
|
||||||
return toOutboundObservation(warehouseId, reference, schedulingFacade.planOutbound(warehouseId, request));
|
return toOutboundObservation(warehouseId, reference.getRequestId(), reference,
|
||||||
|
schedulingFacade.planOutbound(warehouseId, request));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public OutboundShadowObservation planExactStockOutbound(String stockoutDetailId, String stockId) {
|
public OutboundShadowObservation planExactStockOutbound(String stockoutDetailId, String stockId) {
|
||||||
return transactions.execute(status -> {
|
return transactions.execute(status -> {
|
||||||
ShadowStockReference reference = inputMapper.selectStockReference(stockId);
|
ShadowStockReference reference = inputMapper.selectStockReference(stockId);
|
||||||
String warehouseId = warehouseId();
|
String warehouseId = warehouseId(reference == null ? null : reference.getWarehouseId());
|
||||||
if (reference == null || isBlank(reference.getTrayNo())) {
|
if (reference == null || isBlank(reference.getTrayNo())) {
|
||||||
return OutboundShadowObservation.unavailable(warehouseId, stockoutDetailId,
|
return OutboundShadowObservation.unavailable(warehouseId, stockoutDetailId,
|
||||||
"SKIPPED_MISSING_STOCK", "Selected stock was unavailable to shadow planning");
|
"SKIPPED_MISSING_STOCK", "Selected stock was unavailable to shadow planning");
|
||||||
}
|
}
|
||||||
OutboundRequest request = new OutboundRequest(stockoutDetailId, reference.getTrayNo(),
|
OutboundRequest request = new OutboundRequest(stockoutDetailId, reference.getTrayNo(),
|
||||||
reference.getProjectNo(), reference.getProductId(), null, null, null, true);
|
reference.getProjectNo(), reference.getProductId(), null, null, null, true);
|
||||||
return toOutboundObservation(warehouseId, reference, schedulingFacade.planOutbound(warehouseId, request));
|
return toOutboundObservation(warehouseId, stockoutDetailId, reference,
|
||||||
|
schedulingFacade.planOutbound(warehouseId, request));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +75,7 @@ public class ShadowPlanningEngine {
|
|||||||
comparisonCode(observation.proposedSpaceId(), legacySpaceId),
|
comparisonCode(observation.proposedSpaceId(), legacySpaceId),
|
||||||
"request=" + observation.requestId() + "; shadow=" + observation.resultCode()
|
"request=" + observation.requestId() + "; shadow=" + observation.resultCode()
|
||||||
+ "; proposedSpace=" + nullSafe(observation.proposedSpaceId())
|
+ "; proposedSpace=" + nullSafe(observation.proposedSpaceId())
|
||||||
|
+ "; shadowReason=" + nullSafe(observation.reason())
|
||||||
+ "; legacy=" + nullSafe(legacyResultCode)
|
+ "; legacy=" + nullSafe(legacyResultCode)
|
||||||
+ "; legacySpace=" + nullSafe(legacySpaceId)));
|
+ "; legacySpace=" + nullSafe(legacySpaceId)));
|
||||||
}
|
}
|
||||||
@@ -84,12 +87,13 @@ public class ShadowPlanningEngine {
|
|||||||
"request=" + observation.requestId() + "; shadow=" + observation.resultCode()
|
"request=" + observation.requestId() + "; shadow=" + observation.resultCode()
|
||||||
+ "; proposedStock=" + nullSafe(observation.proposedStockId())
|
+ "; proposedStock=" + nullSafe(observation.proposedStockId())
|
||||||
+ "; proposedSpace=" + nullSafe(observation.proposedSpaceId())
|
+ "; proposedSpace=" + nullSafe(observation.proposedSpaceId())
|
||||||
|
+ "; shadowReason=" + nullSafe(observation.reason())
|
||||||
+ "; legacy=" + nullSafe(legacyResultCode)));
|
+ "; legacy=" + nullSafe(legacyResultCode)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private InboundShadowObservation planInboundInsideTransaction(String taskNo, String trayNo) {
|
private InboundShadowObservation planInboundInsideTransaction(String taskNo, String trayNo) {
|
||||||
String warehouseId = warehouseId();
|
|
||||||
ShadowInboundReference reference = inputMapper.selectInboundInput(taskNo, trayNo);
|
ShadowInboundReference reference = inputMapper.selectInboundInput(taskNo, trayNo);
|
||||||
|
String warehouseId = warehouseId(reference == null ? null : reference.getWarehouseId());
|
||||||
if (reference == null || isBlank(reference.getRequestId()) || isBlank(reference.getTrayNo())) {
|
if (reference == null || isBlank(reference.getRequestId()) || isBlank(reference.getTrayNo())) {
|
||||||
return InboundShadowObservation.unavailable(warehouseId, taskNo,
|
return InboundShadowObservation.unavailable(warehouseId, taskNo,
|
||||||
"SKIPPED_MISSING_INPUT", "Inbound task details were unavailable to shadow planning");
|
"SKIPPED_MISSING_INPUT", "Inbound task details were unavailable to shadow planning");
|
||||||
@@ -102,38 +106,90 @@ public class ShadowPlanningEngine {
|
|||||||
if (plan.assignment().isPresent()) {
|
if (plan.assignment().isPresent()) {
|
||||||
var assignment = plan.assignment().orElseThrow();
|
var assignment = plan.assignment().orElseThrow();
|
||||||
return new InboundShadowObservation(true, warehouseId, reference.getRequestId(),
|
return new InboundShadowObservation(true, warehouseId, reference.getRequestId(),
|
||||||
"READY", assignment.spaceId(), assignment.reason());
|
"READY", assignment.spaceId(), inboundDiagnostic(taskNo, trayNo, reference, assignment.reason()));
|
||||||
}
|
}
|
||||||
var rejection = plan.rejection().orElseThrow();
|
var rejection = plan.rejection().orElseThrow();
|
||||||
return InboundShadowObservation.unavailable(warehouseId, reference.getRequestId(),
|
return InboundShadowObservation.unavailable(warehouseId, reference.getRequestId(),
|
||||||
"REJECTED_" + rejection.code().name(), rejection.reason());
|
"REJECTED_" + rejection.code().name(),
|
||||||
|
inboundDiagnostic(taskNo, trayNo, reference, rejection.reason()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private OutboundShadowObservation toOutboundObservation(String warehouseId,
|
private OutboundShadowObservation toOutboundObservation(String warehouseId, String requestId,
|
||||||
ShadowStockReference reference, OutboundPlan plan) {
|
ShadowStockReference reference, OutboundPlan plan) {
|
||||||
String requestId = reference.getRequestId();
|
|
||||||
if (isBlank(requestId)) {
|
if (isBlank(requestId)) {
|
||||||
requestId = plan.assignment().map(assignment -> assignment.requestId()).orElse(null);
|
requestId = plan.assignment().map(assignment -> assignment.requestId()).orElse(null);
|
||||||
}
|
}
|
||||||
if (plan.assignment().isPresent()) {
|
if (plan.assignment().isPresent()) {
|
||||||
var assignment = plan.assignment().orElseThrow();
|
var assignment = plan.assignment().orElseThrow();
|
||||||
return new OutboundShadowObservation(true, warehouseId, requestId, "READY",
|
return new OutboundShadowObservation(true, warehouseId, requestId, "READY",
|
||||||
reference.getStockId(), assignment.sourceSpaceId(), assignment.reason());
|
reference.getStockId(), assignment.sourceSpaceId(),
|
||||||
|
outboundDiagnostic(requestId, reference, assignment.reason()));
|
||||||
}
|
}
|
||||||
if (plan.waiting().isPresent()) {
|
if (plan.waiting().isPresent()) {
|
||||||
var waiting = plan.waiting().orElseThrow();
|
var waiting = plan.waiting().orElseThrow();
|
||||||
return OutboundShadowObservation.unavailable(warehouseId, requestId,
|
return OutboundShadowObservation.unavailable(warehouseId, requestId,
|
||||||
"WAITING_" + waiting.code().name(), waiting.reason());
|
"WAITING_" + waiting.code().name(), outboundDiagnostic(requestId, reference, waiting.reason()));
|
||||||
}
|
}
|
||||||
var error = plan.error().orElseThrow();
|
var error = plan.error().orElseThrow();
|
||||||
return OutboundShadowObservation.unavailable(warehouseId, requestId,
|
return OutboundShadowObservation.unavailable(warehouseId, requestId,
|
||||||
"ERROR_" + error.code().name(), error.message());
|
"ERROR_" + error.code().name(), outboundDiagnostic(requestId, reference, error.message()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String warehouseId() {
|
private String warehouseId(String preferredWarehouseId) {
|
||||||
return isBlank(properties.getWarehouseId())
|
String configuredWarehouseId = isBlank(properties.getWarehouseId())
|
||||||
? schedulingFacade.defaultWarehouseId()
|
? schedulingFacade.defaultWarehouseId()
|
||||||
: properties.getWarehouseId();
|
: properties.getWarehouseId();
|
||||||
|
if (!isBlank(preferredWarehouseId)) {
|
||||||
|
String normalizedPreferred = preferredWarehouseId.trim();
|
||||||
|
if (sameNumericWarehouse(normalizedPreferred, configuredWarehouseId)) {
|
||||||
|
return configuredWarehouseId;
|
||||||
|
}
|
||||||
|
return normalizedPreferred;
|
||||||
|
}
|
||||||
|
return configuredWarehouseId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean sameNumericWarehouse(String first, String second) {
|
||||||
|
if (isBlank(first) || isBlank(second)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(first) == Integer.parseInt(second);
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String outboundDiagnostic(String requestId, ShadowStockReference reference, String resultReason) {
|
||||||
|
return "detail=" + nullSafe(requestId)
|
||||||
|
+ "; project=" + nullSafe(reference.getProjectNo())
|
||||||
|
+ "; product=" + nullSafe(reference.getProductId())
|
||||||
|
+ "; tray=" + nullSafe(reference.getTrayNo())
|
||||||
|
+ "; stock=" + nullSafe(reference.getStockId())
|
||||||
|
+ "; snapshotSync=" + nullSafe(reference.getSnapshotSynchronizedAt())
|
||||||
|
+ "; legacyMatches=" + numberOrUnknown(reference.getLiveMatchCount())
|
||||||
|
+ "; shadowMatches=" + numberOrUnknown(reference.getShadowMatchCount())
|
||||||
|
+ "; reason=" + abbreviate(nullSafe(resultReason), 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String inboundDiagnostic(String taskNo, String trayNo, ShadowInboundReference reference,
|
||||||
|
String resultReason) {
|
||||||
|
return "task=" + nullSafe(taskNo)
|
||||||
|
+ "; tray=" + nullSafe(trayNo)
|
||||||
|
+ "; request=" + nullSafe(reference.getRequestId())
|
||||||
|
+ "; warehouse=" + nullSafe(reference.getWarehouseId())
|
||||||
|
+ "; project=" + nullSafe(reference.getProjectNo())
|
||||||
|
+ "; product=" + nullSafe(reference.getProductId())
|
||||||
|
+ "; lot=" + nullSafe(reference.getLotNo())
|
||||||
|
+ "; reason=" + abbreviate(nullSafe(resultReason), 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String numberOrUnknown(Integer value) {
|
||||||
|
return value == null ? "" : value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String abbreviate(String value, int maxLength) {
|
||||||
|
return value.length() <= maxLength ? value : value.substring(0, maxLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String comparisonCode(String shadowSpaceId, String legacySpaceId) {
|
private String comparisonCode(String shadowSpaceId, String legacySpaceId) {
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ public class ShadowStockReference {
|
|||||||
private String warehouseId;
|
private String warehouseId;
|
||||||
private String projectNo;
|
private String projectNo;
|
||||||
private String productId;
|
private String productId;
|
||||||
|
private String snapshotSynchronizedAt;
|
||||||
|
private Integer liveMatchCount;
|
||||||
|
private Integer shadowMatchCount;
|
||||||
|
|
||||||
public String getRequestId() {
|
public String getRequestId() {
|
||||||
return requestId;
|
return requestId;
|
||||||
@@ -64,4 +67,28 @@ public class ShadowStockReference {
|
|||||||
public void setProductId(String productId) {
|
public void setProductId(String productId) {
|
||||||
this.productId = productId;
|
this.productId = productId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSnapshotSynchronizedAt() {
|
||||||
|
return snapshotSynchronizedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSnapshotSynchronizedAt(String snapshotSynchronizedAt) {
|
||||||
|
this.snapshotSynchronizedAt = snapshotSynchronizedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLiveMatchCount() {
|
||||||
|
return liveMatchCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLiveMatchCount(Integer liveMatchCount) {
|
||||||
|
this.liveMatchCount = liveMatchCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getShadowMatchCount() {
|
||||||
|
return shadowMatchCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShadowMatchCount(Integer shadowMatchCount) {
|
||||||
|
this.shadowMatchCount = shadowMatchCount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<select id="selectInboundInput" resultType="com.ruoyi.algorithm.shadow.ShadowInboundReference">
|
<select id="selectInboundInput" resultType="com.ruoyi.algorithm.shadow.ShadowInboundReference">
|
||||||
select top (1)
|
select top (1)
|
||||||
job.job_id as requestId,
|
job.job_id as requestId,
|
||||||
|
job.warehouse_id as warehouseId,
|
||||||
job.tray_code as trayNo,
|
job.tray_code as trayNo,
|
||||||
coalesce(nullif(ltrim(rtrim(detail.spare4)), ''),
|
coalesce(nullif(ltrim(rtrim(detail.spare4)), ''),
|
||||||
nullif(ltrim(rtrim(stock.spare4)), '')) as projectNo,
|
nullif(ltrim(rtrim(stock.spare4)), '')) as projectNo,
|
||||||
@@ -25,7 +26,26 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
select detail.stockout_detail_id as requestId,
|
select detail.stockout_detail_id as requestId,
|
||||||
detail.warehouse_id as warehouseId,
|
detail.warehouse_id as warehouseId,
|
||||||
nullif(ltrim(rtrim(detail.spare4)), '') as projectNo,
|
nullif(ltrim(rtrim(detail.spare4)), '') as projectNo,
|
||||||
detail.product_id as productId
|
detail.product_id as productId,
|
||||||
|
convert(varchar(40), (select max(snapshot.synchronized_at)
|
||||||
|
from alg.inventory_item_snapshot snapshot
|
||||||
|
where snapshot.warehouse_id = detail.warehouse_id), 126)
|
||||||
|
as snapshotSynchronizedAt,
|
||||||
|
(select count(*)
|
||||||
|
from bas_stock live
|
||||||
|
where live.warehouse_id = detail.warehouse_id
|
||||||
|
and live.stock_status = '1'
|
||||||
|
and live.product_status = '1'
|
||||||
|
and live.quantity > live.out_quantity
|
||||||
|
and (NULLIF(LTRIM(RTRIM(detail.product_id)), '') IS NULL
|
||||||
|
OR live.product_id = NULLIF(LTRIM(RTRIM(detail.product_id)), ''))) as liveMatchCount,
|
||||||
|
(select count(*)
|
||||||
|
from alg.inventory_item_snapshot snapshot
|
||||||
|
where snapshot.warehouse_id = detail.warehouse_id
|
||||||
|
and snapshot.project_no = nullif(ltrim(rtrim(detail.spare4)), '')
|
||||||
|
and (NULLIF(LTRIM(RTRIM(detail.product_id)), '') IS NULL
|
||||||
|
OR snapshot.product_no = NULLIF(LTRIM(RTRIM(detail.product_id)), '')))
|
||||||
|
as shadowMatchCount
|
||||||
from out_stockout_detail detail
|
from out_stockout_detail detail
|
||||||
where detail.stockout_detail_id = #{stockoutDetailId}
|
where detail.stockout_detail_id = #{stockoutDetailId}
|
||||||
</select>
|
</select>
|
||||||
@@ -37,7 +57,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
stock.cargo_space_id as spaceId,
|
stock.cargo_space_id as spaceId,
|
||||||
stock.warehouse_id as warehouseId,
|
stock.warehouse_id as warehouseId,
|
||||||
nullif(ltrim(rtrim(stock.spare4)), '') as projectNo,
|
nullif(ltrim(rtrim(stock.spare4)), '') as projectNo,
|
||||||
stock.product_id as productId
|
stock.product_id as productId,
|
||||||
|
convert(varchar(40), (select max(snapshot.synchronized_at)
|
||||||
|
from alg.inventory_item_snapshot snapshot
|
||||||
|
where snapshot.warehouse_id = stock.warehouse_id), 126)
|
||||||
|
as snapshotSynchronizedAt,
|
||||||
|
(select count(*)
|
||||||
|
from bas_stock live
|
||||||
|
where live.stock_id = stock.stock_id
|
||||||
|
and live.stock_status = '1'
|
||||||
|
and live.product_status = '1'
|
||||||
|
and live.quantity > live.out_quantity) as liveMatchCount,
|
||||||
|
(select count(*)
|
||||||
|
from alg.inventory_item_snapshot snapshot
|
||||||
|
where snapshot.stock_id = stock.stock_id
|
||||||
|
and snapshot.warehouse_id = stock.warehouse_id) as shadowMatchCount
|
||||||
from bas_stock stock
|
from bas_stock stock
|
||||||
where stock.stock_id = #{stockId}
|
where stock.stock_id = #{stockId}
|
||||||
and stock.stock_status = '1'
|
and stock.stock_status = '1'
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import com.ruoyi.algorithm.inbound.InboundBorrowType;
|
|||||||
import com.ruoyi.algorithm.inbound.InboundLadderRung;
|
import com.ruoyi.algorithm.inbound.InboundLadderRung;
|
||||||
import com.ruoyi.algorithm.inbound.InboundPlan;
|
import com.ruoyi.algorithm.inbound.InboundPlan;
|
||||||
import com.ruoyi.algorithm.integration.SchedulingFacade;
|
import com.ruoyi.algorithm.integration.SchedulingFacade;
|
||||||
|
import com.ruoyi.algorithm.outbound.OutboundAssignment;
|
||||||
|
import com.ruoyi.algorithm.outbound.OutboundPlan;
|
||||||
|
import com.ruoyi.algorithm.outbound.FifoDecision;
|
||||||
import com.ruoyi.algorithm.repository.VersionToken;
|
import com.ruoyi.algorithm.repository.VersionToken;
|
||||||
import com.ruoyi.algorithm.service.InboundPlanningResult;
|
import com.ruoyi.algorithm.service.InboundPlanningResult;
|
||||||
import com.ruoyi.algorithm.shadow.mapper.ShadowInputMapper;
|
import com.ruoyi.algorithm.shadow.mapper.ShadowInputMapper;
|
||||||
@@ -16,8 +19,10 @@ import org.springframework.transaction.support.SimpleTransactionStatus;
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
class ShadowPlanningEngineTest {
|
class ShadowPlanningEngineTest {
|
||||||
@@ -25,13 +30,14 @@ class ShadowPlanningEngineTest {
|
|||||||
@Test
|
@Test
|
||||||
void inboundPlanningUsesRequiresNewAndConfiguredTimeout() {
|
void inboundPlanningUsesRequiresNewAndConfiguredTimeout() {
|
||||||
ShadowObservationProperties properties = new ShadowObservationProperties();
|
ShadowObservationProperties properties = new ShadowObservationProperties();
|
||||||
properties.setWarehouseId("W01");
|
properties.setWarehouseId("FALLBACK");
|
||||||
properties.setTimeoutSeconds(7);
|
properties.setTimeoutSeconds(7);
|
||||||
CapturingTransactionManager transactions = new CapturingTransactionManager();
|
CapturingTransactionManager transactions = new CapturingTransactionManager();
|
||||||
ShadowInputMapper mapper = mock(ShadowInputMapper.class);
|
ShadowInputMapper mapper = mock(ShadowInputMapper.class);
|
||||||
SchedulingFacade facade = mock(SchedulingFacade.class);
|
SchedulingFacade facade = mock(SchedulingFacade.class);
|
||||||
ShadowInboundReference input = new ShadowInboundReference();
|
ShadowInboundReference input = new ShadowInboundReference();
|
||||||
input.setRequestId("JOB-1");
|
input.setRequestId("JOB-1");
|
||||||
|
input.setWarehouseId("W01");
|
||||||
input.setTrayNo("TRAY-1");
|
input.setTrayNo("TRAY-1");
|
||||||
input.setProjectNo("PROJECT-1");
|
input.setProjectNo("PROJECT-1");
|
||||||
input.setProductId("PRODUCT-1");
|
input.setProductId("PRODUCT-1");
|
||||||
@@ -56,6 +62,69 @@ class ShadowPlanningEngineTest {
|
|||||||
assertThat(observation.proposedSpaceId()).isEqualTo("SPACE-9");
|
assertThat(observation.proposedSpaceId()).isEqualTo("SPACE-9");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void outboundPlanningUsesReferenceWarehouseAndKeepsMatchDiagnostics() {
|
||||||
|
ShadowObservationProperties properties = new ShadowObservationProperties();
|
||||||
|
properties.setWarehouseId("FALLBACK");
|
||||||
|
CapturingTransactionManager transactions = new CapturingTransactionManager();
|
||||||
|
ShadowInputMapper mapper = mock(ShadowInputMapper.class);
|
||||||
|
SchedulingFacade facade = mock(SchedulingFacade.class);
|
||||||
|
ShadowStockReference reference = new ShadowStockReference();
|
||||||
|
reference.setRequestId("DETAIL-1");
|
||||||
|
reference.setWarehouseId("W02");
|
||||||
|
reference.setProjectNo("PROJECT-1");
|
||||||
|
reference.setProductId("PRODUCT-1");
|
||||||
|
reference.setSnapshotSynchronizedAt("2026-08-28T02:53:45.133Z");
|
||||||
|
reference.setLiveMatchCount(2);
|
||||||
|
reference.setShadowMatchCount(1);
|
||||||
|
when(mapper.selectProjectOutboundReference("DETAIL-1")).thenReturn(reference);
|
||||||
|
OutboundAssignment assignment = new OutboundAssignment(
|
||||||
|
"DETAIL-1", "TRAY-1", "PROJECT-1", "PRODUCT-1", "SPACE-9", 1,
|
||||||
|
true, java.util.List.of(), java.util.List.of(), false,
|
||||||
|
1.0, 2.0, 3.0, FifoDecision.off(), java.util.Optional.empty(),
|
||||||
|
"shadow proposal");
|
||||||
|
when(facade.planOutbound(eq("W02"), any()))
|
||||||
|
.thenReturn(OutboundPlan.ready(assignment, com.ruoyi.algorithm.outbound.TaskCountEstimate.zero()));
|
||||||
|
ShadowPlanningEngine engine = new ShadowPlanningEngine(facade, mapper, properties, transactions);
|
||||||
|
|
||||||
|
OutboundShadowObservation observation = engine.planProjectOutbound("DETAIL-1");
|
||||||
|
engine.compareOutbound(observation, "10");
|
||||||
|
|
||||||
|
assertThat(observation.warehouseId()).isEqualTo("W02");
|
||||||
|
assertThat(observation.requestId()).isEqualTo("DETAIL-1");
|
||||||
|
assertThat(observation.proposedSpaceId()).isEqualTo("SPACE-9");
|
||||||
|
verify(mapper).insertComparison(any(), eq("W02"), eq("OUTBOUND_COMPARE"), eq("LEGACY_10"),
|
||||||
|
argThat(reason -> reason.contains("detail=DETAIL-1")
|
||||||
|
&& reason.contains("project=PROJECT-1")
|
||||||
|
&& reason.contains("product=PRODUCT-1")
|
||||||
|
&& reason.contains("snapshotSync=2026-08-28T02:53:45.133Z")
|
||||||
|
&& reason.contains("legacyMatches=2")
|
||||||
|
&& reason.contains("shadowMatches=1")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void outboundPlanningTreatsPaddedAndUnpaddedWarehouseIdsAsEquivalent() {
|
||||||
|
ShadowObservationProperties properties = new ShadowObservationProperties();
|
||||||
|
properties.setWarehouseId("01");
|
||||||
|
CapturingTransactionManager transactions = new CapturingTransactionManager();
|
||||||
|
ShadowInputMapper mapper = mock(ShadowInputMapper.class);
|
||||||
|
SchedulingFacade facade = mock(SchedulingFacade.class);
|
||||||
|
ShadowStockReference reference = new ShadowStockReference();
|
||||||
|
reference.setRequestId("DETAIL-2");
|
||||||
|
reference.setWarehouseId("1");
|
||||||
|
reference.setProjectNo("PROJECT-2");
|
||||||
|
when(mapper.selectProjectOutboundReference("DETAIL-2")).thenReturn(reference);
|
||||||
|
when(facade.planOutbound(eq("01"), any())).thenReturn(
|
||||||
|
OutboundPlan.error(new com.ruoyi.algorithm.outbound.OutboundError(
|
||||||
|
com.ruoyi.algorithm.outbound.OutboundErrorCode.STOCK_NOT_FOUND,
|
||||||
|
"no stock", java.util.Map.of())));
|
||||||
|
ShadowPlanningEngine engine = new ShadowPlanningEngine(facade, mapper, properties, transactions);
|
||||||
|
|
||||||
|
OutboundShadowObservation observation = engine.planProjectOutbound("DETAIL-2");
|
||||||
|
|
||||||
|
assertThat(observation.warehouseId()).isEqualTo("01");
|
||||||
|
}
|
||||||
|
|
||||||
private static final class CapturingTransactionManager implements PlatformTransactionManager {
|
private static final class CapturingTransactionManager implements PlatformTransactionManager {
|
||||||
private TransactionDefinition definition;
|
private TransactionDefinition definition;
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ class OutStockoutServiceImplShadowTest {
|
|||||||
private void prepareAutoAllot() {
|
private void prepareAutoAllot() {
|
||||||
OutStockout bill = new OutStockout();
|
OutStockout bill = new OutStockout();
|
||||||
bill.setStatus("2");
|
bill.setStatus("2");
|
||||||
|
bill.setWarehouseId("1");
|
||||||
when(outStockoutMapper.selectOutStockoutByStockoutId("OUT-1")).thenReturn(bill);
|
when(outStockoutMapper.selectOutStockoutByStockoutId("OUT-1")).thenReturn(bill);
|
||||||
when(outStockoutDetailService.selectOutStockoutDetailList(any()))
|
when(outStockoutDetailService.selectOutStockoutDetailList(any()))
|
||||||
.thenReturn(List.of(pendingDetail));
|
.thenReturn(List.of(pendingDetail));
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ async function main() {
|
|||||||
["package_manifest", "SELECT package_id, package_version, installed_at FROM alg.shadow_package_manifest;"],
|
["package_manifest", "SELECT package_id, package_version, installed_at FROM alg.shadow_package_manifest;"],
|
||||||
["decision_summary", `SELECT decision_type, result_code, COUNT(*) AS row_count, MIN(created_at) AS first_created_at, MAX(created_at) AS last_created_at FROM alg.decision_log WHERE created_at >= CAST('${detailStart}' AS datetime2) AND created_at < CAST('${detailEnd}' AS datetime2) GROUP BY decision_type, result_code ORDER BY decision_type, result_code`],
|
["decision_summary", `SELECT decision_type, result_code, COUNT(*) AS row_count, MIN(created_at) AS first_created_at, MAX(created_at) AS last_created_at FROM alg.decision_log WHERE created_at >= CAST('${detailStart}' AS datetime2) AND created_at < CAST('${detailEnd}' AS datetime2) GROUP BY decision_type, result_code ORDER BY decision_type, result_code`],
|
||||||
["decision_log", `SELECT decision_id, warehouse_id, decision_type, input_version, result_code, reason, created_at FROM alg.decision_log WHERE created_at >= CAST('${detailStart}' AS datetime2) AND created_at < CAST('${detailEnd}' AS datetime2) ORDER BY created_at, decision_id`],
|
["decision_log", `SELECT decision_id, warehouse_id, decision_type, input_version, result_code, reason, created_at FROM alg.decision_log WHERE created_at >= CAST('${detailStart}' AS datetime2) AND created_at < CAST('${detailEnd}' AS datetime2) ORDER BY created_at, decision_id`],
|
||||||
["outbound_jobs", `SELECT TOP (300) d.created_at AS decision_created_at, d.result_code AS shadow_compare_result, d.reason, j.job_id, j.task_no, j.tray_code AS job_tray_code, j.cargo_space_id AS job_source_space_id, j.target_cargo_space_id, j.warehouse_id, j.status AS job_status, j.in_or_out, j.create_time, j.finish_time FROM alg.decision_log d LEFT JOIN dbo.mydo_job j ON d.reason LIKE '%request=' + CONVERT(varchar(255), j.task_no) + '%' AND j.warehouse_id = '01' WHERE d.decision_type = 'OUTBOUND_COMPARE' AND d.created_at >= CAST('${detailStart}' AS datetime2) AND d.created_at < CAST('${detailEnd}' AS datetime2) ORDER BY d.created_at, j.create_time, j.task_no`],
|
["outbound_jobs", `SELECT TOP (300) d.created_at AS decision_created_at, d.result_code AS shadow_compare_result, d.reason, detail.stockout_detail_id, detail.stockout_id, detail.warehouse_id, detail.spare4 AS project_no, detail.product_id, detail.quantity, detail.allot_quantity, detail.assign_num, detail.create_time AS detail_create_time FROM alg.decision_log d OUTER APPLY (SELECT LTRIM(RTRIM(SUBSTRING(d.reason, CHARINDEX('request=', d.reason) + 8, CHARINDEX(CHAR(59), d.reason + CHAR(59), CHARINDEX('request=', d.reason)) - (CHARINDEX('request=', d.reason) + 8)))) AS request_id) r LEFT JOIN dbo.out_stockout_detail detail ON detail.stockout_detail_id = r.request_id AND TRY_CONVERT(int, detail.warehouse_id) = TRY_CONVERT(int, '01') WHERE d.decision_type = 'OUTBOUND_COMPARE' AND d.created_at >= CAST('${detailStart}' AS datetime2) AND d.created_at < CAST('${detailEnd}' AS datetime2) ORDER BY d.created_at, detail.create_time, detail.stockout_detail_id`],
|
||||||
["inventory_mapping", "SELECT TOP (1000) c.warehouse_id, s.stock_id, s.cargo_space_id, s.tray_code, s.spare4 AS legacy_project_no, s.product_id AS legacy_product_no, s.lot_no, s.in_date, s.quantity, s.out_quantity, i.space_id AS shadow_space_id, i.tray_code AS shadow_tray_code, i.project_no AS shadow_project_no, i.product_no AS shadow_product_no, i.inbound_time_raw, i.inbound_at, i.quantity AS shadow_quantity, i.out_quantity AS shadow_out_quantity FROM dbo.bas_stock s INNER JOIN dbo.bas_cargo_space c ON c.cargo_space_id = s.cargo_space_id AND c.warehouse_id = '01' LEFT JOIN alg.v_inventory_item i ON i.stock_id = s.stock_id ORDER BY s.stock_id;"],
|
["inventory_mapping", "SELECT TOP (1000) c.warehouse_id, s.stock_id, s.cargo_space_id, s.tray_code, s.spare4 AS legacy_project_no, s.product_id AS legacy_product_no, s.lot_no, s.in_date, s.quantity, s.out_quantity, i.space_id AS shadow_space_id, i.tray_code AS shadow_tray_code, i.project_no AS shadow_project_no, i.product_no AS shadow_product_no, i.inbound_time_raw, i.inbound_at, i.quantity AS shadow_quantity, i.out_quantity AS shadow_out_quantity FROM dbo.bas_stock s INNER JOIN dbo.bas_cargo_space c ON c.cargo_space_id = s.cargo_space_id AND c.warehouse_id = '01' LEFT JOIN alg.v_inventory_item i ON i.stock_id = s.stock_id ORDER BY s.stock_id;"],
|
||||||
["jobs", "SELECT TOP (1000) job_id, task_no, tray_code, cargo_space_id, target_cargo_space_id, warehouse_id, status, in_or_out, create_time, finish_time FROM dbo.mydo_job WHERE warehouse_id = '01' ORDER BY create_time, job_id;"],
|
["jobs", "SELECT TOP (1000) job_id, task_no, tray_code, cargo_space_id, target_cargo_space_id, warehouse_id, status, in_or_out, create_time, finish_time FROM dbo.mydo_job WHERE warehouse_id = '01' ORDER BY create_time, job_id;"],
|
||||||
["lane_state", "SELECT warehouse_id, COUNT(*) AS lane_state_count, SUM(CASE WHEN pending_outbound = 1 THEN 1 ELSE 0 END) AS pending_lane_count, SUM(CASE WHEN has_gap = 1 THEN 1 ELSE 0 END) AS gap_lane_count, MAX(rebuilt_at) AS latest_rebuild_at FROM alg.lane_state WHERE warehouse_id = '01' GROUP BY warehouse_id;"],
|
["lane_state", "SELECT warehouse_id, COUNT(*) AS lane_state_count, SUM(CASE WHEN pending_outbound = 1 THEN 1 ELSE 0 END) AS pending_lane_count, SUM(CASE WHEN has_gap = 1 THEN 1 ELSE 0 END) AS gap_lane_count, MAX(rebuilt_at) AS latest_rebuild_at FROM alg.lane_state WHERE warehouse_id = '01' GROUP BY warehouse_id;"],
|
||||||
|
|||||||
@@ -89,8 +89,8 @@ async function main() {
|
|||||||
["shadow_missing_from_legacy", `SELECT TOP (1000) i.warehouse_id, i.stock_id, i.space_id, i.tray_code, i.project_no, i.product_no, i.lot_no, i.inbound_time_raw, i.quantity, i.out_quantity FROM alg.inventory_item_snapshot i LEFT JOIN dbo.bas_stock s ON s.stock_id = i.stock_id WHERE i.warehouse_id = '${warehouse}' AND s.stock_id IS NULL ORDER BY i.stock_id`],
|
["shadow_missing_from_legacy", `SELECT TOP (1000) i.warehouse_id, i.stock_id, i.space_id, i.tray_code, i.project_no, i.product_no, i.lot_no, i.inbound_time_raw, i.quantity, i.out_quantity FROM alg.inventory_item_snapshot i LEFT JOIN dbo.bas_stock s ON s.stock_id = i.stock_id WHERE i.warehouse_id = '${warehouse}' AND s.stock_id IS NULL ORDER BY i.stock_id`],
|
||||||
["matched_field_difference_counts", `SELECT COUNT_BIG(*) AS matched_stock_count, SUM(CASE WHEN ISNULL(LTRIM(RTRIM(s.cargo_space_id)), '') <> ISNULL(LTRIM(RTRIM(i.space_id)), '') THEN 1 ELSE 0 END) AS space_mismatch_count, SUM(CASE WHEN ISNULL(LTRIM(RTRIM(s.tray_code)), '') <> ISNULL(LTRIM(RTRIM(i.tray_code)), '') THEN 1 ELSE 0 END) AS tray_mismatch_count, SUM(CASE WHEN ISNULL(LTRIM(RTRIM(s.spare4)), '') <> ISNULL(LTRIM(RTRIM(i.project_no)), '') THEN 1 ELSE 0 END) AS project_mismatch_count, SUM(CASE WHEN ISNULL(LTRIM(RTRIM(s.product_id)), '') <> ISNULL(LTRIM(RTRIM(i.product_no)), '') THEN 1 ELSE 0 END) AS product_mismatch_count, SUM(CASE WHEN ISNULL(LTRIM(RTRIM(s.lot_no)), '') <> ISNULL(LTRIM(RTRIM(i.lot_no)), '') THEN 1 ELSE 0 END) AS lot_mismatch_count, SUM(CASE WHEN ISNULL(s.quantity, 0) <> ISNULL(i.quantity, 0) THEN 1 ELSE 0 END) AS quantity_mismatch_count, SUM(CASE WHEN ISNULL(s.out_quantity, 0) <> ISNULL(i.out_quantity, 0) THEN 1 ELSE 0 END) AS out_quantity_mismatch_count FROM dbo.bas_stock s INNER JOIN dbo.bas_cargo_space c ON c.cargo_space_id = s.cargo_space_id AND c.warehouse_id = '${warehouse}' INNER JOIN alg.inventory_item_snapshot i ON i.warehouse_id = c.warehouse_id AND i.stock_id = s.stock_id`],
|
["matched_field_difference_counts", `SELECT COUNT_BIG(*) AS matched_stock_count, SUM(CASE WHEN ISNULL(LTRIM(RTRIM(s.cargo_space_id)), '') <> ISNULL(LTRIM(RTRIM(i.space_id)), '') THEN 1 ELSE 0 END) AS space_mismatch_count, SUM(CASE WHEN ISNULL(LTRIM(RTRIM(s.tray_code)), '') <> ISNULL(LTRIM(RTRIM(i.tray_code)), '') THEN 1 ELSE 0 END) AS tray_mismatch_count, SUM(CASE WHEN ISNULL(LTRIM(RTRIM(s.spare4)), '') <> ISNULL(LTRIM(RTRIM(i.project_no)), '') THEN 1 ELSE 0 END) AS project_mismatch_count, SUM(CASE WHEN ISNULL(LTRIM(RTRIM(s.product_id)), '') <> ISNULL(LTRIM(RTRIM(i.product_no)), '') THEN 1 ELSE 0 END) AS product_mismatch_count, SUM(CASE WHEN ISNULL(LTRIM(RTRIM(s.lot_no)), '') <> ISNULL(LTRIM(RTRIM(i.lot_no)), '') THEN 1 ELSE 0 END) AS lot_mismatch_count, SUM(CASE WHEN ISNULL(s.quantity, 0) <> ISNULL(i.quantity, 0) THEN 1 ELSE 0 END) AS quantity_mismatch_count, SUM(CASE WHEN ISNULL(s.out_quantity, 0) <> ISNULL(i.out_quantity, 0) THEN 1 ELSE 0 END) AS out_quantity_mismatch_count FROM dbo.bas_stock s INNER JOIN dbo.bas_cargo_space c ON c.cargo_space_id = s.cargo_space_id AND c.warehouse_id = '${warehouse}' INNER JOIN alg.inventory_item_snapshot i ON i.warehouse_id = c.warehouse_id AND i.stock_id = s.stock_id`],
|
||||||
["field_difference_rows", `SELECT TOP (1000) s.stock_id, s.cargo_space_id AS legacy_space_id, i.space_id AS shadow_space_id, s.tray_code AS legacy_tray_code, i.tray_code AS shadow_tray_code, s.spare4 AS legacy_project_no, i.project_no AS shadow_project_no, s.product_id AS legacy_product_no, i.product_no AS shadow_product_no, s.lot_no AS legacy_lot_no, i.lot_no AS shadow_lot_no, s.quantity AS legacy_quantity, i.quantity AS shadow_quantity, s.out_quantity AS legacy_out_quantity, i.out_quantity AS shadow_out_quantity FROM dbo.bas_stock s INNER JOIN dbo.bas_cargo_space c ON c.cargo_space_id = s.cargo_space_id AND c.warehouse_id = '${warehouse}' INNER JOIN alg.inventory_item_snapshot i ON i.warehouse_id = c.warehouse_id AND i.stock_id = s.stock_id WHERE ISNULL(LTRIM(RTRIM(s.cargo_space_id)), '') <> ISNULL(LTRIM(RTRIM(i.space_id)), '') OR ISNULL(LTRIM(RTRIM(s.tray_code)), '') <> ISNULL(LTRIM(RTRIM(i.tray_code)), '') OR ISNULL(LTRIM(RTRIM(s.spare4)), '') <> ISNULL(LTRIM(RTRIM(i.project_no)), '') OR ISNULL(LTRIM(RTRIM(s.product_id)), '') <> ISNULL(LTRIM(RTRIM(i.product_no)), '') OR ISNULL(LTRIM(RTRIM(s.lot_no)), '') <> ISNULL(LTRIM(RTRIM(i.lot_no)), '') OR ISNULL(s.quantity, 0) <> ISNULL(i.quantity, 0) OR ISNULL(s.out_quantity, 0) <> ISNULL(i.out_quantity, 0) ORDER BY s.stock_id`],
|
["field_difference_rows", `SELECT TOP (1000) s.stock_id, s.cargo_space_id AS legacy_space_id, i.space_id AS shadow_space_id, s.tray_code AS legacy_tray_code, i.tray_code AS shadow_tray_code, s.spare4 AS legacy_project_no, i.project_no AS shadow_project_no, s.product_id AS legacy_product_no, i.product_no AS shadow_product_no, s.lot_no AS legacy_lot_no, i.lot_no AS shadow_lot_no, s.quantity AS legacy_quantity, i.quantity AS shadow_quantity, s.out_quantity AS legacy_out_quantity, i.out_quantity AS shadow_out_quantity FROM dbo.bas_stock s INNER JOIN dbo.bas_cargo_space c ON c.cargo_space_id = s.cargo_space_id AND c.warehouse_id = '${warehouse}' INNER JOIN alg.inventory_item_snapshot i ON i.warehouse_id = c.warehouse_id AND i.stock_id = s.stock_id WHERE ISNULL(LTRIM(RTRIM(s.cargo_space_id)), '') <> ISNULL(LTRIM(RTRIM(i.space_id)), '') OR ISNULL(LTRIM(RTRIM(s.tray_code)), '') <> ISNULL(LTRIM(RTRIM(i.tray_code)), '') OR ISNULL(LTRIM(RTRIM(s.spare4)), '') <> ISNULL(LTRIM(RTRIM(i.project_no)), '') OR ISNULL(LTRIM(RTRIM(s.product_id)), '') <> ISNULL(LTRIM(RTRIM(i.product_no)), '') OR ISNULL(LTRIM(RTRIM(s.lot_no)), '') <> ISNULL(LTRIM(RTRIM(i.lot_no)), '') OR ISNULL(s.quantity, 0) <> ISNULL(i.quantity, 0) OR ISNULL(s.out_quantity, 0) <> ISNULL(i.out_quantity, 0) ORDER BY s.stock_id`],
|
||||||
["request_match_counts", `SELECT COUNT_BIG(*) AS compare_count, SUM(CASE WHEN j_by_id.job_id IS NOT NULL THEN 1 ELSE 0 END) AS matched_by_job_id, SUM(CASE WHEN j_by_id.job_id IS NULL AND j_by_task.job_id IS NOT NULL THEN 1 ELSE 0 END) AS matched_by_task_no, SUM(CASE WHEN j_by_id.job_id IS NULL AND j_by_task.job_id IS NULL THEN 1 ELSE 0 END) AS unmatched_count FROM alg.decision_log d OUTER APPLY (SELECT LTRIM(RTRIM(SUBSTRING(d.reason, CHARINDEX('request=', d.reason) + 8, CHARINDEX(CHAR(59), d.reason + CHAR(59), CHARINDEX('request=', d.reason)) - (CHARINDEX('request=', d.reason) + 8)))) AS request_id) r LEFT JOIN dbo.mydo_job j_by_id ON j_by_id.warehouse_id = '${warehouse}' AND CONVERT(varchar(255), j_by_id.job_id) = r.request_id LEFT JOIN dbo.mydo_job j_by_task ON j_by_task.warehouse_id = '${warehouse}' AND CONVERT(varchar(255), j_by_task.task_no) = r.request_id WHERE d.decision_type = 'OUTBOUND_COMPARE' AND d.created_at >= '${start}' AND d.created_at < '${end}'`],
|
["request_match_counts", `SELECT COUNT_BIG(*) AS compare_count, SUM(CASE WHEN detail.stockout_detail_id IS NOT NULL THEN 1 ELSE 0 END) AS matched_by_detail_id, SUM(CASE WHEN detail.stockout_detail_id IS NULL THEN 1 ELSE 0 END) AS unmatched_count FROM alg.decision_log d OUTER APPLY (SELECT LTRIM(RTRIM(SUBSTRING(d.reason, CHARINDEX('request=', d.reason) + 8, CHARINDEX(CHAR(59), d.reason + CHAR(59), CHARINDEX('request=', d.reason)) - (CHARINDEX('request=', d.reason) + 8)))) AS request_id) r LEFT JOIN dbo.out_stockout_detail detail ON detail.stockout_detail_id = r.request_id AND TRY_CONVERT(int, detail.warehouse_id) = TRY_CONVERT(int, '${warehouse}') WHERE d.decision_type = 'OUTBOUND_COMPARE' AND d.created_at >= '${start}' AND d.created_at < '${end}'`],
|
||||||
["request_match_details", `SELECT TOP (300) d.created_at, d.result_code, r.request_id, j_by_id.job_id AS job_id_match, j_by_id.task_no AS job_id_match_task_no, j_by_id.tray_code AS job_id_match_tray_code, j_by_id.status AS job_id_match_status, j_by_id.in_or_out AS job_id_match_in_or_out, j_by_task.job_id AS task_no_match_job_id, j_by_task.task_no AS task_no_match_task_no, j_by_task.tray_code AS task_no_match_tray_code, j_by_task.status AS task_no_match_status, d.reason FROM alg.decision_log d OUTER APPLY (SELECT LTRIM(RTRIM(SUBSTRING(d.reason, CHARINDEX('request=', d.reason) + 8, CHARINDEX(CHAR(59), d.reason + CHAR(59), CHARINDEX('request=', d.reason)) - (CHARINDEX('request=', d.reason) + 8)))) AS request_id) r LEFT JOIN dbo.mydo_job j_by_id ON j_by_id.warehouse_id = '${warehouse}' AND CONVERT(varchar(255), j_by_id.job_id) = r.request_id LEFT JOIN dbo.mydo_job j_by_task ON j_by_task.warehouse_id = '${warehouse}' AND CONVERT(varchar(255), j_by_task.task_no) = r.request_id WHERE d.decision_type = 'OUTBOUND_COMPARE' AND d.created_at >= '${start}' AND d.created_at < '${end}' ORDER BY d.created_at`]
|
["request_match_details", `SELECT TOP (300) d.created_at, d.result_code, r.request_id, detail.stockout_detail_id AS detail_id_match, detail.stockout_id, detail.warehouse_id, detail.spare4 AS detail_project_no, detail.product_id AS detail_product_id, detail.quantity AS detail_quantity, detail.allot_quantity AS detail_allot_quantity, detail.assign_num AS detail_assign_num, d.reason FROM alg.decision_log d OUTER APPLY (SELECT LTRIM(RTRIM(SUBSTRING(d.reason, CHARINDEX('request=', d.reason) + 8, CHARINDEX(CHAR(59), d.reason + CHAR(59), CHARINDEX('request=', d.reason)) - (CHARINDEX('request=', d.reason) + 8)))) AS request_id) r LEFT JOIN dbo.out_stockout_detail detail ON detail.stockout_detail_id = r.request_id AND TRY_CONVERT(int, detail.warehouse_id) = TRY_CONVERT(int, '${warehouse}') WHERE d.decision_type = 'OUTBOUND_COMPARE' AND d.created_at >= '${start}' AND d.created_at < '${end}' ORDER BY d.created_at`]
|
||||||
];
|
];
|
||||||
|
|
||||||
const results = [];
|
const results = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user