优化
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) { }
|
||||
}
|
||||
|
||||
@@ -42,16 +42,20 @@ paths:
|
||||
/api/pda/v1/last-leg/receiving-containers/{containerNo}:
|
||||
get:
|
||||
summary: 查询待签收柜号详情
|
||||
description: 权限 lastleg:pda:operate;按来源柜返回跨客户的全部入仓码、目的类型、目的仓、托数和实收进度;每个入仓码仍按自身客户隔离。
|
||||
description: 权限 lastleg:pda:operate;sourceContainerBookingId 与真实集装箱号共同锁定本次柜次,允许物理集装箱号在不同柜次重复使用;按来源柜返回跨客户的全部入仓码、目的类型、目的仓、托数和实收进度。
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ name: containerNo, in: path, required: true, schema: { type: string } }]
|
||||
parameters:
|
||||
- { name: containerNo, in: path, required: true, schema: { type: string } }
|
||||
- { name: sourceContainerBookingId, in: query, required: true, schema: { type: string, minLength: 1, maxLength: 128 } }
|
||||
responses: { '200': { description: 柜号详情, content: { application/json: { schema: { $ref: '#/components/schemas/ReceivingSessionApiResponse' } } } }, '409': { description: 当前账号已锁定其他柜号 } }
|
||||
/api/pda/v1/last-leg/receiving-containers/{containerNo}/prepare:
|
||||
post:
|
||||
summary: 选择当前待签收柜号
|
||||
description: 权限 lastleg:pda:operate;仅 RECEIVING_AVAILABLE 柜可建立 WAIT_SIGN 会话,并以平台操作账号、柜号全局双唯一键防止切柜或多人同时处理。
|
||||
description: 权限 lastleg:pda:operate;sourceContainerBookingId 与真实集装箱号共同锁定本次柜次;仅 RECEIVING_AVAILABLE 柜可建立 WAIT_SIGN 会话,并以平台操作账号、当前作业柜全局双唯一键防止切柜或多人同时处理。
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ name: containerNo, in: path, required: true, schema: { type: string } }]
|
||||
parameters:
|
||||
- { name: containerNo, in: path, required: true, schema: { type: string } }
|
||||
- { name: sourceContainerBookingId, in: query, required: true, schema: { type: string, minLength: 1, maxLength: 128 } }
|
||||
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ReceivingAction' } } } }
|
||||
responses: { '200': { description: 当前柜会话, content: { application/json: { schema: { $ref: '#/components/schemas/ReceivingSessionApiResponse' } } } }, '409': { description: 账号或柜号已有未完成会话 } }
|
||||
/api/pda/v1/last-leg/receiving-containers/current/unload-photos/files:
|
||||
|
||||
+56
-12
@@ -117,7 +117,7 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
when(shippingGates.receivingAvailable("BOOKING-A", "CONT-A")).thenReturn(0);
|
||||
|
||||
assertThat(service.candidates("7", 100L)).isEmpty();
|
||||
assertThatThrownBy(() -> service.prepare("7", "receiver", 100L, "CONT-A",
|
||||
assertThatThrownBy(() -> service.prepare("7", "receiver", 100L, "CONT-A", "BOOKING-A",
|
||||
new ReceivingContainerRequests.Action("not-ready")))
|
||||
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("409");
|
||||
verifyNoInteractions(warehouse);
|
||||
@@ -133,7 +133,7 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
when(sessions.insert(any(ReceivingContainerSession.class))).thenReturn(1);
|
||||
when(operations.insert(any(ReceivingContainerOperation.class))).thenReturn(1);
|
||||
when(warehouse.requireActiveSharedWarehouse(100L)).thenReturn(warehouseReference());
|
||||
var response = service.prepare("7", "receiver", 100L, "CONT-A",
|
||||
var response = service.prepare("7", "receiver", 100L, "CONT-A", "BOOKING-A",
|
||||
new ReceivingContainerRequests.Action("same-key"));
|
||||
assertThat(response.overseasWarehouseId()).isEqualTo(100L);
|
||||
assertThat(response.overseasWarehouseName()).isEqualTo("Shared LAX");
|
||||
@@ -145,7 +145,7 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
verify(operations).insert(saved.capture());
|
||||
when(operations.find("7", "same-key")).thenReturn(saved.getValue());
|
||||
|
||||
assertThatThrownBy(() -> service.prepare("7", "receiver", 100L, "CONT-B",
|
||||
assertThatThrownBy(() -> service.prepare("7", "receiver", 100L, "CONT-B", "BOOKING-A",
|
||||
new ReceivingContainerRequests.Action("same-key"))).isInstanceOf(ResponseStatusException.class)
|
||||
.hasMessageContaining("409");
|
||||
}
|
||||
@@ -249,13 +249,44 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
when(warehouse.requireActiveSharedWarehouse(100L)).thenReturn(warehouseReference());
|
||||
|
||||
var candidate = service.candidates("7", 100L).get(0);
|
||||
var detail = service.detail("7", 100L, "TSTU0000204");
|
||||
var detail = service.detail("7", 100L, "TSTU0000204", "BOOKING-204");
|
||||
|
||||
assertThat(candidate.operable()).isTrue();
|
||||
assertThat(detail.overseasWarehouseId()).isEqualTo(100L);
|
||||
assertThat(detail.overseasWarehouseCode()).isEqualTo("SHARED-LAX");
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailUsesBookingOccurrenceWhenPhysicalContainerNumberIsReused() {
|
||||
InboundPlan historicalPlan = plan("VAXU2748524");
|
||||
historicalPlan.setId(2L); historicalPlan.setPlanNo("PLAN-OLD"); historicalPlan.setStatus("COMPLETED");
|
||||
InboundPlan currentPlan = plan("VAXU2748524");
|
||||
currentPlan.setId(3L); currentPlan.setPlanNo("PLAN-CURRENT");
|
||||
InboundPlanLine historicalLine = line("FBA");
|
||||
historicalLine.setId(10L); historicalLine.setPlanId(2L); historicalLine.setOrderId(1L);
|
||||
historicalLine.setInboundCode("IN-OLD");
|
||||
InboundPlanLine currentLine = line("FBA");
|
||||
currentLine.setId(11L); currentLine.setPlanId(3L); currentLine.setOrderId(2L);
|
||||
currentLine.setInboundCode("IN-CURRENT");
|
||||
LastlegOrder historicalOrder = containerOrder(
|
||||
1L, "100", "VAXU2748524", "BOOKING-OLD", "IN-OLD");
|
||||
LastlegOrder currentOrder = containerOrder(
|
||||
2L, "100", "VAXU2748524", "BOOKING-CURRENT", "IN-CURRENT");
|
||||
when(plans.selectList(any())).thenReturn(List.of(historicalPlan, currentPlan));
|
||||
when(lines.selectList(any())).thenReturn(
|
||||
List.of(currentLine),
|
||||
List.of(historicalLine), List.of(currentLine),
|
||||
List.of(historicalLine), List.of(currentLine));
|
||||
when(orders.selectList(any())).thenReturn(List.of(currentOrder));
|
||||
when(orders.selectById(1L)).thenReturn(historicalOrder);
|
||||
when(orders.selectById(2L)).thenReturn(currentOrder);
|
||||
when(warehouse.requireActiveSharedWarehouse(100L)).thenReturn(warehouseReference());
|
||||
|
||||
var detail = service.detail("7", 100L, "VAXU2748524", "BOOKING-CURRENT");
|
||||
|
||||
assertThat(detail.inbounds()).extracting("inboundCode").containsExactly("IN-CURRENT");
|
||||
}
|
||||
|
||||
@Test
|
||||
void prepareAtomicallyBindsUnassignedPlansToThePickerWarehouse() {
|
||||
InboundPlan unassigned = plan("TSTU0195472");
|
||||
@@ -269,7 +300,7 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
when(orders.selectById(1L)).thenReturn(order);
|
||||
when(warehouse.requireActiveSharedWarehouse(100L)).thenReturn(warehouseReference());
|
||||
|
||||
var result = service.prepare("7", "receiver", 100L, "TSTU0195472",
|
||||
var result = service.prepare("7", "receiver", 100L, "TSTU0195472", "BOOKING-472",
|
||||
new ReceivingContainerRequests.Action("bind-warehouse"));
|
||||
|
||||
assertThat(result.overseasWarehouseId()).isEqualTo(100L);
|
||||
@@ -285,9 +316,10 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
when(plans.selectList(any())).thenReturn(List.of(plan));
|
||||
when(lines.selectList(any())).thenReturn(List.of(planLine));
|
||||
when(orders.selectList(any())).thenReturn(List.of(order));
|
||||
when(orders.selectById(1L)).thenReturn(order);
|
||||
when(pallets.selectCount(any())).thenReturn(0L);
|
||||
|
||||
assertThatThrownBy(() -> service.prepare("7", "receiver", 100L, "TSTU0195472",
|
||||
assertThatThrownBy(() -> service.prepare("7", "receiver", 100L, "TSTU0195472", "BOOKING-472",
|
||||
new ReceivingContainerRequests.Action("missing-real-pallets")))
|
||||
.isInstanceOf(ResponseStatusException.class)
|
||||
.hasMessageContaining("尚未完整绑定真实托盘");
|
||||
@@ -339,7 +371,7 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
assertThat(candidate.pendingPlanCount()).isEqualTo(1);
|
||||
assertThat(candidate.operable()).isFalse();
|
||||
assertThat(candidate.blockReason()).isEqualTo("1个入仓码已生成计划,1个订单未操作/缺入仓码");
|
||||
assertThatThrownBy(() -> service.prepare("7", "receiver", 100L, "CONT-A",
|
||||
assertThatThrownBy(() -> service.prepare("7", "receiver", 100L, "CONT-A", "BOOKING-A",
|
||||
new ReceivingContainerRequests.Action("partial")))
|
||||
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("409");
|
||||
verify(sessions, never()).insert(any(ReceivingContainerSession.class));
|
||||
@@ -351,12 +383,14 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
when(lines.selectList(any())).thenReturn(List.of(line("FBA")));
|
||||
when(orders.selectList(any())).thenReturn(List.of(
|
||||
containerOrder(1L, "100", "CONT-B", "BOOKING-B", "IN-1")));
|
||||
when(orders.selectById(1L)).thenReturn(
|
||||
containerOrder(1L, "100", "CONT-B", "BOOKING-B", "IN-1"));
|
||||
ReceivingContainerSession other = session("CONT-B", "SIGNED");
|
||||
other.setOperatorKey("8"); other.setActiveOperatorKey("OPERATOR:8");
|
||||
other.setActiveContainerKey("CONTAINER:CONT-B");
|
||||
when(sessions.findActiveByContainer("CONTAINER:CONT-B")).thenReturn(other);
|
||||
|
||||
assertThatThrownBy(() -> service.detail("7", 100L, "CONT-B"))
|
||||
assertThatThrownBy(() -> service.detail("7", 100L, "CONT-B", "BOOKING-B"))
|
||||
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("409");
|
||||
verifyNoInteractions(warehouse);
|
||||
}
|
||||
@@ -367,8 +401,10 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
when(lines.selectList(any())).thenReturn(List.of(line("FBA")));
|
||||
when(orders.selectList(any())).thenReturn(List.of(
|
||||
containerOrder(1L, "100", "CONT-B", "BOOKING-B", "IN-1")));
|
||||
when(orders.selectById(1L)).thenReturn(
|
||||
containerOrder(1L, "100", "CONT-B", "BOOKING-B", "IN-1"));
|
||||
when(sessions.findActiveByOperator("OPERATOR:7")).thenReturn(session("CONT-A", "WAIT_SIGN"));
|
||||
assertThatThrownBy(() -> service.detail("7", 100L, "CONT-B"))
|
||||
assertThatThrownBy(() -> service.detail("7", 100L, "CONT-B", "BOOKING-B"))
|
||||
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("409");
|
||||
}
|
||||
|
||||
@@ -378,14 +414,15 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
when(lines.selectList(any())).thenReturn(List.of(line("FBA")));
|
||||
LastlegOrder order = containerOrder(1L, "100", "CONT-B", "BOOKING-B", "IN-1");
|
||||
when(orders.selectList(any())).thenReturn(List.of(order));
|
||||
when(orders.selectById(1L)).thenReturn(order);
|
||||
when(sessions.lockActiveByOperator("OPERATOR:7")).thenReturn(session("CONT-A", "WAIT_SIGN"));
|
||||
assertThatThrownBy(() -> service.prepare("7", "receiver", 100L, "CONT-B",
|
||||
assertThatThrownBy(() -> service.prepare("7", "receiver", 100L, "CONT-B", "BOOKING-B",
|
||||
new ReceivingContainerRequests.Action("account-lock"))).isInstanceOf(ResponseStatusException.class);
|
||||
verify(sessions, never()).insert(any(ReceivingContainerSession.class));
|
||||
|
||||
org.mockito.Mockito.reset(sessions, operations);
|
||||
when(sessions.lockActiveByContainer("CONTAINER:CONT-B")).thenReturn(session("CONT-B", "WAIT_SIGN"));
|
||||
assertThatThrownBy(() -> service.prepare("7", "receiver", 100L, "CONT-B",
|
||||
assertThatThrownBy(() -> service.prepare("7", "receiver", 100L, "CONT-B", "BOOKING-B",
|
||||
new ReceivingContainerRequests.Action("container-lock"))).isInstanceOf(ResponseStatusException.class);
|
||||
verify(sessions, never()).insert(any(ReceivingContainerSession.class));
|
||||
}
|
||||
@@ -675,7 +712,8 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
void signRequiresEachTypedPhotoExactlyOnceAndRestoresRoles() {
|
||||
when(sessions.lockActiveByOperator("OPERATOR:7")).thenReturn(session("CONT-A", "WAIT_SIGN"));
|
||||
when(plans.selectList(any())).thenReturn(List.of(plan("CONT-A")));
|
||||
when(lines.selectList(any())).thenReturn(List.of());
|
||||
when(lines.selectList(any())).thenReturn(List.of(line("FBA")));
|
||||
when(orders.selectById(1L)).thenReturn(order("FBA"));
|
||||
when(sessions.updateById(any(ReceivingContainerSession.class))).thenReturn(1);
|
||||
when(operations.insert(any(ReceivingContainerOperation.class))).thenReturn(1);
|
||||
ReceivingContainerRequests.Sign request = new ReceivingContainerRequests.Sign(List.of(
|
||||
@@ -1046,9 +1084,12 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
InboundPlan plan = plan(containerNo);
|
||||
InboundPlanLine line = line(destinationType);
|
||||
LastlegOrder order = order(destinationType);
|
||||
order.setContainerNo(containerNo); order.setSourceContainerBookingId("BOOKING-A");
|
||||
order.setInboundCode("IN-1");
|
||||
when(sessions.lockActiveByOperator("OPERATOR:7")).thenReturn(session(containerNo, "SIGNED"));
|
||||
when(plans.selectList(any())).thenReturn(List.of(plan));
|
||||
when(lines.selectList(any())).thenReturn(List.of(line));
|
||||
org.mockito.Mockito.lenient().when(orders.selectList(any())).thenReturn(List.of(order));
|
||||
when(orders.selectById(1L)).thenReturn(order);
|
||||
}
|
||||
|
||||
@@ -1076,6 +1117,8 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
private static LastlegOrder order(String destinationType) {
|
||||
LastlegOrder value = new LastlegOrder();
|
||||
value.setId(1L); value.setCustomerId("100"); value.setDestinationType(destinationType);
|
||||
value.setContainerNo("CONT-A"); value.setSourceContainerBookingId("BOOKING-A");
|
||||
value.setInboundCode("IN-1");
|
||||
value.setOverseasWarehouseId("100"); value.setOverseasWarehouseCode("SHARED-LAX");
|
||||
value.setOverseasWarehouseName("Shared LAX");
|
||||
value.setFbaWarehouseCode("FBA".equals(destinationType) ? "FBA-1" : null);
|
||||
@@ -1100,6 +1143,7 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
private static ReceivingContainerSession session(String containerNo, String status) {
|
||||
ReceivingContainerSession value = new ReceivingContainerSession();
|
||||
value.setId(8L); value.setContainerNo(containerNo); value.setOperatorKey("7");
|
||||
value.setSourceContainerBookingId("BOOKING-A");
|
||||
value.setOverseasWarehouseId(100L); value.setOverseasWarehouseCode("SHARED-LAX");
|
||||
value.setOverseasWarehouseName("Shared LAX");
|
||||
value.setStatus(status); value.setVersion(0);
|
||||
|
||||
Reference in New Issue
Block a user