fix(lastleg): reject unavailable receiving pallets
This commit is contained in:
+38
-4
@@ -59,10 +59,20 @@ public class HttpWarehouseResourceGateway implements WarehouseResourceGateway {
|
||||
"businessId", businessId, "reservationKey", reservationKey))
|
||||
.retrieve()
|
||||
.onStatus(HttpStatusCode::isError, (request, result) -> {
|
||||
throw new IllegalStateException("Warehouse rejected pallet reservation: HTTP " + result.getStatusCode());
|
||||
throw warehouseReservationFailure(result.getStatusCode());
|
||||
})
|
||||
.body(ReserveEnvelope.class);
|
||||
if (response == null || response.data() == null) throw new IllegalStateException("Warehouse returned an empty lock response");
|
||||
if (response == null) {
|
||||
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE,
|
||||
"Warehouse resource reservation returned no response");
|
||||
}
|
||||
if (response.data() == null) {
|
||||
HttpStatus status = "A0409".equals(response.code()) ? HttpStatus.CONFLICT
|
||||
: "A0404".equals(response.code()) ? HttpStatus.NOT_FOUND : HttpStatus.SERVICE_UNAVAILABLE;
|
||||
throw new ResponseStatusException(status,
|
||||
response.message() == null || response.message().isBlank()
|
||||
? "Warehouse resource reservation was rejected" : response.message());
|
||||
}
|
||||
return response.data().lockId();
|
||||
}
|
||||
|
||||
@@ -120,6 +130,7 @@ public class HttpWarehouseResourceGateway implements WarehouseResourceGateway {
|
||||
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE,
|
||||
"Warehouse pallet service returned no usable response");
|
||||
}
|
||||
requireReservable(response.data());
|
||||
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());
|
||||
@@ -133,6 +144,7 @@ public class HttpWarehouseResourceGateway implements WarehouseResourceGateway {
|
||||
.body(WarehousePalletResponse.class);
|
||||
if (response == null || response.data() == null) throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE,
|
||||
"Warehouse pallet service returned no usable response");
|
||||
requireReservable(response.data());
|
||||
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());
|
||||
@@ -144,6 +156,26 @@ public class HttpWarehouseResourceGateway implements WarehouseResourceGateway {
|
||||
status.value() == 404 ? "Warehouse pallet was not found" : "Warehouse pallet lookup failed");
|
||||
}
|
||||
|
||||
private ResponseStatusException warehouseReservationFailure(HttpStatusCode status) {
|
||||
HttpStatus mapped = status.value() == 409 ? HttpStatus.CONFLICT
|
||||
: status.value() == 404 ? HttpStatus.NOT_FOUND
|
||||
: status.is4xxClientError() ? HttpStatus.BAD_REQUEST : HttpStatus.SERVICE_UNAVAILABLE;
|
||||
return new ResponseStatusException(mapped, "Warehouse resource reservation was rejected");
|
||||
}
|
||||
|
||||
private void requireReservable(WarehousePalletData pallet) {
|
||||
if (pallet.reservable()) return;
|
||||
String reason = switch (pallet.reservationBlockReason() == null
|
||||
? "RESOURCE_STATE_UNKNOWN" : pallet.reservationBlockReason()) {
|
||||
case "PALLET_NOT_IDLE" -> "Pallet is not idle";
|
||||
case "LOCATION_NOT_FOUND" -> "Pallet location was not found";
|
||||
case "VIRTUAL_LOCATION_NOT_ACTIVE" -> "Pallet virtual location is not active";
|
||||
case "LOCATION_NOT_IDLE" -> "Pallet location is not idle";
|
||||
default -> "Pallet resource availability could not be verified";
|
||||
};
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WarehouseLocationReference resolveLocation(long warehouseId, String locationCode) {
|
||||
WarehouseLocationResponse response = client.get()
|
||||
@@ -200,8 +232,10 @@ public class HttpWarehouseResourceGateway implements WarehouseResourceGateway {
|
||||
|
||||
private record WarehousePalletResponse(String code, String message, WarehousePalletData data) {}
|
||||
private record WarehousePalletData(@JsonAlias("palletId") long id, String palletCode, long warehouseId, long locationId,
|
||||
String status, String fbaWarehouseCode, java.math.BigDecimal unitPrice, int version) {}
|
||||
private record ReserveEnvelope(ReserveData data) {}
|
||||
String status, String fbaWarehouseCode, java.math.BigDecimal unitPrice, int version,
|
||||
String locationType, String locationStatus, boolean reservable,
|
||||
String reservationBlockReason) {}
|
||||
private record ReserveEnvelope(String code, String message, ReserveData data) {}
|
||||
private record ReserveData(long lockId) {}
|
||||
private record WarehouseReferenceResponse(String code, String message, WarehouseReference data) {}
|
||||
private record WarehouseLocationResponse(String code, String message, WarehouseLocationReference data) {}
|
||||
|
||||
+70
-1
@@ -11,6 +11,7 @@ import org.springframework.web.server.ResponseStatusException;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
|
||||
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;
|
||||
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
|
||||
|
||||
class HttpWarehouseResourceGatewayTest {
|
||||
@@ -32,7 +33,9 @@ class HttpWarehouseResourceGatewayTest {
|
||||
.andRespond(withSuccess("""
|
||||
{"code":"200","data":{"palletId":2092040891261427713,"palletCode":"PAL-1",
|
||||
"warehouseId":2092040869757231105,"locationId":2092040891076878337,
|
||||
"status":"IDLE","fbaWarehouseCode":"ABE3","version":3}}
|
||||
"status":"IDLE","fbaWarehouseCode":"ABE3","version":3,
|
||||
"locationType":"STORAGE","locationStatus":"IDLE","reservable":true,
|
||||
"reservationBlockReason":null}}
|
||||
""", MediaType.APPLICATION_JSON));
|
||||
|
||||
WarehouseResourceGateway.WarehousePalletReference result = gateway.resolvePallet("PAL-1");
|
||||
@@ -43,6 +46,40 @@ class HttpWarehouseResourceGatewayTest {
|
||||
server.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveRejectsPalletWhoseLocationIsNotReservable() {
|
||||
server.expect(requestTo("http://warehouse/internal/v1/warehouse-pallets/resolve/PAL-BLOCKED"))
|
||||
.andRespond(withSuccess("""
|
||||
{"code":"200","data":{"palletId":2092040891261427713,"palletCode":"PAL-BLOCKED",
|
||||
"warehouseId":2092040869757231105,"locationId":2092040891076878337,
|
||||
"status":"IDLE","fbaWarehouseCode":"ABE3","version":3,
|
||||
"locationType":"STORAGE","locationStatus":"OCCUPIED","reservable":false,
|
||||
"reservationBlockReason":"LOCATION_NOT_IDLE"}}
|
||||
""", MediaType.APPLICATION_JSON));
|
||||
|
||||
assertThatThrownBy(() -> gateway.resolvePallet("PAL-BLOCKED"))
|
||||
.isInstanceOf(ResponseStatusException.class)
|
||||
.hasMessageContaining("409")
|
||||
.hasMessageContaining("Pallet location is not idle");
|
||||
server.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveFailsClosedWhenWarehouseOmitsReservationFacts() {
|
||||
server.expect(requestTo("http://warehouse/internal/v1/warehouse-pallets/resolve/PAL-UNKNOWN"))
|
||||
.andRespond(withSuccess("""
|
||||
{"code":"200","data":{"palletId":2092040891261427713,"palletCode":"PAL-UNKNOWN",
|
||||
"warehouseId":2092040869757231105,"locationId":2092040891076878337,
|
||||
"status":"IDLE","fbaWarehouseCode":"ABE3","version":3}}
|
||||
""", MediaType.APPLICATION_JSON));
|
||||
|
||||
assertThatThrownBy(() -> gateway.resolvePallet("PAL-UNKNOWN"))
|
||||
.isInstanceOf(ResponseStatusException.class)
|
||||
.hasMessageContaining("409")
|
||||
.hasMessageContaining("availability could not be verified");
|
||||
server.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void ensureDropshipStillMapsWarehouseEntityId() {
|
||||
server.expect(requestTo("http://warehouse/internal/v1/warehouse-pallets/ensure-dropship"))
|
||||
@@ -72,4 +109,36 @@ class HttpWarehouseResourceGatewayTest {
|
||||
server.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void reserveMapsWarehouseLocationConflictToStableConflict() {
|
||||
server.expect(requestTo("http://warehouse/internal/v1/warehouse-resource-locks"))
|
||||
.andRespond(withStatus(org.springframework.http.HttpStatus.CONFLICT)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body("""
|
||||
{"code":"A0409","message":"Location is not idle","data":null}
|
||||
"""));
|
||||
|
||||
assertThatThrownBy(() -> gateway.reserve("1", 2L, 3L, 4L,
|
||||
"LASTLEG_INBOUND", "plan:5", "reservation-1"))
|
||||
.isInstanceOf(ResponseStatusException.class)
|
||||
.hasMessageContaining("409")
|
||||
.hasMessageContaining("Warehouse resource reservation was rejected");
|
||||
server.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void reserveMapsBusinessConflictEnvelopeToStableConflict() {
|
||||
server.expect(requestTo("http://warehouse/internal/v1/warehouse-resource-locks"))
|
||||
.andRespond(withSuccess("""
|
||||
{"code":"A0409","message":"Location is not idle","data":null}
|
||||
""", MediaType.APPLICATION_JSON));
|
||||
|
||||
assertThatThrownBy(() -> gateway.reserve("1", 2L, 3L, 4L,
|
||||
"LASTLEG_INBOUND", "plan:5", "reservation-1"))
|
||||
.isInstanceOf(ResponseStatusException.class)
|
||||
.hasMessageContaining("409")
|
||||
.hasMessageContaining("Location is not idle");
|
||||
server.verify();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user