优化
This commit is contained in:
@@ -193,20 +193,32 @@ export async function run(context) {
|
||||
&& Number(claimed?.assignment?.version) === 0,
|
||||
`workflow=${claimed?.workflowStatus || 'missing'}, assigneeId=${id(claimed?.assignment?.assigneeId) || 'missing'}`, OPTIONS)
|
||||
|
||||
const stalePickResponse = await request(context, 'POST', `${PDA}/tasks/${taskId}/items/${id(pickItem.id)}/pick`, {
|
||||
warehouseId, pickedPackageCount: 1, locationIds: [], version: Number(pickItem.version) + 1,
|
||||
operationId: `${context.marker}-pda-stale`.slice(0, 128),
|
||||
})
|
||||
context.addStep(MODULE, 'PDA 旧明细版本拣货返回冲突', stalePickResponse,
|
||||
stalePickResponse.http === 200 && ['A0409', '409'].includes(codeOf(stalePickResponse)),
|
||||
`HTTP=${stalePickResponse.http}, code=${codeOf(stalePickResponse)}`, OPTIONS)
|
||||
const batchPath = `${PDA}/tasks/${taskId}/batch`
|
||||
const batchItems = [{ itemId: id(pickItem.id), action: 'PICK', pickedPackageCount: 1,
|
||||
finalPickedPackageCount: null, releaseMode: 'MANUAL', locationIds: [],
|
||||
version: Number(pickItem.version), reason: null }]
|
||||
const staleBatchResponse = await request(context, 'POST', batchPath, {
|
||||
warehouseId, containerNo, expectedVersion: Number(task.version) + 1, items: batchItems,
|
||||
}, { 'Idempotency-Key': `${context.marker}-pda-batch-stale`.slice(0, 128) })
|
||||
context.addStep(MODULE, 'PDA batch 旧任务版本返回 A0409', staleBatchResponse,
|
||||
staleBatchResponse.http === 200 && ['A0409', '409'].includes(codeOf(staleBatchResponse)),
|
||||
`HTTP=${staleBatchResponse.http}, code=${codeOf(staleBatchResponse)}`, OPTIONS)
|
||||
|
||||
const pickPayload = {
|
||||
warehouseId, pickedPackageCount: 1, locationIds: [], version: Number(pickItem.version),
|
||||
operationId: `${context.marker}-pda-pick`.slice(0, 128),
|
||||
}
|
||||
const pickResponse = await request(context, 'POST', `${PDA}/tasks/${taskId}/items/${id(pickItem.id)}/pick`, pickPayload)
|
||||
const picked = context.apiSuccess(MODULE, '执行本轮 PDA 全量拣货', pickResponse, '', OPTIONS)
|
||||
const afterFailedBatchResponse = await context.request('GET',
|
||||
`${PDA}/tasks?containerNo=${encodeURIComponent(containerNo)}&warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
const afterFailedBatch = context.apiSuccess(MODULE, '回查失败 batch 后任务快照', afterFailedBatchResponse, '', OPTIONS)
|
||||
const afterFailedItem = afterFailedBatch?.items?.find?.((item) => id(item.id) === id(pickItem.id))
|
||||
context.assert(MODULE, 'PDA batch 失败后整批未写入', afterFailedBatchResponse,
|
||||
Number(afterFailedBatch?.version) === Number(task.version)
|
||||
&& Number(afterFailedItem?.version) === Number(pickItem.version)
|
||||
&& Number(afterFailedItem?.pickedPackageCount) === Number(pickItem.pickedPackageCount || 0)
|
||||
&& afterFailedItem?.itemStatus === pickItem.itemStatus,
|
||||
`taskVersion=${afterFailedBatch?.version ?? 'missing'}, itemVersion=${afterFailedItem?.version ?? 'missing'}, picked=${afterFailedItem?.pickedPackageCount ?? 'missing'}`, OPTIONS)
|
||||
|
||||
const batchPayload = { warehouseId, containerNo, expectedVersion: Number(task.version), items: batchItems }
|
||||
const batchHeaders = { 'Idempotency-Key': `${context.marker}-pda-batch`.slice(0, 128) }
|
||||
const pickResponse = await request(context, 'POST', batchPath, batchPayload, batchHeaders)
|
||||
const picked = context.apiSuccess(MODULE, '单次调用 PDA 原子 batch 完成拣货', pickResponse, '', OPTIONS)
|
||||
const directPdaCompletion = picked?.taskStatus === 'COMPLETED'
|
||||
&& picked?.workflowStatus === 'COMPLETED'
|
||||
context.assert(MODULE, '最后一箱拣货后 PDA 任务按已部署契约直接完成', pickResponse,
|
||||
@@ -214,9 +226,9 @@ export async function run(context) {
|
||||
&& picked?.items?.every?.((item) => Number(item.pickedPackageCount) === Number(item.plannedPackageCount)),
|
||||
`taskStatus=${picked?.taskStatus || 'missing'}, workflow=${picked?.workflowStatus || 'missing'}`, OPTIONS)
|
||||
|
||||
const pickReplayResponse = await request(context, 'POST', `${PDA}/tasks/${taskId}/items/${id(pickItem.id)}/pick`, pickPayload)
|
||||
const pickReplay = context.apiSuccess(MODULE, 'PDA operationId 幂等重放', pickReplayResponse, '', OPTIONS)
|
||||
context.assert(MODULE, 'PDA 重放保持完成任务和拣货数', pickReplayResponse,
|
||||
const pickReplayResponse = await request(context, 'POST', batchPath, batchPayload, batchHeaders)
|
||||
const pickReplay = context.apiSuccess(MODULE, 'PDA batch Idempotency-Key 幂等重放', pickReplayResponse, '', OPTIONS)
|
||||
context.assert(MODULE, 'PDA batch 重放保持完成任务和拣货数', pickReplayResponse,
|
||||
pickReplay?.taskStatus === 'COMPLETED' && pickReplay?.workflowStatus === 'COMPLETED'
|
||||
&& pickReplay?.items?.every?.((item) => Number(item.pickedPackageCount) === Number(item.plannedPackageCount)),
|
||||
`taskStatus=${pickReplay?.taskStatus || 'missing'}`, OPTIONS)
|
||||
|
||||
@@ -88,23 +88,27 @@ export async function run(context) {
|
||||
const slicesResponse = await context.request('GET', slicesPath)
|
||||
const slices = context.apiSuccess(MODULE, '读取柜次真实履约分片以确定报关订单', slicesResponse,
|
||||
`containerBookingId=${containerBookingId}, tenantId=${tenantId}`, OPTIONS)
|
||||
const loadedSlices = Array.isArray(slices)
|
||||
const carryingSlices = Array.isArray(slices)
|
||||
? slices.filter((item) => id(item?.tenantId) === tenantId
|
||||
&& id(item?.containerBookingId) === containerBookingId
|
||||
&& id(item?.parentOrderId) === parentOrderId
|
||||
&& item?.splitRole === 'LOADED'
|
||||
&& (!loadingPlanId || id(item?.loadingPlanId) === loadingPlanId))
|
||||
: []
|
||||
const loadedSlice = loadedSlices.length === 1 ? loadedSlices[0] : null
|
||||
const orderId = id(loadedSlice?.childOrderId)
|
||||
context.assert(MODULE, '报关绑定柜次实际承载的唯一 LOADED 子订单', slicesResponse,
|
||||
loadedSlices.length === 1 && Boolean(orderId) && orderId !== parentOrderId
|
||||
&& id(loadedSlice?.parentOrderId) === parentOrderId
|
||||
&& id(loadedSlice?.containerBookingId) === containerBookingId
|
||||
&& id(loadedSlice?.tenantId) === tenantId
|
||||
&& (!loadingPlanId || id(loadedSlice?.loadingPlanId) === loadingPlanId),
|
||||
`parentOrderId=${parentOrderId}, childOrderId=${orderId || 'missing'}, containerBookingId=${containerBookingId}, loadingPlanId=${id(loadedSlice?.loadingPlanId) || 'missing'}, matches=${loadedSlices.length}`, OPTIONS)
|
||||
if (!orderId || loadedSlices.length !== 1 || orderId === parentOrderId) return
|
||||
const carryingSlice = carryingSlices.length === 1 ? carryingSlices[0] : null
|
||||
const actualOrderId = id(carryingSlice?.childOrderId)
|
||||
const sourceOrderId = id(carryingSlice?.sourceOrderId)
|
||||
const sourceCarriesLoaded = Boolean(actualOrderId) && actualOrderId === sourceOrderId
|
||||
context.assert(MODULE, '报关绑定柜次实际承载的唯一 LOADED 来源分片', slicesResponse,
|
||||
carryingSlices.length === 1 && sourceCarriesLoaded
|
||||
&& id(carryingSlice?.parentOrderId) === parentOrderId
|
||||
&& id(carryingSlice?.containerBookingId) === containerBookingId
|
||||
&& id(carryingSlice?.tenantId) === tenantId
|
||||
&& Number.isInteger(Number(carryingSlice?.packageCount)) && Number(carryingSlice.packageCount) > 0
|
||||
&& (!loadingPlanId || id(carryingSlice?.loadingPlanId) === loadingPlanId),
|
||||
`parentOrderId=${parentOrderId}, sourceOrderId=${sourceOrderId || 'missing'}, actualOrderId=${actualOrderId || 'missing'}, packageCount=${carryingSlice?.packageCount ?? 'missing'}, sourceCarriesLoaded=${sourceCarriesLoaded}, containerBookingId=${containerBookingId}, loadingPlanId=${id(carryingSlice?.loadingPlanId) || 'missing'}, matches=${carryingSlices.length}`, OPTIONS)
|
||||
if (carryingSlices.length !== 1 || !sourceCarriesLoaded
|
||||
|| !Number.isInteger(Number(carryingSlice?.packageCount)) || Number(carryingSlice.packageCount) <= 0) return
|
||||
|
||||
const loginResponse = await request(context, 'POST', '/api/auth/login', {
|
||||
username: MERCHANT_USERNAME, password: MERCHANT_PASSWORD,
|
||||
@@ -131,18 +135,18 @@ export async function run(context) {
|
||||
`checklistVersionId=${checklistVersionId || 'missing'}, items=${checklist?.items?.length ?? 'n/a'}`, OPTIONS)
|
||||
if (!checklistVersionId) return
|
||||
|
||||
const casePayload = { customerId: tenantId, orderId, inboundId, containerBookingId,
|
||||
const casePayload = { customerId: tenantId, orderId: actualOrderId, inboundId, containerBookingId,
|
||||
checklistVersionId, remark: context.marker, expectedVersion: 0 }
|
||||
const caseResponse = await request(context, 'POST', `${PLATFORM}/cases`, casePayload, context.token,
|
||||
{ 'Idempotency-Key': key(context, 'case') })
|
||||
const created = context.apiSuccess(MODULE, '创建客户订单柜次关联报关协同单', caseResponse, '', OPTIONS)
|
||||
const caseId = id(created?.customsCase?.id)
|
||||
context.assert(MODULE, '报关单绑定本轮 LOADED 子订单、入库单、柜次和清单版本', caseResponse,
|
||||
Boolean(caseId) && id(created?.customsCase?.tenantId) === tenantId && id(created?.customsCase?.orderId) === orderId
|
||||
context.assert(MODULE, '报关单绑定本轮实际承载来源分片、入库单、柜次和清单版本', caseResponse,
|
||||
Boolean(caseId) && id(created?.customsCase?.tenantId) === tenantId && id(created?.customsCase?.orderId) === actualOrderId
|
||||
&& id(created?.customsCase?.inboundId) === inboundId && id(created?.customsCase?.containerBookingId) === containerBookingId
|
||||
&& id(created?.customsCase?.checklistVersionId) === checklistVersionId
|
||||
&& created?.customsCase?.status === 'DOCUMENTS_PENDING' && Number(created?.customsCase?.version) === 0,
|
||||
`caseId=${caseId || 'missing'}, parentOrderId=${parentOrderId}, childOrderId=${orderId}, status=${created?.customsCase?.status || 'missing'}`, OPTIONS)
|
||||
`caseId=${caseId || 'missing'}, parentOrderId=${parentOrderId}, sourceOrderId=${sourceOrderId}, actualOrderId=${actualOrderId}, status=${created?.customsCase?.status || 'missing'}`, OPTIONS)
|
||||
if (!caseId) return
|
||||
|
||||
const caseReplay = await request(context, 'POST', `${PLATFORM}/cases`, casePayload, context.token,
|
||||
@@ -305,7 +309,7 @@ export async function run(context) {
|
||||
`HTTP=${forgedReference.http}, code=${codeOf(forgedReference)}`, OPTIONS)
|
||||
|
||||
context.createdResources.phase3Customs = {
|
||||
marker: context.marker, tenantId, parentOrderId, orderId, inboundId, containerBookingId, loadingPlanId,
|
||||
marker: context.marker, tenantId, parentOrderId, orderId: actualOrderId, inboundId, containerBookingId, loadingPlanId,
|
||||
customsCaseId: caseId, submissionBatchId: batchId, releasedReceiptId: releasedId,
|
||||
finalStatus: 'CLEARANCE_COMPLETED',
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const MODULE = 'phase3-loading'
|
||||
const OPTIONS = { scenario: 'phase3-split-preload-loading', deploymentBlocker: true }
|
||||
const MERCHANT_USERNAME = process.env.MERCHANT_TEST_USERNAME || 'merchant_admin'
|
||||
const MERCHANT_PASSWORD = process.env.MERCHANT_TEST_PASSWORD || 'ChangeMe123!'
|
||||
|
||||
function id(value) { return value == null ? '' : String(value) }
|
||||
function codeOf(response) { return response?.body?.code == null ? 'missing' : String(response.body.code) }
|
||||
@@ -227,20 +229,102 @@ export async function run(context) {
|
||||
claimed?.workflowStatus === 'IN_PROGRESS' && Boolean(claimed?.assignment?.assigneeId),
|
||||
`workflow=${claimed?.workflowStatus || 'missing'}`, OPTIONS)
|
||||
|
||||
// P3-Q11 uses task-level assignment as the single-operator guard; no item claim API exists.
|
||||
const batchPath = `${pdaBase}/tasks/${canonicalTaskId}/batch`
|
||||
const batchKey = `${context.marker}-bridge-batch`.slice(0, 128)
|
||||
const batchPayload = {
|
||||
warehouseId, containerNo,
|
||||
expectedVersion: Number(claimed?.version),
|
||||
items: [{
|
||||
itemId: id(canonicalItem.id), action: 'PICK', pickedPackageCount: 1,
|
||||
releaseMode: 'MANUAL', locationIds: [], version: Number(canonicalItem.version),
|
||||
}],
|
||||
}
|
||||
|
||||
const canonicalPick = await request(context, 'POST',
|
||||
`${pdaBase}/tasks/${canonicalTaskId}/items/${id(canonicalItem.id)}/pick`, {
|
||||
warehouseId, pickedPackageCount: 1, locationIds: [], version: Number(canonicalItem.version),
|
||||
operationId: `${context.marker}-bridge-pick`.slice(0, 128),
|
||||
const staleBatch = await request(context, 'POST', batchPath,
|
||||
{ ...batchPayload, expectedVersion: Number(batchPayload.expectedVersion) + 1 },
|
||||
{ 'Idempotency-Key': `${context.marker}-bridge-batch-stale`.slice(0, 128) })
|
||||
context.addStep(MODULE, 'PDA 原子批次旧任务版本返回冲突', staleBatch,
|
||||
staleBatch.http === 200 && codeOf(staleBatch) === 'A0409',
|
||||
`HTTP=${staleBatch.http}, code=${codeOf(staleBatch)}`, OPTIONS)
|
||||
|
||||
const invalidBatch = await request(context, 'POST', batchPath, {
|
||||
...batchPayload,
|
||||
items: [...batchPayload.items, {
|
||||
itemId: '999999999999999999', action: 'SKIP', version: 0, reason: 'forged-item-must-rollback',
|
||||
}],
|
||||
}, { 'Idempotency-Key': `${context.marker}-bridge-batch-rollback`.slice(0, 128) })
|
||||
context.addStep(MODULE, 'PDA 原子批次任一非法明细时整批拒绝', invalidBatch,
|
||||
invalidBatch.http === 200 && codeOf(invalidBatch) === 'A0409',
|
||||
`HTTP=${invalidBatch.http}, code=${codeOf(invalidBatch)}`, OPTIONS)
|
||||
const unchangedAfterInvalidResponse = await context.request('GET',
|
||||
`${pdaBase}/tasks?containerNo=${encodeURIComponent(containerNo)}&warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
const unchangedAfterInvalid = context.apiSuccess(MODULE, '回查失败批次后的 PDA 任务快照', unchangedAfterInvalidResponse, '', OPTIONS)
|
||||
const unchangedItem = unchangedAfterInvalid?.items?.find?.((item) => id(item.id) === id(canonicalItem.id))
|
||||
context.assert(MODULE, '失败批次整批回滚且无明细部分写入', unchangedAfterInvalidResponse,
|
||||
unchangedAfterInvalid?.taskStatus === 'LOADING'
|
||||
&& Number(unchangedAfterInvalid?.version) === Number(batchPayload.expectedVersion)
|
||||
&& unchangedItem?.itemStatus === 'PENDING' && Number(unchangedItem?.pickedPackageCount) === 0
|
||||
&& Number(unchangedItem?.version) === Number(canonicalItem.version),
|
||||
`taskVersion=${unchangedAfterInvalid?.version ?? 'missing'}, itemStatus=${unchangedItem?.itemStatus || 'missing'}, picked=${unchangedItem?.pickedPackageCount ?? 'missing'}`, OPTIONS)
|
||||
|
||||
const merchantLogin = await context.request('POST', '/api/auth/login', {
|
||||
username: MERCHANT_USERNAME, password: MERCHANT_PASSWORD,
|
||||
}, '')
|
||||
const merchantToken = merchantLogin.http === 200 && codeOf(merchantLogin) === '200'
|
||||
? merchantLogin.body?.data?.accessToken : null
|
||||
context.addStep(MODULE, '取得客户侧隔离令牌用于 PDA 批次越权验证', merchantLogin,
|
||||
Boolean(merchantToken), `username=${MERCHANT_USERNAME}`, OPTIONS)
|
||||
const customerIsolatedBatch = merchantToken
|
||||
? await request(context, 'POST', batchPath, batchPayload, {
|
||||
Authorization: `Bearer ${merchantToken}`,
|
||||
'Idempotency-Key': `${context.marker}-bridge-batch-customer`.slice(0, 128),
|
||||
})
|
||||
const pickedCanonical = context.apiSuccess(MODULE, '执行柜次 PDA 明细拣货', canonicalPick, '', OPTIONS)
|
||||
: merchantLogin
|
||||
context.addStep(MODULE, '客户身份无权提交平台 PDA 原子批次', customerIsolatedBatch,
|
||||
customerIsolatedBatch.http === 403 && codeOf(customerIsolatedBatch) === 'A0403'
|
||||
&& Boolean(customerIsolatedBatch.requestId) && Boolean(customerIsolatedBatch.traceId),
|
||||
`HTTP=${customerIsolatedBatch.http}, code=${codeOf(customerIsolatedBatch)}, requestId=${customerIsolatedBatch.requestId || 'missing'}, traceId=${customerIsolatedBatch.traceId || 'missing'}`, OPTIONS)
|
||||
|
||||
const currentAssigneeId = Number(claimed?.assignment?.assigneeId)
|
||||
const reassigned = await request(context, 'POST', `${pdaBase}/tasks/${canonicalTaskId}/assign`, {
|
||||
assigneeId: currentAssigneeId + 1000000, assigneeName: `${context.marker}-other-picker`,
|
||||
expectedAssignmentVersion: Number(claimed?.assignment?.version),
|
||||
operationId: `${context.marker}-bridge-assign-other`.slice(0, 128),
|
||||
})
|
||||
const otherAssignment = context.apiSuccess(MODULE, '将 PDA 任务改派给另一作业员', reassigned, '', OPTIONS)
|
||||
const operatorIsolatedBatch = await request(context, 'POST', batchPath, batchPayload,
|
||||
{ 'Idempotency-Key': `${context.marker}-bridge-batch-other-operator`.slice(0, 128) })
|
||||
context.addStep(MODULE, '非任务作业员提交 PDA 原子批次返回冲突', operatorIsolatedBatch,
|
||||
operatorIsolatedBatch.http === 200 && codeOf(operatorIsolatedBatch) === 'A0409',
|
||||
`HTTP=${operatorIsolatedBatch.http}, code=${codeOf(operatorIsolatedBatch)}`, OPTIONS)
|
||||
const restoredAssignmentResponse = await request(context, 'POST', `${pdaBase}/tasks/${canonicalTaskId}/assign`, {
|
||||
assigneeId: currentAssigneeId, assigneeName: claimed?.assignment?.assigneeName || 'admin',
|
||||
expectedAssignmentVersion: Number(otherAssignment?.assignment?.version),
|
||||
operationId: `${context.marker}-bridge-assign-restore`.slice(0, 128),
|
||||
})
|
||||
const restoredAssignment = context.apiSuccess(MODULE, '恢复 PDA 任务到当前作业员', restoredAssignmentResponse, '', OPTIONS)
|
||||
context.assert(MODULE, '作业员隔离验证后任务归属恢复', restoredAssignmentResponse,
|
||||
id(restoredAssignment?.assignment?.assigneeId) === id(currentAssigneeId),
|
||||
`assigneeId=${id(restoredAssignment?.assignment?.assigneeId) || 'missing'}`, OPTIONS)
|
||||
|
||||
const canonicalPick = await request(context, 'POST', batchPath, batchPayload,
|
||||
{ 'Idempotency-Key': batchKey })
|
||||
const pickedCanonical = context.apiSuccess(MODULE, '原子提交柜次 PDA 整批拣货', canonicalPick, '', OPTIONS)
|
||||
const directPdaCompletion = pickedCanonical?.taskStatus === 'COMPLETED'
|
||||
&& pickedCanonical?.workflowStatus === 'COMPLETED'
|
||||
context.assert(MODULE, '柜次 PDA 明细完成后按已部署契约推进任务终态', canonicalPick,
|
||||
directPdaCompletion,
|
||||
context.assert(MODULE, 'PDA 原子批次成功并推进任务终态', canonicalPick,
|
||||
directPdaCompletion && pickedCanonical?.items?.every?.((item) => item.itemStatus !== 'PENDING'),
|
||||
`taskStatus=${pickedCanonical?.taskStatus || 'missing'}, workflow=${pickedCanonical?.workflowStatus || 'missing'}`, OPTIONS)
|
||||
|
||||
const canonicalBatchReplay = await request(context, 'POST', batchPath, batchPayload,
|
||||
{ 'Idempotency-Key': batchKey })
|
||||
context.assert(MODULE, 'PDA 原子批次同参同键重放返回同一终态', canonicalBatchReplay,
|
||||
canonicalBatchReplay.http === 200 && codeOf(canonicalBatchReplay) === '200'
|
||||
&& id(canonicalBatchReplay.body?.data?.id) === canonicalTaskId
|
||||
&& canonicalBatchReplay.body?.data?.taskStatus === 'COMPLETED'
|
||||
&& Number(canonicalBatchReplay.body?.data?.version) === Number(pickedCanonical?.version),
|
||||
`HTTP=${canonicalBatchReplay.http}, code=${codeOf(canonicalBatchReplay)}, taskVersion=${canonicalBatchReplay.body?.data?.version ?? 'missing'}`, OPTIONS)
|
||||
|
||||
// The deployed settlement contract completes a one-item task during pick. A
|
||||
// review call is therefore a negative compatibility assertion, never a second
|
||||
// positive action that can poison the marker fixture with A0409.
|
||||
|
||||
@@ -56,13 +56,10 @@ export async function run(context) {
|
||||
context.addStep(MODULE, '入库兼容入口拒绝客户端伪造汇总数量', forged, failure(forged),
|
||||
`HTTP=${forged.http}, code=${codeOf(forged)}`, OPTIONS)
|
||||
|
||||
context.addCoverageGap(MODULE, OPTIONS.scenario, 'PDA 批量提交、设备草稿版本与批次幂等',
|
||||
'当前 PlatformLoadingPdaController 仅公开单明细 pick/partial/skip 与任务 review,没有批量提交或设备草稿 DTO/OpenAPI',
|
||||
'contract-gap')
|
||||
context.addCoverageGap(MODULE, OPTIONS.scenario, 'PDA 实时库存查询与提交前库存快照校验',
|
||||
'当前 PDA Controller/OpenAPI 未公开实时库存查询或库存版本快照端点,不能用智能装柜候选列表伪装库存契约',
|
||||
'contract-gap')
|
||||
context.addCoverageGap(MODULE, OPTIONS.scenario, '不同仓库作业员竞争同一 PDA 明细',
|
||||
'单明细领取和版本 CAS 已自动化;当前总控没有第二个具备 warehouse PDA work 权限的隔离账号 fixture,不能伪造跨操作者冲突',
|
||||
'fixture')
|
||||
context.addCoverageGap(MODULE, OPTIONS.scenario, 'PDA Android/HBuilderX 真机完整闭环',
|
||||
'原子 batch 真实 HTTP 契约已自动化;扫码、弱网恢复、在线草稿和设备交互仍需 Android/HBuilderX 真机验收',
|
||||
'device')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user