diff --git a/apps/backend/data/kuaishou-cloud-fulfillment.example.json b/apps/backend/data/kuaishou-cloud-fulfillment.example.json deleted file mode 100644 index 29ccc46e..00000000 --- a/apps/backend/data/kuaishou-cloud-fulfillment.example.json +++ /dev/null @@ -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 后填写真实配置" - } - ] -} diff --git a/apps/backend/src/services/admin/admin-order-read-helpers.ts b/apps/backend/src/services/admin/admin-order-read-helpers.ts index 0a1b7f1a..17481412 100644 --- a/apps/backend/src/services/admin/admin-order-read-helpers.ts +++ b/apps/backend/src/services/admin/admin-order-read-helpers.ts @@ -2,7 +2,7 @@ import { listOrderItemsByOrderId } from '../../repositories/order-item-repo.js' import { listTasksByOrderId } from '../../repositories/task-repo.js' import { formatFenToAmount, normalizeFen } from '../../utils/money.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 { OrderItemRow, OrderListRow } from '../../types/repository/rows.js' @@ -12,7 +12,7 @@ export async function mapAdminOrderListItem(item: OrderListRow): Promise sum + Math.max(1, Number(orderItem.quantity || 1)), 0), itemSummary, taskCount: Number(item.task_count || tasks.length || 0), - systemBindingStatus: bindingSummary.systemBindingStatus, - userBindingStatus: bindingSummary.userBindingStatus, - systemBoundTaskCount: bindingSummary.systemBoundTaskCount, - completedBindingTaskCount: bindingSummary.completedBindingTaskCount, - totalBindingCount: bindingSummary.totalBindingCount, - reservedBindingCount: bindingSummary.reservedBindingCount, - consumedBindingCount: bindingSummary.consumedBindingCount, - releasedBindingCount: bindingSummary.releasedBindingCount, + resourceStatus: fulfillmentProgress.resourceStatus, + customerStatus: fulfillmentProgress.customerStatus, + preparedTaskCount: fulfillmentProgress.preparedTaskCount, + completedTaskCount: fulfillmentProgress.completedTaskCount, } } diff --git a/apps/backend/src/services/admin/admin-read-service.ts b/apps/backend/src/services/admin/admin-read-service.ts index 5d44c2b5..04aa3f9f 100644 --- a/apps/backend/src/services/admin/admin-read-service.ts +++ b/apps/backend/src/services/admin/admin-read-service.ts @@ -26,7 +26,7 @@ import { } from './admin-read-shared-helpers.js' import { mapAdminOrderListItem, summarizeOrderItems } from './admin-order-read-helpers.js' import { - buildOrderBindingSummary, + buildOrderFulfillmentProgress, getRequiredTask, mapAdminTaskEvent, mapAdminTaskListItem, @@ -104,7 +104,7 @@ export async function getAdminOrderDetail(orderId: AdminEntityIdInput): Promise< rawPayload: order.raw_payload_json && typeof order.raw_payload_json === 'object' ? order.raw_payload_json : JSON.parse(String(order.raw_payload_json || '{}')), - bindingSummary: buildOrderBindingSummary(tasks), + fulfillmentProgress: buildOrderFulfillmentProgress(tasks), }, items: items.map((item) => ({ orderItemId: item.id, diff --git a/apps/backend/src/services/admin/admin-task-read-helpers.ts b/apps/backend/src/services/admin/admin-task-read-helpers.ts index a28f3a3d..8c611e2a 100644 --- a/apps/backend/src/services/admin/admin-task-read-helpers.ts +++ b/apps/backend/src/services/admin/admin-task-read-helpers.ts @@ -7,7 +7,7 @@ import { } from './admin-read-shared-helpers.js' import type { - AdminTaskBindingSummary, + AdminOrderFulfillmentProgress, AdminTaskListItem, } from '../../types/admin/read-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 -type TaskBindingState = { - systemBindingStatus: string - userBindingStatus: string -} - -type OrderBindingSummary = { - totalTaskCount: number - systemBoundTaskCount: number - completedBindingTaskCount: number - totalBindingCount: number - reservedBindingCount: number - consumedBindingCount: number - releasedBindingCount: number - systemBindingStatus: string - userBindingStatus: string +type TaskFulfillmentState = { + resourceStatus: string + customerStatus: string } export function mapAdminTaskSummary( task: TaskRow, - bindingSummary: AdminTaskBindingSummary = createEmptyTaskBindingSummary(), ): JsonRecord { - const binding = buildTaskBindingState(task) + const fulfillment = buildTaskFulfillmentState(task) return { taskId: task.id, taskNo: task.task_no, deliveryStatus: task.delivery_status || '', status: task.task_status, - systemBindingStatus: binding.systemBindingStatus, - userBindingStatus: binding.userBindingStatus, + resourceStatus: fulfillment.resourceStatus, + customerStatus: fulfillment.customerStatus, loginType: task.login_type, runtimeSessionId: task.runtime_session_id, claimedAt: task.claimed_at, @@ -56,7 +43,6 @@ export function mapAdminTaskSummary( redeemedAt: task.redeemed_at, lastError: task.last_error, retryCount: getTaskRetryCount(task), - bindingSummary, createdAt: task.created_at, updatedAt: task.updated_at, } @@ -95,8 +81,7 @@ export function mapAdminTaskListItem( task: TaskRow, viewerContext: AdminViewerContext = createAdminViewerContext(), ): AdminTaskListItem { - const binding = buildTaskBindingState(task) - const bindingSummary = createEmptyTaskBindingSummary() + const fulfillment = buildTaskFulfillmentState(task) return { taskId: task.id, @@ -109,8 +94,8 @@ export function mapAdminTaskListItem( deliveryStatus: task.delivery_status || '', resultCode: task.result_code || '', resultMessage: task.result_message || '', - systemBindingStatus: binding.systemBindingStatus, - userBindingStatus: binding.userBindingStatus, + resourceStatus: fulfillment.resourceStatus, + customerStatus: fulfillment.customerStatus, loginType: task.login_type, roleName: task.role_name, roleId: task.role_id, @@ -119,7 +104,6 @@ export function mapAdminTaskListItem( roleConfirmedAt: task.role_confirmed_at, redeemedAt: task.redeemed_at, retryCount: getTaskRetryCount(task), - bindingSummary, lastError: task.last_error, createdAt: task.created_at, updatedAt: task.updated_at, @@ -139,158 +123,140 @@ export function mapAdminTaskEvent(event: TaskEventRow): JsonRecord { } } -export function createEmptyTaskBindingSummary(): AdminTaskBindingSummary { - return { - totalBindingCount: 0, - reservedBindingCount: 0, - consumedBindingCount: 0, - releasedBindingCount: 0, - roleKeys: [], - } -} - -export function buildOrderBindingSummary( +export function buildOrderFulfillmentProgress( tasks: TaskRow[] | null | undefined, -): OrderBindingSummary { +): AdminOrderFulfillmentProgress { const normalizedTasks = Array.isArray(tasks) ? tasks : [] const totalTaskCount = normalizedTasks.length - const taskBindings = normalizedTasks.map((task) => buildTaskBindingState(task)) - const systemBoundTaskCount = normalizedTasks.filter((task) => isTaskSystemBound(task)).length - const completedBindingTaskCount = normalizedTasks.filter((task) => String(task?.task_status || '') === 'redeemed').length - const totalBindingCount = 0 - const reservedBindingCount = 0 - const consumedBindingCount = 0 - const releasedBindingCount = 0 + const taskFulfillments = normalizedTasks.map((task) => buildTaskFulfillmentState(task)) + const preparedTaskCount = normalizedTasks.filter((task) => isTaskResourcePrepared(task)).length + const completedTaskCount = normalizedTasks.filter((task) => isTaskFulfillmentCompleted(task)).length - let systemBindingStatus = 'pending_binding' - let userBindingStatus = 'not_started' + let resourceStatus = 'pending_prepare' + let customerStatus = 'not_started' if (totalTaskCount === 0) { return { totalTaskCount, - systemBoundTaskCount, - completedBindingTaskCount, - totalBindingCount, - reservedBindingCount, - consumedBindingCount, - releasedBindingCount, - systemBindingStatus, - userBindingStatus, + preparedTaskCount, + completedTaskCount, + resourceStatus, + customerStatus, } } - if (normalizedTasks.every((task) => String(task.task_status || '') === 'redeemed')) { - systemBindingStatus = 'system_bound' - userBindingStatus = 'binding_completed' - } else if (taskBindings.some((item) => ['binding_in_progress', 'binding_confirmed', 'link_opened'].includes(item.userBindingStatus))) { - systemBindingStatus = systemBoundTaskCount > 0 ? 'system_bound' : 'pending_binding' - userBindingStatus = 'user_binding' - } else if (taskBindings.some((item) => item.userBindingStatus === 'waiting_user_claim')) { - systemBindingStatus = systemBoundTaskCount > 0 ? 'system_bound' : 'pending_binding' - userBindingStatus = 'waiting_user_claim' - } else if (taskBindings.some((item) => item.userBindingStatus === 'binding_exception')) { - systemBindingStatus = systemBoundTaskCount > 0 ? 'system_bound' : 'pending_binding' - userBindingStatus = 'binding_exception' - } else if (systemBoundTaskCount > 0) { - systemBindingStatus = 'system_bound' - } else if (taskBindings.some((item) => ['manual_review', 'retry_pending'].includes(item.systemBindingStatus))) { - systemBindingStatus = 'binding_exception' + if (normalizedTasks.every((task) => isTaskFulfillmentCompleted(task))) { + resourceStatus = 'resource_ready' + customerStatus = 'customer_completed' + } else if (taskFulfillments.some((item) => ['customer_processing', 'customer_confirmed', 'link_opened'].includes(item.customerStatus))) { + resourceStatus = preparedTaskCount > 0 ? 'resource_ready' : 'pending_prepare' + customerStatus = 'customer_processing' + } else if (taskFulfillments.some((item) => item.customerStatus === 'waiting_customer')) { + resourceStatus = preparedTaskCount > 0 ? 'resource_ready' : 'pending_prepare' + customerStatus = 'waiting_customer' + } else if (taskFulfillments.some((item) => item.customerStatus === 'customer_exception')) { + resourceStatus = preparedTaskCount > 0 ? 'resource_ready' : 'pending_prepare' + customerStatus = 'customer_exception' + } else if (preparedTaskCount > 0) { + resourceStatus = 'resource_ready' + } else if (taskFulfillments.some((item) => ['manual_review', 'retry_pending'].includes(item.resourceStatus))) { + resourceStatus = 'resource_exception' } return { totalTaskCount, - systemBoundTaskCount, - completedBindingTaskCount, - totalBindingCount, - reservedBindingCount, - consumedBindingCount, - releasedBindingCount, - systemBindingStatus, - userBindingStatus, + preparedTaskCount, + completedTaskCount, + resourceStatus, + customerStatus, } } -function buildTaskBindingState(task: TaskRow | null | undefined): TaskBindingState { +function buildTaskFulfillmentState(task: TaskRow | null | undefined): TaskFulfillmentState { const normalizedStatus = String(task?.task_status || '').trim() if (!normalizedStatus) { return { - systemBindingStatus: 'pending_binding', - userBindingStatus: 'not_started', + resourceStatus: 'pending_prepare', + customerStatus: 'not_started', } } if (normalizedStatus === 'pending_payment') { - return { systemBindingStatus: 'pending_payment', userBindingStatus: 'not_started' } + return { resourceStatus: 'pending_payment', customerStatus: 'not_started' } } if (normalizedStatus === 'paid') { - return { systemBindingStatus: 'pending_binding', userBindingStatus: 'not_started' } + return { resourceStatus: 'pending_prepare', customerStatus: 'not_started' } } if (normalizedStatus === 'pending_binding_prepare') { - return { systemBindingStatus: 'pending_binding', userBindingStatus: 'not_started' } + return { resourceStatus: 'pending_prepare', customerStatus: 'not_started' } } if (normalizedStatus === 'waiting_binding') { - return { systemBindingStatus: 'system_bound', userBindingStatus: 'waiting_user_claim' } + return { resourceStatus: 'resource_ready', customerStatus: 'waiting_customer' } } if (normalizedStatus === 'dispatched_pending_return') { - return { systemBindingStatus: 'system_bound', userBindingStatus: 'binding_completed' } + return { resourceStatus: 'resource_ready', customerStatus: 'customer_completed' } } if (normalizedStatus === 'completed') { - return { systemBindingStatus: 'system_bound', userBindingStatus: 'binding_completed' } + return { resourceStatus: 'resource_ready', customerStatus: 'customer_completed' } } if (normalizedStatus === 'manual_review') { - return { systemBindingStatus: 'manual_review', userBindingStatus: 'binding_exception' } + return { resourceStatus: 'manual_review', customerStatus: 'customer_exception' } } if (normalizedStatus === 'retry_pending') { - return { systemBindingStatus: 'retry_pending', userBindingStatus: 'binding_exception' } + return { resourceStatus: 'retry_pending', customerStatus: 'customer_exception' } } if (normalizedStatus === 'closed') { - return { systemBindingStatus: 'closed', userBindingStatus: 'closed' } + return { resourceStatus: 'closed', customerStatus: 'closed' } } if (normalizedStatus === 'expired') { - return { systemBindingStatus: 'expired', userBindingStatus: 'expired' } + return { resourceStatus: 'expired', customerStatus: 'expired' } } if (normalizedStatus === 'link_generated') { - return { systemBindingStatus: 'system_bound', userBindingStatus: 'waiting_user_claim' } + return { resourceStatus: 'resource_ready', customerStatus: 'waiting_customer' } } if (normalizedStatus === 'claimed') { - return { systemBindingStatus: 'system_bound', userBindingStatus: 'link_opened' } + return { resourceStatus: 'resource_ready', customerStatus: 'link_opened' } } if (normalizedStatus === 'role_confirmed') { - return { systemBindingStatus: 'system_bound', userBindingStatus: 'binding_confirmed' } + return { resourceStatus: 'resource_ready', customerStatus: 'customer_confirmed' } } if (normalizedStatus === 'redeeming') { - return { systemBindingStatus: 'system_bound', userBindingStatus: 'binding_in_progress' } + return { resourceStatus: 'resource_ready', customerStatus: 'customer_processing' } } if (normalizedStatus === 'redeemed') { - return { systemBindingStatus: 'system_bound', userBindingStatus: 'binding_completed' } + return { resourceStatus: 'resource_ready', customerStatus: 'customer_completed' } } - if (isTaskSystemBound(task)) { - return { systemBindingStatus: 'system_bound', userBindingStatus: 'waiting_user_claim' } + if (isTaskResourcePrepared(task)) { + 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)) } +function isTaskFulfillmentCompleted(task: TaskRow | null | undefined): boolean { + return ['redeemed', 'completed', 'dispatched_pending_return'].includes(String(task?.task_status || '').trim()) +} + function normalizeRecord(value: unknown): JsonRecord { return value && typeof value === 'object' && !Array.isArray(value) ? value : {} } diff --git a/apps/backend/src/services/admin/platform-config/cloudtentacles/domain.test.ts b/apps/backend/src/services/admin/platform-config/cloudtentacles/domain.test.ts deleted file mode 100644 index 4670b7d4..00000000 --- a/apps/backend/src/services/admin/platform-config/cloudtentacles/domain.test.ts +++ /dev/null @@ -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', - }, - }) -}) diff --git a/apps/backend/src/services/admin/platform-config/cloudtentacles/domain.ts b/apps/backend/src/services/admin/platform-config/cloudtentacles/domain.ts deleted file mode 100644 index 690ac297..00000000 --- a/apps/backend/src/services/admin/platform-config/cloudtentacles/domain.ts +++ /dev/null @@ -1,57 +0,0 @@ -type JsonObject = Record - -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, - } -} diff --git a/apps/backend/src/services/admin/platform-config/cloudtentacles/observed-products.test.ts b/apps/backend/src/services/admin/platform-config/cloudtentacles/observed-products.test.ts deleted file mode 100644 index 346ff065..00000000 --- a/apps/backend/src/services/admin/platform-config/cloudtentacles/observed-products.test.ts +++ /dev/null @@ -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', - }, - }, - ]) -}) diff --git a/apps/backend/src/services/admin/platform-config/cloudtentacles/observed-products.ts b/apps/backend/src/services/admin/platform-config/cloudtentacles/observed-products.ts deleted file mode 100644 index 37980aec..00000000 --- a/apps/backend/src/services/admin/platform-config/cloudtentacles/observed-products.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { query } from '../../../../db/client.js' -import { mapAdminObservedProductItem } from './domain.js' - -type JsonObject = Record - -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)) -} diff --git a/apps/backend/src/types/admin/read-models.ts b/apps/backend/src/types/admin/read-models.ts index 43852453..cc494b28 100644 --- a/apps/backend/src/types/admin/read-models.ts +++ b/apps/backend/src/types/admin/read-models.ts @@ -1,9 +1,9 @@ -export type AdminTaskBindingSummary = { - totalBindingCount: number - reservedBindingCount: number - consumedBindingCount: number - releasedBindingCount: number - roleKeys: string[] +export type AdminOrderFulfillmentProgress = { + totalTaskCount: number + preparedTaskCount: number + completedTaskCount: number + resourceStatus: string + customerStatus: string } export type AdminOrderListItem = { @@ -28,14 +28,10 @@ export type AdminOrderListItem = { totalQuantity: number itemSummary: string taskCount: number - systemBindingStatus: string - userBindingStatus: string - systemBoundTaskCount: number - completedBindingTaskCount: number - totalBindingCount: number - reservedBindingCount: number - consumedBindingCount: number - releasedBindingCount: number + resourceStatus: string + customerStatus: string + preparedTaskCount: number + completedTaskCount: number } export type AdminTaskListItem = { @@ -49,8 +45,8 @@ export type AdminTaskListItem = { deliveryStatus: string resultCode: string resultMessage: string - systemBindingStatus: string - userBindingStatus: string + resourceStatus: string + customerStatus: string loginType: string roleName: string roleId: string @@ -59,7 +55,6 @@ export type AdminTaskListItem = { roleConfirmedAt: string | null redeemedAt: string | null retryCount: number - bindingSummary: AdminTaskBindingSummary lastError: string createdAt: string updatedAt: string diff --git a/apps/frontend/src/components/admin/AdminStatusTag.vue b/apps/frontend/src/components/admin/AdminStatusTag.vue index 8f10c311..134fb47d 100644 --- a/apps/frontend/src/components/admin/AdminStatusTag.vue +++ b/apps/frontend/src/components/admin/AdminStatusTag.vue @@ -98,7 +98,7 @@ function resolveLabel(status: string) { refunding: '退款中', closed: '已关闭', pending_payment: '待支付', - cdk_reserved: '已预占', + cdk_reserved: '资源已准备', link_generated: '已生成链接', claimed: '已创建会话', role_confirmed: '已确认角色', @@ -108,9 +108,9 @@ function resolveLabel(status: string) { manual_review: '人工处理', expired: '已过期', available: '可用', - reserved: '已预占', + reserved: '资源已准备', delivered: '已发放', - consumed: '已消耗', + consumed: '已使用', invalid: '已作废', active: '生效中', admin: '管理员', @@ -123,21 +123,29 @@ function resolveLabel(status: string) { pending: '待触发', waiting: '等待中', processing: '处理中', - system_bound: '系统已绑定', - pending_binding: '待系统绑定', + system_bound: '资源已准备', + pending_binding: '待准备资源', + resource_ready: '资源已准备', + pending_prepare: '待准备资源', pending_config: '待配置', - pending_binding_prepare: '待准备绑定', + pending_binding_prepare: '待准备资源', not_started: '未开始', waiting_binding: '待客户绑定', dispatched_pending_return: '待退还号码', completed: '已完成', - waiting_user_claim: '待用户打开链接', + waiting_user_claim: '待客户打开链接', + waiting_customer: '待客户绑定', link_opened: '已打开链接', - binding_confirmed: '已提交绑定', - binding_in_progress: '绑定处理中', - binding_completed: '完整绑定成功', - binding_exception: '绑定异常', - user_binding: '用户绑定中', + binding_confirmed: '已提交', + binding_in_progress: '处理中', + binding_completed: '履约完成', + binding_exception: '履约异常', + customer_confirmed: '已提交', + customer_processing: '处理中', + customer_completed: '履约完成', + customer_exception: '履约异常', + resource_exception: '资源异常', + user_binding: '客户处理中', } if (!props.showRaw) { diff --git a/apps/frontend/src/types/admin/index.ts b/apps/frontend/src/types/admin/index.ts index 9ace322b..7153287b 100644 --- a/apps/frontend/src/types/admin/index.ts +++ b/apps/frontend/src/types/admin/index.ts @@ -15,14 +15,13 @@ export type { AdminDashboardSummary } from './dashboard' // Orders types export type { - AdminOrderBindingSummary, + AdminOrderFulfillmentProgress, AdminOrderListItem, AdminOrderDetail, } from './orders' // Tasks types export type { - AdminTaskBindingSummary, AdminTaskListItem, AdminTaskOperations, AdminTaskActionResponse, diff --git a/apps/frontend/src/types/admin/orders.ts b/apps/frontend/src/types/admin/orders.ts index 037a154d..c41d39aa 100644 --- a/apps/frontend/src/types/admin/orders.ts +++ b/apps/frontend/src/types/admin/orders.ts @@ -1,15 +1,11 @@ import type { AdminTaskListItem } from './tasks' -export interface AdminOrderBindingSummary { +export interface AdminOrderFulfillmentProgress { totalTaskCount: number - systemBoundTaskCount: number - completedBindingTaskCount: number - totalBindingCount: number - reservedBindingCount: number - consumedBindingCount: number - releasedBindingCount: number - systemBindingStatus: string - userBindingStatus: string + preparedTaskCount: number + completedTaskCount: number + resourceStatus: string + customerStatus: string } export interface AdminOrderListItem { @@ -34,14 +30,10 @@ export interface AdminOrderListItem { totalQuantity: number itemSummary: string taskCount: number - systemBindingStatus: string - userBindingStatus: string - systemBoundTaskCount: number - completedBindingTaskCount: number - totalBindingCount: number - reservedBindingCount: number - consumedBindingCount: number - releasedBindingCount: number + resourceStatus: string + customerStatus: string + preparedTaskCount: number + completedTaskCount: number } export interface AdminOrderDetail { @@ -65,7 +57,7 @@ export interface AdminOrderDetail { updatedAt: string itemSummary: string rawPayload: Record - bindingSummary: AdminOrderBindingSummary + fulfillmentProgress: AdminOrderFulfillmentProgress } items: Array<{ orderItemId: number diff --git a/apps/frontend/src/types/admin/tasks.ts b/apps/frontend/src/types/admin/tasks.ts index cd478812..ec69c354 100644 --- a/apps/frontend/src/types/admin/tasks.ts +++ b/apps/frontend/src/types/admin/tasks.ts @@ -1,11 +1,3 @@ -export interface AdminTaskBindingSummary { - totalBindingCount: number - reservedBindingCount: number - consumedBindingCount: number - releasedBindingCount: number - roleKeys: string[] -} - export interface AdminTaskListItem { taskId: number taskNo: string @@ -17,8 +9,8 @@ export interface AdminTaskListItem { deliveryStatus: string resultCode: string resultMessage: string - systemBindingStatus: string - userBindingStatus: string + resourceStatus: string + customerStatus: string loginType: string roleName: string roleId: string @@ -27,7 +19,6 @@ export interface AdminTaskListItem { roleConfirmedAt: string | null redeemedAt: string | null retryCount: number - bindingSummary: AdminTaskBindingSummary lastError: string createdAt: string updatedAt: string diff --git a/apps/frontend/src/views/admin/orders/AdminOrderDetailView.vue b/apps/frontend/src/views/admin/orders/AdminOrderDetailView.vue index f5d45461..09f48685 100644 --- a/apps/frontend/src/views/admin/orders/AdminOrderDetailView.vue +++ b/apps/frontend/src/views/admin/orders/AdminOrderDetailView.vue @@ -41,12 +41,15 @@ function formatSpecSummary(spec: Record) { function resolveResourceStageLabel(status: string) { const normalized = String(status || '').trim() const labelMap: Record = { - system_bound: '资源已准备', - pending_binding: '待准备资源', + resource_ready: '资源已准备', + pending_prepare: '待准备资源', + pending_payment: '待支付', pending_binding_prepare: '待准备资源', retry_pending: '待重试', manual_review: '人工处理', - binding_exception: '资源异常', + resource_exception: '资源异常', + closed: '已关闭', + expired: '已过期', completed: '已完成', not_started: '未开始', } @@ -58,15 +61,14 @@ function resolveCustomerStageLabel(status: string) { const normalized = String(status || '').trim() const labelMap: Record = { not_started: '待打开链接', - waiting_user_claim: '待打开链接', - waiting_binding: '待客户绑定', + waiting_customer: '待客户绑定', link_opened: '已打开链接', - user_binding: '客户绑定中', - binding_confirmed: '已提交', - binding_in_progress: '处理中', - binding_completed: '客户已完成', - binding_exception: '客户步骤异常', - completed: '已完成', + customer_processing: '处理中', + customer_confirmed: '已提交', + customer_completed: '客户已完成', + customer_exception: '客户步骤异常', + closed: '已关闭', + expired: '已过期', } return labelMap[normalized] || normalized || '-' @@ -74,32 +76,30 @@ function resolveCustomerStageLabel(status: string) { function resolveStageTone(status: string): TagType { 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 ( [ - 'waiting_binding', - 'waiting_user_claim', + 'waiting_customer', 'link_opened', - 'user_binding', - 'binding_confirmed', - 'binding_in_progress', + 'customer_confirmed', + 'customer_processing', ].includes(normalized) ) return 'primary' - if (['binding_exception', 'manual_review', 'failed'].includes(normalized)) return 'danger' - if (['pending_binding', 'pending_binding_prepare', 'retry_pending'].includes(normalized)) return 'warning' + if (['resource_exception', 'customer_exception', 'manual_review', 'failed'].includes(normalized)) return 'danger' + if (['pending_prepare', 'pending_binding_prepare', 'retry_pending'].includes(normalized)) return 'warning' return 'info' } -function resolveOrderProgressText(summary: AdminOrderDetail['order']['bindingSummary']) { +function resolveOrderProgressText(summary: AdminOrderDetail['order']['fulfillmentProgress']) { if (!summary.totalTaskCount) return '未生成履约任务' - if (summary.completedBindingTaskCount >= summary.totalTaskCount) return '履约完成' - if (summary.systemBoundTaskCount > 0) return '等待客户绑定' + if (summary.completedTaskCount >= summary.totalTaskCount) return '履约完成' + if (summary.preparedTaskCount > 0) return '等待客户绑定' return '待准备领取链接' } -function resolveOrderProgressTone(summary: AdminOrderDetail['order']['bindingSummary']): TagType { +function resolveOrderProgressTone(summary: AdminOrderDetail['order']['fulfillmentProgress']): TagType { if (!summary.totalTaskCount) return 'warning' - if (summary.completedBindingTaskCount >= summary.totalTaskCount) return 'success' + if (summary.completedTaskCount >= summary.totalTaskCount) return 'success' return 'warning' } @@ -145,12 +145,12 @@ function resolveOrderProgressTone(summary: AdminOrderDetail['order']['bindingSum
履约进度 - - {{ resolveOrderProgressText(detail.order.bindingSummary) }} + + {{ resolveOrderProgressText(detail.order.fulfillmentProgress) }} - 完成 {{ detail.order.bindingSummary.completedBindingTaskCount }}/{{ detail.order.bindingSummary.totalTaskCount }} - · 资源 {{ detail.order.bindingSummary.systemBoundTaskCount }}/{{ detail.order.bindingSummary.totalTaskCount }} + 完成 {{ detail.order.fulfillmentProgress.completedTaskCount }}/{{ detail.order.fulfillmentProgress.totalTaskCount }} + · 资源 {{ detail.order.fulfillmentProgress.preparedTaskCount }}/{{ detail.order.fulfillmentProgress.totalTaskCount }}
@@ -186,15 +186,15 @@ function resolveOrderProgressTone(summary: AdminOrderDetail['order']['bindingSum diff --git a/apps/frontend/src/views/admin/orders/AdminOrdersView.vue b/apps/frontend/src/views/admin/orders/AdminOrdersView.vue index 27dd717a..6d78eb19 100644 --- a/apps/frontend/src/views/admin/orders/AdminOrdersView.vue +++ b/apps/frontend/src/views/admin/orders/AdminOrdersView.vue @@ -50,7 +50,7 @@ const summary = computed(() => ({ completedCount: items.value.filter( (item) => item.taskCount > 0 && - item.completedBindingTaskCount >= item.taskCount && + item.completedTaskCount >= item.taskCount && item.payStatus === "paid" ).length, issueCount: items.value.filter((item) => hasOrderIssue(item)).length, @@ -86,8 +86,8 @@ function resolveItemSummary(item: AdminOrderListItem) { function hasOrderIssue(item: AdminOrderListItem) { return ( ["refunded", "failed", "unpaid"].includes(item.payStatus) || - item.systemBindingStatus === "binding_exception" || - item.userBindingStatus === "binding_exception" + ["resource_exception", "manual_review", "retry_pending"].includes(item.resourceStatus) || + item.customerStatus === "customer_exception" ); } @@ -96,20 +96,20 @@ function resolveFollowUpText(item: AdminOrderListItem) { if (item.payStatus === "refunded") return "已退款"; if (item.payStatus === "failed") return "支付失败"; if ( - item.userBindingStatus === "binding_exception" || - item.systemBindingStatus === "binding_exception" + item.customerStatus === "customer_exception" || + ["resource_exception", "manual_review", "retry_pending"].includes(item.resourceStatus) ) { return "履约异常"; } if (item.taskCount <= 0) return "未生成履约任务"; - if (item.completedBindingTaskCount >= item.taskCount) return "履约完成"; - if (item.systemBoundTaskCount > 0) return "等待客户绑定"; + if (item.completedTaskCount >= item.taskCount) return "履约完成"; + if (item.preparedTaskCount > 0) return "等待客户绑定"; return "待准备领取链接"; } function resolveFollowUpTone(item: AdminOrderListItem) { if (hasOrderIssue(item)) return "danger"; - if (item.taskCount <= 0 || item.completedBindingTaskCount < item.taskCount) + if (item.taskCount <= 0 || item.completedTaskCount < item.taskCount) return "warning"; return "success"; } @@ -117,12 +117,15 @@ function resolveFollowUpTone(item: AdminOrderListItem) { function resolveResourceStageLabel(status: string) { const normalized = String(status || "").trim(); const labelMap: Record = { - system_bound: "资源已准备", - pending_binding: "待准备资源", + resource_ready: "资源已准备", + pending_prepare: "待准备资源", + pending_payment: "待支付", pending_binding_prepare: "待准备资源", retry_pending: "待重试", manual_review: "人工处理", - binding_exception: "资源异常", + resource_exception: "资源异常", + closed: "已关闭", + expired: "已过期", completed: "已完成", not_started: "未开始", }; @@ -134,15 +137,14 @@ function resolveCustomerStageLabel(status: string) { const normalized = String(status || "").trim(); const labelMap: Record = { not_started: "待打开链接", - waiting_user_claim: "待打开链接", - waiting_binding: "待客户绑定", + waiting_customer: "待客户绑定", link_opened: "已打开链接", - user_binding: "客户绑定中", - binding_confirmed: "已提交", - binding_in_progress: "处理中", - binding_completed: "客户已完成", - binding_exception: "客户步骤异常", - completed: "已完成", + customer_processing: "处理中", + customer_confirmed: "已提交", + customer_completed: "客户已完成", + customer_exception: "客户步骤异常", + closed: "已关闭", + expired: "已过期", }; return labelMap[normalized] || normalized || "-"; @@ -315,9 +317,9 @@ onMounted(() => {

- 客户 {{ resolveCustomerStageLabel(row.userBindingStatus) }} - · 资源 {{ resolveResourceStageLabel(row.systemBindingStatus) }} - · {{ row.completedBindingTaskCount }}/{{ row.taskCount }} + 客户 {{ resolveCustomerStageLabel(row.customerStatus) }} + · 资源 {{ resolveResourceStageLabel(row.resourceStatus) }} + · {{ row.completedTaskCount }}/{{ row.taskCount }}

diff --git a/apps/frontend/src/views/admin/tasks/AdminTasksView.vue b/apps/frontend/src/views/admin/tasks/AdminTasksView.vue index 1752cb17..cb952bc3 100644 --- a/apps/frontend/src/views/admin/tasks/AdminTasksView.vue +++ b/apps/frontend/src/views/admin/tasks/AdminTasksView.vue @@ -129,12 +129,15 @@ function resetFilters() { function resolveResourceStageLabel(status: string) { const normalized = String(status || "").trim(); const labelMap: Record = { - system_bound: "资源已准备", - pending_binding: "待准备资源", + resource_ready: "资源已准备", + pending_prepare: "待准备资源", + pending_payment: "待支付", pending_binding_prepare: "待准备资源", retry_pending: "待重试", manual_review: "人工处理", - binding_exception: "资源异常", + resource_exception: "资源异常", + closed: "已关闭", + expired: "已过期", completed: "已完成", not_started: "未开始", }; @@ -146,15 +149,14 @@ function resolveCustomerStageLabel(status: string) { const normalized = String(status || "").trim(); const labelMap: Record = { not_started: "待打开链接", - waiting_user_claim: "待打开链接", - waiting_binding: "待客户绑定", + waiting_customer: "待客户绑定", link_opened: "已打开链接", - user_binding: "客户绑定中", - binding_confirmed: "已提交", - binding_in_progress: "处理中", - binding_completed: "客户已完成", - binding_exception: "客户步骤异常", - completed: "已完成", + customer_processing: "处理中", + customer_confirmed: "已提交", + customer_completed: "客户已完成", + customer_exception: "客户步骤异常", + closed: "已关闭", + expired: "已过期", }; return labelMap[normalized] || normalized || "-"; @@ -164,8 +166,8 @@ function resolveTaskMainText(item: AdminTaskListItem) { if (item.lastError) return "需要关注"; if (["failed", "manual_review", "retry_pending"].includes(item.status)) return "待处理"; if (["redeemed", "completed", "closed"].includes(item.status)) return "已完成"; - if (item.userBindingStatus === "waiting_binding") return "等待客户绑定"; - if (item.systemBindingStatus === "system_bound") return "资源已准备"; + if (item.customerStatus === "waiting_customer") return "等待客户绑定"; + if (item.resourceStatus === "resource_ready") return "资源已准备"; if (item.status === "pending_binding_prepare") return "待准备资源"; return item.status || "处理中"; } @@ -332,8 +334,8 @@ onMounted(() => { {{ resolveTaskMainText(row) }}

- 客户 {{ resolveCustomerStageLabel(row.userBindingStatus) }} - · 资源 {{ resolveResourceStageLabel(row.systemBindingStatus) }} + 客户 {{ resolveCustomerStageLabel(row.customerStatus) }} + · 资源 {{ resolveResourceStageLabel(row.resourceStatus) }}

角色 {{ row.roleName || "-" }} / {{ row.roleId || "-" }} diff --git a/apps/frontend/src/views/admin/tasks/components/AdminTaskHeroSection.vue b/apps/frontend/src/views/admin/tasks/components/AdminTaskHeroSection.vue index 65e9384c..77cb98ab 100644 --- a/apps/frontend/src/views/admin/tasks/components/AdminTaskHeroSection.vue +++ b/apps/frontend/src/views/admin/tasks/components/AdminTaskHeroSection.vue @@ -17,12 +17,15 @@ defineProps() function resolveResourceStageLabel(status: string) { const normalized = String(status || '').trim() const labelMap: Record = { - system_bound: '资源已准备', - pending_binding: '待准备资源', + resource_ready: '资源已准备', + pending_prepare: '待准备资源', + pending_payment: '待支付', pending_binding_prepare: '待准备资源', retry_pending: '待重试', manual_review: '人工处理', - binding_exception: '资源异常', + resource_exception: '资源异常', + closed: '已关闭', + expired: '已过期', completed: '已完成', not_started: '未开始', } @@ -34,15 +37,14 @@ function resolveCustomerStageLabel(status: string) { const normalized = String(status || '').trim() const labelMap: Record = { not_started: '待打开链接', - waiting_user_claim: '待打开链接', - waiting_binding: '待客户绑定', + waiting_customer: '待客户绑定', link_opened: '已打开链接', - user_binding: '客户绑定中', - binding_confirmed: '已提交', - binding_in_progress: '处理中', - binding_completed: '客户已完成', - binding_exception: '客户步骤异常', - completed: '已完成', + customer_processing: '处理中', + customer_confirmed: '已提交', + customer_completed: '客户已完成', + customer_exception: '客户步骤异常', + closed: '已关闭', + expired: '已过期', } return labelMap[normalized] || normalized || '-' @@ -85,8 +87,8 @@ function resolveCustomerStageLabel(status: string) {

- 资源 {{ resolveResourceStageLabel(detail.task.systemBindingStatus) }} - 客户 {{ resolveCustomerStageLabel(detail.task.userBindingStatus) }} + 资源 {{ resolveResourceStageLabel(detail.task.resourceStatus) }} + 客户 {{ resolveCustomerStageLabel(detail.task.customerStatus) }} 最近异常 {{ detail.task.lastError }}