优化
This commit is contained in:
+8
-4
@@ -24,6 +24,7 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
@@ -58,17 +59,20 @@ public class ReceivingContainerController {
|
||||
@GetMapping("/{containerNo}")
|
||||
@Operation(summary = "Get one receiving container without exposing plan or allocation IDs")
|
||||
public ApiResponse<ReceivingContainerResponses.Session> detail(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String containerNo) {
|
||||
@PathVariable String containerNo,
|
||||
@RequestParam String sourceContainerBookingId) {
|
||||
PdaWarehouseIdentity identity = identity(user);
|
||||
return ApiResponse.success(service.detail(identity.operatorKey(), identity.overseasWarehouseId(), containerNo));
|
||||
return ApiResponse.success(service.detail(identity.operatorKey(), identity.overseasWarehouseId(),
|
||||
containerNo, sourceContainerBookingId));
|
||||
}
|
||||
|
||||
@PostMapping("/{containerNo}/prepare")
|
||||
public ApiResponse<ReceivingContainerResponses.Session> prepare(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String containerNo, @Valid @RequestBody ReceivingContainerRequests.Action request) {
|
||||
@PathVariable String containerNo, @RequestParam String sourceContainerBookingId,
|
||||
@Valid @RequestBody ReceivingContainerRequests.Action request) {
|
||||
PdaWarehouseIdentity identity = identity(user);
|
||||
return ApiResponse.success(service.prepare(identity.operatorKey(), identity.operatorName(), identity.overseasWarehouseId(),
|
||||
containerNo, request));
|
||||
containerNo, sourceContainerBookingId, request));
|
||||
}
|
||||
|
||||
@PostMapping(value = "/current/unload-photos/files", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
|
||||
+105
-80
@@ -62,6 +62,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@@ -142,14 +143,16 @@ public class ReceivingContainerService {
|
||||
.map(value -> candidate(value, current)).toList();
|
||||
}
|
||||
|
||||
public ReceivingContainerResponses.Session detail(String operatorKey, long overseasWarehouseId, String containerNo) {
|
||||
requireReadyContainer(containerNo, overseasWarehouseId, false);
|
||||
List<InboundPlan> containerPlans = requirePlans(containerNo);
|
||||
public ReceivingContainerResponses.Session detail(String operatorKey, long overseasWarehouseId, String containerNo,
|
||||
String sourceContainerBookingId) {
|
||||
requireReadyContainer(containerNo, sourceContainerBookingId, overseasWarehouseId, false);
|
||||
List<InboundPlan> containerPlans = requirePlans(sourceContainerBookingId, containerNo);
|
||||
requireMaterializedPlans(containerPlans);
|
||||
if (!warehouseEligible(containerPlans, overseasWarehouseId)) throw conflict("当前柜已分配至其他海外接柜仓,无法在本仓操作");
|
||||
ReceivingContainerSession current = sessions.findActiveByOperator(activeOperatorKey(operatorKey));
|
||||
requireSessionWarehouse(current, overseasWarehouseId);
|
||||
if (current != null && !containerNo.equals(current.getContainerNo())) {
|
||||
if (current != null && (!containerNo.equals(current.getContainerNo())
|
||||
|| !sourceContainerBookingId.equals(current.getSourceContainerBookingId()))) {
|
||||
throw conflict("Current account is locked to another selected container");
|
||||
}
|
||||
ReceivingContainerSession reserved = sessions.findActiveByContainer(activeContainerKey(containerNo));
|
||||
@@ -158,15 +161,17 @@ public class ReceivingContainerService {
|
||||
}
|
||||
WarehouseResourceGateway.WarehouseReference receivingWarehouse =
|
||||
warehouse.requireActiveSharedWarehouse(overseasWarehouseId);
|
||||
return snapshot(current != null && containerNo.equals(current.getContainerNo()) ? current : waitingView(
|
||||
operatorKey, receivingWarehouse, containerNo), containerPlans);
|
||||
return snapshot(current != null && containerNo.equals(current.getContainerNo())
|
||||
&& sourceContainerBookingId.equals(current.getSourceContainerBookingId()) ? current : waitingView(
|
||||
operatorKey, receivingWarehouse, sourceContainerBookingId, containerNo), containerPlans);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ReceivingContainerResponses.Session prepare(String operatorKey, String operatorName, long overseasWarehouseId, String containerNo,
|
||||
String sourceContainerBookingId,
|
||||
ReceivingContainerRequests.Action request) {
|
||||
PrepareCommand command = new PrepareCommand(containerNo, request.idempotencyKey());
|
||||
ContainerReadiness readiness = requireReadyContainer(containerNo, overseasWarehouseId, true);
|
||||
PrepareCommand command = new PrepareCommand(sourceContainerBookingId, containerNo, request.idempotencyKey());
|
||||
ContainerReadiness readiness = requireReadyContainer(containerNo, sourceContainerBookingId, overseasWarehouseId, true);
|
||||
ReceivingContainerOperation replay = replay(operatorKey, "PREPARE", request.idempotencyKey(), command);
|
||||
if (replay != null) {
|
||||
ReceivingContainerResponses.Session result = read(replay, ReceivingContainerResponses.Session.class);
|
||||
@@ -175,13 +180,14 @@ public class ReceivingContainerService {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
List<InboundPlan> containerPlans = requirePlans(containerNo);
|
||||
List<InboundPlan> containerPlans = requirePlans(sourceContainerBookingId, containerNo);
|
||||
requireMaterializedPlans(containerPlans);
|
||||
if (!warehouseEligible(containerPlans, overseasWarehouseId)) throw conflict("当前柜已分配至其他海外接柜仓,无法在本仓操作");
|
||||
String activeOperatorKey = activeOperatorKey(operatorKey);
|
||||
ReceivingContainerSession active = sessions.lockActiveByOperator(activeOperatorKey);
|
||||
requireSessionWarehouse(active, overseasWarehouseId);
|
||||
if (active != null && !containerNo.equals(active.getContainerNo())) {
|
||||
if (active != null && (!containerNo.equals(active.getContainerNo())
|
||||
|| !sourceContainerBookingId.equals(active.getSourceContainerBookingId()))) {
|
||||
throw conflict("Finish the current container before selecting another one");
|
||||
}
|
||||
if (active == null && sessions.lockActiveByContainer(activeContainerKey(containerNo)) != null) {
|
||||
@@ -229,7 +235,7 @@ public class ReceivingContainerService {
|
||||
List<ReceivingContainerResponses.SignedPhoto> photos = validateSignPhotos(request);
|
||||
List<Long> photoFileIds = photos.stream().map(photo -> positiveFileId(photo.fileId())).toList();
|
||||
photoFileIds.forEach(fileId -> unloadPhotos.validate(PLATFORM_SCOPE, session.getId(), fileId));
|
||||
List<InboundPlan> containerPlans = requirePlans(session.getContainerNo());
|
||||
List<InboundPlan> containerPlans = requirePlans(session.getSourceContainerBookingId(), session.getContainerNo());
|
||||
requireMaterializedPlans(containerPlans);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
session.setSignPhotos(write(photos)); session.setStatus("SIGNED"); session.setSignedAt(now);
|
||||
@@ -243,7 +249,7 @@ public class ReceivingContainerService {
|
||||
|
||||
public ReceivingContainerResponses.Session current(String operatorKey) {
|
||||
ReceivingContainerSession session = requireCurrent(operatorKey, false);
|
||||
return snapshot(session, requirePlans(session.getContainerNo()));
|
||||
return snapshot(session, requirePlans(session.getSourceContainerBookingId(), session.getContainerNo()));
|
||||
}
|
||||
|
||||
public UnloadPhotoFileGateway.DownloadedFile downloadCurrentPhoto(String operatorKey, String fileId) {
|
||||
@@ -276,10 +282,11 @@ public class ReceivingContainerService {
|
||||
public ReceivingContainerResponses.CartonResolved resolveCarton(String operatorKey,
|
||||
ReceivingContainerRequests.CartonResolve request) {
|
||||
ReceivingContainerSession session = requireSigned(operatorKey);
|
||||
List<InboundRef> currentRefs = refs(session.getContainerNo());
|
||||
List<InboundRef> currentRefs = refs(session.getSourceContainerBookingId(), session.getContainerNo());
|
||||
String sourcePalletNo = dropshipSourcePallet(currentRefs, session.getContainerNo(), request.cartonMark());
|
||||
if (sourcePalletNo != null) {
|
||||
List<DropshipPalletRef> palletRefs = dropshipPalletRefs(session.getContainerNo(), sourcePalletNo);
|
||||
List<DropshipPalletRef> palletRefs = dropshipPalletRefs(
|
||||
session.getSourceContainerBookingId(), session.getContainerNo(), sourcePalletNo);
|
||||
requireCompleteManifest(session.getContainerNo(), sourcePalletNo);
|
||||
List<InboundRef> group = palletRefs.stream().map(DropshipPalletRef::ref).distinct().toList();
|
||||
InboundRef first = group.get(0);
|
||||
@@ -306,14 +313,16 @@ public class ReceivingContainerService {
|
||||
}
|
||||
carton = new CartonRef(matches.get(0));
|
||||
} else if (matches.size() == 1) {
|
||||
carton = requireCartonRef(matches.get(0).order().getCustomerId(), session.getContainerNo(), request.cartonMark());
|
||||
carton = requireCartonRef(matches.get(0).order().getCustomerId(),
|
||||
session.getSourceContainerBookingId(), session.getContainerNo(), request.cartonMark());
|
||||
} else {
|
||||
carton = requireUnmatchedFbaCartonPrefix(inboundCode);
|
||||
}
|
||||
InboundRef ref = carton.ref();
|
||||
if ("FBA".equals(destinationType(ref.line(), ref.order()))
|
||||
&& !"ONE_CLICK_DROPSHIP".equals(ref.order().getOrderType())) {
|
||||
List<InboundRef> group = requireFbaPlanRefs(session.getContainerNo(), ref);
|
||||
List<InboundRef> group = requireFbaPlanRefs(
|
||||
session.getSourceContainerBookingId(), session.getContainerNo(), ref);
|
||||
List<String> inboundCodes = group.stream().map(value -> value.line().getInboundCode()).distinct().sorted().toList();
|
||||
int total = group.stream().mapToInt(value -> value(value.line().getRequiredPackageCount())).sum();
|
||||
int received = group.stream().mapToInt(this::receivedLinePackages).sum();
|
||||
@@ -339,10 +348,11 @@ public class ReceivingContainerService {
|
||||
public ReceivingContainerResponses.PalletResolved resolvePallet(String operatorKey,
|
||||
ReceivingContainerRequests.PalletResolve request) {
|
||||
ReceivingContainerSession session = requireSigned(operatorKey);
|
||||
InboundRef ref = requireInbound(session.getContainerNo(), request.inboundCode());
|
||||
InboundRef ref = requireInbound(session.getSourceContainerBookingId(),
|
||||
session.getContainerNo(), request.inboundCode());
|
||||
List<InboundRef> group = "FBA".equals(destinationType(ref.line(), ref.order()))
|
||||
&& !"ONE_CLICK_DROPSHIP".equals(ref.order().getOrderType())
|
||||
? requireFbaPlanRefs(session.getContainerNo(), ref) : List.of(ref);
|
||||
? requireFbaPlanRefs(session.getSourceContainerBookingId(), session.getContainerNo(), ref) : List.of(ref);
|
||||
PalletSelection selection = group.size() == 1 && !"FBA".equals(destinationType(ref.line(), ref.order()))
|
||||
? resolveReceivingPallet(ref, request.palletCode(), false) : resolveFbaGroupPallet(group, request.palletCode());
|
||||
WarehouseResourceGateway.WarehousePalletReference pallet = selection.resource();
|
||||
@@ -369,14 +379,16 @@ public class ReceivingContainerService {
|
||||
if (replay != null) return read(replay, ReceivingContainerResponses.Receipt.class);
|
||||
requirePalletCapacity(request.packageCount());
|
||||
ReceivingContainerSession session = requireSigned(operatorKey);
|
||||
InboundRef ref = requireInbound(session.getContainerNo(), request.inboundCode());
|
||||
InboundRef ref = requireInbound(session.getSourceContainerBookingId(),
|
||||
session.getContainerNo(), request.inboundCode());
|
||||
if ("ONE_CLICK_DROPSHIP".equals(ref.order().getOrderType())) {
|
||||
throw conflict("One-click dropship inbound must be operated by complete source pallet");
|
||||
}
|
||||
if (!"FBA".equals(destinationType(ref.line(), ref.order()))) {
|
||||
throw conflict("OVERSEAS destination must use carton-by-carton scanning; bulk count is forbidden");
|
||||
}
|
||||
List<InboundRef> fbaPlanRefs = requireFbaPlanRefs(session.getContainerNo(), ref);
|
||||
List<InboundRef> fbaPlanRefs = requireFbaPlanRefs(
|
||||
session.getSourceContainerBookingId(), session.getContainerNo(), ref);
|
||||
PalletSelection selection = resolveFbaGroupPallet(fbaPlanRefs, request.palletCode());
|
||||
WarehouseResourceGateway.WarehousePalletReference resource = selection.resource();
|
||||
InboundPlanPallet pallet = selection.pallet();
|
||||
@@ -451,15 +463,16 @@ public class ReceivingContainerService {
|
||||
ReceivingContainerOperation replay = replay(operatorKey, "CARTON_RECEIVE", request.idempotencyKey(), request);
|
||||
if (replay != null) return read(replay, ReceivingContainerResponses.Receipt.class);
|
||||
ReceivingContainerSession session = requireSigned(operatorKey);
|
||||
List<InboundRef> currentRefs = refs(session.getContainerNo());
|
||||
List<InboundRef> currentRefs = refs(session.getSourceContainerBookingId(), session.getContainerNo());
|
||||
if (currentRefs.stream().noneMatch(value ->
|
||||
"OVERSEAS".equals(destinationType(value.line(), value.order())))) {
|
||||
throw conflict("FBA destination must use bulk count");
|
||||
}
|
||||
String inboundCode = cartonInboundCode(request.cartonMark());
|
||||
InboundRef inboundRef = requireInbound(session.getContainerNo(), inboundCode);
|
||||
InboundRef inboundRef = requireInbound(session.getSourceContainerBookingId(), session.getContainerNo(), inboundCode);
|
||||
String customerId = inboundRef.order().getCustomerId();
|
||||
CartonRef carton = requireCartonRef(customerId, session.getContainerNo(), request.cartonMark());
|
||||
CartonRef carton = requireCartonRef(customerId, session.getSourceContainerBookingId(),
|
||||
session.getContainerNo(), request.cartonMark());
|
||||
InboundRef ref = carton.ref();
|
||||
if (!"OVERSEAS".equals(destinationType(ref.line(), ref.order()))) {
|
||||
throw conflict("FBA destination must use bulk count");
|
||||
@@ -496,10 +509,12 @@ public class ReceivingContainerService {
|
||||
ReceivingContainerOperation replay = replay(operatorKey, "PALLET_RECEIVE", request.idempotencyKey(), request);
|
||||
if (replay != null) return read(replay, ReceivingContainerResponses.Receipt.class);
|
||||
ReceivingContainerSession session = requireSigned(operatorKey);
|
||||
String sourcePalletNo = dropshipSourcePallet(session.getContainerNo(), request.sourcePalletNo());
|
||||
String sourcePalletNo = dropshipSourcePallet(
|
||||
session.getSourceContainerBookingId(), session.getContainerNo(), request.sourcePalletNo());
|
||||
if (sourcePalletNo == null) throw notFound("DP or carton is not part of the current container");
|
||||
requireCompleteManifest(session.getContainerNo(), sourcePalletNo);
|
||||
List<DropshipPalletRef> matches = dropshipPalletRefs(session.getContainerNo(), sourcePalletNo);
|
||||
List<DropshipPalletRef> matches = dropshipPalletRefs(
|
||||
session.getSourceContainerBookingId(), session.getContainerNo(), sourcePalletNo);
|
||||
int total = matches.stream().mapToInt(value -> value(value.allocation().getPlannedPackageCount())).sum();
|
||||
requirePalletCapacity(total);
|
||||
boolean alreadyReceived = matches.stream().allMatch(match ->
|
||||
@@ -538,17 +553,17 @@ public class ReceivingContainerService {
|
||||
ReceivingContainerOperation replay = replay(operatorKey, "COMPLETE", request.idempotencyKey(), request);
|
||||
if (replay != null) return read(replay, ReceivingContainerResponses.Session.class);
|
||||
ReceivingContainerSession session = requireSigned(operatorKey);
|
||||
List<InboundPlan> containerPlans = requirePlans(session.getContainerNo());
|
||||
Totals totals = totals(containerPlans);
|
||||
List<InboundPlan> containerPlans = requirePlans(session.getSourceContainerBookingId(), session.getContainerNo());
|
||||
Totals totals = totals(session, containerPlans);
|
||||
boolean hasDifference = totals.total() != totals.received() || hasFbaOverReceipt(session, containerPlans);
|
||||
if (hasDifference && !request.allowDifference()) {
|
||||
throw conflict("Container has a receiving difference; confirm onsite completion with difference");
|
||||
}
|
||||
if (!hasDifference) {
|
||||
validateFbaSkuBalance(session.getContainerNo());
|
||||
validateFbaSkuBalance(session.getSourceContainerBookingId(), session.getContainerNo());
|
||||
workflow.completeContainer(containerPlans);
|
||||
} else {
|
||||
workflow.completeContainerWithDifferences(containerPlans, reconciledPlans(containerPlans));
|
||||
workflow.completeContainerWithDifferences(containerPlans, reconciledPlans(session, containerPlans));
|
||||
}
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
session.setStatus(hasDifference ? "COMPLETED_WITH_DIFFERENCE" : "COMPLETED");
|
||||
@@ -558,23 +573,24 @@ public class ReceivingContainerService {
|
||||
save(session, "COMPLETE", request.idempotencyKey(), request, result); return result;
|
||||
}
|
||||
|
||||
private List<InboundPlan> reconciledPlans(List<InboundPlan> containerPlans) {
|
||||
Set<Long> complete = refs(containerPlans.get(0).getContainerNo()).stream()
|
||||
private List<InboundPlan> reconciledPlans(ReceivingContainerSession session, List<InboundPlan> containerPlans) {
|
||||
List<InboundRef> currentRefs = refs(session.getSourceContainerBookingId(), session.getContainerNo());
|
||||
Set<Long> complete = currentRefs.stream()
|
||||
.collect(Collectors.groupingBy(value -> value.plan().getId(), LinkedHashMap::new, Collectors.toList()))
|
||||
.entrySet().stream().filter(entry -> entry.getValue().stream().allMatch(ref ->
|
||||
receivedLinePackages(ref) == value(ref.line().getRequiredPackageCount())))
|
||||
.map(Map.Entry::getKey).collect(Collectors.toSet());
|
||||
Set<String> incompleteFbaGroups = refs(containerPlans.get(0).getContainerNo()).stream()
|
||||
Set<String> incompleteFbaGroups = currentRefs.stream()
|
||||
.filter(ref -> !"ONE_CLICK_DROPSHIP".equals(ref.order().getOrderType()))
|
||||
.filter(ref -> "FBA".equals(destinationType(ref.line(), ref.order())))
|
||||
.filter(ref -> !complete.contains(ref.plan().getId()))
|
||||
.map(ref -> normalizedFba(ref.line().getFbaWarehouseCode())).collect(Collectors.toSet());
|
||||
refs(containerPlans.get(0).getContainerNo()).stream()
|
||||
currentRefs.stream()
|
||||
.filter(ref -> "FBA".equals(destinationType(ref.line(), ref.order())))
|
||||
.filter(ref -> fbaGroupHasOverReceipt(findSession(containerPlans.get(0).getContainerNo()), ref))
|
||||
.map(ref -> normalizedFba(ref.line().getFbaWarehouseCode())).forEach(incompleteFbaGroups::add);
|
||||
return containerPlans.stream().filter(plan -> complete.contains(plan.getId()))
|
||||
.filter(plan -> refs(plan.getContainerNo()).stream().filter(ref -> Objects.equals(ref.plan().getId(), plan.getId()))
|
||||
.filter(plan -> currentRefs.stream().filter(ref -> Objects.equals(ref.plan().getId(), plan.getId()))
|
||||
.noneMatch(ref -> "FBA".equals(destinationType(ref.line(), ref.order()))
|
||||
&& incompleteFbaGroups.contains(normalizedFba(ref.line().getFbaWarehouseCode()))))
|
||||
.toList();
|
||||
@@ -589,22 +605,23 @@ public class ReceivingContainerService {
|
||||
return session;
|
||||
}
|
||||
|
||||
private ContainerReadiness requireReadyContainer(String containerNo, long overseasWarehouseId, boolean lock) {
|
||||
private ContainerReadiness requireReadyContainer(String containerNo, String sourceContainerBookingId,
|
||||
long overseasWarehouseId, boolean lock) {
|
||||
String bookingId = trim(sourceContainerBookingId);
|
||||
if (bookingId == null) throw conflict("sourceContainerBookingId is required");
|
||||
LambdaQueryWrapper<LastlegOrder> query = new LambdaQueryWrapper<LastlegOrder>()
|
||||
.eq(LastlegOrder::getContainerNo, containerNo).orderByAsc(LastlegOrder::getId);
|
||||
.eq(LastlegOrder::getContainerNo, containerNo)
|
||||
.eq(LastlegOrder::getSourceContainerBookingId, bookingId)
|
||||
.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");
|
||||
}
|
||||
if (orderFacts.isEmpty()) throw notFound("当前柜次来源与集装箱号不匹配");
|
||||
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,
|
||||
ContainerReadiness value = readiness(bookingId, orderFacts, planFacts, lineFacts,
|
||||
overseasWarehouseId);
|
||||
if (value == null || !value.operable()) {
|
||||
throw conflict(value == null ? "当前柜已分配至其他海外接柜仓,无法在本仓操作" : value.blockReason());
|
||||
@@ -614,7 +631,8 @@ public class ReceivingContainerService {
|
||||
|
||||
private void requireStableSessionFacts(ReceivingContainerSession session, boolean lock) {
|
||||
if (trim(session.getContainerFactHash()) == null) return;
|
||||
ContainerReadiness current = requireReadyContainer(session.getContainerNo(), session.getOverseasWarehouseId(), lock);
|
||||
ContainerReadiness current = requireReadyContainer(session.getContainerNo(),
|
||||
session.getSourceContainerBookingId(), 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");
|
||||
@@ -657,10 +675,8 @@ public class ReceivingContainerService {
|
||||
int planCount = matchedPlans.size();
|
||||
boolean allPlansReady = totalOrders > 0 && inboundCodes == totalOrders
|
||||
&& plannedOrders == totalOrders && planCount == totalOrders;
|
||||
List<InboundPlan> containerPlans = planFacts.stream()
|
||||
.filter(plan -> containerNo.equals(plan.getContainerNo())).toList();
|
||||
boolean allPlansCompleted = !containerPlans.isEmpty()
|
||||
&& containerPlans.stream().allMatch(plan -> List.of("COMPLETED", "PENDING_RECONCILIATION")
|
||||
boolean allPlansCompleted = !matchedPlans.isEmpty()
|
||||
&& matchedPlans.stream().allMatch(plan -> List.of("COMPLETED", "PENDING_RECONCILIATION")
|
||||
.contains(plan.getStatus()));
|
||||
boolean shippingReady = shippingGates.receivingAvailable(sourceContainerBookingId, containerNo) == 1;
|
||||
boolean operable = allPlansReady && shippingReady && warehouseSnapshotConsistent;
|
||||
@@ -716,9 +732,12 @@ public class ReceivingContainerService {
|
||||
return session;
|
||||
}
|
||||
|
||||
private List<InboundPlan> requirePlans(String containerNo) {
|
||||
List<InboundPlan> values = plans.selectList(new LambdaQueryWrapper<InboundPlan>()
|
||||
.eq(InboundPlan::getContainerNo, containerNo).orderByAsc(InboundPlan::getId));
|
||||
private List<InboundPlan> requirePlans(String sourceContainerBookingId, String containerNo) {
|
||||
List<InboundPlan> values = refs(sourceContainerBookingId, containerNo).stream()
|
||||
.map(InboundRef::plan)
|
||||
.collect(Collectors.toMap(InboundPlan::getId, Function.identity(), (left, right) -> left,
|
||||
LinkedHashMap::new))
|
||||
.values().stream().toList();
|
||||
if (values.isEmpty()) throw notFound("Receiving container was not found");
|
||||
if (values.stream().anyMatch(value -> trim(value.getCustomerId()) == null)) {
|
||||
throw conflict("Every inbound plan in the receiving container must have an owning customer");
|
||||
@@ -730,32 +749,31 @@ public class ReceivingContainerService {
|
||||
return values;
|
||||
}
|
||||
|
||||
private List<InboundPlan> plansForContainer(String containerNo) {
|
||||
List<InboundPlan> values = plans.selectList(new LambdaQueryWrapper<InboundPlan>()
|
||||
.eq(InboundPlan::getContainerNo, containerNo)
|
||||
.orderByAsc(InboundPlan::getId));
|
||||
if (values.isEmpty()) throw notFound("Receiving container was not found");
|
||||
return values;
|
||||
}
|
||||
|
||||
private List<InboundRef> refs(String containerNo) {
|
||||
private List<InboundRef> refs(String sourceContainerBookingId, String containerNo) {
|
||||
List<InboundRef> result = new ArrayList<>();
|
||||
for (InboundPlan plan : plansForContainer(containerNo)) {
|
||||
List<InboundPlan> containerPlans = plans.selectList(new LambdaQueryWrapper<InboundPlan>()
|
||||
.eq(InboundPlan::getContainerNo, containerNo).orderByAsc(InboundPlan::getId));
|
||||
for (InboundPlan plan : containerPlans) {
|
||||
String customerId = trim(plan.getCustomerId());
|
||||
if (customerId == null) throw conflict("Inbound plan has no owning customer");
|
||||
for (InboundPlanLine line : lines.selectList(new LambdaQueryWrapper<InboundPlanLine>()
|
||||
.eq(InboundPlanLine::getCustomerId, customerId).eq(InboundPlanLine::getPlanId, plan.getId())
|
||||
.orderByAsc(InboundPlanLine::getId))) {
|
||||
LastlegOrder order = orders.selectById(line.getOrderId());
|
||||
if (order == null || !customerId.equals(order.getCustomerId())) throw conflict("Inbound line order is missing");
|
||||
if (order == null || !customerId.equals(order.getCustomerId())
|
||||
|| !Objects.equals(containerNo, trim(order.getContainerNo()))) {
|
||||
throw conflict("Inbound line order is missing or belongs to another cabinet occurrence");
|
||||
}
|
||||
if (!Objects.equals(sourceContainerBookingId, trim(order.getSourceContainerBookingId()))) continue;
|
||||
result.add(new InboundRef(plan, line, order));
|
||||
}
|
||||
}
|
||||
if (result.isEmpty()) throw notFound("Receiving container was not found");
|
||||
return result;
|
||||
}
|
||||
|
||||
private InboundRef requireInbound(String containerNo, String inboundCode) {
|
||||
List<InboundRef> matches = refs(containerNo).stream()
|
||||
private InboundRef requireInbound(String sourceContainerBookingId, String containerNo, String inboundCode) {
|
||||
List<InboundRef> matches = refs(sourceContainerBookingId, containerNo).stream()
|
||||
.filter(value -> inboundCode.equals(value.line().getInboundCode())).toList();
|
||||
if (matches.isEmpty()) throw notFound("Inbound code is not part of the current container");
|
||||
Set<Long> planIds = matches.stream().map(value -> value.plan().getId()).collect(Collectors.toSet());
|
||||
@@ -763,11 +781,12 @@ public class ReceivingContainerService {
|
||||
return matches.stream().min(Comparator.comparing(value -> value.line().getId())).orElseThrow();
|
||||
}
|
||||
|
||||
private CartonRef requireCartonRef(String customerId, String containerNo, String cartonMark) {
|
||||
private CartonRef requireCartonRef(String customerId, String sourceContainerBookingId,
|
||||
String containerNo, String cartonMark) {
|
||||
InboundPlanCarton carton = cartons.selectOne(new LambdaQueryWrapper<InboundPlanCarton>()
|
||||
.eq(InboundPlanCarton::getCustomerId, customerId).eq(InboundPlanCarton::getCartonMark, cartonMark));
|
||||
if (carton == null) throw notFound("Carton mark was not found");
|
||||
List<InboundRef> matches = refs(containerNo).stream()
|
||||
List<InboundRef> matches = refs(sourceContainerBookingId, containerNo).stream()
|
||||
.filter(value -> customerId.equals(value.order().getCustomerId()))
|
||||
.filter(value -> carton.getLineId() == null || Objects.equals(value.line().getId(), carton.getLineId()))
|
||||
.filter(value -> carton.getPlanId() == null || Objects.equals(value.plan().getId(), carton.getPlanId()))
|
||||
@@ -791,8 +810,9 @@ public class ReceivingContainerService {
|
||||
throw notFound("Carton mark was not found");
|
||||
}
|
||||
|
||||
private List<InboundRef> requireFbaPlanRefs(String containerNo, InboundRef selected) {
|
||||
List<InboundRef> values = refs(containerNo).stream()
|
||||
private List<InboundRef> requireFbaPlanRefs(String sourceContainerBookingId,
|
||||
String containerNo, InboundRef selected) {
|
||||
List<InboundRef> values = refs(sourceContainerBookingId, containerNo).stream()
|
||||
.filter(value -> !"ONE_CLICK_DROPSHIP".equals(value.order().getOrderType()))
|
||||
.filter(value -> "FBA".equals(destinationType(value.line(), value.order())))
|
||||
.filter(value -> Objects.equals(normalizedFba(value.line().getFbaWarehouseCode()),
|
||||
@@ -940,8 +960,8 @@ public class ReceivingContainerService {
|
||||
return Objects.equals(line.getDestinationWarehouseCode(), pallet.getDestinationWarehouseCode());
|
||||
}
|
||||
|
||||
private String dropshipSourcePallet(String containerNo, String scanCode) {
|
||||
return dropshipSourcePallet(refs(containerNo), containerNo, scanCode);
|
||||
private String dropshipSourcePallet(String sourceContainerBookingId, String containerNo, String scanCode) {
|
||||
return dropshipSourcePallet(refs(sourceContainerBookingId, containerNo), containerNo, scanCode);
|
||||
}
|
||||
|
||||
private String dropshipSourcePallet(List<InboundRef> currentRefs, String containerNo, String scanCode) {
|
||||
@@ -960,9 +980,10 @@ public class ReceivingContainerService {
|
||||
return resolved.isEmpty() ? null : resolved.get(0);
|
||||
}
|
||||
|
||||
private List<DropshipPalletRef> dropshipPalletRefs(String containerNo, String sourcePalletNo) {
|
||||
private List<DropshipPalletRef> dropshipPalletRefs(String sourceContainerBookingId,
|
||||
String containerNo, String sourcePalletNo) {
|
||||
List<DropshipPalletRef> result = new ArrayList<>();
|
||||
for (InboundRef ref : refs(containerNo)) {
|
||||
for (InboundRef ref : refs(sourceContainerBookingId, containerNo)) {
|
||||
if (!"ONE_CLICK_DROPSHIP".equals(ref.order().getOrderType())) continue;
|
||||
List<InboundPlanPallet> sourcePallets = pallets.selectList(new LambdaQueryWrapper<InboundPlanPallet>()
|
||||
.eq(InboundPlanPallet::getCustomerId, ref.order().getCustomerId())
|
||||
@@ -1009,7 +1030,8 @@ public class ReceivingContainerService {
|
||||
InboundPlanPalletAllocation allocation) { }
|
||||
|
||||
private ReceivingContainerResponses.Session snapshot(ReceivingContainerSession session, List<InboundPlan> containerPlans) {
|
||||
List<ReceivingContainerResponses.Inbound> inbounds = refs(session.getContainerNo()).stream()
|
||||
List<ReceivingContainerResponses.Inbound> inbounds = refs(
|
||||
session.getSourceContainerBookingId(), session.getContainerNo()).stream()
|
||||
.map(this::inbound).toList();
|
||||
int total = inbounds.stream().mapToInt(ReceivingContainerResponses.Inbound::totalPackages).sum();
|
||||
int received = inbounds.stream().mapToInt(ReceivingContainerResponses.Inbound::receivedPackages).sum();
|
||||
@@ -1065,7 +1087,7 @@ public class ReceivingContainerService {
|
||||
|
||||
private boolean hasFbaOverReceipt(ReceivingContainerSession session, List<InboundPlan> containerPlans) {
|
||||
if (session == null || containerPlans == null || containerPlans.isEmpty()) return false;
|
||||
return refs(session.getContainerNo()).stream()
|
||||
return refs(session.getSourceContainerBookingId(), session.getContainerNo()).stream()
|
||||
.filter(ref -> !"ONE_CLICK_DROPSHIP".equals(ref.order().getOrderType()))
|
||||
.filter(ref -> "FBA".equals(destinationType(ref.line(), ref.order())))
|
||||
.anyMatch(ref -> fbaGroupHasOverReceipt(session, ref));
|
||||
@@ -1073,7 +1095,7 @@ public class ReceivingContainerService {
|
||||
|
||||
private boolean fbaGroupHasOverReceipt(ReceivingContainerSession session, InboundRef ref) {
|
||||
if (session == null || session.getId() == null) return false;
|
||||
int planned = requireFbaPlanRefs(session.getContainerNo(), ref).stream()
|
||||
int planned = requireFbaPlanRefs(session.getSourceContainerBookingId(), session.getContainerNo(), ref).stream()
|
||||
.mapToInt(value -> value(value.line().getRequiredPackageCount())).sum();
|
||||
return fbaPalletCounts.sumActual(session.getId(), fbaGroupKey(session.getContainerNo(), ref)) > planned;
|
||||
}
|
||||
@@ -1115,8 +1137,9 @@ public class ReceivingContainerService {
|
||||
return mergeAllowed(plan) ? PALLET_MERGE_HINT : null;
|
||||
}
|
||||
|
||||
private Totals totals(List<InboundPlan> containerPlans) {
|
||||
List<ReceivingContainerResponses.Inbound> values = refs(containerPlans.get(0).getContainerNo())
|
||||
private Totals totals(ReceivingContainerSession session, List<InboundPlan> containerPlans) {
|
||||
List<ReceivingContainerResponses.Inbound> values = refs(
|
||||
session.getSourceContainerBookingId(), session.getContainerNo())
|
||||
.stream().map(this::inbound).toList();
|
||||
List<String> destinationCodes = values.stream().map(this::destinationCode)
|
||||
.filter(Objects::nonNull).distinct().sorted().toList();
|
||||
@@ -1246,8 +1269,8 @@ public class ReceivingContainerService {
|
||||
}
|
||||
}
|
||||
|
||||
private void validateFbaSkuBalance(String containerNo) {
|
||||
for (InboundRef ref : refs(containerNo)) {
|
||||
private void validateFbaSkuBalance(String sourceContainerBookingId, String containerNo) {
|
||||
for (InboundRef ref : refs(sourceContainerBookingId, containerNo)) {
|
||||
if (!"FBA".equals(destinationType(ref.line(), ref.order()))) continue;
|
||||
if ("ONE_CLICK_DROPSHIP".equals(ref.order().getOrderType())) continue;
|
||||
String customerId = ref.order().getCustomerId();
|
||||
@@ -1296,12 +1319,14 @@ public class ReceivingContainerService {
|
||||
|
||||
private ReceivingContainerSession waitingView(String operatorKey,
|
||||
WarehouseResourceGateway.WarehouseReference warehouse,
|
||||
String sourceContainerBookingId,
|
||||
String containerNo) {
|
||||
ReceivingContainerSession value = new ReceivingContainerSession();
|
||||
value.setOverseasWarehouseId(warehouse.warehouseId());
|
||||
value.setOverseasWarehouseCode(warehouse.warehouseCode());
|
||||
value.setOverseasWarehouseName(warehouse.warehouseName());
|
||||
value.setOperatorKey(operatorKey); value.setContainerNo(containerNo); value.setStatus("WAIT_SIGN"); value.setVersion(0);
|
||||
value.setOperatorKey(operatorKey); value.setSourceContainerBookingId(sourceContainerBookingId);
|
||||
value.setContainerNo(containerNo); value.setStatus("WAIT_SIGN"); value.setVersion(0);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -1411,5 +1436,5 @@ public class ReceivingContainerService {
|
||||
int planCount, int plannedInboundCodeCount, int pendingPlanCount,
|
||||
boolean allPlansReady, boolean allPlansCompleted, boolean shippingReady, boolean operable, String blockReason,
|
||||
LocalDateTime plannedArrivalAt, String factHash, Totals totals) { }
|
||||
private record PrepareCommand(String containerNo, String idempotencyKey) { }
|
||||
private record PrepareCommand(String sourceContainerBookingId, String containerNo, String idempotencyKey) { }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user