feat: gate overseas receiving and harden phase4 workflows

This commit is contained in:
2026-08-25 10:31:04 +08:00
parent cb78bc6b41
commit 52b9fc382f
93 changed files with 2779 additions and 201 deletions
@@ -29,7 +29,7 @@ import java.util.List;
@RestController
@RequestMapping("/api/pda/v1/last-leg/receiving-containers")
@Tag(name = "Overseas PDA container receiving")
@PreAuthorize("hasAuthority('lastleg:pda:operate')")
@PreAuthorize("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_WAREHOUSE_PICKER')")
public class ReceivingContainerController {
public static final String OPERATE_PERMISSION = PdaWarehouseIdentity.OPERATE_PERMISSION;
@Resource private ReceivingContainerService service;
@@ -114,6 +114,33 @@ public class HttpWarehouseResourceGateway implements WarehouseResourceGateway {
response.data().fbaWarehouseCode(), response.data().version());
}
@Override
public WarehouseLocationReference resolveLocation(long warehouseId, String locationCode) {
WarehouseLocationResponse response = client.get()
.uri(uri -> uri.path("/internal/v1/warehouse-resource-locks/locations/resolve/{locationCode}")
.queryParam("warehouseId", warehouseId).build(locationCode))
.header("X-Internal-Service-Token", internalToken).retrieve()
.body(WarehouseLocationResponse.class);
return location(response);
}
@Override
public WarehouseLocationReference resolveLocation(long warehouseId, long locationId) {
WarehouseLocationResponse response = client.get()
.uri(uri -> uri.path("/internal/v1/warehouse-resource-locks/locations/{locationId}")
.queryParam("warehouseId", warehouseId).build(locationId))
.header("X-Internal-Service-Token", internalToken).retrieve()
.body(WarehouseLocationResponse.class);
return location(response);
}
private WarehouseLocationReference location(WarehouseLocationResponse response) {
if (response == null || response.data() == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Warehouse location was not found");
}
return response.data();
}
@Override
public WarehouseReference requireActiveSharedWarehouse(long warehouseId) {
WarehouseReferenceResponse response = client.get()
@@ -147,4 +174,5 @@ public class HttpWarehouseResourceGateway implements WarehouseResourceGateway {
private record ReserveEnvelope(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) {}
}
@@ -8,6 +8,12 @@ public interface WarehouseResourceGateway {
void scrap(String customerId, long resourceLockId);
void scrapPallet(String customerId, long palletId);
WarehousePalletReference resolvePallet(String palletCode);
default WarehouseLocationReference resolveLocation(long warehouseId, String locationCode) {
throw new UnsupportedOperationException("Warehouse location resolution is not configured");
}
default WarehouseLocationReference resolveLocation(long warehouseId, long locationId) {
throw new UnsupportedOperationException("Warehouse location resolution is not configured");
}
default WarehouseReference requireActiveSharedWarehouse(long warehouseId) {
throw new UnsupportedOperationException("Shared warehouse resolution is not configured");
}
@@ -21,4 +27,6 @@ public interface WarehouseResourceGateway {
record WarehouseReference(long warehouseId, String warehouseCode, String warehouseName,
String status) { }
record WarehouseLocationReference(long locationId, String locationCode, String locationType,
java.math.BigDecimal maxLoadKg, String status) { }
}
@@ -27,6 +27,7 @@ import com.baiyue.lastleg.inboundplan.pda.mapper.ReceivingContainerSessionMapper
import com.baiyue.lastleg.inboundplan.pda.mapper.ReceivingSkuReceiptMapper;
import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper;
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
import com.baiyue.lastleg.shipping.mapper.ContainerShippingGateMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.MapperFeature;
@@ -67,6 +68,7 @@ public class ReceivingContainerService {
@Resource private ReceivingContainerSessionMapper sessions;
@Resource private ReceivingContainerOperationMapper operations;
@Resource private ReceivingSkuReceiptMapper skuReceipts;
@Resource private ContainerShippingGateMapper shippingGates;
@Resource private WarehouseResourceGateway warehouse;
@Resource private UnloadPhotoFileGateway unloadPhotos;
@Resource private InboundPdaWorkflowService workflow;
@@ -104,7 +106,8 @@ public class ReceivingContainerService {
public ReceivingContainerResponses.Session detail(String operatorKey, long overseasWarehouseId, String containerNo) {
List<InboundPlan> containerPlans = requirePlans(containerNo);
String customerId = requireSingleCustomer(containerPlans, containerNo);
if (!eligible(containerPlans, overseasWarehouseId)) throw notFound("Container is assigned to another overseas warehouse");
if (!warehouseEligible(containerPlans, overseasWarehouseId)) throw notFound("Container is assigned to another overseas warehouse");
requireReceivingAvailable(customerId, containerNo);
ReceivingContainerSession current = sessions.findActiveByOperator(activeOperatorKey(operatorKey));
requireSessionWarehouse(current, overseasWarehouseId);
if (current != null && !containerNo.equals(current.getContainerNo())) {
@@ -130,7 +133,8 @@ public class ReceivingContainerService {
}
List<InboundPlan> containerPlans = requirePlans(containerNo);
String customerId = requireSingleCustomer(containerPlans, containerNo);
if (!eligible(containerPlans, overseasWarehouseId)) throw conflict("Container is assigned to another overseas warehouse");
if (!warehouseEligible(containerPlans, overseasWarehouseId)) throw conflict("Container is assigned to another overseas warehouse");
requireReceivingAvailable(customerId, containerNo);
String activeOperatorKey = activeOperatorKey(operatorKey);
ReceivingContainerSession active = sessions.lockActiveByOperator(activeOperatorKey);
requireSessionWarehouse(active, overseasWarehouseId);
@@ -641,6 +645,12 @@ public class ReceivingContainerService {
}
private boolean eligible(List<InboundPlan> containerPlans, long overseasWarehouseId) {
String customerId = requireSingleCustomer(containerPlans, containerPlans.get(0).getContainerNo());
return warehouseEligible(containerPlans, overseasWarehouseId)
&& shippingGates.receivingAvailable(customerId, containerPlans.get(0).getContainerNo()) == 1;
}
private boolean warehouseEligible(List<InboundPlan> containerPlans, long overseasWarehouseId) {
Set<Long> assigned = containerPlans.stream().map(InboundPlan::getOverseasWarehouseId)
.filter(Objects::nonNull).collect(Collectors.toSet());
if (assigned.size() > 1 || (!assigned.isEmpty() && containerPlans.stream()
@@ -650,6 +660,12 @@ public class ReceivingContainerService {
return assigned.isEmpty() || assigned.contains(overseasWarehouseId);
}
private void requireReceivingAvailable(String customerId, String containerNo) {
if (shippingGates.receivingAvailable(customerId, containerNo) != 1) {
throw conflict("Container has not reached PICKUP_READY and is not available for overseas receiving");
}
}
private void claimReceivingWarehouse(String customerId, List<InboundPlan> containerPlans,
WarehouseResourceGateway.WarehouseReference warehouse) {
long overseasWarehouseId = warehouse.warehouseId();