This commit is contained in:
2026-08-21 10:38:28 +08:00
parent 361e6be3be
commit d5e9ba6d4c
21 changed files with 1705 additions and 15 deletions
@@ -2,6 +2,7 @@ package com.baiyue.lastleg.inventory.query.dto;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
@@ -10,8 +11,8 @@ public final class InventoryQueryRequests {
}
public record BalanceQuery(
@Min(1) long page,
@Min(1) @Max(200) long size,
@Min(1) @Schema(defaultValue = "1", minimum = "1") Long page,
@Min(1) @Max(200) @Schema(defaultValue = "20", minimum = "1", maximum = "200") Long size,
String customerName,
String overseasWarehouseId,
String overseasWarehouseCode,
@@ -26,8 +27,8 @@ public final class InventoryQueryRequests {
String inventoryStatus
) {
public BalanceQuery {
if (page == 0) page = 1;
if (size == 0) size = 20;
if (page == null || page == 0) page = 1L;
if (size == null || size == 0) size = 20L;
}
}
@@ -35,8 +36,8 @@ public final class InventoryQueryRequests {
}
public record LedgerQuery(
@Min(1) long page,
@Min(1) @Max(200) long size,
@Min(1) @Schema(defaultValue = "1", minimum = "1") Long page,
@Min(1) @Max(200) @Schema(defaultValue = "20", minimum = "1", maximum = "200") Long size,
String overseasWarehouseId,
String destinationWarehouseCode,
String businessType,
@@ -52,8 +53,8 @@ public final class InventoryQueryRequests {
LocalDateTime occurredTo
) {
public LedgerQuery {
if (page == 0) page = 1;
if (size == 0) size = 20;
if (page == null || page == 0) page = 1L;
if (size == null || size == 0) size = 20L;
}
}
}
@@ -40,6 +40,9 @@ public class PartnerWarehouseDirectory {
.map(value -> new OverseasQuoteResponses.PartnerWarehouseOption(value.id().toString(),
value.warehouseCode(), value.warehouseName(), value.countryCode()))
.toList();
// An empty directory can be a short-lived cross-region visibility gap after a partner
// warehouse is created. Do not preserve that absence for the normal 30-minute TTL.
if (options.isEmpty()) return List.of();
snapshot = new Snapshot(options, now.plus(ttl));
return snapshot.options();
}
@@ -18,7 +18,7 @@ class InventoryQueryControllerTest {
private final InventoryQueryService service = mock(InventoryQueryService.class);
private final InventoryQueryController controller = controller();
private final InventoryQueryRequests.BalanceQuery query = new InventoryQueryRequests.BalanceQuery(
1, 20, null, null, null, null, null, null, null, null, null, null, null, null);
1L, 20L, null, null, null, null, null, null, null, null, null, null, null, null);
@Test
void merchantCannotOverrideCustomerScope() {
@@ -105,7 +105,7 @@ class InventoryQueryServiceTest {
@Test
void reversedLedgerDateRangeIsRejected() {
InventoryQueryRequests.LedgerQuery query = new InventoryQueryRequests.LedgerQuery(
1, 20, null, null, null, null, null, null, null, null, null, null, null,
1L, 20L, null, null, null, null, null, null, null, null, null, null, null,
java.time.LocalDateTime.of(2026, 8, 18, 0, 0),
java.time.LocalDateTime.of(2026, 8, 17, 0, 0));
@@ -125,6 +125,19 @@ class InventoryQueryServiceTest {
}
private static InventoryQueryRequests.BalanceQuery balanceQuery(String status) {
return new InventoryQueryRequests.BalanceQuery(1, 20, null, null, null, null, null, null, null, null, null, null, null, status);
return new InventoryQueryRequests.BalanceQuery(1L, 20L, null, null, null, null, null, null, null, null, null, null, null, status);
}
@Test
void omittedPaginationDefaultsForSummaryAndExports() {
InventoryQueryRequests.BalanceQuery balances = new InventoryQueryRequests.BalanceQuery(
null, null, null, null, null, null, null, null, null, null, null, null, null, null);
InventoryQueryRequests.LedgerQuery ledgers = new InventoryQueryRequests.LedgerQuery(
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
assertEquals(1L, balances.page());
assertEquals(20L, balances.size());
assertEquals(1L, ledgers.page());
assertEquals(20L, ledgers.size());
}
}
@@ -71,6 +71,17 @@ class LocalLooseboardServiceTest {
assertEquals(409, error.getStatusCode().value());
}
@Test
void staleOrdinaryUpdateIsRejectedAsConflict() {
LocalLooseboardOrder order = order(LocalLooseboardService.PENDING_ACCEPTANCE, 2L);
when(orders.lockNo("100", "LB1")).thenReturn(order);
ResponseStatusException error = assertThrows(ResponseStatusException.class,
() -> service.update("100", "LB1", "7", update(1L)));
assertEquals(409, error.getStatusCode().value());
}
@Test
void statusTransitionWritesHistoryAndVersion() {
LocalLooseboardOrder order = order(LocalLooseboardService.PENDING_ACCEPTANCE, 0L);
@@ -0,0 +1,39 @@
package com.baiyue.lastleg.masterdata.quote.service;
import com.baiyue.lastleg.masterdata.quote.dto.OverseasQuoteRequests;
import com.baiyue.lastleg.masterdata.quote.entity.OverseasQuote;
import com.baiyue.lastleg.masterdata.quote.mapper.OverseasQuoteMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ResponseStatusException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class OverseasQuoteServiceTest {
private OverseasQuoteMapper mapper;
private OverseasQuoteService service;
@BeforeEach
void setUp() {
mapper = mock(OverseasQuoteMapper.class);
service = new OverseasQuoteService();
ReflectionTestUtils.setField(service, "quotes", mapper);
}
@Test
void rejectsStaleVersionAsConflict() {
OverseasQuote quote = new OverseasQuote();
quote.setId(1L);
quote.setRecordVersion(4L);
quote.setStatus(OverseasQuoteService.INACTIVE);
when(mapper.lockById(1L)).thenReturn(quote);
assertThatThrownBy(() -> service.changeStatus(1L, "9",
new OverseasQuoteRequests.ChangeStatus(OverseasQuoteService.ACTIVE, 3L)))
.isInstanceOf(ResponseStatusException.class)
.hasMessageContaining("409 CONFLICT");
}
}
@@ -53,4 +53,17 @@ class PartnerWarehouseDirectoryTest {
directory.options();
verify(client, times(2)).activePartners("internal-token");
}
@Test
void emptyDomesticResultIsNotCachedForThirtyMinutes() {
when(client.activePartners("internal-token"))
.thenReturn(ApiResponse.success(List.of()))
.thenReturn(ApiResponse.success(List.of(new DomesticPartnerWarehouseClient.Option(
101L, "US-ONT", "安大略合作仓", "US"))));
assertThat(directory.options()).isEmpty();
assertThat(directory.options()).singleElement().satisfies(option ->
assertThat(option.warehouseId()).isEqualTo("101"));
verify(client, times(2)).activePartners("internal-token");
}
}
@@ -84,6 +84,19 @@ class UsTruckOrderServiceTest {
assertEquals(409, error.getStatusCode().value());
}
@Test
void staleOrdinaryUpdateIsRejectedAsConflict() {
when(orders.lockNo("100", "TK1")).thenReturn(order(UsTruckOrderService.PENDING_REVIEW, 2L));
UsTruckOrderRequests.Update request = new UsTruckOrderRequests.Update(1L,
null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null);
ResponseStatusException error = assertThrows(ResponseStatusException.class,
() -> service.update("100", "TK1", "7", request));
assertEquals(409, error.getStatusCode().value());
}
@Test
void detailIncludesBoundPallets() {
UsTruckOrder order = order(UsTruckOrderService.WAITING_INBOUND, 1L);