fix: bind overseas receiving to shared warehouse

This commit is contained in:
2026-08-24 21:46:19 +08:00
parent 3607bebbfd
commit 8cc4c6f6da
61 changed files with 774 additions and 118 deletions
@@ -46,32 +46,40 @@ async function loginMerchantForNegativeProbe(context) {
return nonBlank(data?.overseasAccessToken) ? data.overseasAccessToken : null
}
async function createPicker(context, index) {
async function resolvePickerAccess(context) {
const rolesResponse = await context.request('GET', '/api/platform/v1/iam/roles')
const roles = context.apiSuccess(MODULE, `查询海外PDA仓库操作员角色${index}`, rolesResponse, '', OPTIONS)
const roles = context.apiSuccess(MODULE, '查询海外PDA仓库操作员角色', rolesResponse, '', OPTIONS)
const role = (Array.isArray(roles) ? roles : []).find((item) => item?.roleCode === 'WAREHOUSE_PICKER' && item?.status === 'ACTIVE')
const permissionsResponse = await context.request('GET', '/api/platform/v1/iam/permissions')
const permissions = context.apiSuccess(MODULE, `查询海外PDA权限资源${index}`, permissionsResponse, '', OPTIONS)
const permissions = context.apiSuccess(MODULE, '查询海外PDA权限资源', permissionsResponse, '', OPTIONS)
const operate = (Array.isArray(permissions) ? permissions : []).find((item) => item?.permissionCode === 'lastleg:pda:operate')
const assigned = Boolean(role && operate && (role.permissionIds || []).map(id).includes(id(operate.id)))
context.assert(MODULE, `WAREHOUSE_PICKER精确具备海外PDA权限${index}`, rolesResponse, assigned,
context.assert(MODULE, 'WAREHOUSE_PICKER精确具备海外PDA权限', rolesResponse, assigned,
`roleId=${id(role?.id) || 'missing'}, permissionId=${id(operate?.id) || 'missing'}`, OPTIONS)
if (!assigned) {
blockerGap(context, '海外PDA仓库操作员权限', 'WAREHOUSE_PICKER未获得lastleg:pda:operate;必须先执行最新国内统一增量SQL并部署IAM')
return null
}
return { role, operate }
}
async function createPicker(context, index, overseasWarehouseId, access) {
const role = access.role
const suffix = markerSuffix(context)
const username = `pda_overseas_${suffix.toLowerCase()}_${index}`.slice(0, 64)
const password = `Pda${suffix.replace(/[^A-Za-z0-9]/g, '')}A${index}9`
const phone = await context.findAvailablePhone(MODULE)
const payload = { username, displayName: `海外PDA验收操作员${index}-${suffix}`, phone,
email: `${username}@example.com`, tenantId: '0', roleIds: [id(role.id)], password, status: 'ACTIVE' }
email: `${username}@example.com`, tenantId: '0', roleIds: [id(role.id)],
overseasWarehouseId, password, status: 'ACTIVE' }
const createdResponse = await context.request('POST', '/api/platform/v1/iam/users', payload)
const created = context.apiSuccess(MODULE, `创建海外PDA仓库操作员${index}`, createdResponse, '', OPTIONS)
const membershipId = id(created?.membershipId)
context.assert(MODULE, `海外PDA操作员${index}冻结平台范围与角色`, createdResponse,
longId(membershipId) && id(created?.tenantId) === '0' && (created?.roleIds || []).map(id).includes(id(role.id)),
`membershipId=${membershipId || 'missing'}, tenantId=${id(created?.tenantId) || 'missing'}`, OPTIONS)
longId(membershipId) && id(created?.tenantId) === '0'
&& id(created?.overseasWarehouseId) === id(overseasWarehouseId)
&& (created?.roleIds || []).map(id).includes(id(role.id)),
`membershipId=${membershipId || 'missing'}, tenantId=${id(created?.tenantId) || 'missing'}, overseasWarehouseId=${id(created?.overseasWarehouseId) || 'missing'}`, OPTIONS)
if (!longId(membershipId)) return null
const login = await context.request('POST', '/api/auth/login', { username, password }, '')
const token = success(login) ? login.body?.data?.overseasAccessToken : null
@@ -88,7 +96,7 @@ async function disablePicker(context, picker, index) {
}
async function createPallet(context, inbound, index) {
const warehouseId = id(inbound?.destinationWarehouseId)
const warehouseId = id(inbound?.overseasWarehouseId)
if (!longId(warehouseId)) return null
const suffix = markerSuffix(context)
const location = await context.requestOverseas('POST', `/api/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/locations`, {
@@ -114,7 +122,8 @@ async function uploadPhotos(context, token) {
const malicious = await context.requestOverseasMultipart('POST', '/api/pda/v1/last-leg/receiving-containers/current/unload-photos/files', {
name: `${context.marker}-forged.png`, type: 'image/png', content: Uint8Array.from(Buffer.from('<script>alert(1)</script>')),
}, token)
context.addStep(MODULE, '伪造图片内容安全扫描被拒绝', malicious, !success(malicious) && traced(malicious), trace(malicious), OPTIONS)
context.addStep(MODULE, '伪造图片内容安全扫描返回A0400', malicious,
malicious?.http === 200 && code(malicious) === 'A0400' && traced(malicious), trace(malicious), OPTIONS)
const photos = []
for (const role of PHOTO_ROLES) {
const response = await context.requestOverseasMultipart('POST', '/api/pda/v1/last-leg/receiving-containers/current/unload-photos/files', {
@@ -265,7 +274,7 @@ async function runReceiving(context, token, secondaryToken, merchantToken) {
const pallet = await createPallet(context, inbound, index)
if (!pallet) {
blockerGap(context, `${inbound.destinationType}入仓码${inbound.inboundCode}实物托盘`,
`无法在目的仓 ${inbound.destinationWarehouseId || 'missing'} 创建可解析真实托盘`, 'warehouse-resource')
`无法在实际海外收货仓 ${inbound.overseasWarehouseId || 'missing'} 创建可解析真实托盘;目的仓 ${inbound.destinationWarehouseId || 'missing'} 不得代替物理仓`, 'warehouse-resource')
continue
}
pallets.set(inbound.inboundCode, pallet)
@@ -313,11 +322,21 @@ export async function run(context) {
context.createdResources.phase4OverseasPda = {
marker: context.marker, containerNo: null, customerId: null,
palletIds: [], locationIds: [], finalStatus: null, pickerMembershipIds: [],
cleanup: 'Pallets and locations used by completed inventory are retained as acceptance evidence',
overseasWarehouseId: null, overseasWarehouseName: null,
cleanup: 'Shared warehouse, pallets and locations used by completed inventory are retained as acceptance evidence',
}
const merchantToken = await loginMerchantForNegativeProbe(context)
const primary = await createPicker(context, 1)
const secondary = primary?.token ? await createPicker(context, 2) : null
const pickerAccess = await resolvePickerAccess(context)
if (!pickerAccess) return
const sharedWarehouse = await createSharedWarehouse(context)
if (!sharedWarehouse) {
blockerGap(context, '海外PDA共享实际收货仓', '无法通过平台正式API创建customerId=0共享物理仓')
return
}
context.createdResources.phase4OverseasPda.overseasWarehouseId = sharedWarehouse.id
context.createdResources.phase4OverseasPda.overseasWarehouseName = sharedWarehouse.name
const primary = await createPicker(context, 1, sharedWarehouse.id, pickerAccess)
const secondary = primary?.token ? await createPicker(context, 2, sharedWarehouse.id, pickerAccess) : null
for (const picker of [primary, secondary]) if (picker?.membershipId) {
context.createdResources.phase4OverseasPda.pickerMembershipIds.push(picker.membershipId)
}
@@ -332,3 +351,22 @@ export async function run(context) {
await disablePicker(context, primary, 1)
}
}
async function createSharedWarehouse(context) {
const suffix = markerSuffix(context)
const payload = {
customerId: 0, warehouseCode: `PDA-${suffix}`.slice(0, 50),
warehouseName: `PDA共享收货仓-${suffix}`.slice(0, 120), warehouseType: 'OVERSEAS',
countryCode: 'US', timezone: 'America/Los_Angeles', contactName: 'PDA Acceptance',
contactPhone: '+1-213-555-0198', contactEmail: `pda-${suffix.toLowerCase()}@example.com`,
status: 'ACTIVE', remark: `${context.marker}-shared-receiving`,
address: { addressType: 'WAREHOUSE', countryCode: 'US', stateProvince: 'CA', city: 'Los Angeles',
district: 'Commerce', postalCode: '90040', addressLine1: '1 Acceptance Logistics Ave' },
capabilities: [{ capabilityCode: 'RECEIVING', capabilityName: '收货', enabled: true }],
}
const created = await context.requestOverseas('POST', '/api/platform/v1/warehouse-admin', payload)
const warehouseId = id(created?.body?.data)
context.addStep(MODULE, '创建本轮PDA共享实际收货仓', created,
success(created) && longId(warehouseId), `warehouseId=${warehouseId || 'missing'}; ${trace(created)}`, OPTIONS)
return longId(warehouseId) ? { id: warehouseId, name: payload.warehouseName } : null
}