feat: close overseas wallet and pallet cost flow
This commit is contained in:
+20
@@ -0,0 +1,20 @@
|
||||
package com.baiyue.lastleg.inboundplan.service;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
class DestuffingPalletCostServiceTest {
|
||||
@Test void preservesTotalAndLetsFinalCustomerAbsorbRoundingRemainder() {
|
||||
List<BigDecimal> result = DestuffingPalletCostService.allocate(new BigDecimal("10.00"),
|
||||
List.of(new BigDecimal("1"), new BigDecimal("1"), new BigDecimal("1")));
|
||||
assertThat(result).containsExactly(new BigDecimal("3.33"), new BigDecimal("3.33"), new BigDecimal("3.34"));
|
||||
assertThat(result.stream().reduce(BigDecimal.ZERO, BigDecimal::add)).isEqualByComparingTo("10.00");
|
||||
}
|
||||
@Test void rejectsZeroActualVolume() {
|
||||
assertThatThrownBy(() -> DestuffingPalletCostService.allocate(BigDecimal.ONE, List.of(BigDecimal.ZERO)))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
}
|
||||
+18
-1
@@ -6,6 +6,10 @@ import com.baiyue.lastleg.procurement.dto.OverseasProcurementRequests;
|
||||
import com.baiyue.lastleg.procurement.entity.OverseasProcurementOrder;
|
||||
import com.baiyue.lastleg.procurement.mapper.OverseasProcurementOrderMapper;
|
||||
import com.baiyue.lastleg.procurement.mapper.OverseasProcurementStatusHistoryMapper;
|
||||
import com.baiyue.lastleg.procurement.mapper.OverseasProcurementFileMapper;
|
||||
import com.baiyue.lastleg.procurement.mapper.OverseasProcurementFeeMapper;
|
||||
import com.baiyue.lastleg.procurement.gateway.ProcurementFileGateway;
|
||||
import com.baiyue.lastleg.wallet.WalletCommandOutboxService;
|
||||
import com.baiyue.lastleg.masterdata.quote.service.PartnerWarehouseDirectory;
|
||||
import com.baiyue.lastleg.masterdata.quote.dto.OverseasQuoteResponses;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -29,6 +33,7 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@@ -36,6 +41,11 @@ class OverseasProcurementServiceTest {
|
||||
@Mock OverseasProcurementOrderMapper orders;
|
||||
@Mock OverseasProcurementStatusHistoryMapper histories;
|
||||
@Mock PartnerWarehouseDirectory warehouseDirectory;
|
||||
@Mock ProcurementOrderNumberGenerator orderNoGenerator;
|
||||
@Mock OverseasProcurementFileMapper procurementFiles;
|
||||
@Mock OverseasProcurementFeeMapper procurementFees;
|
||||
@Mock ProcurementFileGateway fileGateway;
|
||||
@Mock WalletCommandOutboxService walletCommands;
|
||||
private OverseasProcurementService service;
|
||||
|
||||
@BeforeEach
|
||||
@@ -45,7 +55,14 @@ class OverseasProcurementServiceTest {
|
||||
ReflectionTestUtils.setField(service, "histories", histories);
|
||||
ReflectionTestUtils.setField(service, "warehouseDirectory", warehouseDirectory);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper());
|
||||
ReflectionTestUtils.setField(service, "orderNoGenerator", new com.baiyue.lastleg.service.LastlegOrderNoGenerator());
|
||||
ReflectionTestUtils.setField(service, "orderNoGenerator", orderNoGenerator);
|
||||
ReflectionTestUtils.setField(service, "procurementFiles", procurementFiles);
|
||||
ReflectionTestUtils.setField(service, "procurementFees", procurementFees);
|
||||
ReflectionTestUtils.setField(service, "fileGateway", fileGateway);
|
||||
ReflectionTestUtils.setField(service, "walletCommands", walletCommands);
|
||||
lenient().when(orderNoGenerator.next(anyString(), any())).thenReturn("ACME202609050001");
|
||||
lenient().when(procurementFiles.selectList(any())).thenReturn(List.of());
|
||||
lenient().when(procurementFees.selectList(any())).thenReturn(List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.baiyue.lastleg.procurement.service;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ProcurementOrderNumberGeneratorTest {
|
||||
@Test void convertsChineseCompanyNameToInitials() {
|
||||
assertThat(ProcurementOrderNumberGenerator.initials("杭州积乐贸易有限公司")).isEqualTo("HZJLMYYX");
|
||||
}
|
||||
@Test void keepsOneInitialPerLatinWordAndUsesFallback() {
|
||||
assertThat(ProcurementOrderNumberGenerator.initials("Great Ocean Trading LLC")).isEqualTo("GOTL");
|
||||
assertThat(ProcurementOrderNumberGenerator.initials(" ")).isEqualTo("KH");
|
||||
}
|
||||
@Test void limitsInitialsToEightCharacters() {
|
||||
assertThat(ProcurementOrderNumberGenerator.initials("一二三四五六七八九十")).hasSize(8);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -36,7 +36,7 @@ class RelabelApplicationControllerTest {
|
||||
|
||||
private RelabelRequests.Create request() {
|
||||
return new RelabelRequests.Create("CUSTOMER-1", "WH-1",
|
||||
LocalDateTime.of(2026, 8, 20, 10, 0), "Li", "13800000000", null, "key-1",
|
||||
LocalDateTime.of(2026, 8, 20, 10, 0), "Li", "13800000000", null, null, "key-1",
|
||||
List.of(new RelabelRequests.Item("SKU-A", null, null, null, "Product", 1, 1,
|
||||
"SHIP-1", null, "SKU-B", "Replace", null, null, null)));
|
||||
}
|
||||
|
||||
+33
-26
@@ -1,5 +1,7 @@
|
||||
package com.baiyue.lastleg.relabel.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderStatusHistory;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
@@ -13,6 +15,7 @@ import com.baiyue.lastleg.relabel.mapper.RelabelApplicationMapper;
|
||||
import com.baiyue.lastleg.relabel.mapper.RelabelItemMapper;
|
||||
import com.baiyue.lastleg.relabel.mapper.RelabelStatusHistoryMapper;
|
||||
import com.baiyue.lastleg.service.LastlegOrderNoGenerator;
|
||||
import com.baiyue.lastleg.wallet.WalletCommandOutboxService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -45,6 +48,7 @@ class RelabelApplicationServiceTest {
|
||||
@Mock RelabelStatusHistoryMapper histories;
|
||||
@Mock LastlegOrderMapper orders;
|
||||
@Mock LastlegOrderStatusHistoryMapper orderHistories;
|
||||
@Mock WalletCommandOutboxService walletCommands;
|
||||
@Spy LastlegOrderNoGenerator orderNoGenerator = new LastlegOrderNoGenerator();
|
||||
|
||||
private RelabelApplicationService service;
|
||||
@@ -58,23 +62,25 @@ class RelabelApplicationServiceTest {
|
||||
ReflectionTestUtils.setField(service, "orders", orders);
|
||||
ReflectionTestUtils.setField(service, "orderHistories", orderHistories);
|
||||
ReflectionTestUtils.setField(service, "orderNoGenerator", orderNoGenerator);
|
||||
ReflectionTestUtils.setField(service, "walletCommands", walletCommands);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", mapper());
|
||||
lenient().when(walletCommands.publish(any(), any(Long.class), any(), any(), any(), any(), any(Long.class))).thenReturn("wallet-event-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createBuildsPendingRelabelTailOrderItemsAndBothHistories() {
|
||||
RelabelRequests.Create request = request("key-1", 10, 6);
|
||||
RelabelApplication persisted = application(11L, "FBR-1", "TENANT-A", "key-1", null,
|
||||
RelabelApplication persisted = application(11L, "FBR-1", "10001", "key-1", null,
|
||||
RelabelApplicationService.PENDING_PROCESSING);
|
||||
persisted.setLastlegOrderId(21L);
|
||||
persisted.setItemCount(1); persisted.setTotalPackageCount(10); persisted.setNotifiedPackageCount(6);
|
||||
when(applications.findNo(eq("TENANT-A"), any())).thenReturn(persisted);
|
||||
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, "TENANT-A", "LMO-RELABEL-1",
|
||||
when(orders.selectById(21L)).thenReturn(order(21L, "10001", "LMO-RELABEL-1",
|
||||
RelabelApplicationService.PENDING_PROCESSING));
|
||||
|
||||
RelabelResponses.Detail result = service.create("TENANT-A", "operator-1", request);
|
||||
RelabelResponses.Detail result = service.create("10001", "operator-1", request);
|
||||
|
||||
ArgumentCaptor<LastlegOrder> tailOrder = ArgumentCaptor.forClass(LastlegOrder.class);
|
||||
ArgumentCaptor<RelabelApplication> application = ArgumentCaptor.forClass(RelabelApplication.class);
|
||||
@@ -106,7 +112,7 @@ class RelabelApplicationServiceTest {
|
||||
@Test
|
||||
void createRejectsNotifiedBoxesAboveTotalBeforeAnyWrite() {
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class, () ->
|
||||
service.create("TENANT-A", "operator-1", request("key-1", 5, 6)));
|
||||
service.create("10001", "operator-1", request("key-1", 5, 6)));
|
||||
|
||||
assertEquals(400, exception.getStatusCode().value());
|
||||
verify(orders, never()).insert(any(LastlegOrder.class));
|
||||
@@ -117,14 +123,14 @@ class RelabelApplicationServiceTest {
|
||||
@Test
|
||||
void sameIdempotencyKeyAndPayloadReturnsExistingApplicationWithoutWrites() {
|
||||
RelabelRequests.Create request = request("key-1", 10, 6);
|
||||
RelabelApplication existing = application(11L, "FBR-1", "TENANT-A", "key-1", hash(request),
|
||||
RelabelApplication existing = application(11L, "FBR-1", "10001", "key-1", hash(request),
|
||||
RelabelApplicationService.PENDING_PROCESSING);
|
||||
existing.setLastlegOrderId(21L); existing.setItemCount(1); existing.setTotalPackageCount(10);
|
||||
existing.setNotifiedPackageCount(6);
|
||||
when(applications.findKey("TENANT-A", "key-1")).thenReturn(existing);
|
||||
when(applications.findKey("10001", "key-1")).thenReturn(existing);
|
||||
stubDetail(existing);
|
||||
|
||||
RelabelResponses.Detail result = service.create("TENANT-A", "operator-1", request);
|
||||
RelabelResponses.Detail result = service.create("10001", "operator-1", request);
|
||||
|
||||
assertEquals("FBR-1", result.application().relabelNo());
|
||||
verify(orders, never()).insert(any(LastlegOrder.class));
|
||||
@@ -135,12 +141,12 @@ class RelabelApplicationServiceTest {
|
||||
@Test
|
||||
void idempotencyKeyCannotBeReusedWithDifferentRelabelPayload() {
|
||||
RelabelRequests.Create request = request("key-1", 10, 6);
|
||||
RelabelApplication existing = application(11L, "FBR-1", "TENANT-A", "key-1", "different-hash",
|
||||
RelabelApplication existing = application(11L, "FBR-1", "10001", "key-1", "different-hash",
|
||||
RelabelApplicationService.PENDING_PROCESSING);
|
||||
when(applications.findKey("TENANT-A", "key-1")).thenReturn(existing);
|
||||
when(applications.findKey("10001", "key-1")).thenReturn(existing);
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class, () ->
|
||||
service.create("TENANT-A", "operator-1", request));
|
||||
service.create("10001", "operator-1", request));
|
||||
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
verify(orders, never()).insert(any(LastlegOrder.class));
|
||||
@@ -148,13 +154,13 @@ class RelabelApplicationServiceTest {
|
||||
|
||||
@Test
|
||||
void statusChangeIsTenantScopedAndWritesApplicationAndTailOrderHistory() {
|
||||
RelabelApplication application = application(11L, "FBR-1", "TENANT-A", "key-1", "hash",
|
||||
RelabelApplication application = application(11L, "FBR-1", "10001", "key-1", "hash",
|
||||
RelabelApplicationService.PENDING_PROCESSING);
|
||||
application.setLastlegOrderId(21L);
|
||||
when(applications.lockNo("TENANT-A", "FBR-1")).thenReturn(application);
|
||||
when(applications.lockNo("10001", "FBR-1")).thenReturn(application);
|
||||
stubDetail(application);
|
||||
|
||||
RelabelResponses.Detail result = service.changeStatus("TENANT-A", "FBR-1", "operator-2",
|
||||
RelabelResponses.Detail result = service.changeStatus("10001", "FBR-1", "operator-2",
|
||||
new RelabelRequests.ChangeStatus("processing", "started"));
|
||||
|
||||
assertEquals("PROCESSING", application.getStatus());
|
||||
@@ -170,32 +176,32 @@ class RelabelApplicationServiceTest {
|
||||
|
||||
@Test
|
||||
void completedApplicationCannotReturnToProcessingAndOtherTenantCannotReadIt() {
|
||||
RelabelApplication completed = application(11L, "FBR-1", "TENANT-A", "key-1", "hash", "COMPLETED");
|
||||
RelabelApplication completed = application(11L, "FBR-1", "10001", "key-1", "hash", "COMPLETED");
|
||||
completed.setLastlegOrderId(21L);
|
||||
when(applications.lockNo("TENANT-A", "FBR-1")).thenReturn(completed);
|
||||
when(applications.lockNo("10001", "FBR-1")).thenReturn(completed);
|
||||
|
||||
ResponseStatusException transition = assertThrows(ResponseStatusException.class, () ->
|
||||
service.changeStatus("TENANT-A", "FBR-1", "operator-2", new RelabelRequests.ChangeStatus("PROCESSING", null)));
|
||||
service.changeStatus("10001", "FBR-1", "operator-2", new RelabelRequests.ChangeStatus("PROCESSING", null)));
|
||||
assertEquals(409, transition.getStatusCode().value());
|
||||
|
||||
when(applications.findNo("TENANT-B", "FBR-1")).thenReturn(null);
|
||||
when(applications.findNo("10002", "FBR-1")).thenReturn(null);
|
||||
ResponseStatusException boundary = assertThrows(ResponseStatusException.class, () ->
|
||||
service.detail("TENANT-B", "FBR-1"));
|
||||
service.detail("10002", "FBR-1"));
|
||||
assertEquals(404, boundary.getStatusCode().value());
|
||||
verify(applications).findNo("TENANT-B", "FBR-1");
|
||||
verify(applications).findNo("10002", "FBR-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void completedReturnRelabelOrderEntersPendingAssignment() {
|
||||
RelabelApplication application = application(11L, "FBR-1", "TENANT-A", "key-1", "hash", "PROCESSING");
|
||||
RelabelApplication application = application(11L, "FBR-1", "10001", "key-1", "hash", "PROCESSING");
|
||||
application.setLastlegOrderId(21L);
|
||||
LastlegOrder order = order(21L, "TENANT-A", "LMO-RELABEL-1", "PROCESSING");
|
||||
LastlegOrder order = order(21L, "10001", "LMO-RELABEL-1", "PROCESSING");
|
||||
order.setOrderType("RETURN_RELABEL");
|
||||
when(applications.lockNo("TENANT-A", "FBR-1")).thenReturn(application);
|
||||
when(applications.lockNo("10001", "FBR-1")).thenReturn(application);
|
||||
stubDetail(application);
|
||||
when(orders.selectById(21L)).thenReturn(order);
|
||||
|
||||
service.changeStatus("TENANT-A", "FBR-1", "operator-2",
|
||||
service.changeStatus("10001", "FBR-1", "operator-2",
|
||||
new RelabelRequests.ChangeStatus("COMPLETED", "finished"));
|
||||
|
||||
assertEquals("COMPLETED", application.getStatus());
|
||||
@@ -213,7 +219,7 @@ class RelabelApplicationServiceTest {
|
||||
|
||||
private static RelabelRequests.Create request(String key, int total, int notified) {
|
||||
return new RelabelRequests.Create("CUSTOMER-1", "WH-1", LocalDateTime.of(2026, 8, 20, 10, 0),
|
||||
"Li", "13800000000", "remark", key, List.of(new RelabelRequests.Item("SKU-A", "img-1",
|
||||
"Li", "13800000000", new BigDecimal("20.00"), "remark", key, List.of(new RelabelRequests.Item("SKU-A", "img-1",
|
||||
"image.png", "https://files/image.png", "Chinese name", total, notified, "SHP-1", "TRACK-1",
|
||||
"SKU-NEW", "Label on carton", "file-1", "relabel.pdf", "https://files/relabel.pdf")));
|
||||
}
|
||||
@@ -226,12 +232,13 @@ class RelabelApplicationServiceTest {
|
||||
value.setContactName("Li"); value.setContactPhone("13800000000"); value.setCustomerOrderNo("CUSTOMER-1");
|
||||
value.setOverseasWarehouseId("WH-1"); value.setLabelDeadline(LocalDateTime.now().plusDays(1));
|
||||
value.setItemCount(1); value.setTotalPackageCount(10); value.setNotifiedPackageCount(6);
|
||||
value.setChargeAmount(new BigDecimal("20.00")); value.setChargeCurrency("CNY"); value.setWalletStatus("FROZEN");
|
||||
return value;
|
||||
}
|
||||
|
||||
private static RelabelItem item(long id, long applicationId, String sku, int total, int notified) {
|
||||
RelabelItem value = new RelabelItem();
|
||||
value.setId(id); value.setApplicationId(applicationId); value.setCustomerId("TENANT-A"); value.setSkuCode(sku);
|
||||
value.setId(id); value.setApplicationId(applicationId); value.setCustomerId("10001"); value.setSkuCode(sku);
|
||||
value.setProductNameZh("Chinese name"); value.setTotalPackageCount(total); value.setNotifiedPackageCount(notified);
|
||||
value.setShipmentNo("SHP-1"); value.setNewSkuCode("SKU-NEW");
|
||||
return value;
|
||||
|
||||
+4
-1
@@ -84,6 +84,9 @@ class TaillegRealConsumerAcceptanceTest {
|
||||
return new WarehousePalletReference(1L, palletCode, 1L, 1L,
|
||||
"AVAILABLE", null, 0);
|
||||
}
|
||||
public WarehousePalletReference resolvePallet(long palletId) {
|
||||
return new WarehousePalletReference(palletId, "UNUSED", 1L);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -104,7 +107,7 @@ class TaillegRealConsumerAcceptanceTest {
|
||||
if (!exists("ll_relabel_application", MARKER + "-RELABEL-KEY")) {
|
||||
relabel.create(CUSTOMER, "real-acceptance", new RelabelRequests.Create(
|
||||
MARKER + "-RELABEL", "92", RELABEL_DEADLINE, "验收联系人", "13800000000",
|
||||
MARKER, MARKER + "-RELABEL-KEY", List.of(new RelabelRequests.Item("SKU-REAL-A", null, null,
|
||||
new java.math.BigDecimal("20.00"), MARKER, MARKER + "-RELABEL-KEY", List.of(new RelabelRequests.Item("SKU-REAL-A", null, null,
|
||||
null, "真实验收商品", 3, 3, MARKER + "-SHIP", MARKER + "-TRACK", "SKU-REAL-A-NEW",
|
||||
"换标验收", null, null, null))));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user