fix(shadow): capture and validate inbound task evidence for v3.4
This commit is contained in:
@@ -8,6 +8,37 @@ public class ShadowInboundReference {
|
||||
private String productId;
|
||||
private String lotNo;
|
||||
private Integer shadowCandidateCount;
|
||||
private String spare1;
|
||||
private String spare2;
|
||||
private String spare3;
|
||||
private String faultFlag;
|
||||
private Integer detailCount;
|
||||
private Integer fieldGroupCount;
|
||||
private Integer productScopeCount;
|
||||
private Integer trayJobCount;
|
||||
private Integer legacyScopeComplete;
|
||||
private Integer predicateVersion;
|
||||
|
||||
public String getSpare1() { return spare1; }
|
||||
public void setSpare1(String value) { spare1 = value; }
|
||||
public String getSpare2() { return spare2; }
|
||||
public void setSpare2(String value) { spare2 = value; }
|
||||
public String getSpare3() { return spare3; }
|
||||
public void setSpare3(String value) { spare3 = value; }
|
||||
public String getFaultFlag() { return faultFlag; }
|
||||
public void setFaultFlag(String value) { faultFlag = value; }
|
||||
public Integer getDetailCount() { return detailCount; }
|
||||
public void setDetailCount(Integer value) { detailCount = value; }
|
||||
public Integer getFieldGroupCount() { return fieldGroupCount; }
|
||||
public void setFieldGroupCount(Integer value) { fieldGroupCount = value; }
|
||||
public Integer getProductScopeCount() { return productScopeCount; }
|
||||
public void setProductScopeCount(Integer value) { productScopeCount = value; }
|
||||
public Integer getTrayJobCount() { return trayJobCount; }
|
||||
public void setTrayJobCount(Integer value) { trayJobCount = value; }
|
||||
public Integer getLegacyScopeComplete() { return legacyScopeComplete; }
|
||||
public void setLegacyScopeComplete(Integer value) { legacyScopeComplete = value; }
|
||||
public Integer getPredicateVersion() { return predicateVersion; }
|
||||
public void setPredicateVersion(Integer value) { predicateVersion = value; }
|
||||
|
||||
public String getRequestId() {
|
||||
return requestId;
|
||||
|
||||
@@ -195,7 +195,8 @@ public class ShadowPlanningEngine {
|
||||
UUID.randomUUID().toString(), observation.warehouseId(), "INBOUND_COMPARE",
|
||||
observation.snapshotGeneration(), observation.snapshotSynchronizedAt(),
|
||||
observation.sourceSignature(),
|
||||
comparisonCode(observation.proposedSpaceId(), legacySpaceId),
|
||||
observation.observed() && "READY".equals(observation.resultCode()) && "10".equals(legacyResultCode)
|
||||
? comparisonCode(observation.proposedSpaceId(), legacySpaceId) : "NOT_COMPARABLE",
|
||||
inboundComparisonReason(observation, legacyResultCode, legacySpaceId));
|
||||
requireComparisonInserted(inserted);
|
||||
});
|
||||
@@ -226,6 +227,36 @@ public class ShadowPlanningEngine {
|
||||
inboundDiagnostic(snapshot, taskNo, trayNo, reference,
|
||||
"Inbound task details were unavailable to shadow planning"));
|
||||
}
|
||||
if (!Integer.valueOf(1).equals(reference.getPredicateVersion())) {
|
||||
return unavailableInbound(snapshot, taskNo, trayNo, reference, "SKIPPED_MISSING_PREDICATE_INPUT",
|
||||
"Task input is missing or predates inbound predicate version 1");
|
||||
}
|
||||
if (!WarehouseIdNormalizer.equivalent(reference.getWarehouseId(), warehouseId)
|
||||
|| !Integer.valueOf(1).equals(reference.getLegacyScopeComplete())) {
|
||||
return unavailableInbound(snapshot, taskNo, trayNo, reference, "SKIPPED_INCOMPLETE_LEGACY_SCOPE",
|
||||
"Warehouse snapshot does not cover the global Legacy allocation scope");
|
||||
}
|
||||
if (!Integer.valueOf(1).equals(reference.getTrayJobCount())) {
|
||||
return unavailableInbound(snapshot, taskNo, trayNo, reference, "SKIPPED_AMBIGUOUS_TRAY_JOB",
|
||||
"Task or tray identifies more than one Legacy job");
|
||||
}
|
||||
if (reference.getDetailCount() == null || reference.getDetailCount() <= 0) {
|
||||
return unavailableInbound(snapshot, taskNo, trayNo, reference, "SKIPPED_MISSING_TASK_DETAILS",
|
||||
"Job was captured but has no task details");
|
||||
}
|
||||
if (!Integer.valueOf(1).equals(reference.getFieldGroupCount())) {
|
||||
return unavailableInbound(snapshot, taskNo, trayNo, reference, "SKIPPED_AMBIGUOUS_LEGACY_FIELDS",
|
||||
"Legacy TOP (1) has no ordering and task details have different spare1/2/3 values");
|
||||
}
|
||||
if (!Integer.valueOf(1).equals(reference.getProductScopeCount()) || isBlank(reference.getProductId())) {
|
||||
return unavailableInbound(snapshot, taskNo, trayNo, reference, "SKIPPED_MULTI_PRODUCT_INPUT",
|
||||
"Single-product planner cannot represent this task product/lot scope");
|
||||
}
|
||||
if (isBlank(reference.getProjectNo()) || "999".equals(reference.getProjectNo().trim())
|
||||
|| "COMMON".equalsIgnoreCase(reference.getProjectNo().trim())) {
|
||||
return unavailableInbound(snapshot, taskNo, trayNo, reference, "SKIPPED_UNSUPPORTED_COMMON_PROJECT",
|
||||
"Common-project mapping and pool policy require separate validation");
|
||||
}
|
||||
InboundRequest request = new InboundRequest(reference.getRequestId(), reference.getTrayNo(),
|
||||
reference.getProjectNo(), reference.getProductId(), reference.getLotNo(),
|
||||
null, null, null, Set.of(), null);
|
||||
@@ -233,10 +264,17 @@ public class ShadowPlanningEngine {
|
||||
InboundPlan plan = result.plan();
|
||||
if (plan.assignment().isPresent()) {
|
||||
var assignment = plan.assignment().orElseThrow();
|
||||
if (inputMapper.countInboundEligibleSpace(warehouseId, snapshot.generation(),
|
||||
taskNo, trayNo, assignment.spaceId()) != 1) {
|
||||
return unavailableInbound(snapshot, taskNo, trayNo, reference, "SKIPPED_LEGACY_CONSTRAINT",
|
||||
"unverifiedProposal=" + abbreviate(nullSafe(assignment.spaceId()), IDENTIFIER_MAX_LENGTH)
|
||||
+ "; outside conservative Legacy eligibility subset; " + assignment.reason());
|
||||
}
|
||||
return new InboundShadowObservation(true, snapshot.generation(), warehouseId,
|
||||
snapshot.synchronizedAt(), snapshot.sourceSignature(),
|
||||
reference.getRequestId(), "READY", assignment.spaceId(),
|
||||
inboundDiagnostic(snapshot, taskNo, trayNo, reference, assignment.reason()));
|
||||
inboundDiagnostic(snapshot, taskNo, trayNo, reference,
|
||||
"legacyEligibility=STRICT_GROUP_OR_EMPTY; " + assignment.reason()));
|
||||
}
|
||||
var rejection = plan.rejection().orElseThrow();
|
||||
return InboundShadowObservation.unavailable(snapshot,
|
||||
@@ -244,6 +282,12 @@ public class ShadowPlanningEngine {
|
||||
inboundDiagnostic(snapshot, taskNo, trayNo, reference, rejection.reason()));
|
||||
}
|
||||
|
||||
private InboundShadowObservation unavailableInbound(PublishedShadowSnapshot snapshot, String taskNo,
|
||||
String trayNo, ShadowInboundReference reference, String code, String reason) {
|
||||
return InboundShadowObservation.unavailable(snapshot, reference.getRequestId(), code,
|
||||
inboundDiagnostic(snapshot, taskNo, trayNo, reference, reason));
|
||||
}
|
||||
|
||||
private OutboundShadowObservation toOutboundObservation(PublishedShadowSnapshot snapshot,
|
||||
String requestId, ShadowStockReference reference, OutboundPlan plan) {
|
||||
if (isBlank(requestId)) {
|
||||
@@ -409,6 +453,18 @@ public class ShadowPlanningEngine {
|
||||
IDENTIFIER_MAX_LENGTH);
|
||||
appendField(diagnostic, "shadowCandidates", numberOrUnknown(
|
||||
reference == null ? null : reference.getShadowCandidateCount()), NUMBER_MAX_LENGTH);
|
||||
if (reference != null) {
|
||||
appendField(diagnostic, "inboundPredicateVersion", numberOrUnknown(reference.getPredicateVersion()), NUMBER_MAX_LENGTH);
|
||||
appendField(diagnostic, "detailCount", numberOrUnknown(reference.getDetailCount()), NUMBER_MAX_LENGTH);
|
||||
appendField(diagnostic, "fieldGroupCount", numberOrUnknown(reference.getFieldGroupCount()), NUMBER_MAX_LENGTH);
|
||||
appendField(diagnostic, "productScopeCount", numberOrUnknown(reference.getProductScopeCount()), NUMBER_MAX_LENGTH);
|
||||
appendField(diagnostic, "trayJobCount", numberOrUnknown(reference.getTrayJobCount()), NUMBER_MAX_LENGTH);
|
||||
appendField(diagnostic, "legacyScopeComplete", numberOrUnknown(reference.getLegacyScopeComplete()), NUMBER_MAX_LENGTH);
|
||||
appendField(diagnostic, "spare1", reference.getSpare1(), 120);
|
||||
appendField(diagnostic, "spare2", reference.getSpare2(), 120);
|
||||
appendField(diagnostic, "spare3", reference.getSpare3(), 120);
|
||||
appendField(diagnostic, "faultFlag", reference.getFaultFlag(), 20);
|
||||
}
|
||||
appendField(diagnostic, "reason", resultReason, RESULT_REASON_MAX_LENGTH);
|
||||
return diagnostic.toString();
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@ public interface ShadowInputMapper {
|
||||
@Param("generation") long generation, @Param("taskNo") String taskNo,
|
||||
@Param("trayNo") String trayNo);
|
||||
|
||||
int countInboundEligibleSpace(@Param("warehouseId") String warehouseId,
|
||||
@Param("generation") long generation, @Param("taskNo") String taskNo,
|
||||
@Param("trayNo") String trayNo, @Param("spaceId") String spaceId);
|
||||
|
||||
ShadowStockReference selectProjectOutboundReference(@Param("warehouseId") String warehouseId,
|
||||
@Param("generation") long generation,
|
||||
@Param("stockoutDetailId") String stockoutDetailId);
|
||||
|
||||
@@ -12,6 +12,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
input.project_no as projectNo,
|
||||
input.product_id as productId,
|
||||
input.lot_no as lotNo,
|
||||
input.spare1, input.spare2, input.spare3, input.fault_flag as faultFlag,
|
||||
input.detail_count as detailCount, input.field_group_count as fieldGroupCount,
|
||||
input.product_scope_count as productScopeCount, input.tray_job_count as trayJobCount,
|
||||
input.legacy_scope_complete as legacyScopeComplete, input.predicate_version as predicateVersion,
|
||||
convert(int, coalesce((
|
||||
select sum(convert(bigint, lane.available_count))
|
||||
from alg.lane_state lane
|
||||
@@ -25,6 +29,57 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
and input.tray_no = #{trayNo}
|
||||
</select>
|
||||
|
||||
<!-- Conservative subset valid in either Legacy project-count branch. Ranking remains in the planner. -->
|
||||
<select id="countInboundEligibleSpace" resultType="int">
|
||||
select count(*)
|
||||
from alg.shadow_inbound_request_snapshot input
|
||||
join alg.cargo_space_snapshot cargo
|
||||
on cargo.warehouse_id = input.warehouse_id
|
||||
and cargo.snapshot_generation = input.snapshot_generation
|
||||
where input.warehouse_id = #{warehouseId} and input.snapshot_generation = #{generation}
|
||||
and input.task_no = #{taskNo} and input.tray_no = #{trayNo}
|
||||
and input.predicate_version = 1 and input.detail_count > 0
|
||||
and input.field_group_count = 1 and input.product_scope_count = 1
|
||||
and input.tray_job_count = 1 and input.legacy_scope_complete = 1
|
||||
and nullif(ltrim(rtrim(input.project_no)), '') is not null
|
||||
and input.project_no not in ('999', 'COMMON')
|
||||
and cargo.space_id = #{spaceId} and cargo.space_status = '1' and cargo.locked = 'N'
|
||||
and nullif(ltrim(rtrim(cargo.tray_code)), '') is null
|
||||
and cargo.this_cargo_area in ('A', 'C')
|
||||
and nullif(ltrim(rtrim(cargo.lane_code)), '') is not null
|
||||
and nullif(ltrim(rtrim(cargo.floor_no)), '') is not null
|
||||
and not exists (
|
||||
select 1 from alg.inventory_item_snapshot occupied
|
||||
where occupied.warehouse_id = cargo.warehouse_id
|
||||
and occupied.snapshot_generation = cargo.snapshot_generation and occupied.space_id = cargo.space_id)
|
||||
and not exists (
|
||||
select 1 from alg.cargo_space_snapshot busy
|
||||
where busy.warehouse_id = cargo.warehouse_id and busy.snapshot_generation = cargo.snapshot_generation
|
||||
and ((busy.floor_no = cargo.floor_no and busy.space_status = '3')
|
||||
or (busy.lane_code = cargo.lane_code and (busy.space_status in ('3', '4') or busy.locked = 'Y'))))
|
||||
and (
|
||||
not exists (
|
||||
select 1 from alg.cargo_space_snapshot lane
|
||||
where lane.warehouse_id = cargo.warehouse_id and lane.snapshot_generation = cargo.snapshot_generation
|
||||
and lane.lane_code = cargo.lane_code
|
||||
and (lane.space_status is null or lane.space_status <> '1'
|
||||
or lane.locked is null or lane.locked <> 'N'
|
||||
or nullif(ltrim(rtrim(lane.tray_code)), '') is not null
|
||||
or exists (select 1 from alg.inventory_item_snapshot stock
|
||||
where stock.warehouse_id = lane.warehouse_id
|
||||
and stock.snapshot_generation = lane.snapshot_generation and stock.space_id = lane.space_id)))
|
||||
or exists (
|
||||
select 1 from alg.cargo_space_snapshot resident
|
||||
join alg.inventory_item_snapshot stock
|
||||
on stock.warehouse_id = resident.warehouse_id and stock.snapshot_generation = resident.snapshot_generation
|
||||
and stock.space_id = resident.space_id and stock.tray_code = resident.tray_code
|
||||
where resident.warehouse_id = cargo.warehouse_id and resident.snapshot_generation = cargo.snapshot_generation
|
||||
and resident.lane_code = cargo.lane_code and resident.space_status = '2'
|
||||
and resident.this_cargo_area in ('A', 'C')
|
||||
and stock.project_no = input.project_no
|
||||
and stock.spare1 = input.spare1 and stock.spare2 = input.spare2 and stock.spare3 = input.spare3))
|
||||
</select>
|
||||
|
||||
<sql id="outboundCandidatePredicate">
|
||||
item.warehouse_id = #{warehouseId}
|
||||
and item.snapshot_generation = #{generation}
|
||||
|
||||
Reference in New Issue
Block a user