From 5cbb977b96c4c50989f8ee99fd34ffc8165bc0ef Mon Sep 17 00:00:00 2001 From: yml2213 Date: Tue, 15 Sep 2026 10:45:53 +0800 Subject: [PATCH] =?UTF-8?q?fix(pdd):=20=E5=8C=B9=E9=85=8D=E8=AF=8A?= =?UTF-8?q?=E6=96=AD=E5=AF=B9=E9=BD=90=20out=5Fsku=5Fsn=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E5=8A=A0=E9=80=9F=E6=8B=BC=E5=A4=9A=E5=A4=9A=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E5=8F=B7=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 匹配测试/结构化规则补读商家编码,诊断日志标出缺失字段;订单/任务/工单关键词支持带连字符的 PDD order_sn 走索引快路径。 Co-authored-by: Cursor --- .../src/repositories/order-repo.test.ts | 32 ++++++++++++ apps/backend/src/repositories/order-repo.ts | 18 +++++-- .../src/repositories/task-list-filter.test.ts | 14 +++++ apps/backend/src/repositories/task-repo.ts | 18 +++++-- .../worker-platform/work-order-query-repo.ts | 4 +- .../admin-kuaishou-match-service.ts | 49 ++++++++++++----- .../work-product-match-mappers.ts | 15 ++++-- .../worker-platform-service.test.ts | 52 +++++++++++++++++++ .../src/utils/platform-order-id.test.ts | 22 ++++++++ apps/backend/src/utils/platform-order-id.ts | 8 +++ .../pages/admin/panels/ProductMatchPanel.tsx | 48 +++++++++++------ 11 files changed, 237 insertions(+), 43 deletions(-) create mode 100644 apps/backend/src/utils/platform-order-id.test.ts create mode 100644 apps/backend/src/utils/platform-order-id.ts diff --git a/apps/backend/src/repositories/order-repo.test.ts b/apps/backend/src/repositories/order-repo.test.ts index e7ec4de3..a11700c9 100644 --- a/apps/backend/src/repositories/order-repo.test.ts +++ b/apps/backend/src/repositories/order-repo.test.ts @@ -56,6 +56,38 @@ test('订单列表保留订单号和精确 SKU 旧筛选参数兼容', async () assert.deepEqual(calls[0]?.params, ['%26245%', 'SKU-1']) }) +test('订单列表关键词为拼多多 order_sn 时走平台订单号索引快路径', async () => { + const calls: Array<{ sql: string; params: unknown[] }> = [] + const orderSn = '260915-091435834962130' + + await listOrdersWithExecutor(createExecutor(calls, 0), { + keyword: orderSn, + }) + + assert.match( + calls[0]?.sql || '', + /o\.platform_order_id = \$1 OR o\.platform_order_id LIKE \$1 \|\| '%'/s, + ) + assert.doesNotMatch(calls[0]?.sql || '', /oi\.sku_code/i) + assert.doesNotMatch(calls[0]?.sql || '', /item_snapshot_json/i) + assert.deepEqual(calls[0]?.params, [orderSn]) +}) + +test('订单列表 platformOrderId 为拼多多 order_sn 时走索引快路径', async () => { + const calls: Array<{ sql: string; params: unknown[] }> = [] + const orderSn = '260915-091435834962130' + + await listOrdersWithExecutor(createExecutor(calls, 0), { + platformOrderId: orderSn, + }) + + assert.match( + calls[0]?.sql || '', + /o\.platform_order_id = \$1 OR o\.platform_order_id LIKE \$1 \|\| '%'/s, + ) + assert.deepEqual(calls[0]?.params, [orderSn]) +}) + function createExecutor( calls: Array<{ sql: string; params: unknown[] }>, total: number, diff --git a/apps/backend/src/repositories/order-repo.ts b/apps/backend/src/repositories/order-repo.ts index 05df2a71..6d7b1e5e 100644 --- a/apps/backend/src/repositories/order-repo.ts +++ b/apps/backend/src/repositories/order-repo.ts @@ -7,6 +7,7 @@ import type { OrderUpdateInput, } from '../types/repository/inputs.js' import type { OrderListQueryResult, OrderListRow, OrderRow } from '../types/repository/rows.js' +import { isPlatformOrderIdLookupKeyword } from '../utils/platform-order-id.js' export type OrderQueryExecutor = ( text: string, @@ -251,9 +252,9 @@ export async function listOrdersWithExecutor( if (keyword) { const trimmedKeyword = String(keyword).trim() - const isPureOrderNo = /^\d{6,}$/.test(trimmedKeyword) + const isOrderNoLookup = isPlatformOrderIdLookupKeyword(trimmedKeyword) - if (isPureOrderNo) { + if (isOrderNoLookup) { params.push(trimmedKeyword) const paramIndex = `$${params.length}` filters.push( @@ -299,8 +300,17 @@ export async function listOrdersWithExecutor( } if (platformOrderId) { - params.push(`%${platformOrderId}%`) - filters.push(`o.platform_order_id ILIKE $${params.length}`) + const trimmedPlatformOrderId = String(platformOrderId).trim() + if (isPlatformOrderIdLookupKeyword(trimmedPlatformOrderId)) { + params.push(trimmedPlatformOrderId) + const paramIndex = `$${params.length}` + filters.push( + `(o.platform_order_id = ${paramIndex} OR o.platform_order_id LIKE ${paramIndex} || '%')`, + ) + } else { + params.push(`%${trimmedPlatformOrderId}%`) + filters.push(`o.platform_order_id ILIKE $${params.length}`) + } } if (payStatus) { diff --git a/apps/backend/src/repositories/task-list-filter.test.ts b/apps/backend/src/repositories/task-list-filter.test.ts index 6d7647c4..ede1d740 100644 --- a/apps/backend/src/repositories/task-list-filter.test.ts +++ b/apps/backend/src/repositories/task-list-filter.test.ts @@ -62,6 +62,20 @@ test('任务列表关键词为纯数字时智能路由到平台订单号索引 assert.deepEqual(result.params, ['20247230034021572']) }) +test('任务列表关键词为拼多多 order_sn 时智能路由到平台订单号索引条件', () => { + const result = buildTaskListFilters({ + keyword: '260915-091435834962130', + }) + + assert.match( + result.whereClause, + /ft\.platform_order_id = \$1 OR ft\.platform_order_id LIKE \$1 \|\| '%'/s, + ) + assert.doesNotMatch(result.whereClause, /oi\.sku_code/i) + assert.doesNotMatch(result.whereClause, /item_snapshot_json/i) + assert.deepEqual(result.params, ['260915-091435834962130']) +}) + test('任务列表关键词为 DT 任务号时智能路由到任务号索引条件', () => { const result = buildTaskListFilters({ keyword: 'DT881aa66294cc', diff --git a/apps/backend/src/repositories/task-repo.ts b/apps/backend/src/repositories/task-repo.ts index 66c172bb..2fece3cc 100644 --- a/apps/backend/src/repositories/task-repo.ts +++ b/apps/backend/src/repositories/task-repo.ts @@ -1,6 +1,7 @@ import { query, withTransaction } from '../db/client.js' import type { PoolClient } from 'pg' +import { isPlatformOrderIdLookupKeyword } from '../utils/platform-order-id.js' import { normalizeTimestampIso } from '../utils/time.js' import type { TaskCreateInput, @@ -471,10 +472,10 @@ export function buildTaskListFilters({ if (keyword) { const trimmedKeyword = String(keyword).trim() - const isPureOrderNo = /^\d{6,}$/.test(trimmedKeyword) + const isOrderNoLookup = isPlatformOrderIdLookupKeyword(trimmedKeyword) const isTaskNo = /^DT[a-zA-Z0-9_-]+$/i.test(trimmedKeyword) - if (isPureOrderNo) { + if (isOrderNoLookup) { params.push(trimmedKeyword) const paramIndex = `$${params.length}` filters.push( @@ -510,8 +511,17 @@ export function buildTaskListFilters({ } if (platformOrderId) { - params.push(`%${platformOrderId}%`) - filters.push(`ft.platform_order_id ILIKE $${params.length}`) + const trimmedPlatformOrderId = String(platformOrderId).trim() + if (isPlatformOrderIdLookupKeyword(trimmedPlatformOrderId)) { + params.push(trimmedPlatformOrderId) + const paramIndex = `$${params.length}` + filters.push( + `(ft.platform_order_id = ${paramIndex} OR ft.platform_order_id LIKE ${paramIndex} || '%')`, + ) + } else { + params.push(`%${trimmedPlatformOrderId}%`) + filters.push(`ft.platform_order_id ILIKE $${params.length}`) + } } if (taskNo) { diff --git a/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts index 408b5ff6..0b895387 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts @@ -5,6 +5,7 @@ import { normalizeWorkOrderAcceptanceMode, WORK_ORDER_GIFT_PHASE, } from '../../domain/work-order-acceptance-mode.js' +import { isPlatformOrderIdLookupKeyword } from '../../utils/platform-order-id.js' export const WORK_ORDER_SELECT = ` SELECT wo.*, @@ -819,8 +820,7 @@ export function buildWorkOrderWhere({ filters.push(`wo.id = $${params.length}`) } if (normalizedKeyword) { - const isPureOrderNo = /^\d{6,}$/.test(normalizedKeyword) - if (isPureOrderNo) { + if (isPlatformOrderIdLookupKeyword(normalizedKeyword)) { params.push(normalizedKeyword) const paramIndex = `$${params.length}` filters.push( diff --git a/apps/backend/src/services/worker-platform/admin-kuaishou-match-service.ts b/apps/backend/src/services/worker-platform/admin-kuaishou-match-service.ts index 7af68d55..26006fa5 100644 --- a/apps/backend/src/services/worker-platform/admin-kuaishou-match-service.ts +++ b/apps/backend/src/services/worker-platform/admin-kuaishou-match-service.ts @@ -16,6 +16,10 @@ import type { JsonObject } from '../../types/json.js' import type { OrderItemRow, OrderRow } from '../../types/repository/rows.js' import { createHttpError } from '../../utils/http.js' import { nowIso } from '../../utils/time.js' +import { + extractPddOutSkuSn, + type PddRawOrderItem, +} from '../platforms/pdd/pdd-order-mapper.js' import { normalizeDateString, normalizePage, @@ -251,15 +255,21 @@ export async function listAdminKuaishouMatchSources(payload: JsonObject = {}) { for (const row of recentOrderItemsResult.rows) { const snapshot = safeParseJson(row.item_snapshot_json) const ksc = safeParseJson(snapshot.kuaishouSendCode) + const outSkuSn = String(snapshot.out_sku_sn || snapshot.outSkuSn || '').trim() const sellerId = normalizeKuaishouMatchId(ksc.sellerId || row.shop_id || row.platform) const relItemId = normalizeKuaishouMatchId( - ksc.relItemId || row.sku_code || snapshot.goodsId || snapshot.goods_id, + ksc.relItemId || snapshot.goodsId || snapshot.goods_id || row.sku_code, ) const itemTitle = String( ksc.itemTitle || snapshot.goodsName || snapshot.goods_name || row.sku_name || '', ).trim() - const relSkuId = normalizeKuaishouMatchId(ksc.relSkuId || snapshot.spec || row.sku_code) - const skuNick = String(ksc.skuNick || snapshot.spec || row.sku_name || '').trim() + // 拼多多优先展示/分组 out_sku_sn,与生产匹配字段对齐 + const relSkuId = normalizeKuaishouMatchId( + ksc.relSkuId || outSkuSn || snapshot.spec || row.sku_code, + ) + const skuNick = String( + ksc.skuNick || outSkuSn || snapshot.spec || row.sku_name || '', + ).trim() if (!sellerId || (!relItemId && !itemTitle)) continue @@ -296,30 +306,45 @@ export async function listAdminKuaishouMatchSources(payload: JsonObject = {}) { export async function testAdminKuaishouProductMatch(payload: JsonObject = {}) { const body = resolveKuaishouMatchPayload(payload.rawPayload ?? payload.raw_payload ?? payload) const ext = safeParseJson(body.ext) + const outSkuSn = extractPddOutSkuSn(body as PddRawOrderItem) + const goodsId = String(body.goodsId || body.goods_id || '').trim() + const goodsName = String(body.goodsName || body.goods_name || body.itemTitle || '').trim() + const spec = String(body.spec || '').trim() const isPdd = body.platform === 'pdd' || + String(body.provider || '').trim() === 'pdd' || String(body.sellerId || '').includes('pdd') || - Boolean(body.goodsName || body.goods_name || body.goodsId || body.goods_id) + Boolean(outSkuSn) || + Boolean(body.order_sn && (goodsName || goodsId)) + // 与生产 mapper 对齐:匹配字段优先 out_sku_sn + const skuCode = String(body.sku_code || outSkuSn || body.itemId || goodsId || '').trim() + const skuName = String(body.sku_name || outSkuSn || spec || goodsName || '').trim() const order = { provider: String(body.provider || (isPdd ? 'pdd' : '91kaquan')).trim(), platform: String(body.platform || (isPdd ? 'pdd' : 'kuaishou')).trim(), shop_id: String(body.sellerId || body.shop_id || '').trim(), } as OrderRow - const relItemId = normalizeKuaishouMatchId(ext.relItemId || body.goodsId || body.goods_id) - const relSkuId = normalizeKuaishouMatchId(ext.relSkuId || body.spec) - const itemTitle = String(body.itemTitle || body.goodsName || body.goods_name || '').trim() - const skuNick = String(ext.skuNick || body.skuNick || body.spec || '').trim() + const relItemId = normalizeKuaishouMatchId(ext.relItemId || goodsId) + const relSkuId = normalizeKuaishouMatchId(ext.relSkuId || outSkuSn || body.spec) + const itemTitle = String(body.itemTitle || goodsName || '').trim() + const skuNick = String(ext.skuNick || body.skuNick || outSkuSn || body.spec || '').trim() const item = { id: 0, order_id: 0, - sku_code: String(body.sku_code || body.itemId || relItemId || '').trim(), - sku_name: String(body.sku_name || skuNick || itemTitle || '').trim(), - quantity: Math.max(1, Number(body.num || body.quantity) || 1), + sku_code: skuCode, + sku_name: skuName, + quantity: Math.max(1, Number(body.num || body.quantity || body.goods_number) || 1), spec_json: {}, item_snapshot_json: { goodsId: body.goodsId || body.goods_id, - goodsName: body.goodsName || body.goods_name, + goods_id: body.goods_id || body.goodsId, + goodsName, + goods_name: body.goods_name || body.goodsName || goodsName, spec: body.spec, + out_sku_sn: outSkuSn, + outSkuSn, + externalSkuCode: skuCode, + externalSkuName: skuName, kuaishouSendCode: { sellerId: String(body.sellerId || order.shop_id || '').trim(), itemId: String(body.itemId || '').trim(), diff --git a/apps/backend/src/services/worker-platform/work-product-match-mappers.ts b/apps/backend/src/services/worker-platform/work-product-match-mappers.ts index 73433a7d..18669e1d 100644 --- a/apps/backend/src/services/worker-platform/work-product-match-mappers.ts +++ b/apps/backend/src/services/worker-platform/work-product-match-mappers.ts @@ -241,14 +241,20 @@ function scoreWorkProductRule(order: OrderRow, item: OrderItemRow, rule: WorkPro const snapshot = safeParseJson(item.item_snapshot_json) const source = safeParseJson(snapshot.kuaishouSendCode) + const outSkuSn = String(snapshot.out_sku_sn || snapshot.outSkuSn || '').trim() const context = { sellerId: normalizeMatchId(source.sellerId), itemId: normalizeMatchId(source.itemId), - relItemId: normalizeMatchId(source.relItemId), + relItemId: normalizeMatchId(source.relItemId || snapshot.goodsId || snapshot.goods_id), skuId: normalizeMatchId(source.skuId), - relSkuId: normalizeMatchId(source.relSkuId), - itemTitle: normalizeMatchText(source.itemTitle), - skuNick: normalizeMatchText(source.skuNick || source.skuName || snapshot.externalSkuName), + relSkuId: normalizeMatchId(source.relSkuId || outSkuSn || snapshot.spec), + itemTitle: normalizeMatchText( + source.itemTitle || snapshot.goodsName || snapshot.goods_name || '', + ), + // 拼多多等平台:结构化匹配也要读 out_sku_sn,避免有编码却未命中 + skuNick: normalizeMatchText( + source.skuNick || source.skuName || outSkuSn || snapshot.externalSkuName || snapshot.spec, + ), } const ruleShopId = normalizeMatchId(rule.shop_id) if ( @@ -292,7 +298,6 @@ function scoreWorkProductRule(order: OrderRow, item: OrderItemRow, rule: WorkPro return { rule, score } } - const outSkuSn = String(snapshot.out_sku_sn || snapshot.outSkuSn || '').trim() const skuCodes = [item.sku_code, snapshot.externalSkuCode, outSkuSn, snapshot.externalItemId] .map(normalizeMatchId) .filter(Boolean) diff --git a/apps/backend/src/services/worker-platform/worker-platform-service.test.ts b/apps/backend/src/services/worker-platform/worker-platform-service.test.ts index b16ce00b..bff383e8 100644 --- a/apps/backend/src/services/worker-platform/worker-platform-service.test.ts +++ b/apps/backend/src/services/worker-platform/worker-platform-service.test.ts @@ -602,6 +602,58 @@ test('拼多多订单使用 out_sku_sn 字段精准匹配接单规则', () => { assert.equal(decision.rule?.id, rule.id) }) +test('拼多多订单在结构化 match_json 下仍可用 out_sku_sn 命中', () => { + const rule = buildProductRule({ + provider: 'pdd', + platform: 'pdd', + product_name: '安卓怒嚎3钻', + match_type: 'contains', + match_json: { + skuNick: '安卓怒嚎3钻', + skuNickMatchType: 'contains', + }, + }) + const order = { ...buildOrderRow(), provider: 'pdd', platform: 'pdd', shop_id: 'pdd_shop' } + const item = { + ...buildOrderItemRow(), + sku_code: '安卓怒嚎3钻', + sku_name: '安卓怒嚎3钻', + item_snapshot_json: { + goods_id: 961856378217, + goods_name: '和平精英地铁逃生苏尔南暗潮怒嚎血宴苏尔南1-6钻手枪传世武器', + spec: '安卓区(qv互通),苏尔南-怒嚎3钻', + out_sku_sn: '安卓怒嚎3钻', + }, + } + + const decision = resolveMatchingProductRuleDecision(order, item, [rule], []) + assert.equal(decision.reason, 'matched') + assert.equal(decision.rule?.id, rule.id) +}) + +test('拼多多订单缺少 out_sku_sn 且 spec 不含规则名时不命中', () => { + const rule = buildProductRule({ + provider: 'pdd', + platform: 'pdd', + product_name: '安卓怒嚎3钻', + match_type: 'contains', + }) + const order = { ...buildOrderRow(), provider: 'pdd', platform: 'pdd' } + const item = { + ...buildOrderItemRow(), + sku_code: '961856378217', + sku_name: '安卓区(qv互通),苏尔南-怒嚎3钻', + item_snapshot_json: { + goods_id: 961856378217, + goods_name: '和平精英地铁逃生苏尔南暗潮怒嚎血宴苏尔南1-6钻手枪传世武器', + spec: '安卓区(qv互通),苏尔南-怒嚎3钻', + }, + } + + const decision = resolveMatchingProductRuleDecision(order, item, [rule], []) + assert.equal(decision.reason, 'unmatched') +}) + test('assertWorkerLoginAllowed blocks rejected (frozen) workers with worker_rejected', () => { assert.throws( () => assertWorkerLoginAllowed(buildWorkerUserRow({ status: 'rejected' })), diff --git a/apps/backend/src/utils/platform-order-id.test.ts b/apps/backend/src/utils/platform-order-id.test.ts new file mode 100644 index 00000000..9a1870e8 --- /dev/null +++ b/apps/backend/src/utils/platform-order-id.test.ts @@ -0,0 +1,22 @@ +import assert from 'node:assert/strict' +import test from 'node:test' + +import { isPlatformOrderIdLookupKeyword } from './platform-order-id.js' + +test('识别纯数字平台订单号', () => { + assert.equal(isPlatformOrderIdLookupKeyword('20247230034021572'), true) + assert.equal(isPlatformOrderIdLookupKeyword('123456'), true) + assert.equal(isPlatformOrderIdLookupKeyword('12345'), false) +}) + +test('识别拼多多带连字符的 order_sn', () => { + assert.equal(isPlatformOrderIdLookupKeyword('260915-091435834962130'), true) + assert.equal(isPlatformOrderIdLookupKeyword('260912-123456789012345'), true) +}) + +test('拒绝普通关键词', () => { + assert.equal(isPlatformOrderIdLookupKeyword('皮肤店'), false) + assert.equal(isPlatformOrderIdLookupKeyword('安卓怒嚎3钻'), false) + assert.equal(isPlatformOrderIdLookupKeyword('DT881aa66294cc'), false) + assert.equal(isPlatformOrderIdLookupKeyword(''), false) +}) diff --git a/apps/backend/src/utils/platform-order-id.ts b/apps/backend/src/utils/platform-order-id.ts new file mode 100644 index 00000000..2ca55447 --- /dev/null +++ b/apps/backend/src/utils/platform-order-id.ts @@ -0,0 +1,8 @@ +/** + * Detect platform order IDs that should use indexed equality/prefix lookup. + * Covers pure numeric IDs (快手等) and PDD style `digits-digits` (e.g. 260915-091435834962130). + */ +export function isPlatformOrderIdLookupKeyword(value: unknown): boolean { + const trimmed = String(value || '').trim() + return /^\d{6,}(-\d{6,})?$/.test(trimmed) +} diff --git a/apps/frontend/src/pages/admin/panels/ProductMatchPanel.tsx b/apps/frontend/src/pages/admin/panels/ProductMatchPanel.tsx index fc04de88..dc9d4320 100644 --- a/apps/frontend/src/pages/admin/panels/ProductMatchPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/ProductMatchPanel.tsx @@ -169,7 +169,7 @@ export default function ProductMatchPanel() { async function runMatchTest() { if (!testPayload.trim()) { - message.warning('请选择或粘贴快手原始载荷') + message.warning('请选择或粘贴原始订单载荷') return } try { @@ -194,7 +194,7 @@ export default function ProductMatchPanel() { const sourceColumns: TableColumnsType = [ { - title: '快手商品 / SKU', + title: '商品 / SKU', key: 'source', render: (_, row) => (
@@ -252,17 +252,30 @@ export default function ProductMatchPanel() { { title: '店铺 / 商品 / SKU', key: 'target', - render: (_, row) => ( -
- - {row.itemTitle || '-'} - - - {formatShopLabel(row.sellerId, shopNameBySellerId)} ·{' '} - {row.skuNick || row.relSkuId || '商品默认'} - -
- ), + render: (_, row) => { + const payload = (row.rawPayload || {}) as Record + const outSkuSn = String(payload.out_sku_sn || payload.outSkuSn || '').trim() + const skuLabel = outSkuSn || row.skuNick || row.relSkuId || '' + return ( +
+ + {row.itemTitle || '-'} + + + {formatShopLabel(row.sellerId, shopNameBySellerId)} ·{' '} + {skuLabel ? ( + + {outSkuSn ? `编码 ${skuLabel}` : skuLabel} + + ) : ( + + 无 out_sku_sn + + )} + +
+ ) + }, }, { title: '结果', @@ -345,7 +358,7 @@ export default function ProductMatchPanel() { showSearch optionFilterProp="label" style={{ minWidth: 360 }} - placeholder="选择已接收的快手原始载荷" + placeholder="选择已接收的原始载荷" options={sources.map((source, index) => ({ value: index, label: `${formatShopLabel(source.sellerId, shopNameBySellerId)} · ${source.itemTitle} · ${source.skuNick || '商品默认'}`, @@ -363,7 +376,7 @@ export default function ProductMatchPanel() { value={testPayload} onChange={(event) => setTestPayload(event.target.value)} rows={16} - placeholder="粘贴快手 send-code 原始载荷" + placeholder="粘贴快手 send-code 或拼多多订单原始载荷(含 out_sku_sn)" /> {testResult ? (
@@ -380,7 +393,10 @@ export default function ProductMatchPanel() { : '-'} - {testResult.context.skuNick || '-'} + {testResult.context.skuNick || testResult.context.relSkuId || '-'} + + + {testResult.context.relItemId || '-'}