feat(pda): prefer business identifiers
This commit is contained in:
@@ -63,8 +63,9 @@ test('picking task list uses remote loading orders and never falls back to demo
|
||||
assert.match(tasks, /\['UNASSIGNED', 'IN_PROGRESS', 'AWAITING_REVIEW', 'PROCESSING', 'COMPLETION_FAILED'\]\.includes\(task\.workflowStatus\)/)
|
||||
assert.match(tasks, /\['AWAITING_REVIEW', 'COMPLETION_FAILED'\]\.includes\(task\.workflowStatus\) \|\| \(task\.assignmentVersion !== null/)
|
||||
assert.match(tasks, /暂无待处理的拣货任务/)
|
||||
assert.match(tasks, /class="task-label">集装箱号<\/text><text class="task-code">\{\{ task\.currentContainerNo \}\}/)
|
||||
assert.match(tasks, /柜次号 \{\{ task\.cabinetNo \}\}/)
|
||||
assert.match(tasks, /class="task-label">集装箱号<\/text><text class="task-code">\{\{ displayBusinessValue\(task\.currentContainerNo\) \}\}/)
|
||||
assert.match(tasks, /柜次号 \{\{ displayBusinessValue\(task\.cabinetNo\) \}\}/)
|
||||
assert.match(tasks, /\{\{ businessReference\(task\) \}\}/)
|
||||
assert.match(tasks, /button v-for="task in tasks"[\s\S]*?class="task-card"[\s\S]*?@tap="enterTask\(task\)"/)
|
||||
assert.match(tasks, /records\.filter\(\(task\) => this\.isTaskVisible\(task\)\)/)
|
||||
assert.match(tasks, /isTaskReady\(task\)[\s\S]*?task\.recordStatus === 'ACTIVE'[\s\S]*?!task\.mergedIntoContainerBookingId/)
|
||||
@@ -291,7 +292,9 @@ test('shared visual contracts preserve accessible PDA targets', async () => {
|
||||
assert.match(tasks, /\.task-card:last-child\s*\{[^}]*margin-bottom:\s*0;/)
|
||||
assert.match(styles, /\.work-content\s*\{[^}]*padding:\s*1px 0 104px;/)
|
||||
assert.match(picking, /class="box-select-summary"/)
|
||||
assert.match(styles, /\.box-select-summary\s*\{[^}]*min-width:\s*0;[^}]*overflow:\s*hidden;[^}]*text-overflow:\s*ellipsis;[^}]*white-space:\s*nowrap;/)
|
||||
assert.match(styles, /\.box-select-summary\s*\{[^}]*min-width:\s*0;[^}]*overflow-wrap:\s*anywhere;[^}]*white-space:\s*normal;/)
|
||||
assert.doesNotMatch(styles, /\.box-select-summary\s*\{[^}]*(text-overflow:\s*ellipsis|white-space:\s*nowrap)/)
|
||||
assert.match(styles, /\.box-dropdown\s*\{[^}]*top:\s*calc\(100% \+ 5px\);/)
|
||||
assert.match(styles, /\.submit-section\s*\{[\s\S]*?bottom:\s*8px;[\s\S]*?width:\s*calc\(100% - 20px\);/)
|
||||
assert.match(styles, /\.submit-btn\s*\{[\s\S]*?flex:\s*1;[\s\S]*?height:\s*56px;/)
|
||||
assert.match(styles, /\.home-group-mark\s*\{[^}]*margin-right:\s*7px;/)
|
||||
|
||||
@@ -105,7 +105,7 @@ test('completed detail overrides stale local state and an empty batch refreshes
|
||||
|
||||
test('final completion preserves backend gate errors and refreshes after an atomic A0409', () => {
|
||||
const complete = page.match(/async completeLoadingTask\(\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || ''
|
||||
assert.match(complete, /this\.isTaskConflict\(error\)[\s\S]*?await this\.refreshAfterTaskConflict\(error\.message \|\| '订舱、装柜关联或证据状态已变化,请刷新后重试'\)/)
|
||||
assert.match(complete, /this\.isTaskConflict\(error\)[\s\S]*?await this\.refreshAfterTaskConflict\(sanitizeUserMessage\(error\.message, '订舱、装柜关联或证据状态已变化,请刷新后重试'\)\)/)
|
||||
assert.doesNotMatch(complete, /拖车|trucking/i)
|
||||
const save = page.match(/async saveEvidence\([^)]*\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || ''
|
||||
assert.match(save, /this\.isTaskConflict\(error\)[\s\S]*?refreshAfterTaskConflict/)
|
||||
@@ -268,9 +268,8 @@ test('picking mutations stay closed until the canonical task has been claimed',
|
||||
|
||||
test('completion shows a Chinese server failure with a request correlation id', () => {
|
||||
assert.match(page, /remoteFailureMessage\(error, fallback = '操作失败'\)/)
|
||||
assert.match(page, /\^internal server error\$\/i/)
|
||||
assert.match(page, /服务端处理异常,请联系管理员/)
|
||||
assert.match(page, /请求编号/)
|
||||
assert.match(page, /formatApiError\(error, fallback\)/)
|
||||
assert.match(page, /import \{ businessReference, displayBusinessValue, formatApiError, sanitizeUserMessage \}/)
|
||||
assert.match(page, /requestIdFromServer: error\.requestId/)
|
||||
assert.match(page, /this\.remoteFailureMessage\(error, '装柜完成失败'\)/)
|
||||
})
|
||||
|
||||
@@ -53,7 +53,7 @@ test('task detail restores before and after claim and exposes S0500 metadata', a
|
||||
assert.match(http, /error\.code = body\.code \|\| response\.statusCode/)
|
||||
assert.match(http, /error\.requestId = body\.requestId/)
|
||||
assert.match(http, /error\.traceId = body\.traceId/)
|
||||
assert.match(page, /const code = String\(error\?\.code \|\| ''\)\.trim\(\)/)
|
||||
assert.match(page, /错误码:\$\{code\}/)
|
||||
assert.match(page, /formatApiError\(error, fallback\)/)
|
||||
assert.match(page, /@\/utils\/user-facing\.js/)
|
||||
assert.match(sync, /this\.remoteFailureMessage\(error, '任务同步失败'\)/)
|
||||
})
|
||||
|
||||
@@ -11,8 +11,8 @@ test('picking task list exposes only operator-facing production information', as
|
||||
])
|
||||
|
||||
assert.match(tasks, /共 \$\{tasks\.length\} 个待处理任务/)
|
||||
assert.match(tasks, /v-if="task\.displayNo"/)
|
||||
assert.match(tasks, /v-if="task\.warehouseName"/)
|
||||
assert.match(tasks, /\{\{ businessReference\(task\) \}\}/)
|
||||
assert.match(tasks, /displayBusinessValue\(task\.warehouseName, '未关联'\)/)
|
||||
assert.doesNotMatch(tasks, /接口返回|当前接口|apiEnvironment|rawTaskCount|账号:|未命名订单|缺少仓库信息|服务端就绪校验|console\.info/)
|
||||
assert.doesNotMatch(adapter, /业务单号待同步/)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { businessReference, displayBusinessValue, formatApiError, sanitizeUserMessage } from '../utils/user-facing.js'
|
||||
|
||||
const read = (path) => readFile(new URL(path, import.meta.url), 'utf8')
|
||||
|
||||
test('business identifiers never fall back to an internal id', () => {
|
||||
assert.equal(displayBusinessValue(null), '—')
|
||||
assert.equal(displayBusinessValue('', '未关联'), '未关联')
|
||||
assert.equal(businessReference({ orderNo: 'SO-2026-001', inboundCode: 'RK-2026-002', taskId: '2092889048731414530' }), '订单号 SO-2026-001 · 入仓码 RK-2026-002')
|
||||
assert.equal(businessReference({ taskId: '2092889048731414530', inboundId: '2092889048731414531' }), '业务编号未关联')
|
||||
})
|
||||
|
||||
test('operator errors hide internal ids but retain request and trace evidence', () => {
|
||||
const error = {
|
||||
message: 'taskId: 2092889048731414530 对应 loadingPlanId=2092889048731414531 处理失败',
|
||||
code: 'S0500',
|
||||
requestId: 'dbde0d81-7798-4d22-a12e-1731d7ec9bb1',
|
||||
traceId: '5b0ba4f3ed5236b34997dd82162a5138'
|
||||
}
|
||||
const message = formatApiError(error, '任务处理失败')
|
||||
assert.doesNotMatch(message, /209288904873141453[01]|taskId|loadingPlanId/)
|
||||
assert.match(message, /错误码:S0500/)
|
||||
assert.match(message, /requestId:dbde0d81/)
|
||||
assert.match(message, /traceId:5b0ba4f3/)
|
||||
assert.equal(sanitizeUserMessage('operation 2092889048731414530 failed'), 'operation 内部标识已隐藏 failed')
|
||||
})
|
||||
|
||||
test('priority PDA templates do not interpolate internal ids', async () => {
|
||||
const pages = await Promise.all([
|
||||
read('../pages/login/login.vue'),
|
||||
read('../pages/tasks/tasks.vue'),
|
||||
read('../pages/picking/picking.vue'),
|
||||
read('../pages/history/history.vue')
|
||||
])
|
||||
for (const page of pages) {
|
||||
const template = page.match(/<template>[\s\S]*?<\/template>/)?.[0] || ''
|
||||
assert.doesNotMatch(template, /\{\{[^}\n]*(?:taskId|inboundId|orderId|loadingPlanId|containerBookingId)[^}\n]*\}\}/)
|
||||
}
|
||||
assert.match(pages[0], /formatApiError\(error, '登录失败'\)/)
|
||||
assert.match(pages[1], /businessReference\(task\)/)
|
||||
assert.match(pages[2], /businessReference\(currentBox\)/)
|
||||
assert.match(pages[3], /businessReference\(entry\.box\)/)
|
||||
})
|
||||
Reference in New Issue
Block a user