清理旧履约模型残留
This commit is contained in:
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"enabled": true,
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "示例快手云商品",
|
|
||||||
"enabled": false,
|
|
||||||
"priority": 100,
|
|
||||||
"provider": "91kaquan",
|
|
||||||
"platform": "kuaishou",
|
|
||||||
"shopId": "91kaquan",
|
|
||||||
"internalSkuCode": "示例快手云商品",
|
|
||||||
"internalSkuName": "示例快手云商品",
|
|
||||||
"externalSkuCode": "EXTERNAL-SKU-001",
|
|
||||||
"externalItemId": "",
|
|
||||||
"externalSkuName": "示例外部商品",
|
|
||||||
"resolvedSkuName": "示例快手云商品",
|
|
||||||
"cloudSourceKeys": ["account_example"],
|
|
||||||
"cloudSkuId": 1,
|
|
||||||
"cloudSkuName": "示例云商品",
|
|
||||||
"autoBuyEnabled": true,
|
|
||||||
"minAssetReserve": 0,
|
|
||||||
"autoReturnNumberAfterDispatch": true,
|
|
||||||
"autoConsumeAfterDispatch": false,
|
|
||||||
"kuaishouConsumeShopId": "",
|
|
||||||
"kuaishouConsumeShopName": "",
|
|
||||||
"notes": "复制为 kuaishou-cloud-fulfillment.json 后填写真实配置"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ import { listOrderItemsByOrderId } from '../../repositories/order-item-repo.js'
|
|||||||
import { listTasksByOrderId } from '../../repositories/task-repo.js'
|
import { listTasksByOrderId } from '../../repositories/task-repo.js'
|
||||||
import { formatFenToAmount, normalizeFen } from '../../utils/money.js'
|
import { formatFenToAmount, normalizeFen } from '../../utils/money.js'
|
||||||
import { resolveDisplayShopName, resolveOrderItemTitle } from './admin-read-shared-helpers.js'
|
import { resolveDisplayShopName, resolveOrderItemTitle } from './admin-read-shared-helpers.js'
|
||||||
import { buildOrderBindingSummary } from './admin-task-read-helpers.js'
|
import { buildOrderFulfillmentProgress } from './admin-task-read-helpers.js'
|
||||||
|
|
||||||
import type { AdminOrderListItem } from '../../types/admin/read-models.js'
|
import type { AdminOrderListItem } from '../../types/admin/read-models.js'
|
||||||
import type { OrderItemRow, OrderListRow } from '../../types/repository/rows.js'
|
import type { OrderItemRow, OrderListRow } from '../../types/repository/rows.js'
|
||||||
@@ -12,7 +12,7 @@ export async function mapAdminOrderListItem(item: OrderListRow): Promise<AdminOr
|
|||||||
listTasksByOrderId(item.id),
|
listTasksByOrderId(item.id),
|
||||||
listOrderItemsByOrderId(item.id),
|
listOrderItemsByOrderId(item.id),
|
||||||
])
|
])
|
||||||
const bindingSummary = buildOrderBindingSummary(tasks)
|
const fulfillmentProgress = buildOrderFulfillmentProgress(tasks)
|
||||||
const itemSummary = summarizeOrderItems(orderItems)
|
const itemSummary = summarizeOrderItems(orderItems)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -37,14 +37,10 @@ export async function mapAdminOrderListItem(item: OrderListRow): Promise<AdminOr
|
|||||||
totalQuantity: orderItems.reduce((sum, orderItem) => sum + Math.max(1, Number(orderItem.quantity || 1)), 0),
|
totalQuantity: orderItems.reduce((sum, orderItem) => sum + Math.max(1, Number(orderItem.quantity || 1)), 0),
|
||||||
itemSummary,
|
itemSummary,
|
||||||
taskCount: Number(item.task_count || tasks.length || 0),
|
taskCount: Number(item.task_count || tasks.length || 0),
|
||||||
systemBindingStatus: bindingSummary.systemBindingStatus,
|
resourceStatus: fulfillmentProgress.resourceStatus,
|
||||||
userBindingStatus: bindingSummary.userBindingStatus,
|
customerStatus: fulfillmentProgress.customerStatus,
|
||||||
systemBoundTaskCount: bindingSummary.systemBoundTaskCount,
|
preparedTaskCount: fulfillmentProgress.preparedTaskCount,
|
||||||
completedBindingTaskCount: bindingSummary.completedBindingTaskCount,
|
completedTaskCount: fulfillmentProgress.completedTaskCount,
|
||||||
totalBindingCount: bindingSummary.totalBindingCount,
|
|
||||||
reservedBindingCount: bindingSummary.reservedBindingCount,
|
|
||||||
consumedBindingCount: bindingSummary.consumedBindingCount,
|
|
||||||
releasedBindingCount: bindingSummary.releasedBindingCount,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import {
|
|||||||
} from './admin-read-shared-helpers.js'
|
} from './admin-read-shared-helpers.js'
|
||||||
import { mapAdminOrderListItem, summarizeOrderItems } from './admin-order-read-helpers.js'
|
import { mapAdminOrderListItem, summarizeOrderItems } from './admin-order-read-helpers.js'
|
||||||
import {
|
import {
|
||||||
buildOrderBindingSummary,
|
buildOrderFulfillmentProgress,
|
||||||
getRequiredTask,
|
getRequiredTask,
|
||||||
mapAdminTaskEvent,
|
mapAdminTaskEvent,
|
||||||
mapAdminTaskListItem,
|
mapAdminTaskListItem,
|
||||||
@@ -104,7 +104,7 @@ export async function getAdminOrderDetail(orderId: AdminEntityIdInput): Promise<
|
|||||||
rawPayload: order.raw_payload_json && typeof order.raw_payload_json === 'object'
|
rawPayload: order.raw_payload_json && typeof order.raw_payload_json === 'object'
|
||||||
? order.raw_payload_json
|
? order.raw_payload_json
|
||||||
: JSON.parse(String(order.raw_payload_json || '{}')),
|
: JSON.parse(String(order.raw_payload_json || '{}')),
|
||||||
bindingSummary: buildOrderBindingSummary(tasks),
|
fulfillmentProgress: buildOrderFulfillmentProgress(tasks),
|
||||||
},
|
},
|
||||||
items: items.map((item) => ({
|
items: items.map((item) => ({
|
||||||
orderItemId: item.id,
|
orderItemId: item.id,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
} from './admin-read-shared-helpers.js'
|
} from './admin-read-shared-helpers.js'
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
AdminTaskBindingSummary,
|
AdminOrderFulfillmentProgress,
|
||||||
AdminTaskListItem,
|
AdminTaskListItem,
|
||||||
} from '../../types/admin/read-models.js'
|
} from '../../types/admin/read-models.js'
|
||||||
import type { AdminTaskActionPayload } from '../../types/admin/write-models.js'
|
import type { AdminTaskActionPayload } from '../../types/admin/write-models.js'
|
||||||
@@ -19,36 +19,23 @@ import type { AdminViewerContext } from './admin-read-shared-helpers.js'
|
|||||||
|
|
||||||
type JsonRecord = Record<string, any>
|
type JsonRecord = Record<string, any>
|
||||||
|
|
||||||
type TaskBindingState = {
|
type TaskFulfillmentState = {
|
||||||
systemBindingStatus: string
|
resourceStatus: string
|
||||||
userBindingStatus: string
|
customerStatus: string
|
||||||
}
|
|
||||||
|
|
||||||
type OrderBindingSummary = {
|
|
||||||
totalTaskCount: number
|
|
||||||
systemBoundTaskCount: number
|
|
||||||
completedBindingTaskCount: number
|
|
||||||
totalBindingCount: number
|
|
||||||
reservedBindingCount: number
|
|
||||||
consumedBindingCount: number
|
|
||||||
releasedBindingCount: number
|
|
||||||
systemBindingStatus: string
|
|
||||||
userBindingStatus: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapAdminTaskSummary(
|
export function mapAdminTaskSummary(
|
||||||
task: TaskRow,
|
task: TaskRow,
|
||||||
bindingSummary: AdminTaskBindingSummary = createEmptyTaskBindingSummary(),
|
|
||||||
): JsonRecord {
|
): JsonRecord {
|
||||||
const binding = buildTaskBindingState(task)
|
const fulfillment = buildTaskFulfillmentState(task)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
taskNo: task.task_no,
|
taskNo: task.task_no,
|
||||||
deliveryStatus: task.delivery_status || '',
|
deliveryStatus: task.delivery_status || '',
|
||||||
status: task.task_status,
|
status: task.task_status,
|
||||||
systemBindingStatus: binding.systemBindingStatus,
|
resourceStatus: fulfillment.resourceStatus,
|
||||||
userBindingStatus: binding.userBindingStatus,
|
customerStatus: fulfillment.customerStatus,
|
||||||
loginType: task.login_type,
|
loginType: task.login_type,
|
||||||
runtimeSessionId: task.runtime_session_id,
|
runtimeSessionId: task.runtime_session_id,
|
||||||
claimedAt: task.claimed_at,
|
claimedAt: task.claimed_at,
|
||||||
@@ -56,7 +43,6 @@ export function mapAdminTaskSummary(
|
|||||||
redeemedAt: task.redeemed_at,
|
redeemedAt: task.redeemed_at,
|
||||||
lastError: task.last_error,
|
lastError: task.last_error,
|
||||||
retryCount: getTaskRetryCount(task),
|
retryCount: getTaskRetryCount(task),
|
||||||
bindingSummary,
|
|
||||||
createdAt: task.created_at,
|
createdAt: task.created_at,
|
||||||
updatedAt: task.updated_at,
|
updatedAt: task.updated_at,
|
||||||
}
|
}
|
||||||
@@ -95,8 +81,7 @@ export function mapAdminTaskListItem(
|
|||||||
task: TaskRow,
|
task: TaskRow,
|
||||||
viewerContext: AdminViewerContext = createAdminViewerContext(),
|
viewerContext: AdminViewerContext = createAdminViewerContext(),
|
||||||
): AdminTaskListItem {
|
): AdminTaskListItem {
|
||||||
const binding = buildTaskBindingState(task)
|
const fulfillment = buildTaskFulfillmentState(task)
|
||||||
const bindingSummary = createEmptyTaskBindingSummary()
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
@@ -109,8 +94,8 @@ export function mapAdminTaskListItem(
|
|||||||
deliveryStatus: task.delivery_status || '',
|
deliveryStatus: task.delivery_status || '',
|
||||||
resultCode: task.result_code || '',
|
resultCode: task.result_code || '',
|
||||||
resultMessage: task.result_message || '',
|
resultMessage: task.result_message || '',
|
||||||
systemBindingStatus: binding.systemBindingStatus,
|
resourceStatus: fulfillment.resourceStatus,
|
||||||
userBindingStatus: binding.userBindingStatus,
|
customerStatus: fulfillment.customerStatus,
|
||||||
loginType: task.login_type,
|
loginType: task.login_type,
|
||||||
roleName: task.role_name,
|
roleName: task.role_name,
|
||||||
roleId: task.role_id,
|
roleId: task.role_id,
|
||||||
@@ -119,7 +104,6 @@ export function mapAdminTaskListItem(
|
|||||||
roleConfirmedAt: task.role_confirmed_at,
|
roleConfirmedAt: task.role_confirmed_at,
|
||||||
redeemedAt: task.redeemed_at,
|
redeemedAt: task.redeemed_at,
|
||||||
retryCount: getTaskRetryCount(task),
|
retryCount: getTaskRetryCount(task),
|
||||||
bindingSummary,
|
|
||||||
lastError: task.last_error,
|
lastError: task.last_error,
|
||||||
createdAt: task.created_at,
|
createdAt: task.created_at,
|
||||||
updatedAt: task.updated_at,
|
updatedAt: task.updated_at,
|
||||||
@@ -139,158 +123,140 @@ export function mapAdminTaskEvent(event: TaskEventRow): JsonRecord {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createEmptyTaskBindingSummary(): AdminTaskBindingSummary {
|
export function buildOrderFulfillmentProgress(
|
||||||
return {
|
|
||||||
totalBindingCount: 0,
|
|
||||||
reservedBindingCount: 0,
|
|
||||||
consumedBindingCount: 0,
|
|
||||||
releasedBindingCount: 0,
|
|
||||||
roleKeys: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildOrderBindingSummary(
|
|
||||||
tasks: TaskRow[] | null | undefined,
|
tasks: TaskRow[] | null | undefined,
|
||||||
): OrderBindingSummary {
|
): AdminOrderFulfillmentProgress {
|
||||||
const normalizedTasks = Array.isArray(tasks) ? tasks : []
|
const normalizedTasks = Array.isArray(tasks) ? tasks : []
|
||||||
const totalTaskCount = normalizedTasks.length
|
const totalTaskCount = normalizedTasks.length
|
||||||
const taskBindings = normalizedTasks.map((task) => buildTaskBindingState(task))
|
const taskFulfillments = normalizedTasks.map((task) => buildTaskFulfillmentState(task))
|
||||||
const systemBoundTaskCount = normalizedTasks.filter((task) => isTaskSystemBound(task)).length
|
const preparedTaskCount = normalizedTasks.filter((task) => isTaskResourcePrepared(task)).length
|
||||||
const completedBindingTaskCount = normalizedTasks.filter((task) => String(task?.task_status || '') === 'redeemed').length
|
const completedTaskCount = normalizedTasks.filter((task) => isTaskFulfillmentCompleted(task)).length
|
||||||
const totalBindingCount = 0
|
|
||||||
const reservedBindingCount = 0
|
|
||||||
const consumedBindingCount = 0
|
|
||||||
const releasedBindingCount = 0
|
|
||||||
|
|
||||||
let systemBindingStatus = 'pending_binding'
|
let resourceStatus = 'pending_prepare'
|
||||||
let userBindingStatus = 'not_started'
|
let customerStatus = 'not_started'
|
||||||
|
|
||||||
if (totalTaskCount === 0) {
|
if (totalTaskCount === 0) {
|
||||||
return {
|
return {
|
||||||
totalTaskCount,
|
totalTaskCount,
|
||||||
systemBoundTaskCount,
|
preparedTaskCount,
|
||||||
completedBindingTaskCount,
|
completedTaskCount,
|
||||||
totalBindingCount,
|
resourceStatus,
|
||||||
reservedBindingCount,
|
customerStatus,
|
||||||
consumedBindingCount,
|
|
||||||
releasedBindingCount,
|
|
||||||
systemBindingStatus,
|
|
||||||
userBindingStatus,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedTasks.every((task) => String(task.task_status || '') === 'redeemed')) {
|
if (normalizedTasks.every((task) => isTaskFulfillmentCompleted(task))) {
|
||||||
systemBindingStatus = 'system_bound'
|
resourceStatus = 'resource_ready'
|
||||||
userBindingStatus = 'binding_completed'
|
customerStatus = 'customer_completed'
|
||||||
} else if (taskBindings.some((item) => ['binding_in_progress', 'binding_confirmed', 'link_opened'].includes(item.userBindingStatus))) {
|
} else if (taskFulfillments.some((item) => ['customer_processing', 'customer_confirmed', 'link_opened'].includes(item.customerStatus))) {
|
||||||
systemBindingStatus = systemBoundTaskCount > 0 ? 'system_bound' : 'pending_binding'
|
resourceStatus = preparedTaskCount > 0 ? 'resource_ready' : 'pending_prepare'
|
||||||
userBindingStatus = 'user_binding'
|
customerStatus = 'customer_processing'
|
||||||
} else if (taskBindings.some((item) => item.userBindingStatus === 'waiting_user_claim')) {
|
} else if (taskFulfillments.some((item) => item.customerStatus === 'waiting_customer')) {
|
||||||
systemBindingStatus = systemBoundTaskCount > 0 ? 'system_bound' : 'pending_binding'
|
resourceStatus = preparedTaskCount > 0 ? 'resource_ready' : 'pending_prepare'
|
||||||
userBindingStatus = 'waiting_user_claim'
|
customerStatus = 'waiting_customer'
|
||||||
} else if (taskBindings.some((item) => item.userBindingStatus === 'binding_exception')) {
|
} else if (taskFulfillments.some((item) => item.customerStatus === 'customer_exception')) {
|
||||||
systemBindingStatus = systemBoundTaskCount > 0 ? 'system_bound' : 'pending_binding'
|
resourceStatus = preparedTaskCount > 0 ? 'resource_ready' : 'pending_prepare'
|
||||||
userBindingStatus = 'binding_exception'
|
customerStatus = 'customer_exception'
|
||||||
} else if (systemBoundTaskCount > 0) {
|
} else if (preparedTaskCount > 0) {
|
||||||
systemBindingStatus = 'system_bound'
|
resourceStatus = 'resource_ready'
|
||||||
} else if (taskBindings.some((item) => ['manual_review', 'retry_pending'].includes(item.systemBindingStatus))) {
|
} else if (taskFulfillments.some((item) => ['manual_review', 'retry_pending'].includes(item.resourceStatus))) {
|
||||||
systemBindingStatus = 'binding_exception'
|
resourceStatus = 'resource_exception'
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
totalTaskCount,
|
totalTaskCount,
|
||||||
systemBoundTaskCount,
|
preparedTaskCount,
|
||||||
completedBindingTaskCount,
|
completedTaskCount,
|
||||||
totalBindingCount,
|
resourceStatus,
|
||||||
reservedBindingCount,
|
customerStatus,
|
||||||
consumedBindingCount,
|
|
||||||
releasedBindingCount,
|
|
||||||
systemBindingStatus,
|
|
||||||
userBindingStatus,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTaskBindingState(task: TaskRow | null | undefined): TaskBindingState {
|
function buildTaskFulfillmentState(task: TaskRow | null | undefined): TaskFulfillmentState {
|
||||||
const normalizedStatus = String(task?.task_status || '').trim()
|
const normalizedStatus = String(task?.task_status || '').trim()
|
||||||
|
|
||||||
if (!normalizedStatus) {
|
if (!normalizedStatus) {
|
||||||
return {
|
return {
|
||||||
systemBindingStatus: 'pending_binding',
|
resourceStatus: 'pending_prepare',
|
||||||
userBindingStatus: 'not_started',
|
customerStatus: 'not_started',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedStatus === 'pending_payment') {
|
if (normalizedStatus === 'pending_payment') {
|
||||||
return { systemBindingStatus: 'pending_payment', userBindingStatus: 'not_started' }
|
return { resourceStatus: 'pending_payment', customerStatus: 'not_started' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedStatus === 'paid') {
|
if (normalizedStatus === 'paid') {
|
||||||
return { systemBindingStatus: 'pending_binding', userBindingStatus: 'not_started' }
|
return { resourceStatus: 'pending_prepare', customerStatus: 'not_started' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedStatus === 'pending_binding_prepare') {
|
if (normalizedStatus === 'pending_binding_prepare') {
|
||||||
return { systemBindingStatus: 'pending_binding', userBindingStatus: 'not_started' }
|
return { resourceStatus: 'pending_prepare', customerStatus: 'not_started' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedStatus === 'waiting_binding') {
|
if (normalizedStatus === 'waiting_binding') {
|
||||||
return { systemBindingStatus: 'system_bound', userBindingStatus: 'waiting_user_claim' }
|
return { resourceStatus: 'resource_ready', customerStatus: 'waiting_customer' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedStatus === 'dispatched_pending_return') {
|
if (normalizedStatus === 'dispatched_pending_return') {
|
||||||
return { systemBindingStatus: 'system_bound', userBindingStatus: 'binding_completed' }
|
return { resourceStatus: 'resource_ready', customerStatus: 'customer_completed' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedStatus === 'completed') {
|
if (normalizedStatus === 'completed') {
|
||||||
return { systemBindingStatus: 'system_bound', userBindingStatus: 'binding_completed' }
|
return { resourceStatus: 'resource_ready', customerStatus: 'customer_completed' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedStatus === 'manual_review') {
|
if (normalizedStatus === 'manual_review') {
|
||||||
return { systemBindingStatus: 'manual_review', userBindingStatus: 'binding_exception' }
|
return { resourceStatus: 'manual_review', customerStatus: 'customer_exception' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedStatus === 'retry_pending') {
|
if (normalizedStatus === 'retry_pending') {
|
||||||
return { systemBindingStatus: 'retry_pending', userBindingStatus: 'binding_exception' }
|
return { resourceStatus: 'retry_pending', customerStatus: 'customer_exception' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedStatus === 'closed') {
|
if (normalizedStatus === 'closed') {
|
||||||
return { systemBindingStatus: 'closed', userBindingStatus: 'closed' }
|
return { resourceStatus: 'closed', customerStatus: 'closed' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedStatus === 'expired') {
|
if (normalizedStatus === 'expired') {
|
||||||
return { systemBindingStatus: 'expired', userBindingStatus: 'expired' }
|
return { resourceStatus: 'expired', customerStatus: 'expired' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedStatus === 'link_generated') {
|
if (normalizedStatus === 'link_generated') {
|
||||||
return { systemBindingStatus: 'system_bound', userBindingStatus: 'waiting_user_claim' }
|
return { resourceStatus: 'resource_ready', customerStatus: 'waiting_customer' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedStatus === 'claimed') {
|
if (normalizedStatus === 'claimed') {
|
||||||
return { systemBindingStatus: 'system_bound', userBindingStatus: 'link_opened' }
|
return { resourceStatus: 'resource_ready', customerStatus: 'link_opened' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedStatus === 'role_confirmed') {
|
if (normalizedStatus === 'role_confirmed') {
|
||||||
return { systemBindingStatus: 'system_bound', userBindingStatus: 'binding_confirmed' }
|
return { resourceStatus: 'resource_ready', customerStatus: 'customer_confirmed' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedStatus === 'redeeming') {
|
if (normalizedStatus === 'redeeming') {
|
||||||
return { systemBindingStatus: 'system_bound', userBindingStatus: 'binding_in_progress' }
|
return { resourceStatus: 'resource_ready', customerStatus: 'customer_processing' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedStatus === 'redeemed') {
|
if (normalizedStatus === 'redeemed') {
|
||||||
return { systemBindingStatus: 'system_bound', userBindingStatus: 'binding_completed' }
|
return { resourceStatus: 'resource_ready', customerStatus: 'customer_completed' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTaskSystemBound(task)) {
|
if (isTaskResourcePrepared(task)) {
|
||||||
return { systemBindingStatus: 'system_bound', userBindingStatus: 'waiting_user_claim' }
|
return { resourceStatus: 'resource_ready', customerStatus: 'waiting_customer' }
|
||||||
}
|
}
|
||||||
|
|
||||||
return { systemBindingStatus: 'pending_binding', userBindingStatus: 'not_started' }
|
return { resourceStatus: 'pending_prepare', customerStatus: 'not_started' }
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTaskSystemBound(task: TaskRow | null | undefined): boolean {
|
function isTaskResourcePrepared(task: TaskRow | null | undefined): boolean {
|
||||||
return Boolean(task && getTaskPrimaryClaimTokenId(task))
|
return Boolean(task && getTaskPrimaryClaimTokenId(task))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTaskFulfillmentCompleted(task: TaskRow | null | undefined): boolean {
|
||||||
|
return ['redeemed', 'completed', 'dispatched_pending_return'].includes(String(task?.task_status || '').trim())
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeRecord(value: unknown): JsonRecord {
|
function normalizeRecord(value: unknown): JsonRecord {
|
||||||
return value && typeof value === 'object' && !Array.isArray(value) ? value : {}
|
return value && typeof value === 'object' && !Array.isArray(value) ? value : {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
import test from 'node:test'
|
|
||||||
import assert from 'node:assert/strict'
|
|
||||||
|
|
||||||
import {
|
|
||||||
findMatchingObservedBinding,
|
|
||||||
mapAdminObservedProductItem,
|
|
||||||
matchesObservedProduct,
|
|
||||||
} from './domain.js'
|
|
||||||
|
|
||||||
test('matchesObservedProduct honors provider platform shop and external fields', () => {
|
|
||||||
const binding = {
|
|
||||||
provider: '91kaquan',
|
|
||||||
platform: 'kuaishou',
|
|
||||||
shopId: 'shop-1',
|
|
||||||
match: {
|
|
||||||
externalSkuCode: 'sku-ext-1',
|
|
||||||
externalItemId: '',
|
|
||||||
externalSkuName: '',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
matchesObservedProduct(binding, {
|
|
||||||
provider: '91kaquan',
|
|
||||||
platform: 'kuaishou',
|
|
||||||
shopId: 'shop-1',
|
|
||||||
externalSkuCode: 'sku-ext-1',
|
|
||||||
}),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
matchesObservedProduct(binding, {
|
|
||||||
provider: '91kaquan',
|
|
||||||
platform: 'kuaishou',
|
|
||||||
shopId: 'shop-2',
|
|
||||||
externalSkuCode: 'sku-ext-1',
|
|
||||||
}),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('mapAdminObservedProductItem attaches matched binding summary', () => {
|
|
||||||
const bindings = [
|
|
||||||
{
|
|
||||||
provider: '91kaquan',
|
|
||||||
platform: 'kuaishou',
|
|
||||||
shopId: 'shop-1',
|
|
||||||
skuCode: 'sku-1',
|
|
||||||
skuName: 'SKU 1',
|
|
||||||
profileKey: 'profile-1',
|
|
||||||
match: {
|
|
||||||
externalSkuCode: 'ext-1',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
const observed = {
|
|
||||||
provider: '91kaquan',
|
|
||||||
platform: 'kuaishou',
|
|
||||||
shopId: 'shop-1',
|
|
||||||
shopName: '店铺1',
|
|
||||||
externalItemId: '',
|
|
||||||
externalSkuCode: 'ext-1',
|
|
||||||
externalSkuName: '礼包1',
|
|
||||||
latestSeenAt: '2026-05-04T10:00:00.000Z',
|
|
||||||
orderItemCount: 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.deepEqual(findMatchingObservedBinding(bindings, observed), bindings[0])
|
|
||||||
assert.deepEqual(mapAdminObservedProductItem(observed, bindings), {
|
|
||||||
provider: '91kaquan',
|
|
||||||
platform: 'kuaishou',
|
|
||||||
shopId: 'shop-1',
|
|
||||||
shopName: '店铺1',
|
|
||||||
externalItemId: '',
|
|
||||||
externalSkuCode: 'ext-1',
|
|
||||||
externalSkuName: '礼包1',
|
|
||||||
latestSeenAt: '2026-05-04T10:00:00.000Z',
|
|
||||||
orderItemCount: 2,
|
|
||||||
configured: true,
|
|
||||||
matchedBinding: {
|
|
||||||
skuCode: 'sku-1',
|
|
||||||
skuName: 'SKU 1',
|
|
||||||
profileKey: 'profile-1',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
type JsonObject = Record<string, any>
|
|
||||||
|
|
||||||
export function matchesObservedProduct(binding: JsonObject, observed: JsonObject) {
|
|
||||||
const provider = String(binding?.provider || '').trim()
|
|
||||||
const platform = String(binding?.platform || '').trim()
|
|
||||||
const shopId = String(binding?.shopId || '').trim()
|
|
||||||
const match = binding?.match || {}
|
|
||||||
const externalSkuCode = String(match.externalSkuCode || '').trim()
|
|
||||||
const externalItemId = String(match.externalItemId || '').trim()
|
|
||||||
const externalSkuName = String(match.externalSkuName || '').trim()
|
|
||||||
|
|
||||||
if (provider && provider !== String(observed?.provider || '').trim()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (platform && platform !== String(observed?.platform || '').trim()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shopId && shopId !== String(observed?.shopId || '').trim()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
(externalSkuCode && externalSkuCode === String(observed?.externalSkuCode || '').trim())
|
|
||||||
|| (externalItemId && externalItemId === String(observed?.externalItemId || '').trim())
|
|
||||||
|| (externalSkuName && externalSkuName === String(observed?.externalSkuName || '').trim())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findMatchingObservedBinding(bindings: JsonObject[], observed: JsonObject) {
|
|
||||||
return (Array.isArray(bindings) ? bindings : []).find((binding) => matchesObservedProduct(binding, observed)) || null
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mapAdminObservedProductItem(item: JsonObject, bindings: JsonObject[] = []) {
|
|
||||||
const matchedBinding = findMatchingObservedBinding(bindings, item)
|
|
||||||
|
|
||||||
return {
|
|
||||||
provider: String(item?.provider || '').trim(),
|
|
||||||
platform: String(item?.platform || '').trim(),
|
|
||||||
shopId: String(item?.shopId || '').trim(),
|
|
||||||
shopName: String(item?.shopName || '').trim(),
|
|
||||||
externalItemId: String(item?.externalItemId || '').trim(),
|
|
||||||
externalSkuCode: String(item?.externalSkuCode || '').trim(),
|
|
||||||
externalSkuName: String(item?.externalSkuName || '').trim(),
|
|
||||||
latestSeenAt: item?.latestSeenAt || null,
|
|
||||||
orderItemCount: Number(item?.orderItemCount || 0),
|
|
||||||
configured: Boolean(matchedBinding),
|
|
||||||
matchedBinding: matchedBinding
|
|
||||||
? {
|
|
||||||
skuCode: String(matchedBinding.skuCode || '').trim(),
|
|
||||||
skuName: String(matchedBinding.skuName || '').trim(),
|
|
||||||
profileKey: String(matchedBinding.profileKey || '').trim(),
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-97
@@ -1,97 +0,0 @@
|
|||||||
import test from 'node:test'
|
|
||||||
import assert from 'node:assert/strict'
|
|
||||||
|
|
||||||
import {
|
|
||||||
ADMIN_OBSERVED_PRODUCTS_QUERY,
|
|
||||||
listAdminObservedProducts,
|
|
||||||
mapAdminObservedProductRow,
|
|
||||||
} from './observed-products.js'
|
|
||||||
|
|
||||||
test('mapAdminObservedProductRow normalizes query row fields', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
mapAdminObservedProductRow({
|
|
||||||
provider: ' 91kaquan ',
|
|
||||||
platform: ' kuaishou ',
|
|
||||||
shop_id: ' shop-1 ',
|
|
||||||
shop_name: ' 店铺A ',
|
|
||||||
external_item_id: ' item-1 ',
|
|
||||||
external_sku_code: ' sku-1 ',
|
|
||||||
external_sku_name: ' 商品A ',
|
|
||||||
latest_seen_at: '2026-05-04T12:00:00.000Z',
|
|
||||||
order_item_count: '3',
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
provider: '91kaquan',
|
|
||||||
platform: 'kuaishou',
|
|
||||||
shopId: 'shop-1',
|
|
||||||
shopName: '店铺A',
|
|
||||||
externalItemId: 'item-1',
|
|
||||||
externalSkuCode: 'sku-1',
|
|
||||||
externalSkuName: '商品A',
|
|
||||||
latestSeenAt: '2026-05-04T12:00:00.000Z',
|
|
||||||
orderItemCount: 3,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('listAdminObservedProducts queries rows and maps matched binding summary', async () => {
|
|
||||||
/** @type {string[]} */
|
|
||||||
const sqlCalls = []
|
|
||||||
const result = await listAdminObservedProducts(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
provider: '91kaquan',
|
|
||||||
platform: 'kuaishou',
|
|
||||||
shopId: 'shop-1',
|
|
||||||
skuCode: 'inner-1',
|
|
||||||
skuName: '内部商品A',
|
|
||||||
profileKey: 'manual_review',
|
|
||||||
match: {
|
|
||||||
externalSkuCode: 'sku-1',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
{
|
|
||||||
async query(sql) {
|
|
||||||
sqlCalls.push(sql)
|
|
||||||
return {
|
|
||||||
rows: [
|
|
||||||
{
|
|
||||||
provider: '91kaquan',
|
|
||||||
platform: 'kuaishou',
|
|
||||||
shop_id: 'shop-1',
|
|
||||||
shop_name: '店铺A',
|
|
||||||
external_item_id: '',
|
|
||||||
external_sku_code: 'sku-1',
|
|
||||||
external_sku_name: '商品A',
|
|
||||||
latest_seen_at: '2026-05-04T12:00:00.000Z',
|
|
||||||
order_item_count: 2,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert.equal(sqlCalls.length, 1)
|
|
||||||
assert.equal(sqlCalls[0], ADMIN_OBSERVED_PRODUCTS_QUERY)
|
|
||||||
assert.deepEqual(result, [
|
|
||||||
{
|
|
||||||
provider: '91kaquan',
|
|
||||||
platform: 'kuaishou',
|
|
||||||
shopId: 'shop-1',
|
|
||||||
shopName: '店铺A',
|
|
||||||
externalItemId: '',
|
|
||||||
externalSkuCode: 'sku-1',
|
|
||||||
externalSkuName: '商品A',
|
|
||||||
latestSeenAt: '2026-05-04T12:00:00.000Z',
|
|
||||||
orderItemCount: 2,
|
|
||||||
configured: true,
|
|
||||||
matchedBinding: {
|
|
||||||
skuCode: 'inner-1',
|
|
||||||
skuName: '内部商品A',
|
|
||||||
profileKey: 'manual_review',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
})
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import { query } from '../../../../db/client.js'
|
|
||||||
import { mapAdminObservedProductItem } from './domain.js'
|
|
||||||
|
|
||||||
type JsonObject = Record<string, any>
|
|
||||||
|
|
||||||
export const ADMIN_OBSERVED_PRODUCTS_QUERY = `
|
|
||||||
SELECT
|
|
||||||
o.provider,
|
|
||||||
o.platform,
|
|
||||||
o.shop_id,
|
|
||||||
MAX(CASE WHEN trim(o.shop_name) != '' THEN o.shop_name ELSE '' END) AS shop_name,
|
|
||||||
COALESCE(NULLIF(oi.item_snapshot_json ->> 'externalItemId', ''), '') AS external_item_id,
|
|
||||||
COALESCE(NULLIF(oi.item_snapshot_json ->> 'externalSkuCode', ''), '') AS external_sku_code,
|
|
||||||
COALESCE(NULLIF(oi.item_snapshot_json ->> 'externalSkuName', ''), '') AS external_sku_name,
|
|
||||||
MAX(oi.created_at) AS latest_seen_at,
|
|
||||||
COUNT(*)::int AS order_item_count
|
|
||||||
FROM order_items oi
|
|
||||||
JOIN orders o ON o.id = oi.order_id
|
|
||||||
WHERE
|
|
||||||
COALESCE(NULLIF(oi.item_snapshot_json ->> 'externalItemId', ''), '') != ''
|
|
||||||
OR COALESCE(NULLIF(oi.item_snapshot_json ->> 'externalSkuCode', ''), '') != ''
|
|
||||||
OR COALESCE(NULLIF(oi.item_snapshot_json ->> 'externalSkuName', ''), '') != ''
|
|
||||||
GROUP BY
|
|
||||||
o.provider,
|
|
||||||
o.platform,
|
|
||||||
o.shop_id,
|
|
||||||
external_item_id,
|
|
||||||
external_sku_code,
|
|
||||||
external_sku_name
|
|
||||||
ORDER BY latest_seen_at DESC, o.platform ASC, o.shop_id ASC, external_sku_code ASC
|
|
||||||
LIMIT 200
|
|
||||||
`
|
|
||||||
|
|
||||||
export function mapAdminObservedProductRow(row: JsonObject = {}) {
|
|
||||||
return {
|
|
||||||
provider: String(row.provider || '').trim(),
|
|
||||||
platform: String(row.platform || '').trim(),
|
|
||||||
shopId: String(row.shop_id || '').trim(),
|
|
||||||
shopName: String(row.shop_name || '').trim(),
|
|
||||||
externalItemId: String(row.external_item_id || '').trim(),
|
|
||||||
externalSkuCode: String(row.external_sku_code || '').trim(),
|
|
||||||
externalSkuName: String(row.external_sku_name || '').trim(),
|
|
||||||
latestSeenAt: row.latest_seen_at || null,
|
|
||||||
orderItemCount: Number(row.order_item_count || 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function listAdminObservedProducts(bindings: JsonObject[] = [], options: JsonObject = {}) {
|
|
||||||
const queryImpl = (options.query || query) as (sql: string) => Promise<{ rows: JsonObject[] }>
|
|
||||||
const rowsResult = await queryImpl(ADMIN_OBSERVED_PRODUCTS_QUERY)
|
|
||||||
return rowsResult.rows.map((row) => mapAdminObservedProductItem(mapAdminObservedProductRow(row), bindings))
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
export type AdminTaskBindingSummary = {
|
export type AdminOrderFulfillmentProgress = {
|
||||||
totalBindingCount: number
|
totalTaskCount: number
|
||||||
reservedBindingCount: number
|
preparedTaskCount: number
|
||||||
consumedBindingCount: number
|
completedTaskCount: number
|
||||||
releasedBindingCount: number
|
resourceStatus: string
|
||||||
roleKeys: string[]
|
customerStatus: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AdminOrderListItem = {
|
export type AdminOrderListItem = {
|
||||||
@@ -28,14 +28,10 @@ export type AdminOrderListItem = {
|
|||||||
totalQuantity: number
|
totalQuantity: number
|
||||||
itemSummary: string
|
itemSummary: string
|
||||||
taskCount: number
|
taskCount: number
|
||||||
systemBindingStatus: string
|
resourceStatus: string
|
||||||
userBindingStatus: string
|
customerStatus: string
|
||||||
systemBoundTaskCount: number
|
preparedTaskCount: number
|
||||||
completedBindingTaskCount: number
|
completedTaskCount: number
|
||||||
totalBindingCount: number
|
|
||||||
reservedBindingCount: number
|
|
||||||
consumedBindingCount: number
|
|
||||||
releasedBindingCount: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AdminTaskListItem = {
|
export type AdminTaskListItem = {
|
||||||
@@ -49,8 +45,8 @@ export type AdminTaskListItem = {
|
|||||||
deliveryStatus: string
|
deliveryStatus: string
|
||||||
resultCode: string
|
resultCode: string
|
||||||
resultMessage: string
|
resultMessage: string
|
||||||
systemBindingStatus: string
|
resourceStatus: string
|
||||||
userBindingStatus: string
|
customerStatus: string
|
||||||
loginType: string
|
loginType: string
|
||||||
roleName: string
|
roleName: string
|
||||||
roleId: string
|
roleId: string
|
||||||
@@ -59,7 +55,6 @@ export type AdminTaskListItem = {
|
|||||||
roleConfirmedAt: string | null
|
roleConfirmedAt: string | null
|
||||||
redeemedAt: string | null
|
redeemedAt: string | null
|
||||||
retryCount: number
|
retryCount: number
|
||||||
bindingSummary: AdminTaskBindingSummary
|
|
||||||
lastError: string
|
lastError: string
|
||||||
createdAt: string
|
createdAt: string
|
||||||
updatedAt: string
|
updatedAt: string
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ function resolveLabel(status: string) {
|
|||||||
refunding: '退款中',
|
refunding: '退款中',
|
||||||
closed: '已关闭',
|
closed: '已关闭',
|
||||||
pending_payment: '待支付',
|
pending_payment: '待支付',
|
||||||
cdk_reserved: '已预占',
|
cdk_reserved: '资源已准备',
|
||||||
link_generated: '已生成链接',
|
link_generated: '已生成链接',
|
||||||
claimed: '已创建会话',
|
claimed: '已创建会话',
|
||||||
role_confirmed: '已确认角色',
|
role_confirmed: '已确认角色',
|
||||||
@@ -108,9 +108,9 @@ function resolveLabel(status: string) {
|
|||||||
manual_review: '人工处理',
|
manual_review: '人工处理',
|
||||||
expired: '已过期',
|
expired: '已过期',
|
||||||
available: '可用',
|
available: '可用',
|
||||||
reserved: '已预占',
|
reserved: '资源已准备',
|
||||||
delivered: '已发放',
|
delivered: '已发放',
|
||||||
consumed: '已消耗',
|
consumed: '已使用',
|
||||||
invalid: '已作废',
|
invalid: '已作废',
|
||||||
active: '生效中',
|
active: '生效中',
|
||||||
admin: '管理员',
|
admin: '管理员',
|
||||||
@@ -123,21 +123,29 @@ function resolveLabel(status: string) {
|
|||||||
pending: '待触发',
|
pending: '待触发',
|
||||||
waiting: '等待中',
|
waiting: '等待中',
|
||||||
processing: '处理中',
|
processing: '处理中',
|
||||||
system_bound: '系统已绑定',
|
system_bound: '资源已准备',
|
||||||
pending_binding: '待系统绑定',
|
pending_binding: '待准备资源',
|
||||||
|
resource_ready: '资源已准备',
|
||||||
|
pending_prepare: '待准备资源',
|
||||||
pending_config: '待配置',
|
pending_config: '待配置',
|
||||||
pending_binding_prepare: '待准备绑定',
|
pending_binding_prepare: '待准备资源',
|
||||||
not_started: '未开始',
|
not_started: '未开始',
|
||||||
waiting_binding: '待客户绑定',
|
waiting_binding: '待客户绑定',
|
||||||
dispatched_pending_return: '待退还号码',
|
dispatched_pending_return: '待退还号码',
|
||||||
completed: '已完成',
|
completed: '已完成',
|
||||||
waiting_user_claim: '待用户打开链接',
|
waiting_user_claim: '待客户打开链接',
|
||||||
|
waiting_customer: '待客户绑定',
|
||||||
link_opened: '已打开链接',
|
link_opened: '已打开链接',
|
||||||
binding_confirmed: '已提交绑定',
|
binding_confirmed: '已提交',
|
||||||
binding_in_progress: '绑定处理中',
|
binding_in_progress: '处理中',
|
||||||
binding_completed: '完整绑定成功',
|
binding_completed: '履约完成',
|
||||||
binding_exception: '绑定异常',
|
binding_exception: '履约异常',
|
||||||
user_binding: '用户绑定中',
|
customer_confirmed: '已提交',
|
||||||
|
customer_processing: '处理中',
|
||||||
|
customer_completed: '履约完成',
|
||||||
|
customer_exception: '履约异常',
|
||||||
|
resource_exception: '资源异常',
|
||||||
|
user_binding: '客户处理中',
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!props.showRaw) {
|
if (!props.showRaw) {
|
||||||
|
|||||||
@@ -15,14 +15,13 @@ export type { AdminDashboardSummary } from './dashboard'
|
|||||||
|
|
||||||
// Orders types
|
// Orders types
|
||||||
export type {
|
export type {
|
||||||
AdminOrderBindingSummary,
|
AdminOrderFulfillmentProgress,
|
||||||
AdminOrderListItem,
|
AdminOrderListItem,
|
||||||
AdminOrderDetail,
|
AdminOrderDetail,
|
||||||
} from './orders'
|
} from './orders'
|
||||||
|
|
||||||
// Tasks types
|
// Tasks types
|
||||||
export type {
|
export type {
|
||||||
AdminTaskBindingSummary,
|
|
||||||
AdminTaskListItem,
|
AdminTaskListItem,
|
||||||
AdminTaskOperations,
|
AdminTaskOperations,
|
||||||
AdminTaskActionResponse,
|
AdminTaskActionResponse,
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
import type { AdminTaskListItem } from './tasks'
|
import type { AdminTaskListItem } from './tasks'
|
||||||
|
|
||||||
export interface AdminOrderBindingSummary {
|
export interface AdminOrderFulfillmentProgress {
|
||||||
totalTaskCount: number
|
totalTaskCount: number
|
||||||
systemBoundTaskCount: number
|
preparedTaskCount: number
|
||||||
completedBindingTaskCount: number
|
completedTaskCount: number
|
||||||
totalBindingCount: number
|
resourceStatus: string
|
||||||
reservedBindingCount: number
|
customerStatus: string
|
||||||
consumedBindingCount: number
|
|
||||||
releasedBindingCount: number
|
|
||||||
systemBindingStatus: string
|
|
||||||
userBindingStatus: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminOrderListItem {
|
export interface AdminOrderListItem {
|
||||||
@@ -34,14 +30,10 @@ export interface AdminOrderListItem {
|
|||||||
totalQuantity: number
|
totalQuantity: number
|
||||||
itemSummary: string
|
itemSummary: string
|
||||||
taskCount: number
|
taskCount: number
|
||||||
systemBindingStatus: string
|
resourceStatus: string
|
||||||
userBindingStatus: string
|
customerStatus: string
|
||||||
systemBoundTaskCount: number
|
preparedTaskCount: number
|
||||||
completedBindingTaskCount: number
|
completedTaskCount: number
|
||||||
totalBindingCount: number
|
|
||||||
reservedBindingCount: number
|
|
||||||
consumedBindingCount: number
|
|
||||||
releasedBindingCount: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminOrderDetail {
|
export interface AdminOrderDetail {
|
||||||
@@ -65,7 +57,7 @@ export interface AdminOrderDetail {
|
|||||||
updatedAt: string
|
updatedAt: string
|
||||||
itemSummary: string
|
itemSummary: string
|
||||||
rawPayload: Record<string, unknown>
|
rawPayload: Record<string, unknown>
|
||||||
bindingSummary: AdminOrderBindingSummary
|
fulfillmentProgress: AdminOrderFulfillmentProgress
|
||||||
}
|
}
|
||||||
items: Array<{
|
items: Array<{
|
||||||
orderItemId: number
|
orderItemId: number
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
export interface AdminTaskBindingSummary {
|
|
||||||
totalBindingCount: number
|
|
||||||
reservedBindingCount: number
|
|
||||||
consumedBindingCount: number
|
|
||||||
releasedBindingCount: number
|
|
||||||
roleKeys: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AdminTaskListItem {
|
export interface AdminTaskListItem {
|
||||||
taskId: number
|
taskId: number
|
||||||
taskNo: string
|
taskNo: string
|
||||||
@@ -17,8 +9,8 @@ export interface AdminTaskListItem {
|
|||||||
deliveryStatus: string
|
deliveryStatus: string
|
||||||
resultCode: string
|
resultCode: string
|
||||||
resultMessage: string
|
resultMessage: string
|
||||||
systemBindingStatus: string
|
resourceStatus: string
|
||||||
userBindingStatus: string
|
customerStatus: string
|
||||||
loginType: string
|
loginType: string
|
||||||
roleName: string
|
roleName: string
|
||||||
roleId: string
|
roleId: string
|
||||||
@@ -27,7 +19,6 @@ export interface AdminTaskListItem {
|
|||||||
roleConfirmedAt: string | null
|
roleConfirmedAt: string | null
|
||||||
redeemedAt: string | null
|
redeemedAt: string | null
|
||||||
retryCount: number
|
retryCount: number
|
||||||
bindingSummary: AdminTaskBindingSummary
|
|
||||||
lastError: string
|
lastError: string
|
||||||
createdAt: string
|
createdAt: string
|
||||||
updatedAt: string
|
updatedAt: string
|
||||||
|
|||||||
@@ -41,12 +41,15 @@ function formatSpecSummary(spec: Record<string, unknown>) {
|
|||||||
function resolveResourceStageLabel(status: string) {
|
function resolveResourceStageLabel(status: string) {
|
||||||
const normalized = String(status || '').trim()
|
const normalized = String(status || '').trim()
|
||||||
const labelMap: Record<string, string> = {
|
const labelMap: Record<string, string> = {
|
||||||
system_bound: '资源已准备',
|
resource_ready: '资源已准备',
|
||||||
pending_binding: '待准备资源',
|
pending_prepare: '待准备资源',
|
||||||
|
pending_payment: '待支付',
|
||||||
pending_binding_prepare: '待准备资源',
|
pending_binding_prepare: '待准备资源',
|
||||||
retry_pending: '待重试',
|
retry_pending: '待重试',
|
||||||
manual_review: '人工处理',
|
manual_review: '人工处理',
|
||||||
binding_exception: '资源异常',
|
resource_exception: '资源异常',
|
||||||
|
closed: '已关闭',
|
||||||
|
expired: '已过期',
|
||||||
completed: '已完成',
|
completed: '已完成',
|
||||||
not_started: '未开始',
|
not_started: '未开始',
|
||||||
}
|
}
|
||||||
@@ -58,15 +61,14 @@ function resolveCustomerStageLabel(status: string) {
|
|||||||
const normalized = String(status || '').trim()
|
const normalized = String(status || '').trim()
|
||||||
const labelMap: Record<string, string> = {
|
const labelMap: Record<string, string> = {
|
||||||
not_started: '待打开链接',
|
not_started: '待打开链接',
|
||||||
waiting_user_claim: '待打开链接',
|
waiting_customer: '待客户绑定',
|
||||||
waiting_binding: '待客户绑定',
|
|
||||||
link_opened: '已打开链接',
|
link_opened: '已打开链接',
|
||||||
user_binding: '客户绑定中',
|
customer_processing: '处理中',
|
||||||
binding_confirmed: '已提交',
|
customer_confirmed: '已提交',
|
||||||
binding_in_progress: '处理中',
|
customer_completed: '客户已完成',
|
||||||
binding_completed: '客户已完成',
|
customer_exception: '客户步骤异常',
|
||||||
binding_exception: '客户步骤异常',
|
closed: '已关闭',
|
||||||
completed: '已完成',
|
expired: '已过期',
|
||||||
}
|
}
|
||||||
|
|
||||||
return labelMap[normalized] || normalized || '-'
|
return labelMap[normalized] || normalized || '-'
|
||||||
@@ -74,32 +76,30 @@ function resolveCustomerStageLabel(status: string) {
|
|||||||
|
|
||||||
function resolveStageTone(status: string): TagType {
|
function resolveStageTone(status: string): TagType {
|
||||||
const normalized = String(status || '').trim()
|
const normalized = String(status || '').trim()
|
||||||
if (['system_bound', 'binding_completed', 'completed'].includes(normalized)) return 'success'
|
if (['resource_ready', 'customer_completed', 'completed'].includes(normalized)) return 'success'
|
||||||
if (
|
if (
|
||||||
[
|
[
|
||||||
'waiting_binding',
|
'waiting_customer',
|
||||||
'waiting_user_claim',
|
|
||||||
'link_opened',
|
'link_opened',
|
||||||
'user_binding',
|
'customer_confirmed',
|
||||||
'binding_confirmed',
|
'customer_processing',
|
||||||
'binding_in_progress',
|
|
||||||
].includes(normalized)
|
].includes(normalized)
|
||||||
) return 'primary'
|
) return 'primary'
|
||||||
if (['binding_exception', 'manual_review', 'failed'].includes(normalized)) return 'danger'
|
if (['resource_exception', 'customer_exception', 'manual_review', 'failed'].includes(normalized)) return 'danger'
|
||||||
if (['pending_binding', 'pending_binding_prepare', 'retry_pending'].includes(normalized)) return 'warning'
|
if (['pending_prepare', 'pending_binding_prepare', 'retry_pending'].includes(normalized)) return 'warning'
|
||||||
return 'info'
|
return 'info'
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveOrderProgressText(summary: AdminOrderDetail['order']['bindingSummary']) {
|
function resolveOrderProgressText(summary: AdminOrderDetail['order']['fulfillmentProgress']) {
|
||||||
if (!summary.totalTaskCount) return '未生成履约任务'
|
if (!summary.totalTaskCount) return '未生成履约任务'
|
||||||
if (summary.completedBindingTaskCount >= summary.totalTaskCount) return '履约完成'
|
if (summary.completedTaskCount >= summary.totalTaskCount) return '履约完成'
|
||||||
if (summary.systemBoundTaskCount > 0) return '等待客户绑定'
|
if (summary.preparedTaskCount > 0) return '等待客户绑定'
|
||||||
return '待准备领取链接'
|
return '待准备领取链接'
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveOrderProgressTone(summary: AdminOrderDetail['order']['bindingSummary']): TagType {
|
function resolveOrderProgressTone(summary: AdminOrderDetail['order']['fulfillmentProgress']): TagType {
|
||||||
if (!summary.totalTaskCount) return 'warning'
|
if (!summary.totalTaskCount) return 'warning'
|
||||||
if (summary.completedBindingTaskCount >= summary.totalTaskCount) return 'success'
|
if (summary.completedTaskCount >= summary.totalTaskCount) return 'success'
|
||||||
return 'warning'
|
return 'warning'
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -145,12 +145,12 @@ function resolveOrderProgressTone(summary: AdminOrderDetail['order']['bindingSum
|
|||||||
</div>
|
</div>
|
||||||
<div class="summary-block">
|
<div class="summary-block">
|
||||||
<span>履约进度</span>
|
<span>履约进度</span>
|
||||||
<el-tag :type="resolveOrderProgressTone(detail.order.bindingSummary)" effect="light">
|
<el-tag :type="resolveOrderProgressTone(detail.order.fulfillmentProgress)" effect="light">
|
||||||
{{ resolveOrderProgressText(detail.order.bindingSummary) }}
|
{{ resolveOrderProgressText(detail.order.fulfillmentProgress) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
<small>
|
<small>
|
||||||
完成 {{ detail.order.bindingSummary.completedBindingTaskCount }}/{{ detail.order.bindingSummary.totalTaskCount }}
|
完成 {{ detail.order.fulfillmentProgress.completedTaskCount }}/{{ detail.order.fulfillmentProgress.totalTaskCount }}
|
||||||
· 资源 {{ detail.order.bindingSummary.systemBoundTaskCount }}/{{ detail.order.bindingSummary.totalTaskCount }}
|
· 资源 {{ detail.order.fulfillmentProgress.preparedTaskCount }}/{{ detail.order.fulfillmentProgress.totalTaskCount }}
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -186,15 +186,15 @@ function resolveOrderProgressTone(summary: AdminOrderDetail['order']['bindingSum
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="资源准备">
|
<el-table-column label="资源准备">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="resolveStageTone(row.systemBindingStatus)" effect="light" size="small">
|
<el-tag :type="resolveStageTone(row.resourceStatus)" effect="light" size="small">
|
||||||
{{ resolveResourceStageLabel(row.systemBindingStatus) }}
|
{{ resolveResourceStageLabel(row.resourceStatus) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="客户步骤">
|
<el-table-column label="客户步骤">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="resolveStageTone(row.userBindingStatus)" effect="light" size="small">
|
<el-tag :type="resolveStageTone(row.customerStatus)" effect="light" size="small">
|
||||||
{{ resolveCustomerStageLabel(row.userBindingStatus) }}
|
{{ resolveCustomerStageLabel(row.customerStatus) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const summary = computed(() => ({
|
|||||||
completedCount: items.value.filter(
|
completedCount: items.value.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.taskCount > 0 &&
|
item.taskCount > 0 &&
|
||||||
item.completedBindingTaskCount >= item.taskCount &&
|
item.completedTaskCount >= item.taskCount &&
|
||||||
item.payStatus === "paid"
|
item.payStatus === "paid"
|
||||||
).length,
|
).length,
|
||||||
issueCount: items.value.filter((item) => hasOrderIssue(item)).length,
|
issueCount: items.value.filter((item) => hasOrderIssue(item)).length,
|
||||||
@@ -86,8 +86,8 @@ function resolveItemSummary(item: AdminOrderListItem) {
|
|||||||
function hasOrderIssue(item: AdminOrderListItem) {
|
function hasOrderIssue(item: AdminOrderListItem) {
|
||||||
return (
|
return (
|
||||||
["refunded", "failed", "unpaid"].includes(item.payStatus) ||
|
["refunded", "failed", "unpaid"].includes(item.payStatus) ||
|
||||||
item.systemBindingStatus === "binding_exception" ||
|
["resource_exception", "manual_review", "retry_pending"].includes(item.resourceStatus) ||
|
||||||
item.userBindingStatus === "binding_exception"
|
item.customerStatus === "customer_exception"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,20 +96,20 @@ function resolveFollowUpText(item: AdminOrderListItem) {
|
|||||||
if (item.payStatus === "refunded") return "已退款";
|
if (item.payStatus === "refunded") return "已退款";
|
||||||
if (item.payStatus === "failed") return "支付失败";
|
if (item.payStatus === "failed") return "支付失败";
|
||||||
if (
|
if (
|
||||||
item.userBindingStatus === "binding_exception" ||
|
item.customerStatus === "customer_exception" ||
|
||||||
item.systemBindingStatus === "binding_exception"
|
["resource_exception", "manual_review", "retry_pending"].includes(item.resourceStatus)
|
||||||
) {
|
) {
|
||||||
return "履约异常";
|
return "履约异常";
|
||||||
}
|
}
|
||||||
if (item.taskCount <= 0) return "未生成履约任务";
|
if (item.taskCount <= 0) return "未生成履约任务";
|
||||||
if (item.completedBindingTaskCount >= item.taskCount) return "履约完成";
|
if (item.completedTaskCount >= item.taskCount) return "履约完成";
|
||||||
if (item.systemBoundTaskCount > 0) return "等待客户绑定";
|
if (item.preparedTaskCount > 0) return "等待客户绑定";
|
||||||
return "待准备领取链接";
|
return "待准备领取链接";
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveFollowUpTone(item: AdminOrderListItem) {
|
function resolveFollowUpTone(item: AdminOrderListItem) {
|
||||||
if (hasOrderIssue(item)) return "danger";
|
if (hasOrderIssue(item)) return "danger";
|
||||||
if (item.taskCount <= 0 || item.completedBindingTaskCount < item.taskCount)
|
if (item.taskCount <= 0 || item.completedTaskCount < item.taskCount)
|
||||||
return "warning";
|
return "warning";
|
||||||
return "success";
|
return "success";
|
||||||
}
|
}
|
||||||
@@ -117,12 +117,15 @@ function resolveFollowUpTone(item: AdminOrderListItem) {
|
|||||||
function resolveResourceStageLabel(status: string) {
|
function resolveResourceStageLabel(status: string) {
|
||||||
const normalized = String(status || "").trim();
|
const normalized = String(status || "").trim();
|
||||||
const labelMap: Record<string, string> = {
|
const labelMap: Record<string, string> = {
|
||||||
system_bound: "资源已准备",
|
resource_ready: "资源已准备",
|
||||||
pending_binding: "待准备资源",
|
pending_prepare: "待准备资源",
|
||||||
|
pending_payment: "待支付",
|
||||||
pending_binding_prepare: "待准备资源",
|
pending_binding_prepare: "待准备资源",
|
||||||
retry_pending: "待重试",
|
retry_pending: "待重试",
|
||||||
manual_review: "人工处理",
|
manual_review: "人工处理",
|
||||||
binding_exception: "资源异常",
|
resource_exception: "资源异常",
|
||||||
|
closed: "已关闭",
|
||||||
|
expired: "已过期",
|
||||||
completed: "已完成",
|
completed: "已完成",
|
||||||
not_started: "未开始",
|
not_started: "未开始",
|
||||||
};
|
};
|
||||||
@@ -134,15 +137,14 @@ function resolveCustomerStageLabel(status: string) {
|
|||||||
const normalized = String(status || "").trim();
|
const normalized = String(status || "").trim();
|
||||||
const labelMap: Record<string, string> = {
|
const labelMap: Record<string, string> = {
|
||||||
not_started: "待打开链接",
|
not_started: "待打开链接",
|
||||||
waiting_user_claim: "待打开链接",
|
waiting_customer: "待客户绑定",
|
||||||
waiting_binding: "待客户绑定",
|
|
||||||
link_opened: "已打开链接",
|
link_opened: "已打开链接",
|
||||||
user_binding: "客户绑定中",
|
customer_processing: "处理中",
|
||||||
binding_confirmed: "已提交",
|
customer_confirmed: "已提交",
|
||||||
binding_in_progress: "处理中",
|
customer_completed: "客户已完成",
|
||||||
binding_completed: "客户已完成",
|
customer_exception: "客户步骤异常",
|
||||||
binding_exception: "客户步骤异常",
|
closed: "已关闭",
|
||||||
completed: "已完成",
|
expired: "已过期",
|
||||||
};
|
};
|
||||||
|
|
||||||
return labelMap[normalized] || normalized || "-";
|
return labelMap[normalized] || normalized || "-";
|
||||||
@@ -315,9 +317,9 @@ onMounted(() => {
|
|||||||
<AdminStatusTag :status="row.payStatus" :show-raw="false" />
|
<AdminStatusTag :status="row.payStatus" :show-raw="false" />
|
||||||
</div>
|
</div>
|
||||||
<p class="status-note">
|
<p class="status-note">
|
||||||
客户 {{ resolveCustomerStageLabel(row.userBindingStatus) }}
|
客户 {{ resolveCustomerStageLabel(row.customerStatus) }}
|
||||||
· 资源 {{ resolveResourceStageLabel(row.systemBindingStatus) }}
|
· 资源 {{ resolveResourceStageLabel(row.resourceStatus) }}
|
||||||
· {{ row.completedBindingTaskCount }}/{{ row.taskCount }}
|
· {{ row.completedTaskCount }}/{{ row.taskCount }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -129,12 +129,15 @@ function resetFilters() {
|
|||||||
function resolveResourceStageLabel(status: string) {
|
function resolveResourceStageLabel(status: string) {
|
||||||
const normalized = String(status || "").trim();
|
const normalized = String(status || "").trim();
|
||||||
const labelMap: Record<string, string> = {
|
const labelMap: Record<string, string> = {
|
||||||
system_bound: "资源已准备",
|
resource_ready: "资源已准备",
|
||||||
pending_binding: "待准备资源",
|
pending_prepare: "待准备资源",
|
||||||
|
pending_payment: "待支付",
|
||||||
pending_binding_prepare: "待准备资源",
|
pending_binding_prepare: "待准备资源",
|
||||||
retry_pending: "待重试",
|
retry_pending: "待重试",
|
||||||
manual_review: "人工处理",
|
manual_review: "人工处理",
|
||||||
binding_exception: "资源异常",
|
resource_exception: "资源异常",
|
||||||
|
closed: "已关闭",
|
||||||
|
expired: "已过期",
|
||||||
completed: "已完成",
|
completed: "已完成",
|
||||||
not_started: "未开始",
|
not_started: "未开始",
|
||||||
};
|
};
|
||||||
@@ -146,15 +149,14 @@ function resolveCustomerStageLabel(status: string) {
|
|||||||
const normalized = String(status || "").trim();
|
const normalized = String(status || "").trim();
|
||||||
const labelMap: Record<string, string> = {
|
const labelMap: Record<string, string> = {
|
||||||
not_started: "待打开链接",
|
not_started: "待打开链接",
|
||||||
waiting_user_claim: "待打开链接",
|
waiting_customer: "待客户绑定",
|
||||||
waiting_binding: "待客户绑定",
|
|
||||||
link_opened: "已打开链接",
|
link_opened: "已打开链接",
|
||||||
user_binding: "客户绑定中",
|
customer_processing: "处理中",
|
||||||
binding_confirmed: "已提交",
|
customer_confirmed: "已提交",
|
||||||
binding_in_progress: "处理中",
|
customer_completed: "客户已完成",
|
||||||
binding_completed: "客户已完成",
|
customer_exception: "客户步骤异常",
|
||||||
binding_exception: "客户步骤异常",
|
closed: "已关闭",
|
||||||
completed: "已完成",
|
expired: "已过期",
|
||||||
};
|
};
|
||||||
|
|
||||||
return labelMap[normalized] || normalized || "-";
|
return labelMap[normalized] || normalized || "-";
|
||||||
@@ -164,8 +166,8 @@ function resolveTaskMainText(item: AdminTaskListItem) {
|
|||||||
if (item.lastError) return "需要关注";
|
if (item.lastError) return "需要关注";
|
||||||
if (["failed", "manual_review", "retry_pending"].includes(item.status)) return "待处理";
|
if (["failed", "manual_review", "retry_pending"].includes(item.status)) return "待处理";
|
||||||
if (["redeemed", "completed", "closed"].includes(item.status)) return "已完成";
|
if (["redeemed", "completed", "closed"].includes(item.status)) return "已完成";
|
||||||
if (item.userBindingStatus === "waiting_binding") return "等待客户绑定";
|
if (item.customerStatus === "waiting_customer") return "等待客户绑定";
|
||||||
if (item.systemBindingStatus === "system_bound") return "资源已准备";
|
if (item.resourceStatus === "resource_ready") return "资源已准备";
|
||||||
if (item.status === "pending_binding_prepare") return "待准备资源";
|
if (item.status === "pending_binding_prepare") return "待准备资源";
|
||||||
return item.status || "处理中";
|
return item.status || "处理中";
|
||||||
}
|
}
|
||||||
@@ -332,8 +334,8 @@ onMounted(() => {
|
|||||||
{{ resolveTaskMainText(row) }}
|
{{ resolveTaskMainText(row) }}
|
||||||
</span>
|
</span>
|
||||||
<p class="task-status-note">
|
<p class="task-status-note">
|
||||||
客户 {{ resolveCustomerStageLabel(row.userBindingStatus) }}
|
客户 {{ resolveCustomerStageLabel(row.customerStatus) }}
|
||||||
· 资源 {{ resolveResourceStageLabel(row.systemBindingStatus) }}
|
· 资源 {{ resolveResourceStageLabel(row.resourceStatus) }}
|
||||||
</p>
|
</p>
|
||||||
<p v-if="row.roleName || row.roleId" class="task-status-note">
|
<p v-if="row.roleName || row.roleId" class="task-status-note">
|
||||||
角色 {{ row.roleName || "-" }} / {{ row.roleId || "-" }}
|
角色 {{ row.roleName || "-" }} / {{ row.roleId || "-" }}
|
||||||
|
|||||||
@@ -17,12 +17,15 @@ defineProps<Props>()
|
|||||||
function resolveResourceStageLabel(status: string) {
|
function resolveResourceStageLabel(status: string) {
|
||||||
const normalized = String(status || '').trim()
|
const normalized = String(status || '').trim()
|
||||||
const labelMap: Record<string, string> = {
|
const labelMap: Record<string, string> = {
|
||||||
system_bound: '资源已准备',
|
resource_ready: '资源已准备',
|
||||||
pending_binding: '待准备资源',
|
pending_prepare: '待准备资源',
|
||||||
|
pending_payment: '待支付',
|
||||||
pending_binding_prepare: '待准备资源',
|
pending_binding_prepare: '待准备资源',
|
||||||
retry_pending: '待重试',
|
retry_pending: '待重试',
|
||||||
manual_review: '人工处理',
|
manual_review: '人工处理',
|
||||||
binding_exception: '资源异常',
|
resource_exception: '资源异常',
|
||||||
|
closed: '已关闭',
|
||||||
|
expired: '已过期',
|
||||||
completed: '已完成',
|
completed: '已完成',
|
||||||
not_started: '未开始',
|
not_started: '未开始',
|
||||||
}
|
}
|
||||||
@@ -34,15 +37,14 @@ function resolveCustomerStageLabel(status: string) {
|
|||||||
const normalized = String(status || '').trim()
|
const normalized = String(status || '').trim()
|
||||||
const labelMap: Record<string, string> = {
|
const labelMap: Record<string, string> = {
|
||||||
not_started: '待打开链接',
|
not_started: '待打开链接',
|
||||||
waiting_user_claim: '待打开链接',
|
waiting_customer: '待客户绑定',
|
||||||
waiting_binding: '待客户绑定',
|
|
||||||
link_opened: '已打开链接',
|
link_opened: '已打开链接',
|
||||||
user_binding: '客户绑定中',
|
customer_processing: '处理中',
|
||||||
binding_confirmed: '已提交',
|
customer_confirmed: '已提交',
|
||||||
binding_in_progress: '处理中',
|
customer_completed: '客户已完成',
|
||||||
binding_completed: '客户已完成',
|
customer_exception: '客户步骤异常',
|
||||||
binding_exception: '客户步骤异常',
|
closed: '已关闭',
|
||||||
completed: '已完成',
|
expired: '已过期',
|
||||||
}
|
}
|
||||||
|
|
||||||
return labelMap[normalized] || normalized || '-'
|
return labelMap[normalized] || normalized || '-'
|
||||||
@@ -85,8 +87,8 @@ function resolveCustomerStageLabel(status: string) {
|
|||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
<div class="hero-inline">
|
<div class="hero-inline">
|
||||||
<span>资源 {{ resolveResourceStageLabel(detail.task.systemBindingStatus) }}</span>
|
<span>资源 {{ resolveResourceStageLabel(detail.task.resourceStatus) }}</span>
|
||||||
<span>客户 {{ resolveCustomerStageLabel(detail.task.userBindingStatus) }}</span>
|
<span>客户 {{ resolveCustomerStageLabel(detail.task.customerStatus) }}</span>
|
||||||
<span v-if="detail.task.lastError">最近异常 {{ detail.task.lastError }}</span>
|
<span v-if="detail.task.lastError">最近异常 {{ detail.task.lastError }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="claimUrl" class="hero-link-row">
|
<div v-if="claimUrl" class="hero-link-row">
|
||||||
@@ -113,8 +115,8 @@ function resolveCustomerStageLabel(status: string) {
|
|||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
资源:{{ resolveResourceStageLabel(detail.task.systemBindingStatus) }} ·
|
资源:{{ resolveResourceStageLabel(detail.task.resourceStatus) }} ·
|
||||||
客户:{{ resolveCustomerStageLabel(detail.task.userBindingStatus) }}
|
客户:{{ resolveCustomerStageLabel(detail.task.customerStatus) }}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
角色:{{ detail.task.roleName || '-' }} /
|
角色:{{ detail.task.roleName || '-' }} /
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ https://你的域名/api/v1/open/91
|
|||||||
| 91字段 | 当前项目用途 | 说明 |
|
| 91字段 | 当前项目用途 | 说明 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `orderNo` | 外部订单号 / 幂等键 | 建议映射为内部 `platformOrderId` |
|
| `orderNo` | 外部订单号 / 幂等键 | 建议映射为内部 `platformOrderId` |
|
||||||
| `productNo` | 商品映射 | 映射到内部 SKU 或履约绑定规则 |
|
| `productNo` | 商品和店铺映射 | 默认按商品名匹配 cloudtentacles 商品;可用 `商品名----店铺编号` 携带快手小店 `shopId` |
|
||||||
| `buyNum` | 购买数量 | 生成对应数量的履约任务 |
|
| `buyNum` | 购买数量 | 生成对应数量的履约任务 |
|
||||||
| `maxAmount` | 成本上限校验 | 可选,超限返回失败 |
|
| `maxAmount` | 成本上限校验 | 可选,超限返回失败 |
|
||||||
| `callbackUrl` | 备用回调地址 | 先保留,不作为主流程依赖 |
|
| `callbackUrl` | 备用回调地址 | 先保留,不作为主流程依赖 |
|
||||||
|
|||||||
+7
-15
@@ -155,8 +155,8 @@ platforms: {
|
|||||||
|
|
||||||
这样可直接复用现有:
|
这样可直接复用现有:
|
||||||
|
|
||||||
- 商品匹配;
|
- 91 商品名到 cloudtentacles 商品名的匹配;
|
||||||
- 履约绑定;
|
- cloudtentacles 覆盖规则(套装、多数量发货);
|
||||||
- 快手 Cloud task 创建;
|
- 快手 Cloud task 创建;
|
||||||
- claim token 生成。
|
- claim token 生成。
|
||||||
|
|
||||||
@@ -247,21 +247,13 @@ Node 侧实现等价为:
|
|||||||
|
|
||||||
## 10. 商品匹配设计
|
## 10. 商品匹配设计
|
||||||
|
|
||||||
当前项目的订单商品匹配依赖:
|
当前项目的 `91` 下单流程直接使用 `productNo` 做履约匹配:
|
||||||
|
|
||||||
- `provider`
|
- 默认把 `productNo` 当作商品名,匹配 cloudtentacles 商品列表里的同名商品;
|
||||||
- `platform`
|
- 如果 `productNo` 使用 `商品名----店铺编号` 格式,前半段用于商品匹配,后半段作为快手小店 `shopId` 用于核销;
|
||||||
- `shopId`
|
- 特殊商品通过 cloudtentacles 覆盖规则处理,例如一个 91 商品发多个 cloudtentacles 商品,或某个商品发多次。
|
||||||
- `externalSkuCode` / `externalItemId` / `externalSkuName`
|
|
||||||
|
|
||||||
因此接入 `91` 后,运营侧需要新增对应绑定规则,使:
|
最终目标是让 91 卡券只传商品名,系统按商品名和覆盖规则自动创建快手 Cloud 履约任务。
|
||||||
|
|
||||||
- `provider = '91kaquan'`
|
|
||||||
- `platform = 'kuaishou'`
|
|
||||||
- `shopId = '91kaquan'`
|
|
||||||
- `externalSkuCode = productNo`
|
|
||||||
|
|
||||||
最终匹配到现有快手 Cloud 履约配置。
|
|
||||||
|
|
||||||
## 11. 日志与排障
|
## 11. 日志与排障
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user