贴标申请,仓储转运

This commit is contained in:
2026-09-17 17:37:29 +08:00
parent 3b8b6f5aa2
commit 29f05b7b09
106 changed files with 5019 additions and 471 deletions
@@ -15,7 +15,8 @@ class DispatchCandidateMapperContractTest {
"planNo", "orderType", "volumeWeightKg", "containerNo", "appointmentAt", "expectedLoadingAt",
"overseasWarehouseId", "overseasWarehouseName", "orderNo", "orderRefs", "inboundCode", "palletNo",
"destinationWarehouseId", "destinationWarehouseCode", "destinationWarehouseName", "fbaWarehouseCode",
"packageCount", "volumeCbm", "sourceStage", "cartonMarks", "relabelCartonMarks"
"packageCount", "volumeCbm", "sourceStage", "cartonMarks", "relabelCartonMarks",
"destinationType", "deliveryAddress", "postalCode", "contactName", "contactPhone"
);
@Test
@@ -37,6 +38,29 @@ class DispatchCandidateMapperContractTest {
assertTrue(sql.contains("pos.available_packages > 0"));
}
@Test
void candidatesWithoutFbaWarehouseAreExcludedFromReadAndWriteQueries() throws Exception {
for (Method method : List.of(
DispatchCandidateMapper.class.getMethod("available", String.class, List.class, String.class,
String.class, String.class, String.class),
DispatchCandidateMapper.class.getMethod("lockSelected", String.class, List.class))) {
String sql = String.join(" ", method.getAnnotation(Select.class).value());
assertTrue(sql.contains("NULLIF(TRIM(l.fba_warehouse_code),'') IS NOT NULL"));
}
}
@Test
void candidateKeywordSearchIncludesSourceInboundCode() throws Exception {
Method available = DispatchCandidateMapper.class.getMethod("available", String.class, List.class,
String.class, String.class, String.class, String.class);
String availableSql = String.join(" ", available.getAnnotation(Select.class).value());
assertTrue(availableSql.contains("o.order_no LIKE CONCAT('%',#{orderNo},'%') OR l.inbound_code LIKE CONCAT('%',#{orderNo},'%')"));
Method relabel = DispatchCandidateMapper.class.getMethod("relabelAvailable", String.class, String.class, String.class);
String relabelSql = String.join(" ", relabel.getAnnotation(Select.class).value());
assertTrue(relabelSql.contains("ri.source_inbound_code LIKE CONCAT('%',#{orderNo},'%')"));
}
@Test
void candidateWarehouseComesFromPhysicalInventoryOrFrozenInboundPlan() throws Exception {
for (Method method : List.of(
@@ -190,6 +190,22 @@ class DispatchServiceTest {
verify(orders, never()).insert(any(DispatchOrder.class));
}
@Test
void preArrangeRejectsPalletWithoutFbaWarehouse() {
DispatchPalletCandidate missingFba = new DispatchPalletCandidate(1L, 10L, 20L, 2L,
"WH-1", "Overseas warehouse", "LMO1", "LMO1", "RK1", "LIP1-P001",
null, null, null, null, 12, new BigDecimal("1.2000"), "RECEIVED");
when(candidates.lockSelected("100", List.of(10L))).thenReturn(List.of(missingFba));
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
() -> service.preArrange("100", "7",
new DispatchRequests.PreArrange(List.of(10L), null, null, null, null)));
assertEquals(409, exception.getStatusCode().value());
assertEquals("所选托盘尚未设置 FBA 仓,不能智能排车", exception.getReason());
verify(orders, never()).insert(any(DispatchOrder.class));
}
@Test
void removingPalletFromOwnedDraftImmediatelyReleasesLock() {
when(candidates.lockSelected("100", List.of(10L))).thenReturn(List.of(candidate(10L, null, "ONT8")));
@@ -307,6 +323,14 @@ class DispatchServiceTest {
verify(candidates).available(null, null, null, null, "RECEIVED", "draft-1");
}
@Test
void relabelSourceDoesNotRequireOrQueryAnInboundPlan() {
when(candidates.relabelAvailable(null, "RL-1", null)).thenReturn(List.of());
org.junit.jupiter.api.Assertions.assertTrue(service.platformCandidates("RL-1", null, "RELABEL", "draft-1").isEmpty());
verify(candidates).relabelAvailable(null, "RL-1", null);
verify(candidates, never()).available(any(), any(), any(), any(), any(), any());
}
@Test
void resolvesCustomerFromSelectedPlansAndPalletsInMultiCustomerDeployment() {
when(candidates.findCustomerScopesByPlanIds(List.of(1L, 2L))).thenReturn(List.of("100"));