修复订单详情错误
This commit is contained in:
@@ -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,15 +522,10 @@ 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) {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user