From 2c7464b1ebb1cfcdaa854e319f97ae1822e25877 Mon Sep 17 00:00:00 2001 From: yml Date: Mon, 25 May 2026 23:07:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=BA=93=E5=AD=98=E5=92=8C?= =?UTF-8?q?=E6=97=A7=E5=9B=9E=E8=B0=83=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/backend/README.md | 2 +- apps/backend/src/db/migrations/001_init.sql | 76 --- .../src/repositories/admin-user-repo.ts | 48 +- .../src/repositories/inventory-repo.ts | 497 ------------------ .../src/repositories/order-item-repo.test.ts | 2 +- .../task-inventory-binding-repo.ts | 105 ---- apps/backend/src/repositories/task-repo.ts | 62 +-- .../src/repositories/webhook-event-repo.ts | 181 ------- apps/backend/src/routes/admin.ts | 4 - apps/backend/src/routes/admin/inventory.ts | 149 ------ apps/backend/src/routes/admin/users.ts | 23 - .../src/routes/admin/webhook-events.ts | 76 --- apps/backend/src/routes/open-91.ts | 14 +- .../src/services/admin/admin-auth-service.ts | 28 - .../services/admin/admin-dashboard-service.ts | 13 +- .../admin/admin-inventory-read-helpers.ts | 46 -- .../admin/admin-order-read-helpers.ts | 5 +- .../src/services/admin/admin-read-service.ts | 164 +----- .../admin/admin-read-shared-helpers.ts | 48 -- .../services/admin/admin-task-read-helpers.ts | 128 +---- .../admin/admin-webhook-read-helpers.test.ts | 14 - .../admin/admin-webhook-read-helpers.ts | 235 --------- .../src/services/admin/write/inventory.ts | 213 -------- .../admin/write/kuaishou-cloud-actions.ts | 1 - .../src/services/admin/write/shared.ts | 7 - .../claim/kuaishou-cloud-claim-context.ts | 8 +- .../kuaishou-cloud-task-service.ts | 1 - .../notification/domain-notifications.ts | 4 +- .../services/open-91/order-create-service.ts | 6 +- .../services/open-91/order-query-service.ts | 6 +- .../order/delivery-task-service.test.ts | 49 +- .../services/order/delivery-task-service.ts | 113 +--- .../services/order/inventory-service.test.ts | 110 ---- .../src/services/order/inventory-service.ts | 122 ----- .../src/services/order/order-service.test.ts | 14 +- .../src/services/order/order-service.ts | 16 +- apps/backend/src/types/admin-read-inputs.ts | 31 -- apps/backend/src/types/admin-read-models.ts | 80 --- apps/backend/src/types/admin-route-inputs.ts | 16 - apps/backend/src/types/admin-write-inputs.ts | 33 -- apps/backend/src/types/admin-write-models.ts | 33 -- apps/backend/src/types/repository-inputs.ts | 68 --- apps/backend/src/types/repository-rows.ts | 91 ---- apps/backend/src/utils/logger.test.ts | 10 +- apps/backend/src/utils/logger.ts | 10 +- .../src/components/admin/AdminStatusTag.vue | 2 - apps/frontend/src/router/index.ts | 19 - apps/frontend/src/services/admin/index.ts | 2 - apps/frontend/src/services/admin/inventory.ts | 51 -- apps/frontend/src/services/admin/tasks.ts | 14 - apps/frontend/src/services/admin/users.ts | 10 - .../src/services/admin/webhook-events.ts | 22 - apps/frontend/src/styles/admin-list-pages.css | 3 +- apps/frontend/src/types/admin/auth.ts | 2 - apps/frontend/src/types/admin/dashboard.ts | 1 - apps/frontend/src/types/admin/index.ts | 10 - apps/frontend/src/types/admin/inventory.ts | 27 - apps/frontend/src/types/admin/orders.ts | 13 - apps/frontend/src/types/admin/tasks.ts | 33 -- apps/frontend/src/types/admin/users.ts | 1 - .../src/types/admin/webhook-events.ts | 60 --- apps/frontend/src/types/claim.ts | 2 - apps/frontend/src/utils/admin-display.ts | 32 -- apps/frontend/src/utils/admin-options.ts | 39 -- apps/frontend/src/utils/admin-webhook.ts | 22 - .../admin/dashboard/AdminDashboardView.vue | 1 - .../AdminKuaishouCloudOverviewSection.vue | 2 +- .../admin/inventory/AdminInventoryView.vue | 146 ----- .../components/AdminInventoryEntryCard.vue | 296 ----------- .../components/AdminInventoryFilterCard.vue | 107 ---- .../components/AdminInventoryPoolGrid.vue | 242 --------- .../components/AdminInventoryTable.vue | 222 -------- .../admin/inventory/composables/types.ts | 21 - .../composables/useAdminInventory.ts | 429 --------------- .../src/views/admin/layout/AdminLayout.vue | 11 - .../admin/orders/AdminOrderDetailView.vue | 59 --- .../views/admin/tasks/AdminTaskDetailView.vue | 16 - .../src/views/admin/tasks/AdminTasksView.vue | 40 +- .../tasks/components/AdminTaskInfoSection.vue | 13 +- .../AdminTaskInventoryBindingsSection.vue | 98 ---- .../AdminTaskRedeemResolutionSection.vue | 6 +- .../src/views/admin/users/AdminUsersView.vue | 4 - .../users/components/AdminUsersTable.vue | 31 -- .../admin/users/composables/useAdminUsers.ts | 42 -- .../AdminWebhookEventDetailView.vue | 354 ------------- .../webhook-events/AdminWebhookEventsView.vue | 415 --------------- docs/91卡券/6.开发设计.md | 6 +- 87 files changed, 94 insertions(+), 5794 deletions(-) delete mode 100644 apps/backend/src/repositories/inventory-repo.ts delete mode 100644 apps/backend/src/repositories/task-inventory-binding-repo.ts delete mode 100644 apps/backend/src/repositories/webhook-event-repo.ts delete mode 100644 apps/backend/src/routes/admin/inventory.ts delete mode 100644 apps/backend/src/routes/admin/webhook-events.ts delete mode 100644 apps/backend/src/services/admin/admin-inventory-read-helpers.ts delete mode 100644 apps/backend/src/services/admin/admin-webhook-read-helpers.test.ts delete mode 100644 apps/backend/src/services/admin/admin-webhook-read-helpers.ts delete mode 100644 apps/backend/src/services/admin/write/inventory.ts delete mode 100644 apps/backend/src/services/order/inventory-service.test.ts delete mode 100644 apps/backend/src/services/order/inventory-service.ts delete mode 100644 apps/frontend/src/services/admin/inventory.ts delete mode 100644 apps/frontend/src/services/admin/webhook-events.ts delete mode 100644 apps/frontend/src/types/admin/inventory.ts delete mode 100644 apps/frontend/src/types/admin/webhook-events.ts delete mode 100644 apps/frontend/src/utils/admin-webhook.ts delete mode 100644 apps/frontend/src/views/admin/inventory/AdminInventoryView.vue delete mode 100644 apps/frontend/src/views/admin/inventory/components/AdminInventoryEntryCard.vue delete mode 100644 apps/frontend/src/views/admin/inventory/components/AdminInventoryFilterCard.vue delete mode 100644 apps/frontend/src/views/admin/inventory/components/AdminInventoryPoolGrid.vue delete mode 100644 apps/frontend/src/views/admin/inventory/components/AdminInventoryTable.vue delete mode 100644 apps/frontend/src/views/admin/inventory/composables/types.ts delete mode 100644 apps/frontend/src/views/admin/inventory/composables/useAdminInventory.ts delete mode 100644 apps/frontend/src/views/admin/tasks/components/AdminTaskInventoryBindingsSection.vue delete mode 100644 apps/frontend/src/views/admin/webhook-events/AdminWebhookEventDetailView.vue delete mode 100644 apps/frontend/src/views/admin/webhook-events/AdminWebhookEventsView.vue diff --git a/apps/backend/README.md b/apps/backend/README.md index 76179c76..ab710e58 100644 --- a/apps/backend/README.md +++ b/apps/backend/README.md @@ -89,7 +89,7 @@ npm test 运行日志默认保存在: - `data/logs/app-YYYY-MM-DD.log` -- `data/logs/webhook-YYYY-MM-DD.log` +- `data/logs/integration-YYYY-MM-DD.log` 日志按天切分,默认自动清理 7 天前的旧日志。成功的 `/health`、`/health/live`、`/health/ready` 探活请求默认不写 access log。 diff --git a/apps/backend/src/db/migrations/001_init.sql b/apps/backend/src/db/migrations/001_init.sql index c3389371..7f6bc9f5 100644 --- a/apps/backend/src/db/migrations/001_init.sql +++ b/apps/backend/src/db/migrations/001_init.sql @@ -135,42 +135,6 @@ CREATE TABLE IF NOT EXISTS order_items ( CREATE INDEX IF NOT EXISTS idx_order_items_order_id ON order_items(order_id); CREATE INDEX IF NOT EXISTS idx_order_items_sku_code ON order_items(sku_code); -CREATE TABLE IF NOT EXISTS inventory_items ( - id BIGSERIAL PRIMARY KEY, - batch_no TEXT NOT NULL DEFAULT '', - sku_code TEXT NOT NULL, - credential_type TEXT NOT NULL, - display_value TEXT NOT NULL DEFAULT '', - payload_json JSONB NOT NULL DEFAULT '{}'::jsonb, - source_type TEXT NOT NULL DEFAULT 'static_import', - status TEXT NOT NULL DEFAULT 'available', - invalid_reason TEXT NOT NULL DEFAULT '', - inventory_group_code TEXT NOT NULL DEFAULT '', - metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb, - consumed_at TIMESTAMPTZ, - created_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ NOT NULL -); - -CREATE UNIQUE INDEX IF NOT EXISTS idx_inventory_items_unique_payload - ON inventory_items(sku_code, credential_type, md5(payload_json::text)); -CREATE INDEX IF NOT EXISTS idx_inventory_items_pool - ON inventory_items(sku_code, credential_type, status, id); -CREATE INDEX IF NOT EXISTS idx_inventory_items_group_lookup - ON inventory_items(sku_code, credential_type, inventory_group_code, status, id); - -CREATE TABLE IF NOT EXISTS admin_user_inventory_group_bindings ( - id BIGSERIAL PRIMARY KEY, - admin_user_id BIGINT NOT NULL REFERENCES admin_users(id) ON DELETE CASCADE, - inventory_group_code TEXT NOT NULL, - created_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ NOT NULL, - UNIQUE(admin_user_id, inventory_group_code) -); - -CREATE INDEX IF NOT EXISTS idx_admin_user_inventory_group_bindings_user_id - ON admin_user_inventory_group_bindings(admin_user_id, inventory_group_code); - CREATE TABLE IF NOT EXISTS fulfillment_tasks ( id BIGSERIAL PRIMARY KEY, order_id BIGINT NOT NULL REFERENCES orders(id) ON DELETE CASCADE, @@ -185,7 +149,6 @@ CREATE TABLE IF NOT EXISTS fulfillment_tasks ( profile_id BIGINT NOT NULL REFERENCES fulfillment_profiles(id) ON DELETE RESTRICT, executor_key TEXT NOT NULL, task_status TEXT NOT NULL DEFAULT 'pending_payment', - inventory_status TEXT NOT NULL DEFAULT 'pending', delivery_status TEXT NOT NULL DEFAULT 'pending', result_code TEXT NOT NULL DEFAULT '', result_message TEXT NOT NULL DEFAULT '', @@ -224,24 +187,6 @@ CREATE TABLE IF NOT EXISTS claim_tokens ( CREATE INDEX IF NOT EXISTS idx_claim_tokens_status ON claim_tokens(status, expired_at); -CREATE TABLE IF NOT EXISTS task_inventory_bindings ( - id BIGSERIAL PRIMARY KEY, - task_id BIGINT NOT NULL REFERENCES fulfillment_tasks(id) ON DELETE CASCADE, - inventory_item_id BIGINT NOT NULL REFERENCES inventory_items(id) ON DELETE RESTRICT, - role_key TEXT NOT NULL, - quantity INTEGER NOT NULL DEFAULT 1, - binding_status TEXT NOT NULL DEFAULT 'reserved', - consumed_at TIMESTAMPTZ, - released_at TIMESTAMPTZ, - metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb, - created_at TIMESTAMPTZ NOT NULL, - updated_at TIMESTAMPTZ NOT NULL, - UNIQUE(task_id, role_key, inventory_item_id) -); - -CREATE INDEX IF NOT EXISTS idx_task_inventory_bindings_task ON task_inventory_bindings(task_id); -CREATE INDEX IF NOT EXISTS idx_task_inventory_bindings_inventory ON task_inventory_bindings(inventory_item_id); - CREATE TABLE IF NOT EXISTS task_events ( id BIGSERIAL PRIMARY KEY, task_id BIGINT NOT NULL REFERENCES fulfillment_tasks(id) ON DELETE CASCADE, @@ -268,24 +213,3 @@ CREATE TABLE IF NOT EXISTS task_runtime_contexts ( created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL ); - -CREATE TABLE IF NOT EXISTS webhook_events ( - id BIGSERIAL PRIMARY KEY, - provider TEXT NOT NULL DEFAULT '', - platform TEXT NOT NULL DEFAULT '', - shop_id TEXT NOT NULL DEFAULT '', - shop_name TEXT NOT NULL DEFAULT '', - event_type TEXT NOT NULL, - event_key TEXT NOT NULL, - signature_valid BOOLEAN NOT NULL DEFAULT FALSE, - headers_json JSONB NOT NULL DEFAULT '{}'::jsonb, - query_json JSONB NOT NULL DEFAULT '{}'::jsonb, - body_json JSONB NOT NULL DEFAULT '{}'::jsonb, - processed BOOLEAN NOT NULL DEFAULT FALSE, - process_error TEXT NOT NULL DEFAULT '', - related_order_id BIGINT REFERENCES orders(id) ON DELETE SET NULL, - created_at TIMESTAMPTZ NOT NULL -); - -CREATE INDEX IF NOT EXISTS idx_webhook_events_lookup ON webhook_events(provider, platform, shop_id, event_key); -CREATE INDEX IF NOT EXISTS idx_webhook_events_processed ON webhook_events(processed, created_at DESC); diff --git a/apps/backend/src/repositories/admin-user-repo.ts b/apps/backend/src/repositories/admin-user-repo.ts index db686581..22044d99 100644 --- a/apps/backend/src/repositories/admin-user-repo.ts +++ b/apps/backend/src/repositories/admin-user-repo.ts @@ -1,4 +1,4 @@ -import { query, withTransaction } from '../db/client.js' +import { query } from '../db/client.js' type AdminUserRow = { id: number @@ -8,7 +8,6 @@ type AdminUserRow = { status: string created_at: string updated_at: string - inventory_group_codes: string[] } type AdminUserCreateInput = { @@ -39,15 +38,8 @@ type AdminUserListResult = { } const ADMIN_USER_SELECT = ` - SELECT - au.*, - COALESCE(bindings.inventory_group_codes, ARRAY[]::text[]) AS inventory_group_codes + SELECT au.* FROM admin_users au - LEFT JOIN LATERAL ( - SELECT ARRAY_AGG(auigb.inventory_group_code ORDER BY auigb.inventory_group_code) AS inventory_group_codes - FROM admin_user_inventory_group_bindings auigb - WHERE auigb.admin_user_id = au.id - ) bindings ON TRUE ` export async function getAdminUserById(userId: number | string): Promise { @@ -183,39 +175,3 @@ export async function countActiveAdminUsers(): Promise { ) return Number(result.rows[0]?.total || 0) } - -export async function replaceAdminUserInventoryGroupBindings( - userId: number | string, - inventoryGroupCodes: unknown[] = [], - timestamp: string, -): Promise { - const normalizedUserId = Number(userId) - const normalizedCodes = Array.from(new Set((Array.isArray(inventoryGroupCodes) ? inventoryGroupCodes : []) - .map((value) => String(value || '').trim()) - .filter(Boolean))) - - await withTransaction(async (client) => { - await client.query( - 'DELETE FROM admin_user_inventory_group_bindings WHERE admin_user_id = $1', - [normalizedUserId], - ) - - for (const inventoryGroupCode of normalizedCodes) { - await client.query( - ` - INSERT INTO admin_user_inventory_group_bindings ( - admin_user_id, - inventory_group_code, - created_at, - updated_at - ) VALUES ($1, $2, $3, $4) - ON CONFLICT (admin_user_id, inventory_group_code) DO UPDATE - SET updated_at = EXCLUDED.updated_at - `, - [normalizedUserId, inventoryGroupCode, timestamp, timestamp], - ) - } - }) - - return getAdminUserById(normalizedUserId) -} diff --git a/apps/backend/src/repositories/inventory-repo.ts b/apps/backend/src/repositories/inventory-repo.ts deleted file mode 100644 index c995c1d3..00000000 --- a/apps/backend/src/repositories/inventory-repo.ts +++ /dev/null @@ -1,497 +0,0 @@ -import { query, withTransaction } from '../db/client.js' -import type { - InventoryCreateItemInput, - InventoryListQueryInput, - InventorySkuSuggestionQueryInput, -} from '../types/repository-inputs.js' -import type { - InventoryItemRow, - InventoryListQueryResult, - InventorySkuSuggestionRow, -} from '../types/repository-rows.js' - -type InventoryGroupCodesInput = string | string[] | null | undefined - -type InventoryGroupListInput = { - keyword?: string - limit?: number | string -} - -const INVENTORY_ITEM_SELECT = ` - SELECT - ii.id, - ii.sku_code, - ii.batch_no, - ii.credential_type, - ii.inventory_group_code, - ii.display_value, - ii.status, - ii.invalid_reason, - ii.consumed_at AS delivered_at, - ii.created_at, - ii.updated_at, - tib.task_id AS reserved_by_task_id, - ft.task_no AS reserved_by_task_no, - ft.platform_order_id - FROM inventory_items ii - LEFT JOIN LATERAL ( - SELECT * - FROM task_inventory_bindings - WHERE inventory_item_id = ii.id AND binding_status IN ('reserved', 'consumed') - ORDER BY id DESC - LIMIT 1 - ) tib ON TRUE - LEFT JOIN fulfillment_tasks ft ON ft.id = tib.task_id -` - -export async function findFirstAvailableInventoryItemBySkuCode( - skuCode: string, - credentialType = 'claim_code', - inventoryGroupCodes: InventoryGroupCodesInput = null, -): Promise { - const normalizedInventoryGroupCodes = normalizeInventoryGroupCodes(inventoryGroupCodes) - if (normalizedInventoryGroupCodes && normalizedInventoryGroupCodes.length === 0) { - return null - } - - const params: unknown[] = [skuCode, credentialType] - const filters = [ - 'ii.sku_code = $1', - 'ii.credential_type = $2', - `ii.status = 'available'`, - ] - - if (normalizedInventoryGroupCodes) { - params.push(normalizedInventoryGroupCodes) - filters.push(`ii.inventory_group_code = ANY($${params.length}::text[])`) - } - - const result = await query( - `${INVENTORY_ITEM_SELECT} - WHERE ${filters.join(' AND ')} - ORDER BY ii.id ASC - LIMIT 1`, - params, - ) - - return result.rows[0] || null -} - -export async function assignReservedInventoryItem( - inventoryItemId: number | string, - taskId: number | string, - updatedAt: string, - roleKey = 'primary_code', -): Promise { - return withTransaction(async (client) => { - const inventoryResult = await client.query( - ` - UPDATE inventory_items - SET status = 'reserved', updated_at = $1 - WHERE id = $2 AND status = 'available' - RETURNING id - `, - [updatedAt, Number(inventoryItemId)], - ) - - if (!inventoryResult.rows[0]) { - return getInventoryItemById(inventoryItemId) - } - - await client.query( - ` - INSERT INTO task_inventory_bindings ( - task_id, - inventory_item_id, - role_key, - quantity, - binding_status, - metadata_json, - created_at, - updated_at - ) VALUES ($1, $2, $3, 1, 'reserved', '{}'::jsonb, $4, $4) - ON CONFLICT (task_id, role_key, inventory_item_id) DO UPDATE - SET binding_status = 'reserved', updated_at = EXCLUDED.updated_at, released_at = NULL - `, - [Number(taskId), Number(inventoryItemId), roleKey, updatedAt], - ) - - return getInventoryItemById(inventoryItemId) - }) -} - -export async function getInventoryItemById(inventoryItemId: number | string): Promise { - const result = await query( - `${INVENTORY_ITEM_SELECT} - WHERE ii.id = $1 - LIMIT 1`, - [Number(inventoryItemId)], - ) - - return result.rows[0] || null -} - -export async function markInventoryItemDelivered( - inventoryItemId: number | string, - deliveredAt: string, -): Promise { - return withTransaction(async (client) => { - await client.query( - ` - UPDATE inventory_items - SET status = 'consumed', consumed_at = $1, updated_at = $1 - WHERE id = $2 - `, - [deliveredAt, Number(inventoryItemId)], - ) - - await client.query( - ` - UPDATE task_inventory_bindings - SET binding_status = 'consumed', consumed_at = $1, updated_at = $1 - WHERE inventory_item_id = $2 AND binding_status = 'reserved' - `, - [deliveredAt, Number(inventoryItemId)], - ) - - return getInventoryItemById(inventoryItemId) - }) -} - -export async function markInventoryItemConsumed( - inventoryItemId: number | string, - reason: string, - consumedAt: string, -): Promise { - return withTransaction(async (client) => { - await client.query( - ` - UPDATE inventory_items - SET - status = 'consumed', - invalid_reason = $1, - consumed_at = $2, - updated_at = $2 - WHERE id = $3 - `, - [String(reason || '').trim(), consumedAt, Number(inventoryItemId)], - ) - - await client.query( - ` - UPDATE task_inventory_bindings - SET binding_status = 'consumed', consumed_at = $1, updated_at = $1 - WHERE inventory_item_id = $2 AND binding_status = 'reserved' - `, - [consumedAt, Number(inventoryItemId)], - ) - - return getInventoryItemById(inventoryItemId) - }) -} - -export async function listInventoryItems({ - page = 1, - pageSize = 20, - skuCode = '', - credentialType = '', - status = '', - batchNo = '', - inventoryGroupCode = '', - allowedInventoryGroupCodes = null, -}: InventoryListQueryInput = {}): Promise { - const normalizedAllowedInventoryGroupCodes = normalizeInventoryGroupCodes(allowedInventoryGroupCodes) - if (normalizedAllowedInventoryGroupCodes && normalizedAllowedInventoryGroupCodes.length === 0) { - return { - items: [], - total: 0, - } - } - - const offset = (page - 1) * pageSize - const filters: string[] = [] - const params: unknown[] = [] - - if (skuCode) { - params.push(skuCode) - filters.push(`ii.sku_code = $${params.length}`) - } - - if (status) { - params.push(status === 'delivered' ? 'consumed' : status) - filters.push(`ii.status = $${params.length}`) - } - - if (credentialType) { - params.push(credentialType) - filters.push(`ii.credential_type = $${params.length}`) - } - - if (batchNo) { - params.push(`%${batchNo}%`) - filters.push(`ii.batch_no ILIKE $${params.length}`) - } - - if (inventoryGroupCode) { - params.push(inventoryGroupCode) - filters.push(`ii.inventory_group_code = $${params.length}`) - } - - if (normalizedAllowedInventoryGroupCodes) { - params.push(normalizedAllowedInventoryGroupCodes) - filters.push(`ii.inventory_group_code = ANY($${params.length}::text[])`) - } - - const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '' - const totalResult = await query<{ [column: string]: unknown, total: number }>( - `SELECT COUNT(*)::int AS total FROM inventory_items ii ${whereClause}`, - params, - ) - - params.push(pageSize) - params.push(offset) - const itemsResult = await query( - `${INVENTORY_ITEM_SELECT} - ${whereClause} - ORDER BY ii.id DESC - LIMIT $${params.length - 1} OFFSET $${params.length}`, - params, - ) - - return { - items: itemsResult.rows, - total: Number(totalResult.rows[0]?.total || 0), - } -} - -export async function listInventorySkuSuggestions({ - credentialType = '', - keyword = '', - limit = 50, - inventoryGroupCode = '', - allowedInventoryGroupCodes = null, -}: InventorySkuSuggestionQueryInput = {}): Promise { - const normalizedAllowedInventoryGroupCodes = normalizeInventoryGroupCodes(allowedInventoryGroupCodes) - if (normalizedAllowedInventoryGroupCodes && normalizedAllowedInventoryGroupCodes.length === 0) { - return [] - } - - const filters: string[] = [] - const params: unknown[] = [] - - if (credentialType) { - params.push(credentialType) - filters.push(`credential_type = $${params.length}`) - } - - if (keyword) { - params.push(`%${keyword}%`) - filters.push(`sku_code ILIKE $${params.length}`) - } - - if (inventoryGroupCode) { - params.push(inventoryGroupCode) - filters.push(`inventory_group_code = $${params.length}`) - } - - if (normalizedAllowedInventoryGroupCodes) { - params.push(normalizedAllowedInventoryGroupCodes) - filters.push(`inventory_group_code = ANY($${params.length}::text[])`) - } - - const normalizedLimit = Number.isFinite(Number(limit)) - ? Math.max(1, Math.min(Number(limit), 100)) - : 50 - - params.push(normalizedLimit) - const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '' - const result = await query( - ` - SELECT - sku_code, - credential_type, - inventory_group_code, - COUNT(*)::int AS total_count, - COUNT(*) FILTER (WHERE status = 'available')::int AS available_count, - MAX(updated_at) AS latest_updated_at - FROM inventory_items - ${whereClause} - GROUP BY sku_code, credential_type, inventory_group_code - ORDER BY - COUNT(*) FILTER (WHERE status = 'available') DESC, - COUNT(*) DESC, - MAX(updated_at) DESC, - sku_code ASC, - inventory_group_code ASC - LIMIT $${params.length} - `, - params, - ) - - return result.rows -} - -export async function createInventoryItems(rows: InventoryCreateItemInput[]): Promise { - let created = 0 - - for (const row of rows) { - const payloadJson = JSON.stringify(row.payload || { code: row.displayValue }) - const displayValue = String(row.displayValue || '').trim() - const inventoryGroupCode = String(row.inventoryGroupCode || '').trim() - const result = await query<{ [column: string]: unknown, id: number }>( - ` - INSERT INTO inventory_items ( - batch_no, - sku_code, - credential_type, - inventory_group_code, - display_value, - payload_json, - source_type, - status, - invalid_reason, - metadata_json, - created_at, - updated_at - ) VALUES ($1, $2, $3, $4, $5, $6::jsonb, 'static_import', 'available', '', '{}'::jsonb, $7, $8) - ON CONFLICT DO NOTHING - RETURNING id - `, - [ - row.batchNo || '', - row.skuCode, - row.credentialType || 'claim_code', - inventoryGroupCode, - displayValue, - payloadJson, - row.createdAt, - row.updatedAt, - ], - ) - - if (result.rows[0]?.id) { - created += 1 - } - } - - return created -} - -export async function listInventoryGroupCodes({ - keyword = '', - limit = 100, -}: InventoryGroupListInput = {}): Promise { - const params: unknown[] = [] - const filters = [`inventory_group_code <> ''`] - - if (keyword) { - params.push(`%${String(keyword || '').trim()}%`) - filters.push(`inventory_group_code ILIKE $${params.length}`) - } - - const normalizedLimit = Number.isFinite(Number(limit)) - ? Math.max(1, Math.min(Number(limit), 200)) - : 100 - - params.push(normalizedLimit) - const result = await query<{ [column: string]: unknown, inventory_group_code: string }>( - ` - SELECT inventory_group_code - FROM inventory_items - WHERE ${filters.join(' AND ')} - GROUP BY inventory_group_code - ORDER BY inventory_group_code ASC - LIMIT $${params.length} - `, - params, - ) - - return result.rows.map((row) => String(row.inventory_group_code || '').trim()).filter(Boolean) -} - -function normalizeInventoryGroupCodes(inventoryGroupCodes: InventoryGroupCodesInput): string[] | null { - if (inventoryGroupCodes == null) { - return null - } - - return Array.from(new Set((Array.isArray(inventoryGroupCodes) ? inventoryGroupCodes : [inventoryGroupCodes]) - .map((value) => String(value || '').trim()) - .filter(Boolean))) -} - -export async function releaseReservedInventoryItem( - inventoryItemId: number | string, - updatedAt: string, -): Promise { - return withTransaction(async (client) => { - await client.query( - ` - UPDATE task_inventory_bindings - SET binding_status = 'released', released_at = $1, updated_at = $1 - WHERE inventory_item_id = $2 AND binding_status = 'reserved' - `, - [updatedAt, Number(inventoryItemId)], - ) - - await client.query( - ` - UPDATE inventory_items - SET status = 'available', updated_at = $1 - WHERE id = $2 AND status = 'reserved' - `, - [updatedAt, Number(inventoryItemId)], - ) - - return getInventoryItemById(inventoryItemId) - }) -} - -export async function invalidateInventoryItem( - inventoryItemId: number | string, - invalidReason: string, - updatedAt: string, -): Promise { - const result = await query<{ [column: string]: unknown, id: number }>( - ` - UPDATE inventory_items - SET status = 'invalid', invalid_reason = $1, updated_at = $2 - WHERE id = $3 AND status = 'available' - RETURNING id - `, - [invalidReason, updatedAt, Number(inventoryItemId)], - ) - - if (!result.rows[0]) { - return getInventoryItemById(inventoryItemId) - } - - return getInventoryItemById(inventoryItemId) -} - -export async function invalidateReservedInventoryItem( - inventoryItemId: number | string, - invalidReason: string, - updatedAt: string, -): Promise { - return withTransaction(async (client) => { - await client.query( - ` - UPDATE task_inventory_bindings - SET binding_status = 'released', released_at = $1, updated_at = $1 - WHERE inventory_item_id = $2 AND binding_status = 'reserved' - `, - [updatedAt, Number(inventoryItemId)], - ) - - await client.query( - ` - UPDATE inventory_items - SET status = 'invalid', invalid_reason = $1, updated_at = $2 - WHERE id = $3 - `, - [String(invalidReason || '').trim(), updatedAt, Number(inventoryItemId)], - ) - - return getInventoryItemById(inventoryItemId) - }) -} diff --git a/apps/backend/src/repositories/order-item-repo.test.ts b/apps/backend/src/repositories/order-item-repo.test.ts index f9d5de1c..4dbfc258 100644 --- a/apps/backend/src/repositories/order-item-repo.test.ts +++ b/apps/backend/src/repositories/order-item-repo.test.ts @@ -3,7 +3,7 @@ import assert from 'node:assert/strict' import { resolveOrderItemSyncPlan } from './order-item-repo.js' -test('resolveOrderItemSyncPlan preserves matching order item ids across repeated webhook syncs', () => { +test('resolveOrderItemSyncPlan preserves matching order item ids across repeated source syncs', () => { const existingItems = [ { id: 15, diff --git a/apps/backend/src/repositories/task-inventory-binding-repo.ts b/apps/backend/src/repositories/task-inventory-binding-repo.ts deleted file mode 100644 index 497f3710..00000000 --- a/apps/backend/src/repositories/task-inventory-binding-repo.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { query } from '../db/client.js' -import type { - TaskInventoryBindingRow, - TaskInventoryBindingSummaryRow, -} from '../types/repository-rows.js' - -export async function listTaskInventoryBindingsByTaskId( - taskId: number | string, -): Promise { - const result = await query( - ` - SELECT - tib.id, - tib.task_id, - tib.inventory_item_id, - tib.role_key, - tib.quantity, - tib.binding_status, - tib.consumed_at, - tib.released_at, - tib.metadata_json, - tib.created_at, - tib.updated_at, - ii.sku_code, - ii.batch_no, - ii.credential_type, - ii.inventory_group_code, - ii.display_value, - ii.status AS inventory_item_status, - ii.invalid_reason - FROM task_inventory_bindings tib - JOIN inventory_items ii ON ii.id = tib.inventory_item_id - WHERE tib.task_id = $1 - ORDER BY tib.id ASC - `, - [Number(taskId)], - ) - - return result.rows -} - -export async function getTaskInventoryBindingById( - bindingId: number | string, -): Promise { - const result = await query( - ` - SELECT - tib.id, - tib.task_id, - tib.inventory_item_id, - tib.role_key, - tib.quantity, - tib.binding_status, - tib.consumed_at, - tib.released_at, - tib.metadata_json, - tib.created_at, - tib.updated_at, - ii.sku_code, - ii.batch_no, - ii.credential_type, - ii.inventory_group_code, - ii.display_value, - ii.status AS inventory_item_status, - ii.invalid_reason - FROM task_inventory_bindings tib - JOIN inventory_items ii ON ii.id = tib.inventory_item_id - WHERE tib.id = $1 - LIMIT 1 - `, - [Number(bindingId)], - ) - - return result.rows[0] || null -} - -export async function listTaskInventoryBindingSummariesByTaskIds( - taskIds: unknown[] = [], -): Promise { - const normalizedTaskIds = Array.from(new Set((Array.isArray(taskIds) ? taskIds : []) - .map((value) => Number(value)) - .filter((value) => Number.isFinite(value) && value > 0))) - - if (normalizedTaskIds.length === 0) { - return [] - } - - const result = await query( - ` - SELECT - tib.task_id, - COUNT(*)::int AS total_binding_count, - COUNT(*) FILTER (WHERE tib.binding_status = 'reserved')::int AS reserved_binding_count, - COUNT(*) FILTER (WHERE tib.binding_status = 'consumed')::int AS consumed_binding_count, - COUNT(*) FILTER (WHERE tib.binding_status = 'released')::int AS released_binding_count, - ARRAY_AGG(DISTINCT tib.role_key ORDER BY tib.role_key) AS role_keys - FROM task_inventory_bindings tib - WHERE tib.task_id = ANY($1::bigint[]) - GROUP BY tib.task_id - `, - [normalizedTaskIds], - ) - - return result.rows -} diff --git a/apps/backend/src/repositories/task-repo.ts b/apps/backend/src/repositories/task-repo.ts index 029a4b86..2dab2c69 100644 --- a/apps/backend/src/repositories/task-repo.ts +++ b/apps/backend/src/repositories/task-repo.ts @@ -42,35 +42,13 @@ const TASK_FIELDS = ` ctx.partition_name, ctx.screenshot_path, ctx.artifacts_json, - ctx.state_json, - inv.inventory_item_id AS primary_inventory_item_id, - inv.display_value AS primary_inventory_display_value, - inv.credential_type AS primary_inventory_credential_type, - inv.binding_status AS primary_inventory_binding_status + ctx.state_json ` const TASK_JOINS = ` FROM fulfillment_tasks ft LEFT JOIN claim_tokens ct ON ct.task_id = ft.id LEFT JOIN task_runtime_contexts ctx ON ctx.task_id = ft.id - LEFT JOIN LATERAL ( - SELECT - tib.inventory_item_id, - tib.binding_status, - ii.display_value, - ii.credential_type - FROM task_inventory_bindings tib - JOIN inventory_items ii ON ii.id = tib.inventory_item_id - WHERE tib.task_id = ft.id AND tib.binding_status IN ('reserved', 'consumed') - ORDER BY - CASE tib.binding_status - WHEN 'reserved' THEN 0 - WHEN 'consumed' THEN 1 - ELSE 2 - END ASC, - tib.id DESC - LIMIT 1 - ) inv ON TRUE ` function buildTaskSelect(extraFields = ''): string { @@ -106,7 +84,6 @@ export async function createTask(input: TaskCreateInput): Promise { - const result = await query( - ` - INSERT INTO webhook_events ( - provider, - platform, - shop_id, - shop_name, - event_type, - event_key, - signature_valid, - headers_json, - query_json, - body_json, - processed, - process_error, - related_order_id, - created_at - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9::jsonb, $10::jsonb, $11, $12, $13, $14) - RETURNING * - `, - [ - input.provider, - input.platform, - input.shopId || '', - input.shopName || '', - input.eventType, - input.eventKey, - Boolean(input.signatureValid), - input.headersJson || '{}', - input.queryJson || '{}', - input.bodyJson || '{}', - Boolean(input.processed), - input.processError || '', - input.relatedOrderId || null, - input.createdAt, - ], - ) - - return result.rows[0] || null -} - -export async function updateWebhookEvent( - eventId: number | string, - patch: WebhookEventUpdatePatch, -): Promise { - const current = await getWebhookEventById(eventId) - if (!current) { - return null - } - - const next = { ...current, ...patch } - const result = await query( - ` - UPDATE webhook_events - SET - processed = $1, - process_error = $2, - related_order_id = $3 - WHERE id = $4 - RETURNING * - `, - [Boolean(next.processed), next.process_error || '', next.related_order_id || null, Number(eventId)], - ) - - return result.rows[0] || null -} - -export async function getWebhookEventById(eventId: number | string): Promise { - const result = await query('SELECT * FROM webhook_events WHERE id = $1 LIMIT 1', [ - Number(eventId), - ]) - return result.rows[0] || null -} - -export async function listWebhookEvents({ - page = 1, - pageSize = 20, - provider = '', - platform = '', - platformOrderId = '', - processed = '', - visibility = 'important', - relatedOrderId = '', - dateFrom = '', - dateTo = '', -}: WebhookEventListQueryInput = {}): Promise { - const offset = (page - 1) * pageSize - const filters: string[] = [] - const params: unknown[] = [] - - if (provider) { - params.push(provider) - filters.push(`provider = $${params.length}`) - } - - if (platform) { - params.push(platform) - filters.push(`platform = $${params.length}`) - } - - if (platformOrderId) { - params.push(`%${platformOrderId}%`) - filters.push(`(event_key ILIKE $${params.length} OR body_json::text ILIKE $${params.length})`) - } - - if (processed === '0' || processed === '1') { - params.push(processed === '1') - filters.push(`processed = $${params.length}`) - } - - if (visibility === 'ignored') { - filters.push(`process_error LIKE 'ignored_%'`) - } else if (visibility === 'important') { - filters.push(`(process_error = '' OR process_error NOT LIKE 'ignored_%')`) - } - - if (relatedOrderId) { - params.push(Number(relatedOrderId)) - filters.push(`related_order_id = $${params.length}`) - } - - if (dateFrom) { - params.push(dateFrom) - filters.push(`created_at >= $${params.length}`) - } - - if (dateTo) { - params.push(dateTo) - filters.push(`created_at <= $${params.length}`) - } - - const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '' - const totalResult = await query<{ [column: string]: unknown, total: number }>( - `SELECT COUNT(*)::int AS total FROM webhook_events ${whereClause}`, - params, - ) - - params.push(pageSize) - params.push(offset) - const itemsResult = await query( - ` - SELECT * - FROM webhook_events - ${whereClause} - ORDER BY id DESC - LIMIT $${params.length - 1} OFFSET $${params.length} - `, - params, - ) - - return { - items: itemsResult.rows, - total: Number(totalResult.rows[0]?.total || 0), - } -} - -export async function listWebhookEventsByOrderId(orderId: number | string): Promise { - const result = await query( - ` - SELECT * - FROM webhook_events - WHERE related_order_id = $1 - ORDER BY id DESC - `, - [Number(orderId)], - ) - - return result.rows -} diff --git a/apps/backend/src/routes/admin.ts b/apps/backend/src/routes/admin.ts index ef34e404..2c56d851 100644 --- a/apps/backend/src/routes/admin.ts +++ b/apps/backend/src/routes/admin.ts @@ -3,13 +3,11 @@ import { Router } from "express"; import authRouter from "./admin/auth.js"; import auditLogsRouter from "./admin/audit-logs.js"; import dashboardRouter from "./admin/dashboard.js"; -import inventoryRouter from "./admin/inventory.js"; import ordersRouter from "./admin/orders.js"; import platformConfigRouter from "./admin/platform-config.js"; import { requireAdminSession } from "./admin/shared.js"; import tasksRouter from "./admin/tasks.js"; import usersRouter from "./admin/users.js"; -import webhookEventsRouter from "./admin/webhook-events.js"; import { buildNotFoundPayload } from "../utils/http.js"; const router = Router(); @@ -22,8 +20,6 @@ router.use(auditLogsRouter); router.use(platformConfigRouter); router.use(ordersRouter); router.use(tasksRouter); -router.use(inventoryRouter); -router.use(webhookEventsRouter); router.use((req, res) => { res.status(404).json(buildNotFoundPayload(req)); diff --git a/apps/backend/src/routes/admin/inventory.ts b/apps/backend/src/routes/admin/inventory.ts deleted file mode 100644 index b446d580..00000000 --- a/apps/backend/src/routes/admin/inventory.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { Router } from "express"; - -import { - createAdminInventoryItem, - importAdminInventoryItems, - invalidateAdminInventoryItem, - releaseAdminInventoryItem, -} from "../../services/admin/write/inventory.js"; -import { - getAdminInventoryItems, - getAdminInventorySkuSuggestions, -} from "../../services/admin/admin-read-service.js"; -import { createJsonHandler, requireAdminRoles } from "./shared.js"; -import type { - AdminInventoryCreateRouteBody, - AdminInventoryImportRouteBody, - AdminInventoryInvalidateRouteBody, - AdminInventoryRouteParams, - AdminInventoryRouteQuery, - AdminInventorySkuSuggestionRouteQuery, -} from "../../types/admin-route-inputs.js"; -import type { AdminInventoryMutationResponse } from "../../types/admin-write-models.js"; - -const router = Router(); - -router.use("/inventory", requireAdminRoles(["admin", "operator"])); - -router.get( - "/inventory", - createJsonHandler( - (req) => - getAdminInventoryItems( - req.query as AdminInventoryRouteQuery, - req.adminSession || null - ), - { - successMessage: "ok", - errorMessage: "读取库存列表失败", - scope: "[admin/inventory]", - } - ) -); - -router.get( - "/inventory/sku-suggestions", - createJsonHandler( - (req) => - getAdminInventorySkuSuggestions( - req.query as AdminInventorySkuSuggestionRouteQuery, - req.adminSession || null - ), - { - successMessage: "ok", - errorMessage: "读取内部 SKU 建议失败", - scope: "[admin/inventory/sku-suggestions]", - } - ) -); - -router.post( - "/inventory", - createJsonHandler( - (req) => createAdminInventoryItem(req.body as AdminInventoryCreateRouteBody), - { - successMessage: "库存项已新增", - errorMessage: "新增库存项失败", - scope: "[admin/inventory]", - } - ) -); - -router.post( - "/inventory/import", - createJsonHandler( - (req) => importAdminInventoryItems(req.body as AdminInventoryImportRouteBody), - { - successMessage: "导入完成", - errorMessage: "导入库存凭据失败", - scope: "[admin/inventory/import]", - } - ) -); - -router.post( - "/inventory/:inventoryItemId/release", - requireAdminRoles(["admin"]), - createJsonHandler( - (req) => - releaseAdminInventoryItem( - (req.params as AdminInventoryRouteParams).inventoryItemId - ), - { - successMessage: "库存项已释放", - errorMessage: "释放库存项失败", - scope: "[admin/inventory/:inventoryItemId/release]", - audit: (req, data) => { - const result = data as AdminInventoryMutationResponse; - - return { - action: "inventory_item_released", - targetType: "inventory_item", - targetId: String( - (req.params as AdminInventoryRouteParams).inventoryItemId - ), - data: { - inventoryItemId: result.inventoryItem?.inventoryItemId, - skuCode: result.inventoryItem?.skuCode, - displayValue: result.inventoryItem?.displayValue, - }, - }; - }, - } - ) -); - -router.post( - "/inventory/:inventoryItemId/invalidate", - requireAdminRoles(["admin"]), - createJsonHandler( - (req) => - invalidateAdminInventoryItem( - (req.params as AdminInventoryRouteParams).inventoryItemId, - req.body as AdminInventoryInvalidateRouteBody - ), - { - successMessage: "库存项已作废", - errorMessage: "作废库存项失败", - scope: "[admin/inventory/:inventoryItemId/invalidate]", - audit: (req, data) => { - const result = data as AdminInventoryMutationResponse; - - return { - action: "inventory_item_invalidated", - targetType: "inventory_item", - targetId: String( - (req.params as AdminInventoryRouteParams).inventoryItemId - ), - data: { - inventoryItemId: result.inventoryItem?.inventoryItemId, - skuCode: result.inventoryItem?.skuCode, - invalidReason: result.inventoryItem?.invalidReason, - }, - }; - }, - } - ) -); - -export default router; diff --git a/apps/backend/src/routes/admin/users.ts b/apps/backend/src/routes/admin/users.ts index bf55f15f..45f532b5 100644 --- a/apps/backend/src/routes/admin/users.ts +++ b/apps/backend/src/routes/admin/users.ts @@ -4,7 +4,6 @@ import { createManagedAdminUser, getAdminUserList, resetManagedAdminUserPassword, - updateManagedAdminUserInventoryGroups, updateManagedAdminUserRole, updateManagedAdminUserStatus, } from '../../services/admin/admin-auth-service.js' @@ -16,7 +15,6 @@ type AdminUserMutationResult = { username: string role: string status?: string - inventoryGroupCodes?: string[] } } @@ -97,27 +95,6 @@ router.post('/users/:userId/status', createJsonHandler( }, )) -router.post('/users/:userId/inventory-groups', createJsonHandler( - (req) => updateManagedAdminUserInventoryGroups(String(req.params.userId), req.body, req.adminSession), - { - successMessage: '用户库存组已更新', - errorMessage: '更新用户库存组失败', - scope: '[admin/users/:userId/inventory-groups]', - audit: (_req, data) => { - const result = data as AdminUserMutationResult - return { - action: 'admin_user_inventory_groups_updated', - targetType: 'admin_user', - targetId: String(result.user.userId), - data: { - username: result.user.username, - inventoryGroupCodes: result.user.inventoryGroupCodes, - }, - } - }, - }, -)) - router.post('/users/:userId/reset-password', createJsonHandler( (req) => resetManagedAdminUserPassword(String(req.params.userId), req.body), { diff --git a/apps/backend/src/routes/admin/webhook-events.ts b/apps/backend/src/routes/admin/webhook-events.ts deleted file mode 100644 index aff97045..00000000 --- a/apps/backend/src/routes/admin/webhook-events.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Router } from "express"; - -import { - getAdminWebhookEventDetail, - getAdminWebhookEvents, -} from "../../services/admin/admin-read-service.js"; -import { createHttpError } from "../../utils/http.js"; -import { createJsonHandler, requireAdminRoles } from "./shared.js"; -import type { - AdminWebhookEventRouteParams, - AdminWebhookEventRouteQuery, -} from "../../types/admin-route-inputs.js"; - -const router = Router(); - -router.use("/webhook-events", requireAdminRoles(["admin", "operator"])); - -router.get( - "/webhook-events", - createJsonHandler( - (req) => getAdminWebhookEvents(req.query as AdminWebhookEventRouteQuery), - { - successMessage: "ok", - errorMessage: "读取 webhook 列表失败", - scope: "[admin/webhook-events]", - } - ) -); - -router.get( - "/webhook-events/:eventId", - createJsonHandler( - (req) => - getAdminWebhookEventDetail( - (req.params as AdminWebhookEventRouteParams).eventId - ), - { - successMessage: "ok", - errorMessage: "读取 webhook 详情失败", - scope: "[admin/webhook-events/:eventId]", - } - ) -); - -router.post( - "/webhook-events/:eventId/replay", - requireAdminRoles(["admin"]), - createJsonHandler( - () => { - throw createHttpError("快手轻量后端不支持重放旧 Webhook", { - statusCode: 409, - errorCode: "admin_webhook_replay_disabled", - }); - }, - { - successMessage: "Webhook 已重放", - errorMessage: "重放 webhook 失败", - scope: "[admin/webhook-events/:eventId/replay]", - audit: (req, data) => { - return { - action: "webhook_replayed", - targetType: "webhook_event", - targetId: String( - (req.params as AdminWebhookEventRouteParams).eventId - ), - data: { - disabled: true, - result: data, - }, - }; - }, - } - ) -); - -export default router; diff --git a/apps/backend/src/routes/open-91.ts b/apps/backend/src/routes/open-91.ts index 86fb9e66..879a281a 100644 --- a/apps/backend/src/routes/open-91.ts +++ b/apps/backend/src/routes/open-91.ts @@ -3,7 +3,7 @@ import { Router } from 'express' import { createOpen91Order } from '../services/open-91/order-create-service.js' import { queryOpen91Order } from '../services/open-91/order-query-service.js' import { buildOpen91ErrorResponse } from '../services/open-91/shared.js' -import { createRequestId, logWebhook } from '../utils/logger.js' +import { createRequestId, logIntegration } from '../utils/logger.js' const router = Router() @@ -11,7 +11,7 @@ router.post('/orders/create', async (req, res) => { const requestId = createRequestId('91') const startedAt = Date.now() - logWebhook('[open-91/create]', '收到 91卡券异步下单请求', { + logIntegration('[open-91/create]', '收到 91卡券异步下单请求', { requestId, method: req.method, originalUrl: req.originalUrl, @@ -21,7 +21,7 @@ router.post('/orders/create', async (req, res) => { try { const result = await createOpen91Order(req.body, { requestId }) - logWebhook('[open-91/create]', '91卡券异步下单处理完成', { + logIntegration('[open-91/create]', '91卡券异步下单处理完成', { requestId, durationMs: Date.now() - startedAt, result, @@ -30,7 +30,7 @@ router.post('/orders/create', async (req, res) => { } catch (error) { const message = error instanceof Error ? error.message : '系统错误' const code = Number(error?.statusCode || 500) >= 500 ? 500 : 400 - logWebhook('[open-91/create]', '91卡券异步下单处理失败', { + logIntegration('[open-91/create]', '91卡券异步下单处理失败', { requestId, durationMs: Date.now() - startedAt, error, @@ -43,7 +43,7 @@ router.post('/orders/query', async (req, res) => { const requestId = createRequestId('91') const startedAt = Date.now() - logWebhook('[open-91/query]', '收到 91卡券订单查询请求', { + logIntegration('[open-91/query]', '收到 91卡券订单查询请求', { requestId, method: req.method, originalUrl: req.originalUrl, @@ -53,7 +53,7 @@ router.post('/orders/query', async (req, res) => { try { const result = await queryOpen91Order(req.body, { requestId }) - logWebhook('[open-91/query]', '91卡券订单查询处理完成', { + logIntegration('[open-91/query]', '91卡券订单查询处理完成', { requestId, durationMs: Date.now() - startedAt, result, @@ -62,7 +62,7 @@ router.post('/orders/query', async (req, res) => { } catch (error) { const message = error instanceof Error ? error.message : '系统错误' const code = Number(error?.statusCode || 500) >= 500 ? 500 : 400 - logWebhook('[open-91/query]', '91卡券订单查询处理失败', { + logIntegration('[open-91/query]', '91卡券订单查询处理失败', { requestId, durationMs: Date.now() - startedAt, error, diff --git a/apps/backend/src/services/admin/admin-auth-service.ts b/apps/backend/src/services/admin/admin-auth-service.ts index eba3150b..f6378cbd 100644 --- a/apps/backend/src/services/admin/admin-auth-service.ts +++ b/apps/backend/src/services/admin/admin-auth-service.ts @@ -7,7 +7,6 @@ import { getAdminUserById, getAdminUserByUsername, listAdminUsers, - replaceAdminUserInventoryGroupBindings, updateAdminUser, } from '../../repositories/admin-user-repo.js' import { addHours, nowIso } from '../../utils/time.js' @@ -23,7 +22,6 @@ export type AdminSession = { userId: number username: string role: AdminRole - allowedInventoryGroups: string[] expiresAt: string } @@ -144,7 +142,6 @@ export async function verifyAdminSessionToken(token: unknown): Promise { - const user = await getRequiredAdminUser(userId) - const inventoryGroupCodes = normalizeInventoryGroupCodes(payload.inventoryGroupCodes) - - await ensureAdminUserChangeAllowed(user, {}, session) - const updated = await replaceAdminUserInventoryGroupBindings(user.id, inventoryGroupCodes, nowIso()) - - return { - user: mapAdminUser(updated), - } -} - export async function updateManagedAdminUserRole( userId: number | string, payload: JsonObject = {}, @@ -358,7 +338,6 @@ function createAdminSession(user: AdminUserRow): JsonObject { userId: Number(user.id), username: String(user.username || ''), role: normalizeAdminRole(user.role), - inventoryGroupCodes: normalizeInventoryGroupCodes(user.inventory_group_codes), }, } } @@ -493,14 +472,7 @@ function mapAdminUser(user: AdminUserRow | null): JsonObject { username: String(user.username || ''), role: normalizeAdminRole(user.role), status: normalizeAdminUserStatus(user.status), - inventoryGroupCodes: normalizeInventoryGroupCodes(user.inventory_group_codes), createdAt: user.created_at, updatedAt: user.updated_at, } } - -function normalizeInventoryGroupCodes(values: unknown): string[] { - return Array.from(new Set((Array.isArray(values) ? values : []) - .map((value) => String(value || '').trim()) - .filter(Boolean))) -} diff --git a/apps/backend/src/services/admin/admin-dashboard-service.ts b/apps/backend/src/services/admin/admin-dashboard-service.ts index 79dc6dc4..1681d987 100644 --- a/apps/backend/src/services/admin/admin-dashboard-service.ts +++ b/apps/backend/src/services/admin/admin-dashboard-service.ts @@ -12,8 +12,7 @@ export async function getAdminDashboardSummary() { ( SELECT COUNT(*)::int FROM fulfillment_tasks - WHERE task_status IN ('paid', 'link_generated') - OR (inventory_status = 'reserved' AND task_status NOT IN ('claimed', 'role_confirmed', 'redeeming', 'redeemed', 'closed', 'expired')) + WHERE task_status IN ('paid', 'link_generated', 'pending_binding_prepare', 'waiting_binding') ) AS paid_pending_claim, ( SELECT COUNT(*)::int @@ -29,13 +28,8 @@ export async function getAdminDashboardSummary() { ( SELECT COUNT(*)::int FROM fulfillment_tasks - WHERE task_status IN ('retry_pending', 'manual_review', 'waiting_inventory') - ) AS abnormal_tasks, - ( - SELECT COUNT(DISTINCT sku_code)::int - FROM inventory_items - WHERE status = 'available' - ) AS sku_with_inventory + WHERE task_status IN ('retry_pending', 'manual_review') + ) AS abnormal_tasks `, [todayPrefix], ) @@ -47,6 +41,5 @@ export async function getAdminDashboardSummary() { claimingTasks: Number(summary?.claiming_tasks || 0), redeemedToday: Number(summary?.redeemed_today || 0), abnormalTasks: Number(summary?.abnormal_tasks || 0), - skuWithInventory: Number(summary?.sku_with_inventory || 0), } } diff --git a/apps/backend/src/services/admin/admin-inventory-read-helpers.ts b/apps/backend/src/services/admin/admin-inventory-read-helpers.ts deleted file mode 100644 index 6198831f..00000000 --- a/apps/backend/src/services/admin/admin-inventory-read-helpers.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { getInventoryItemById } from '../../repositories/inventory-repo.js' -import { getOrderById } from '../../repositories/order-repo.js' -import { getTaskById } from '../../repositories/task-repo.js' -import { createHttpError } from '../../utils/http.js' -import { mapAdminTaskSummary } from './admin-task-read-helpers.js' - -import type { AdminInventoryItemListItem } from '../../types/admin-read-models.js' -import type { InventoryItemRow } from '../../types/repository-rows.js' - -export async function mapAdminInventoryListItem(item: InventoryItemRow): Promise { - const task = item.reserved_by_task_id ? await getTaskById(item.reserved_by_task_id) : null - const order = task?.order_id ? await getOrderById(task.order_id) : null - const taskSummary = task ? mapAdminTaskSummary(task) : null - - return { - inventoryItemId: item.id, - skuCode: item.sku_code, - batchNo: item.batch_no, - credentialType: item.credential_type || 'claim_code', - inventoryGroupCode: String(item.inventory_group_code || '').trim(), - displayValue: item.display_value, - status: item.status, - reservedByTaskId: item.reserved_by_task_id, - reservedByTaskNo: task?.task_no || '', - platformOrderId: order?.platform_order_id || '', - systemBindingStatus: item.status === 'consumed' ? 'system_bound' : (taskSummary?.systemBindingStatus || 'pending_binding'), - userBindingStatus: item.status === 'consumed' ? 'binding_completed' : (taskSummary?.userBindingStatus || 'not_started'), - invalidReason: item.invalid_reason || '', - deliveredAt: item.delivered_at, - createdAt: item.created_at, - updatedAt: item.updated_at, - } -} - -export async function getRequiredInventoryItem(inventoryItemId: number | string): Promise { - const inventoryItem = await getInventoryItemById(Number(inventoryItemId)) - - if (!inventoryItem) { - throw createHttpError('库存项不存在', { - statusCode: 404, - errorCode: 'admin_inventory_not_found', - }) - } - - return inventoryItem -} 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 00d1f055..f1ea3eea 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, getTaskBindingSummaryMap } from './admin-task-read-helpers.js' +import { buildOrderBindingSummary } 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,8 +12,7 @@ export async function mapAdminOrderListItem(item: OrderListRow): Promise task.id)) - const bindingSummary = buildOrderBindingSummary(tasks, taskBindingSummaryMap) + const bindingSummary = buildOrderBindingSummary(tasks) const itemSummary = summarizeOrderItems(orderItems) return { diff --git a/apps/backend/src/services/admin/admin-read-service.ts b/apps/backend/src/services/admin/admin-read-service.ts index 135b6f76..902f00c2 100644 --- a/apps/backend/src/services/admin/admin-read-service.ts +++ b/apps/backend/src/services/admin/admin-read-service.ts @@ -1,16 +1,9 @@ import { buildClaimUrl } from '../claim/claim-service.js' import { getClaimTokenById } from '../../repositories/claim-token-repo.js' -import { - getInventoryItemById, - listInventoryItems, - listInventorySkuSuggestions, -} from '../../repositories/inventory-repo.js' import { listOrderItemsByOrderId } from '../../repositories/order-item-repo.js' import { getOrderById, listOrders } from '../../repositories/order-repo.js' import { getTaskById, listTasks, listTasksByOrderId } from '../../repositories/task-repo.js' -import { listTaskInventoryBindingsByTaskId } from '../../repositories/task-inventory-binding-repo.js' import { listTaskEventsByTaskId } from '../../repositories/task-event-repo.js' -import { getWebhookEventById, listWebhookEvents, listWebhookEventsByOrderId } from '../../repositories/webhook-event-repo.js' import { createHttpError } from '../../utils/http.js' import { formatFenToAmount, normalizeFen } from '../../utils/money.js' import { normalizeDateQuery, normalizePage, normalizePageSize, safeParseJson } from './admin-query-utils.js' @@ -19,7 +12,6 @@ import { canRegenerateClaimLinkForViewer, createAdminViewerContext, getTaskPrimaryClaimTokenId, - getTaskPrimaryInventoryItemId, isManualDispatchTask, mapKuaishouCloudFulfillmentContext, mapManualDispatchContext, @@ -31,36 +23,24 @@ import { resolveOrderItemDeliveryMode, resolveOrderItemTitle, } from './admin-read-shared-helpers.js' -import { mapAdminInventoryListItem } from './admin-inventory-read-helpers.js' import { mapAdminOrderListItem, summarizeOrderItems } from './admin-order-read-helpers.js' -import { mapAdminWebhookEvent } from './admin-webhook-read-helpers.js' import { buildOrderBindingSummary, - createTaskBindingSummaryFromBindings, getRequiredTask, - getTaskBindingSummary, - getTaskBindingSummaryMap, mapAdminTaskEvent, - mapAdminTaskInventoryBinding, mapAdminTaskListItem, mapAdminTaskSummary, } from './admin-task-read-helpers.js' import type { - AdminInventoryListResponse, - AdminInventorySkuSuggestionResponse, AdminOrderListResponse, AdminTaskListResponse, - AdminWebhookEventListResponse, } from '../../types/admin-read-models.js' import type { AdminEntityIdInput, - AdminInventoryListQueryInput, - AdminInventorySkuSuggestionQueryInput, AdminOrderListQueryInput, AdminTaskListQueryInput, AdminViewerSessionInput, - AdminWebhookEventListQueryInput, } from '../../types/admin-read-inputs.js' type JsonRecord = Record @@ -94,12 +74,10 @@ export async function getAdminOrderDetail(orderId: AdminEntityIdInput): Promise< }) } - const [items, tasks, webhookEvents] = await Promise.all([ + const [items, tasks] = await Promise.all([ listOrderItemsByOrderId(order.id), listTasksByOrderId(order.id), - listWebhookEventsByOrderId(order.id), ]) - const taskBindingSummaryMap = await getTaskBindingSummaryMap(tasks.map((task) => task.id)) const itemSummary = summarizeOrderItems(items) return { @@ -125,7 +103,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, taskBindingSummaryMap), + bindingSummary: buildOrderBindingSummary(tasks), }, items: items.map((item) => ({ orderItemId: item.id, @@ -138,20 +116,7 @@ export async function getAdminOrderDetail(orderId: AdminEntityIdInput): Promise< ? item.spec_json : JSON.parse(String(item.spec_json || '{}')), })), - tasks: tasks.map((task) => mapAdminTaskSummary(task, getTaskBindingSummary(taskBindingSummaryMap, task.id))), - webhookEvents: webhookEvents.map((event) => ({ - eventId: event.id, - provider: event.provider || '', - platform: event.platform, - shopId: event.shop_id || '', - shopName: resolveDisplayShopName(event.provider, event.shop_id, event.shop_name), - eventType: event.event_type, - eventKey: event.event_key, - signatureValid: Boolean(event.signature_valid), - processed: Boolean(event.processed), - processError: event.process_error, - createdAt: event.created_at, - })), + tasks: tasks.map((task) => mapAdminTaskSummary(task)), } } @@ -172,15 +137,10 @@ export async function getAdminTasks( dateFrom: normalizeDateQuery(query.dateFrom), dateTo: normalizeDateQuery(query.dateTo, true), }) - const taskBindingSummaryMap = await getTaskBindingSummaryMap(items.map((item) => item.id)) const viewerContext = createAdminViewerContext(session) return { - items: items.map((item) => mapAdminTaskListItem( - item, - getTaskBindingSummary(taskBindingSummaryMap, item.id), - viewerContext, - )), + items: items.map((item) => mapAdminTaskListItem(item, viewerContext)), pagination: { page, pageSize, total }, } } @@ -198,20 +158,16 @@ export async function getAdminTaskDetail( }) } - const [order, claimToken, inventoryItem, taskEvents, inventoryBindings] = await Promise.all([ + const [order, claimToken, taskEvents] = await Promise.all([ getOrderById(task.order_id), getTaskPrimaryClaimTokenId(task) ? getClaimTokenById(getTaskPrimaryClaimTokenId(task)) : Promise.resolve(null), - getTaskPrimaryInventoryItemId(task) ? getInventoryItemById(getTaskPrimaryInventoryItemId(task)) : Promise.resolve(null), listTaskEventsByTaskId(task.id), - listTaskInventoryBindingsByTaskId(task.id), ]) const orderItems = order ? await listOrderItemsByOrderId(order.id) : [] const orderItem = orderItems.find((item) => item.id === task.order_item_id) || null const taskContext = parseTaskContext(task) const taskState = parseTaskState(task) - const taskBindingSummary = createTaskBindingSummaryFromBindings(inventoryBindings) const viewerContext = createAdminViewerContext(session) - const mappedInventoryBindings = inventoryBindings.map((binding) => mapAdminTaskInventoryBinding(binding, task, viewerContext)) const claimUrl = claimToken ? buildClaimUrl(claimToken.token) : '' const screenshotUrl = await resolveAdminTaskScreenshotUrl(task, viewerContext) const kuaishouCloudFulfillment = mapKuaishouCloudFulfillmentContext(taskContext.kuaishouCloudFulfillment) @@ -221,9 +177,8 @@ export async function getAdminTaskDetail( ...task, sku_code: orderItem?.sku_code || '', sku_name: orderItem?.sku_name || '', - inventory_display_value: inventoryItem?.display_value || '', claim_token: claimToken?.token || '', - }, taskBindingSummary, viewerContext), + }, viewerContext), order: order ? { orderId: order.id, @@ -253,17 +208,6 @@ export async function getAdminTaskDetail( claimUrl, } : null, - inventory: inventoryItem - ? { - inventoryItemId: inventoryItem.id, - skuCode: inventoryItem.sku_code, - batchNo: inventoryItem.batch_no, - credentialType: inventoryItem.credential_type || 'claim_code', - displayValue: viewerContext.canViewSensitiveTaskData ? inventoryItem.display_value : '', - status: inventoryItem.status, - } - : null, - inventoryBindings: mappedInventoryBindings, artifacts: viewerContext.canViewSensitiveTaskData ? safeParseJson(task.artifacts_json) : {}, screenshotUrl, review: { @@ -279,8 +223,7 @@ export async function getAdminTaskDetail( operations: { canRetry: viewerContext.canManageTaskLifecycle && !isManualDispatchTask(task) - && ['retry_pending', 'manual_review', 'waiting_inventory'].includes(task.task_status), - canReleaseInventory: mappedInventoryBindings.some((binding) => binding.canRelease), + && ['retry_pending', 'manual_review'].includes(task.task_status), canRegenerateClaimLink: canRegenerateClaimLinkForViewer(task, viewerContext), canClose: canViewerCloseTask(task, viewerContext), canMarkManualReview: viewerContext.canManageTaskLifecycle && !['redeemed', 'closed', 'manual_review'].includes(task.task_status), @@ -319,96 +262,3 @@ export async function getAdminTaskScreenshotPath( errorCode: 'admin_task_screenshot_not_found', }) } - -export async function getAdminInventoryItems( - query: AdminInventoryListQueryInput = {}, - session: AdminViewerSessionInput | null = null, -): Promise { - const page = normalizePage(query.page) - const pageSize = normalizePageSize(query.pageSize) - const viewerContext = createAdminViewerContext(session) - const { items, total } = await listInventoryItems({ - page, - pageSize, - skuCode: String(query.skuCode || '').trim(), - credentialType: String(query.credentialType || '').trim(), - status: String(query.status || '').trim(), - batchNo: String(query.batchNo || '').trim(), - inventoryGroupCode: String(query.inventoryGroupCode || '').trim(), - allowedInventoryGroupCodes: viewerContext.allowedInventoryGroupCodes, - }) - - return { - items: await Promise.all(items.map((item) => mapAdminInventoryListItem(item))), - pagination: { page, pageSize, total }, - } -} - -export async function getAdminInventorySkuSuggestions( - query: AdminInventorySkuSuggestionQueryInput = {}, - session: AdminViewerSessionInput | null = null, -): Promise { - const viewerContext = createAdminViewerContext(session) - const credentialType = String(query.credentialType || '').trim() - const keyword = String(query.keyword || '').trim() - const inventoryGroupCode = String(query.inventoryGroupCode || '').trim() - const requestedLimit = Number(query.limit) - const limit = Number.isFinite(requestedLimit) && requestedLimit > 0 - ? Math.min(100, Math.floor(requestedLimit)) - : 50 - const items = await listInventorySkuSuggestions({ - credentialType, - keyword, - limit, - inventoryGroupCode, - allowedInventoryGroupCodes: viewerContext.allowedInventoryGroupCodes, - }) - - return { - items: items.map((item) => ({ - skuCode: String(item.sku_code || '').trim(), - credentialType: String(item.credential_type || '').trim() || 'claim_code', - inventoryGroupCode: String(item.inventory_group_code || '').trim(), - totalCount: Number(item.total_count || 0), - availableCount: Number(item.available_count || 0), - latestUpdatedAt: item.latest_updated_at || null, - })), - } -} - -export async function getAdminWebhookEvents( - query: AdminWebhookEventListQueryInput = {}, -): Promise { - const page = normalizePage(query.page) - const pageSize = normalizePageSize(query.pageSize) - const { items, total } = await listWebhookEvents({ - page, - pageSize, - provider: String(query.provider || '').trim(), - platform: String(query.platform || '').trim(), - platformOrderId: String(query.platformOrderId || '').trim(), - processed: String(query.processed || '').trim(), - visibility: String(query.visibility || '').trim() || 'important', - relatedOrderId: String(query.relatedOrderId || '').trim(), - dateFrom: normalizeDateQuery(query.dateFrom), - dateTo: normalizeDateQuery(query.dateTo, true), - }) - - return { - items: await Promise.all(items.map((item) => mapAdminWebhookEvent(item))), - pagination: { page, pageSize, total }, - } -} - -export async function getAdminWebhookEventDetail(eventId: AdminEntityIdInput): Promise { - const event = await getWebhookEventById(Number(eventId)) - - if (!event) { - throw createHttpError('Webhook 事件不存在', { - statusCode: 404, - errorCode: 'admin_webhook_not_found', - }) - } - - return mapAdminWebhookEvent(event, { includeRaw: true }) -} diff --git a/apps/backend/src/services/admin/admin-read-shared-helpers.ts b/apps/backend/src/services/admin/admin-read-shared-helpers.ts index f3557f3f..f111760c 100644 --- a/apps/backend/src/services/admin/admin-read-shared-helpers.ts +++ b/apps/backend/src/services/admin/admin-read-shared-helpers.ts @@ -16,17 +16,13 @@ type TaskLike = Partial & { export type AdminViewerContext = { role: string - allowedInventoryGroupCodes: string[] | null - restrictInventoryGroups: boolean canViewSensitiveTaskData: boolean - canManageSensitiveInventory: boolean canManageTaskLifecycle: boolean canOperateAssistedTask: boolean } type RedeemResolutionAttempt = { attempt: number - inventoryItemId: number | null codeMasked: string credentialType: string outcome: string @@ -166,11 +162,6 @@ export function mapRedeemResolutionContext(value: unknown): JsonRecord | null { } } -export function getTaskPrimaryInventoryItemId(task: TaskLike | null | undefined): number | null { - const value = Number(task?.primary_inventory_item_id || 0) - return Number.isFinite(value) && value > 0 ? value : null -} - export function getTaskPrimaryClaimTokenId(task: TaskLike | null | undefined): number | null { const value = Number(task?.primary_claim_token_id || 0) return Number.isFinite(value) && value > 0 ? value : null @@ -178,16 +169,10 @@ export function getTaskPrimaryClaimTokenId(task: TaskLike | null | undefined): n export function createAdminViewerContext(session: AdminViewerSessionInput | null = null): AdminViewerContext { const role = normalizeAdminRole(session?.role) - const allowedInventoryGroupCodes = role === 'support' - ? normalizeInventoryGroupCodes(session?.allowedInventoryGroups) - : null return { role, - allowedInventoryGroupCodes, - restrictInventoryGroups: role === 'support', canViewSensitiveTaskData: role === 'admin' || role === 'operator', - canManageSensitiveInventory: role === 'admin', canManageTaskLifecycle: role === 'admin' || role === 'operator', canOperateAssistedTask: role === 'admin' || role === 'operator' || role === 'support', } @@ -297,38 +282,6 @@ export function canRegenerateClaimLinkForViewer(task: TaskLike, viewerContext: A return false } -export function canViewerAccessTaskInventoryGroup( - task: TaskLike, - viewerContext: AdminViewerContext = createAdminViewerContext(), -): boolean { - if (!viewerContext.restrictInventoryGroups) { - return true - } - - const allowedInventoryGroupCodes = Array.isArray(viewerContext.allowedInventoryGroupCodes) - ? viewerContext.allowedInventoryGroupCodes - : [] - - if (allowedInventoryGroupCodes.length === 0) { - return false - } - - const taskContext = parseTaskContext(task) - const inventoryGroupCode = String(taskContext?.inventoryGroupCode || '').trim() - - if (!inventoryGroupCode) { - return true - } - - return allowedInventoryGroupCodes.includes(inventoryGroupCode) -} - -export function normalizeInventoryGroupCodes(values: unknown): string[] { - return Array.from(new Set((Array.isArray(values) ? values : []) - .map((value) => String(value || '').trim()) - .filter(Boolean))) -} - function mapRedeemResolutionAttempt(value: unknown): RedeemResolutionAttempt | null { if (!value || typeof value !== 'object') { return null @@ -337,7 +290,6 @@ function mapRedeemResolutionAttempt(value: unknown): RedeemResolutionAttempt | n const record = value as JsonRecord return { attempt: Math.max(1, Number(record.attempt || 1)), - inventoryItemId: Number(record.inventoryItemId || 0) || null, codeMasked: String(record.codeMasked || '').trim(), credentialType: String(record.credentialType || '').trim(), outcome: String(record.outcome || '').trim(), 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 c08b5609..17330a4d 100644 --- a/apps/backend/src/services/admin/admin-task-read-helpers.ts +++ b/apps/backend/src/services/admin/admin-task-read-helpers.ts @@ -1,12 +1,9 @@ import { getTaskById } from '../../repositories/task-repo.js' -import { listTaskInventoryBindingSummariesByTaskIds } from '../../repositories/task-inventory-binding-repo.js' import { createHttpError } from '../../utils/http.js' import { safeParseJson } from './admin-query-utils.js' import { createAdminViewerContext, getTaskPrimaryClaimTokenId, - getTaskPrimaryInventoryItemId, - parseTaskContext, } from './admin-read-shared-helpers.js' import type { @@ -16,8 +13,6 @@ import type { import type { AdminTaskActionPayload } from '../../types/admin-write-models.js' import type { TaskEventRow, - TaskInventoryBindingRow, - TaskInventoryBindingSummaryRow, TaskRow, } from '../../types/repository-rows.js' import type { AdminViewerContext } from './admin-read-shared-helpers.js' @@ -29,10 +24,6 @@ type TaskBindingState = { userBindingStatus: string } -type TaskInventoryBindingLike = TaskInventoryBindingRow & { - inventory_item_status?: string -} - type OrderBindingSummary = { totalTaskCount: number systemBoundTaskCount: number @@ -72,7 +63,6 @@ export function mapAdminTaskSummary( } export function mapTaskActionPayload(task: any): AdminTaskActionPayload { - const inventoryItemId = getTaskPrimaryInventoryItemId(task) const primaryClaimTokenId = getTaskPrimaryClaimTokenId(task) return { @@ -82,7 +72,6 @@ export function mapTaskActionPayload(task: any): AdminTaskActionPayload { deliveryStatus: task.delivery_status, resultCode: task.result_code, resultMessage: task.result_message, - inventoryItemId, primaryClaimTokenId, lastError: task.last_error, updatedAt: task.updated_at, @@ -104,10 +93,10 @@ export async function getRequiredTask(taskId: number | string): Promise export function mapAdminTaskListItem( task: TaskRow, - bindingSummary: AdminTaskBindingSummary = createEmptyTaskBindingSummary(), viewerContext: AdminViewerContext = createAdminViewerContext(), ): AdminTaskListItem { const binding = buildTaskBindingState(task) + const bindingSummary = createEmptyTaskBindingSummary() return { taskId: task.id, @@ -134,8 +123,6 @@ export function mapAdminTaskListItem( lastError: task.last_error, createdAt: task.created_at, updatedAt: task.updated_at, - inventoryDisplayMasked: viewerContext.canViewSensitiveTaskData ? maskCode(task.primary_inventory_display_value) : '', - inventoryCredentialType: String(task.primary_inventory_credential_type || '').trim(), claimToken: viewerContext.canViewSensitiveTaskData ? (task.primary_claim_token || task.claim_token || '') : '', screenshotPath: viewerContext.role === 'support' ? '' : (task.screenshot_path || ''), } @@ -162,92 +149,18 @@ export function createEmptyTaskBindingSummary(): AdminTaskBindingSummary { } } -export async function getTaskBindingSummaryMap(taskIds: Array = []): Promise> { - const rows = await listTaskInventoryBindingSummariesByTaskIds(taskIds) - const output = new Map() - - for (const row of rows as TaskInventoryBindingSummaryRow[]) { - output.set(Number(row.task_id), { - totalBindingCount: Number(row.total_binding_count || 0), - reservedBindingCount: Number(row.reserved_binding_count || 0), - consumedBindingCount: Number(row.consumed_binding_count || 0), - releasedBindingCount: Number(row.released_binding_count || 0), - roleKeys: Array.isArray(row.role_keys) - ? row.role_keys.map((value) => String(value || '').trim()).filter(Boolean) - : [], - }) - } - - return output -} - -export function getTaskBindingSummary( - summaryMap: Map, - taskId: number | string, -): AdminTaskBindingSummary { - return summaryMap.get(Number(taskId)) || createEmptyTaskBindingSummary() -} - -export function createTaskBindingSummaryFromBindings( - bindings: Array> | null | undefined = [], -): AdminTaskBindingSummary { - const normalizedBindings = Array.isArray(bindings) ? bindings : [] - const roleKeys = Array.from(new Set(normalizedBindings - .map((binding) => String(binding?.role_key || '').trim()) - .filter(Boolean))) - - return { - totalBindingCount: normalizedBindings.length, - reservedBindingCount: normalizedBindings.filter((binding) => String(binding?.binding_status || '') === 'reserved').length, - consumedBindingCount: normalizedBindings.filter((binding) => String(binding?.binding_status || '') === 'consumed').length, - releasedBindingCount: normalizedBindings.filter((binding) => String(binding?.binding_status || '') === 'released').length, - roleKeys, - } -} - -export function mapAdminTaskInventoryBinding( - binding: TaskInventoryBindingLike, - task: TaskRow, - viewerContext: AdminViewerContext = createAdminViewerContext(), -): JsonRecord { - const metadata = safeParseJson(binding.metadata_json) - - return { - bindingId: binding.id, - inventoryItemId: binding.inventory_item_id, - roleKey: String(binding.role_key || '').trim(), - quantity: Math.max(1, Number(binding.quantity || 1)), - bindingStatus: String(binding.binding_status || '').trim(), - inventoryStatus: String(binding.inventory_item_status || '').trim(), - skuCode: String(binding.sku_code || '').trim(), - batchNo: String(binding.batch_no || '').trim(), - credentialType: String(binding.credential_type || 'claim_code').trim() || 'claim_code', - displayValue: viewerContext.canViewSensitiveTaskData ? String(binding.display_value || '').trim() : '', - invalidReason: String(binding.invalid_reason || '').trim(), - consumedAt: binding.consumed_at || null, - releasedAt: binding.released_at || null, - createdAt: binding.created_at, - updatedAt: binding.updated_at, - metadata: normalizeRecord(metadata), - isPrimary: Number(binding.inventory_item_id || 0) === getTaskPrimaryInventoryItemId(task), - canRelease: viewerContext.canManageSensitiveInventory && canReleaseTaskInventoryBinding(task, binding), - } -} - export function buildOrderBindingSummary( tasks: TaskRow[] | null | undefined, - taskBindingSummaryMap: Map = new Map(), ): OrderBindingSummary { 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 bindingSummaries = normalizedTasks.map((task) => getTaskBindingSummary(taskBindingSummaryMap, task.id)) - const totalBindingCount = bindingSummaries.reduce((sum, item) => sum + item.totalBindingCount, 0) - const reservedBindingCount = bindingSummaries.reduce((sum, item) => sum + item.reservedBindingCount, 0) - const consumedBindingCount = bindingSummaries.reduce((sum, item) => sum + item.consumedBindingCount, 0) - const releasedBindingCount = bindingSummaries.reduce((sum, item) => sum + item.releasedBindingCount, 0) + const totalBindingCount = 0 + const reservedBindingCount = 0 + const consumedBindingCount = 0 + const releasedBindingCount = 0 let systemBindingStatus = 'pending_binding' let userBindingStatus = 'not_started' @@ -280,7 +193,7 @@ export function buildOrderBindingSummary( userBindingStatus = 'binding_exception' } else if (systemBoundTaskCount > 0) { systemBindingStatus = 'system_bound' - } else if (taskBindings.some((item) => ['manual_review', 'retry_pending', 'waiting_inventory'].includes(item.systemBindingStatus))) { + } else if (taskBindings.some((item) => ['manual_review', 'retry_pending'].includes(item.systemBindingStatus))) { systemBindingStatus = 'binding_exception' } @@ -297,18 +210,6 @@ export function buildOrderBindingSummary( } } -function canReleaseTaskInventoryBinding(task: TaskRow | null | undefined, binding: TaskInventoryBindingLike | null | undefined): boolean { - if (!task || !binding) { - return false - } - - if (String(binding.binding_status || '').trim() !== 'reserved') { - return false - } - - return !['redeemed', 'expired'].includes(String(task.task_status || '').trim()) -} - function buildTaskBindingState(task: TaskRow | null | undefined): TaskBindingState { const normalizedStatus = String(task?.task_status || '').trim() @@ -327,10 +228,6 @@ function buildTaskBindingState(task: TaskRow | null | undefined): TaskBindingSta return { systemBindingStatus: 'pending_binding', userBindingStatus: 'not_started' } } - if (normalizedStatus === 'waiting_inventory') { - return { systemBindingStatus: 'waiting_inventory', userBindingStatus: 'not_started' } - } - if (normalizedStatus === 'pending_binding_prepare') { return { systemBindingStatus: 'pending_binding', userBindingStatus: 'not_started' } } @@ -391,24 +288,13 @@ function buildTaskBindingState(task: TaskRow | null | undefined): TaskBindingSta } function isTaskSystemBound(task: TaskRow | null | undefined): boolean { - return Boolean(task && (getTaskPrimaryInventoryItemId(task) || getTaskPrimaryClaimTokenId(task))) + return Boolean(task && getTaskPrimaryClaimTokenId(task)) } function normalizeRecord(value: unknown): JsonRecord { return value && typeof value === 'object' && !Array.isArray(value) ? value : {} } -function maskCode(value: unknown): string { - const text = String(value || '').trim() - if (!text) { - return '' - } - if (text.length <= 8) { - return `${text.slice(0, 2)}****${text.slice(-2)}` - } - return `${text.slice(0, 4)}****${text.slice(-4)}` -} - function getTaskRetryCount(task: TaskRow | null | undefined): number { return Number(task?.attempt_count || 0) } diff --git a/apps/backend/src/services/admin/admin-webhook-read-helpers.test.ts b/apps/backend/src/services/admin/admin-webhook-read-helpers.test.ts deleted file mode 100644 index 496d098c..00000000 --- a/apps/backend/src/services/admin/admin-webhook-read-helpers.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import test from 'node:test' -import assert from 'node:assert/strict' - -import { resolveWebhookVisibilityLevel } from './admin-webhook-read-helpers.js' - -test('resolveWebhookVisibilityLevel marks ignored process errors as ignored', () => { - assert.equal(resolveWebhookVisibilityLevel('ignored_unconfigured_product'), 'ignored') - assert.equal(resolveWebhookVisibilityLevel('ignored_duplicate_event'), 'ignored') -}) - -test('resolveWebhookVisibilityLevel keeps normal and failed events as important', () => { - assert.equal(resolveWebhookVisibilityLevel(''), 'important') - assert.equal(resolveWebhookVisibilityLevel('invalid_signature'), 'important') -}) diff --git a/apps/backend/src/services/admin/admin-webhook-read-helpers.ts b/apps/backend/src/services/admin/admin-webhook-read-helpers.ts deleted file mode 100644 index d7fddcc0..00000000 --- a/apps/backend/src/services/admin/admin-webhook-read-helpers.ts +++ /dev/null @@ -1,235 +0,0 @@ -import { listTasksByOrderId } from '../../repositories/task-repo.js' -import { formatFenToAmount, parseAmountToFen } from '../../utils/money.js' -import { safeParseJson } from './admin-query-utils.js' -import { resolveDisplayShopName } from './admin-read-shared-helpers.js' - -import type { AdminWebhookEventListItem } from '../../types/admin-read-models.js' -import type { WebhookEventRow } from '../../types/repository-rows.js' - -type JsonRecord = Record - -type MapAdminWebhookEventOptions = { - includeRaw?: boolean -} - -export async function mapAdminWebhookEvent( - item: WebhookEventRow, - { includeRaw = false }: MapAdminWebhookEventOptions = {}, -): Promise { - const headers = normalizeRecord(safeParseJson(item.headers_json)) - const query = normalizeRecord(safeParseJson(item.query_json)) - const body = normalizeRecord(safeParseJson(item.body_json)) - const payload = extractWebhookPayload(body) - const itemSources = extractWebhookItemSources(payload) - const rawAmount = pickFirstNonEmpty([ - payload.total_fee, - payload.totalFee, - payload.TotalFee, - payload.pay_fee, - payload.payFee, - payload.Payment, - payload.payment, - payload.buy_amount, - payload.buyAmount, - ]) - const totalAmountFen = parseAmountToFen(rawAmount) - const taskCount = item.related_order_id ? (await listTasksByOrderId(item.related_order_id)).length : 0 - const shopId = item.shop_id || pickFirstNonEmpty([ - payload.PlatformUserId, - payload.platformUserId, - payload.platform_user_id, - payload.seller_id, - payload.sellerId, - payload.SellerId, - payload.seller_nick, - payload.sellerNick, - payload.SellerNick, - ]) - - const mapped = { - eventId: item.id, - provider: item.provider || '', - platform: item.platform, - platformRaw: pickFirstNonEmpty([ - query.fromPlatform, - query.from_platform, - body.fromPlatform, - body.from_platform, - payload.fromPlatform, - payload.FromPlatform, - payload.platform, - payload.Platform, - ]), - shopId, - shopName: resolveDisplayShopName( - item.provider, - shopId, - item.shop_name || pickFirstNonEmpty([ - payload.shopName, - payload.shop_name, - payload.ShopName, - payload.seller_nick, - payload.sellerNick, - payload.SellerNick, - ]), - ), - eventType: item.event_type, - eventKey: item.event_key, - signatureValid: Boolean(item.signature_valid), - processed: Boolean(item.processed), - processError: item.process_error, - visibilityLevel: resolveWebhookVisibilityLevel(item.process_error), - relatedOrderId: item.related_order_id, - createdAt: item.created_at, - platformOrderId: resolveWebhookPlatformOrderId(item, payload), - buyerId: pickFirstNonEmpty([ - payload.buyer_id, - payload.buyerId, - payload.BuyerId, - payload.openid, - payload.BuyerOpenUid, - payload.buyer_open_uid, - payload.buyerOpenUid, - ]), - buyerName: pickFirstNonEmpty([ - payload.buyer_name, - payload.buyerName, - payload.BuyerName, - payload.nick, - payload.BuyerNick, - payload.buyer_nick, - payload.buyerNick, - ]), - totalAmount: rawAmount ? formatFenToAmount(totalAmountFen) : '', - totalAmountFen, - currency: pickFirstNonEmpty([payload.currency, payload.Currency, 'CNY']) || 'CNY', - itemCount: itemSources.length, - taskCount, - messageText: pickFirstNonEmpty([ - payload.MsgTypeDes, - payload.msgTypeDes, - payload.msg_type_desc, - payload.message, - payload.msg, - ]), - requestTimestamp: pickFirstNonEmpty([query.timestamp]), - requestSign: pickFirstNonEmpty([query.sign]), - aopic: pickFirstNonEmpty([query.aopic]), - sourceHost: pickFirstNonEmpty([headers['x-forwarded-host'], headers.host]), - sourceIp: pickFirstNonEmpty([headers['x-forwarded-for'], headers['x-real-ip']]), - userAgent: pickFirstNonEmpty([headers['user-agent'], headers['User-Agent']]), - } - - if (!includeRaw) { - return mapped - } - - return { - ...mapped, - headers, - query, - body, - payload, - } -} - -function extractWebhookPayload(body: JsonRecord): JsonRecord { - const candidates = [ - body.data, - body.Data, - body.payload, - body.Payload, - body.message, - body.Message, - ] - - for (const candidate of candidates) { - if (candidate && typeof candidate === 'object' && !Array.isArray(candidate)) { - return normalizeRecord(candidate) - } - } - - return body -} - -function resolveWebhookPlatformOrderId(item: WebhookEventRow, payload: JsonRecord): string { - const fromPayload = pickFirstNonEmpty([ - payload.biz_order_id, - payload.bizOrderId, - payload.BizOrderId, - payload.tid, - payload.Tid, - payload.order_id, - payload.orderId, - payload.OrderId, - payload.oid, - payload.Oid, - ]) - - if (fromPayload) { - return fromPayload - } - - const parts = String(item.event_key || '').split(':').map((part) => part.trim()).filter(Boolean) - return parts.find((part) => /^\d{6,}$/.test(part)) || '' -} - -function extractWebhookItemSources(payload: unknown): JsonRecord[] { - const normalizedPayload = normalizeRecord(payload) - const candidates = [ - normalizedPayload.orders, - normalizedPayload.Orders, - normalizedPayload.items, - normalizedPayload.Items, - normalizedPayload.order_items, - ] - - for (const candidate of candidates) { - if (Array.isArray(candidate) && candidate.length > 0) { - return candidate.map((item) => normalizeRecord(item)).filter((item) => Object.keys(item).length > 0) - } - } - - if ( - pickFirstNonEmpty([ - normalizedPayload.item_id, - normalizedPayload.itemId, - normalizedPayload.ItemId, - normalizedPayload.num_iid, - normalizedPayload.numIid, - normalizedPayload.NumIid, - ]) - ) { - return [normalizedPayload] - } - - return [] -} - -export function resolveWebhookVisibilityLevel(processError: unknown): string { - const normalized = String(processError || '').trim() - - if (normalized.startsWith('ignored_')) { - return 'ignored' - } - - return 'important' -} - -function normalizeRecord(value: unknown): JsonRecord { - return value && typeof value === 'object' && !Array.isArray(value) ? value : {} -} - -function pickFirstNonEmpty(values: unknown[]): string { - for (const value of values) { - if (typeof value === 'string' && value.trim()) { - return value.trim() - } - - if (typeof value === 'number' && Number.isFinite(value)) { - return String(value) - } - } - - return '' -} diff --git a/apps/backend/src/services/admin/write/inventory.ts b/apps/backend/src/services/admin/write/inventory.ts deleted file mode 100644 index e24215b3..00000000 --- a/apps/backend/src/services/admin/write/inventory.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { - createInventoryItems, - invalidateInventoryItem, - listInventoryItems, - releaseReservedInventoryItem, -} from '../../../repositories/inventory-repo.js' -import { createHttpError } from '../../../utils/http.js' -import { nowIso } from '../../../utils/time.js' -import { - getRequiredInventoryItem, - mapAdminInventoryListItem, -} from '../admin-inventory-read-helpers.js' - -import type { AdminEntityIdInput } from '../../../types/admin-read-inputs.js' -import type { - AdminInventoryCreateInput, - AdminInventoryImportInput, - AdminInventoryImportRowInput, - AdminInventoryInvalidateInput, -} from '../../../types/admin-write-inputs.js' -import type { - AdminInventoryImportResponse, - AdminInventoryMutationResponse, -} from '../../../types/admin-write-models.js' -import type { InventoryItemRow } from '../../../types/repository-rows.js' - -type NormalizedInventoryImportRow = Required> - -export async function createAdminInventoryItem( - payload: AdminInventoryCreateInput = {}, -): Promise { - const skuCode = String(payload.skuCode || '').trim() - const displayValue = String(payload.displayValue || '').trim() - const batchNo = String(payload.batchNo || '').trim() - const credentialType = String(payload.credentialType || 'claim_code').trim() || 'claim_code' - const inventoryGroupCode = String(payload.inventoryGroupCode || '').trim() - - if (!skuCode || !displayValue) { - throw createHttpError('缺少 skuCode 或 displayValue', { - statusCode: 400, - errorCode: 'admin_inventory_create_invalid', - }) - } - - const now = nowIso() - const created = await createInventoryItems([ - { - skuCode, - displayValue, - batchNo, - credentialType, - inventoryGroupCode, - createdAt: now, - updatedAt: now, - }, - ]) - - if (created === 0) { - throw createHttpError('库存凭据已存在,不能重复新增', { - statusCode: 409, - errorCode: 'admin_inventory_duplicate', - }) - } - - const { items } = await listInventoryItems({ - page: 1, - pageSize: 20, - skuCode, - inventoryGroupCode, - }) - const createdItem = items.find((item) => item.display_value === displayValue && item.inventory_group_code === inventoryGroupCode) || null - - return { - inventoryItem: createdItem ? await mapAdminInventoryListItem(createdItem) : null, - } -} - -export async function importAdminInventoryItems( - payload: AdminInventoryImportInput = {}, -): Promise { - const rows = normalizeInventoryImportRows(payload) - - if (rows.length === 0) { - throw createHttpError('没有可导入的库存凭据数据', { - statusCode: 400, - errorCode: 'admin_inventory_import_empty', - }) - } - - const now = nowIso() - const normalizedRows = rows.map((row) => ({ - skuCode: row.skuCode, - batchNo: row.batchNo, - displayValue: row.displayValue, - credentialType: row.credentialType, - inventoryGroupCode: row.inventoryGroupCode, - createdAt: now, - updatedAt: now, - })) - - const created = await createInventoryItems(normalizedRows) - - return { - total: normalizedRows.length, - created, - duplicated: normalizedRows.length - created, - } -} - -export async function releaseAdminInventoryItem( - inventoryItemId: AdminEntityIdInput, -): Promise { - const inventoryItem = await getRequiredInventoryItem(inventoryItemId) - - if (inventoryItem.status !== 'reserved') { - throw createHttpError('当前库存项不是预占状态,不能释放', { - statusCode: 409, - errorCode: 'admin_inventory_release_not_allowed', - }) - } - - const updated = await releaseReservedInventoryItem(inventoryItem.id, nowIso()) - - return { - inventoryItem: await mapAdminInventoryListItem(updated as InventoryItemRow), - } -} - -export async function invalidateAdminInventoryItem( - inventoryItemId: AdminEntityIdInput, - payload: AdminInventoryInvalidateInput = {}, -): Promise { - const inventoryItem = await getRequiredInventoryItem(inventoryItemId) - - if (inventoryItem.status !== 'available') { - throw createHttpError('只有可用库存项才能作废,请先释放预占', { - statusCode: 409, - errorCode: 'admin_inventory_invalidate_not_allowed', - }) - } - - const reason = String(payload.reason || '').trim() || '后台手动作废' - const updated = await invalidateInventoryItem(inventoryItem.id, reason, nowIso()) - - return { - inventoryItem: await mapAdminInventoryListItem(updated as InventoryItemRow), - } -} - -function normalizeInventoryImportRows(payload: AdminInventoryImportInput): NormalizedInventoryImportRow[] { - const rows: NormalizedInventoryImportRow[] = [] - const directRows = Array.isArray(payload.rows) ? payload.rows : [] - const bulkCodes = Array.isArray(payload.codes) ? payload.codes : [] - - if (directRows.length > 0) { - for (const row of directRows) { - const skuCode = String(row?.skuCode || '').trim() - const displayValue = String(row?.displayValue || '').trim() - const batchNo = String(row?.batchNo || '').trim() - const credentialType = String(row?.credentialType || payload.credentialType || 'claim_code').trim() || 'claim_code' - const inventoryGroupCode = String(row?.inventoryGroupCode || payload.inventoryGroupCode || '').trim() - - if (!skuCode || !displayValue) { - continue - } - - rows.push({ skuCode, displayValue, batchNo, credentialType, inventoryGroupCode }) - } - } - - if (bulkCodes.length > 0) { - const skuCode = String(payload.skuCode || '').trim() - const batchNo = String(payload.batchNo || '').trim() - const credentialType = String(payload.credentialType || 'claim_code').trim() || 'claim_code' - const inventoryGroupCode = String(payload.inventoryGroupCode || '').trim() - - if (!skuCode) { - throw createHttpError('批量导入时缺少 skuCode', { - statusCode: 400, - errorCode: 'admin_inventory_import_missing_sku', - }) - } - - for (const rawValue of bulkCodes) { - const displayValue = String(rawValue || '').trim() - if (!displayValue) { - continue - } - rows.push({ skuCode, displayValue, batchNo, credentialType, inventoryGroupCode }) - } - } - - return dedupeRows(rows) -} - -function dedupeRows(rows: NormalizedInventoryImportRow[]): NormalizedInventoryImportRow[] { - const seen = new Set() - const output: NormalizedInventoryImportRow[] = [] - - for (const row of rows) { - const key = `${row.skuCode}::${row.credentialType || 'claim_code'}::${row.displayValue}` - if (seen.has(key)) { - continue - } - seen.add(key) - output.push(row) - } - - return output -} diff --git a/apps/backend/src/services/admin/write/kuaishou-cloud-actions.ts b/apps/backend/src/services/admin/write/kuaishou-cloud-actions.ts index 20b270c3..a3818bbb 100644 --- a/apps/backend/src/services/admin/write/kuaishou-cloud-actions.ts +++ b/apps/backend/src/services/admin/write/kuaishou-cloud-actions.ts @@ -198,7 +198,6 @@ export async function prepareAdminTaskKuaishouCloudFulfillment( const updatedTask = await updateTask(task.id, { task_status: 'waiting_binding', - inventory_status: 'not_required', user_action_status: 'pending', claim_token: claimLinkState.token || task.claim_token || '', claim_expires_at: claimLinkState.expiredAt || getTaskClaimExpiresAt(task), diff --git a/apps/backend/src/services/admin/write/shared.ts b/apps/backend/src/services/admin/write/shared.ts index acf143b5..7cb37a44 100644 --- a/apps/backend/src/services/admin/write/shared.ts +++ b/apps/backend/src/services/admin/write/shared.ts @@ -3,7 +3,6 @@ import { maskCode as maskCodeValue, maskPhone as maskPhoneValue, } from '../../../utils/masking.js' -import { parseTaskContext } from '../admin-read-shared-helpers.js' import type { TaskRow } from '../../../types/repository-rows.js' @@ -26,12 +25,6 @@ export function maskCode(value: unknown): string { return maskCodeValue(value) } -export function resolveTaskInventoryGroupCodes(task: TaskRow): string[] | null { - const taskContext = parseTaskContext(task) - const inventoryGroupCode = String(taskContext?.inventoryGroupCode || '').trim() - return inventoryGroupCode ? [inventoryGroupCode] : null -} - export function isRecoverableTaskSessionCloseError(error: ErrorLike | null | undefined): boolean { const errorCode = String(error?.errorCode || error?.code || '').trim() return errorCode === 'session_not_found' || errorCode === 'session_closed' diff --git a/apps/backend/src/services/claim/kuaishou-cloud-claim-context.ts b/apps/backend/src/services/claim/kuaishou-cloud-claim-context.ts index 1e9b729a..8f40357a 100644 --- a/apps/backend/src/services/claim/kuaishou-cloud-claim-context.ts +++ b/apps/backend/src/services/claim/kuaishou-cloud-claim-context.ts @@ -1,5 +1,4 @@ import { findClaimTokenByToken, updateClaimToken } from '../../repositories/claim-token-repo.js' -import { releaseReservedInventoryItem } from '../../repositories/inventory-repo.js' import { getOrderItemById } from '../../repositories/order-item-repo.js' import { getOrderById } from '../../repositories/order-repo.js' import { findTaskByClaimTokenId, updateTask } from '../../repositories/task-repo.js' @@ -228,15 +227,10 @@ async function expireClaimContext(claimToken, task) { let nextTask = task if (!CLAIM_TERMINAL_STATUSES.has(String(task.task_status || '')) && task.task_status !== 'redeemed') { - if (task.primary_inventory_item_id) { - await releaseReservedInventoryItem(task.primary_inventory_item_id, now) - } - nextTask = await updateTask(task.id, { task_status: 'expired', - inventory_status: 'pending', user_action_status: 'expired', - last_error: '领取链接已过期,预占库存项已释放', + last_error: '领取链接已过期', updated_at: now, }) } diff --git a/apps/backend/src/services/fulfillment/kuaishou-cloud-task-service.ts b/apps/backend/src/services/fulfillment/kuaishou-cloud-task-service.ts index 702f6d75..51aca310 100644 --- a/apps/backend/src/services/fulfillment/kuaishou-cloud-task-service.ts +++ b/apps/backend/src/services/fulfillment/kuaishou-cloud-task-service.ts @@ -275,7 +275,6 @@ export async function prepareKuaishouCloudFulfillmentTask( const updatedTask = await updateTask(task.id, { task_status: "waiting_binding", - inventory_status: "not_required", user_action_status: "pending_claim", claim_token: claimLinkState.token || task.claim_token || "", claim_expires_at: claimLinkState.expiredAt || getTaskClaimExpiresAt(task), diff --git a/apps/backend/src/services/notification/domain-notifications.ts b/apps/backend/src/services/notification/domain-notifications.ts index 86e531c9..ab57db72 100644 --- a/apps/backend/src/services/notification/domain-notifications.ts +++ b/apps/backend/src/services/notification/domain-notifications.ts @@ -221,14 +221,14 @@ export function notifyClaimRedeemNeedsAttention({ const orderRecord = toRecord(order) return notifyInternalSafely({ - title: status === 'waiting_inventory' ? '库存不足,任务等待补货' : '兑换失败,任务需关注', + title: '兑换失败,任务需关注', body: [ formatTaskLine(taskRecord), `订单:${String(orderRecord.platform_order_id || taskRecord.platform_order_id || '').trim() || '-'}`, `状态:${String(status || taskRecord.task_status || '').trim() || '-'}`, `原因:${String(errorMessage || taskRecord.last_error || '').trim() || '-'}`, ].join('\n'), - category: status === 'waiting_inventory' ? 'task_waiting_inventory' : 'claim_redeem_failed', + category: 'claim_redeem_failed', cooldownKey: ['claim_redeem_needs_attention', taskRecord.id || '', status].join(':'), }) } diff --git a/apps/backend/src/services/open-91/order-create-service.ts b/apps/backend/src/services/open-91/order-create-service.ts index 7b7cc553..68f96387 100644 --- a/apps/backend/src/services/open-91/order-create-service.ts +++ b/apps/backend/src/services/open-91/order-create-service.ts @@ -1,5 +1,5 @@ import { upsertOrderFromSource } from '../order/order-service.js' -import { logWebhook } from '../../utils/logger.js' +import { logIntegration } from '../../utils/logger.js' import { parseAmountToFen } from '../../utils/money.js' import { buildOpen91SourceEvent, @@ -45,7 +45,7 @@ export async function createOpen91Order(payload = {}, { requestId = '' } = {}) { sourceLabel: 'open91', }) - logWebhook('[open-91/create]', '91卡券下单已处理', { + logIntegration('[open-91/create]', '91卡券下单已处理', { requestId, orderNo: normalized.orderNo, productNo: normalized.productNo, @@ -58,7 +58,7 @@ export async function createOpen91Order(payload = {}, { requestId = '' } = {}) { if (result.ignored) { const pending = await upsertOpen91PendingOrder(normalized, config) - logWebhook('[open-91/create]', '91卡券订单已进入待补全队列', { + logIntegration('[open-91/create]', '91卡券订单已进入待补全队列', { requestId, orderNo: normalized.orderNo, productNo: normalized.productNo, diff --git a/apps/backend/src/services/open-91/order-query-service.ts b/apps/backend/src/services/open-91/order-query-service.ts index a0202494..0b39f832 100644 --- a/apps/backend/src/services/open-91/order-query-service.ts +++ b/apps/backend/src/services/open-91/order-query-service.ts @@ -2,7 +2,7 @@ import { findOrderByPlatformOrderId } from '../../repositories/order-repo.js' import { listTasksByOrderId } from '../../repositories/task-repo.js' import { buildClaimUrl } from '../claim/claim-service.js' import { ensureTaskClaimLink } from '../fulfillment/kuaishou-cloud-task-service.js' -import { logWebhook } from '../../utils/logger.js' +import { logIntegration } from '../../utils/logger.js' import { OPEN_91_MANUAL_FAILED_STATUS, OPEN_91_PENDING_CONFIG_STATUS, @@ -111,7 +111,7 @@ export async function queryOpen91Order(payload = {}, { requestId = '' } = {}) { readyTaskIds, }) - logWebhook('[open-91/query]', '91卡券订单查询已处理', { + logIntegration('[open-91/query]', '91卡券订单查询已处理', { requestId, orderNo: normalized.orderNo, orderId: order.id, @@ -154,7 +154,7 @@ export async function queryOpen91Order(payload = {}, { requestId = '' } = {}) { cards: encryptedCards, } - logWebhook('[open-91/query]', '91卡券订单查询返回 cards 已生成', { + logIntegration('[open-91/query]', '91卡券订单查询返回 cards 已生成', { requestId, orderNo: normalized.orderNo, orderId: order.id, diff --git a/apps/backend/src/services/order/delivery-task-service.test.ts b/apps/backend/src/services/order/delivery-task-service.test.ts index 1c3d5298..a0b8d4bf 100644 --- a/apps/backend/src/services/order/delivery-task-service.test.ts +++ b/apps/backend/src/services/order/delivery-task-service.test.ts @@ -31,58 +31,12 @@ function createTaskFixture(patch = {}) { requires_claim: true, claim_token: '', primary_claim_token_id: null, - primary_inventory_item_id: null, last_error: '', - context_json: JSON.stringify({ - inventorySkuCode: 'dnf-cdk-a', - primaryRequirement: { - roleKey: 'primary_code', - credentialType: 'claim_code', - }, - }), + context_json: '{}', ...patch, } } -test('syncDeliveryTasksForOrderWithDeps moves paid claim task to waiting inventory when reservation fails', async () => { - const updates = [] - const reserveCalls = [] - - const result = await syncDeliveryTasksForOrderWithDeps(paidOrder, orderItems, { - listTasksByOrderId: async () => [createTaskFixture()], - reserveInventoryForTask: async (payload) => { - reserveCalls.push(payload) - return null - }, - updateTask: async (taskId, patch) => { - updates.push({ taskId, patch }) - return { id: taskId, ...patch } - }, - nowIso: () => '2026-04-14T12:00:00.000Z', - }) - - assert.deepEqual(reserveCalls, [ - { - skuCode: 'dnf-cdk-a', - taskId: 30, - credentialType: 'claim_code', - roleKey: 'primary_code', - }, - ]) - assert.deepEqual(updates, [ - { - taskId: 30, - patch: { - task_status: 'waiting_inventory', - inventory_status: 'pending', - last_error: '库存不足,等待可用库存凭据', - updated_at: '2026-04-14T12:00:00.000Z', - }, - }, - ]) - assert.equal(result[0]?.task_status, 'waiting_inventory') -}) - test('syncDeliveryTasksForOrderWithDeps sends manual dispatch tasks to manual review', async () => { const notifications = [] const updates = [] @@ -150,7 +104,6 @@ test('syncDeliveryTasksForOrderWithDeps prepares kuaishou cloud task with claim taskId: 30, patch: { task_status: 'pending_binding_prepare', - inventory_status: 'not_required', claim_token: 'claim-token', claim_expires_at: '2026-04-15T12:00:00.000Z', user_action_status: 'pending_claim', diff --git a/apps/backend/src/services/order/delivery-task-service.ts b/apps/backend/src/services/order/delivery-task-service.ts index 5397e77f..f94d7048 100644 --- a/apps/backend/src/services/order/delivery-task-service.ts +++ b/apps/backend/src/services/order/delivery-task-service.ts @@ -1,14 +1,12 @@ import { createTask, listTasksByOrderId, updateTask } from '../../repositories/task-repo.js' import { - listFulfillmentProfileRequirements, resolveFulfillmentBinding, } from '../../repositories/fulfillment-profile-repo.js' -import { reserveInventoryForTask } from './inventory-service.js' import { createTaskClaimToken } from '../claim/claim-service.js' import { notifyTaskAutoManualReview } from '../notification/domain-notifications.js' import { nowIso } from '../../utils/time.js' import { randomId } from '../../utils/random.js' -import type { InventoryItemRow, OrderItemRow, OrderRow, TaskRow } from '../../types/repository-rows.js' +import type { OrderItemRow, OrderRow, TaskRow } from '../../types/repository-rows.js' type JsonObject = Record @@ -31,15 +29,6 @@ type FulfillmentBindingLike = { [key: string]: unknown } -type FulfillmentRequirementLike = { - role_key?: string - roleKey?: string - credential_type?: string - credentialType?: string - is_required?: boolean - [key: string]: unknown -} - type DeliveryTaskRow = TaskRow & { skuCode?: string skuName?: string @@ -49,21 +38,12 @@ type DeliveryTaskDeps = { createTask?: typeof createTask listTasksByOrderId?: typeof listTasksByOrderId updateTask?: typeof updateTask - listFulfillmentProfileRequirements?: ( - profileId: number | string, - ) => Promise resolveFulfillmentBinding?: (input: { skuCode: string provider?: string platform?: string shopId?: string }) => Promise - reserveInventoryForTask?: (input: { - skuCode: string - taskId: number | string - credentialType?: string - roleKey?: string - }) => Promise createTaskClaimToken?: (taskId: number | string) => Promise notifyTaskAutoManualReview?: (payload: { task: unknown @@ -77,18 +57,12 @@ type DeliveryTaskDeps = { type RuntimeDeliveryTaskDeps = Required> type TaskContext = { - inventorySkuCode?: string - primaryRequirement?: { - roleKey?: string - credentialType?: string - } | null [key: string]: unknown } @@ -108,9 +82,7 @@ export async function syncDeliveryTasksForOrderWithDeps( createTask: createDeliveryTask = createTask, listTasksByOrderId: listTasks = listTasksByOrderId, updateTask: updateDeliveryTask = updateTask, - listFulfillmentProfileRequirements: listProfileRequirements = listFulfillmentProfileRequirements, resolveFulfillmentBinding: resolveBinding = resolveFulfillmentBinding, - reserveInventoryForTask: reserveInventory = reserveInventoryForTask, createTaskClaimToken: createClaimToken = createTaskClaimToken, notifyTaskAutoManualReview: notifyManualReview = notifyTaskAutoManualReview, nowIso: getNowIso = nowIso, @@ -119,7 +91,6 @@ export async function syncDeliveryTasksForOrderWithDeps( const runtimeDeps: RuntimeDeliveryTaskDeps = { updateTask: updateDeliveryTask, - reserveInventoryForTask: reserveInventory, createTaskClaimToken: createClaimToken, notifyTaskAutoManualReview: notifyManualReview, nowIso: getNowIso, @@ -154,8 +125,6 @@ export async function syncDeliveryTasksForOrderWithDeps( continue } const profile = binding - const requirements = await listProfileRequirements(profile.profile_id || profile.id) - const primaryRequirement = requirements.find((requirement) => requirement.is_required !== false) || requirements[0] || null const quantity = Math.max(1, Number(item.quantity || 1)) const fulfillmentConfig = parseJsonObject(profile.config_json) const cloudtentaclesConfig = parseJsonObject(fulfillmentConfig.cloudtentacles) @@ -181,7 +150,6 @@ export async function syncDeliveryTasksForOrderWithDeps( profileId: Number(profile.profile_id || profile.id), executorKey: String(profile.executor_key || 'manual_dispatch'), taskStatus: initialStatus, - inventoryStatus: profile.requires_claim ? 'pending' : 'not_required', deliveryStatus: 'pending', resultCode: '', resultMessage: '', @@ -197,13 +165,6 @@ export async function syncDeliveryTasksForOrderWithDeps( profileName: String(profile.profile_name || profile.name || ''), skuCode: item.sku_code, skuName: item.sku_name, - inventorySkuCode: item.sku_code, - primaryRequirement: primaryRequirement - ? { - roleKey: String(primaryRequirement.role_key || primaryRequirement.roleKey || 'primary_code'), - credentialType: String(primaryRequirement.credential_type || primaryRequirement.credentialType || 'claim_code'), - } - : null, kuaishouCloudFulfillment: isKuaishouCloudExecutor(profile.executor_key) ? { flowType: 'kuaishou_cloud_fulfillment', @@ -303,16 +264,12 @@ async function preparePaidTask( ): Promise { const { updateTask: updateDeliveryTask = updateTask, - reserveInventoryForTask: reserveInventory = reserveInventoryForTask, createTaskClaimToken: createClaimToken = createTaskClaimToken, notifyTaskAutoManualReview: notifyManualReview = notifyTaskAutoManualReview, nowIso: getNowIso = nowIso, } = deps const now = getNowIso() - const taskContext = parseTaskContext(task) - const primaryRequirement = taskContext.primaryRequirement || null - const inventorySkuCode = String(taskContext.inventorySkuCode || task.skuCode || '').trim() if (['link_generated', 'claimed', 'role_confirmed', 'redeeming', 'redeemed', 'closed'].includes(task.task_status)) { return task @@ -344,7 +301,6 @@ async function preparePaidTask( return updateDeliveryTask(task.id, { task_status: 'pending_binding_prepare', - inventory_status: 'not_required', claim_token: claimToken.token, claim_expires_at: claimToken.expired_at, user_action_status: 'pending_claim', @@ -357,7 +313,6 @@ async function preparePaidTask( const lastError = task.last_error || '当前任务需要人工履约处理' const updatedTask = await updateDeliveryTask(task.id, { task_status: 'manual_review', - inventory_status: 'not_required', user_action_status: 'not_required', last_error: lastError, updated_at: now, @@ -370,71 +325,7 @@ async function preparePaidTask( return updatedTask } - if (!inventorySkuCode) { - const lastError = '未匹配到 SKU,无法为任务分配库存凭据' - const updatedTask = await updateDeliveryTask(task.id, { - task_status: 'manual_review', - last_error: lastError, - updated_at: now, - }) - await notifyManualReview({ - task: updatedTask || task, - reason: lastError, - source: 'missing_sku', - }) - return updatedTask - } - - if (task.primary_inventory_item_id && task.primary_claim_token_id) { - return updateDeliveryTask(task.id, { - task_status: 'link_generated', - last_error: '', - updated_at: now, - }) - } - - if (!primaryRequirement?.credentialType) { - const lastError = '履约档案未配置库存要求,无法自动分配库存' - const updatedTask = await updateDeliveryTask(task.id, { - task_status: 'manual_review', - last_error: lastError, - updated_at: now, - }) - await notifyManualReview({ - task: updatedTask || task, - reason: lastError, - source: 'missing_inventory_requirement', - }) - return updatedTask - } - - const reserved = await reserveInventory({ - skuCode: inventorySkuCode, - taskId: task.id, - credentialType: primaryRequirement.credentialType, - roleKey: primaryRequirement.roleKey || 'primary_code', - }) - - if (!reserved) { - return updateDeliveryTask(task.id, { - task_status: 'waiting_inventory', - inventory_status: 'pending', - last_error: '库存不足,等待可用库存凭据', - updated_at: now, - }) - } - - const claimToken = await createClaimToken(task.id) - - return updateDeliveryTask(task.id, { - task_status: 'link_generated', - inventory_status: 'reserved', - claim_token: claimToken.token, - claim_expires_at: claimToken.expired_at, - user_action_status: 'pending_claim', - last_error: '', - updated_at: now, - }) + return task } function resolvePaidTaskStatus(profile: FulfillmentBindingLike | null | undefined): string { diff --git a/apps/backend/src/services/order/inventory-service.test.ts b/apps/backend/src/services/order/inventory-service.test.ts deleted file mode 100644 index 4d5e1daa..00000000 --- a/apps/backend/src/services/order/inventory-service.test.ts +++ /dev/null @@ -1,110 +0,0 @@ -import test from 'node:test' -import assert from 'node:assert/strict' - -import { - releaseReservedInventoryItemsWithDeps, - reserveInventoryForTaskWithDeps, -} from './inventory-service.js' - -test('reserveInventoryForTaskWithDeps returns null when skuCode is missing', async () => { - let findCalled = false - - const result = await reserveInventoryForTaskWithDeps( - { skuCode: '', taskId: 12 }, - { - findFirstAvailableInventoryItemBySkuCode: async () => { - findCalled = true - return null - }, - }, - ) - - assert.equal(result, null) - assert.equal(findCalled, false) -}) - -test('reserveInventoryForTaskWithDeps reserves first available inventory item with defaults', async () => { - const calls = [] - const result = await reserveInventoryForTaskWithDeps( - { skuCode: 'dnf-cdk-a', taskId: 88 }, - { - findFirstAvailableInventoryItemBySkuCode: async (skuCode, credentialType, inventoryGroupCodes) => { - calls.push(['find', skuCode, credentialType, inventoryGroupCodes]) - return { id: 321 } - }, - assignReservedInventoryItem: async (inventoryItemId, taskId, updatedAt, roleKey) => { - calls.push(['assign', inventoryItemId, taskId, updatedAt, roleKey]) - return { id: inventoryItemId, reserved_by_task_id: taskId, role_key: roleKey, updated_at: updatedAt } - }, - nowIso: () => '2026-04-14T12:00:00.000Z', - }, - ) - - assert.deepEqual(calls, [ - ['find', 'dnf-cdk-a', 'claim_code', null], - ['assign', 321, 88, '2026-04-14T12:00:00.000Z', 'primary_code'], - ]) - assert.equal(result?.id, 321) - assert.equal(result?.reserved_by_task_id, 88) -}) - -test('reserveInventoryForTaskWithDeps forwards inventory group filters', async () => { - const calls = [] - - await reserveInventoryForTaskWithDeps( - { skuCode: 'dnf-cdk-a', taskId: 88, inventoryGroupCodes: ['A组', 'B组'] }, - { - findFirstAvailableInventoryItemBySkuCode: async (skuCode, credentialType, inventoryGroupCodes) => { - calls.push(['find', skuCode, credentialType, inventoryGroupCodes]) - return { id: 321 } - }, - assignReservedInventoryItem: async () => ({ id: 321 }), - nowIso: () => '2026-04-14T12:00:00.000Z', - }, - ) - - assert.deepEqual(calls, [ - ['find', 'dnf-cdk-a', 'claim_code', ['A组', 'B组']], - ]) -}) - -test('reserveInventoryForTaskWithDeps returns null when restricted inventory groups are empty', async () => { - let findCalled = false - - const result = await reserveInventoryForTaskWithDeps( - { skuCode: 'dnf-cdk-a', taskId: 12, inventoryGroupCodes: [] }, - { - findFirstAvailableInventoryItemBySkuCode: async () => { - findCalled = true - return null - }, - }, - ) - - assert.equal(result, null) - assert.equal(findCalled, false) -}) - -test('releaseReservedInventoryItemsWithDeps skips empty ids and keeps successful releases only', async () => { - const calls = [] - const result = await releaseReservedInventoryItemsWithDeps( - [0, null, 11, 12, '', 13], - '2026-04-14T13:00:00.000Z', - { - releaseReservedInventoryItem: async (inventoryItemId, updatedAt) => { - calls.push([inventoryItemId, updatedAt]) - return inventoryItemId === 12 ? null : { id: inventoryItemId } - }, - }, - ) - - assert.deepEqual(calls, [ - [11, '2026-04-14T13:00:00.000Z'], - [12, '2026-04-14T13:00:00.000Z'], - [13, '2026-04-14T13:00:00.000Z'], - ]) - assert.deepEqual(result, [ - { id: 11 }, - { id: 13 }, - ]) -}) diff --git a/apps/backend/src/services/order/inventory-service.ts b/apps/backend/src/services/order/inventory-service.ts deleted file mode 100644 index 2c9086ac..00000000 --- a/apps/backend/src/services/order/inventory-service.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { - assignReservedInventoryItem, - findFirstAvailableInventoryItemBySkuCode, - releaseReservedInventoryItem, -} from '../../repositories/inventory-repo.js' -import type { InventoryItemRow } from '../../types/repository-rows.js' -import { nowIso } from '../../utils/time.js' - -type InventoryItemId = number | string | null | undefined -type InventoryGroupCodesInput = string | string[] | null | undefined - -type ReserveInventoryForTaskInput = { - skuCode?: string - taskId: number | string - credentialType?: string - roleKey?: string - inventoryGroupCodes?: InventoryGroupCodesInput -} - -type ReserveInventoryForTaskDeps = { - assignReservedInventoryItem?: ( - inventoryItemId: number | string, - taskId: number | string, - updatedAt: string, - roleKey?: string, - ) => Promise - findFirstAvailableInventoryItemBySkuCode?: ( - skuCode: string, - credentialType?: string, - inventoryGroupCodes?: InventoryGroupCodesInput, - ) => Promise - nowIso?: () => string -} - -type ReleaseReservedInventoryItemsDeps = { - releaseReservedInventoryItem?: ( - inventoryItemId: number | string, - updatedAt: string, - ) => Promise -} - -export async function reserveInventoryForTask({ - skuCode, - taskId, - credentialType = 'claim_code', - roleKey = 'primary_code', - inventoryGroupCodes = null, -}: ReserveInventoryForTaskInput): Promise { - return reserveInventoryForTaskWithDeps( - { skuCode, taskId, credentialType, roleKey, inventoryGroupCodes }, - { - assignReservedInventoryItem, - findFirstAvailableInventoryItemBySkuCode, - nowIso, - }, - ) -} - -export async function reserveInventoryForTaskWithDeps( - { - skuCode, - taskId, - credentialType = 'claim_code', - roleKey = 'primary_code', - inventoryGroupCodes = null, - }: ReserveInventoryForTaskInput, - { - assignReservedInventoryItem: assignReserved = assignReservedInventoryItem, - findFirstAvailableInventoryItemBySkuCode: findAvailable = findFirstAvailableInventoryItemBySkuCode, - nowIso: getNowIso = nowIso, - }: ReserveInventoryForTaskDeps = {}, -): Promise { - if (!skuCode) { - return null - } - - if (Array.isArray(inventoryGroupCodes) && inventoryGroupCodes.length === 0) { - return null - } - - const available = await findAvailable(skuCode, credentialType, inventoryGroupCodes) - - if (!available) { - return null - } - - return assignReserved(available.id, taskId, getNowIso(), roleKey) -} - -export async function releaseReservedInventoryItems( - inventoryItemIds: InventoryItemId[] = [], - updatedAt = nowIso(), -): Promise { - return releaseReservedInventoryItemsWithDeps( - inventoryItemIds, - updatedAt, - { releaseReservedInventoryItem }, - ) -} - -export async function releaseReservedInventoryItemsWithDeps( - inventoryItemIds: InventoryItemId[] = [], - updatedAt = nowIso(), - { - releaseReservedInventoryItem: releaseReserved = releaseReservedInventoryItem, - }: ReleaseReservedInventoryItemsDeps = {}, -): Promise { - const released: InventoryItemRow[] = [] - - for (const inventoryItemId of inventoryItemIds) { - if (!inventoryItemId) { - continue - } - - const item = await releaseReserved(inventoryItemId, updatedAt) - if (item) { - released.push(item) - } - } - - return released -} diff --git a/apps/backend/src/services/order/order-service.test.ts b/apps/backend/src/services/order/order-service.test.ts index 50be87b9..c1ba1b3c 100644 --- a/apps/backend/src/services/order/order-service.test.ts +++ b/apps/backend/src/services/order/order-service.test.ts @@ -1,10 +1,10 @@ import test from 'node:test' import assert from 'node:assert/strict' -import { mergeWebhookOrderState } from './order-service.js' +import { mergeSourceOrderState } from './order-service.js' -test('mergeWebhookOrderState keeps paid state when a late trade_create webhook arrives', () => { - const merged = mergeWebhookOrderState( +test('mergeSourceOrderState keeps paid state when a late created source event arrives', () => { + const merged = mergeSourceOrderState( { order_status: 'paid', pay_status: 'paid', @@ -22,8 +22,8 @@ test('mergeWebhookOrderState keeps paid state when a late trade_create webhook a assert.equal(merged.paidAt, '2026-04-14T10:06:10.000Z') }) -test('mergeWebhookOrderState upgrades unpaid orders when payment webhook arrives later', () => { - const merged = mergeWebhookOrderState( +test('mergeSourceOrderState upgrades unpaid orders when payment source event arrives later', () => { + const merged = mergeSourceOrderState( { order_status: 'created', pay_status: 'unpaid', @@ -41,8 +41,8 @@ test('mergeWebhookOrderState upgrades unpaid orders when payment webhook arrives assert.equal(merged.paidAt, '2026-04-14T10:06:12.000Z') }) -test('mergeWebhookOrderState preserves refunded status over older paid webhook', () => { - const merged = mergeWebhookOrderState( +test('mergeSourceOrderState preserves refunded status over older paid source event', () => { + const merged = mergeSourceOrderState( { order_status: 'refunded', pay_status: 'refunded', diff --git a/apps/backend/src/services/order/order-service.ts b/apps/backend/src/services/order/order-service.ts index d8749dab..b6102fa8 100644 --- a/apps/backend/src/services/order/order-service.ts +++ b/apps/backend/src/services/order/order-service.ts @@ -8,7 +8,7 @@ import { replaceOrderItems } from '../../repositories/order-item-repo.js' import { syncDeliveryTasksForOrder } from './delivery-task-service.js' import { resolveOrderItemForFulfillment } from './product-match-service.js' import { nowIso } from '../../utils/time.js' -import { logWebhook } from '../../utils/logger.js' +import { logIntegration } from '../../utils/logger.js' import type { OrderItemRow, OrderRow, TaskRow } from '../../types/repository-rows.js' type SourceOrderItem = { @@ -83,10 +83,6 @@ type OrderStateMergeResult = { type StatusPriorityMap = Record -export async function upsertOrderFromWebhook(event: SourceOrderEvent): Promise { - return upsertOrderFromSource(event, { sourceLabel: 'webhook' }) -} - export async function upsertOrderFromSource( event: SourceOrderEvent, { sourceLabel = 'source' }: UpsertOrderSourceOptions = {}, @@ -105,7 +101,7 @@ export async function upsertOrderFromSource( platformOrderId: event.platformOrderId, }) - logWebhook('[order-service]', `开始处理 ${sourceLabel} 订单 upsert`, { + logIntegration('[order-service]', `开始处理 ${sourceLabel} 订单 upsert`, { provider: event.provider, platform: event.platform, shopId: event.shopId, @@ -126,7 +122,7 @@ export async function upsertOrderFromSource( const configuredItems = (resolvedItems as FulfillmentOrderItem[]).filter((item) => item.isConfigured) if (configuredItems.length === 0) { - logWebhook('[order-service]', `${sourceLabel} 订单已忽略:未命中任何已配置履约商品`, { + logIntegration('[order-service]', `${sourceLabel} 订单已忽略:未命中任何已配置履约商品`, { provider: event.provider, platform: event.platform, shopId: event.shopId, @@ -161,7 +157,7 @@ export async function upsertOrderFromSource( rawPayloadJson: JSON.stringify(event.rawPayload), paidAt: event.paidAt, } - const mergedPayload = mergeWebhookOrderState(existing, basePayload) + const mergedPayload = mergeSourceOrderState(existing, basePayload) const order = existing ? await updateOrder(existing.id, { @@ -195,7 +191,7 @@ export async function upsertOrderFromSource( const tasks = await syncDeliveryTasksForOrder(order, orderItems) - logWebhook('[order-service]', `${sourceLabel} 订单 upsert 完成`, { + logIntegration('[order-service]', `${sourceLabel} 订单 upsert 完成`, { orderId: order.id, provider: order.provider, platform: order.platform, @@ -235,7 +231,7 @@ const PAY_STATUS_PRIORITY = { refunded: 2, } -export function mergeWebhookOrderState( +export function mergeSourceOrderState( existing: OrderStateInput | null | undefined, incoming: OrderStateInput | null | undefined, ): OrderStateMergeResult { diff --git a/apps/backend/src/types/admin-read-inputs.ts b/apps/backend/src/types/admin-read-inputs.ts index 41e5b3fd..59802b47 100644 --- a/apps/backend/src/types/admin-read-inputs.ts +++ b/apps/backend/src/types/admin-read-inputs.ts @@ -2,7 +2,6 @@ export type AdminViewerSessionInput = { userId?: number | string username?: string role?: string - allowedInventoryGroups?: string[] } export type AdminOrderListQueryInput = { @@ -27,34 +26,4 @@ export type AdminTaskListQueryInput = { dateTo?: string } -export type AdminInventoryListQueryInput = { - page?: number | string - pageSize?: number | string - skuCode?: string - credentialType?: string - status?: string - batchNo?: string - inventoryGroupCode?: string -} - -export type AdminInventorySkuSuggestionQueryInput = { - credentialType?: string - keyword?: string - limit?: number | string - inventoryGroupCode?: string -} - -export type AdminWebhookEventListQueryInput = { - page?: number | string - pageSize?: number | string - provider?: string - platform?: string - platformOrderId?: string - processed?: string - visibility?: string - relatedOrderId?: string - dateFrom?: string - dateTo?: string -} - export type AdminEntityIdInput = number | string diff --git a/apps/backend/src/types/admin-read-models.ts b/apps/backend/src/types/admin-read-models.ts index 9e887e43..43852453 100644 --- a/apps/backend/src/types/admin-read-models.ts +++ b/apps/backend/src/types/admin-read-models.ts @@ -63,76 +63,10 @@ export type AdminTaskListItem = { lastError: string createdAt: string updatedAt: string - inventoryDisplayMasked: string - inventoryCredentialType: string claimToken: string screenshotPath: string } -export type AdminInventoryItemListItem = { - inventoryItemId: number - skuCode: string - batchNo: string - credentialType: string - inventoryGroupCode: string - displayValue: string - status: string - reservedByTaskId: number | null - reservedByTaskNo: string - platformOrderId: string - systemBindingStatus: string - userBindingStatus: string - invalidReason: string - deliveredAt: string | null - createdAt: string - updatedAt: string -} - -export type AdminWebhookEventListItem = { - eventId: number - provider: string - platform: string - platformRaw: string - shopId: string - shopName: string - eventType: string - eventKey: string - signatureValid: boolean - processed: boolean - processError: string - visibilityLevel: string - relatedOrderId: number | null - platformOrderId: string - buyerId: string - buyerName: string - totalAmount: string - totalAmountFen: number - currency: string - itemCount: number - taskCount: number - messageText: string - requestTimestamp: string - requestSign: string - aopic: string - sourceHost: string - sourceIp: string - userAgent: string - createdAt: string - headers?: Record - query?: Record - body?: Record - payload?: Record -} - -export type AdminInventorySkuSuggestion = { - skuCode: string - credentialType: string - inventoryGroupCode: string - totalCount: number - availableCount: number - latestUpdatedAt: string | null -} - export type AdminPagination = { page: number pageSize: number @@ -148,17 +82,3 @@ export type AdminTaskListResponse = { items: AdminTaskListItem[] pagination: AdminPagination } - -export type AdminInventoryListResponse = { - items: AdminInventoryItemListItem[] - pagination: AdminPagination -} - -export type AdminWebhookEventListResponse = { - items: AdminWebhookEventListItem[] - pagination: AdminPagination -} - -export type AdminInventorySkuSuggestionResponse = { - items: AdminInventorySkuSuggestion[] -} diff --git a/apps/backend/src/types/admin-route-inputs.ts b/apps/backend/src/types/admin-route-inputs.ts index a3773f50..fb4f1cfe 100644 --- a/apps/backend/src/types/admin-route-inputs.ts +++ b/apps/backend/src/types/admin-route-inputs.ts @@ -1,10 +1,7 @@ import type { - AdminInventoryListQueryInput, - AdminInventorySkuSuggestionQueryInput, AdminOrderListQueryInput, AdminTaskListQueryInput, AdminViewerSessionInput, - AdminWebhookEventListQueryInput, } from './admin-read-inputs.js' import type { AdminCloudtentaclesCatalogQueryInput, @@ -16,9 +13,6 @@ import type { AdminCloudtentaclesTestLoginInput, AdminCloudtentaclesValidateSessionInput, AdminCloudtentaclesVirtualNumberInput, - AdminInventoryCreateInput, - AdminInventoryImportInput, - AdminInventoryInvalidateInput, AdminKuaishouCloudFulfillmentConfigInput, AdminKuaishouEticketConsumeInput, AdminKuaishouEticketDetailQueryInput, @@ -29,15 +23,11 @@ import type { AdminScheduledJobsConfigInput, AdminTaskKuaishouCloudDispatchInput, AdminTaskManualDispatchInput, - AdminUserInventoryGroupsInput, } from './admin-write-inputs.js' -export type AdminInventoryRouteQuery = AdminInventoryListQueryInput -export type AdminInventorySkuSuggestionRouteQuery = AdminInventorySkuSuggestionQueryInput export type AdminOrderRouteQuery = AdminOrderListQueryInput export type AdminTaskRouteQuery = AdminTaskListQueryInput export type AdminRouteAdminSession = AdminViewerSessionInput -export type AdminWebhookEventRouteQuery = AdminWebhookEventListQueryInput export type AdminKuaishouEticketSourceConfigRouteBody = AdminKuaishouEticketSourceConfigInput export type AdminNotificationConfigRouteBody = AdminNotificationConfigInput @@ -56,16 +46,10 @@ export type AdminCloudtentaclesSkuBuyRouteBody = AdminCloudtentaclesSkuBuyInput export type AdminCloudtentaclesSkuUseRouteBody = AdminCloudtentaclesSkuUseInput export type AdminCloudtentaclesVirtualNumberRouteBody = AdminCloudtentaclesVirtualNumberInput export type AdminCloudtentaclesFullFlowRouteBody = AdminCloudtentaclesFullFlowInput -export type AdminInventoryCreateRouteBody = AdminInventoryCreateInput -export type AdminInventoryImportRouteBody = AdminInventoryImportInput -export type AdminInventoryInvalidateRouteBody = AdminInventoryInvalidateInput -export type AdminUserInventoryGroupsRouteBody = AdminUserInventoryGroupsInput export type AdminTaskManualDispatchRouteBody = AdminTaskManualDispatchInput export type AdminTaskKuaishouCloudDispatchRouteBody = AdminTaskKuaishouCloudDispatchInput -export type AdminInventoryRouteParams = { inventoryItemId?: string } export type AdminOrderRouteParams = { orderId?: string } export type AdminTaskRouteParams = { taskId?: string, bindingId?: string } -export type AdminWebhookEventRouteParams = { eventId?: string } export type AdminEntityRouteParams = { id?: string } export type AdminCloudtentaclesSourceRouteParams = { sourceKey?: string } diff --git a/apps/backend/src/types/admin-write-inputs.ts b/apps/backend/src/types/admin-write-inputs.ts index 3fb27fc5..0a7350d5 100644 --- a/apps/backend/src/types/admin-write-inputs.ts +++ b/apps/backend/src/types/admin-write-inputs.ts @@ -1,32 +1,3 @@ -export type AdminInventoryCreateInput = { - skuCode?: string - displayValue?: string - batchNo?: string - credentialType?: string - inventoryGroupCode?: string -} - -export type AdminInventoryImportRowInput = { - skuCode?: string - displayValue?: string - batchNo?: string - credentialType?: string - inventoryGroupCode?: string -} - -export type AdminInventoryImportInput = { - rows?: AdminInventoryImportRowInput[] - codes?: unknown[] - skuCode?: string - batchNo?: string - credentialType?: string - inventoryGroupCode?: string -} - -export type AdminUserInventoryGroupsInput = { - inventoryGroupCodes?: string[] -} - export type AdminKuaishouCloudFulfillmentItemInput = { id?: string enabled?: boolean @@ -249,10 +220,6 @@ export type AdminCloudtentaclesFullFlowInput = { vnKey?: string } -export type AdminInventoryInvalidateInput = { - reason?: string -} - export type AdminTaskManualDispatchInput = { outcome?: string resultMessage?: string diff --git a/apps/backend/src/types/admin-write-models.ts b/apps/backend/src/types/admin-write-models.ts index 150f1f04..ed51e6b8 100644 --- a/apps/backend/src/types/admin-write-models.ts +++ b/apps/backend/src/types/admin-write-models.ts @@ -1,30 +1,3 @@ -import type { - AdminInventoryItemListItem, - AdminInventorySkuSuggestion, -} from './admin-read-models.js' - -export type { AdminInventoryItemListItem, AdminInventorySkuSuggestion } - -export type AdminInventoryMutationResponse = { - inventoryItem: AdminInventoryItemListItem | null -} - -export type AdminInventoryImportResponse = { - total: number - created: number - duplicated: number -} - -export type AdminInventoryGroupListResponse = { - items: AdminInventorySkuSuggestion[] -} - -export type AdminWebhookReplayResponse = { - eventId: number - replayed: boolean - result: unknown -} - export type AdminTaskActionPayload = { taskId: number taskNo: string @@ -32,7 +5,6 @@ export type AdminTaskActionPayload = { deliveryStatus: string resultCode: string resultMessage: string - inventoryItemId: number | null primaryClaimTokenId: number | null lastError: string updatedAt: string @@ -44,11 +16,6 @@ export type AdminTaskActionResponse = { token?: string } -export type AdminTaskBindingReleaseResponse = AdminTaskActionResponse & { - bindingId: number - inventoryItemId: number -} - export type AdminManualDispatchOutcome = 'delivered' | 'failed' export type AdminTaskManualDispatchResponse = AdminTaskActionResponse & { diff --git a/apps/backend/src/types/repository-inputs.ts b/apps/backend/src/types/repository-inputs.ts index 29759ba1..f8c3bc34 100644 --- a/apps/backend/src/types/repository-inputs.ts +++ b/apps/backend/src/types/repository-inputs.ts @@ -83,7 +83,6 @@ export type TaskCreateInput = { profileId: number | string executorKey: string taskStatus?: string - inventoryStatus?: string deliveryStatus?: string resultCode?: string resultMessage?: string @@ -102,7 +101,6 @@ export type TaskCreateInput = { export type TaskUpdatePatch = { task_status?: string - inventory_status?: string delivery_status?: string result_code?: string result_message?: string @@ -129,69 +127,3 @@ export type TaskUpdatePatch = { artifacts_json?: string | Record state_json?: string | Record } - -export type InventoryListQueryInput = { - page?: number - pageSize?: number - skuCode?: string - credentialType?: string - status?: string - batchNo?: string - inventoryGroupCode?: string - allowedInventoryGroupCodes?: string[] | null -} - -export type InventorySkuSuggestionQueryInput = { - credentialType?: string - keyword?: string - limit?: number - inventoryGroupCode?: string - allowedInventoryGroupCodes?: string[] | null -} - -export type InventoryCreateItemInput = { - batchNo?: string - skuCode: string - credentialType?: string - inventoryGroupCode?: string - displayValue: string - payload?: Record - createdAt: string - updatedAt: string -} - -export type WebhookEventListQueryInput = { - page?: number - pageSize?: number - provider?: string - platform?: string - platformOrderId?: string - processed?: string - visibility?: string - relatedOrderId?: string - dateFrom?: string - dateTo?: string -} - -export type WebhookEventCreateInput = { - provider: string - platform: string - shopId?: string - shopName?: string - eventType: string - eventKey: string - signatureValid?: boolean - headersJson?: string | Record - queryJson?: string | Record - bodyJson?: string | Record - processed?: boolean - processError?: string - relatedOrderId?: number | null - createdAt: string -} - -export type WebhookEventUpdatePatch = { - processed?: boolean - process_error?: string - related_order_id?: number | null -} diff --git a/apps/backend/src/types/repository-rows.ts b/apps/backend/src/types/repository-rows.ts index 03ed989a..7c533427 100644 --- a/apps/backend/src/types/repository-rows.ts +++ b/apps/backend/src/types/repository-rows.ts @@ -46,7 +46,6 @@ export type TaskRow = { delivery_status: string result_code: string result_message: string - inventory_status: string automation_mode: string requires_claim: boolean user_action_status: string @@ -64,12 +63,6 @@ export type TaskRow = { primary_claim_token_id: number | null primary_claim_token_status: string primary_claim_expires_at?: string | null - primary_inventory_item_id: number | null - primary_inventory_binding_status: string - inventory_display_value: string - primary_inventory_display_value: string - inventory_credential_type?: string - primary_inventory_credential_type?: string artifacts_json: string | Record context_json: string | Record screenshot_path: string @@ -84,80 +77,6 @@ export type TaskRow = { sku_name?: string } -export type InventoryItemRow = { - id: number - sku_code: string - batch_no: string - credential_type: string - inventory_group_code: string - display_value: string - status: string - invalid_reason: string - delivered_at: string | null - created_at: string - updated_at: string - reserved_by_task_id: number | null - reserved_by_task_no: string - platform_order_id: string -} - -export type InventorySkuSuggestionRow = { - sku_code: string - credential_type: string - inventory_group_code: string - total_count: number - available_count: number - latest_updated_at: string | null -} - -export type WebhookEventRow = { - id: number - provider: string - platform: string - shop_id: string - shop_name: string - event_type: string - event_key: string - signature_valid: boolean - headers_json: string | Record - query_json: string | Record - body_json: string | Record - processed: boolean - process_error: string - related_order_id: number | null - created_at: string -} - -export type TaskInventoryBindingRow = { - id: number - task_id: number - inventory_item_id: number - role_key: string - quantity: number - binding_status: string - metadata_json: string | Record - invalid_reason: string - consumed_at: string | null - released_at: string | null - created_at: string - updated_at: string - sku_code: string - batch_no: string - credential_type: string - inventory_group_code: string - display_value: string - inventory_status: string -} - -export type TaskInventoryBindingSummaryRow = { - task_id: number - total_binding_count: number - reserved_binding_count: number - consumed_binding_count: number - released_binding_count: number - role_keys: string[] -} - export type ClaimTokenRow = { id: number token: string @@ -183,13 +102,3 @@ export type TaskListQueryResult = { items: TaskRow[] total: number } - -export type InventoryListQueryResult = { - items: InventoryItemRow[] - total: number -} - -export type WebhookEventListQueryResult = { - items: WebhookEventRow[] - total: number -} diff --git a/apps/backend/src/utils/logger.test.ts b/apps/backend/src/utils/logger.test.ts index a9a07d3a..b9d53227 100644 --- a/apps/backend/src/utils/logger.test.ts +++ b/apps/backend/src/utils/logger.test.ts @@ -28,7 +28,7 @@ test('shouldWriteLog respects configured minimum log level', () => { test('resolveLogFilePath uses daily log file names by channel', () => { assert.match(resolveLogFilePath('app', '2026-04-14T10:00:00.000Z'), /app-2026-04-14\.log$/) - assert.match(resolveLogFilePath('webhook', '2026-04-14T10:00:00.000Z'), /webhook-2026-04-14\.log$/) + assert.match(resolveLogFilePath('integration', '2026-04-14T10:00:00.000Z'), /integration-2026-04-14\.log$/) }) test('resolveExpiredLogFilenames keeps latest seven days and ignores unknown files', () => { @@ -37,17 +37,17 @@ test('resolveExpiredLogFilenames keeps latest seven days and ignores unknown fil 'app-2026-04-13.log', 'app-2026-04-08.log', 'app-2026-04-07.log', - 'webhook-2026-04-06.log', + 'integration-2026-04-06.log', 'app.log', - 'webhook.log', + 'integration.log', 'random.txt', ], '2026-04-14T12:00:00.000Z', 7) assert.deepEqual(expired, [ 'app-2026-04-07.log', - 'webhook-2026-04-06.log', + 'integration-2026-04-06.log', 'app.log', - 'webhook.log', + 'integration.log', ]) }) diff --git a/apps/backend/src/utils/logger.ts b/apps/backend/src/utils/logger.ts index 566663c9..5f3680fe 100644 --- a/apps/backend/src/utils/logger.ts +++ b/apps/backend/src/utils/logger.ts @@ -35,7 +35,7 @@ const DATA_ROOT = resolveDataRoot() const LOG_DIR = path.join(DATA_ROOT, 'logs') const ACTIVE_LOG_LEVEL = normalizeLogLevel(runtimeConfig.logging?.level) const LOG_RETENTION_DAYS = 7 -const LEGACY_LOG_FILES = new Set(['app.log', 'webhook.log']) +const LEGACY_LOG_FILES = new Set(['app.log', 'integration.log']) let writeQueue = Promise.resolve() let lastCleanupDateKey = '' @@ -60,8 +60,8 @@ export function logError(scope, message, detail = undefined) { return writeLog('error', scope, message, detail) } -export function logWebhook(scope, message, detail = undefined, { level = 'info' } = {}) { - return writeLog(level, scope, message, detail, { channel: 'webhook' }) +export function logIntegration(scope, message, detail = undefined, { level = 'info' } = {}) { + return writeLog(level, scope, message, detail, { channel: 'integration' }) } function writeLog(level, scope, message, detail, { channel = 'app' } = {}) { @@ -167,7 +167,7 @@ export function formatLogEntry(entry, { color = false } = {}) { } export function resolveLogFilePath(channel = 'app', time = new Date().toISOString()) { - const normalizedChannel = String(channel || '').trim() === 'webhook' ? 'webhook' : 'app' + const normalizedChannel = String(channel || '').trim() === 'integration' ? 'integration' : 'app' return path.join(LOG_DIR, `${normalizedChannel}-${extractLogDateKey(time)}.log`) } @@ -392,7 +392,7 @@ function shouldDeleteLogFileByDate(fileName, cutoffDateKey) { return true } - const matched = /^(app|webhook)-(\d{4}-\d{2}-\d{2})\.log$/.exec(String(fileName || '').trim()) + const matched = /^(app|integration)-(\d{4}-\d{2}-\d{2})\.log$/.exec(String(fileName || '').trim()) if (!matched) { return false diff --git a/apps/frontend/src/components/admin/AdminStatusTag.vue b/apps/frontend/src/components/admin/AdminStatusTag.vue index f985d497..9cd72ba7 100644 --- a/apps/frontend/src/components/admin/AdminStatusTag.vue +++ b/apps/frontend/src/components/admin/AdminStatusTag.vue @@ -59,7 +59,6 @@ function resolveType(status: string): ElTagType { if ( [ - 'waiting_inventory', 'retry_pending', 'manual_review', 'invalid', @@ -98,7 +97,6 @@ function resolveLabel(status: string) { refunding: '退款中', closed: '已关闭', pending_payment: '待支付', - waiting_inventory: '待库存', cdk_reserved: '已预占', link_generated: '已生成链接', claimed: '已创建会话', diff --git a/apps/frontend/src/router/index.ts b/apps/frontend/src/router/index.ts index 1dd36e31..c33d583f 100644 --- a/apps/frontend/src/router/index.ts +++ b/apps/frontend/src/router/index.ts @@ -53,20 +53,6 @@ const router = createRouter({ path: 'tasks/:taskId', component: () => import('@/views/admin/tasks/AdminTaskDetailView.vue'), }, - { - path: 'cdks', - redirect: '/admin/inventory', - }, - { - path: 'inventory', - meta: { allowedRoles: ['admin', 'operator'] }, - component: () => import('@/views/admin/inventory/AdminInventoryView.vue'), - }, - { - path: 'webhook-events', - meta: { allowedRoles: ['admin', 'operator'] }, - component: () => import('@/views/admin/webhook-events/AdminWebhookEventsView.vue'), - }, { path: 'platform-shops', meta: { allowedRoles: ['admin'] }, @@ -82,11 +68,6 @@ const router = createRouter({ meta: { allowedRoles: ['admin'] }, redirect: '/admin/platform-fulfillment?tab=kuaishou-cloud', }, - { - path: 'webhook-events/:eventId', - meta: { allowedRoles: ['admin', 'operator'] }, - component: () => import('@/views/admin/webhook-events/AdminWebhookEventDetailView.vue'), - }, { path: 'audit-logs', meta: { allowedRoles: ['admin'] }, diff --git a/apps/frontend/src/services/admin/index.ts b/apps/frontend/src/services/admin/index.ts index 403adc0f..99c97f94 100644 --- a/apps/frontend/src/services/admin/index.ts +++ b/apps/frontend/src/services/admin/index.ts @@ -5,5 +5,3 @@ export * from './audit-logs' export * from './platform-config' export * from './orders' export * from './tasks' -export * from './inventory' -export * from './webhook-events' diff --git a/apps/frontend/src/services/admin/inventory.ts b/apps/frontend/src/services/admin/inventory.ts deleted file mode 100644 index 32c13c67..00000000 --- a/apps/frontend/src/services/admin/inventory.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { apiGet, apiPost } from '@/lib/http' -import type { - AdminInventoryItemListItem, - AdminInventorySkuSuggestion, - AdminPagination, -} from '@/types/admin' - -export function fetchAdminInventoryItems(params?: Record) { - return apiGet<{ items: AdminInventoryItemListItem[]; pagination: AdminPagination }>( - '/api/v1/admin/inventory', - params, - ) -} - -export function fetchAdminInventorySkuSuggestions(params?: Record) { - return apiGet<{ items: AdminInventorySkuSuggestion[] }>( - '/api/v1/admin/inventory/sku-suggestions', - params, - ) -} - -export function importAdminInventoryItems(payload: Record) { - return apiPost<{ total: number; created: number; duplicated: number }>( - '/api/v1/admin/inventory/import', - payload, - ) -} - -export function createAdminInventoryItem(payload: Record) { - return apiPost<{ inventoryItem: AdminInventoryItemListItem | null }>( - '/api/v1/admin/inventory', - payload, - ) -} - -export function releaseAdminInventoryItem(inventoryItemId: number | string) { - return apiPost<{ inventoryItem: AdminInventoryItemListItem | null }>( - `/api/v1/admin/inventory/${inventoryItemId}/release`, - {}, - ) -} - -export function invalidateAdminInventoryItem( - inventoryItemId: number | string, - payload?: Record, -) { - return apiPost<{ inventoryItem: AdminInventoryItemListItem | null }>( - `/api/v1/admin/inventory/${inventoryItemId}/invalidate`, - payload || {}, - ) -} diff --git a/apps/frontend/src/services/admin/tasks.ts b/apps/frontend/src/services/admin/tasks.ts index 4b22b2f2..a574863f 100644 --- a/apps/frontend/src/services/admin/tasks.ts +++ b/apps/frontend/src/services/admin/tasks.ts @@ -25,20 +25,6 @@ export function retryAdminTask(taskId: number | string) { return apiPost(`/api/v1/admin/tasks/${taskId}/retry`, {}) } -export function releaseAdminTaskInventory(taskId: number | string) { - return apiPost(`/api/v1/admin/tasks/${taskId}/release-inventory`, {}) -} - -export function releaseAdminTaskInventoryBinding( - taskId: number | string, - bindingId: number | string, -) { - return apiPost( - `/api/v1/admin/tasks/${taskId}/inventory-bindings/${bindingId}/release`, - {}, - ) -} - export function regenerateAdminTaskClaimLink(taskId: number | string) { return apiPost(`/api/v1/admin/tasks/${taskId}/regenerate-claim-link`, {}) } diff --git a/apps/frontend/src/services/admin/users.ts b/apps/frontend/src/services/admin/users.ts index 7e66cea2..039a1ddb 100644 --- a/apps/frontend/src/services/admin/users.ts +++ b/apps/frontend/src/services/admin/users.ts @@ -25,16 +25,6 @@ export function updateAdminUserStatus(userId: number | string, payload: { status return apiPost<{ user: AdminUserListItem }>(`/api/v1/admin/users/${userId}/status`, payload) } -export function updateAdminUserInventoryGroups( - userId: number | string, - payload: { inventoryGroupCodes: string[] }, -) { - return apiPost<{ user: AdminUserListItem }>( - `/api/v1/admin/users/${userId}/inventory-groups`, - payload, - ) -} - export function resetAdminUserPassword(userId: number | string, payload: { password: string }) { return apiPost<{ user: AdminUserListItem }>( `/api/v1/admin/users/${userId}/reset-password`, diff --git a/apps/frontend/src/services/admin/webhook-events.ts b/apps/frontend/src/services/admin/webhook-events.ts deleted file mode 100644 index 22f1f798..00000000 --- a/apps/frontend/src/services/admin/webhook-events.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { apiGet, apiPost } from '@/lib/http' -import type { - AdminPagination, - AdminWebhookEventDetail, - AdminWebhookEventListItem, - AdminWebhookReplayResponse, -} from '@/types/admin' - -export function fetchAdminWebhookEvents(params?: Record) { - return apiGet<{ items: AdminWebhookEventListItem[]; pagination: AdminPagination }>( - '/api/v1/admin/webhook-events', - params, - ) -} - -export function fetchAdminWebhookEventDetail(eventId: number | string) { - return apiGet(`/api/v1/admin/webhook-events/${eventId}`) -} - -export function replayAdminWebhookEvent(eventId: number | string) { - return apiPost(`/api/v1/admin/webhook-events/${eventId}/replay`, {}) -} diff --git a/apps/frontend/src/styles/admin-list-pages.css b/apps/frontend/src/styles/admin-list-pages.css index 7ae70814..fda6e0d9 100644 --- a/apps/frontend/src/styles/admin-list-pages.css +++ b/apps/frontend/src/styles/admin-list-pages.css @@ -1,7 +1,6 @@ /* ============================================================ 管理员列表页共享样式 - 引入: AdminOrdersView, AdminTasksView, AdminWebhookEventsView, - AdminUsersView, AdminInventoryView + 引入: AdminOrdersView, AdminTasksView, AdminUsersView 各页面自行覆盖 grid-template-columns 等布局差异 ============================================================ */ diff --git a/apps/frontend/src/types/admin/auth.ts b/apps/frontend/src/types/admin/auth.ts index 385922ab..d9d23fc7 100644 --- a/apps/frontend/src/types/admin/auth.ts +++ b/apps/frontend/src/types/admin/auth.ts @@ -8,7 +8,6 @@ export interface AdminLoginResponse { userId: number username: string role: AdminRole - inventoryGroupCodes?: string[] } } @@ -19,6 +18,5 @@ export interface AdminSessionSummary { userId: number username: string role: AdminRole - inventoryGroupCodes?: string[] } } diff --git a/apps/frontend/src/types/admin/dashboard.ts b/apps/frontend/src/types/admin/dashboard.ts index b360c3d7..7d666bf0 100644 --- a/apps/frontend/src/types/admin/dashboard.ts +++ b/apps/frontend/src/types/admin/dashboard.ts @@ -4,5 +4,4 @@ export interface AdminDashboardSummary { claimingTasks: number redeemedToday: number abnormalTasks: number - skuWithInventory: number } diff --git a/apps/frontend/src/types/admin/index.ts b/apps/frontend/src/types/admin/index.ts index ceb818b1..2e13be8f 100644 --- a/apps/frontend/src/types/admin/index.ts +++ b/apps/frontend/src/types/admin/index.ts @@ -29,16 +29,6 @@ export type { AdminTaskDetail, } from './tasks' -// Inventory types -export type { AdminInventorySkuSuggestion, AdminInventoryItemListItem } from './inventory' - -// Webhook events types -export type { - AdminWebhookEventListItem, - AdminWebhookEventDetail, - AdminWebhookReplayResponse, -} from './webhook-events' - // Platform config types export type { AdminNotificationBarkRecipient, diff --git a/apps/frontend/src/types/admin/inventory.ts b/apps/frontend/src/types/admin/inventory.ts deleted file mode 100644 index 74325a87..00000000 --- a/apps/frontend/src/types/admin/inventory.ts +++ /dev/null @@ -1,27 +0,0 @@ -export interface AdminInventorySkuSuggestion { - skuCode: string - credentialType: string - inventoryGroupCode: string - totalCount: number - availableCount: number - latestUpdatedAt: string | null -} - -export interface AdminInventoryItemListItem { - inventoryItemId: number - skuCode: string - batchNo: string - credentialType: string - inventoryGroupCode: string - displayValue: string - status: string - reservedByTaskId: number | null - reservedByTaskNo: string - platformOrderId: string - systemBindingStatus: string - userBindingStatus: string - invalidReason: string - deliveredAt: string | null - createdAt: string - updatedAt: string -} diff --git a/apps/frontend/src/types/admin/orders.ts b/apps/frontend/src/types/admin/orders.ts index cf6e7a4e..037a154d 100644 --- a/apps/frontend/src/types/admin/orders.ts +++ b/apps/frontend/src/types/admin/orders.ts @@ -77,17 +77,4 @@ export interface AdminOrderDetail { spec: Record }> tasks: AdminTaskListItem[] - webhookEvents: Array<{ - eventId: number - provider: string - platform: string - shopId: string - shopName: string - eventType: string - eventKey: string - signatureValid: boolean - processed: boolean - processError: string - createdAt: string - }> } diff --git a/apps/frontend/src/types/admin/tasks.ts b/apps/frontend/src/types/admin/tasks.ts index db2ae0ff..f791a628 100644 --- a/apps/frontend/src/types/admin/tasks.ts +++ b/apps/frontend/src/types/admin/tasks.ts @@ -31,15 +31,12 @@ export interface AdminTaskListItem { lastError: string createdAt: string updatedAt: string - inventoryDisplayMasked: string - inventoryCredentialType: string claimToken: string screenshotPath: string } export interface AdminTaskOperations { canRetry: boolean - canReleaseInventory: boolean canRegenerateClaimLink: boolean canClose: boolean canMarkManualReview: boolean @@ -59,7 +56,6 @@ export interface AdminTaskActionResponse { deliveryStatus: string resultCode: string resultMessage: string - inventoryItemId: number | null primaryClaimTokenId: number | null lastError: string updatedAt: string @@ -93,34 +89,6 @@ export interface AdminTaskDetail { expiredAt: string claimUrl: string } - inventory: null | { - inventoryItemId: number - skuCode: string - batchNo: string - credentialType: string - displayValue: string - status: string - } - inventoryBindings: Array<{ - bindingId: number - inventoryItemId: number - roleKey: string - quantity: number - bindingStatus: string - inventoryStatus: string - skuCode: string - batchNo: string - credentialType: string - displayValue: string - invalidReason: string - consumedAt: string | null - releasedAt: string | null - createdAt: string - updatedAt: string - metadata: Record - isPrimary: boolean - canRelease: boolean - }> artifacts: Record screenshotUrl: string review: { @@ -136,7 +104,6 @@ export interface AdminTaskDetail { finishedAt: string | null attempts: Array<{ attempt: number - inventoryItemId: number | null codeMasked: string credentialType: string outcome: string diff --git a/apps/frontend/src/types/admin/users.ts b/apps/frontend/src/types/admin/users.ts index e8ad7774..7c5438a2 100644 --- a/apps/frontend/src/types/admin/users.ts +++ b/apps/frontend/src/types/admin/users.ts @@ -5,7 +5,6 @@ export interface AdminUserListItem { username: string role: AdminRole status: AdminUserStatus - inventoryGroupCodes: string[] createdAt: string updatedAt: string } diff --git a/apps/frontend/src/types/admin/webhook-events.ts b/apps/frontend/src/types/admin/webhook-events.ts deleted file mode 100644 index 7d11d078..00000000 --- a/apps/frontend/src/types/admin/webhook-events.ts +++ /dev/null @@ -1,60 +0,0 @@ -export interface AdminWebhookEventListItem { - eventId: number - provider: string - platform: string - platformRaw: string - shopId: string - shopName: string - eventType: string - eventKey: string - signatureValid: boolean - processed: boolean - processError: string - visibilityLevel: string - relatedOrderId: number | null - platformOrderId: string - buyerId: string - buyerName: string - totalAmount: string - totalAmountFen: number - currency: string - itemCount: number - taskCount: number - messageText: string - requestTimestamp: string - requestSign: string - aopic: string - sourceHost: string - sourceIp: string - userAgent: string - createdAt: string -} - -export interface AdminWebhookEventDetail extends AdminWebhookEventListItem { - headers: Record - query: Record - body: Record - payload: Record -} - -export interface AdminWebhookReplayResponse { - eventId: number - replayed: boolean - result: { - accepted: boolean - provider: string - platform: string - platformRaw: string - shopId: string - shopName: string - eventType: string - platformOrderId: string - orderId: number - taskCount: number - tasks: Array<{ - taskId: number - taskNo: string - status: string - }> - } -} diff --git a/apps/frontend/src/types/claim.ts b/apps/frontend/src/types/claim.ts index bdbe3cd7..f05d76d1 100644 --- a/apps/frontend/src/types/claim.ts +++ b/apps/frontend/src/types/claim.ts @@ -3,13 +3,11 @@ export type ClaimTokenStatus = 'active' | 'used' | 'expired' | 'revoked' | (stri export type ClaimTaskStatus = | 'pending_payment' | 'paid' - | 'waiting_inventory' | 'pending_binding_prepare' | 'waiting_binding' | 'dispatched_pending_return' | 'completed' | 'failed' - | 'cdk_reserved' | 'link_generated' | 'claimed' | 'role_confirmed' diff --git a/apps/frontend/src/utils/admin-display.ts b/apps/frontend/src/utils/admin-display.ts index 70e9b9e4..f0b5524b 100644 --- a/apps/frontend/src/utils/admin-display.ts +++ b/apps/frontend/src/utils/admin-display.ts @@ -24,15 +24,10 @@ export function formatAuditAction(action: string) { admin_user_role_updated: "修改用户角色", admin_user_status_updated: "修改用户状态", admin_user_password_reset: "重置用户密码", - task_release_inventory: "释放任务库存", - task_release_inventory_binding: "释放任务库存绑定", task_regenerate_claim_link: "重发领取链接", task_closed: "关闭任务", task_mark_manual_review: "转人工处理", task_complete_manual_dispatch: "完成人工履约", - inventory_item_released: "释放库存项", - inventory_item_invalidated: "作废库存项", - webhook_replayed: "重放 Webhook", platform_shop_config_updated: "更新店铺配置", platform_fulfillment_config_updated: "更新履约配置", }; @@ -44,9 +39,6 @@ export function formatAuditTargetType(targetType: string) { const labelMap: Record = { admin_user: "后台用户", task: "交付任务", - task_inventory_binding: "任务库存绑定", - inventory_item: "库存项", - webhook_event: "Webhook 事件", platform_config: "平台配置", }; @@ -56,10 +48,8 @@ export function formatAuditTargetType(targetType: string) { export function formatTaskEventType(eventType: string) { const labelMap: Record = { manual_dispatch_completed: "人工履约已回写", - inventory_binding_released: "库存绑定已释放", claim_redeem_code_used: "兑换码已使用", claim_redeem_code_invalid: "兑换码错误", - claim_redeem_inventory_replaced: "已切换新兑换码", claim_redeem_completed: "兑换链路完成", }; @@ -114,11 +104,6 @@ export function formatTaskEventPayload(payload: Record) { payload.codeMasked ? `凭据 ${payload.codeMasked}` : "", payload.previousCodeMasked ? `旧码 ${payload.previousCodeMasked}` : "", payload.nextCodeMasked ? `新码 ${payload.nextCodeMasked}` : "", - payload.inventoryItemId ? `库存项 #${payload.inventoryItemId}` : "", - payload.previousInventoryItemId - ? `旧库存 #${payload.previousInventoryItemId}` - : "", - payload.nextInventoryItemId ? `新库存 #${payload.nextInventoryItemId}` : "", payload.ticketCodeMasked ? `卡券号 ${payload.ticketCodeMasked}` : "", payload.vnId ? `虚拟号 #${payload.vnId}` : "", payload.vnPhoneMasked ? `号码 ${payload.vnPhoneMasked}` : "", @@ -147,20 +132,3 @@ export function formatTaskEventPayload(payload: Record) { return JSON.stringify(payload || {}); } - -export function formatBindingRoleSummary( - roleKeys: string[] | undefined | null, - emptyLabel = "-" -) { - if (!Array.isArray(roleKeys) || roleKeys.length === 0) return emptyLabel; - return roleKeys.join(" / "); -} - -export function formatBindingCountSummary(summary: { - totalBindingCount: number; - reservedBindingCount: number; - consumedBindingCount: number; - releasedBindingCount: number; -}) { - return `共 ${summary.totalBindingCount} 条 · 预占 ${summary.reservedBindingCount} · 已消费 ${summary.consumedBindingCount} · 已释放 ${summary.releasedBindingCount}`; -} diff --git a/apps/frontend/src/utils/admin-options.ts b/apps/frontend/src/utils/admin-options.ts index b871248e..39dc4c38 100644 --- a/apps/frontend/src/utils/admin-options.ts +++ b/apps/frontend/src/utils/admin-options.ts @@ -10,8 +10,6 @@ export const adminTaskStatusOptions = [ { label: '全部', value: '' }, { label: '待支付', value: 'pending_payment' }, { label: '已支付', value: 'paid' }, - { label: '待库存', value: 'waiting_inventory' }, - { label: '已预占', value: 'cdk_reserved' }, { label: '已生成链接', value: 'link_generated' }, { label: '已创建会话', value: 'claimed' }, { label: '已确认角色', value: 'role_confirmed' }, @@ -23,36 +21,6 @@ export const adminTaskStatusOptions = [ { label: '已关闭', value: 'closed' }, ] -export const adminInventoryStatusOptions = [ - { label: '全部', value: '' }, - { label: '可用', value: 'available' }, - { label: '已预占', value: 'reserved' }, - { label: '已发放', value: 'delivered' }, - { label: '已消耗', value: 'consumed' }, - { label: '已作废', value: 'invalid' }, -] - -export const adminInventoryCredentialTypeOptions = [ - { label: '全部凭据类型', value: '' }, - { label: '兑换码(领取链路)', value: 'claim_code' }, - { label: '卡号卡密', value: 'card_password' }, - { label: '账号密码', value: 'account_password' }, - { label: '激活链接', value: 'activation_link' }, - { label: '纯文本凭据', value: 'text_credential' }, -] - -export const adminWebhookProcessedOptions = [ - { label: '全部', value: '' }, - { label: '处理成功', value: '1' }, - { label: '处理失败', value: '0' }, -] - -export const adminWebhookVisibilityOptions = [ - { label: '仅重要', value: 'important' }, - { label: '仅忽略', value: 'ignored' }, - { label: '全部', value: 'all' }, -] - export const adminUserRoleOptions = [ { label: '全部', value: '' }, { label: '管理员', value: 'admin' }, @@ -71,21 +39,14 @@ export const adminAuditActionOptions = [ { label: '创建后台用户', value: 'admin_user_created' }, { label: '修改用户角色', value: 'admin_user_role_updated' }, { label: '修改用户状态', value: 'admin_user_status_updated' }, - { label: '修改用户库存组', value: 'admin_user_inventory_groups_updated' }, { label: '重置用户密码', value: 'admin_user_password_reset' }, - { label: '释放任务库存', value: 'task_release_inventory' }, { label: '重发领取链接', value: 'task_regenerate_claim_link' }, { label: '关闭任务', value: 'task_closed' }, { label: '转人工处理', value: 'task_mark_manual_review' }, - { label: '释放库存项', value: 'inventory_item_released' }, - { label: '作废库存项', value: 'inventory_item_invalidated' }, - { label: '重放 Webhook', value: 'webhook_replayed' }, ] export const adminAuditTargetTypeOptions = [ { label: '全部', value: '' }, { label: '后台用户', value: 'admin_user' }, { label: '交付任务', value: 'task' }, - { label: '库存项', value: 'inventory_item' }, - { label: 'Webhook 事件', value: 'webhook_event' }, ] diff --git a/apps/frontend/src/utils/admin-webhook.ts b/apps/frontend/src/utils/admin-webhook.ts deleted file mode 100644 index 32af4328..00000000 --- a/apps/frontend/src/utils/admin-webhook.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { formatStatusWithRaw } from '@/utils/admin-display' - -export function formatAdminWebhookEventType(eventType: string) { - const normalized = String(eventType || '').trim() - - if (!normalized) { - return '-' - } - - const labelMap: Record = { - payment_success: '支付成功', - trade_create: '订单创建', - trade_closed: '订单关闭', - buyer_confirm_goods: '确认收货', - auto_delivery_done: '自动发货完成', - auto_delivery_confirmed: '自动发货确认完成', - refund_apply: '买家申请退款', - trade_event: '交易事件', - } - - return formatStatusWithRaw(normalized, labelMap) -} diff --git a/apps/frontend/src/views/admin/dashboard/AdminDashboardView.vue b/apps/frontend/src/views/admin/dashboard/AdminDashboardView.vue index 41a81e97..4d73b0ec 100644 --- a/apps/frontend/src/views/admin/dashboard/AdminDashboardView.vue +++ b/apps/frontend/src/views/admin/dashboard/AdminDashboardView.vue @@ -26,7 +26,6 @@ const cards = [ { key: 'claimingTasks', label: '领取中' }, { key: 'redeemedToday', label: '今日成功' }, { key: 'abnormalTasks', label: '异常任务' }, - { key: 'skuWithInventory', label: '有库存 SKU' }, ] as const diff --git a/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/components/AdminKuaishouCloudOverviewSection.vue b/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/components/AdminKuaishouCloudOverviewSection.vue index bc66eda4..ffef2d4d 100644 --- a/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/components/AdminKuaishouCloudOverviewSection.vue +++ b/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/components/AdminKuaishouCloudOverviewSection.vue @@ -36,7 +36,7 @@ defineProps<{
91卡券订单来自已接收的待补全队列 - 内部 SKU 决定最终任务与库存绑定 + 内部 SKU 决定任务与云资源匹配 cloud SKU 决定自动购买、发货与退号资源
diff --git a/apps/frontend/src/views/admin/inventory/AdminInventoryView.vue b/apps/frontend/src/views/admin/inventory/AdminInventoryView.vue deleted file mode 100644 index ab3f0761..00000000 --- a/apps/frontend/src/views/admin/inventory/AdminInventoryView.vue +++ /dev/null @@ -1,146 +0,0 @@ - - - - - diff --git a/apps/frontend/src/views/admin/inventory/components/AdminInventoryEntryCard.vue b/apps/frontend/src/views/admin/inventory/components/AdminInventoryEntryCard.vue deleted file mode 100644 index 86466f5a..00000000 --- a/apps/frontend/src/views/admin/inventory/components/AdminInventoryEntryCard.vue +++ /dev/null @@ -1,296 +0,0 @@ - - - - - diff --git a/apps/frontend/src/views/admin/inventory/components/AdminInventoryFilterCard.vue b/apps/frontend/src/views/admin/inventory/components/AdminInventoryFilterCard.vue deleted file mode 100644 index ac8bed39..00000000 --- a/apps/frontend/src/views/admin/inventory/components/AdminInventoryFilterCard.vue +++ /dev/null @@ -1,107 +0,0 @@ - - - - - diff --git a/apps/frontend/src/views/admin/inventory/components/AdminInventoryPoolGrid.vue b/apps/frontend/src/views/admin/inventory/components/AdminInventoryPoolGrid.vue deleted file mode 100644 index 89e7099f..00000000 --- a/apps/frontend/src/views/admin/inventory/components/AdminInventoryPoolGrid.vue +++ /dev/null @@ -1,242 +0,0 @@ - - - - - diff --git a/apps/frontend/src/views/admin/inventory/components/AdminInventoryTable.vue b/apps/frontend/src/views/admin/inventory/components/AdminInventoryTable.vue deleted file mode 100644 index eb5ab0cb..00000000 --- a/apps/frontend/src/views/admin/inventory/components/AdminInventoryTable.vue +++ /dev/null @@ -1,222 +0,0 @@ - - - - - diff --git a/apps/frontend/src/views/admin/inventory/composables/types.ts b/apps/frontend/src/views/admin/inventory/composables/types.ts deleted file mode 100644 index 6f0092e1..00000000 --- a/apps/frontend/src/views/admin/inventory/composables/types.ts +++ /dev/null @@ -1,21 +0,0 @@ -export type InventoryImportRow = { - skuCode: string - credentialType: string - batchNo: string - inventoryGroupCode: string - displayValue: string -} - -export type InventoryPoolGroup = { - key: string - skuCode: string - credentialType: string - inventoryGroupCode: string - totalCount: number - availableCount: number - reservedCount: number - deliveredCount: number - invalidCount: number - latestUpdatedAt: string - batchSummary: string -} diff --git a/apps/frontend/src/views/admin/inventory/composables/useAdminInventory.ts b/apps/frontend/src/views/admin/inventory/composables/useAdminInventory.ts deleted file mode 100644 index 72cb410f..00000000 --- a/apps/frontend/src/views/admin/inventory/composables/useAdminInventory.ts +++ /dev/null @@ -1,429 +0,0 @@ -import { computed, onMounted, ref, watch } from 'vue' - -import { showConfirm, showError, showSuccess } from '@/lib/feedback' -import { - createAdminInventoryItem, - fetchAdminInventoryItems, - fetchAdminInventorySkuSuggestions, - importAdminInventoryItems, -} from '@/services/admin' -import type { - AdminInventoryItemListItem, - AdminInventorySkuSuggestion, - AdminPagination, -} from '@/types/admin' -import { formatAdminDateTime } from '@/utils/admin-time' -import { - adminInventoryCredentialTypeOptions, - adminInventoryStatusOptions, -} from '@/utils/admin-options' - -import type { InventoryImportRow, InventoryPoolGroup } from './types' - -export function useAdminInventory() { - const loading = ref(true) - const importing = ref(false) - const creating = ref(false) - const actionLoadingId = ref(null) - const errorMessage = ref('') - - const filterSkuCode = ref('') - const filterCredentialType = ref('') - const filterStatus = ref('') - const filterBatchNo = ref('') - const filterInventoryGroupCode = ref('') - - const entrySkuCode = ref('') - const entryCredentialType = ref('claim_code') - const entryBatchNo = ref('') - const entryInventoryGroupCode = ref('') - const singleDisplayValue = ref('') - const bulkText = ref('') - const skuSuggestionsLoading = ref(false) - const inventorySkuSuggestions = ref([]) - - const items = ref([]) - const pagination = ref({ - page: 1, - pageSize: 20, - total: 0, - }) - - const pageSummary = computed(() => { - const availableCount = items.value.filter((item) => item.status === 'available').length - const reservedCount = items.value.filter((item) => item.status === 'reserved').length - const deliveredCount = items.value.filter((item) => item.status === 'consumed').length - const invalidCount = items.value.filter((item) => item.status === 'invalid').length - return { availableCount, reservedCount, deliveredCount, invalidCount } - }) - - const inventoryGroups = computed(() => { - const groupMap = new Map< - string, - { - skuCode: string - credentialType: string - inventoryGroupCode: string - totalCount: number - availableCount: number - reservedCount: number - deliveredCount: number - invalidCount: number - latestUpdatedAt: string - batches: Set - } - >() - - for (const item of items.value) { - const key = `${item.skuCode}::${item.credentialType || 'claim_code'}::${item.inventoryGroupCode || ''}` - const current = groupMap.get(key) || { - skuCode: item.skuCode, - credentialType: item.credentialType || 'claim_code', - inventoryGroupCode: item.inventoryGroupCode || '', - totalCount: 0, - availableCount: 0, - reservedCount: 0, - deliveredCount: 0, - invalidCount: 0, - latestUpdatedAt: item.updatedAt, - batches: new Set(), - } - - current.totalCount += 1 - if (item.status === 'available') current.availableCount += 1 - else if (item.status === 'reserved') current.reservedCount += 1 - else if (item.status === 'consumed') current.deliveredCount += 1 - else if (item.status === 'invalid') current.invalidCount += 1 - - if (item.batchNo) current.batches.add(item.batchNo) - if (String(item.updatedAt || '') > String(current.latestUpdatedAt || '')) - current.latestUpdatedAt = item.updatedAt - groupMap.set(key, current) - } - - return Array.from(groupMap.entries()) - .map(([key, value]) => ({ - key, - skuCode: value.skuCode, - credentialType: value.credentialType, - inventoryGroupCode: value.inventoryGroupCode, - totalCount: value.totalCount, - availableCount: value.availableCount, - reservedCount: value.reservedCount, - deliveredCount: value.deliveredCount, - invalidCount: value.invalidCount, - latestUpdatedAt: value.latestUpdatedAt, - batchSummary: - value.batches.size > 0 ? Array.from(value.batches).slice(0, 3).join(' / ') : '未分批', - })) - .sort((left, right) => { - if (right.availableCount !== left.availableCount) - return right.availableCount - left.availableCount - if (right.reservedCount !== left.reservedCount) - return right.reservedCount - left.reservedCount - return left.skuCode.localeCompare(right.skuCode) - }) - }) - - const pageOverview = computed(() => ({ - skuCount: new Set(items.value.map((item) => item.skuCode)).size, - groupCount: inventoryGroups.value.length, - })) - - const entrySkuSuggestions = computed(() => { - const keyword = entrySkuCode.value.trim().toLowerCase() - const suggestions = inventorySkuSuggestions.value.filter( - (item) => item.credentialType === entryCredentialType.value, - ) - if (!keyword) return suggestions.slice(0, 12) - return suggestions.filter((item) => item.skuCode.toLowerCase().includes(keyword)).slice(0, 12) - }) - - function getCredentialTypeLabel(value: string) { - const normalized = String(value || '').trim() - const option = adminInventoryCredentialTypeOptions.find((item) => item.value === normalized) - return option?.label || normalized || '-' - } - - function getCredentialTypeHint(value: string) { - const normalized = String(value || '').trim() - if (normalized === 'claim_code') - return '当前领取链路会优先预占这一类兑换码,快手云履约等场景可按内部 SKU 统一匹配。' - if (normalized === 'card_password') return '适合卡号 / 卡密一体的库存项。' - if (normalized === 'account_password') return '适合账号密码、账号令牌等组合凭据。' - if (normalized === 'activation_link') return '适合直接发链接的激活类库存。' - if (normalized === 'text_credential') return '适合任意纯文本凭据。' - return '库存预占时会按凭据类型与内部履约 SKU 一起匹配。' - } - - function getSkuNamingHint() { - if (entrySkuSuggestions.value.length > 0) - return `当前凭据类型下已有 ${entrySkuSuggestions.value.length} 个可复用的内部 SKU,也可以直接输入新的中文或英文名称。` - return '内部 SKU 支持中文、英文、数字和常见连接符。建议同一类商品长期使用同一个命名,方便共用库存池。' - } - - function formatInventoryStatus(item: AdminInventoryItemListItem) { - if (item.status === 'consumed') return '已完成发放' - if (item.status === 'reserved') return '已被任务预占' - if (item.status === 'invalid') return '已手动作废' - return '可参与后续自动分配' - } - - function getGroupSuggestion(group: InventoryPoolGroup) { - if (group.availableCount > 0) - return `当前还有 ${group.availableCount} 条可直接分配的库存,可以继续承接订单。` - if (group.reservedCount > 0) - return `当前没有可用库存,但有 ${group.reservedCount} 条正在被任务预占,建议优先检查任务进度。` - if (group.deliveredCount > 0 && group.invalidCount === 0) - return '当前页看到的库存都已经发放完成,若继续售卖需要补新的库存项。' - if (group.invalidCount > 0 && group.totalCount === group.invalidCount) - return '当前页这组库存都已作废,建议重新补充可用库存。' - return '当前页没有可立即分配的库存,建议补库存或检查对应履约任务。' - } - - function useGroupAsFilter(group: InventoryPoolGroup) { - filterSkuCode.value = group.skuCode - filterCredentialType.value = group.credentialType - filterInventoryGroupCode.value = group.inventoryGroupCode - void loadInventoryItems(1) - } - - function useGroupAsEntry(group: InventoryPoolGroup) { - entrySkuCode.value = group.skuCode - entryCredentialType.value = group.credentialType - entryInventoryGroupCode.value = group.inventoryGroupCode - if (group.batchSummary !== '未分批' && !group.batchSummary.includes(' / ')) - entryBatchNo.value = group.batchSummary - showSuccess( - `已把 ${group.skuCode} / ${group.credentialType} / ${group.inventoryGroupCode || '未分组'} 填入入库表单`, - ) - } - - function applySkuSuggestion(skuCode: string) { - entrySkuCode.value = String(skuCode || '').trim() - } - - function buildImportRows() { - const lines = bulkText.value - .split('\n') - .map((line) => line.trim()) - .filter(Boolean) - const rows: InventoryImportRow[] = [] - const defaultSkuCode = entrySkuCode.value.trim() - const defaultCredentialType = entryCredentialType.value.trim() || 'claim_code' - const defaultBatchNo = entryBatchNo.value.trim() - - for (let index = 0; index < lines.length; index += 1) { - const line = lines[index] - if (line.includes('|')) { - const parts = line.split('|').map((part) => part.trim()) - if (parts.length !== 4 && parts.length !== 5) - throw new Error( - `第 ${index + 1} 行格式不正确,请使用 内部SKU|凭据类型|批次号|库存组|凭据内容`, - ) - const [skuCode, credentialType, batchNo, inventoryGroupCode, displayValue] = - parts.length === 5 ? parts : [parts[0], parts[1], parts[2], '', parts[3]] - if (!skuCode || !displayValue) throw new Error(`第 ${index + 1} 行缺少内部 SKU 或凭据内容`) - rows.push({ - skuCode, - credentialType: credentialType || defaultCredentialType, - batchNo, - inventoryGroupCode: inventoryGroupCode || entryInventoryGroupCode.value.trim(), - displayValue, - }) - continue - } - if (!defaultSkuCode) - throw new Error('存在未带内部 SKU 的行,请先填写默认内部履约 SKU,或改用四列导入格式') - rows.push({ - skuCode: defaultSkuCode, - credentialType: defaultCredentialType, - batchNo: defaultBatchNo, - inventoryGroupCode: entryInventoryGroupCode.value.trim(), - displayValue: line, - }) - } - return rows - } - - async function loadInventoryItems(page = pagination.value.page) { - loading.value = true - errorMessage.value = '' - try { - const response = await fetchAdminInventoryItems({ - page, - pageSize: pagination.value.pageSize, - skuCode: filterSkuCode.value.trim(), - credentialType: filterCredentialType.value.trim(), - status: filterStatus.value.trim(), - batchNo: filterBatchNo.value.trim(), - inventoryGroupCode: filterInventoryGroupCode.value.trim(), - }) - items.value = response.data.items - pagination.value = response.data.pagination - } catch (error) { - errorMessage.value = error instanceof Error ? error.message : '读取库存列表失败' - } finally { - loading.value = false - } - } - - async function loadInventorySkuSuggestions() { - skuSuggestionsLoading.value = true - try { - const response = await fetchAdminInventorySkuSuggestions({ - credentialType: entryCredentialType.value.trim() || 'claim_code', - inventoryGroupCode: entryInventoryGroupCode.value.trim(), - limit: 50, - }) - inventorySkuSuggestions.value = response.data.items - } catch (error) { - showError(error instanceof Error ? error.message : '读取内部 SKU 建议失败') - } finally { - skuSuggestionsLoading.value = false - } - } - - function resetFilters() { - filterSkuCode.value = '' - filterCredentialType.value = '' - filterStatus.value = '' - filterBatchNo.value = '' - filterInventoryGroupCode.value = '' - void loadInventoryItems(1) - } - - async function submitCreate() { - if (!entrySkuCode.value.trim() || !singleDisplayValue.value.trim()) { - showError('请先填写内部履约 SKU 和凭据内容') - return - } - creating.value = true - try { - await createAdminInventoryItem({ - skuCode: entrySkuCode.value.trim(), - credentialType: entryCredentialType.value.trim() || 'claim_code', - batchNo: entryBatchNo.value.trim(), - inventoryGroupCode: entryInventoryGroupCode.value.trim(), - displayValue: singleDisplayValue.value.trim(), - }) - showSuccess('库存项已新增') - singleDisplayValue.value = '' - await loadInventoryItems(1) - } catch (error) { - showError(error instanceof Error ? error.message : '新增库存项失败') - } finally { - creating.value = false - } - } - - async function submitImport() { - const rawLines = bulkText.value - .split('\n') - .map((line) => line.trim()) - .filter(Boolean) - if (rawLines.length === 0) { - showError('请至少输入一条待导入库存') - return - } - importing.value = true - try { - const rows = buildImportRows() - const response = await importAdminInventoryItems({ rows }) - showSuccess(`导入完成,新增 ${response.data.created} 条,重复 ${response.data.duplicated} 条`) - bulkText.value = '' - await loadInventoryItems(1) - } catch (error) { - showError(error instanceof Error ? error.message : '导入失败') - } finally { - importing.value = false - } - } - - async function runRowAction( - inventoryItemId: number, - action: () => Promise, - successMessage: string, - confirmText: string, - ) { - try { - await showConfirm(confirmText, '确认操作', { - type: 'warning', - confirmButtonText: '继续执行', - cancelButtonText: '取消', - }) - } catch { - return - } - actionLoadingId.value = inventoryItemId - try { - await action() - showSuccess(successMessage) - await loadInventoryItems() - } catch (error) { - showError(error instanceof Error ? error.message : '操作失败') - } finally { - actionLoadingId.value = null - } - } - - watch(entryCredentialType, () => { - void loadInventorySkuSuggestions() - }) - watch(entryInventoryGroupCode, () => { - void loadInventorySkuSuggestions() - }) - - onMounted(async () => { - await Promise.all([loadInventoryItems(), loadInventorySkuSuggestions()]) - }) - - return { - // state - loading, - importing, - creating, - actionLoadingId, - errorMessage, - items, - pagination, - pageSummary, - pageOverview, - inventoryGroups, - // filter - filterSkuCode, - filterCredentialType, - filterStatus, - filterBatchNo, - filterInventoryGroupCode, - // entry - entrySkuCode, - entryCredentialType, - entryBatchNo, - entryInventoryGroupCode, - singleDisplayValue, - bulkText, - skuSuggestionsLoading, - inventorySkuSuggestions, - entrySkuSuggestions, - // actions - loadInventoryItems, - resetFilters, - submitCreate, - submitImport, - runRowAction, - useGroupAsFilter, - useGroupAsEntry, - applySkuSuggestion, - // formatters - getCredentialTypeLabel, - getCredentialTypeHint, - getSkuNamingHint, - formatInventoryStatus, - getGroupSuggestion, - // utils - formatAdminDateTime, - adminInventoryCredentialTypeOptions, - adminInventoryStatusOptions, - } -} diff --git a/apps/frontend/src/views/admin/layout/AdminLayout.vue b/apps/frontend/src/views/admin/layout/AdminLayout.vue index aac897da..f2e40359 100644 --- a/apps/frontend/src/views/admin/layout/AdminLayout.vue +++ b/apps/frontend/src/views/admin/layout/AdminLayout.vue @@ -2,9 +2,7 @@ import { computed, ref } from 'vue' import { useRoute, useRouter } from 'vue-router' import { - Box, Monitor, - Connection, DataAnalysis, Document, Expand, @@ -35,7 +33,6 @@ const isSidebarCollapsed = ref( const currentRole = computed(() => getAdminRole()) const isAdmin = computed(() => currentRole.value === 'admin') -const isOperator = computed(() => currentRole.value === 'operator') const roleLabel = computed(() => { if (currentRole.value === 'admin') return '管理员' @@ -57,14 +54,6 @@ const navItems = computed(() => { { to: '/admin/tasks', label: '任务', icon: List }, ) - if (isAdmin.value || isOperator.value) { - items.push({ to: '/admin/inventory', label: '库存', icon: Box }) - } - - if (isAdmin.value || isOperator.value) { - items.push({ to: '/admin/webhook-events', label: 'Webhook', icon: Connection }) - } - if (isAdmin.value) { items.push( { to: '/admin/platform-shops', label: '平台配置', icon: Setting }, diff --git a/apps/frontend/src/views/admin/orders/AdminOrderDetailView.vue b/apps/frontend/src/views/admin/orders/AdminOrderDetailView.vue index a53f1242..13adc27c 100644 --- a/apps/frontend/src/views/admin/orders/AdminOrderDetailView.vue +++ b/apps/frontend/src/views/admin/orders/AdminOrderDetailView.vue @@ -5,12 +5,7 @@ import { useRoute } from 'vue-router' import AdminStatusTag from '@/components/admin/AdminStatusTag.vue' import { fetchAdminOrderDetail } from '@/services/admin' import type { AdminOrderDetail } from '@/types/admin' -import { - formatBindingRoleSummary, - formatBindingCountSummary, -} from '@/utils/admin-display' import { formatAdminDateTime } from '@/utils/admin-time' -import { formatAdminWebhookEventType } from '@/utils/admin-webhook' const route = useRoute() const loading = ref(true) @@ -104,15 +99,6 @@ function formatSpecSummary(spec: Record) { 系统绑定 - - 库存绑定 - {{ detail.order.bindingSummary.totalBindingCount }} 条绑定记录 - 预占 {{ detail.order.bindingSummary.reservedBindingCount }} · 已消费 - {{ detail.order.bindingSummary.consumedBindingCount }} · 已释放 - {{ detail.order.bindingSummary.releasedBindingCount }} - @@ -176,14 +162,6 @@ function formatSpecSummary(spec: Record) { - - - @@ -193,43 +171,6 @@ function formatSpecSummary(spec: Record) { - -

Webhook 摘要

- - - - - - - - - - - - - - - - - - - - - -
-

原始载荷

diff --git a/apps/frontend/src/views/admin/tasks/AdminTaskDetailView.vue b/apps/frontend/src/views/admin/tasks/AdminTaskDetailView.vue index ac5d65b6..3bffec92 100644 --- a/apps/frontend/src/views/admin/tasks/AdminTaskDetailView.vue +++ b/apps/frontend/src/views/admin/tasks/AdminTaskDetailView.vue @@ -5,7 +5,6 @@ import { closeAdminTask, markAdminTaskManualReview, regenerateAdminTaskClaimLink, - releaseAdminTaskInventoryBinding, retryAdminTask, } from '@/services/admin' @@ -14,7 +13,6 @@ import AdminTaskActionBar from './components/AdminTaskActionBar.vue' import AdminTaskEventsSection from './components/AdminTaskEventsSection.vue' import AdminTaskHeroSection from './components/AdminTaskHeroSection.vue' import AdminTaskInfoSection from './components/AdminTaskInfoSection.vue' -import AdminTaskInventoryBindingsSection from './components/AdminTaskInventoryBindingsSection.vue' import AdminTaskManualDispatchSection from './components/AdminTaskManualDispatchSection.vue' import AdminTaskRedeemResolutionSection from './components/AdminTaskRedeemResolutionSection.vue' import AdminTaskScreenshotSection from './components/AdminTaskScreenshotSection.vue' @@ -135,20 +133,6 @@ onMounted(loadDetail) - - {
- + @@ -367,11 +337,7 @@ onMounted(() => { v-if="canManageTaskLifecycle" :disabled=" row.executorKey === 'manual_dispatch' || - ![ - 'retry_pending', - 'manual_review', - 'waiting_inventory', - ].includes(row.status) + !['retry_pending', 'manual_review'].includes(row.status) " :loading="actionLoadingId === row.taskId" size="small" diff --git a/apps/frontend/src/views/admin/tasks/components/AdminTaskInfoSection.vue b/apps/frontend/src/views/admin/tasks/components/AdminTaskInfoSection.vue index c0dc5b9a..2ebe7a00 100644 --- a/apps/frontend/src/views/admin/tasks/components/AdminTaskInfoSection.vue +++ b/apps/frontend/src/views/admin/tasks/components/AdminTaskInfoSection.vue @@ -36,21 +36,12 @@ defineProps() {{ detail.task.resultMessage || '-' }} - - 客服账号不可查看库存凭据与原始领取 Token + + 客服账号不可查看原始领取 Token diff --git a/apps/frontend/src/views/admin/tasks/components/AdminTaskInventoryBindingsSection.vue b/apps/frontend/src/views/admin/tasks/components/AdminTaskInventoryBindingsSection.vue deleted file mode 100644 index 5d97d2c9..00000000 --- a/apps/frontend/src/views/admin/tasks/components/AdminTaskInventoryBindingsSection.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - - - diff --git a/apps/frontend/src/views/admin/tasks/components/AdminTaskRedeemResolutionSection.vue b/apps/frontend/src/views/admin/tasks/components/AdminTaskRedeemResolutionSection.vue index d760e401..26f7fb8e 100644 --- a/apps/frontend/src/views/admin/tasks/components/AdminTaskRedeemResolutionSection.vue +++ b/apps/frontend/src/views/admin/tasks/components/AdminTaskRedeemResolutionSection.vue @@ -40,7 +40,7 @@ defineProps()
@@ -48,10 +48,6 @@ defineProps() {{ formatRedeemOutcomeLabel(attempt.outcome) }}
-
- 库存项 - {{ attempt.inventoryItemId ? `#${attempt.inventoryItemId}` : '-' }} -
凭据 {{ attempt.codeMasked || '-' }} diff --git a/apps/frontend/src/views/admin/users/AdminUsersView.vue b/apps/frontend/src/views/admin/users/AdminUsersView.vue index d55676ad..9518122a 100644 --- a/apps/frontend/src/views/admin/users/AdminUsersView.vue +++ b/apps/frontend/src/views/admin/users/AdminUsersView.vue @@ -26,11 +26,9 @@ const { updateRole, toggleStatus, promptResetPassword, - promptUpdateInventoryGroups, // formatters isCurrentUser, formatRoleLabel, - formatInventoryGroups, // utils hasAdminRole, formatAdminDateTime, @@ -78,9 +76,7 @@ const { :update-role="updateRole" :toggle-status="toggleStatus" :prompt-reset-password="promptResetPassword" - :prompt-update-inventory-groups="promptUpdateInventoryGroups" :format-role-label="formatRoleLabel" - :format-inventory-groups="formatInventoryGroups" :format-admin-date-time="formatAdminDateTime" /> diff --git a/apps/frontend/src/views/admin/users/components/AdminUsersTable.vue b/apps/frontend/src/views/admin/users/components/AdminUsersTable.vue index ccfd079a..47862a68 100644 --- a/apps/frontend/src/views/admin/users/components/AdminUsersTable.vue +++ b/apps/frontend/src/views/admin/users/components/AdminUsersTable.vue @@ -13,9 +13,7 @@ defineProps<{ updateRole: (item: AdminUserListItem, nextRole: AdminRole) => Promise toggleStatus: (item: AdminUserListItem) => Promise promptResetPassword: (item: AdminUserListItem) => Promise - promptUpdateInventoryGroups: (item: AdminUserListItem) => Promise formatRoleLabel: (role: AdminRole) => string - formatInventoryGroups: (item: AdminUserListItem) => string[] formatAdminDateTime: (value: string) => string }>() @@ -39,19 +37,6 @@ defineProps<{ - - - @@ -101,13 +86,6 @@ defineProps<{ @click="toggleStatus(row)" >{{ row.status === 'active' ? '停用' : '启用' }} - 配库存组 td.el-table__cell) { background: var(--el-table-row-hover-bg-color); } -.inventory-tags { - display: flex; - gap: 4px; - align-items: center; - white-space: nowrap; -} -.inventory-tags :deep(.el-tag) { - flex: 0 0 auto; -} .date-cell { display: inline-block; white-space: nowrap; diff --git a/apps/frontend/src/views/admin/users/composables/useAdminUsers.ts b/apps/frontend/src/views/admin/users/composables/useAdminUsers.ts index c08944e1..82de9cf1 100644 --- a/apps/frontend/src/views/admin/users/composables/useAdminUsers.ts +++ b/apps/frontend/src/views/admin/users/composables/useAdminUsers.ts @@ -11,7 +11,6 @@ import { createAdminUser, fetchAdminUsers, resetAdminUserPassword, - updateAdminUserInventoryGroups, updateAdminUserRole, updateAdminUserStatus, } from '@/services/admin' @@ -44,12 +43,6 @@ export function useAdminUsers() { return '普通运营' } - function formatInventoryGroups(item: AdminUserListItem) { - if (item.role !== 'support') return ['不限库存组'] - if (item.inventoryGroupCodes.length === 0) return ['未绑定'] - return item.inventoryGroupCodes - } - async function loadUsers(page = pagination.value.page) { if (!hasAdminRole('admin')) { loading.value = false @@ -170,39 +163,6 @@ export function useAdminUsers() { } } - async function promptUpdateInventoryGroups(item: AdminUserListItem) { - if (item.role !== 'support') { - showError('仅客服账号需要绑定库存组') - return - } - try { - const result = await showPrompt( - `请输入 ${item.username} 可用的库存组,多个组用英文逗号分隔`, - '配置库存组', - { - inputType: 'text', - inputValue: item.inventoryGroupCodes.join(','), - inputPlaceholder: '例如 A组,B组,售后组', - confirmButtonText: '保存', - cancelButtonText: '取消', - }, - ) - const inventoryGroupCodes = String(result.value || '') - .split(',') - .map((v) => v.trim()) - .filter(Boolean) - actionLoadingId.value = item.userId - await updateAdminUserInventoryGroups(item.userId, { inventoryGroupCodes }) - showSuccess('用户库存组已更新') - await loadUsers() - } catch (error) { - if (isFeedbackDismissed(error)) return - showError(error instanceof Error ? error.message : '更新库存组失败') - } finally { - actionLoadingId.value = null - } - } - onMounted(loadUsers) return { @@ -227,11 +187,9 @@ export function useAdminUsers() { updateRole, toggleStatus, promptResetPassword, - promptUpdateInventoryGroups, // formatters isCurrentUser, formatRoleLabel, - formatInventoryGroups, // utils hasAdminRole, formatAdminDateTime, diff --git a/apps/frontend/src/views/admin/webhook-events/AdminWebhookEventDetailView.vue b/apps/frontend/src/views/admin/webhook-events/AdminWebhookEventDetailView.vue deleted file mode 100644 index 96ffeab6..00000000 --- a/apps/frontend/src/views/admin/webhook-events/AdminWebhookEventDetailView.vue +++ /dev/null @@ -1,354 +0,0 @@ - - - - - diff --git a/apps/frontend/src/views/admin/webhook-events/AdminWebhookEventsView.vue b/apps/frontend/src/views/admin/webhook-events/AdminWebhookEventsView.vue deleted file mode 100644 index 69bc5084..00000000 --- a/apps/frontend/src/views/admin/webhook-events/AdminWebhookEventsView.vue +++ /dev/null @@ -1,415 +0,0 @@ - - - - - diff --git a/docs/91卡券/6.开发设计.md b/docs/91卡券/6.开发设计.md index b4ba9967..ae572be3 100644 --- a/docs/91卡券/6.开发设计.md +++ b/docs/91卡券/6.开发设计.md @@ -50,11 +50,11 @@ - `POST /api/v1/open/91/orders/create` - `POST /api/v1/open/91/orders/query` -不放进现有 `/api/v1/webhooks`,因为: +接口使用独立公开路由组,原因: -- `91` 不是 webhook 来源; - `91` 需要自己的响应结构:`code/message/data`; -- 与现有项目 `buildSuccessPayload(code=0)` 不兼容。 +- 与后台管理接口的统一响应结构隔离; +- 便于按 91 的签名、时间戳和错误码规范单独演进。 ### 4.2 返回格式