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
@@ -51,7 +51,8 @@ class ReceivingContainerContractTest {
void everyBrowserReachableReceivingMethodUsesOnlyPdaOperatePermission() {
PreAuthorize permission = ReceivingContainerController.class.getAnnotation(PreAuthorize.class);
assertTrue(permission != null);
assertTrue("hasAuthority('lastleg:pda:operate')".equals(permission.value()));
assertTrue("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_WAREHOUSE_PICKER')"
.equals(permission.value()));
}
@Test
@@ -131,9 +132,9 @@ class ReceivingContainerContractTest {
String receivingPaths = yaml.substring(yaml.indexOf(prefix), yaml.indexOf("/api/platform/v1/last-leg/orders:"));
List<String> successResponses = receivingPaths.lines().map(String::trim)
.filter(line -> line.startsWith("responses: { '200':")).toList();
assertEquals(12, successResponses.size());
assertTrue(successResponses.stream().allMatch(line ->
line.contains("content: { application/json: { schema: { $ref: '#/components/schemas/Receiving")));
assertEquals(16, successResponses.size());
assertEquals(12, successResponses.stream().filter(line -> line.contains("/schemas/Receiving")).count());
assertEquals(4, successResponses.stream().filter(line -> line.contains("/schemas/Pda")).count());
assertTrue(receivingPaths.contains("返回 WAIT_SIGN、SIGNED 或 RECEIVING 当前柜"));
assertTrue(receivingPaths.contains("仅 PLATFORM WAREHOUSE_PICKER 可用"));
assertFalse(receivingPaths.contains("客户与操作账号均从 PDA Token 获取"));
@@ -3,6 +3,7 @@ package com.baiyue.lastleg.inboundplan.pda.controller;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.lastleg.inboundplan.pda.dto.ReceivingContainerResponses;
import com.baiyue.lastleg.inboundplan.pda.service.ReceivingContainerService;
import com.baiyue.lastleg.pda.security.PdaWarehouseIdentity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -57,13 +58,14 @@ class ReceivingContainerControllerTest {
@Test
void pickerAuthorityWithoutWarehouseBindingIsForbidden() {
AuthenticatedUser unbound = new AuthenticatedUser(42L, 0L, 0L, 8L, "receiver", "PLATFORM", "PDA",
Set.of(ReceivingContainerController.OPERATE_PERMISSION));
Set.of(PdaWarehouseIdentity.PICKER_ROLE, ReceivingContainerController.OPERATE_PERMISSION));
assertThatThrownBy(() -> controller.candidates(unbound)).isInstanceOf(ResponseStatusException.class)
.hasMessageContaining("403");
}
private static AuthenticatedUser picker() {
return new AuthenticatedUser(42L, 0L, 0L, 8L, "receiver", "PLATFORM", "PDA",
Set.of(ReceivingContainerController.OPERATE_PERMISSION), false, null, null, false, 900L);
Set.of(PdaWarehouseIdentity.PICKER_ROLE, ReceivingContainerController.OPERATE_PERMISSION),
false, null, null, false, 900L);
}
}
@@ -23,6 +23,7 @@ import com.baiyue.lastleg.inboundplan.pda.mapper.ReceivingContainerSessionMapper
import com.baiyue.lastleg.inboundplan.pda.mapper.ReceivingSkuReceiptMapper;
import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper;
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
import com.baiyue.lastleg.shipping.mapper.ContainerShippingGateMapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -57,6 +58,7 @@ class ReceivingContainerServiceBehaviorTest {
@Mock ReceivingContainerSessionMapper sessions;
@Mock ReceivingContainerOperationMapper operations;
@Mock ReceivingSkuReceiptMapper skuReceipts;
@Mock ContainerShippingGateMapper shippingGates;
@Mock WarehouseResourceGateway warehouse;
@Mock UnloadPhotoFileGateway unloadPhotos;
@Mock InboundPdaWorkflowService workflow;
@@ -75,11 +77,25 @@ class ReceivingContainerServiceBehaviorTest {
ReflectionTestUtils.setField(service, "sessions", sessions);
ReflectionTestUtils.setField(service, "operations", operations);
ReflectionTestUtils.setField(service, "skuReceipts", skuReceipts);
ReflectionTestUtils.setField(service, "shippingGates", shippingGates);
ReflectionTestUtils.setField(service, "warehouse", warehouse);
ReflectionTestUtils.setField(service, "unloadPhotos", unloadPhotos);
ReflectionTestUtils.setField(service, "workflow", workflow);
ReflectionTestUtils.setField(service, "sourceObjectMapper", new ObjectMapper().findAndRegisterModules());
service.initializeObjectMapper();
org.mockito.Mockito.lenient().when(shippingGates.receivingAvailable(any(), any())).thenReturn(1);
}
@Test
void containerIsHiddenAndPrepareRejectedBeforePickupReady() {
when(plans.selectList(any())).thenReturn(List.of(plan("CONT-A")));
when(shippingGates.receivingAvailable("100", "CONT-A")).thenReturn(0);
assertThat(service.candidates("7", 100L)).isEmpty();
assertThatThrownBy(() -> service.prepare("7", "receiver", 100L, "CONT-A",
new ReceivingContainerRequests.Action("not-ready")))
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("409");
verifyNoInteractions(warehouse);
}
@Test
@@ -315,6 +331,16 @@ class ReceivingContainerServiceBehaviorTest {
verifyNoInteractions(unloadPhotos);
}
@Test
void signRejectsMissingPhotosBeforeFileValidation() {
when(sessions.lockActiveByOperator("OPERATOR:7")).thenReturn(session("CONT-A", "WAIT_SIGN"));
ReceivingContainerRequests.Sign request = new ReceivingContainerRequests.Sign(List.of(), "missing-photos");
assertThatThrownBy(() -> service.sign("7", "receiver", request))
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("409");
verifyNoInteractions(unloadPhotos);
}
@Test
void signRejectsDuplicateFileIdsBeforeFileValidation() {
when(sessions.lockActiveByOperator("OPERATOR:7")).thenReturn(session("CONT-A", "WAIT_SIGN"));