优化
This commit is contained in:
+24
-4
@@ -229,7 +229,7 @@ public class ReceivingContainerService {
|
||||
CartonRef carton = requireCartonRef(customerId, session.getContainerNo(), request.cartonMark());
|
||||
InboundRef ref = carton.ref();
|
||||
ReceivingContainerResponses.Inbound inbound = inbound(ref);
|
||||
return new ReceivingContainerResponses.CartonResolved(carton.carton().getCartonMark(),
|
||||
return new ReceivingContainerResponses.CartonResolved(request.cartonMark().trim(),
|
||||
inbound.inboundCode(), inbound.destinationType(), inbound.overseasWarehouseId(),
|
||||
inbound.overseasWarehouseName(), inbound.destinationWarehouseId(),
|
||||
inbound.destinationWarehouseCode(), inbound.destinationWarehouseName(), inbound.fbaWarehouseCode(),
|
||||
@@ -423,13 +423,33 @@ public class ReceivingContainerService {
|
||||
private CartonRef requireCartonRef(String customerId, 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");
|
||||
if (carton == null) return resolveFbaCartonCodeWithoutManifest(customerId, containerNo, cartonMark);
|
||||
List<InboundRef> matches = refs(customerId, containerNo).stream()
|
||||
.filter(value -> carton.getLineId() == null || Objects.equals(value.line().getId(), carton.getLineId()))
|
||||
.filter(value -> carton.getPlanId() == null || Objects.equals(value.plan().getId(), carton.getPlanId()))
|
||||
.filter(value -> Objects.equals(value.line().getOrderId(), carton.getOrderId())).toList();
|
||||
if (matches.size() != 1) throw conflict("Carton mark belongs to another container");
|
||||
return new CartonRef(carton, matches.get(0));
|
||||
return new CartonRef(matches.get(0));
|
||||
}
|
||||
|
||||
private CartonRef resolveFbaCartonCodeWithoutManifest(String customerId, String containerNo, String cartonMark) {
|
||||
int delimiter = cartonMark == null ? -1 : cartonMark.indexOf('#');
|
||||
if (delimiter < 1) throw conflict("Carton mark must contain a non-empty inbound code prefix");
|
||||
String inboundCode = cartonMark.substring(0, delimiter).trim();
|
||||
if (inboundCode.isEmpty()) throw conflict("Carton mark must contain a non-empty inbound code prefix");
|
||||
List<InboundRef> matches = refs(customerId, containerNo).stream()
|
||||
.filter(value -> inboundCode.equals(value.line().getInboundCode())).toList();
|
||||
if (matches.isEmpty()) {
|
||||
boolean exists = lines.selectList(new LambdaQueryWrapper<InboundPlanLine>()
|
||||
.eq(InboundPlanLine::getCustomerId, customerId)
|
||||
.eq(InboundPlanLine::getInboundCode, inboundCode)).stream().findAny().isPresent();
|
||||
if (exists) throw conflict("Carton mark belongs to another container");
|
||||
throw notFound("Carton mark was not found");
|
||||
}
|
||||
if (matches.size() != 1) throw conflict("Carton mark inbound code is ambiguous in the current container");
|
||||
InboundRef ref = matches.get(0);
|
||||
if (!"FBA".equals(destinationType(ref.line(), ref.order()))) throw notFound("Carton mark was not found");
|
||||
return new CartonRef(ref);
|
||||
}
|
||||
|
||||
private List<InboundRef> requireFbaPlanRefs(String customerId, String containerNo, InboundRef selected) {
|
||||
@@ -806,7 +826,7 @@ public class ReceivingContainerService {
|
||||
private static ResponseStatusException notFound(String message) { return new ResponseStatusException(HttpStatus.NOT_FOUND, message); }
|
||||
|
||||
private record InboundRef(InboundPlan plan, InboundPlanLine line, LastlegOrder order) { }
|
||||
private record CartonRef(InboundPlanCarton carton, InboundRef ref) { }
|
||||
private record CartonRef(InboundRef ref) { }
|
||||
private record SkuExpectation(String skuCode, String productName, int expectedPackages) { }
|
||||
private record FbaSkuAllocation(InboundRef ref, String skuCode, String productName, int packages) { }
|
||||
private record Totals(int inboundCount, List<String> destinationCodes, int plannedPalletCount,
|
||||
|
||||
@@ -72,10 +72,10 @@ paths:
|
||||
/api/pda/v1/last-leg/receiving-containers/current/cartons/resolve:
|
||||
post:
|
||||
summary: 解析当前柜箱唛
|
||||
description: 权限 lastleg:pda:operate;FBA 和 OVERSEAS 均可解析当前锁定柜内箱码,返回唯一入仓码、目的类型、目标仓和实收进度;不返回 SKU。
|
||||
description: 权限 lastleg:pda:operate;FBA 从完整 cartonMark 的第一个 # 前提取固定入仓码,并精确匹配当前锁定柜唯一 FBA line;不要求逐箱 manifest,不校验 SKU 或箱序号。OVERSEAS 必须命中真实 manifest。两者均不返回 SKU,也不登记收货。
|
||||
security: [{ bearerAuth: [] }]
|
||||
requestBody: { required: true, content: { application/json: { schema: { type: object, required: [cartonMark], properties: { cartonMark: { type: string } } } } } }
|
||||
responses: { '200': { description: 箱唛与入仓码信息, content: { application/json: { schema: { $ref: '#/components/schemas/ReceivingCartonResolvedApiResponse' } } } }, '409': { description: 箱码跨柜或归属不唯一 } }
|
||||
responses: { '200': { description: 箱唛与入仓码信息, content: { application/json: { schema: { $ref: '#/components/schemas/ReceivingCartonResolvedApiResponse' } } } }, '404': { description: 入仓码未命中当前柜,或 OVERSEAS 箱码未命中真实 manifest }, '409': { description: 缺少 #、空入仓码前缀、跨柜或当前柜多重命中 } }
|
||||
/api/pda/v1/last-leg/receiving-containers/current/pallets/resolve:
|
||||
post:
|
||||
summary: 解析当前柜实物托盘码
|
||||
|
||||
+3
-3
@@ -100,10 +100,10 @@ class ReceivingContainerControllerTest {
|
||||
@Test
|
||||
void fbaCartonResolutionDelegatesWithoutControllerModeRejection() {
|
||||
ReceivingContainerRequests.CartonResolve request =
|
||||
new ReceivingContainerRequests.CartonResolve("NYEXX2608260002#4-1");
|
||||
new ReceivingContainerRequests.CartonResolve("NYEXX2608260001#4-1");
|
||||
when(service.resolveCarton("42", request)).thenReturn(new ReceivingContainerResponses.CartonResolved(
|
||||
"NYEXX2608260002#4-1", "IN-1", "FBA", "900", "Shared LAX", null, null, null,
|
||||
"ABE3", 1, 40, 0, 40));
|
||||
"NYEXX2608260001#4-1", "NYEXX2608260001", "FBA", "900", "Shared LAX", null, null, null,
|
||||
"ABE3", 1, 4, 0, 4));
|
||||
|
||||
controller.resolveCarton(picker(), request);
|
||||
|
||||
|
||||
+59
-5
@@ -406,15 +406,69 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void fbaCartonResolutionForNyexx2608260002DoesNotApplyReceiptModeGate() {
|
||||
void fbaCartonResolutionWithoutManifestForNyexx2608260001DoesNotWriteReceiptState() {
|
||||
stubCurrent("FBA", "CONT-A");
|
||||
when(cartons.selectOne(any())).thenReturn(carton(1L, "NYEXX2608260002#4-1"));
|
||||
InboundPlanLine fba = line("FBA");
|
||||
fba.setInboundCode("NYEXX2608260001"); fba.setRequiredPackageCount(4);
|
||||
when(lines.selectList(any())).thenReturn(List.of(fba));
|
||||
|
||||
var resolved = service.resolveCarton("7", new ReceivingContainerRequests.CartonResolve("NYEXX2608260002#4-1"));
|
||||
var resolved = service.resolveCarton("7", new ReceivingContainerRequests.CartonResolve("NYEXX2608260001#4-1"));
|
||||
var replay = service.resolveCarton("7", new ReceivingContainerRequests.CartonResolve("NYEXX2608260001#4-1"));
|
||||
|
||||
assertThat(resolved.cartonMark()).isEqualTo("NYEXX2608260002#4-1");
|
||||
assertThat(resolved.inboundCode()).isEqualTo("IN-1");
|
||||
assertThat(resolved.cartonMark()).isEqualTo("NYEXX2608260001#4-1");
|
||||
assertThat(resolved.inboundCode()).isEqualTo("NYEXX2608260001");
|
||||
assertThat(resolved.destinationType()).isEqualTo("FBA");
|
||||
assertThat(replay).isEqualTo(resolved);
|
||||
verifyNoInteractions(workflow, operations);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fbaCartonResolutionRejectsCrossContainerAndInvalidPrefixesAsConflicts() {
|
||||
stubCurrent("FBA", "CONT-A");
|
||||
InboundPlanLine current = line("FBA");
|
||||
current.setInboundCode("NYEXX2608260001"); current.setRequiredPackageCount(4);
|
||||
InboundPlanLine foreign = line("FBA");
|
||||
foreign.setInboundCode("NYEXX2608269999"); foreign.setPlanId(99L);
|
||||
when(lines.selectList(any())).thenReturn(List.of(current), List.of(foreign));
|
||||
|
||||
assertThatThrownBy(() -> service.resolveCarton("7",
|
||||
new ReceivingContainerRequests.CartonResolve("NYEXX2608269999#4-1")))
|
||||
.isInstanceOfSatisfying(ResponseStatusException.class,
|
||||
error -> assertThat(error.getStatusCode().value()).isEqualTo(409));
|
||||
assertThatThrownBy(() -> service.resolveCarton("7",
|
||||
new ReceivingContainerRequests.CartonResolve("NYEXX2608260001")))
|
||||
.isInstanceOfSatisfying(ResponseStatusException.class,
|
||||
error -> assertThat(error.getStatusCode().value()).isEqualTo(409));
|
||||
assertThatThrownBy(() -> service.resolveCarton("7",
|
||||
new ReceivingContainerRequests.CartonResolve("#4-1")))
|
||||
.isInstanceOfSatisfying(ResponseStatusException.class,
|
||||
error -> assertThat(error.getStatusCode().value()).isEqualTo(409));
|
||||
|
||||
org.mockito.Mockito.reset(lines, cartons);
|
||||
when(lines.selectList(any())).thenReturn(List.of(current), List.of());
|
||||
assertThatThrownBy(() -> service.resolveCarton("7",
|
||||
new ReceivingContainerRequests.CartonResolve("UNKNOWN#5-1")))
|
||||
.isInstanceOfSatisfying(ResponseStatusException.class,
|
||||
error -> assertThat(error.getStatusCode().value()).isEqualTo(404));
|
||||
|
||||
InboundPlanLine duplicate = line("FBA");
|
||||
duplicate.setId(20L); duplicate.setInboundCode("NYEXX2608260001");
|
||||
org.mockito.Mockito.reset(lines);
|
||||
when(lines.selectList(any())).thenReturn(List.of(current, duplicate));
|
||||
assertThatThrownBy(() -> service.resolveCarton("7",
|
||||
new ReceivingContainerRequests.CartonResolve("NYEXX2608260001#any-tail")))
|
||||
.isInstanceOfSatisfying(ResponseStatusException.class,
|
||||
error -> assertThat(error.getStatusCode().value()).isEqualTo(409));
|
||||
}
|
||||
|
||||
@Test
|
||||
void overseasCartonResolutionWithoutManifestIsNotFound() {
|
||||
stubCurrent("OVERSEAS", "CONT-A");
|
||||
|
||||
assertThatThrownBy(() -> service.resolveCarton("7",
|
||||
new ReceivingContainerRequests.CartonResolve("IN-1#2-1")))
|
||||
.isInstanceOfSatisfying(ResponseStatusException.class,
|
||||
error -> assertThat(error.getStatusCode().value()).isEqualTo(404));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user