修复订单详情错误

This commit is contained in:
yml
2026-04-09 02:36:09 +08:00
parent 98cc0c16fc
commit b509394b09
6 changed files with 137 additions and 15 deletions
@@ -74,6 +74,7 @@ export function getAdminOrderDetail(orderId) {
const items = listOrderItemsByOrderId(order.id)
const tasks = listTasksByOrderId(order.id)
const webhookEvents = listWebhookEventsByOrderId(order.id)
const itemSummary = summarizeOrderItems(items)
return {
order: {
@@ -94,6 +95,7 @@ export function getAdminOrderDetail(orderId) {
paidAt: order.paid_at,
createdAt: order.created_at,
updatedAt: order.updated_at,
itemSummary,
rawPayload: safeParseJson(order.raw_payload_json),
bindingSummary: buildOrderBindingSummary(tasks),
},
@@ -101,6 +103,7 @@ export function getAdminOrderDetail(orderId) {
orderItemId: item.id,
skuCode: item.sku_code,
skuName: item.sku_name,
itemTitle: resolveOrderItemTitle(item),
quantity: item.quantity,
deliveryMode: item.delivery_mode,
spec: safeParseJson(item.spec_json),
@@ -965,7 +968,8 @@ function summarizeOrderItems(items) {
}
const [firstItem] = normalizedItems
const firstLabel = String(firstItem?.sku_name || firstItem?.sku_code || '').trim()
const firstLabel = resolveOrderItemTitle(firstItem)
|| String(firstItem?.sku_name || firstItem?.sku_code || '').trim()
if (normalizedItems.length === 1) {
return firstLabel
@@ -974,6 +978,25 @@ function summarizeOrderItems(items) {
return `${firstLabel}${normalizedItems.length}`
}
function resolveOrderItemTitle(item) {
if (!item) {
return ''
}
const spec = safeParseJson(item.spec_json)
return pickFirstNonEmpty([
spec.title,
spec.Title,
spec.itemTitle,
spec.item_title,
spec.goods_name,
spec.goodsName,
item.sku_name,
item.sku_code,
])
}
function buildOrderBindingSummary(tasks) {
const normalizedTasks = Array.isArray(tasks) ? tasks : []
const totalTaskCount = normalizedTasks.length
@@ -1,4 +1,5 @@
import { getDb, runInTransaction } from '../../db/client.js'
import { listOrderItemsByOrderId, replaceOrderItems } from '../../repositories/order-item-repo.js'
import { enrichAgisoXianyuTradeOrder } from '../platforms/agiso/xianyu/order-detail-service.js'
import { logInfo } from '../../utils/logger.js'
import { nowIso } from '../../utils/time.js'
@@ -37,6 +38,7 @@ export async function repairAgisoOrderData() {
let repairedOrderIdCount = 0
let repairedAmountCount = 0
let repairedOrderItemCount = 0
for (const row of rows) {
const latestRequestBody = parseJsonObject(row.latest_body_json)
@@ -91,6 +93,11 @@ export async function repairAgisoOrderData() {
const nextBuyerName = pickFirstNonEmpty([detailResult.parsed.buyerName, row.buyer_name])
const nextReceiverContact = pickFirstNonEmpty([detailResult.parsed.receiverContact, row.receiver_contact])
const nextPaidAt = detailResult.parsed.paidAt || row.paid_at
const currentOrderItems = listOrderItemsByOrderId(row.id)
const repairedItems = Array.isArray(detailResult.parsed.items) ? detailResult.parsed.items : []
const shouldRepairOrderItems = repairedItems.length > 0
&& (currentOrderItems.length === 0 || currentOrderItems.length === repairedItems.length)
&& hasOrderItemChanges(currentOrderItems, repairedItems)
const orderIdChanged = nextOrderId && nextOrderId !== String(row.platform_order_id || '')
const amountChanged = nextAmount > 0 && nextAmount !== Number(row.total_amount || 0)
@@ -101,7 +108,7 @@ export async function repairAgisoOrderData() {
|| nextReceiverContact !== String(row.receiver_contact || '')
|| nextPaidAt !== row.paid_at
if (!orderIdChanged && !amountChanged && !payloadChanged && !metadataChanged) {
if (!orderIdChanged && !amountChanged && !payloadChanged && !metadataChanged && !shouldRepairOrderItems) {
continue
}
@@ -132,6 +139,22 @@ export async function repairAgisoOrderData() {
row.id,
)
if (shouldRepairOrderItems) {
const itemNow = nowIso()
replaceOrderItems(
row.id,
repairedItems.map((item) => ({
skuCode: item.skuCode,
skuName: item.skuName,
quantity: item.quantity,
specJson: JSON.stringify(item.spec || {}),
deliveryMode: 'claim_link',
createdAt: itemNow,
updatedAt: itemNow,
})),
)
}
if (orderIdChanged) {
db.prepare('UPDATE delivery_tasks SET platform_order_id = ? WHERE order_id = ?').run(nextOrderId, row.id)
db.prepare(`
@@ -155,16 +178,38 @@ export async function repairAgisoOrderData() {
if (amountChanged) {
repairedAmountCount += 1
}
if (shouldRepairOrderItems) {
repairedOrderItemCount += 1
}
}
if (repairedOrderIdCount > 0 || repairedAmountCount > 0) {
if (repairedOrderIdCount > 0 || repairedAmountCount > 0 || repairedOrderItemCount > 0) {
logInfo('[startup]', '已自动修复历史 Agiso 咸鱼订单数据', {
repairedOrderIdCount,
repairedAmountCount,
repairedOrderItemCount,
})
}
}
function hasOrderItemChanges(currentItems, repairedItems) {
if (!Array.isArray(currentItems) || !Array.isArray(repairedItems) || currentItems.length !== repairedItems.length) {
return repairedItems.length > 0
}
return repairedItems.some((item, index) => {
const current = currentItems[index]
const currentSpec = parseJsonObject(current?.spec_json, { preserveLargeIntegers: true })
const nextSpec = item?.spec || {}
return String(current?.sku_code || '').trim() !== String(item?.skuCode || '').trim()
|| String(current?.sku_name || '').trim() !== String(item?.skuName || '').trim()
|| Number(current?.quantity || 0) !== Number(item?.quantity || 0)
|| JSON.stringify(currentSpec) !== JSON.stringify(nextSpec)
})
}
function pickFirstNonEmpty(values) {
for (const value of values) {
const normalized = String(value || '').trim()
@@ -5,6 +5,7 @@ import { createWebhookEvent, updateWebhookEvent } from '../../repositories/webho
import { enrichAgisoXianyuTradeOrder } from '../platforms/agiso/xianyu/order-detail-service.js'
import { upsertOrderFromWebhook } from './order-service.js'
import { createHttpError } from '../../utils/http.js'
import { parseJsonObject } from '../../utils/json.js'
import { logWebhook } from '../../utils/logger.js'
import { parseAmountToFen } from '../../utils/money.js'
import { nowIso } from '../../utils/time.js'
@@ -172,8 +173,8 @@ async function enrichTradeBeforeUpsert(parsed, { requestId = '', webhookEventId
return { parsed, enriched: false, reason: 'not_supported' }
}
if (Number(parsed.totalAmount || 0) > 0) {
return { parsed, enriched: false, reason: 'already_has_amount' }
if (!shouldEnrichAgisoXianyuTrade(parsed)) {
return { parsed, enriched: false, reason: 'enough_fields_present' }
}
const detailResult = await enrichAgisoXianyuTradeOrder(parsed, { requestId })
@@ -201,6 +202,54 @@ async function enrichTradeBeforeUpsert(parsed, { requestId = '', webhookEventId
}
}
function shouldEnrichAgisoXianyuTrade(parsed) {
if (!parsed || parsed.provider !== 'agiso' || parsed.platform !== 'xianyu') {
return false
}
if (Number(parsed.totalAmount || 0) <= 0) {
return true
}
if (!String(parsed.buyerName || '').trim()) {
return true
}
if (!String(parsed.shopName || '').trim()) {
return true
}
if (!hasDetailedOrderItems(parsed.items)) {
return true
}
return false
}
function hasDetailedOrderItems(items) {
const normalizedItems = Array.isArray(items) ? items : []
if (normalizedItems.length === 0) {
return false
}
return normalizedItems.some((item) => {
const spec = isPlainObject(item?.spec) ? item.spec : {}
const title = pickFirstNonEmpty([
spec.title,
spec.Title,
spec.itemTitle,
spec.item_title,
spec.goods_name,
spec.goodsName,
item?.skuName,
])
const skuCode = String(item?.skuCode || '').trim()
return Boolean(title && title !== skuCode)
})
}
function parseAgisoTradeRequest(requestLike) {
const query = normalizeRecord(requestLike.query)
const body = normalizeRecord(requestLike.body)
@@ -473,16 +522,11 @@ function parsePayloadJson(rawJson) {
return {}
}
try {
const parsed = JSON.parse(rawJson)
return isPlainObject(parsed) ? parsed : {}
} catch {
throw createHttpError('json 参数不是合法 JSON', {
statusCode: 400,
errorCode: 'invalid_json_payload',
return parseJsonObject(rawJson, {
preserveLargeIntegers: true,
throwOnError: true,
})
}
}
function normalizeOrderItems(payload) {
const items = extractOrderItemSources(payload)
@@ -273,6 +273,7 @@ function mergeEnrichedTrade(parsed, detailPayload, { keepOriginalAmount = false
_agisoTradeDetail: detailPayload,
}
const totalAmountFen = resolveTotalAmountFen(detailPayload)
const mergedItems = normalizeOrderItems(detailPayload, normalizeOrderItems(parsed.rawPayload, parsed.items))
return {
...parsed,
@@ -317,7 +318,7 @@ function mergeEnrichedTrade(parsed, detailPayload, { keepOriginalAmount = false
totalAmount: keepOriginalAmount ? parsed.totalAmount : (totalAmountFen || parsed.totalAmount),
paidAt: parsed.paidAt || resolvePaidAt(detailPayload, parsed.paidAt),
rawPayload: mergedPayload,
items: normalizeOrderItems(mergedPayload, parsed.items),
items: mergedItems,
}
}
+2
View File
@@ -164,6 +164,7 @@ export interface AdminOrderDetail {
paidAt: string | null
createdAt: string
updatedAt: string
itemSummary: string
rawPayload: Record<string, unknown>
bindingSummary: {
totalTaskCount: number
@@ -177,6 +178,7 @@ export interface AdminOrderDetail {
orderItemId: number
skuCode: string
skuName: string
itemTitle: string
quantity: number
deliveryMode: string
spec: Record<string, unknown>
@@ -66,6 +66,11 @@ function formatSpecSummary(spec: Record<string, unknown>) {
</div>
<div class="info-grid">
<article class="info-tile">
<span>商品标题</span>
<strong>{{ detail.order.itemSummary || '-' }}</strong>
<small>来自订单详情 title / 商品标题字段</small>
</article>
<article class="info-tile">
<span>订单金额</span>
<strong>{{ detail.order.totalAmount }} {{ detail.order.currency }}</strong>
@@ -103,6 +108,7 @@ function formatSpecSummary(spec: Record<string, unknown>) {
<thead>
<tr>
<th>SKU</th>
<th>标题</th>
<th>商品</th>
<th>发放方式</th>
<th>数量</th>
@@ -112,6 +118,7 @@ function formatSpecSummary(spec: Record<string, unknown>) {
<tbody>
<tr v-for="item in detail.items" :key="item.orderItemId">
<td>{{ item.skuCode }}</td>
<td>{{ item.itemTitle || '-' }}</td>
<td>{{ item.skuName }}</td>
<td>{{ item.deliveryMode }}</td>
<td>{{ item.quantity }}</td>