const MODULE = 'phase3-warehouse-rbac' const OPTIONS = { scenario: 'warehouse-role-boundary', deploymentBlocker: true } 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'] 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 : []) .filter((item) => ['WAREHOUSE_OPERATOR', 'OVERSEAS_WAREHOUSE_PDA_OPERATOR', 'WAREHOUSE_TALLY', 'CN_FIRSTLEG_PDA_OPERATOR'].includes(item.roleCode)) .map((item) => [item.roleCode, item])) context.assert(MODULE, '国内后台、国内PDA、海外PDA、理货复核四个系统角色已启用', rolesResponse, warehouseRoles.size === 4 && [...warehouseRoles.values()] .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)) 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) const operatorCodes = roleCodes('WAREHOUSE_OPERATOR') context.assert(MODULE, '操作员具备国内入仓权限与四期十项只读权限', rolesResponse, operatorCodes.has('platform:firstleg:inbound:loading:view') && operatorCodes.has('platform:firstleg:inbound:loading:edit') && operatorCodes.size === 15 && PHASE4_READ_CODES.every((code) => operatorCodes.has(code)) && !operatorCodes.has('platform:firstleg:inbound:loading:complete') && !operatorCodes.has('platform:firstleg:pda:assign') && !operatorCodes.has('platform:firstleg:outbound-dispatch:confirm') && !operatorCodes.has('platform:firstleg:outbound-dispatch:reverse') && [...operatorCodes].filter((code) => code.startsWith('platform:overseas-')) .every((code) => !PHASE4_WRITE_SUFFIXES.some((suffix) => code.endsWith(suffix))), `permissionCount=${operatorCodes.size}`, OPTIONS) const tallyCodes = roleCodes('WAREHOUSE_TALLY') context.assert(MODULE, '理货员可后台维护证据和复核但不能完成装柜', rolesResponse, tallyCodes.has('platform:firstleg:inbound:loading:view') && tallyCodes.has('platform:firstleg:inbound:loading:edit') && tallyCodes.has('platform:firstleg:pda:review') && tallyCodes.size === 22 && PHASE4_READ_CODES.every((code) => tallyCodes.has(code)) && !tallyCodes.has('platform:firstleg:inbound:loading:complete') && !tallyCodes.has('platform:firstleg:outbound-dispatch:confirm') && [...tallyCodes].filter((code) => code.startsWith('platform:overseas-')) .every((code) => !PHASE4_WRITE_SUFFIXES.some((suffix) => code.endsWith(suffix))), `permissionCount=${tallyCodes.size}`, OPTIONS) context.assert(MODULE, '四期list权限同时用于列表与详情且不隐含写权限', rolesResponse, PHASE4_READ_CODES.every((code) => code.endsWith(':list')) && [...operatorCodes, ...tallyCodes] .filter((code) => code.startsWith('platform:overseas-')) .every((code) => PHASE4_READ_CODES.includes(code)), `phase4ReadCodes=${PHASE4_READ_CODES.length}`, OPTIONS) context.assert(MODULE, '仓库系统角色不包含任何客户侧权限', rolesResponse, [...operatorCodes, ...domesticPdaCodes, ...overseasPdaCodes, ...tallyCodes] .every((code) => code.startsWith('platform:') || code === 'lastleg:pda:operate'), 'warehouse roles may use platform:* plus the single overseas service authority lastleg:pda:operate', OPTIONS) const unauthorized = await context.request('GET', '/api/platform/v1/first-leg/pda-loading/tasks?cabinetNo=UNAUTHORIZED&warehouseId=1', undefined, '') 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) }