feat(orders): 订单读取与筛选只认渠道/状态三列

- 展示与筛选全部改读 fulfillment_channel/status,不再回退旧列
- 支持 third_party_voucher 与 archived,legacy_platform 映射为归档
- 前端类型、筛选项与标签同步
This commit is contained in:
yml2213
2026-10-31 19:52:13 +08:00
parent 65b7f4f9dc
commit 4272515956
10 changed files with 122 additions and 31 deletions
@@ -107,8 +107,8 @@ test('订单列表按当前履约模式筛选,不再依赖任务执行器', as
fulfillmentMode: 'manual',
})
assert.match(calls[0]?.sql || '', /oi\.fulfillment_mode = \$1/)
assert.match(calls[0]?.sql || '', /oi\.fulfillment_mode <> \$1/)
assert.match(calls[0]?.sql || '', /oi\.fulfillment_channel = \$1/)
assert.match(calls[0]?.sql || '', /oi\.fulfillment_channel IS DISTINCT FROM \$1/)
assert.doesNotMatch(calls[0]?.sql || '', /COUNT\(DISTINCT/)
assert.doesNotMatch(calls[0]?.sql || '', /ft\.executor_key/)
assert.deepEqual(calls[0]?.params, ['manual'])
@@ -121,12 +121,23 @@ test('订单列表待判定筛选不扫描任务执行器', async () => {
fulfillmentMode: 'pending',
})
assert.match(calls[0]?.sql || '', /NOT EXISTS/)
assert.match(calls[0]?.sql || '', /oi\.fulfillment_mode IN/)
assert.match(calls[0]?.sql || '', /EXISTS/)
assert.match(calls[0]?.sql || '', /oi\.fulfillment_status = 'pending'/)
assert.doesNotMatch(calls[0]?.sql || '', /COUNT\(DISTINCT/)
assert.deepEqual(calls[0]?.params, [])
})
test('订单列表历史平台筛选映射为 archived', async () => {
const calls: Array<{ sql: string; params: unknown[] }> = []
await listOrdersWithExecutor(createExecutor(calls, 0), {
fulfillmentMode: 'legacy_platform',
})
assert.match(calls[0]?.sql || '', /oi\.fulfillment_status = 'archived'/)
assert.deepEqual(calls[0]?.params, [])
})
test('订单列表混合履约筛选按订单项聚合判定', async () => {
const calls: Array<{ sql: string; params: unknown[] }> = []
@@ -134,7 +145,7 @@ test('订单列表混合履约筛选按订单项聚合判定', async () => {
fulfillmentMode: 'mixed',
})
assert.match(calls[0]?.sql || '', /HAVING COUNT\(DISTINCT oi\.fulfillment_mode\) > 1/)
assert.match(calls[0]?.sql || '', /HAVING COUNT\(DISTINCT oi\.fulfillment_channel\) > 1/)
assert.deepEqual(calls[0]?.params, [])
})
+19 -11
View File
@@ -335,32 +335,40 @@ export async function listOrdersWithExecutor(
}
if (fulfillmentMode) {
const knownModes = `('work_order', 'affiliate_dash', 'legacy_platform', 'manual')`
if (fulfillmentMode === 'pending') {
filters.push(`NOT EXISTS (
// v2:渠道/状态拆列后按新列筛选;legacy_platform 统一映射成 archived。
const normalizedMode = fulfillmentMode === 'legacy_platform' ? 'archived' : fulfillmentMode
if (normalizedMode === 'pending') {
filters.push(`EXISTS (
SELECT 1 FROM order_items oi
WHERE oi.order_id = o.id AND oi.fulfillment_mode IN ${knownModes}
WHERE oi.order_id = o.id AND oi.fulfillment_status = 'pending'
)`)
} else if (fulfillmentMode === 'mixed') {
} else if (normalizedMode === 'archived') {
filters.push(`EXISTS (
SELECT 1 FROM order_items oi
WHERE oi.order_id = o.id AND oi.fulfillment_status = 'archived'
)`)
} else if (normalizedMode === 'mixed') {
filters.push(`o.id IN (
SELECT oi.order_id FROM order_items oi
WHERE oi.fulfillment_mode IN ${knownModes}
WHERE oi.fulfillment_status = 'assigned'
GROUP BY oi.order_id
HAVING COUNT(DISTINCT oi.fulfillment_mode) > 1
HAVING COUNT(DISTINCT oi.fulfillment_channel) > 1
)`)
} else {
params.push(fulfillmentMode)
params.push(normalizedMode)
const modeParam = `$${params.length}`
filters.push(`(
EXISTS (
SELECT 1 FROM order_items oi
WHERE oi.order_id = o.id AND oi.fulfillment_mode = ${modeParam}
WHERE oi.order_id = o.id
AND oi.fulfillment_status = 'assigned'
AND oi.fulfillment_channel = ${modeParam}
)
AND NOT EXISTS (
SELECT 1 FROM order_items oi
WHERE oi.order_id = o.id
AND oi.fulfillment_mode IN ${knownModes}
AND oi.fulfillment_mode <> ${modeParam}
AND oi.fulfillment_status = 'assigned'
AND oi.fulfillment_channel IS DISTINCT FROM ${modeParam}
)
)`)
}
@@ -292,10 +292,16 @@ function buildOrderForFulfillmentTest(): OrderListRow {
}
}
const CHANNEL_VALUES = new Set(['work_order', 'affiliate_dash', 'third_party_voucher', 'manual'])
function buildOrderItemForFulfillmentTest(
id: number,
fulfillmentMode: string | null,
patch: Partial<OrderItemRow> = {},
): OrderItemRow {
const channel = fulfillmentMode && CHANNEL_VALUES.has(fulfillmentMode) ? fulfillmentMode : null
const status =
fulfillmentMode === 'legacy_platform' ? 'archived' : channel ? 'assigned' : 'pending'
return {
id,
order_id: 501,
@@ -303,7 +309,9 @@ function buildOrderItemForFulfillmentTest(
sku_name: `商品-${id}`,
quantity: 1,
spec_json: { itemTitle: `商品-${id}`, skuNick: `规格-${id}` },
fulfillment_mode: fulfillmentMode,
fulfillment_channel: channel,
fulfillment_status: status,
...patch,
}
}
@@ -321,7 +329,7 @@ test('人工单即使有已核销快照也仍然判为 manual,核销码单独
assert.equal(mapped.canClaimH5, false)
})
test('自动发货与历史平台按 fulfillment_mode 区分,不再按 provider 猜', () => {
test('自动发货与历史归档按渠道/状态区分,不再按 provider 猜', () => {
const affiliate = resolveOrderFulfillmentSummary({
items: [buildOrderItemForFulfillmentTest(1, 'affiliate_dash')],
})
@@ -331,7 +339,7 @@ test('自动发货与历史平台按 fulfillment_mode 区分,不再按 provide
assert.equal(affiliate.fulfillmentMode, 'affiliate_dash')
assert.equal(affiliate.canClaimH5, true)
assert.equal(legacy.fulfillmentMode, 'legacy_platform')
assert.equal(legacy.fulfillmentMode, 'archived')
assert.equal(legacy.canClaimH5, false)
})
@@ -358,3 +366,35 @@ test('核销码状态汇总:无、单一、混合', () => {
{ hasVoucher: true, voucherState: 'mixed' },
)
})
test('v2 读取口径:归档状态优先于旧 fulfillment_mode', () => {
const summary = resolveOrderFulfillmentSummary({
items: [buildOrderItemForFulfillmentTest(1, 'work_order', { fulfillment_status: 'archived' })],
})
assert.equal(summary.fulfillmentMode, 'archived')
})
test('v2 读取口径:渠道列优先于旧 fulfillment_mode', () => {
const summary = resolveOrderFulfillmentSummary({
items: [
buildOrderItemForFulfillmentTest(1, 'manual', {
fulfillment_status: 'assigned',
fulfillment_channel: 'third_party_voucher',
}),
],
})
assert.equal(summary.fulfillmentMode, 'third_party_voucher')
})
test('v2 读取口径:状态 pending 且无渠道时判为待分类', () => {
const summary = resolveOrderFulfillmentSummary({
items: [
buildOrderItemForFulfillmentTest(1, 'manual', {
fulfillment_status: 'pending',
fulfillment_channel: null,
}),
],
})
assert.equal(summary.fulfillmentMode, 'pending')
assert.equal(summary.pendingItemCount, 1)
})
@@ -56,7 +56,7 @@ export function mapAdminOrderListItem(
skuNick: itSkuNick,
matchSku: itMatchSku,
quantity: Math.max(1, Number(orderItem.quantity || 1)),
fulfillmentMode: normalizeOrderItemFulfillmentMode(orderItem.fulfillment_mode),
fulfillmentMode: resolveOrderItemFulfillmentMode(orderItem),
}
})
resolvedItemTitle = itemsList[0]?.itemTitle || ''
@@ -141,9 +141,11 @@ export function mapAdminOrderListItem(
const ORDER_ITEM_FULFILLMENT_MODES = new Set<AdminOrderItemFulfillmentMode>([
'work_order',
'affiliate_dash',
'third_party_voucher',
'legacy_platform',
'manual',
'pending',
'archived',
])
export function normalizeOrderItemFulfillmentMode(value: unknown): AdminOrderItemFulfillmentMode {
@@ -153,6 +155,23 @@ export function normalizeOrderItemFulfillmentMode(value: unknown): AdminOrderIte
return ORDER_ITEM_FULFILLMENT_MODES.has(mode) ? mode : 'pending'
}
/**
* v2 读取口径:只认渠道/状态三列,不再回退旧 fulfillment_mode。
*
* archived → archived
* channel → 该渠道
* 其余 → pending
*/
export function resolveOrderItemFulfillmentMode(
item: Pick<OrderItemRow, 'fulfillment_status' | 'fulfillment_channel'> | null | undefined,
): AdminOrderItemFulfillmentMode {
if (!item) return 'pending'
if (String(item.fulfillment_status || '').trim() === 'archived') return 'archived'
const channel = String(item.fulfillment_channel || '').trim()
if (channel) return normalizeOrderItemFulfillmentMode(channel)
return 'pending'
}
export function resolveVoucherSummary(vouchers: KuaishouIndustryVoucherRow[] = []): {
hasVoucher: boolean
voucherState: AdminVoucherState
@@ -182,7 +201,7 @@ export function resolveVoucherSummary(vouchers: KuaishouIndustryVoucherRow[] = [
}
/**
* 订单级履约摘要:只读 order_items.fulfillment_mode,不再按 provider 或核销码推断。
* 订单级履约摘要:只读 order_items.fulfillment_channel / fulfillment_status。
* 多商品订单出现不同模式时显示 mixed;尚未判定的订单项单独计数。
*/
export function resolveOrderFulfillmentSummary({
@@ -205,9 +224,7 @@ export function resolveOrderFulfillmentSummary({
canClaimH5: boolean
} {
const normalizedItems = Array.isArray(items) ? items : []
const itemModes = normalizedItems.map((item) =>
normalizeOrderItemFulfillmentMode(item.fulfillment_mode),
)
const itemModes = normalizedItems.map((item) => resolveOrderItemFulfillmentMode(item))
const knownModes = Array.from(
new Set(
itemModes.filter((mode): mode is Exclude<typeof mode, 'pending'> => mode !== 'pending'),
@@ -232,7 +249,7 @@ export function resolveOrderFulfillmentSummary({
task.executor_key === 'kuaishou_ct_assisted' || task.executor_key === 'kuaishou_feifei',
)
) {
fulfillmentMode = 'legacy_platform'
fulfillmentMode = 'archived'
} else {
fulfillmentMode = 'pending'
}
@@ -67,6 +67,7 @@ import {
mapAdminOrderListItem,
normalizeOrderItemFulfillmentMode,
resolveOrderFulfillmentSummary,
resolveOrderItemFulfillmentMode,
summarizeOrderItems,
} from './admin-order-read-helpers.js'
import {
@@ -149,10 +150,12 @@ export async function getAdminOrders(
const ADMIN_ORDER_FULFILLMENT_MODES = new Set([
'work_order',
'affiliate_dash',
'legacy_platform',
'third_party_voucher',
'manual',
'legacy_platform',
'mixed',
'pending',
'archived',
])
function normalizeOrderFulfillmentModeFilter(value: unknown): string {
@@ -278,7 +281,7 @@ export async function getAdminOrderDetail(
revenueFen,
refundedFen,
refundableFen: Math.max(0, revenueFen - refundedFen),
fulfillmentMode: normalizeOrderItemFulfillmentMode(item.fulfillment_mode),
fulfillmentMode: resolveOrderItemFulfillmentMode(item),
deliveryMode: resolveOrderItemDeliveryMode(tasks, item.id),
spec:
item.spec_json && typeof item.spec_json === 'object'
@@ -7,6 +7,8 @@ export type AdminOrderFulfillmentProgress = {
}
export type AdminOrderItemFulfillmentMode =
| 'third_party_voucher'
| 'archived'
| 'work_order'
| 'affiliate_dash'
| 'legacy_platform'
+4 -1
View File
@@ -35,7 +35,10 @@ export type OrderItemRow = {
canonical_product_id?: number | string | null
spec_json: string | Record<string, unknown>
item_snapshot_json?: string | Record<string, unknown>
fulfillment_mode?: string | null
fulfillment_channel?: string | null
fulfillment_status?: string | null
fulfillment_source?: string | null
fulfillment_locked?: boolean | null
fulfillment_decided_at?: string | null
fulfillment_reason?: string | null
created_at?: string
+2
View File
@@ -11,9 +11,11 @@ export interface AdminOrderFulfillmentProgress {
export type AdminOrderItemFulfillmentMode =
| 'work_order'
| 'affiliate_dash'
| 'third_party_voucher'
| 'legacy_platform'
| 'manual'
| 'pending'
| 'archived'
export type AdminOrderFulfillmentMode = AdminOrderItemFulfillmentMode | 'mixed'
+6 -2
View File
@@ -222,9 +222,11 @@ export function resolveOrderFulfillmentMode(row: {
fulfillmentMode?:
| 'work_order'
| 'affiliate_dash'
| 'third_party_voucher'
| 'legacy_platform'
| 'manual'
| 'pending'
| 'archived'
| 'mixed'
}): { label: string; color: string } {
switch (row.fulfillmentMode) {
@@ -232,8 +234,10 @@ export function resolveOrderFulfillmentMode(row: {
return { label: '⚡ 自动发货', color: 'blue' }
case 'work_order':
return { label: '👥 接单平台', color: 'purple' }
case 'legacy_platform':
return { label: '🗄 历史平台', color: 'default' }
case 'third_party_voucher':
return { label: '🎟 第三方卡券', color: 'cyan' }
case 'archived':
return { label: '🗄 历史归档', color: 'default' }
case 'manual':
return { label: '🙋 人工履约', color: 'orange' }
case 'mixed':
+2 -1
View File
@@ -32,10 +32,11 @@ export const adminOrderExecutorOptions = [
export const adminOrderFulfillmentModeOptions = [
{ label: '自动发货', value: 'affiliate_dash' },
{ label: '接单平台', value: 'work_order' },
{ label: '历史平台', value: 'legacy_platform' },
{ label: '第三方卡券', value: 'third_party_voucher' },
{ label: '人工履约', value: 'manual' },
{ label: '混合履约', value: 'mixed' },
{ label: '待判定', value: 'pending' },
{ label: '历史归档', value: 'archived' },
]
export const adminTaskStatusOptions = [