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