提交
This commit is contained in:
@@ -9,6 +9,8 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
|
||||
import jakarta.validation.Validation;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@@ -24,6 +26,18 @@ class RelabelContractTest {
|
||||
"newSkuCode", "labelRequirement", "relabelFileId")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createAllowsChargeAmountToBeOmitted() {
|
||||
RelabelRequests.Create request = new RelabelRequests.Create("CUSTOMER-1", "WH-1",
|
||||
java.time.LocalDateTime.of(2026, 8, 20, 10, 0), "Li", "13800000000", null, null, "key-free",
|
||||
java.util.List.of(new RelabelRequests.Item("SKU-A", null, null, null, "Product", 1, 1,
|
||||
"SHIP-1", null, "SKU-B", "Replace", null, null, null)));
|
||||
try (var validatorFactory = Validation.buildDefaultValidatorFactory()) {
|
||||
assertTrue(validatorFactory.getValidator().validate(request).stream()
|
||||
.noneMatch(violation -> "chargeAmount".equals(violation.getPropertyPath().toString())));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void schemasDefineRelabelApplicationItemsAndHistoryContracts() throws Exception {
|
||||
String baseline = Files.readString(Path.of("src/main/resources/db/lastleg.sql"), StandardCharsets.UTF_8).toLowerCase();
|
||||
@@ -89,6 +103,8 @@ class RelabelContractTest {
|
||||
String relabelCreate = openApi.substring(openApi.indexOf(" RelabelCreate:"),
|
||||
openApi.indexOf(" RelabelItem:"));
|
||||
assertFalse(relabelCreate.contains("customerId"));
|
||||
assertFalse(relabelCreate.substring(relabelCreate.indexOf("required:"), relabelCreate.indexOf("properties:"))
|
||||
.contains("chargeAmount"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+106
-15
@@ -96,6 +96,35 @@ class PdaRelabelServiceTest {
|
||||
verify(applicationService).changeStatus(eq("100"), eq("FBR-1"), eq("7"), any(RelabelRequests.ChangeStatus.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void claimRejectsAnotherProcessingTaskOwnedBySameOperator() {
|
||||
when(applications.lockAnyByRelabelNo("FBR-1")).thenReturn(application);
|
||||
when(tasks.lock("100", 1L)).thenReturn(null);
|
||||
PdaRelabelTask active = task("PROCESSING", 0);
|
||||
active.setApplicationId(99L);
|
||||
active.setRelabelNo("FBR-ACTIVE");
|
||||
when(tasks.lockUnfinishedByOperator("7")).thenReturn(active);
|
||||
|
||||
ResponseStatusException error = assertThrows(ResponseStatusException.class,
|
||||
() -> service.claim("7", "picker", 900L, "FBR-1", new PdaRelabelRequests.Claim("claim-other")));
|
||||
|
||||
assertTrue(error.getReason().contains("FBR-ACTIVE"));
|
||||
verify(tasks, never()).insert(any(PdaRelabelTask.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void workPhotoDownloadValidatesTaskOwnershipAndFileScope() {
|
||||
application.setStatus("PROCESSING");
|
||||
when(applications.findAnyByRelabelNo("FBR-1")).thenReturn(application);
|
||||
when(tasks.find("100", 1L)).thenReturn(task("PROCESSING", 0));
|
||||
service.downloadWorkPhoto("7", 900L, "FBR-1", 99L);
|
||||
verify(photoFiles).validate(eq("100"), anyLong(), eq(99L));
|
||||
verify(photoFiles).download(eq("100"), anyLong(), eq(99L));
|
||||
assertThrows(ResponseStatusException.class, () -> service.downloadWorkPhoto("8", 900L, "FBR-1", 100L));
|
||||
assertThrows(ResponseStatusException.class, () -> service.downloadWorkPhoto("7", 901L, "FBR-1", 100L));
|
||||
verify(photoFiles, never()).download(anyString(), anyLong(), eq(100L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void crossWarehouseClaimIsHiddenAndCannotCreateTask() {
|
||||
when(applications.lockAnyByRelabelNo("FBR-1")).thenReturn(application);
|
||||
@@ -105,6 +134,57 @@ class PdaRelabelServiceTest {
|
||||
verify(tasks, never()).insert(any(PdaRelabelTask.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void skuScanRequiresEverySkuAndNeverWritesSkuIntoCartonLabel() {
|
||||
application.setStatus("PROCESSING");
|
||||
item.setOperationType("SKU_LABEL");
|
||||
item.setCartonMark("BOX-1");
|
||||
item.setSkuRelabelItemsJson("[{\"skuCode\":\"OLD-A\",\"newSkuCode\":\"NEW-A\",\"quantity\":3}]");
|
||||
when(applications.lockAnyByRelabelNo("FBR-1")).thenReturn(application);
|
||||
when(tasks.lock("100", 1L)).thenReturn(task("PROCESSING", 0));
|
||||
when(items.selectOne(any())).thenReturn(item);
|
||||
assertThrows(ResponseStatusException.class, () -> service.scan("7", "picker", 900L, "FBR-1",
|
||||
new PdaRelabelRequests.Scan(10L, "BOX-1", null, 0, "missing-sku")));
|
||||
assertThrows(ResponseStatusException.class, () -> service.scan("7", "picker", 900L, "FBR-1",
|
||||
new PdaRelabelRequests.Scan(10L, "BOX-1", null, 0, "wrong-quantity",
|
||||
List.of(new PdaRelabelRequests.SkuVerification("OLD-A", "NEW-A", 2)))));
|
||||
var result = service.scan("7", "picker", 900L, "FBR-1",
|
||||
new PdaRelabelRequests.Scan(10L, "BOX-1", null, 0, "valid-sku",
|
||||
List.of(new PdaRelabelRequests.SkuVerification("OLD-A", "NEW-A", 3))));
|
||||
assertEquals(1, result.relabelledPackages());
|
||||
verify(items, never()).updateById(any(RelabelItem.class));
|
||||
assertEquals("BOX-1", persistedFacts.get(0).getOldLabel());
|
||||
}
|
||||
|
||||
@org.junit.jupiter.params.ParameterizedTest
|
||||
@org.junit.jupiter.params.provider.ValueSource(booleans = {true, false})
|
||||
void mixedWorkRequiresBothStagesAndCountsBoxOnce(boolean cartonFirst) {
|
||||
application.setStatus("PROCESSING");
|
||||
item.setOperationType("CARTON_AND_SKU"); item.setCartonMark("BOX-1"); item.setFbaCartonMark("FBA-1");
|
||||
item.setSkuRelabelItemsJson("[{\"skuCode\":\"OLD-A\",\"newSkuCode\":\"NEW-A\",\"quantity\":3}]");
|
||||
var task = task("PROCESSING", 0);
|
||||
var savedHistory = new ArrayList<com.baiyue.lastleg.relabel.pda.entity.PdaRelabelHistory>();
|
||||
when(histories.selectList(any())).thenAnswer(call -> List.copyOf(savedHistory));
|
||||
when(histories.insert(any(com.baiyue.lastleg.relabel.pda.entity.PdaRelabelHistory.class))).thenAnswer(call -> { savedHistory.add(call.getArgument(0)); return 1; });
|
||||
when(applications.findAnyByRelabelNo("FBR-1")).thenReturn(application);
|
||||
when(applications.lockAnyByRelabelNo("FBR-1")).thenReturn(application);
|
||||
when(tasks.lock("100", 1L)).thenReturn(task);
|
||||
when(items.selectOne(any())).thenReturn(item);
|
||||
java.util.function.IntFunction<com.baiyue.lastleg.relabel.pda.dto.PdaRelabelResponses.Detail> carton = version -> service.scanCarton("7", "picker", 900L, "FBR-1", new PdaRelabelRequests.CartonScan("BOX-1", version, "carton-" + version));
|
||||
java.util.function.IntFunction<com.baiyue.lastleg.relabel.pda.dto.PdaRelabelResponses.Detail> sku = version -> service.scan("7", "picker", 900L, "FBR-1", new PdaRelabelRequests.Scan(10L, "BOX-1", null, version, "sku-" + version, List.of(new PdaRelabelRequests.SkuVerification("OLD-A", "NEW-A", 3))));
|
||||
var first = (cartonFirst ? carton : sku).apply(0);
|
||||
assertEquals(0, first.relabelledPackages());
|
||||
assertEquals(cartonFirst ? 1 : 0, first.items().get(0).cartonProcessedPackages());
|
||||
assertEquals(cartonFirst ? 0 : 1, first.items().get(0).skuProcessedPackages());
|
||||
assertThrows(ResponseStatusException.class, () -> (cartonFirst ? carton : sku).apply(1));
|
||||
assertThrows(ResponseStatusException.class, () -> service.complete("7", "picker", 900L, "FBR-1", new PdaRelabelRequests.Complete(List.of(501L), 1, "too-early", null)));
|
||||
var second = (cartonFirst ? sku : carton).apply(1);
|
||||
assertEquals(1, second.relabelledPackages());
|
||||
assertEquals(1, persistedFacts.size());
|
||||
assertEquals(1, second.items().get(0).cartonProcessedPackages());
|
||||
assertEquals(1, second.items().get(0).skuProcessedPackages());
|
||||
}
|
||||
|
||||
@Test
|
||||
void unitScanLocksItemToApplicationValidatesNewSkuAndUpdatesVersion() {
|
||||
application.setStatus("PROCESSING");
|
||||
@@ -167,40 +247,51 @@ class PdaRelabelServiceTest {
|
||||
|
||||
verify(photoFiles).validate("100", 20L, 501L);
|
||||
assertEquals(PdaRelabelService.WAIT_REVIEW, result.taskStatus());
|
||||
assertEquals("PROCESSING", result.applicationStatus());
|
||||
verify(applicationService, never()).changeStatus(anyString(), anyString(), anyString(), any());
|
||||
assertEquals("CUSTOMER_CONFIRM_PENDING", result.applicationStatus());
|
||||
verify(applicationService).changeStatus(eq("100"), eq("FBR-1"), eq("7"), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void approvalUsesExistingApplicationCompletionAndReworkDoesNot() {
|
||||
application.setStatus("PROCESSING");
|
||||
PdaRelabelTask approved = task("WAIT_REVIEW", 2);
|
||||
when(applications.lockAnyByRelabelNo("FBR-1")).thenReturn(application);
|
||||
when(tasks.lock("100", 1L)).thenReturn(approved);
|
||||
|
||||
var result = service.review("8", "reviewer", 900L, "FBR-1",
|
||||
new PdaRelabelRequests.Review("APPROVE", 2, "review-1", "ok"));
|
||||
|
||||
assertEquals("COMPLETED", result.taskStatus());
|
||||
assertEquals(RelabelApplicationService.CUSTOMER_CONFIRM_PENDING, result.applicationStatus());
|
||||
verify(applicationService).changeStatus(eq("100"), eq("FBR-1"), eq("8"), any(RelabelRequests.ChangeStatus.class));
|
||||
assertThrows(ResponseStatusException.class, () -> service.review("8", "reviewer", 900L, "FBR-1",
|
||||
new PdaRelabelRequests.Review("APPROVE", 2, "review-1", "ok")));
|
||||
verify(applicationService, never()).changeStatus(anyString(), anyString(), anyString(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void reviewCanReturnTaskForReworkWithoutCompletingApplication() {
|
||||
application.setStatus("PROCESSING");
|
||||
application.setStatus("CUSTOMER_CONFIRM_PENDING");
|
||||
PdaRelabelTask task = task("WAIT_REVIEW", 2);
|
||||
task.setRelabelledPackages(1);
|
||||
task.setCompletionPhotoFileIds("[501]");
|
||||
when(applications.findAnyByRelabelNo("FBR-1")).thenReturn(application);
|
||||
when(applications.lockAnyByRelabelNo("FBR-1")).thenReturn(application);
|
||||
when(tasks.lock("100", 1L)).thenReturn(task);
|
||||
|
||||
var result = service.review("8", "reviewer", 900L, "FBR-1",
|
||||
var result = service.returnForRework("8", "reviewer", "FBR-1",
|
||||
new PdaRelabelRequests.Review("REWORK", 2, "review-rework", "wrong label"));
|
||||
|
||||
assertEquals("PROCESSING", result.taskStatus());
|
||||
assertEquals("PROCESSING", result.applicationStatus());
|
||||
assertEquals(0, result.relabelledPackages());
|
||||
assertEquals("wrong label", result.reviewRemark());
|
||||
assertTrue(result.completionPhotoFileIds().isEmpty());
|
||||
verify(applicationService).resetForRework("100", "FBR-1", "8", "wrong label");
|
||||
verify(applicationService, never()).changeStatus(anyString(), anyString(), anyString(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void reworkRejectsMissingRemarkAndConfirmedApplication() {
|
||||
assertThrows(ResponseStatusException.class, () -> service.returnForRework("8", "reviewer", "FBR-1",
|
||||
new PdaRelabelRequests.Review("REWORK", 2, "empty-reason", " ")));
|
||||
application.setStatus("READY_FOR_OUTBOUND");
|
||||
when(applications.findAnyByRelabelNo("FBR-1")).thenReturn(application);
|
||||
when(applications.lockAnyByRelabelNo("FBR-1")).thenReturn(application);
|
||||
assertThrows(ResponseStatusException.class, () -> service.returnForRework("8", "reviewer", "FBR-1",
|
||||
new PdaRelabelRequests.Review("REWORK", 2, "confirmed", "wrong label")));
|
||||
verify(applicationService, never()).resetForRework(anyString(), anyString(), anyString(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void completionRejectsMissingPackageFactsBeforePhotoValidation() {
|
||||
application.setStatus("PROCESSING");
|
||||
|
||||
+53
@@ -75,6 +75,7 @@ class RelabelApplicationServiceTest {
|
||||
@Mock OutboundPdaTaskMapper outboundTasks;
|
||||
@Mock WarehouseTransferOrderMapper warehouseTransfers;
|
||||
@Mock OverseasFeeSourceService feeSources;
|
||||
@Mock com.baiyue.lastleg.relabel.pda.mapper.PdaRelabelTaskMapper pdaTasks;
|
||||
@Spy LastlegOrderNoGenerator orderNoGenerator = new LastlegOrderNoGenerator();
|
||||
|
||||
private RelabelApplicationService service;
|
||||
@@ -82,6 +83,12 @@ class RelabelApplicationServiceTest {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new RelabelApplicationService();
|
||||
ReflectionTestUtils.setField(service, "pdaTasks", pdaTasks);
|
||||
lenient().when(pdaTasks.lock(any(), any())).thenAnswer(call -> {
|
||||
var task = new com.baiyue.lastleg.relabel.pda.entity.PdaRelabelTask();
|
||||
task.setStatus("WAIT_REVIEW"); task.setVersion(1); return task;
|
||||
});
|
||||
lenient().when(pdaTasks.updateWithVersion(any(), any())).thenReturn(1);
|
||||
ReflectionTestUtils.setField(service, "applications", applications);
|
||||
ReflectionTestUtils.setField(service, "items", items);
|
||||
ReflectionTestUtils.setField(service, "histories", histories);
|
||||
@@ -152,6 +159,31 @@ class RelabelApplicationServiceTest {
|
||||
assertEquals("FBR-1", result.application().relabelNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithoutChargeSkipsWalletFreeze() {
|
||||
RelabelRequests.Create charged = request("key-free", 10, 6);
|
||||
RelabelRequests.Create request = new RelabelRequests.Create(charged.customerOrderNo(),
|
||||
charged.operationWarehouseId(), charged.labelDeadline(), charged.contactName(), charged.contactPhone(),
|
||||
null, charged.remark(), charged.idempotencyKey(), charged.items(), charged.deliveryMethod(),
|
||||
charged.printShippingLabel());
|
||||
RelabelApplication persisted = application(11L, "FBR-FREE", "10001", "key-free", null,
|
||||
RelabelApplicationService.PENDING_PROCESSING);
|
||||
persisted.setLastlegOrderId(21L);
|
||||
when(applications.findNo(eq("10001"), any())).thenReturn(persisted);
|
||||
when(items.selectList(any())).thenReturn(List.of(item(31L, 11L, "SKU-A", 10, 6)));
|
||||
when(histories.selectList(any())).thenReturn(List.of());
|
||||
when(orders.selectById(21L)).thenReturn(order(21L, "10001", "LMO-RELABEL-FREE",
|
||||
RelabelApplicationService.PENDING_PROCESSING));
|
||||
|
||||
service.create("10001", "operator-1", request);
|
||||
|
||||
ArgumentCaptor<RelabelApplication> application = ArgumentCaptor.forClass(RelabelApplication.class);
|
||||
verify(applications).insert(application.capture());
|
||||
assertEquals(BigDecimal.ZERO, application.getValue().getChargeAmount());
|
||||
assertEquals("NOT_REQUIRED", application.getValue().getWalletStatus());
|
||||
verify(walletCommands, never()).publish(any(), any(Long.class), any(), any(), any(), any(), any(Long.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void partialRelabelFreezesOnlyNotifiedPackages() {
|
||||
RelabelRequests.Create request = request("key-partial", 10, 2);
|
||||
@@ -363,6 +395,23 @@ class RelabelApplicationServiceTest {
|
||||
verify(applications).findNo("10002", "FBR-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void applicationWithoutChargeCanEnterProcessing() {
|
||||
RelabelApplication application = application(11L, "FBR-FREE", "10001", "key-free", "hash",
|
||||
RelabelApplicationService.PENDING_PROCESSING);
|
||||
application.setLastlegOrderId(21L);
|
||||
application.setChargeAmount(BigDecimal.ZERO);
|
||||
application.setWalletStatus("NOT_REQUIRED");
|
||||
when(applications.lockNo("10001", "FBR-FREE")).thenReturn(application);
|
||||
stubDetail(application);
|
||||
|
||||
service.changeStatus("10001", "FBR-FREE", "operator-2",
|
||||
new RelabelRequests.ChangeStatus("PROCESSING", "started"));
|
||||
|
||||
assertEquals("PROCESSING", application.getStatus());
|
||||
verify(walletCommands, never()).publish(any(), any(Long.class), any(), any(), any(), any(), any(Long.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void completedReturnRelabelOrderEntersPendingAssignment() {
|
||||
RelabelApplication application = application(11L, "FBR-1", "10001", "key-1", "hash", RelabelApplicationService.READY_FOR_OUTBOUND);
|
||||
@@ -414,6 +463,10 @@ class RelabelApplicationServiceTest {
|
||||
|
||||
assertEquals(RelabelApplicationService.READY_FOR_OUTBOUND, application.getStatus());
|
||||
assertEquals("WAIT_PRE_DISPATCH", application.getFulfillmentStatus());
|
||||
var review = ArgumentCaptor.forClass(com.baiyue.lastleg.relabel.pda.entity.PdaRelabelTask.class);
|
||||
verify(pdaTasks).updateWithVersion(review.capture(), eq(1));
|
||||
assertEquals("COMPLETED", review.getValue().getStatus());
|
||||
assertEquals("CUSTOMER_CONFIRMED", review.getValue().getReviewDecision());
|
||||
assertNull(application.getOutboundOrderNo());
|
||||
verify(outboundOrders, never()).insert(any(OutboundOrder.class));
|
||||
|
||||
|
||||
+6
@@ -262,6 +262,12 @@ class WarehouseTransferServiceTest {
|
||||
new WarehouseTransferRequests.Review("REJECT", " 地址不完整 "));
|
||||
assertEquals("REJECTED", result.status());
|
||||
assertEquals("CANCELLED", tailOrder.getCurrentStatus());
|
||||
assertEquals("REJECTED", transfer.getReviewDecision());
|
||||
assertEquals("地址不完整", transfer.getReviewRemark());
|
||||
assertEquals("USER-1", transfer.getReviewedBy());
|
||||
assertNotNull(transfer.getReviewedAt());
|
||||
assertEquals("REJECTED", result.reviewDecision());
|
||||
assertEquals("地址不完整", result.reviewRemark());
|
||||
var history = ArgumentCaptor.forClass(com.baiyue.lastleg.entity.LastlegOrderStatusHistory.class);
|
||||
verify(orderHistories).insert(history.capture());
|
||||
assertTrue(history.getValue().getRemark().endsWith("; 地址不完整"));
|
||||
|
||||
Reference in New Issue
Block a user