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
@@ -254,13 +254,18 @@ public class PlatformUserManagementService {
}
private Long validateWarehouseBinding(List<Long> roleIds, Long overseasWarehouseId) {
boolean picker = roleIds.stream().map(roleMapper::selectById).filter(Objects::nonNull)
.anyMatch(role -> "WAREHOUSE_PICKER".equals(role.getRoleCode()));
if (picker && (overseasWarehouseId == null || overseasWarehouseId <= 0)) {
throw new BizException(ResultCode.BAD_REQUEST, "海外仓拣货员必须绑定一个有效海外仓");
Set<String> roleCodes = roleIds.stream().map(roleMapper::selectById).filter(Objects::nonNull)
.map(IamRole::getRoleCode).collect(Collectors.toSet());
boolean overseasPda = roleCodes.contains("OVERSEAS_WAREHOUSE_PDA_OPERATOR");
boolean domesticPda = roleCodes.contains("CN_FIRSTLEG_PDA_OPERATOR");
if (overseasPda && domesticPda) {
throw new BizException(ResultCode.BAD_REQUEST, "国内头程 PDA 与海外仓 PDA 角色不能同时分配");
}
if (!picker && overseasWarehouseId != null) {
throw new BizException(ResultCode.BAD_REQUEST, "仅海外仓拣货员可绑定海外仓");
if (overseasPda && (overseasWarehouseId == null || overseasWarehouseId <= 0)) {
throw new BizException(ResultCode.BAD_REQUEST, "海外仓 PDA 作业员必须绑定一个有效海外仓");
}
if (!overseasPda && overseasWarehouseId != null) {
throw new BizException(ResultCode.BAD_REQUEST, "仅海外仓 PDA 作业员可绑定海外仓");
}
return overseasWarehouseId;
}
@@ -80,14 +80,14 @@ DELETE role_permission FROM iam_role_permission role_permission
JOIN iam_role role ON role.id=role_permission.role_id AND role.tenant_id=role_permission.tenant_id
JOIN iam_permission permission ON permission.id=role_permission.permission_id AND permission.tenant_id=role_permission.tenant_id
WHERE permission.permission_code='lastleg:pda:operate'
AND NOT (role.id=4000005 AND role.tenant_id=0 AND role.app_code='PLATFORM' AND role.role_code='WAREHOUSE_PICKER');
AND NOT (role.id=4000005 AND role.tenant_id=0 AND role.app_code='PLATFORM' AND role.role_code='OVERSEAS_WAREHOUSE_PDA_OPERATOR');
INSERT IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, created_at)
SELECT 0, role.id, permission.id, NOW(3)
FROM iam_role role
JOIN iam_permission permission ON permission.tenant_id=0 AND permission.app_code='PLATFORM'
AND permission.permission_code='lastleg:pda:operate'
WHERE role.id=4000005 AND role.tenant_id=0 AND role.app_code='PLATFORM' AND role.role_code='WAREHOUSE_PICKER'
WHERE role.id=4000005 AND role.tenant_id=0 AND role.app_code='PLATFORM' AND role.role_code='OVERSEAS_WAREHOUSE_PDA_OPERATOR'
AND role.status='ACTIVE' AND role.deleted=0 AND permission.status='ACTIVE' AND permission.deleted=0;
DROP PROCEDURE IF EXISTS assert_overseas_pda_picker_permission;
@@ -98,15 +98,15 @@ BEGIN
JOIN iam_role role ON role.id=grant_row.role_id AND role.tenant_id=grant_row.tenant_id
JOIN iam_permission permission ON permission.id=grant_row.permission_id AND permission.tenant_id=grant_row.tenant_id
WHERE permission.permission_code='lastleg:pda:operate'
AND role.id=4000005 AND role.app_code='PLATFORM' AND role.role_code='WAREHOUSE_PICKER') <> 1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='海外PDA权限初始化失败:WAREHOUSE_PICKER未精确授权';
AND role.id=4000005 AND role.app_code='PLATFORM' AND role.role_code='OVERSEAS_WAREHOUSE_PDA_OPERATOR') <> 1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='海外PDA权限初始化失败:海外仓 PDA 作业员未精确授权';
END IF;
IF EXISTS (SELECT 1 FROM iam_role_permission grant_row
JOIN iam_role role ON role.id=grant_row.role_id AND role.tenant_id=grant_row.tenant_id
JOIN iam_permission permission ON permission.id=grant_row.permission_id AND permission.tenant_id=grant_row.tenant_id
WHERE permission.permission_code='lastleg:pda:operate'
AND NOT (role.id=4000005 AND role.tenant_id=0 AND role.app_code='PLATFORM' AND role.role_code='WAREHOUSE_PICKER')) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='海外PDA权限初始化失败:存在非仓库拣货员授权';
AND NOT (role.id=4000005 AND role.tenant_id=0 AND role.app_code='PLATFORM' AND role.role_code='OVERSEAS_WAREHOUSE_PDA_OPERATOR')) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='海外PDA权限初始化失败:存在非海外仓 PDA 作业员授权';
END IF;
END$$
DELIMITER ;
@@ -148,7 +148,7 @@ JOIN iam_permission permission ON permission.id=grant_row.permission_id AND perm
LEFT JOIN tmp_phase4_warehouse_role_permission expected
ON expected.role_code=role.role_code AND expected.permission_code=permission.permission_code
WHERE role.tenant_id=0 AND role.app_code='PLATFORM'
AND role.role_code IN ('WAREHOUSE_OPERATOR','WAREHOUSE_PICKER','WAREHOUSE_TALLY')
AND role.role_code IN ('WAREHOUSE_OPERATOR','OVERSEAS_WAREHOUSE_PDA_OPERATOR','WAREHOUSE_TALLY','CN_FIRSTLEG_PDA_OPERATOR')
AND permission.permission_code LIKE 'platform:overseas-%'
AND expected.permission_code IS NULL;
@@ -1,8 +1,15 @@
-- 三期仓库常用系统角色:仓库操作员、仓库拣货员、仓库理货员。
-- 三期仓库系统角色:国内仓后台、国内头程 PDA、海外仓 PDA、国内仓理货复核。
-- 本脚本只组合已经存在的权限资源,需在三期 Firstleg/IAM 权限脚本之后执行,可重复部署。
USE baiyue_iam;
SET NAMES utf8mb4;
UPDATE iam_role
SET role_code = 'OVERSEAS_WAREHOUSE_PDA_OPERATOR',
role_name = '海外仓 PDA 作业员',
description = '负责使用海外仓 PDA 执行接柜、入仓和库内作业,必须绑定一个海外仓'
WHERE id = 4000005 AND tenant_id = 0 AND app_code = 'PLATFORM'
AND role_code = 'WAREHOUSE_PICKER';
DROP PROCEDURE IF EXISTS assert_phase3_warehouse_role_ids_available;
DELIMITER $$
CREATE PROCEDURE assert_phase3_warehouse_role_ids_available()
@@ -11,14 +18,16 @@ BEGIN
SELECT 1
FROM iam_role
WHERE (id = 4000004 AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR role_code <> 'WAREHOUSE_OPERATOR'))
OR (id = 4000005 AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR role_code <> 'WAREHOUSE_PICKER'))
OR (id = 4000005 AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR role_code <> 'OVERSEAS_WAREHOUSE_PDA_OPERATOR'))
OR (id = 4000006 AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR role_code <> 'WAREHOUSE_TALLY'))
OR (id = 4000007 AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR role_code <> 'CN_FIRSTLEG_PDA_OPERATOR'))
OR (tenant_id = 0 AND app_code = 'PLATFORM' AND role_code = 'WAREHOUSE_OPERATOR' AND id <> 4000004)
OR (tenant_id = 0 AND app_code = 'PLATFORM' AND role_code = 'WAREHOUSE_PICKER' AND id <> 4000005)
OR (tenant_id = 0 AND app_code = 'PLATFORM' AND role_code = 'OVERSEAS_WAREHOUSE_PDA_OPERATOR' AND id <> 4000005)
OR (tenant_id = 0 AND app_code = 'PLATFORM' AND role_code = 'WAREHOUSE_TALLY' AND id <> 4000006)
OR (tenant_id = 0 AND app_code = 'PLATFORM' AND role_code = 'CN_FIRSTLEG_PDA_OPERATOR' AND id <> 4000007)
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '仓库角色初始化失败:4000004-4000006 或角色编码已被占用';
SET MESSAGE_TEXT = '仓库角色初始化失败:4000004-4000007 或角色编码已被占用';
END IF;
IF EXISTS (
SELECT 1
@@ -26,12 +35,14 @@ BEGIN
WHERE (id = 6000004 AND role_id <> 4000004)
OR (id = 6000005 AND role_id <> 4000005)
OR (id = 6000006 AND role_id <> 4000006)
OR (id = 6000007 AND role_id <> 4000007)
OR (role_id = 4000004 AND id <> 6000004)
OR (role_id = 4000005 AND id <> 6000005)
OR (role_id = 4000006 AND id <> 6000006)
OR (role_id = 4000007 AND id <> 6000007)
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '仓库角色初始化失败:6000004-6000006 数据范围ID已被占用';
SET MESSAGE_TEXT = '仓库角色初始化失败:6000004-6000007 数据范围ID已被占用';
END IF;
END$$
DELIMITER ;
@@ -42,12 +53,14 @@ INSERT INTO iam_role (
id, tenant_id, app_code, role_code, role_name, role_type, status,
description, deleted, version
) VALUES
(4000004, 0, 'PLATFORM', 'WAREHOUSE_OPERATOR', '仓库操作员', 'SYSTEM', 'ACTIVE',
(4000004, 0, 'PLATFORM', 'WAREHOUSE_OPERATOR', '国内仓后台操作员', 'SYSTEM', 'ACTIVE',
'负责查看入仓业务并在后台维护装柜证据', 0, 0),
(4000005, 0, 'PLATFORM', 'WAREHOUSE_PICKER', '仓库拣货员', 'SYSTEM', 'ACTIVE',
'负责使用PDA执行柜次拣货和现场拍照', 0, 0),
(4000006, 0, 'PLATFORM', 'WAREHOUSE_TALLY', '仓库理货员', 'SYSTEM', 'ACTIVE',
'负责入库测量、PDA复核和装柜证据维护', 0, 0)
(4000005, 0, 'PLATFORM', 'OVERSEAS_WAREHOUSE_PDA_OPERATOR', '海外仓 PDA 作业员', 'SYSTEM', 'ACTIVE',
'负责使用海外仓 PDA 执行接柜、入仓和库内作业,必须绑定一个海外仓', 0, 0),
(4000006, 0, 'PLATFORM', 'WAREHOUSE_TALLY', '国内仓理货复核员', 'SYSTEM', 'ACTIVE',
'负责国内入库测量、PDA复核和装柜证据维护', 0, 0),
(4000007, 0, 'PLATFORM', 'CN_FIRSTLEG_PDA_OPERATOR', '国内头程 PDA 作业员', 'SYSTEM', 'ACTIVE',
'负责使用国内 PDA 执行柜次拣货、现场取证和装柜完成', 0, 0)
ON DUPLICATE KEY UPDATE
role_name = VALUES(role_name), role_type = 'SYSTEM', status = 'ACTIVE',
description = VALUES(description), deleted = 0;
@@ -67,10 +80,10 @@ INSERT INTO tmp_phase3_warehouse_role_permission (role_code, permission_code) VA
('WAREHOUSE_OPERATOR', 'platform:firstleg:inbound:loading:view'),
('WAREHOUSE_OPERATOR', 'platform:firstleg:inbound:loading:edit');
-- 拣货员:只允许PDA任务查询、作业和现场照片,不授予后台页面、任务分派或复核权限。
-- 国内头程 PDA 作业员:只允许国内任务查询、作业和现场照片,不授予后台、海外、分派或复核权限。
INSERT INTO tmp_phase3_warehouse_role_permission (role_code, permission_code) VALUES
('WAREHOUSE_PICKER', 'platform:firstleg:pda:view'),
('WAREHOUSE_PICKER', 'platform:firstleg:pda:work');
('CN_FIRSTLEG_PDA_OPERATOR', 'platform:firstleg:pda:view'),
('CN_FIRSTLEG_PDA_OPERATOR', 'platform:firstleg:pda:work');
-- 理货员:负责到仓测量、装柜证据维护和PDA复核,不允许完成装柜或确认出仓。
INSERT INTO tmp_phase3_warehouse_role_permission (role_code, permission_code) VALUES
@@ -92,15 +105,15 @@ DELIMITER $$
CREATE PROCEDURE assert_phase3_warehouse_role_permissions_available()
BEGIN
DECLARE operator_permission_count INT DEFAULT 0;
DECLARE picker_permission_count INT DEFAULT 0;
DECLARE domestic_pda_permission_count INT DEFAULT 0;
DECLARE tally_permission_count INT DEFAULT 0;
SELECT COALESCE(SUM(role_code = 'WAREHOUSE_OPERATOR'), 0),
COALESCE(SUM(role_code = 'WAREHOUSE_PICKER'), 0),
COALESCE(SUM(role_code = 'CN_FIRSTLEG_PDA_OPERATOR'), 0),
COALESCE(SUM(role_code = 'WAREHOUSE_TALLY'), 0)
INTO operator_permission_count, picker_permission_count, tally_permission_count
INTO operator_permission_count, domestic_pda_permission_count, tally_permission_count
FROM tmp_phase3_warehouse_role_permission;
IF operator_permission_count <> 5
OR picker_permission_count <> 2
OR domestic_pda_permission_count <> 2
OR tally_permission_count <> 12 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '仓库角色初始化失败:角色权限组合数量不符合预期';
@@ -124,7 +137,7 @@ DELIMITER ;
CALL assert_phase3_warehouse_role_permissions_available();
DROP PROCEDURE assert_phase3_warehouse_role_permissions_available;
-- 三个系统角色的权限集合由迁移维护。重复执行时移除历史版本或人工误授的越权项,
-- 四个系统角色的权限集合由初始化维护。重复执行时移除人工误授的越权项,
-- 避免最小权限角色因脚本升级只增不减。
DELETE role_permission
FROM iam_role_permission role_permission
@@ -139,8 +152,8 @@ DELETE role_permission
AND expected.permission_code = permission.permission_code
WHERE role.tenant_id = 0
AND role.app_code = 'PLATFORM'
AND role.role_code IN ('WAREHOUSE_OPERATOR', 'WAREHOUSE_PICKER', 'WAREHOUSE_TALLY')
AND NOT (role.role_code = 'WAREHOUSE_PICKER' AND permission.permission_code = 'lastleg:pda:operate')
AND role.role_code IN ('WAREHOUSE_OPERATOR', 'OVERSEAS_WAREHOUSE_PDA_OPERATOR', 'WAREHOUSE_TALLY', 'CN_FIRSTLEG_PDA_OPERATOR')
AND NOT (role.role_code = 'OVERSEAS_WAREHOUSE_PDA_OPERATOR' AND permission.permission_code = 'lastleg:pda:operate')
AND expected.permission_code IS NULL;
INSERT IGNORE INTO iam_role_permission (role_id, permission_id, tenant_id)
@@ -162,7 +175,8 @@ SELECT role.id, permission.id, 0
INSERT INTO iam_data_scope (id, role_id, tenant_id, scope_type, scope_expression) VALUES
(6000004, 4000004, 0, 'TENANT', NULL),
(6000005, 4000005, 0, 'TENANT', NULL),
(6000006, 4000006, 0, 'TENANT', NULL)
(6000006, 4000006, 0, 'TENANT', NULL),
(6000007, 4000007, 0, 'TENANT', NULL)
ON DUPLICATE KEY UPDATE
tenant_id = 0, scope_type = 'TENANT', scope_expression = NULL;
@@ -177,7 +191,7 @@ BEGIN
AND role_type = 'SYSTEM'
AND status = 'ACTIVE'
AND deleted = 0
AND role_code IN ('WAREHOUSE_OPERATOR', 'WAREHOUSE_PICKER', 'WAREHOUSE_TALLY')) <> 3 THEN
AND role_code IN ('WAREHOUSE_OPERATOR', 'OVERSEAS_WAREHOUSE_PDA_OPERATOR', 'WAREHOUSE_TALLY', 'CN_FIRSTLEG_PDA_OPERATOR')) <> 4 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '仓库角色初始化失败:系统角色未完整启用';
END IF;
@@ -203,9 +217,9 @@ BEGIN
JOIN iam_role role ON role.id = scope_row.role_id
WHERE role.tenant_id = 0
AND role.app_code = 'PLATFORM'
AND role.role_code IN ('WAREHOUSE_OPERATOR', 'WAREHOUSE_PICKER', 'WAREHOUSE_TALLY')
AND role.role_code IN ('WAREHOUSE_OPERATOR', 'OVERSEAS_WAREHOUSE_PDA_OPERATOR', 'WAREHOUSE_TALLY', 'CN_FIRSTLEG_PDA_OPERATOR')
AND scope_row.tenant_id = 0
AND scope_row.scope_type = 'TENANT') <> 3 THEN
AND scope_row.scope_type = 'TENANT') <> 4 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '仓库角色初始化失败:数据范围未完整写入';
END IF;
+3 -3
View File
@@ -645,7 +645,7 @@ paths:
get:
tags: [RBAC]
summary: List roles / 查询角色
description: List roles in the selected application and current customer or platform technical scope. The platform scope includes the built-in WAREHOUSE_OPERATOR, WAREHOUSE_PICKER and WAREHOUSE_TALLY roles after the phase-three role migration. / 查询指定应用及当前客户范围或平台技术作用域内的角色;三期角色迁移后,平台作用域包含仓库操作员、仓库拣货员和仓库理货员三个内置角色。
description: List roles in the selected application and current customer or platform technical scope. Domestic first-leg PDA and overseas warehouse PDA operators are separate, mutually exclusive roles. / 查询指定应用及当前客户范围或平台技术作用域内的角色;国内头程 PDA 作业员与海外仓 PDA 作业员是两批独立人员,对应角色互斥且权限不交叉。
responses:
'200': {$ref: '#/components/responses/Success'}
'403': {$ref: '#/components/responses/Forbidden'}
@@ -1323,7 +1323,7 @@ components:
email: {type: string, format: email, maxLength: 128}
tenantId: {type: integer, format: int64, enum: [0], description: 保留的兼容字段,只允许平台技术作用域 0}
roleIds: {type: array, minItems: 1, items: {type: integer, format: int64}}
overseasWarehouseId: {type: integer, format: int64, nullable: true, description: 选择WAREHOUSE_PICKER角色时必填且只能绑定一个实际海外仓;其他角色必须为空}
overseasWarehouseId: {type: integer, format: int64, nullable: true, description: 选择 OVERSEAS_WAREHOUSE_PDA_OPERATOR 时必填且只能绑定一个实际海外仓;CN_FIRSTLEG_PDA_OPERATOR 与其他角色必须为空;两个 PDA 角色不能同时分配}
password: {type: string, format: password, minLength: 8, maxLength: 64, writeOnly: true}
status: {type: string, enum: [ACTIVE, DISABLED]}
UpdatePlatformManagedUserRequest:
@@ -1335,7 +1335,7 @@ components:
email: {type: string, format: email, maxLength: 128}
tenantId: {type: integer, format: int64, enum: [0], description: 保留的兼容字段,只允许平台技术作用域 0}
roleIds: {type: array, minItems: 1, items: {type: integer, format: int64}}
overseasWarehouseId: {type: integer, format: int64, nullable: true, description: 选择WAREHOUSE_PICKER角色时必填且只能绑定一个实际海外仓;其他角色必须为空}
overseasWarehouseId: {type: integer, format: int64, nullable: true, description: 选择 OVERSEAS_WAREHOUSE_PDA_OPERATOR 时必填且只能绑定一个实际海外仓;CN_FIRSTLEG_PDA_OPERATOR 与其他角色必须为空;两个 PDA 角色不能同时分配}
CreateInternalUserRequest:
type: object
required: [displayName]
@@ -82,10 +82,10 @@ class OverseasPhase4PermissionSqlContractTest {
"platform:overseas-business:purchase:list",
"platform:overseas-business:truck:list",
"platform:overseas-business:looseboard:list");
assertThat(matrix).doesNotContain("WAREHOUSE_PICKER");
assertThat(matrix).doesNotContain("OVERSEAS_WAREHOUSE_PDA_OPERATOR");
assertThat(matrix).doesNotContain(":create'", ":edit'", ":status'", ":export'", ":adjust'");
assertThat(sql).contains("assert_phase4_warehouse_role_minimum", "<> 20",
"role.role_code IN ('WAREHOUSE_OPERATOR','WAREHOUSE_PICKER','WAREHOUSE_TALLY')");
"role.role_code IN ('WAREHOUSE_OPERATOR','OVERSEAS_WAREHOUSE_PDA_OPERATOR','WAREHOUSE_TALLY','CN_FIRSTLEG_PDA_OPERATOR')");
}
}
@@ -125,12 +125,12 @@ class OverseasPhase4PermissionSqlContractTest {
"permission.permission_code<>'lastleg:pda:operate'",
"role.id=4000005",
"assert_overseas_pda_picker_permission",
"存在非仓库拣货员授权",
"role.role_code='WAREHOUSE_PICKER'",
"存在非海外仓 PDA 作业员授权",
"role.role_code='OVERSEAS_WAREHOUSE_PDA_OPERATOR'",
"permission.permission_code='lastleg:pda:operate'",
"role.role_code='PLATFORM_ADMIN'",
"DELETE role_permission FROM");
int picker = sql.indexOf("role.role_code='WAREHOUSE_PICKER'");
int picker = sql.indexOf("role.role_code='OVERSEAS_WAREHOUSE_PDA_OPERATOR'");
int grantStart = sql.lastIndexOf("INSERT IGNORE INTO", picker);
int grantEnd = sql.indexOf(';', picker);
assertThat(grantStart).isGreaterThanOrEqualTo(0);
@@ -30,33 +30,35 @@ class WarehouseRoleSqlContractTest {
private static void assertWarehouseRoleContract(String sql) {
assertThat(sql)
.contains("(4000004, 0, 'PLATFORM', 'WAREHOUSE_OPERATOR', '仓库操作员', 'SYSTEM'")
.contains("(4000005, 0, 'PLATFORM', 'WAREHOUSE_PICKER', '仓库拣货员', 'SYSTEM'")
.contains("(4000006, 0, 'PLATFORM', 'WAREHOUSE_TALLY', '仓库理货员', 'SYSTEM'")
.contains("(4000004, 0, 'PLATFORM', 'WAREHOUSE_OPERATOR', '国内仓后台操作员', 'SYSTEM'")
.contains("(4000005, 0, 'PLATFORM', 'OVERSEAS_WAREHOUSE_PDA_OPERATOR', '海外仓 PDA 作业员', 'SYSTEM'")
.contains("(4000006, 0, 'PLATFORM', 'WAREHOUSE_TALLY', '国内仓理货复核员', 'SYSTEM'")
.contains("(4000007, 0, 'PLATFORM', 'CN_FIRSTLEG_PDA_OPERATOR', '国内头程 PDA 作业员', 'SYSTEM'")
.contains("ON DUPLICATE KEY UPDATE")
.contains("INSERT IGNORE INTO iam_role_permission")
.contains("'WAREHOUSE_PICKER', 'platform:firstleg:pda:work'")
.contains("'CN_FIRSTLEG_PDA_OPERATOR', 'platform:firstleg:pda:work'")
.contains("'WAREHOUSE_TALLY', 'platform:firstleg:pda:review'")
.contains("'WAREHOUSE_TALLY', 'platform:firstleg:inbound:loading:edit'")
.contains("'WAREHOUSE_OPERATOR', 'platform:firstleg:inbound:loading:edit'")
.contains("operator_permission_count <> 5")
.contains("picker_permission_count <> 2")
.contains("domestic_pda_permission_count <> 2")
.contains("tally_permission_count <> 12")
.contains("依赖的三期平台权限尚未完整部署")
.contains("DELETE role_permission")
.contains("expected.permission_code IS NULL")
.contains("role.role_code = 'WAREHOUSE_PICKER' AND permission.permission_code = 'lastleg:pda:operate'")
.contains("role.role_code = 'OVERSEAS_WAREHOUSE_PDA_OPERATOR' AND permission.permission_code = 'lastleg:pda:operate'")
.contains("最小权限授权未完整写入")
.contains("grant_row.tenant_id = 0) <> 19")
.contains("(6000004, 4000004, 0, 'TENANT', NULL)")
.contains("(6000005, 4000005, 0, 'TENANT', NULL)")
.contains("(6000006, 4000006, 0, 'TENANT', NULL)");
.contains("(6000006, 4000006, 0, 'TENANT', NULL)")
.contains("(6000007, 4000007, 0, 'TENANT', NULL)");
assertRoleGrant(sql, "WAREHOUSE_PICKER", "platform:firstleg:pda:work");
assertRoleGrant(sql, "CN_FIRSTLEG_PDA_OPERATOR", "platform:firstleg:pda:work");
assertRoleGrant(sql, "WAREHOUSE_TALLY", "platform:firstleg:pda:review");
assertRoleGrant(sql, "WAREHOUSE_OPERATOR", "platform:firstleg:inbound:loading:edit");
assertThat(roleGrantBlock(sql, "WAREHOUSE_PICKER"))
assertThat(roleGrantBlock(sql, "CN_FIRSTLEG_PDA_OPERATOR"))
.doesNotContain("platform:order")
.doesNotContain("platform:master-data")
.doesNotContain("platform:warehouse:list")
@@ -76,9 +78,9 @@ class WarehouseRoleSqlContractTest {
.doesNotContain("'WAREHOUSE_OPERATOR', 'platform:firstleg:inbound:loading:complete'")
.doesNotContain("'WAREHOUSE_OPERATOR', 'platform:firstleg:outbound-dispatch:confirm'")
.doesNotContain("'WAREHOUSE_OPERATOR', 'platform:firstleg:outbound-dispatch:reverse'")
.doesNotContain("'WAREHOUSE_PICKER', 'platform:firstleg:smart-loading:delete'")
.doesNotContain("'CN_FIRSTLEG_PDA_OPERATOR', 'platform:firstleg:smart-loading:delete'")
.doesNotContain("'WAREHOUSE_TALLY', 'firstleg:inbound:fee-detail:edit'")
.doesNotContain("'WAREHOUSE_PICKER', 'merchant:")
.doesNotContain("'CN_FIRSTLEG_PDA_OPERATOR', 'merchant:")
.doesNotContain("'WAREHOUSE_OPERATOR', 'merchant:")
.doesNotContain("'WAREHOUSE_TALLY', 'merchant:");
}
@@ -89,7 +91,7 @@ class WarehouseRoleSqlContractTest {
private static String roleGrantBlock(String sql, String roleCode) {
String marker = "-- " + switch (roleCode) {
case "WAREHOUSE_PICKER" -> "拣货员:";
case "CN_FIRSTLEG_PDA_OPERATOR" -> "国内头程 PDA 作业员:";
case "WAREHOUSE_TALLY" -> "理货员:";
default -> throw new IllegalArgumentException("unsupported role " + roleCode);
};
@@ -146,7 +146,7 @@ class PlatformUserManagementServiceTest {
@Test
void warehousePickerRequiresAndPersistsSingleOverseasWarehouseBinding() {
platformRole.setRoleCode("WAREHOUSE_PICKER");
platformRole.setRoleCode("OVERSEAS_WAREHOUSE_PDA_OPERATOR");
UpdateUserRequest missing = new UpdateUserRequest("张伟", "13800138000", null,
0L, List.of(40001L), null);
assertThrows(BizException.class,
@@ -161,9 +161,31 @@ class PlatformUserManagementServiceTest {
verify(auth).revokeSessions(eq("internal-token"), any());
}
@Test
void domesticAndOverseasPdaRolesAreMutuallyExclusive() {
IamRole domestic = new IamRole();
domestic.setId(40007L); domestic.setTenantId(0L); domestic.setAppCode("PLATFORM");
domestic.setRoleCode("CN_FIRSTLEG_PDA_OPERATOR"); domestic.setRoleName("国内头程 PDA 作业员");
domestic.setStatus("ACTIVE");
IamRole overseas = new IamRole();
overseas.setId(40005L); overseas.setTenantId(0L); overseas.setAppCode("PLATFORM");
overseas.setRoleCode("OVERSEAS_WAREHOUSE_PDA_OPERATOR"); overseas.setRoleName("海外仓 PDA 作业员");
overseas.setStatus("ACTIVE");
when(roles.selectById(40007L)).thenReturn(domestic);
when(roles.selectById(40005L)).thenReturn(overseas);
UpdateUserRequest request = new UpdateUserRequest("张伟", "13800138000", null,
0L, List.of(40007L, 40005L), 900L);
BizException error = assertThrows(BizException.class,
() -> service.update(30L, request, Set.of("platform:user:grant-role")));
assertTrue(error.getMessage().contains("不能同时分配"));
verify(auth, never()).scope(anyString(), anyLong(), any());
}
@Test
void changingWarehouseBindingRevokesAllAuthSessionsSoAccountVersionIsBumped() {
platformRole.setRoleCode("WAREHOUSE_PICKER");
platformRole.setRoleCode("OVERSEAS_WAREHOUSE_PDA_OPERATOR");
membership.setOverseasWarehouseId(900L);
UpdateUserRequest changed = new UpdateUserRequest("张伟", "13800138000", null,
0L, List.of(40001L), 901L);