后端迁移订单同步服务

This commit is contained in:
yml
2026-05-21 14:25:04 +08:00
parent b6b6d2d8ca
commit ca6e8b43c9
2 changed files with 122 additions and 12 deletions
@@ -12,12 +12,95 @@ import { ensureAgisoXianyuClaimMessageDeliveredForTask } from '../platforms/agis
import { resolveOrderItemForFulfillment } from './product-match-service.js' import { resolveOrderItemForFulfillment } from './product-match-service.js'
import { nowIso } from '../../utils/time.js' import { nowIso } from '../../utils/time.js'
import { logWebhook } from '../../utils/logger.js' import { logWebhook } from '../../utils/logger.js'
import type { OrderItemRow, OrderRow, TaskRow } from '../../types/repository-rows.js'
export async function upsertOrderFromWebhook(event) { type SourceOrderItem = {
quantity?: number
spec?: Record<string, unknown>
snapshot?: Record<string, unknown>
[key: string]: unknown
}
type FulfillmentOrderItem = SourceOrderItem & {
isConfigured: boolean
skuCode: string
skuName: string
quantity: number
spec?: Record<string, unknown>
snapshot?: Record<string, unknown>
}
type SourceOrderEvent = {
provider: string
platform: string
shopId: string
shopIdAliases?: string[]
shopName: string
platformOrderId: string
orderStatus: string
payStatus: string
buyerId: string
buyerName: string
receiverContact: string
totalAmount: number | string
currency: string
rawPayload: unknown
paidAt?: string | null
items: SourceOrderItem[]
}
type UpsertOrderSourceOptions = {
sourceLabel?: string
}
type MessageDeliveryResult = {
taskId: number
[key: string]: unknown
}
type UpsertOrderIgnoredResult = {
ignored: true
ignoreReason: string
order: null
orderItems: []
tasks: []
messageDeliveries: []
}
type UpsertOrderResult = {
ignored?: false
ignoreReason?: string
order: OrderRow
orderItems: OrderItemRow[]
tasks: TaskRow[]
messageDeliveries: MessageDeliveryResult[]
} | UpsertOrderIgnoredResult
type OrderStateInput = {
order_status?: string
orderStatus?: string
pay_status?: string
payStatus?: string
paid_at?: string | null
paidAt?: string | null
}
type OrderStateMergeResult = {
orderStatus: string
payStatus: string
paidAt: string | null
}
type StatusPriorityMap = Record<string, number>
export async function upsertOrderFromWebhook(event: SourceOrderEvent): Promise<UpsertOrderResult> {
return upsertOrderFromSource(event, { sourceLabel: 'webhook' }) return upsertOrderFromSource(event, { sourceLabel: 'webhook' })
} }
export async function upsertOrderFromSource(event, { sourceLabel = 'source' } = {}) { export async function upsertOrderFromSource(
event: SourceOrderEvent,
{ sourceLabel = 'source' }: UpsertOrderSourceOptions = {},
): Promise<UpsertOrderResult> {
const now = nowIso() const now = nowIso()
const exactExisting = await findOrderByPlatformOrderId({ const exactExisting = await findOrderByPlatformOrderId({
provider: event.provider, provider: event.provider,
@@ -50,7 +133,7 @@ export async function upsertOrderFromSource(event, { sourceLabel = 'source' } =
item, item,
})), })),
) )
const configuredItems = resolvedItems.filter((item) => item.isConfigured) const configuredItems = (resolvedItems as FulfillmentOrderItem[]).filter((item) => item.isConfigured)
if (configuredItems.length === 0) { if (configuredItems.length === 0) {
logWebhook('[order-service]', `${sourceLabel} 订单已忽略:未命中任何已配置履约商品`, { logWebhook('[order-service]', `${sourceLabel} 订单已忽略:未命中任何已配置履约商品`, {
@@ -104,6 +187,10 @@ export async function upsertOrderFromSource(event, { sourceLabel = 'source' } =
updatedAt: now, updatedAt: now,
}) })
if (!order) {
throw new Error('订单写入失败')
}
const orderItems = await replaceOrderItems( const orderItems = await replaceOrderItems(
order.id, order.id,
configuredItems.map((item) => ({ configuredItems.map((item) => ({
@@ -118,7 +205,7 @@ export async function upsertOrderFromSource(event, { sourceLabel = 'source' } =
) )
const tasks = await syncDeliveryTasksForOrder(order, orderItems) const tasks = await syncDeliveryTasksForOrder(order, orderItems)
const messageDeliveries = [] const messageDeliveries: MessageDeliveryResult[] = []
logWebhook('[order-service]', `${sourceLabel} 订单 upsert 完成`, { logWebhook('[order-service]', `${sourceLabel} 订单 upsert 完成`, {
orderId: order.id, orderId: order.id,
@@ -173,7 +260,7 @@ export async function upsertOrderFromSource(event, { sourceLabel = 'source' } =
} }
} }
function resolveEventShopIdCandidates(event) { function resolveEventShopIdCandidates(event: Pick<SourceOrderEvent, 'shopId' | 'shopIdAliases'>): string[] {
return [...new Set([ return [...new Set([
String(event?.shopId || '').trim(), String(event?.shopId || '').trim(),
...(Array.isArray(event?.shopIdAliases) ? event.shopIdAliases : []).map((item) => String(item || '').trim()), ...(Array.isArray(event?.shopIdAliases) ? event.shopIdAliases : []).map((item) => String(item || '').trim()),
@@ -194,7 +281,10 @@ const PAY_STATUS_PRIORITY = {
refunded: 2, refunded: 2,
} }
export function mergeWebhookOrderState(existing, incoming) { export function mergeWebhookOrderState(
existing: OrderStateInput | null | undefined,
incoming: OrderStateInput | null | undefined,
): OrderStateMergeResult {
const orderStatus = chooseHigherPriorityStatus( const orderStatus = chooseHigherPriorityStatus(
normalizeStateValue(existing?.order_status ?? existing?.orderStatus), normalizeStateValue(existing?.order_status ?? existing?.orderStatus),
normalizeStateValue(incoming?.orderStatus ?? incoming?.order_status), normalizeStateValue(incoming?.orderStatus ?? incoming?.order_status),
@@ -218,7 +308,11 @@ export function mergeWebhookOrderState(existing, incoming) {
} }
} }
function chooseHigherPriorityStatus(existingValue, incomingValue, priorityMap) { function chooseHigherPriorityStatus(
existingValue: string,
incomingValue: string,
priorityMap: StatusPriorityMap,
): string {
const existingPriority = resolveStatusPriority(existingValue, priorityMap) const existingPriority = resolveStatusPriority(existingValue, priorityMap)
const incomingPriority = resolveStatusPriority(incomingValue, priorityMap) const incomingPriority = resolveStatusPriority(incomingValue, priorityMap)
@@ -233,15 +327,23 @@ function chooseHigherPriorityStatus(existingValue, incomingValue, priorityMap) {
return incomingValue || existingValue return incomingValue || existingValue
} }
function resolveStatusPriority(value, priorityMap) { function resolveStatusPriority(value: string, priorityMap: StatusPriorityMap): number {
return Number(priorityMap[normalizeStateValue(value)] ?? -1) return Number(priorityMap[normalizeStateValue(value)] ?? -1)
} }
function normalizeStateValue(value) { function normalizeStateValue(value: unknown): string {
return String(value || '').trim().toLowerCase() return String(value || '').trim().toLowerCase()
} }
function resolveMergedPaidAt({ existingPaidAt, incomingPaidAt, payStatus }) { function resolveMergedPaidAt({
existingPaidAt,
incomingPaidAt,
payStatus,
}: {
existingPaidAt: string | null
incomingPaidAt: string | null
payStatus: string
}): string | null {
if (!['paid', 'refunded'].includes(normalizeStateValue(payStatus))) { if (!['paid', 'refunded'].includes(normalizeStateValue(payStatus))) {
return null return null
} }
@@ -256,7 +358,7 @@ function resolveMergedPaidAt({ existingPaidAt, incomingPaidAt, payStatus }) {
return existingPaidAt || incomingPaidAt || null return existingPaidAt || incomingPaidAt || null
} }
function parseDateValue(value) { function parseDateValue(value: unknown): number | null {
const text = String(value || '').trim() const text = String(value || '').trim()
if (!text) { if (!text) {
+9 -1
View File
@@ -272,13 +272,21 @@
- `npm run typecheck` - `npm run typecheck`
- `npm run build` - `npm run build`
- `npm test` 共 125 个用例通过 - `npm test` 共 125 个用例通过
31. 订单 upsert 服务迁移到 `.ts`
- `src/services/order/order-service.ts`
32. 外部订单事件、订单履约商品、订单状态合并结果、upsert 返回结构已显式类型化
33. Docker 内验证通过:
- 订单 / webhook / open91 相关 3 个测试文件共 15 个用例通过
- `npm run typecheck`
- `npm run build`
- `npm test` 共 125 个用例通过
## 下一步建议 ## 下一步建议
第一批继续推进时,建议按这个顺序: 第一批继续推进时,建议按这个顺序:
1. 继续迁移服务层中最核心、最常改的订单 / 履约 / claim 模块 1. 继续迁移服务层中最核心、最常改的订单 / 履约 / claim 模块
2. 逐步移除服务层 `@ts-nocheck`,优先处理 webhook service、自动发货 2. 逐步移除服务层 `@ts-nocheck`,优先处理 webhook service、自动发货、delivery task
## 执行原则 ## 执行原则