2026-08-10 12:22:29 +08:00
|
|
|
const MODULE = 'phase3-warehouse-rbac'
|
|
|
|
|
const OPTIONS = { scenario: 'warehouse-role-boundary', deploymentBlocker: true }
|
2026-08-25 10:31:04 +08:00
|
|
|
const PHASE4_READ_CODES = [
|
|
|
|
|
'platform:overseas-warehouse:last-mile-order:list',
|
|
|
|
|
'platform:overseas-warehouse:inbound-order:list',
|
|
|
|
|
'platform:overseas-warehouse:relabel-order:list',
|
|
|
|
|
'platform:overseas-warehouse:inventory:list',
|
|
|
|
|
'platform:overseas-warehouse:warehouse:list',
|
|
|
|
|
'platform:overseas-warehouse:location:list',
|
|
|
|
|
'platform:overseas-warehouse:pallet:list',
|
|
|
|
|
'platform:overseas-business:purchase:list',
|
|
|
|
|
'platform:overseas-business:truck:list',
|
|
|
|
|
'platform:overseas-business:looseboard:list',
|
|
|
|
|
]
|
|
|
|
|
const PHASE4_WRITE_SUFFIXES = [':create', ':edit', ':status', ':export', ':adjust', ':delete', ':review']
|
2026-09-03 19:10:18 +08:00
|
|
|
const PHASE5_OPERATOR_CODES = [
|
2026-10-21 15:10:44 +08:00
|
|
|
'platform:overseas-warehouse:dispatch:create',
|
|
|
|
|
'platform:overseas-warehouse:dispatch:loading-complete',
|
|
|
|
|
'platform:overseas-warehouse:dispatch:depart',
|
2026-09-03 19:10:18 +08:00
|
|
|
'platform:overseas-warehouse:dispatch:list',
|
|
|
|
|
'platform:overseas-warehouse:dispatch:edit',
|
|
|
|
|
'platform:overseas-warehouse:dispatch:cancel',
|
|
|
|
|
'platform:overseas-warehouse:dispatch:transfer-delivery',
|
|
|
|
|
'platform:overseas-warehouse:outbound:list',
|
|
|
|
|
'platform:overseas-warehouse:outbound:create',
|
|
|
|
|
'platform:overseas-warehouse:outbound:cancel',
|
|
|
|
|
'platform:overseas-warehouse:delivery:list',
|
|
|
|
|
'platform:overseas-warehouse:exception-order:list',
|
|
|
|
|
'platform:overseas-warehouse:exception-order:create',
|
2026-09-09 20:36:53 +08:00
|
|
|
'platform:overseas-warehouse:exception-order:process',
|
2026-09-03 19:10:18 +08:00
|
|
|
'platform:overseas-warehouse:delivery:return',
|
|
|
|
|
'platform:overseas-warehouse:delivery:status',
|
2026-09-09 20:36:53 +08:00
|
|
|
'platform:overseas-warehouse:delivery:documents',
|
2026-09-03 19:10:18 +08:00
|
|
|
'platform:overseas-warehouse:customer-visit:list',
|
|
|
|
|
'platform:overseas-warehouse:customer-visit:edit',
|
|
|
|
|
]
|
|
|
|
|
const PHASE5_TALLY_CODES = [
|
|
|
|
|
'platform:overseas-warehouse:dispatch:list',
|
|
|
|
|
'platform:overseas-warehouse:outbound:list',
|
|
|
|
|
'platform:overseas-warehouse:delivery:list',
|
|
|
|
|
'platform:overseas-warehouse:exception-order:list',
|
|
|
|
|
'platform:overseas-warehouse:customer-visit:list',
|
|
|
|
|
]
|
2026-08-10 12:22:29 +08:00
|
|
|
|
|
|
|
|
function id(value) { return value == null ? '' : String(value) }
|
|
|
|
|
|
|
|
|
|
export async function run(context) {
|
|
|
|
|
const permissionsResponse = await context.request('GET', '/api/platform/v1/iam/permissions')
|
|
|
|
|
const permissions = context.apiSuccess(MODULE, '查询平台权限资源', permissionsResponse, '', OPTIONS)
|
|
|
|
|
const permissionById = new Map((Array.isArray(permissions) ? permissions : [])
|
|
|
|
|
.map((item) => [id(item.id), item.permissionCode]))
|
|
|
|
|
|
|
|
|
|
const controllerCodes = ['platform:firstleg:pda:view', 'platform:firstleg:pda:assign',
|
|
|
|
|
'platform:firstleg:pda:work', 'platform:firstleg:pda:review',
|
|
|
|
|
'platform:firstleg:inbound:loading:view', 'platform:firstleg:inbound:loading:edit',
|
|
|
|
|
'platform:firstleg:inbound:loading:complete']
|
|
|
|
|
const controllerPermissions = (Array.isArray(permissions) ? permissions : [])
|
|
|
|
|
.filter((item) => controllerCodes.includes(item.permissionCode))
|
|
|
|
|
context.assert(MODULE, 'PDA和装柜证据Controller权限资源完整', permissionsResponse,
|
|
|
|
|
controllerPermissions.length === controllerCodes.length
|
|
|
|
|
&& controllerPermissions.every((item) => item.builtIn === true && item.status === 'ACTIVE'),
|
|
|
|
|
`matched=${controllerPermissions.length}/${controllerCodes.length}`, OPTIONS)
|
|
|
|
|
|
|
|
|
|
const rolesResponse = await context.request('GET', '/api/platform/v1/iam/roles')
|
|
|
|
|
const roles = context.apiSuccess(MODULE, '查询三期仓库内置角色', rolesResponse, '', OPTIONS)
|
|
|
|
|
const warehouseRoles = new Map((Array.isArray(roles) ? roles : [])
|
2026-08-28 10:38:58 +08:00
|
|
|
.filter((item) => ['WAREHOUSE_OPERATOR', 'OVERSEAS_WAREHOUSE_PDA_OPERATOR', 'WAREHOUSE_TALLY', 'CN_FIRSTLEG_PDA_OPERATOR'].includes(item.roleCode))
|
2026-08-10 12:22:29 +08:00
|
|
|
.map((item) => [item.roleCode, item]))
|
2026-08-28 10:38:58 +08:00
|
|
|
context.assert(MODULE, '国内后台、国内PDA、海外PDA、理货复核四个系统角色已启用', rolesResponse,
|
|
|
|
|
warehouseRoles.size === 4 && [...warehouseRoles.values()]
|
2026-08-10 12:22:29 +08:00
|
|
|
.every((role) => role.roleType === 'SYSTEM' && role.status === 'ACTIVE'),
|
|
|
|
|
`roles=${[...warehouseRoles.keys()].join(',')}`, OPTIONS)
|
|
|
|
|
|
|
|
|
|
const roleCodes = (roleCode) => new Set((warehouseRoles.get(roleCode)?.permissionIds || [])
|
|
|
|
|
.map((permissionId) => permissionById.get(id(permissionId))).filter(Boolean))
|
2026-08-28 10:38:58 +08:00
|
|
|
const domesticPdaCodes = roleCodes('CN_FIRSTLEG_PDA_OPERATOR')
|
|
|
|
|
context.assert(MODULE, '国内头程PDA角色只具备国内任务查询与作业权限', rolesResponse,
|
|
|
|
|
domesticPdaCodes.size === 2
|
|
|
|
|
&& domesticPdaCodes.has('platform:firstleg:pda:view')
|
|
|
|
|
&& domesticPdaCodes.has('platform:firstleg:pda:work')
|
|
|
|
|
&& !domesticPdaCodes.has('lastleg:pda:operate'),
|
|
|
|
|
`permissions=${[...domesticPdaCodes].sort().join(',')}`, OPTIONS)
|
|
|
|
|
|
|
|
|
|
const overseasPdaCodes = roleCodes('OVERSEAS_WAREHOUSE_PDA_OPERATOR')
|
|
|
|
|
context.assert(MODULE, '海外仓PDA角色只具备海外PDA作业权限', rolesResponse,
|
|
|
|
|
overseasPdaCodes.size === 1
|
|
|
|
|
&& overseasPdaCodes.has('lastleg:pda:operate')
|
|
|
|
|
&& !overseasPdaCodes.has('platform:firstleg:pda:view')
|
|
|
|
|
&& !overseasPdaCodes.has('platform:firstleg:pda:work'),
|
|
|
|
|
`permissions=${[...overseasPdaCodes].sort().join(',')}`, OPTIONS)
|
2026-08-10 12:22:29 +08:00
|
|
|
|
|
|
|
|
const operatorCodes = roleCodes('WAREHOUSE_OPERATOR')
|
2026-09-03 19:10:18 +08:00
|
|
|
context.assert(MODULE, '操作员具备国内入仓权限、四期只读权限与五期履约权限', rolesResponse,
|
2026-08-10 12:22:29 +08:00
|
|
|
operatorCodes.has('platform:firstleg:inbound:loading:view')
|
|
|
|
|
&& operatorCodes.has('platform:firstleg:inbound:loading:edit')
|
2026-10-21 15:10:44 +08:00
|
|
|
&& operatorCodes.size === 34
|
2026-08-25 10:31:04 +08:00
|
|
|
&& PHASE4_READ_CODES.every((code) => operatorCodes.has(code))
|
2026-09-03 19:10:18 +08:00
|
|
|
&& PHASE5_OPERATOR_CODES.every((code) => operatorCodes.has(code))
|
2026-08-10 12:22:29 +08:00
|
|
|
&& !operatorCodes.has('platform:firstleg:inbound:loading:complete')
|
|
|
|
|
&& !operatorCodes.has('platform:firstleg:pda:assign')
|
|
|
|
|
&& !operatorCodes.has('platform:firstleg:outbound-dispatch:confirm')
|
2026-08-25 10:31:04 +08:00
|
|
|
&& !operatorCodes.has('platform:firstleg:outbound-dispatch:reverse')
|
|
|
|
|
&& [...operatorCodes].filter((code) => code.startsWith('platform:overseas-'))
|
2026-09-03 19:10:18 +08:00
|
|
|
.every((code) => PHASE4_READ_CODES.includes(code) || PHASE5_OPERATOR_CODES.includes(code)),
|
2026-08-10 12:22:29 +08:00
|
|
|
`permissionCount=${operatorCodes.size}`, OPTIONS)
|
|
|
|
|
|
|
|
|
|
const tallyCodes = roleCodes('WAREHOUSE_TALLY')
|
2026-09-03 19:10:18 +08:00
|
|
|
context.assert(MODULE, '理货员可维护证据、复核并只读查看海外履约', rolesResponse,
|
2026-08-10 12:22:29 +08:00
|
|
|
tallyCodes.has('platform:firstleg:inbound:loading:view')
|
|
|
|
|
&& tallyCodes.has('platform:firstleg:inbound:loading:edit')
|
|
|
|
|
&& tallyCodes.has('platform:firstleg:pda:review')
|
2026-09-03 19:41:25 +08:00
|
|
|
&& tallyCodes.size === 27
|
2026-08-25 10:31:04 +08:00
|
|
|
&& PHASE4_READ_CODES.every((code) => tallyCodes.has(code))
|
2026-09-03 19:10:18 +08:00
|
|
|
&& PHASE5_TALLY_CODES.every((code) => tallyCodes.has(code))
|
2026-08-10 12:22:29 +08:00
|
|
|
&& !tallyCodes.has('platform:firstleg:inbound:loading:complete')
|
2026-08-25 10:31:04 +08:00
|
|
|
&& !tallyCodes.has('platform:firstleg:outbound-dispatch:confirm')
|
|
|
|
|
&& [...tallyCodes].filter((code) => code.startsWith('platform:overseas-'))
|
2026-09-03 19:10:18 +08:00
|
|
|
.every((code) => PHASE4_READ_CODES.includes(code) || PHASE5_TALLY_CODES.includes(code)),
|
2026-08-10 12:22:29 +08:00
|
|
|
`permissionCount=${tallyCodes.size}`, OPTIONS)
|
|
|
|
|
|
2026-09-03 19:10:18 +08:00
|
|
|
context.assert(MODULE, '四期只读与五期岗位权限集合严格匹配', rolesResponse,
|
2026-08-25 10:31:04 +08:00
|
|
|
PHASE4_READ_CODES.every((code) => code.endsWith(':list'))
|
2026-09-03 19:10:18 +08:00
|
|
|
&& PHASE5_TALLY_CODES.every((code) => code.endsWith(':list'))
|
|
|
|
|
&& PHASE5_OPERATOR_CODES.some((code) => PHASE4_WRITE_SUFFIXES.some((suffix) => code.endsWith(suffix)))
|
|
|
|
|
&& PHASE5_OPERATOR_CODES.every((code) => operatorCodes.has(code))
|
|
|
|
|
&& PHASE5_TALLY_CODES.every((code) => tallyCodes.has(code)),
|
|
|
|
|
`phase4ReadCodes=${PHASE4_READ_CODES.length}, phase5OperatorCodes=${PHASE5_OPERATOR_CODES.length}, phase5TallyCodes=${PHASE5_TALLY_CODES.length}`, OPTIONS)
|
2026-08-25 10:31:04 +08:00
|
|
|
|
2026-08-10 12:22:29 +08:00
|
|
|
context.assert(MODULE, '仓库系统角色不包含任何客户侧权限', rolesResponse,
|
2026-08-28 10:38:58 +08:00
|
|
|
[...operatorCodes, ...domesticPdaCodes, ...overseasPdaCodes, ...tallyCodes]
|
2026-08-24 21:46:19 +08:00
|
|
|
.every((code) => code.startsWith('platform:') || code === 'lastleg:pda:operate'),
|
|
|
|
|
'warehouse roles may use platform:* plus the single overseas service authority lastleg:pda:operate', OPTIONS)
|
2026-08-10 12:22:29 +08:00
|
|
|
|
|
|
|
|
const unauthorized = await context.request('GET',
|
2026-08-14 16:06:27 +08:00
|
|
|
'/api/platform/v1/first-leg/pda-loading/tasks?cabinetNo=UNAUTHORIZED&warehouseId=1', undefined, '')
|
2026-08-10 12:22:29 +08:00
|
|
|
context.addStep(MODULE, '未登录访问仓库PDA任务被拒绝', unauthorized,
|
|
|
|
|
[401, 403].includes(unauthorized.http)
|
|
|
|
|
|| (unauthorized.http === 200 && ['A0401', 'A0403', '401', '403'].includes(String(unauthorized.body?.code))),
|
|
|
|
|
`HTTP=${unauthorized.http}, code=${unauthorized.body?.code ?? 'missing'}`, OPTIONS)
|
|
|
|
|
}
|