fix(lastleg): enforce container warehouse plan facts
This commit is contained in:
-4
@@ -11,11 +11,7 @@ public record ManualInboundPlanRequest(
|
||||
@NotBlank @Size(max = 128) String customerId,
|
||||
@NotNull Long orderId,
|
||||
@NotNull @Min(0) Integer expectedOrderVersion,
|
||||
@NotNull Long plannedWarehouseId,
|
||||
@NotBlank @Size(max = 128) String plannedWarehouseCode,
|
||||
@NotBlank @Size(max = 255) String plannedWarehouseName,
|
||||
@NotNull LocalDate plannedArrivalDate,
|
||||
@NotBlank @Size(max = 128) String inboundCode,
|
||||
@NotNull @Min(1) @Max(999) Integer palletCount,
|
||||
@NotBlank @Size(max = 32) String palletType,
|
||||
@NotNull Boolean assigned,
|
||||
|
||||
+11
@@ -16,6 +16,17 @@ public interface InboundPlanOrderMapper {
|
||||
String orderType) { }
|
||||
record ReceivingWarehouseSnapshot(String overseasWarehouseId, String overseasWarehouseCode,
|
||||
String overseasWarehouseName) { }
|
||||
record ContainerWarehouseRef(String customerId, Long orderId, String sourceContainerBookingId,
|
||||
String overseasWarehouseId) { }
|
||||
|
||||
@Select("SELECT c.customer_id AS customerId,c.id AS orderId," +
|
||||
"c.source_container_booking_id AS sourceContainerBookingId,c.overseas_warehouse_id AS overseasWarehouseId " +
|
||||
"FROM ll_order target JOIN ll_order c ON c.source_container_booking_id=target.source_container_booking_id " +
|
||||
"WHERE target.customer_id=#{customerId} AND target.id=#{orderId} " +
|
||||
"AND target.source_container_booking_id IS NOT NULL AND target.source_container_booking_id!='' " +
|
||||
"ORDER BY c.customer_id,c.id FOR UPDATE")
|
||||
List<ContainerWarehouseRef> lockContainerWarehouseFacts(@Param("customerId") String customerId,
|
||||
@Param("orderId") Long orderId);
|
||||
|
||||
@Select("SELECT customer_id AS customerId,id AS orderId,current_status AS currentStatus,container_no AS containerNo," +
|
||||
"order_type AS orderType " +
|
||||
|
||||
+53
-20
@@ -75,6 +75,13 @@ public class InboundPlanService {
|
||||
if (key == null || key.length() > 128) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Idempotency-Key 必须为1至128个字符");
|
||||
}
|
||||
List<InboundPlanOrderMapper.ContainerWarehouseRef> containerFacts =
|
||||
orderMapper.lockContainerWarehouseFacts(customerId, request.orderId());
|
||||
if (containerFacts == null || containerFacts.isEmpty()) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "尾程订单不存在或未绑定来源柜");
|
||||
}
|
||||
String containerWarehouseId = requireSingleWarehouseId(containerFacts.stream()
|
||||
.map(InboundPlanOrderMapper.ContainerWarehouseRef::overseasWarehouseId).toList());
|
||||
List<SourceOrderSnapshot> snapshots = orderMapper.lockOrder(customerId, request.orderId());
|
||||
if (snapshots == null || snapshots.size() != 1) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "尾程订单不存在");
|
||||
@@ -96,20 +103,14 @@ public class InboundPlanService {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "尾程订单版本已变化,请刷新后重试");
|
||||
}
|
||||
if (order.detained()) throw new ResponseStatusException(HttpStatus.CONFLICT, "暂扣尾程订单不能生成入库计划");
|
||||
if (!Objects.equals(trimToNull(order.inboundCode()), trimToNull(request.inboundCode()))) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "入仓码与尾程订单冻结事实不一致");
|
||||
}
|
||||
WarehouseResourceGateway.WarehouseReference warehouse =
|
||||
warehouseGateway.requireActiveSharedWarehouse(request.plannedWarehouseId());
|
||||
if (!Objects.equals(String.valueOf(warehouse.warehouseId()), trimToNull(order.overseasWarehouseId()))
|
||||
|| !Objects.equals(warehouse.warehouseCode(), request.plannedWarehouseCode().trim())
|
||||
|| !Objects.equals(warehouse.warehouseName(), request.plannedWarehouseName().trim())
|
||||
|| !Objects.equals(warehouse.warehouseCode(), trimToNull(order.overseasWarehouseCode()))
|
||||
|| !Objects.equals(warehouse.warehouseName(), trimToNull(order.overseasWarehouseName()))) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "计划入仓仓库不存在、已停用或与订单预分配事实不一致");
|
||||
if (!Objects.equals(containerWarehouseId, trimToNull(order.overseasWarehouseId()))) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "来源柜接柜仓冻结事实已变化,请刷新后重试");
|
||||
}
|
||||
WarehouseResourceGateway.WarehouseReference receivingWarehouse =
|
||||
requiredReceivingWarehouse(containerWarehouseId);
|
||||
validateOrders(snapshots);
|
||||
InboundPlanResponse response = createPlan(customerId, request.orderId(), snapshots, List.of(), null, request);
|
||||
InboundPlanResponse response = createPlan(customerId, request.orderId(), snapshots, List.of(), null,
|
||||
request, receivingWarehouse);
|
||||
LastlegOrderCommandRecord command = new LastlegOrderCommandRecord();
|
||||
command.setId(IdWorker.getId()); command.setCustomerId(customerId); command.setOrderId(request.orderId());
|
||||
command.setAction("MANUAL_PLAN"); command.setOperatorId(operatorId); command.setIdempotencyKey(key);
|
||||
@@ -180,12 +181,13 @@ public class InboundPlanService {
|
||||
return detail(customerId, existing);
|
||||
}
|
||||
validateOrders(orders);
|
||||
return createPlan(customerId, selectedOrderId, orders, sourcePalletNos, wholePallets, null);
|
||||
return createPlan(customerId, selectedOrderId, orders, sourcePalletNos, wholePallets, null, null);
|
||||
}
|
||||
|
||||
private InboundPlanResponse createPlan(String customerId, Long selectedOrderId,
|
||||
List<SourceOrderSnapshot> orders, List<String> sourcePalletNos,
|
||||
List<WholePalletInput> wholePallets, ManualInboundPlanRequest manual) {
|
||||
List<WholePalletInput> wholePallets, ManualInboundPlanRequest manual,
|
||||
WarehouseResourceGateway.WarehouseReference receivingWarehouse) {
|
||||
String containerNo = requiredContainerNo(orders.get(0).containerNo());
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
InboundPlan plan = new InboundPlan();
|
||||
@@ -262,7 +264,7 @@ public class InboundPlanService {
|
||||
orderMapper.insertHistory(customerId, order.orderId(), order.currentStatus(),
|
||||
"Inbound order converted to inbound plan " + plan.getPlanNo(), now);
|
||||
}
|
||||
return detail(customerId, plan);
|
||||
return detail(customerId, plan, receivingWarehouse);
|
||||
}
|
||||
|
||||
/** 同一来源柜的每个尾程订单独立建计划;整个柜级批次共用一个事务。 */
|
||||
@@ -326,12 +328,14 @@ public class InboundPlanService {
|
||||
}
|
||||
wholePallets.add(sourcePallets);
|
||||
}
|
||||
requireSingleWarehouseId(snapshots.stream().flatMap(List::stream)
|
||||
.map(SourceOrderSnapshot::overseasWarehouseId).toList());
|
||||
|
||||
List<InboundPlanResponse> created = new ArrayList<>(refs.size());
|
||||
for (int index = 0; index < refs.size(); index++) {
|
||||
InboundPlanOrderMapper.ContainerOrderRef ref = refs.get(index);
|
||||
created.add(createPlan(ref.customerId(), ref.orderId(), snapshots.get(index), List.of(),
|
||||
wholePallets.get(index), null));
|
||||
wholePallets.get(index), null, null));
|
||||
}
|
||||
return List.copyOf(created);
|
||||
}
|
||||
@@ -623,16 +627,21 @@ public class InboundPlanService {
|
||||
}
|
||||
|
||||
private InboundPlanResponse detail(String customerId, InboundPlan plan) {
|
||||
return detail(customerId, plan, null);
|
||||
}
|
||||
|
||||
private InboundPlanResponse detail(String customerId, InboundPlan plan,
|
||||
WarehouseResourceGateway.WarehouseReference resolvedWarehouse) {
|
||||
if (customerId != null && !customerId.equals(plan.getCustomerId())) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Inbound plan not found");
|
||||
}
|
||||
InboundPlanOrderMapper.ReceivingWarehouseSnapshot receivingWarehouse = orderMapper.findReceivingWarehouse(
|
||||
plan.getCustomerId(), plan.getSourceOrderId());
|
||||
if (receivingWarehouse == null || trimToNull(receivingWarehouse.overseasWarehouseId()) == null
|
||||
|| trimToNull(receivingWarehouse.overseasWarehouseCode()) == null
|
||||
|| trimToNull(receivingWarehouse.overseasWarehouseName()) == null) {
|
||||
if (receivingWarehouse == null || trimToNull(receivingWarehouse.overseasWarehouseId()) == null) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "入库计划关联订单缺少接柜仓预分配事实");
|
||||
}
|
||||
WarehouseResourceGateway.WarehouseReference authoritativeWarehouse = resolvedWarehouse == null
|
||||
? requiredReceivingWarehouse(receivingWarehouse.overseasWarehouseId()) : resolvedWarehouse;
|
||||
List<InboundPlanLine> lines = lineMapper.selectList(new LambdaQueryWrapper<InboundPlanLine>()
|
||||
.eq(InboundPlanLine::getPlanId, plan.getId()).orderByAsc(InboundPlanLine::getId));
|
||||
List<InboundPlanPallet> pallets = palletMapper.selectList(new LambdaQueryWrapper<InboundPlanPallet>()
|
||||
@@ -642,7 +651,7 @@ public class InboundPlanService {
|
||||
.eq(InboundPlanPalletAllocation::getPlanId, plan.getId())
|
||||
.orderByAsc(InboundPlanPalletAllocation::getId));
|
||||
return new InboundPlanResponse(plan.getId(), plan.getPlanNo(), plan.getCustomerId(), plan.getSourceOrderId(),
|
||||
plan.getContainerNo(), receivingWarehouse.overseasWarehouseCode(), receivingWarehouse.overseasWarehouseName(),
|
||||
plan.getContainerNo(), authoritativeWarehouse.warehouseCode(), authoritativeWarehouse.warehouseName(),
|
||||
plan.getStatus(), plan.getLineCount(), plan.getPalletCount(), plan.getTotalVolumeCbm(),
|
||||
lines.stream().mapToInt(InboundPlanLine::getRequiredPackageCount).sum(),
|
||||
allocations.stream().mapToInt(InboundPlanPalletAllocation::getPickedPackageCount).sum(),
|
||||
@@ -663,6 +672,30 @@ public class InboundPlanService {
|
||||
Boolean.TRUE.equals(plan.getUrgentFlag()), plan.getCreationMode(), plan.getPlanRemark());
|
||||
}
|
||||
|
||||
private WarehouseResourceGateway.WarehouseReference requiredReceivingWarehouse(String warehouseId) {
|
||||
String stableId = trimToNull(warehouseId);
|
||||
if (stableId == null) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "柜内接柜仓未预分配");
|
||||
}
|
||||
try {
|
||||
return warehouseGateway.requireActiveSharedWarehouse(Long.parseLong(stableId));
|
||||
} catch (NumberFormatException exception) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "柜内接柜仓ID格式无效", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private String requireSingleWarehouseId(List<String> warehouseIds) {
|
||||
List<String> normalized = warehouseIds.stream().map(InboundPlanService::trimToNull).toList();
|
||||
if (normalized.stream().anyMatch(Objects::isNull)) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "来源柜存在未分配接柜仓的尾程订单");
|
||||
}
|
||||
java.util.Set<String> distinct = new java.util.HashSet<>(normalized);
|
||||
if (distinct.size() != 1) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "来源柜内尾程订单接柜仓不一致");
|
||||
}
|
||||
return distinct.iterator().next();
|
||||
}
|
||||
|
||||
private BigDecimal volume(SourceOrderSnapshot order) {
|
||||
return Objects.requireNonNullElse(order.volumeCbm(), BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user