优化
This commit is contained in:
+6
-2
@@ -6,9 +6,13 @@ import java.util.List;
|
||||
public final class ReceivingContainerResponses {
|
||||
private ReceivingContainerResponses() { }
|
||||
|
||||
public record Candidate(String containerNo, String status, int planCount, int inboundCodeCount,
|
||||
public record Candidate(String sourceContainerBookingId, String containerNo, String status,
|
||||
int totalOrderCount, int inboundCodeCount, int missingInboundCodeCount,
|
||||
int planCount, int plannedInboundCodeCount, int pendingPlanCount,
|
||||
List<String> destinationCodes, int plannedPalletCount,
|
||||
int totalPackages, int receivedPackages, boolean currentAccount) { }
|
||||
int totalPackages, int receivedPackages,
|
||||
boolean operable, boolean allPlansReady, String blockReason,
|
||||
LocalDateTime plannedArrivalAt, boolean currentAccount) { }
|
||||
|
||||
public record WarehouseProfile(String warehouseId, String warehouseCode, String warehouseName) { }
|
||||
|
||||
|
||||
+2
@@ -16,6 +16,8 @@ public class ReceivingContainerSession {
|
||||
@TableId(type = IdType.INPUT)
|
||||
private Long id;
|
||||
private String customerId;
|
||||
private String sourceContainerBookingId;
|
||||
private String containerFactHash;
|
||||
private String containerNo;
|
||||
private Long overseasWarehouseId;
|
||||
private String overseasWarehouseName;
|
||||
|
||||
+129
-17
@@ -96,28 +96,24 @@ public class ReceivingContainerService {
|
||||
public List<ReceivingContainerResponses.Candidate> candidates(String operatorKey, long overseasWarehouseId) {
|
||||
ReceivingContainerSession current = sessions.findActiveByOperator(activeOperatorKey(operatorKey));
|
||||
requireSessionWarehouse(current, overseasWarehouseId);
|
||||
Map<String, List<InboundPlan>> grouped = new LinkedHashMap<>();
|
||||
plans.selectList(new LambdaQueryWrapper<InboundPlan>()
|
||||
.ne(InboundPlan::getStatus, "COMPLETED").orderByAsc(InboundPlan::getCreatedAt)).forEach(plan -> {
|
||||
if (plan.getContainerNo() != null && !plan.getContainerNo().isBlank()) {
|
||||
grouped.computeIfAbsent(plan.getContainerNo(), ignored -> new ArrayList<>()).add(plan);
|
||||
}
|
||||
});
|
||||
List<LastlegOrder> orderFacts = orders.selectList(new LambdaQueryWrapper<LastlegOrder>()
|
||||
.isNotNull(LastlegOrder::getSourceContainerBookingId)
|
||||
.isNotNull(LastlegOrder::getContainerNo)
|
||||
.orderByAsc(LastlegOrder::getSourceContainerBookingId, LastlegOrder::getId));
|
||||
List<InboundPlan> planFacts = plans.selectList(new LambdaQueryWrapper<InboundPlan>().orderByAsc(InboundPlan::getId));
|
||||
List<InboundPlanLine> lineFacts = lines.selectList(new LambdaQueryWrapper<InboundPlanLine>().orderByAsc(InboundPlanLine::getId));
|
||||
Map<String, List<LastlegOrder>> grouped = orderFacts.stream()
|
||||
.filter(order -> trim(order.getSourceContainerBookingId()) != null && trim(order.getContainerNo()) != null)
|
||||
.collect(Collectors.groupingBy(LastlegOrder::getSourceContainerBookingId, LinkedHashMap::new, Collectors.toList()));
|
||||
String operatorLockKey = activeOperatorKey(operatorKey);
|
||||
Set<String> reservedByOthers = sessions.findActiveContainers().stream()
|
||||
.filter(session -> !operatorLockKey.equals(session.getActiveOperatorKey()))
|
||||
.map(ReceivingContainerSession::getActiveContainerKey).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
return grouped.entrySet().stream()
|
||||
.filter(entry -> eligible(entry.getValue(), overseasWarehouseId))
|
||||
.filter(entry -> !reservedByOthers.contains(activeContainerKey(entry.getKey())))
|
||||
.map(entry -> {
|
||||
requireSingleCustomer(entry.getValue(), entry.getKey());
|
||||
Totals totals = totals(entry.getValue());
|
||||
boolean active = current != null && entry.getKey().equals(current.getContainerNo());
|
||||
return new ReceivingContainerResponses.Candidate(entry.getKey(), active ? current.getStatus() : "WAIT_SIGN",
|
||||
entry.getValue().size(), totals.inboundCount(), totals.destinationCodes(), totals.plannedPalletCount(),
|
||||
totals.total(), totals.received(), active);
|
||||
}).toList();
|
||||
.map(entry -> readiness(entry.getKey(), entry.getValue(), planFacts, lineFacts, overseasWarehouseId))
|
||||
.filter(Objects::nonNull)
|
||||
.filter(value -> !reservedByOthers.contains(activeContainerKey(value.containerNo())))
|
||||
.map(value -> candidate(value, current)).toList();
|
||||
}
|
||||
|
||||
public ReceivingContainerResponses.Session detail(String operatorKey, long overseasWarehouseId, String containerNo) {
|
||||
@@ -144,6 +140,7 @@ public class ReceivingContainerService {
|
||||
public ReceivingContainerResponses.Session prepare(String operatorKey, String operatorName, long overseasWarehouseId, String containerNo,
|
||||
ReceivingContainerRequests.Action request) {
|
||||
PrepareCommand command = new PrepareCommand(containerNo, request.idempotencyKey());
|
||||
ContainerReadiness readiness = requireReadyContainer(containerNo, overseasWarehouseId, true);
|
||||
ReceivingContainerOperation replay = replay(operatorKey, "PREPARE", request.idempotencyKey(), command);
|
||||
if (replay != null) {
|
||||
ReceivingContainerResponses.Session result = read(replay, ReceivingContainerResponses.Session.class);
|
||||
@@ -176,6 +173,8 @@ public class ReceivingContainerService {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
ReceivingContainerSession session = new ReceivingContainerSession();
|
||||
session.setId(IdWorker.getId()); session.setCustomerId(customerId); session.setContainerNo(containerNo);
|
||||
session.setSourceContainerBookingId(readiness.sourceContainerBookingId());
|
||||
session.setContainerFactHash(readiness.factHash());
|
||||
session.setOverseasWarehouseId(overseasWarehouseId);
|
||||
session.setOverseasWarehouseName(receivingWarehouse.warehouseName());
|
||||
session.setOperatorKey(operatorKey); session.setOperatorName(operatorName);
|
||||
@@ -381,9 +380,117 @@ public class ReceivingContainerService {
|
||||
ReceivingContainerSession session = lock ? sessions.lockActiveByOperator(key)
|
||||
: sessions.findActiveByOperator(key);
|
||||
if (session == null) throw notFound("Current receiving container session was not found");
|
||||
requireStableSessionFacts(session, lock);
|
||||
return session;
|
||||
}
|
||||
|
||||
private ContainerReadiness requireReadyContainer(String containerNo, long overseasWarehouseId, boolean lock) {
|
||||
LambdaQueryWrapper<LastlegOrder> query = new LambdaQueryWrapper<LastlegOrder>()
|
||||
.eq(LastlegOrder::getContainerNo, containerNo).orderByAsc(LastlegOrder::getId);
|
||||
if (lock) query.last("FOR UPDATE");
|
||||
List<LastlegOrder> orderFacts = orders.selectList(query);
|
||||
Set<String> bookingIds = orderFacts.stream().map(LastlegOrder::getSourceContainerBookingId)
|
||||
.map(ReceivingContainerService::trim).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
if (orderFacts.isEmpty() || bookingIds.size() != 1) {
|
||||
throw conflict("Container must resolve exactly one sourceContainerBookingId");
|
||||
}
|
||||
List<InboundPlan> planFacts = plans.selectList(new LambdaQueryWrapper<InboundPlan>()
|
||||
.eq(InboundPlan::getContainerNo, containerNo).orderByAsc(InboundPlan::getId));
|
||||
List<InboundPlanLine> lineFacts = lines.selectList(new LambdaQueryWrapper<InboundPlanLine>()
|
||||
.in(InboundPlanLine::getOrderId, orderFacts.stream().map(LastlegOrder::getId).toList())
|
||||
.orderByAsc(InboundPlanLine::getId));
|
||||
ContainerReadiness value = readiness(bookingIds.iterator().next(), orderFacts, planFacts, lineFacts,
|
||||
overseasWarehouseId);
|
||||
if (value == null || !value.operable()) {
|
||||
throw conflict(value == null ? "Container is assigned to another overseas warehouse" : value.blockReason());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private void requireStableSessionFacts(ReceivingContainerSession session, boolean lock) {
|
||||
if (trim(session.getContainerFactHash()) == null) return;
|
||||
ContainerReadiness current = requireReadyContainer(session.getContainerNo(), session.getOverseasWarehouseId(), lock);
|
||||
if (!Objects.equals(session.getSourceContainerBookingId(), current.sourceContainerBookingId())
|
||||
|| !Objects.equals(session.getContainerFactHash(), current.factHash())) {
|
||||
throw conflict("Container orders or inbound plans changed after prepare; restart the receiving session");
|
||||
}
|
||||
}
|
||||
|
||||
private ContainerReadiness readiness(String sourceContainerBookingId, List<LastlegOrder> orderFacts,
|
||||
List<InboundPlan> planFacts, List<InboundPlanLine> lineFacts,
|
||||
long overseasWarehouseId) {
|
||||
Set<Long> orderIds = orderFacts.stream().map(LastlegOrder::getId).collect(Collectors.toSet());
|
||||
Map<Long, List<InboundPlanLine>> linesByOrder = lineFacts.stream()
|
||||
.filter(line -> orderIds.contains(line.getOrderId()))
|
||||
.collect(Collectors.groupingBy(InboundPlanLine::getOrderId));
|
||||
Map<Long, InboundPlan> plansById = planFacts.stream().collect(Collectors.toMap(InboundPlan::getId,
|
||||
value -> value, (left, right) -> left));
|
||||
List<InboundPlan> matchedPlans = linesByOrder.values().stream().flatMap(List::stream)
|
||||
.map(line -> plansById.get(line.getPlanId())).filter(Objects::nonNull).distinct().toList();
|
||||
Set<Long> assignedWarehouses = matchedPlans.stream().map(InboundPlan::getOverseasWarehouseId)
|
||||
.filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
orderFacts.stream().map(LastlegOrder::getOverseasWarehouseId).map(ReceivingContainerService::trim)
|
||||
.filter(Objects::nonNull).forEach(value -> {
|
||||
try { assignedWarehouses.add(Long.valueOf(value)); }
|
||||
catch (NumberFormatException ignored) { assignedWarehouses.add(Long.MIN_VALUE); }
|
||||
});
|
||||
if (assignedWarehouses.size() > 1) throw conflict("Container plans have inconsistent receiving warehouse assignments");
|
||||
if (!assignedWarehouses.isEmpty() && !assignedWarehouses.contains(overseasWarehouseId)) return null;
|
||||
Set<String> customers = orderFacts.stream().map(LastlegOrder::getCustomerId).collect(Collectors.toSet());
|
||||
String containerNo = orderFacts.stream().map(LastlegOrder::getContainerNo).filter(Objects::nonNull).findFirst().orElse(null);
|
||||
int totalOrders = orderFacts.size();
|
||||
int inboundCodes = (int) orderFacts.stream().map(LastlegOrder::getInboundCode).map(ReceivingContainerService::trim)
|
||||
.filter(Objects::nonNull).distinct().count();
|
||||
int plannedOrders = (int) orderFacts.stream().filter(order -> {
|
||||
List<InboundPlanLine> values = linesByOrder.getOrDefault(order.getId(), List.of());
|
||||
return values.size() == 1 && plansById.containsKey(values.get(0).getPlanId());
|
||||
}).count();
|
||||
int planCount = matchedPlans.size();
|
||||
boolean allPlansReady = totalOrders > 0 && customers.size() == 1 && inboundCodes == totalOrders
|
||||
&& plannedOrders == totalOrders && planCount == totalOrders;
|
||||
String customerId = customers.size() == 1 ? customers.iterator().next() : null;
|
||||
boolean shippingReady = customerId != null && shippingGates.receivingAvailable(customerId, containerNo) == 1;
|
||||
boolean operable = allPlansReady && shippingReady;
|
||||
int pending = Math.max(0, totalOrders - plannedOrders);
|
||||
String blockReason = operable ? null : (!allPlansReady
|
||||
? plannedOrders + "个入仓码已生成计划," + pending + "个订单未操作/缺入仓码"
|
||||
: "柜号尚未达到海外收货可操作节点");
|
||||
LocalDateTime arrival = orderFacts.stream().map(LastlegOrder::getEstimatedWarehouseAt)
|
||||
.filter(Objects::nonNull).min(LocalDateTime::compareTo).orElse(null);
|
||||
String factHash = hash(orderFacts.stream().sorted(Comparator.comparing(LastlegOrder::getId)).map(order -> {
|
||||
List<InboundPlanLine> values = linesByOrder.getOrDefault(order.getId(), List.of());
|
||||
return order.getId() + ":" + trim(order.getInboundCode()) + ":" + values.stream()
|
||||
.map(InboundPlanLine::getPlanId).sorted().toList();
|
||||
}).toList());
|
||||
Map<Long, LastlegOrder> ordersById = orderFacts.stream().collect(Collectors.toMap(LastlegOrder::getId,
|
||||
value -> value, (left, right) -> left));
|
||||
List<InboundRef> matchedRefs = lineFacts.stream().filter(line -> orderIds.contains(line.getOrderId()))
|
||||
.map(line -> new InboundRef(plansById.get(line.getPlanId()), line, ordersById.get(line.getOrderId())))
|
||||
.filter(ref -> ref.plan() != null && ref.order() != null).toList();
|
||||
List<ReceivingContainerResponses.Inbound> inboundValues = matchedRefs.stream().map(this::inbound).toList();
|
||||
List<String> destinationCodes = inboundValues.stream().map(this::destinationCode)
|
||||
.filter(Objects::nonNull).distinct().sorted().toList();
|
||||
Totals totals = new Totals(inboundValues.size(), destinationCodes,
|
||||
inboundValues.stream().mapToInt(ReceivingContainerResponses.Inbound::plannedPalletCount).sum(),
|
||||
inboundValues.stream().mapToInt(ReceivingContainerResponses.Inbound::totalPackages).sum(),
|
||||
inboundValues.stream().mapToInt(ReceivingContainerResponses.Inbound::receivedPackages).sum());
|
||||
return new ContainerReadiness(sourceContainerBookingId, containerNo, totalOrders, inboundCodes,
|
||||
totalOrders - inboundCodes, planCount, plannedOrders, pending, allPlansReady, operable,
|
||||
blockReason, arrival, factHash, totals);
|
||||
}
|
||||
|
||||
private ReceivingContainerResponses.Candidate candidate(ContainerReadiness value,
|
||||
ReceivingContainerSession current) {
|
||||
boolean active = current != null && value.containerNo().equals(current.getContainerNo());
|
||||
Totals totals = value.totals();
|
||||
return new ReceivingContainerResponses.Candidate(value.sourceContainerBookingId(), value.containerNo(),
|
||||
active ? current.getStatus() : "WAIT_SIGN", value.totalOrderCount(), value.inboundCodeCount(),
|
||||
value.missingInboundCodeCount(), value.planCount(), value.plannedInboundCodeCount(),
|
||||
value.pendingPlanCount(), totals.destinationCodes(), totals.plannedPalletCount(), totals.total(),
|
||||
totals.received(), value.operable(), value.allPlansReady(), value.blockReason(),
|
||||
value.plannedArrivalAt(), active);
|
||||
}
|
||||
|
||||
private ReceivingContainerSession requireSigned(String operatorKey) {
|
||||
ReceivingContainerSession session = requireCurrent(operatorKey, true);
|
||||
if (!List.of("SIGNED", "RECEIVING").contains(session.getStatus())) {
|
||||
@@ -858,5 +965,10 @@ public class ReceivingContainerService {
|
||||
private record FbaSkuAllocation(InboundRef ref, String skuCode, String productName, int packages) { }
|
||||
private record Totals(int inboundCount, List<String> destinationCodes, int plannedPalletCount,
|
||||
int total, int received) { }
|
||||
private record ContainerReadiness(String sourceContainerBookingId, String containerNo,
|
||||
int totalOrderCount, int inboundCodeCount, int missingInboundCodeCount,
|
||||
int planCount, int plannedInboundCodeCount, int pendingPlanCount,
|
||||
boolean allPlansReady, boolean operable, String blockReason,
|
||||
LocalDateTime plannedArrivalAt, String factHash, Totals totals) { }
|
||||
private record PrepareCommand(String containerNo, String idempotencyKey) { }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user