This commit is contained in:
2026-08-28 10:38:58 +08:00
parent b34fbb2bd1
commit 1e5ce54858
37 changed files with 256 additions and 159 deletions
@@ -34,7 +34,7 @@ import java.util.List;
@RestController
@RequestMapping("/api/pda/v1/last-leg/receiving-containers")
@Tag(name = "Overseas PDA container receiving")
@PreAuthorize("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_WAREHOUSE_PICKER')")
@PreAuthorize("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_OVERSEAS_WAREHOUSE_PDA_OPERATOR')")
public class ReceivingContainerController {
public static final String OPERATE_PERMISSION = PdaWarehouseIdentity.OPERATE_PERMISSION;
@Resource private ReceivingContainerService service;
@@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/pda/v1/last-leg/pallet-operations")
@PreAuthorize("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_WAREHOUSE_PICKER')")
@PreAuthorize("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_OVERSEAS_WAREHOUSE_PDA_OPERATOR')")
public class PdaPalletOperationController {
@Resource private PdaPalletOperationService service;
@@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/pda/v1/last-leg/inventory-moves")
@PreAuthorize("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_WAREHOUSE_PICKER')")
@PreAuthorize("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_OVERSEAS_WAREHOUSE_PDA_OPERATOR')")
public class PdaInventoryMoveController {
@Resource private PdaInventoryMoveService service;
@@ -6,7 +6,7 @@ import org.springframework.web.server.ResponseStatusException;
public record PdaWarehouseIdentity(String operatorKey, String operatorName, long overseasWarehouseId) {
public static final String OPERATE_PERMISSION = "lastleg:pda:operate";
public static final String PICKER_ROLE = "ROLE_WAREHOUSE_PICKER";
public static final String PICKER_ROLE = "ROLE_OVERSEAS_WAREHOUSE_PDA_OPERATOR";
public static PdaWarehouseIdentity require(AuthenticatedUser user) {
if (user == null || user.userId() == null || user.tenantId() == null || user.tenantId() != 0
@@ -15,7 +15,7 @@ public record PdaWarehouseIdentity(String operatorKey, String operatorName, long
"Authenticated platform warehouse PDA identity is required");
}
if (!user.permissions().contains(PICKER_ROLE)) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "WAREHOUSE_PICKER role is required");
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "OVERSEAS_WAREHOUSE_PDA_OPERATOR role is required");
}
if (!user.permissions().contains(OPERATE_PERMISSION)) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, OPERATE_PERMISSION + " permission is required");
@@ -20,7 +20,7 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/pda/v1/last-leg/stocktakes")
@PreAuthorize("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_WAREHOUSE_PICKER')")
@PreAuthorize("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_OVERSEAS_WAREHOUSE_PDA_OPERATOR')")
public class PdaStocktakeController {
@Resource private PdaStocktakeService service;
@@ -19,7 +19,7 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/pda/v1/last-leg")
@PreAuthorize("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_WAREHOUSE_PICKER')")
@PreAuthorize("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_OVERSEAS_WAREHOUSE_PDA_OPERATOR')")
public class PdaWarehouseController {
@Resource private PdaWarehouseService service;
@@ -28,7 +28,7 @@ import java.util.List;
@PreAuthorize("hasAuthority('lastleg:pda:operate')")
public class PdaRelabelController {
public static final String OPERATE_PERMISSION = "lastleg:pda:operate";
private static final String PICKER_ROLE = "ROLE_WAREHOUSE_PICKER";
private static final String PICKER_ROLE = "ROLE_OVERSEAS_WAREHOUSE_PDA_OPERATOR";
@Resource private PdaRelabelService service;
@@ -111,7 +111,7 @@ public class PdaRelabelController {
if (user.permissions() == null || !user.permissions().contains(OPERATE_PERMISSION)
|| !user.permissions().contains(PICKER_ROLE)) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN,
"WAREHOUSE_PICKER and " + OPERATE_PERMISSION + " are required");
"OVERSEAS_WAREHOUSE_PDA_OPERATOR and " + OPERATE_PERMISSION + " are required");
}
if (user.overseasWarehouseId() == null || user.overseasWarehouseId() <= 0L) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN,
@@ -6,7 +6,7 @@ paths:
/api/pda/v1/last-leg/receiving-containers/candidates:
get:
summary: 查询当前客户待签收柜号
description: 权限 lastleg:pda:operate;仅 PLATFORM WAREHOUSE_PICKER 可用;仅返回已单调消费 PICKUP_READY(80) 或更高实际船节点、状态为 RECEIVING_AVAILABLE 的柜,不接收 customerId。
description: 权限 lastleg:pda:operate;仅 PLATFORM OVERSEAS_WAREHOUSE_PDA_OPERATOR 可用;仅返回已单调消费 PICKUP_READY(80) 或更高实际船节点、状态为 RECEIVING_AVAILABLE 的柜,不接收 customerId。
security: [{ bearerAuth: [] }]
responses: { '200': { description: 柜号候选及箱数进度, content: { application/json: { schema: { $ref: '#/components/schemas/ReceivingCandidateListApiResponse' } } } }, '403': { description: 缺少 PDA 操作权限 } }
/api/pda/v1/last-leg/receiving-containers/warehouse-profile:
@@ -107,7 +107,7 @@ paths:
/api/pda/v1/last-leg/inventory/resolve:
post:
summary: PDA按SKU、箱唛或入仓码扫码查询库存
description: 仅 WAREHOUSE_PICKER + lastleg:pda:operate;海外实际仓只取Token overseasWarehouseId,不接收客户或仓库参数。
description: 仅 OVERSEAS_WAREHOUSE_PDA_OPERATOR + lastleg:pda:operate;海外实际仓只取Token overseasWarehouseId,不接收客户或仓库参数。
security: [{ bearerAuth: [] }]
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/PdaWarehouseScan' } } } }
responses: { '200': { description: 同仓跨客户库存与物理仓位事实, content: { application/json: { schema: { $ref: '#/components/schemas/PdaInventoryScanApiResponse' } } } }, '404': { description: A0404:当前绑定仓无匹配库存 } }
@@ -51,7 +51,7 @@ class ReceivingContainerContractTest {
void everyBrowserReachableReceivingMethodUsesOnlyPdaOperatePermission() {
PreAuthorize permission = ReceivingContainerController.class.getAnnotation(PreAuthorize.class);
assertTrue(permission != null);
assertTrue("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_WAREHOUSE_PICKER')"
assertTrue("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_OVERSEAS_WAREHOUSE_PDA_OPERATOR')"
.equals(permission.value()));
}
@@ -138,7 +138,7 @@ class ReceivingContainerContractTest {
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 可用"));
assertTrue(receivingPaths.contains("仅 PLATFORM OVERSEAS_WAREHOUSE_PDA_OPERATOR 可用"));
assertFalse(receivingPaths.contains("客户与操作账号均从 PDA Token 获取"));
assertTrue(yaml.contains("enum: [LICENSE_PLATE, CONTAINER_SEAL, DOOR_OPEN]"));
assertTrue(yaml.contains("photos: { type: array, minItems: 3, maxItems: 3"));
@@ -19,7 +19,7 @@ class PdaPalletOperationControllerContractTest {
RequestMapping mapping = PdaPalletOperationController.class.getAnnotation(RequestMapping.class);
assertArrayEquals(new String[]{"/api/pda/v1/last-leg/pallet-operations"}, mapping.value());
PreAuthorize authorize = PdaPalletOperationController.class.getAnnotation(PreAuthorize.class);
assertEquals("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_WAREHOUSE_PICKER')", authorize.value());
assertEquals("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_OVERSEAS_WAREHOUSE_PDA_OPERATOR')", authorize.value());
Set<String> paths = Arrays.stream(PdaPalletOperationController.class.getDeclaredMethods())
.map(Method::getDeclaredAnnotations).flatMap(Arrays::stream)
.filter(PostMapping.class::isInstance).map(PostMapping.class::cast)
@@ -25,9 +25,9 @@ class PdaInventoryOperationContractTest {
assertThat(PdaInventoryMoveController.class.getAnnotation(RequestMapping.class).value())
.containsExactly("/api/pda/v1/last-leg/inventory-moves");
assertThat(PdaStocktakeController.class.getAnnotation(PreAuthorize.class).value())
.isEqualTo("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_WAREHOUSE_PICKER')");
.isEqualTo("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_OVERSEAS_WAREHOUSE_PDA_OPERATOR')");
assertThat(PdaInventoryMoveController.class.getAnnotation(PreAuthorize.class).value())
.isEqualTo("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_WAREHOUSE_PICKER')");
.isEqualTo("hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_OVERSEAS_WAREHOUSE_PDA_OPERATOR')");
}
@Test
@@ -34,7 +34,7 @@ class PdaWarehouseContractTest {
void controllerRequiresPickerRoleAndPdaAuthority() {
PreAuthorize permission = PdaWarehouseController.class.getAnnotation(PreAuthorize.class);
assertThat(permission.value()).isEqualTo(
"hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_WAREHOUSE_PICKER')");
"hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_OVERSEAS_WAREHOUSE_PDA_OPERATOR')");
}
@Test
@@ -43,12 +43,12 @@ class PdaRelabelContractTest {
@Test
void identityRequiresPlatformPickerPermissionAndFrozenWarehouse() {
AuthenticatedUser valid = user(Set.of("lastleg:pda:operate", "ROLE_WAREHOUSE_PICKER"), 900L);
AuthenticatedUser valid = user(Set.of("lastleg:pda:operate", "ROLE_OVERSEAS_WAREHOUSE_PDA_OPERATOR"), 900L);
assertEquals(900L, PdaRelabelController.identity(valid).overseasWarehouseId());
assertThrows(ResponseStatusException.class,
() -> PdaRelabelController.identity(user(Set.of("lastleg:pda:operate"), 900L)));
assertThrows(ResponseStatusException.class,
() -> PdaRelabelController.identity(user(Set.of("lastleg:pda:operate", "ROLE_WAREHOUSE_PICKER"), null)));
() -> PdaRelabelController.identity(user(Set.of("lastleg:pda:operate", "ROLE_OVERSEAS_WAREHOUSE_PDA_OPERATOR"), null)));
}
@Test