feat: gate overseas receiving and harden phase4 workflows

This commit is contained in:
2026-08-25 10:31:04 +08:00
parent cb78bc6b41
commit 52b9fc382f
93 changed files with 2779 additions and 201 deletions
@@ -0,0 +1,78 @@
package com.baiyue.lastleg.pda.warehouse;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.lastleg.pda.security.PdaWarehouseIdentity;
import com.baiyue.lastleg.pda.warehouse.controller.PdaWarehouseController;
import com.baiyue.lastleg.pda.warehouse.dto.PdaWarehouseRequests;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.server.ResponseStatusException;
import java.lang.reflect.RecordComponent;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class PdaWarehouseContractTest {
@Test
void requestsNeverAcceptServerOwnedIdentityOrResourceIds() {
Set<String> forbidden = Set.of("customerId", "warehouseId", "planNo", "allocationId", "operatorId");
for (Class<?> type : PdaWarehouseRequests.class.getDeclaredClasses()) {
if (!type.isRecord()) continue;
Set<String> fields = Arrays.stream(type.getRecordComponents()).map(RecordComponent::getName)
.collect(Collectors.toSet());
assertThat(fields).doesNotContainAnyElementsOf(forbidden);
}
}
@Test
void controllerRequiresPickerRoleAndPdaAuthority() {
PreAuthorize permission = PdaWarehouseController.class.getAnnotation(PreAuthorize.class);
assertThat(permission.value()).isEqualTo(
"hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_WAREHOUSE_PICKER')");
}
@Test
void identityRejectsUnboundOrNonPickerPlatformAccounts() {
AuthenticatedUser unbound = user(Set.of(PdaWarehouseIdentity.PICKER_ROLE,
PdaWarehouseIdentity.OPERATE_PERMISSION), null);
assertThatThrownBy(() -> PdaWarehouseIdentity.require(unbound))
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("403");
AuthenticatedUser admin = user(Set.of(PdaWarehouseIdentity.OPERATE_PERMISSION), 900L);
assertThatThrownBy(() -> PdaWarehouseIdentity.require(admin))
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("403");
assertThat(PdaWarehouseIdentity.require(user(Set.of(PdaWarehouseIdentity.PICKER_ROLE,
PdaWarehouseIdentity.OPERATE_PERMISSION), 900L)).overseasWarehouseId()).isEqualTo(900L);
}
@Test
void baselineContainsShippingGateAndRealOperationHistory() throws Exception {
String sql = Files.readString(Path.of("src/main/resources/db/lastleg.sql")).toLowerCase();
assertThat(sql).contains("create table if not exists ll_container_shipping_gate")
.contains("awaiting_pickup_ready")
.contains("receiving_available")
.contains("create table if not exists ll_pda_operation_history")
.contains("unique key uk_ll_pda_history_idempotency(operator_key,idempotency_key)");
}
@Test
void openApiAndGatewayPublishOnlyStablePdaPaths() throws Exception {
String openapi = Files.readString(Path.of("src/main/resources/openapi/overseas-business-v1.yaml"));
for (String path : Set.of("/api/pda/v1/last-leg/inventory/resolve:",
"/api/pda/v1/last-leg/locations/resolve:", "/api/pda/v1/last-leg/pallets/resolve:",
"/api/pda/v1/last-leg/operations:")) assertThat(openapi).contains(path);
assertThat(openapi).doesNotContain("/api/pda/v1/last-leg/inbound-plans/{planNo}");
String gateway = Files.readString(Path.of("../gateway-overseas/src/main/resources/application-overseas.yml"));
assertThat(gateway).contains("/api/pda/v1/last-leg/**").contains("lb://lastleg-service");
}
private static AuthenticatedUser user(Set<String> authorities, Long warehouseId) {
return new AuthenticatedUser(42L, 0L, 0L, 8L, "picker", "PLATFORM", "PDA",
authorities, false, null, null, false, warehouseId);
}
}
@@ -0,0 +1,71 @@
package com.baiyue.lastleg.pda.warehouse.service;
import com.baiyue.lastleg.inboundplan.pda.gateway.WarehouseResourceGateway;
import com.baiyue.lastleg.pda.warehouse.dto.PdaWarehouseRequests;
import com.baiyue.lastleg.pda.warehouse.mapper.PdaWarehouseMapper;
import com.baiyue.lastleg.pda.warehouse.model.PdaInventoryProjection;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ResponseStatusException;
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.when;
@ExtendWith(MockitoExtension.class)
class PdaWarehouseServiceTest {
@Mock PdaWarehouseMapper mapper;
@Mock WarehouseResourceGateway warehouse;
private PdaWarehouseService service;
@BeforeEach
void setUp() {
service = new PdaWarehouseService();
ReflectionTestUtils.setField(service, "mapper", mapper);
ReflectionTestUtils.setField(service, "warehouse", warehouse);
}
@Test
void inventoryScanIsBoundToTokenWarehouse() {
when(mapper.scanInventory("900", "SKU-1")).thenReturn(List.of(inventory()));
var result = service.inventory(900L, new PdaWarehouseRequests.Scan("SKU-1"));
assertThat(result.items()).hasSize(1);
assertThat(result.items().get(0).customerId()).isEqualTo("100");
}
@Test
void palletFromAnotherWarehouseIsRejected() {
when(warehouse.resolvePallet("PAL-1")).thenReturn(new WarehouseResourceGateway.WarehousePalletReference(
1L, "PAL-1", 901L, 2L, "OCCUPIED", null, 1));
assertThatThrownBy(() -> service.pallet(900L, new PdaWarehouseRequests.Scan("PAL-1")))
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("409");
}
@Test
void locationCombinesPhysicalCapacityAndInventoryOccupation() {
when(warehouse.resolveLocation(900L, "A-01")).thenReturn(
new WarehouseResourceGateway.WarehouseLocationReference(2L, "A-01", "STORAGE",
new BigDecimal("1000"), "OCCUPIED"));
when(mapper.inventoryByLocation("900", 2L, "A-01", "__NO_CARTON__"))
.thenReturn(List.of(inventory()));
var result = service.location(900L, new PdaWarehouseRequests.Scan("A-01"));
assertThat(result.occupiedPackages()).isEqualTo(5);
assertThat(result.inventoryPositionCount()).isEqualTo(1);
}
private static PdaInventoryProjection inventory() {
PdaInventoryProjection value = new PdaInventoryProjection(); value.setCustomerId("100");
value.setBalanceId(10L); value.setPositionId(11L); value.setSkuCode("SKU-1");
value.setInboundCode("IN-1"); value.setPalletId(12L); value.setPalletCode("PAL-1");
value.setLocationId(2L); value.setLocationCode("A-01"); value.setAvailablePackages(5L);
value.setHoldPackages(0L); value.setReceivingPackages(0L); value.setVersion(1L); value.setStatus("OCCUPIED");
return value;
}
}