feat(orders): 订单读取与筛选只认渠道/状态三列
- 展示与筛选全部改读 fulfillment_channel/status,不再回退旧列 - 支持 third_party_voucher 与 archived,legacy_platform 映射为归档 - 前端类型、筛选项与标签同步
This commit is contained in:
@@ -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, [])
|
||||
})
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
Reference in New Issue
Block a user