后端可视化调试

This commit is contained in:
yml2213
2026-04-14 00:21:32 +08:00
parent 0c2f2e0a12
commit fa36be6539
14 changed files with 617 additions and 34 deletions
@@ -193,6 +193,54 @@ export async function listInventoryItems({
}
}
export async function listInventorySkuSuggestions({
credentialType = '',
keyword = '',
limit = 50,
} = {}) {
const filters = []
const params = []
if (credentialType) {
params.push(credentialType)
filters.push(`credential_type = $${params.length}`)
}
if (keyword) {
params.push(`%${keyword}%`)
filters.push(`sku_code ILIKE $${params.length}`)
}
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,
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
ORDER BY
COUNT(*) FILTER (WHERE status = 'available') DESC,
COUNT(*) DESC,
MAX(updated_at) DESC,
sku_code ASC
LIMIT $${params.length}
`,
params,
)
return result.rows
}
export async function createInventoryItems(rows) {
let created = 0
@@ -3,6 +3,7 @@ import { Router } from 'express'
import {
createAdminInventoryItem,
getAdminInventoryItems,
getAdminInventorySkuSuggestions,
importAdminInventoryItems,
invalidateAdminInventoryItem,
releaseAdminInventoryItem,
@@ -22,6 +23,15 @@ router.get('/inventory', createJsonHandler(
},
))
router.get('/inventory/sku-suggestions', createJsonHandler(
(req) => getAdminInventorySkuSuggestions(req.query),
{
successMessage: 'ok',
errorMessage: '读取内部 SKU 建议失败',
scope: '[admin/inventory/sku-suggestions]',
},
))
router.post('/inventory', createJsonHandler(
(req) => createAdminInventoryItem(req.body),
{
@@ -21,6 +21,7 @@ import {
getInventoryItemById,
invalidateInventoryItem,
listInventoryItems,
listInventorySkuSuggestions,
markInventoryItemDelivered,
releaseReservedInventoryItem,
} from '../../repositories/inventory-repo.js'
@@ -155,6 +156,7 @@ export async function getAdminOrderDetail(orderId) {
itemSummary,
rawPayload: safeParseJson(order.raw_payload_json),
bindingSummary: buildOrderBindingSummary(tasks, taskBindingSummaryMap),
agisoAutoDelivery: buildOrderAgisoAutoDeliverySummary(order, tasks),
},
items: items.map((item) => ({
orderItemId: item.id,
@@ -424,6 +426,30 @@ export async function createAdminInventoryItem(payload = {}) {
}
}
export async function getAdminInventorySkuSuggestions(query = {}) {
const credentialType = String(query.credentialType || '').trim()
const keyword = String(query.keyword || '').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,
})
return {
items: items.map((item) => ({
skuCode: String(item.sku_code || '').trim(),
credentialType: String(item.credential_type || '').trim() || 'tencent_code',
totalCount: Number(item.total_count || 0),
availableCount: Number(item.available_count || 0),
latestUpdatedAt: item.latest_updated_at || null,
})),
}
}
export async function importAdminInventoryItems(payload = {}) {
const rows = normalizeInventoryImportRows(payload)
@@ -1286,10 +1312,12 @@ export async function completeAdminTaskManualDispatch(taskId, payload = {}, sess
function mapAdminTaskSummary(task, bindingSummary = createEmptyTaskBindingSummary()) {
const binding = buildTaskBindingState(task)
const taskContext = parseTaskContext(task)
return {
taskId: task.id,
taskNo: task.task_no,
deliveryStatus: task.delivery_status || '',
status: task.task_status,
systemBindingStatus: binding.systemBindingStatus,
userBindingStatus: binding.userBindingStatus,
@@ -1301,6 +1329,7 @@ function mapAdminTaskSummary(task, bindingSummary = createEmptyTaskBindingSummar
lastError: task.last_error,
retryCount: getTaskRetryCount(task),
bindingSummary,
agisoAutoDelivery: mapAgisoAutoDeliveryContext(taskContext.agisoAutoDelivery),
createdAt: task.created_at,
updatedAt: task.updated_at,
}
@@ -1606,6 +1635,7 @@ async function getRequiredInventoryItem(inventoryItemId) {
function mapAdminTaskListItem(task, bindingSummary = createEmptyTaskBindingSummary(), viewerContext = createAdminViewerContext()) {
const binding = buildTaskBindingState(task)
const taskContext = parseTaskContext(task)
return {
taskId: task.id,
@@ -1629,6 +1659,7 @@ function mapAdminTaskListItem(task, bindingSummary = createEmptyTaskBindingSumma
redeemedAt: task.redeemed_at,
retryCount: getTaskRetryCount(task),
bindingSummary,
agisoAutoDelivery: mapAgisoAutoDeliveryContext(taskContext.agisoAutoDelivery),
lastError: task.last_error,
createdAt: task.created_at,
updatedAt: task.updated_at,
@@ -1892,6 +1923,61 @@ function buildOrderBindingSummary(tasks, taskBindingSummaryMap = new Map()) {
}
}
function buildOrderAgisoAutoDeliverySummary(order, tasks = []) {
if (String(order?.provider || '').trim() !== 'agiso' || String(order?.platform || '').trim() !== 'xianyu') {
return null
}
const normalizedTasks = Array.isArray(tasks) ? tasks.filter(Boolean) : []
const totalTaskCount = normalizedTasks.length
const deliveredTaskCount = normalizedTasks.filter((task) => String(task?.delivery_status || '').trim() === 'delivered').length
const latest = normalizedTasks.reduce((best, task) => {
const autoDelivery = mapAgisoAutoDeliveryContext(parseTaskContext(task).agisoAutoDelivery)
if (!autoDelivery) {
return best
}
const candidate = {
...autoDelivery,
sourceTaskId: Number(task.id || 0) || null,
sourceTaskNo: String(task.task_no || '').trim(),
}
const candidateTime = Date.parse(String(candidate.updatedAt || task.updated_at || ''))
const bestTime = Date.parse(String(best?.updatedAt || ''))
if (!best || (Number.isFinite(candidateTime) && (!Number.isFinite(bestTime) || candidateTime >= bestTime))) {
return candidate
}
return best
}, null)
if (latest) {
return {
...latest,
totalTaskCount,
deliveredTaskCount,
}
}
return {
status: totalTaskCount === 0 ? 'not_started' : deliveredTaskCount >= totalTaskCount ? 'pending' : 'waiting',
trigger: '',
reason: deliveredTaskCount >= totalTaskCount ? '' : 'waiting_other_tasks',
platformOrderId: String(order?.platform_order_id || '').trim(),
responseStatus: 0,
errorMessage: '',
requestId: '',
aldsType: null,
updatedAt: null,
sourceTaskId: null,
sourceTaskNo: '',
totalTaskCount,
deliveredTaskCount,
}
}
function buildTaskBindingState(task) {
const normalizedStatus = String(task?.task_status || '').trim()
@@ -2018,6 +2104,24 @@ function mapManualDispatchContext(value, viewerContext = createAdminViewerContex
}
}
function mapAgisoAutoDeliveryContext(value) {
if (!value || typeof value !== 'object') {
return null
}
return {
status: String(value.status || '').trim(),
trigger: String(value.trigger || '').trim(),
reason: String(value.reason || '').trim(),
platformOrderId: String(value.platformOrderId || '').trim(),
responseStatus: Number(value.responseStatus || 0),
errorMessage: String(value.errorMessage || '').trim(),
requestId: String(value.requestId || '').trim(),
aldsType: Number(value.aldsType || 0) || null,
updatedAt: value.updatedAt || null,
}
}
function mapRedeemResolutionContext(value) {
if (!value || typeof value !== 'object') {
return null