diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGateway.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGateway.java index 2a56165fe..7a1cca23d 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGateway.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGateway.java @@ -179,15 +179,14 @@ public class HttpWarehouseResourceGateway implements WarehouseResourceGateway { .header("X-Internal-Service-Token", internalToken).retrieve() .onStatus(HttpStatusCode::isError, (request, result) -> { throw palletLookupFailure(result.getStatusCode()); }) .body(WarehousePalletResponse.class); - if (response == null || response.data() == null) throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, - "Warehouse pallet service returned no usable response"); + requirePalletResponse(response); return new WarehousePalletReference(response.data().id(), response.data().palletCode(), response.data().warehouseId(), response.data().locationId(), response.data().status(), response.data().fbaWarehouseCode(), response.data().unitPrice(), response.data().version()); } private ResponseStatusException palletLookupFailure(HttpStatusCode status) { - return new ResponseStatusException(status.value() == 404 ? HttpStatus.NOT_FOUND + return new ResponseStatusException(status.value() == 409 ? HttpStatus.CONFLICT : status.value() == 404 ? HttpStatus.NOT_FOUND : (status.is4xxClientError() ? HttpStatus.BAD_REQUEST : HttpStatus.SERVICE_UNAVAILABLE), status.value() == 404 ? "Warehouse pallet was not found" : "Warehouse pallet lookup failed"); } @@ -199,14 +198,23 @@ public class HttpWarehouseResourceGateway implements WarehouseResourceGateway { .retrieve().onStatus(HttpStatusCode::isError, (request, result) -> { throw palletLookupFailure(result.getStatusCode()); }).body(WarehousePalletResponse.class); + requirePalletResponse(response); + return response; + } + + /** 托盘状态冲突是业务拒绝,不能把HTTP 200内的A0409误报为仓储服务不可用。 */ + private void requirePalletResponse(WarehousePalletResponse response) { + if (response != null && "A0409".equals(response.code())) { + throw new ResponseStatusException(HttpStatus.CONFLICT, + response.message() == null || response.message().isBlank() ? "Warehouse pallet state conflicts with this operation" : response.message()); + } if (response != null && "A0404".equals(response.code())) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Warehouse pallet was not found"); } - if (response == null || response.data() == null) { + if (response == null || !"200".equals(response.code()) || response.data() == null) { throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "Warehouse pallet service returned no usable response"); } - return response; } private WarehousePalletReference reference(WarehousePalletData pallet) { diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGatewayTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGatewayTest.java index 6b3c47e9a..d7cfdc11d 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGatewayTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/gateway/HttpWarehouseResourceGatewayTest.java @@ -164,6 +164,46 @@ class HttpWarehouseResourceGatewayTest { server.verify(); } + /** 实际SHIP后报废的原托扫码仍拒绝;保留提供者业务码/原因,不伪装为服务故障或按ID重试。 */ + @Test + void inspectPreservesScrappedPalletBusinessConflictWithoutFallback() { + server.expect(requestTo("http://warehouse/internal/v1/warehouse-pallets/resolve/D1747-1-001")) + .andRespond(withSuccess(""" + {"code":"A0409","message":"托盘已报废","data":null} + """, MediaType.APPLICATION_JSON)); + assertThatThrownBy(() -> gateway.inspectPallet("D1747-1-001")) + .isInstanceOfSatisfying(ResponseStatusException.class, error -> { + assertThat(error.getStatusCode()).isEqualTo(HttpStatus.CONFLICT); + assertThat(error.getReason()).isEqualTo("托盘已报废"); + }); + server.verify(); + } + + /** 按ID读取使用同一响应边界,错误码即使带data也不能被当作合法实托。 */ + @Test + void idLookupRejectsBusinessConflictEvenWithPayload() { + server.expect(requestTo("http://warehouse/internal/v1/warehouse-pallets/resolve/id/31")) + .andRespond(withSuccess(""" + {"code":"A0409","message":"托盘状态冲突","data":{"palletId":31,"palletCode":"PAL-1", + "warehouseId":88,"locationId":40,"status":"IDLE","version":1}} + """, MediaType.APPLICATION_JSON)); + assertThatThrownBy(() -> gateway.resolvePallet(31L)) + .isInstanceOfSatisfying(ResponseStatusException.class, error -> + assertThat(error.getStatusCode()).isEqualTo(HttpStatus.CONFLICT)); + server.verify(); + } + + /** 同一个状态冲突无论通过HTTP状态还是业务信封传输,都不能降为400或503。 */ + @Test + void inspectPreservesHttpConflict() { + server.expect(requestTo("http://warehouse/internal/v1/warehouse-pallets/resolve/PAL-CONFLICT")) + .andRespond(withStatus(HttpStatus.CONFLICT)); + assertThatThrownBy(() -> gateway.inspectPallet("PAL-CONFLICT")) + .isInstanceOfSatisfying(ResponseStatusException.class, error -> + assertThat(error.getStatusCode()).isEqualTo(HttpStatus.CONFLICT)); + server.verify(); + } + @Test void resolveReservedFallsBackToConfirmedPhysicalBindingForEarlierPlanDisplayCode() { server.expect(requestTo("http://warehouse/internal/v1/warehouse-pallets/resolve/IP-ACY2-20260909-166786-P001"))