完成 admin read helper 领域拆分
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
// @ts-check
|
||||
|
||||
import { getInventoryItemById } from '../../repositories/inventory-repo.js'
|
||||
import { getOrderById } from '../../repositories/order-repo.js'
|
||||
import { getTaskById } from '../../repositories/task-repo.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import { mapAdminTaskSummary } from './admin-task-read-helpers.js'
|
||||
|
||||
/** @typedef {import('../../types/admin-read-models.js').AdminInventoryItemListItem} AdminInventoryItemListItem */
|
||||
/** @typedef {import('../../types/repository-rows.js').InventoryItemRow} InventoryItemRow */
|
||||
|
||||
/**
|
||||
* @param {InventoryItemRow} item
|
||||
* @returns {Promise<AdminInventoryItemListItem>}
|
||||
*/
|
||||
export async function mapAdminInventoryListItem(item) {
|
||||
const task = item.reserved_by_task_id ? await getTaskById(item.reserved_by_task_id) : null
|
||||
const order = task?.order_id ? await getOrderById(task.order_id) : null
|
||||
const taskSummary = task ? mapAdminTaskSummary(task) : null
|
||||
|
||||
return {
|
||||
inventoryItemId: item.id,
|
||||
skuCode: item.sku_code,
|
||||
batchNo: item.batch_no,
|
||||
credentialType: item.credential_type || 'tencent_code',
|
||||
displayValue: item.display_value,
|
||||
status: item.status,
|
||||
reservedByTaskId: item.reserved_by_task_id,
|
||||
reservedByTaskNo: task?.task_no || '',
|
||||
platformOrderId: order?.platform_order_id || '',
|
||||
systemBindingStatus: item.status === 'consumed' ? 'system_bound' : (taskSummary?.systemBindingStatus || 'pending_binding'),
|
||||
userBindingStatus: item.status === 'consumed' ? 'binding_completed' : (taskSummary?.userBindingStatus || 'not_started'),
|
||||
invalidReason: item.invalid_reason || '',
|
||||
deliveredAt: item.delivered_at,
|
||||
createdAt: item.created_at,
|
||||
updatedAt: item.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
export async function getRequiredInventoryItem(inventoryItemId) {
|
||||
const inventoryItem = await getInventoryItemById(Number(inventoryItemId))
|
||||
|
||||
if (!inventoryItem) {
|
||||
throw createHttpError('库存项不存在', {
|
||||
statusCode: 404,
|
||||
errorCode: 'admin_inventory_not_found',
|
||||
})
|
||||
}
|
||||
|
||||
return inventoryItem
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// @ts-check
|
||||
|
||||
import { listOrderItemsByOrderId } from '../../repositories/order-item-repo.js'
|
||||
import { listTasksByOrderId } from '../../repositories/task-repo.js'
|
||||
import { formatFenToAmount, normalizeFen } from '../../utils/money.js'
|
||||
import { resolveDisplayShopName, resolveOrderItemTitle } from './admin-read-shared-helpers.js'
|
||||
import { buildOrderBindingSummary, getTaskBindingSummaryMap } from './admin-task-read-helpers.js'
|
||||
|
||||
/** @typedef {import('../../types/admin-read-models.js').AdminOrderListItem} AdminOrderListItem */
|
||||
/** @typedef {import('../../types/repository-rows.js').OrderListRow} OrderListRow */
|
||||
|
||||
/**
|
||||
* @param {OrderListRow} item
|
||||
* @returns {Promise<AdminOrderListItem>}
|
||||
*/
|
||||
export async function mapAdminOrderListItem(item) {
|
||||
const [tasks, orderItems] = await Promise.all([
|
||||
listTasksByOrderId(item.id),
|
||||
listOrderItemsByOrderId(item.id),
|
||||
])
|
||||
const taskBindingSummaryMap = await getTaskBindingSummaryMap(tasks.map((task) => task.id))
|
||||
const bindingSummary = buildOrderBindingSummary(tasks, taskBindingSummaryMap)
|
||||
const itemSummary = summarizeOrderItems(orderItems)
|
||||
|
||||
return {
|
||||
orderId: item.id,
|
||||
provider: item.provider || 'agiso',
|
||||
platform: item.platform,
|
||||
shopId: item.shop_id || '',
|
||||
shopName: resolveDisplayShopName(item.provider, item.shop_id, item.shop_name),
|
||||
platformOrderId: item.platform_order_id,
|
||||
orderStatus: item.order_status,
|
||||
payStatus: item.pay_status,
|
||||
buyerId: item.buyer_id || '',
|
||||
buyerName: item.buyer_name,
|
||||
receiverContact: item.receiver_contact || '',
|
||||
totalAmount: formatFenToAmount(item.total_amount),
|
||||
totalAmountFen: normalizeFen(item.total_amount),
|
||||
currency: item.currency,
|
||||
paidAt: item.paid_at,
|
||||
createdAt: item.created_at,
|
||||
updatedAt: item.updated_at,
|
||||
itemCount: orderItems.length,
|
||||
totalQuantity: orderItems.reduce((sum, orderItem) => sum + Math.max(1, Number(orderItem.quantity || 1)), 0),
|
||||
itemSummary,
|
||||
taskCount: Number(item.task_count || tasks.length || 0),
|
||||
systemBindingStatus: bindingSummary.systemBindingStatus,
|
||||
userBindingStatus: bindingSummary.userBindingStatus,
|
||||
systemBoundTaskCount: bindingSummary.systemBoundTaskCount,
|
||||
completedBindingTaskCount: bindingSummary.completedBindingTaskCount,
|
||||
totalBindingCount: bindingSummary.totalBindingCount,
|
||||
reservedBindingCount: bindingSummary.reservedBindingCount,
|
||||
consumedBindingCount: bindingSummary.consumedBindingCount,
|
||||
releasedBindingCount: bindingSummary.releasedBindingCount,
|
||||
}
|
||||
}
|
||||
|
||||
export function summarizeOrderItems(items) {
|
||||
const normalizedItems = Array.isArray(items) ? items : []
|
||||
|
||||
if (normalizedItems.length === 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const [firstItem] = normalizedItems
|
||||
const firstLabel = resolveOrderItemTitle(firstItem)
|
||||
|| String(firstItem?.sku_name || firstItem?.sku_code || '').trim()
|
||||
|
||||
if (normalizedItems.length === 1) {
|
||||
return firstLabel
|
||||
}
|
||||
|
||||
return `${firstLabel} 等 ${normalizedItems.length} 项`
|
||||
}
|
||||
@@ -1,308 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
import { extractAgisoTradePayload, resolveAgisoTradePlatformOrderId } from '../order/agiso-trade-parsing.js'
|
||||
import { getInventoryItemById } from '../../repositories/inventory-repo.js'
|
||||
import { listOrderItemsByOrderId } from '../../repositories/order-item-repo.js'
|
||||
import { getOrderById } from '../../repositories/order-repo.js'
|
||||
import { getTaskById, listTasksByOrderId } from '../../repositories/task-repo.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import { formatFenToAmount, normalizeFen, parseAmountToFen } from '../../utils/money.js'
|
||||
import { safeParseJson } from './admin-query-utils.js'
|
||||
import { resolveDisplayShopName, resolveOrderItemTitle } from './admin-read-shared-helpers.js'
|
||||
import { buildOrderBindingSummary, getTaskBindingSummaryMap, mapAdminTaskSummary } from './admin-task-read-helpers.js'
|
||||
|
||||
/** @typedef {import('../../types/admin-read-models.js').AdminInventoryItemListItem} AdminInventoryItemListItem */
|
||||
/** @typedef {import('../../types/admin-read-models.js').AdminOrderListItem} AdminOrderListItem */
|
||||
/** @typedef {import('../../types/admin-read-models.js').AdminWebhookEventListItem} AdminWebhookEventListItem */
|
||||
/** @typedef {import('../../types/repository-rows.js').InventoryItemRow} InventoryItemRow */
|
||||
/** @typedef {import('../../types/repository-rows.js').OrderListRow} OrderListRow */
|
||||
/** @typedef {import('../../types/repository-rows.js').WebhookEventRow} WebhookEventRow */
|
||||
|
||||
/**
|
||||
* @param {WebhookEventRow} item
|
||||
* @returns {Promise<AdminWebhookEventListItem>}
|
||||
*/
|
||||
export async function mapAdminWebhookEvent(item, { includeRaw = false } = {}) {
|
||||
const headers = normalizeRecord(safeParseJson(item.headers_json))
|
||||
const query = normalizeRecord(safeParseJson(item.query_json))
|
||||
const body = normalizeRecord(safeParseJson(item.body_json))
|
||||
const payload = extractWebhookPayload(body)
|
||||
const itemSources = extractWebhookItemSources(payload)
|
||||
const rawAmount = pickFirstNonEmpty([
|
||||
payload.total_fee,
|
||||
payload.totalFee,
|
||||
payload.TotalFee,
|
||||
payload.pay_fee,
|
||||
payload.payFee,
|
||||
payload.Payment,
|
||||
payload.payment,
|
||||
payload.buy_amount,
|
||||
payload.buyAmount,
|
||||
])
|
||||
const totalAmountFen = parseAmountToFen(rawAmount)
|
||||
const taskCount = item.related_order_id ? (await listTasksByOrderId(item.related_order_id)).length : 0
|
||||
|
||||
const mapped = {
|
||||
eventId: item.id,
|
||||
provider: item.provider || 'agiso',
|
||||
platform: item.platform,
|
||||
platformRaw: pickFirstNonEmpty([
|
||||
query.fromPlatform,
|
||||
query.from_platform,
|
||||
body.fromPlatform,
|
||||
body.from_platform,
|
||||
payload.fromPlatform,
|
||||
payload.FromPlatform,
|
||||
payload.platform,
|
||||
payload.Platform,
|
||||
]),
|
||||
shopId: item.shop_id || pickFirstNonEmpty([
|
||||
payload.PlatformUserId,
|
||||
payload.platformUserId,
|
||||
payload.platform_user_id,
|
||||
payload.seller_id,
|
||||
payload.sellerId,
|
||||
payload.SellerId,
|
||||
payload.seller_nick,
|
||||
payload.sellerNick,
|
||||
payload.SellerNick,
|
||||
]),
|
||||
shopName: resolveDisplayShopName(
|
||||
item.provider,
|
||||
item.shop_id || pickFirstNonEmpty([
|
||||
payload.PlatformUserId,
|
||||
payload.platformUserId,
|
||||
payload.platform_user_id,
|
||||
payload.seller_id,
|
||||
payload.sellerId,
|
||||
payload.SellerId,
|
||||
payload.seller_nick,
|
||||
payload.sellerNick,
|
||||
payload.SellerNick,
|
||||
]),
|
||||
item.shop_name || pickFirstNonEmpty([
|
||||
payload.shopName,
|
||||
payload.shop_name,
|
||||
payload.ShopName,
|
||||
payload.seller_nick,
|
||||
payload.sellerNick,
|
||||
payload.SellerNick,
|
||||
]),
|
||||
),
|
||||
eventType: item.event_type,
|
||||
eventKey: item.event_key,
|
||||
signatureValid: Boolean(item.signature_valid),
|
||||
processed: Boolean(item.processed),
|
||||
processError: item.process_error,
|
||||
relatedOrderId: item.related_order_id,
|
||||
createdAt: item.created_at,
|
||||
platformOrderId: resolveAgisoTradePlatformOrderId(payload),
|
||||
buyerId: pickFirstNonEmpty([
|
||||
payload.buyer_id,
|
||||
payload.buyerId,
|
||||
payload.BuyerId,
|
||||
payload.openid,
|
||||
payload.BuyerOpenUid,
|
||||
payload.buyer_open_uid,
|
||||
payload.buyerOpenUid,
|
||||
]),
|
||||
buyerName: pickFirstNonEmpty([
|
||||
payload.buyer_name,
|
||||
payload.buyerName,
|
||||
payload.BuyerName,
|
||||
payload.nick,
|
||||
payload.BuyerNick,
|
||||
payload.buyer_nick,
|
||||
payload.buyerNick,
|
||||
]),
|
||||
totalAmount: rawAmount ? formatFenToAmount(totalAmountFen) : '',
|
||||
totalAmountFen,
|
||||
currency: pickFirstNonEmpty([payload.currency, payload.Currency, 'CNY']) || 'CNY',
|
||||
itemCount: itemSources.length,
|
||||
taskCount,
|
||||
messageText: pickFirstNonEmpty([
|
||||
payload.MsgTypeDes,
|
||||
payload.msgTypeDes,
|
||||
payload.msg_type_desc,
|
||||
payload.message,
|
||||
payload.msg,
|
||||
]),
|
||||
requestTimestamp: pickFirstNonEmpty([query.timestamp]),
|
||||
requestSign: pickFirstNonEmpty([query.sign]),
|
||||
aopic: pickFirstNonEmpty([query.aopic]),
|
||||
sourceHost: pickFirstNonEmpty([headers['x-forwarded-host'], headers.host]),
|
||||
sourceIp: pickFirstNonEmpty([headers['x-forwarded-for'], headers['x-real-ip']]),
|
||||
userAgent: pickFirstNonEmpty([headers['user-agent'], headers['User-Agent']]),
|
||||
}
|
||||
|
||||
if (!includeRaw) {
|
||||
return mapped
|
||||
}
|
||||
|
||||
return {
|
||||
...mapped,
|
||||
headers,
|
||||
query,
|
||||
body,
|
||||
payload,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {InventoryItemRow} item
|
||||
* @returns {Promise<AdminInventoryItemListItem>}
|
||||
*/
|
||||
export async function mapAdminInventoryListItem(item) {
|
||||
const task = item.reserved_by_task_id ? await getTaskById(item.reserved_by_task_id) : null
|
||||
const order = task?.order_id ? await getOrderById(task.order_id) : null
|
||||
const taskSummary = task ? mapAdminTaskSummary(task) : null
|
||||
|
||||
return {
|
||||
inventoryItemId: item.id,
|
||||
skuCode: item.sku_code,
|
||||
batchNo: item.batch_no,
|
||||
credentialType: item.credential_type || 'tencent_code',
|
||||
displayValue: item.display_value,
|
||||
status: item.status,
|
||||
reservedByTaskId: item.reserved_by_task_id,
|
||||
reservedByTaskNo: task?.task_no || '',
|
||||
platformOrderId: order?.platform_order_id || '',
|
||||
systemBindingStatus: item.status === 'consumed' ? 'system_bound' : (taskSummary?.systemBindingStatus || 'pending_binding'),
|
||||
userBindingStatus: item.status === 'consumed' ? 'binding_completed' : (taskSummary?.userBindingStatus || 'not_started'),
|
||||
invalidReason: item.invalid_reason || '',
|
||||
deliveredAt: item.delivered_at,
|
||||
createdAt: item.created_at,
|
||||
updatedAt: item.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
export async function getRequiredInventoryItem(inventoryItemId) {
|
||||
const inventoryItem = await getInventoryItemById(Number(inventoryItemId))
|
||||
|
||||
if (!inventoryItem) {
|
||||
throw createHttpError('库存项不存在', {
|
||||
statusCode: 404,
|
||||
errorCode: 'admin_inventory_not_found',
|
||||
})
|
||||
}
|
||||
|
||||
return inventoryItem
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {OrderListRow} item
|
||||
* @returns {Promise<AdminOrderListItem>}
|
||||
*/
|
||||
export async function mapAdminOrderListItem(item) {
|
||||
const [tasks, orderItems] = await Promise.all([
|
||||
listTasksByOrderId(item.id),
|
||||
listOrderItemsByOrderId(item.id),
|
||||
])
|
||||
const taskBindingSummaryMap = await getTaskBindingSummaryMap(tasks.map((task) => task.id))
|
||||
const bindingSummary = buildOrderBindingSummary(tasks, taskBindingSummaryMap)
|
||||
const itemSummary = summarizeOrderItems(orderItems)
|
||||
|
||||
return {
|
||||
orderId: item.id,
|
||||
provider: item.provider || 'agiso',
|
||||
platform: item.platform,
|
||||
shopId: item.shop_id || '',
|
||||
shopName: resolveDisplayShopName(item.provider, item.shop_id, item.shop_name),
|
||||
platformOrderId: item.platform_order_id,
|
||||
orderStatus: item.order_status,
|
||||
payStatus: item.pay_status,
|
||||
buyerId: item.buyer_id || '',
|
||||
buyerName: item.buyer_name,
|
||||
receiverContact: item.receiver_contact || '',
|
||||
totalAmount: formatFenToAmount(item.total_amount),
|
||||
totalAmountFen: normalizeFen(item.total_amount),
|
||||
currency: item.currency,
|
||||
paidAt: item.paid_at,
|
||||
createdAt: item.created_at,
|
||||
updatedAt: item.updated_at,
|
||||
itemCount: orderItems.length,
|
||||
totalQuantity: orderItems.reduce((sum, orderItem) => sum + Math.max(1, Number(orderItem.quantity || 1)), 0),
|
||||
itemSummary,
|
||||
taskCount: Number(item.task_count || tasks.length || 0),
|
||||
systemBindingStatus: bindingSummary.systemBindingStatus,
|
||||
userBindingStatus: bindingSummary.userBindingStatus,
|
||||
systemBoundTaskCount: bindingSummary.systemBoundTaskCount,
|
||||
completedBindingTaskCount: bindingSummary.completedBindingTaskCount,
|
||||
totalBindingCount: bindingSummary.totalBindingCount,
|
||||
reservedBindingCount: bindingSummary.reservedBindingCount,
|
||||
consumedBindingCount: bindingSummary.consumedBindingCount,
|
||||
releasedBindingCount: bindingSummary.releasedBindingCount,
|
||||
}
|
||||
}
|
||||
|
||||
export function summarizeOrderItems(items) {
|
||||
const normalizedItems = Array.isArray(items) ? items : []
|
||||
|
||||
if (normalizedItems.length === 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const [firstItem] = normalizedItems
|
||||
const firstLabel = resolveOrderItemTitle(firstItem)
|
||||
|| String(firstItem?.sku_name || firstItem?.sku_code || '').trim()
|
||||
|
||||
if (normalizedItems.length === 1) {
|
||||
return firstLabel
|
||||
}
|
||||
|
||||
return `${firstLabel} 等 ${normalizedItems.length} 项`
|
||||
}
|
||||
|
||||
function extractWebhookPayload(body) {
|
||||
return extractAgisoTradePayload(body)
|
||||
}
|
||||
|
||||
function extractWebhookItemSources(payload) {
|
||||
const normalizedPayload = normalizeRecord(payload)
|
||||
const candidates = [
|
||||
normalizedPayload.orders,
|
||||
normalizedPayload.Orders,
|
||||
normalizedPayload.items,
|
||||
normalizedPayload.Items,
|
||||
normalizedPayload.order_items,
|
||||
]
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (Array.isArray(candidate) && candidate.length > 0) {
|
||||
return candidate.map((item) => normalizeRecord(item)).filter((item) => Object.keys(item).length > 0)
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
pickFirstNonEmpty([
|
||||
normalizedPayload.item_id,
|
||||
normalizedPayload.itemId,
|
||||
normalizedPayload.ItemId,
|
||||
normalizedPayload.num_iid,
|
||||
normalizedPayload.numIid,
|
||||
normalizedPayload.NumIid,
|
||||
])
|
||||
) {
|
||||
return [normalizedPayload]
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
function normalizeRecord(value) {
|
||||
return value && typeof value === 'object' && !Array.isArray(value) ? value : {}
|
||||
}
|
||||
|
||||
function pickFirstNonEmpty(values) {
|
||||
for (const value of values) {
|
||||
if (typeof value === 'string' && value.trim()) {
|
||||
return value.trim()
|
||||
}
|
||||
|
||||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||
return String(value)
|
||||
}
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
@@ -31,12 +31,9 @@ import {
|
||||
resolveOrderItemDeliveryMode,
|
||||
resolveOrderItemTitle,
|
||||
} from './admin-read-shared-helpers.js'
|
||||
import {
|
||||
mapAdminInventoryListItem,
|
||||
mapAdminOrderListItem,
|
||||
mapAdminWebhookEvent,
|
||||
summarizeOrderItems,
|
||||
} from './admin-read-helpers.js'
|
||||
import { mapAdminInventoryListItem } from './admin-inventory-read-helpers.js'
|
||||
import { mapAdminOrderListItem, summarizeOrderItems } from './admin-order-read-helpers.js'
|
||||
import { mapAdminWebhookEvent } from './admin-webhook-read-helpers.js'
|
||||
import {
|
||||
buildOrderAgisoAutoDeliverySummary,
|
||||
buildOrderBindingSummary,
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
export {
|
||||
getRequiredInventoryItem,
|
||||
mapAdminInventoryListItem,
|
||||
} from './admin-inventory-read-helpers.js'
|
||||
export {
|
||||
mapAdminOrderListItem,
|
||||
mapAdminWebhookEvent,
|
||||
} from './admin-read-helpers.js'
|
||||
summarizeOrderItems,
|
||||
} from './admin-order-read-helpers.js'
|
||||
export { mapAdminWebhookEvent } from './admin-webhook-read-helpers.js'
|
||||
|
||||
export {
|
||||
buildOrderAgisoAutoDeliverySummary,
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
// @ts-check
|
||||
|
||||
import { listTasksByOrderId } from '../../repositories/task-repo.js'
|
||||
import { formatFenToAmount, parseAmountToFen } from '../../utils/money.js'
|
||||
import { extractAgisoTradePayload, resolveAgisoTradePlatformOrderId } from '../order/agiso-trade-parsing.js'
|
||||
import { safeParseJson } from './admin-query-utils.js'
|
||||
import { resolveDisplayShopName } from './admin-read-shared-helpers.js'
|
||||
|
||||
/** @typedef {import('../../types/admin-read-models.js').AdminWebhookEventListItem} AdminWebhookEventListItem */
|
||||
/** @typedef {import('../../types/repository-rows.js').WebhookEventRow} WebhookEventRow */
|
||||
|
||||
/**
|
||||
* @param {WebhookEventRow} item
|
||||
* @returns {Promise<AdminWebhookEventListItem>}
|
||||
*/
|
||||
export async function mapAdminWebhookEvent(item, { includeRaw = false } = {}) {
|
||||
const headers = normalizeRecord(safeParseJson(item.headers_json))
|
||||
const query = normalizeRecord(safeParseJson(item.query_json))
|
||||
const body = normalizeRecord(safeParseJson(item.body_json))
|
||||
const payload = extractWebhookPayload(body)
|
||||
const itemSources = extractWebhookItemSources(payload)
|
||||
const rawAmount = pickFirstNonEmpty([
|
||||
payload.total_fee,
|
||||
payload.totalFee,
|
||||
payload.TotalFee,
|
||||
payload.pay_fee,
|
||||
payload.payFee,
|
||||
payload.Payment,
|
||||
payload.payment,
|
||||
payload.buy_amount,
|
||||
payload.buyAmount,
|
||||
])
|
||||
const totalAmountFen = parseAmountToFen(rawAmount)
|
||||
const taskCount = item.related_order_id ? (await listTasksByOrderId(item.related_order_id)).length : 0
|
||||
const shopId = item.shop_id || pickFirstNonEmpty([
|
||||
payload.PlatformUserId,
|
||||
payload.platformUserId,
|
||||
payload.platform_user_id,
|
||||
payload.seller_id,
|
||||
payload.sellerId,
|
||||
payload.SellerId,
|
||||
payload.seller_nick,
|
||||
payload.sellerNick,
|
||||
payload.SellerNick,
|
||||
])
|
||||
|
||||
const mapped = {
|
||||
eventId: item.id,
|
||||
provider: item.provider || 'agiso',
|
||||
platform: item.platform,
|
||||
platformRaw: pickFirstNonEmpty([
|
||||
query.fromPlatform,
|
||||
query.from_platform,
|
||||
body.fromPlatform,
|
||||
body.from_platform,
|
||||
payload.fromPlatform,
|
||||
payload.FromPlatform,
|
||||
payload.platform,
|
||||
payload.Platform,
|
||||
]),
|
||||
shopId,
|
||||
shopName: resolveDisplayShopName(
|
||||
item.provider,
|
||||
shopId,
|
||||
item.shop_name || pickFirstNonEmpty([
|
||||
payload.shopName,
|
||||
payload.shop_name,
|
||||
payload.ShopName,
|
||||
payload.seller_nick,
|
||||
payload.sellerNick,
|
||||
payload.SellerNick,
|
||||
]),
|
||||
),
|
||||
eventType: item.event_type,
|
||||
eventKey: item.event_key,
|
||||
signatureValid: Boolean(item.signature_valid),
|
||||
processed: Boolean(item.processed),
|
||||
processError: item.process_error,
|
||||
relatedOrderId: item.related_order_id,
|
||||
createdAt: item.created_at,
|
||||
platformOrderId: resolveAgisoTradePlatformOrderId(payload),
|
||||
buyerId: pickFirstNonEmpty([
|
||||
payload.buyer_id,
|
||||
payload.buyerId,
|
||||
payload.BuyerId,
|
||||
payload.openid,
|
||||
payload.BuyerOpenUid,
|
||||
payload.buyer_open_uid,
|
||||
payload.buyerOpenUid,
|
||||
]),
|
||||
buyerName: pickFirstNonEmpty([
|
||||
payload.buyer_name,
|
||||
payload.buyerName,
|
||||
payload.BuyerName,
|
||||
payload.nick,
|
||||
payload.BuyerNick,
|
||||
payload.buyer_nick,
|
||||
payload.buyerNick,
|
||||
]),
|
||||
totalAmount: rawAmount ? formatFenToAmount(totalAmountFen) : '',
|
||||
totalAmountFen,
|
||||
currency: pickFirstNonEmpty([payload.currency, payload.Currency, 'CNY']) || 'CNY',
|
||||
itemCount: itemSources.length,
|
||||
taskCount,
|
||||
messageText: pickFirstNonEmpty([
|
||||
payload.MsgTypeDes,
|
||||
payload.msgTypeDes,
|
||||
payload.msg_type_desc,
|
||||
payload.message,
|
||||
payload.msg,
|
||||
]),
|
||||
requestTimestamp: pickFirstNonEmpty([query.timestamp]),
|
||||
requestSign: pickFirstNonEmpty([query.sign]),
|
||||
aopic: pickFirstNonEmpty([query.aopic]),
|
||||
sourceHost: pickFirstNonEmpty([headers['x-forwarded-host'], headers.host]),
|
||||
sourceIp: pickFirstNonEmpty([headers['x-forwarded-for'], headers['x-real-ip']]),
|
||||
userAgent: pickFirstNonEmpty([headers['user-agent'], headers['User-Agent']]),
|
||||
}
|
||||
|
||||
if (!includeRaw) {
|
||||
return mapped
|
||||
}
|
||||
|
||||
return {
|
||||
...mapped,
|
||||
headers,
|
||||
query,
|
||||
body,
|
||||
payload,
|
||||
}
|
||||
}
|
||||
|
||||
function extractWebhookPayload(body) {
|
||||
return extractAgisoTradePayload(body)
|
||||
}
|
||||
|
||||
function extractWebhookItemSources(payload) {
|
||||
const normalizedPayload = normalizeRecord(payload)
|
||||
const candidates = [
|
||||
normalizedPayload.orders,
|
||||
normalizedPayload.Orders,
|
||||
normalizedPayload.items,
|
||||
normalizedPayload.Items,
|
||||
normalizedPayload.order_items,
|
||||
]
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (Array.isArray(candidate) && candidate.length > 0) {
|
||||
return candidate.map((item) => normalizeRecord(item)).filter((item) => Object.keys(item).length > 0)
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
pickFirstNonEmpty([
|
||||
normalizedPayload.item_id,
|
||||
normalizedPayload.itemId,
|
||||
normalizedPayload.ItemId,
|
||||
normalizedPayload.num_iid,
|
||||
normalizedPayload.numIid,
|
||||
normalizedPayload.NumIid,
|
||||
])
|
||||
) {
|
||||
return [normalizedPayload]
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
function normalizeRecord(value) {
|
||||
return value && typeof value === 'object' && !Array.isArray(value) ? value : {}
|
||||
}
|
||||
|
||||
function pickFirstNonEmpty(values) {
|
||||
for (const value of values) {
|
||||
if (typeof value === 'string' && value.trim()) {
|
||||
return value.trim()
|
||||
}
|
||||
|
||||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||
return String(value)
|
||||
}
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
import {
|
||||
getRequiredInventoryItem,
|
||||
mapAdminInventoryListItem,
|
||||
} from './admin-read-helpers.js'
|
||||
} from './admin-inventory-read-helpers.js'
|
||||
import {
|
||||
getRequiredTask,
|
||||
mapTaskActionPayload,
|
||||
|
||||
@@ -184,6 +184,10 @@
|
||||
58. `admin-read-service / admin-write-service / admin-service` 已将 task 相关 helper 改为直接依赖独立 task helper 模块
|
||||
59. `admin-read-helpers.js` 已进一步收敛到 webhook / inventory / order-list 三类能力
|
||||
60. `npm --prefix apps/backend run typecheck` 已在 task helper 拆分后继续通过
|
||||
61. `admin-read-helpers.js` 已完成最终拆分,并删除过渡文件
|
||||
62. 新增 `src/services/admin/admin-inventory-read-helpers.js`,库存读取映射与必需实体校验已独立
|
||||
63. 新增 `src/services/admin/admin-order-read-helpers.js` 与 `src/services/admin/admin-webhook-read-helpers.js`,订单列表汇总与 webhook 事件映射已按领域独立
|
||||
64. `admin-read-service / admin-write-service / admin-service` 已全部改为直接依赖 inventory / order / webhook / task / shared 五类 helper,读侧 helper 拆分阶段可以收口
|
||||
|
||||
这一步的设计原则是“只加静态约束,不动运行链路”,所以不会影响:
|
||||
|
||||
@@ -195,11 +199,10 @@
|
||||
|
||||
第一批继续推进时,建议按这个顺序:
|
||||
|
||||
1. 为 `admin` 路由层补请求体 / 查询参数的共享输入类型
|
||||
2. 继续把 `admin-read-helpers.js` 细拆成按领域 helper 模块,下一步优先拆 `webhook` 或 `order-list` helper
|
||||
3. 为 `admin` 路由层继续补响应模型与审计 payload 的共享类型,减少 `unknown` 到处就地断言
|
||||
4. 继续补 repository / service 的边界约束,但不急着改 `.ts` 扩展名
|
||||
5. 为 webhook、库存换码、自动发货补测试
|
||||
1. 为 `admin` 路由层继续补响应模型与审计 payload 的共享类型,减少 `unknown` 到处就地断言
|
||||
2. 继续补 repository / service 的边界约束,但不急着改 `.ts` 扩展名
|
||||
3. 恢复并固定 `npm --prefix apps/backend run typecheck` 为日常校验步骤,确保本地与 CI 环境都能直接执行
|
||||
4. 为 webhook、库存换码、自动发货补测试
|
||||
|
||||
## 执行原则
|
||||
|
||||
|
||||
Reference in New Issue
Block a user