优化
This commit is contained in:
+59
@@ -0,0 +1,59 @@
|
||||
package com.baiyue.warehouse.controller;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.warehouse.dto.PalletRequests;
|
||||
import com.baiyue.warehouse.service.PalletApplicationService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class InternalPalletControllerTest {
|
||||
private static final String TOKEN = "test-internal-token";
|
||||
private final PalletApplicationService service = mock(PalletApplicationService.class);
|
||||
private final InternalPalletController controller = new InternalPalletController();
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
ReflectionTestUtils.setField(controller, "service", service);
|
||||
ReflectionTestUtils.setField(controller, "internalToken", TOKEN);
|
||||
controller.initializeToken();
|
||||
}
|
||||
|
||||
@Test
|
||||
void readsSnapshotFromPlatformScope() {
|
||||
PalletRequests.Snapshot expected = snapshot(30L, "TP-001");
|
||||
when(service.snapshot(0L, 20L, 30L)).thenReturn(expected);
|
||||
|
||||
assertThat(controller.snapshot(TOKEN, 30L, 20L).data()).isEqualTo(expected);
|
||||
verify(service).snapshot(0L, 20L, 30L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void listsAllWarehouseSnapshotsFromPlatformScope() {
|
||||
List<PalletRequests.Snapshot> expected = List.of(snapshot(30L, "TP-001"), snapshot(31L, "TP-002"));
|
||||
when(service.snapshots(0L, 20L)).thenReturn(expected);
|
||||
|
||||
assertThat(controller.snapshots(TOKEN, 20L).data()).isEqualTo(expected);
|
||||
verify(service).snapshots(0L, 20L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsInvalidInternalToken() {
|
||||
assertThatThrownBy(() -> controller.snapshots("invalid", 20L)).isInstanceOf(BizException.class);
|
||||
verifyNoInteractions(service);
|
||||
}
|
||||
|
||||
private static PalletRequests.Snapshot snapshot(long id, String code) {
|
||||
return new PalletRequests.Snapshot(id, code, "STANDARD", new BigDecimal("1.2"), new BigDecimal("8"));
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.baiyue.warehouse.controller;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PalletOpenApiContractTest {
|
||||
@Test
|
||||
void freezesInternalSnapshotPathsAndFields() throws IOException {
|
||||
String yaml = Files.readString(Path.of("src/main/resources/openapi/pallet-v1.yaml"));
|
||||
|
||||
assertThat(yaml)
|
||||
.contains("/internal/warehouse/v1/pallets/{palletId}/snapshot:")
|
||||
.contains("/internal/warehouse/v1/pallets/snapshots:")
|
||||
.contains("required: [palletId, palletCode, palletType, capacityCbm, tareWeightKg]")
|
||||
.contains("additionalProperties: false")
|
||||
.doesNotContain("tenantId:")
|
||||
.doesNotContain("available:")
|
||||
.doesNotContain("status:");
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -10,7 +10,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class InitialWarehouseResourceSeedSqlContractTest {
|
||||
@Test
|
||||
void initializesAvailablePalletAndLocationsForBothDomesticWarehouses() throws Exception {
|
||||
void initializesReusablePalletMasterDataAndAvailableLocationsForBothDomesticWarehouses() throws Exception {
|
||||
assertModuleSeed(Files.readString(Path.of("src", "main", "resources", "db", "phase2-pallet-seed.sql"),
|
||||
StandardCharsets.UTF_8));
|
||||
assertUnifiedSeed(Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"),
|
||||
@@ -19,6 +19,7 @@ class InitialWarehouseResourceSeedSqlContractTest {
|
||||
|
||||
private static void assertModuleSeed(String sql) {
|
||||
assertSeed(sql, "190000000000000001", "190000000000000002", "190000000000000");
|
||||
assertThat(sql).doesNotContain("color,status,remark");
|
||||
}
|
||||
|
||||
private static void assertUnifiedSeed(String sql) {
|
||||
|
||||
+38
-6
@@ -44,21 +44,29 @@ class WarehouseSchemaContractTest {
|
||||
void shouldHandleCase04() throws IOException {
|
||||
String sql = read("phase2-pallet-location-schema.sql");
|
||||
assertColumns(sql, "wh_pallet", "tenant_id", "warehouse_id", "pallet_code", "pallet_type",
|
||||
"capacity_cbm", "weight_kg", "color", "status", "remark", "deleted", "version");
|
||||
assertThat(sql).contains("uk_pallet_tenant_code (tenant_id, pallet_code)");
|
||||
"capacity_cbm", "weight_kg", "color", "remark", "deleted", "version");
|
||||
assertThat(tableDefinition(sql, "wh_pallet"))
|
||||
.contains("uk_pallet_tenant_code (tenant_id, pallet_code)")
|
||||
.contains("idx_pallet_warehouse (tenant_id, warehouse_id, deleted, pallet_code)")
|
||||
.doesNotContain("status VARCHAR")
|
||||
.doesNotContain("AVAILABLE")
|
||||
.doesNotContain("RESERVED")
|
||||
.doesNotContain("OCCUPIED");
|
||||
assertColumns(sql, "wh_location", "tenant_id", "warehouse_id", "zone_code", "zone_name",
|
||||
"location_code", "location_type", "max_weight_kg", "shared_overflow", "status", "remark", "deleted", "version");
|
||||
assertColumns(sql, "wh_location_reservation", "idempotency_key", "business_id", "pallet_id",
|
||||
"location_id", "shared_overflow", "reservation_status", "expires_at",
|
||||
"active_reservation", "active_pallet_reservation", "version");
|
||||
"active_reservation", "version");
|
||||
assertColumns(sql, "wh_location_occupancy", "reservation_id", "business_id", "pallet_id",
|
||||
"location_id", "shared_overflow", "occupancy_status",
|
||||
"active_location_occupancy", "active_pallet_occupancy", "version");
|
||||
"active_location_occupancy", "version");
|
||||
assertThat(sql).contains("uk_location_tenant_warehouse_code")
|
||||
.contains("uk_location_active_reservation")
|
||||
.contains("uk_pallet_active_reservation (tenant_id, pallet_id, active_pallet_reservation)")
|
||||
.contains("uk_location_active_occupancy")
|
||||
.contains("uk_pallet_active_occupancy");
|
||||
.doesNotContain("active_pallet_reservation")
|
||||
.doesNotContain("active_pallet_occupancy")
|
||||
.doesNotContain("uk_pallet_active_reservation")
|
||||
.doesNotContain("uk_pallet_active_occupancy");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -112,6 +120,24 @@ class WarehouseSchemaContractTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void unifiedSqlShouldConvergePalletsToReusableMasterData() throws IOException {
|
||||
String unified = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"));
|
||||
String palletDefinition = tableDefinition(unified, "wh_pallet");
|
||||
|
||||
assertThat(palletDefinition)
|
||||
.contains("idx_pallet_warehouse (tenant_id, warehouse_id, deleted, pallet_code)")
|
||||
.contains("idx_pallet_type (tenant_id, warehouse_id, pallet_type, capacity_cbm, deleted)")
|
||||
.doesNotContain("status VARCHAR")
|
||||
.doesNotContain("idx_pallet_available");
|
||||
assertThat(unified)
|
||||
.contains("ALTER TABLE wh_pallet DROP COLUMN status")
|
||||
.contains("ALTER TABLE wh_location_reservation DROP COLUMN active_pallet_reservation")
|
||||
.contains("ALTER TABLE wh_location_occupancy DROP COLUMN active_pallet_occupancy")
|
||||
.contains("托盘动态资源状态或活动唯一约束仍存在")
|
||||
.contains("库位活动预占或占用唯一约束不完整");
|
||||
}
|
||||
|
||||
private static String read(String file) throws IOException {
|
||||
return Files.readString(Path.of("src/main/resources/db", file));
|
||||
}
|
||||
@@ -133,4 +159,10 @@ class WarehouseSchemaContractTest {
|
||||
int end = sql.indexOf(' ', start);
|
||||
return sql.substring(start, end);
|
||||
}
|
||||
|
||||
private static String tableDefinition(String sql, String table) {
|
||||
int start = sql.indexOf("CREATE TABLE IF NOT EXISTS " + table + " (");
|
||||
int end = sql.indexOf("ENGINE=InnoDB", start);
|
||||
return sql.substring(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
+21
-4
@@ -27,7 +27,7 @@ class PalletApplicationServiceTest {
|
||||
|
||||
@Test
|
||||
void shouldHandleCase01() {
|
||||
PalletEntity entity = pallet("OCCUPIED");
|
||||
PalletEntity entity = pallet();
|
||||
when(repository.findOwned(10001, 20, 30)).thenReturn(entity);
|
||||
org.assertj.core.api.Assertions.assertThat(service.detail(10001, 20, 30))
|
||||
.extracting("id", "palletCode").containsExactly(30L, null);
|
||||
@@ -41,7 +41,7 @@ class PalletApplicationServiceTest {
|
||||
@Test
|
||||
void pagesWithoutTenantFilter() {
|
||||
PalletRequests.PageQuery query = new PalletRequests.PageQuery(1, 20, null, null, null);
|
||||
when(repository.page(null, query)).thenReturn(new PageResult<>(java.util.List.of(pallet("AVAILABLE")),
|
||||
when(repository.page(null, query)).thenReturn(new PageResult<>(java.util.List.of(pallet()),
|
||||
1, 20, 1, 1));
|
||||
|
||||
org.assertj.core.api.Assertions.assertThat(service.page(null, query).records())
|
||||
@@ -63,12 +63,29 @@ class PalletApplicationServiceTest {
|
||||
.hasMessageContaining("仅支持国内仓");
|
||||
}
|
||||
|
||||
private static PalletEntity pallet(String status) {
|
||||
@Test
|
||||
void returnsReusablePalletSnapshotsWithoutResourceStatus() {
|
||||
WarehouseEntity warehouse = new WarehouseEntity();
|
||||
warehouse.setWarehouseType("DOMESTIC");
|
||||
when(warehouseRepository.findOwned(0L, 20L)).thenReturn(warehouse);
|
||||
PalletEntity entity = pallet();
|
||||
entity.setPalletCode("TP-001");
|
||||
entity.setPalletType("STANDARD");
|
||||
entity.setCapacityCbm(new BigDecimal("1.200000"));
|
||||
entity.setWeightKg(new BigDecimal("8.000"));
|
||||
when(repository.findAllByWarehouse(0L, 20L)).thenReturn(java.util.List.of(entity));
|
||||
|
||||
org.assertj.core.api.Assertions.assertThat(service.snapshots(0L, 20L)).singleElement().satisfies(snapshot -> {
|
||||
org.assertj.core.api.Assertions.assertThat(snapshot.palletId()).isEqualTo(30L);
|
||||
org.assertj.core.api.Assertions.assertThat(snapshot.tareWeightKg()).isEqualByComparingTo("8.000");
|
||||
});
|
||||
}
|
||||
|
||||
private static PalletEntity pallet() {
|
||||
PalletEntity entity = new PalletEntity();
|
||||
entity.setId(30L);
|
||||
entity.setTenantId(10001L);
|
||||
entity.setWarehouseId(20L);
|
||||
entity.setStatus(status);
|
||||
entity.setVersion(0);
|
||||
return entity;
|
||||
}
|
||||
|
||||
+26
-24
@@ -52,22 +52,35 @@ class PalletLocationAllocationServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleCase01() {
|
||||
void reusesOnePalletMasterForMultipleAllocations() {
|
||||
when(locations.findActiveResources(10001, "FIRSTLEG", 88)).thenReturn(List.of());
|
||||
when(pallets.findAvailable(10001, 20, "木托盘", new BigDecimal("2"), 1))
|
||||
when(pallets.findReusable(10001, 20, "木托盘", new BigDecimal("2")))
|
||||
.thenReturn(List.of(pallet(30, "TP-001")));
|
||||
when(pallets.transitionStatus(10001, 20, 30, List.of("AVAILABLE"), "RESERVED")).thenReturn(0);
|
||||
when(locations.reserveAvailable(any())).thenReturn(List.of(
|
||||
new LocationDtos.ReservationResult(101, "LR101", 30, "TP-001", 40, "A-01", false,
|
||||
LocalDateTime.now().plusMinutes(30)),
|
||||
new LocationDtos.ReservationResult(102, "LR102", 30, "TP-001", 41, "A-02", false,
|
||||
LocalDateTime.now().plusMinutes(30))));
|
||||
|
||||
assertThatThrownBy(() -> service.reserve(reserve(88))).isInstanceOf(BizException.class);
|
||||
verify(locations, never()).reserveAvailable(any());
|
||||
var request = new PalletAllocationDtos.ReserveRequest(10001, 20, "FIRSTLEG", 88, "RK-001", "measure-001",
|
||||
9001, new BigDecimal("4"), new BigDecimal("2"), "木托盘", LocalDateTime.now().plusMinutes(30), true);
|
||||
assertThat(service.reserve(request).allocations()).hasSize(2)
|
||||
.extracting(PalletAllocationDtos.AllocationResult::palletId)
|
||||
.containsOnly(30L);
|
||||
|
||||
org.mockito.ArgumentCaptor<LocationDtos.ReserveCommand> command =
|
||||
org.mockito.ArgumentCaptor.forClass(LocationDtos.ReserveCommand.class);
|
||||
verify(locations).reserveAvailable(command.capture());
|
||||
assertThat(command.getValue().targets()).hasSize(2)
|
||||
.extracting(LocationDtos.ReservationTarget::palletId)
|
||||
.containsOnly(30L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleCase02() {
|
||||
when(locations.findActiveResources(10001, "FIRSTLEG", 99)).thenReturn(List.of());
|
||||
when(pallets.findAvailable(10001, 20, "STANDARD", new BigDecimal("2"), 1))
|
||||
when(pallets.findReusable(10001, 20, "STANDARD", new BigDecimal("2")))
|
||||
.thenReturn(List.of(pallet(30, "TP-001")));
|
||||
when(pallets.transitionStatus(10001, 20, 30, List.of("AVAILABLE"), "RESERVED")).thenReturn(1);
|
||||
when(locations.reserveAvailable(any())).thenReturn(List.of(
|
||||
new LocationDtos.ReservationResult(101, "LR101", 30, "TP-001", 40, "A0", true,
|
||||
LocalDateTime.now().plusMinutes(30))));
|
||||
@@ -77,7 +90,7 @@ class PalletLocationAllocationServiceTest {
|
||||
var result = service.reserve(request);
|
||||
|
||||
assertThat(result.allocations().get(0).locationCode()).isEqualTo("A0");
|
||||
verify(pallets).findAvailable(10001, 20, "STANDARD", new BigDecimal("2"), 1);
|
||||
verify(pallets).findReusable(10001, 20, "STANDARD", new BigDecimal("2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -86,13 +99,12 @@ class PalletLocationAllocationServiceTest {
|
||||
101, null, 20, 30, "TP-001", 40, "A-01", false, "RESERVED");
|
||||
when(locations.findActiveResources(10001, "FIRSTLEG", 88)).thenReturn(List.of(resource));
|
||||
when(locations.release(any())).thenReturn(List.of(resource));
|
||||
when(pallets.transitionStatus(10001, 20, 30, List.of("RESERVED"), "AVAILABLE")).thenReturn(1);
|
||||
|
||||
List<LocationDtos.ActiveResource> released = service.release(
|
||||
new PalletAllocationDtos.ReleaseRequest(10001, "FIRSTLEG", 88, 9001, "入库补偿"));
|
||||
|
||||
assertThat(released).containsExactly(resource);
|
||||
verify(pallets).transitionStatus(10001, 20, 30, List.of("RESERVED"), "AVAILABLE");
|
||||
verifyNoInteractions(pallets);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -117,7 +129,7 @@ class PalletLocationAllocationServiceTest {
|
||||
|
||||
assertThat(result.palletCount()).isEqualTo(1);
|
||||
assertThat(result.allocations().get(0).state()).isEqualTo("RESERVED");
|
||||
verify(pallets, never()).findAvailable(any(Long.class), any(Long.class), any(), any(), any(Integer.class));
|
||||
verify(pallets, never()).findReusable(any(Long.class), any(Long.class), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -127,8 +139,6 @@ class PalletLocationAllocationServiceTest {
|
||||
when(locations.findActiveResources(10001L, "FIRSTLEG", 88L)).thenReturn(List.of(resource));
|
||||
when(locations.confirmOccupied(any())).thenReturn(List.of(new LocationDtos.OccupancyResult(
|
||||
301L, "OC-301", 101L, 30L, 40L, "A-01", false)));
|
||||
when(pallets.transitionStatus(10001L, 20L, 30L, List.of("RESERVED"), "OCCUPIED"))
|
||||
.thenReturn(1);
|
||||
|
||||
service.confirm(new PalletAllocationDtos.ConfirmRequest(10001L, 20L, "FIRSTLEG", 88L,
|
||||
9001L, new BigDecimal("1.0"), new BigDecimal("2.0"), 20));
|
||||
@@ -153,8 +163,6 @@ class PalletLocationAllocationServiceTest {
|
||||
"TP-" + index, locationId, "A-" + index, false, "RESERVED"));
|
||||
occupancyResults.add(new LocationDtos.OccupancyResult(400L + index, "OC-" + index,
|
||||
reservationId, palletId, locationId, "A-" + index, false));
|
||||
when(pallets.transitionStatus(10001L, 20L, palletId, List.of("RESERVED"), "OCCUPIED"))
|
||||
.thenReturn(1);
|
||||
}
|
||||
when(locations.findActiveResources(10001L, "FIRSTLEG", 89L)).thenReturn(resources);
|
||||
when(locations.confirmOccupied(any())).thenReturn(occupancyResults);
|
||||
@@ -198,8 +206,6 @@ class PalletLocationAllocationServiceTest {
|
||||
});
|
||||
when(releaseOperations.lockByKey(anyLong(), anyString(), anyLong(), anyString())).thenReturn(operation);
|
||||
when(locations.releaseSelectedExactly(any())).thenReturn(List.of(resource));
|
||||
when(pallets.transitionStatus(10001L, 20L, 30L, List.of("OCCUPIED"), "AVAILABLE"))
|
||||
.thenReturn(1);
|
||||
|
||||
var result = service.releaseSelected(new PalletAllocationDtos.SelectiveReleaseRequest(
|
||||
10001L, "FIRSTLEG", 88L, "pda-release-88-202", 9001L, "PDA partial pick", List.of(40L)));
|
||||
@@ -209,7 +215,7 @@ class PalletLocationAllocationServiceTest {
|
||||
assertThat(item.locationId()).isEqualTo(40L);
|
||||
assertThat(item.allocatedPackageCount()).isEqualTo(4);
|
||||
});
|
||||
verify(pallets).transitionStatus(10001L, 20L, 30L, List.of("OCCUPIED"), "AVAILABLE");
|
||||
verifyNoInteractions(pallets);
|
||||
verify(releaseOperations).complete(org.mockito.ArgumentMatchers.eq(701L), anyString());
|
||||
}
|
||||
|
||||
@@ -265,14 +271,12 @@ class PalletLocationAllocationServiceTest {
|
||||
.thenReturn(operation);
|
||||
when(locations.releaseSelectedReservations(any())).thenReturn(List.of(new LocationDtos.ExactSelectiveReleaseResult(
|
||||
101L, 201L, 1, 20L, 30L, 40L, "A-01", 8)));
|
||||
when(pallets.transitionStatus(0L, 20L, 30L, List.of("OCCUPIED"), "AVAILABLE")).thenReturn(1);
|
||||
when(locations.transferToChildByReservation(any())).thenReturn(List.of(new LocationDtos.TransferResult(
|
||||
102L, "LR102", 202L, 2, 20L, 31L, "TP-002", 41L, "A-02",
|
||||
new BigDecimal("1.200000"), 12, "OCCUPIED")));
|
||||
PalletEntity pallet = pallet(31L, "TP-002");
|
||||
pallet.setStatus("OCCUPIED");
|
||||
pallet.setCapacityCbm(new BigDecimal("1.500000"));
|
||||
when(pallets.findOwned(0L, 20L, 31L)).thenReturn(pallet);
|
||||
when(pallets.findOwnedIncludingDeleted(0L, 20L, 31L)).thenReturn(pallet);
|
||||
|
||||
var result = service.settle(settlementRequest());
|
||||
|
||||
@@ -407,9 +411,8 @@ class PalletLocationAllocationServiceTest {
|
||||
101L, "LR101", 201L, 1, 20L, 30L, "TP-001", 40L, "A-01",
|
||||
new BigDecimal("1.200000"), 5, "OCCUPIED")));
|
||||
PalletEntity pallet = pallet(30L, "TP-001");
|
||||
pallet.setStatus("OCCUPIED");
|
||||
pallet.setCapacityCbm(new BigDecimal("1.500000"));
|
||||
when(pallets.findOwned(10001L, 20L, 30L)).thenReturn(pallet);
|
||||
when(pallets.findOwnedIncludingDeleted(10001L, 20L, 30L)).thenReturn(pallet);
|
||||
|
||||
var result = service.transfer(new PalletAllocationDtos.TransferRequest(
|
||||
10001L, "FIRSTLEG", 88L, 89L, "RK-001-S1", "transfer-88-89", 9001L,
|
||||
@@ -421,7 +424,6 @@ class PalletLocationAllocationServiceTest {
|
||||
assertThat(item.remainingPackageCount()).isEqualTo(5);
|
||||
assertThat(item.state()).isEqualTo("OCCUPIED");
|
||||
});
|
||||
verify(pallets, never()).transitionStatus(10001L, 20L, 30L, List.of("OCCUPIED"), "AVAILABLE");
|
||||
verify(transferOperations).complete(org.mockito.ArgumentMatchers.eq(501L), anyString());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user