新增 affiliate_dash 履约配置与商品映射(阶段 3)
- bootstrap CORE_PROFILES + routing ROUTABLE/DEFAULT_PRIORITY 接入 affiliate_dash - planner 上下文 affiliateDash 块 + resolveDynamicAffiliateDashProfile - product-resolution: 91 productNo→sku 映射匹配(skuMapping) + snapshot.affiliateDash - prepare 前余额预检(getAffiliateDashWallet) - AFFILIATE_DASH_SKU_MAPPING_JSON env 注入 - admin 后端接口(GET/POST 配置、match、products、wallet)+ 前端 Tabs 面板 - 联调:模拟 91 进单命中 affiliate_dash 路由, 未命中回退;前后端 typecheck + 212 测试通过
This commit is contained in:
@@ -17,6 +17,7 @@ type RuntimeConfigValue =
|
||||
| AdminDefaultUser[]
|
||||
| KuaishouFeifeiProductRule[]
|
||||
| string[]
|
||||
| Record<string, string>
|
||||
type RuntimeEnv = Record<string, string | undefined>
|
||||
|
||||
type EnvOverride = {
|
||||
@@ -182,6 +183,11 @@ export const ENV_OVERRIDES: readonly EnvOverride[] = [
|
||||
'affiliateDash',
|
||||
'timestampToleranceSeconds',
|
||||
]),
|
||||
affiliateDashSkuMappingEnv('AFFILIATE_DASH_SKU_MAPPING_JSON', [
|
||||
'platforms',
|
||||
'affiliateDash',
|
||||
'skuMapping',
|
||||
]),
|
||||
corsOriginsEnv('CORS_ALLOWED_ORIGINS', ['cors', 'allowedOrigins']),
|
||||
]
|
||||
|
||||
@@ -268,6 +274,28 @@ function kuaishouFeifeiProductRulesEnv(env: string, configPath: RuntimeConfigPat
|
||||
}
|
||||
}
|
||||
|
||||
function affiliateDashSkuMappingEnv(env: string, configPath: RuntimeConfigPath): EnvOverride {
|
||||
return {
|
||||
env,
|
||||
path: configPath,
|
||||
read(rawValue) {
|
||||
const text = String(rawValue ?? '').trim()
|
||||
if (!text) {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(text)
|
||||
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
||||
return parsed as Record<string, string>
|
||||
}
|
||||
} catch {
|
||||
// 忽略非法 JSON
|
||||
}
|
||||
return null
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function corsOriginsEnv(env: string, configPath: RuntimeConfigPath): EnvOverride {
|
||||
return {
|
||||
env,
|
||||
|
||||
@@ -7,6 +7,7 @@ import kuaishouFeifeiRouter from "./platform-config/kuaishou-feifei.js";
|
||||
import ninetyoneRouter from "./platform-config/ninetyone.js";
|
||||
import notificationsRouter from "./platform-config/notifications.js";
|
||||
import fulfillmentRoutingRouter from "./platform-config/fulfillment-routing.js";
|
||||
import affiliateDashRouter from "./platform-config/affiliate-dash.js";
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -17,5 +18,6 @@ router.use("/platform-config", kuaishouFeifeiRouter);
|
||||
router.use("/platform-config", ninetyoneRouter);
|
||||
router.use("/platform-config", fulfillmentRoutingRouter);
|
||||
router.use("/platform-config", cloudtentaclesRouter);
|
||||
router.use("/platform-config", affiliateDashRouter);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { Router } from "express";
|
||||
|
||||
import {
|
||||
getAdminAffiliateDashConfig,
|
||||
getAdminAffiliateDashWallet,
|
||||
listAdminAffiliateDashProducts,
|
||||
matchAdminAffiliateDashSku,
|
||||
updateAdminAffiliateDashConfig,
|
||||
} from "../../../services/admin/platform-config/affiliate-dash-service.js";
|
||||
import { createJsonHandler } from "../session.js";
|
||||
import type { JsonRecord } from "../../../types/json.js";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get(
|
||||
"/affiliate-dash",
|
||||
createJsonHandler(() => getAdminAffiliateDashConfig(), {
|
||||
successMessage: "ok",
|
||||
errorMessage: "读取 affiliate-dash 配置失败",
|
||||
scope: "[admin/platform-config/affiliate-dash]",
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/affiliate-dash",
|
||||
createJsonHandler(
|
||||
(req) => updateAdminAffiliateDashConfig(req.body as JsonRecord),
|
||||
{
|
||||
successMessage: "affiliate-dash 配置已保存",
|
||||
errorMessage: "保存 affiliate-dash 配置失败",
|
||||
scope: "[admin/platform-config/affiliate-dash]",
|
||||
audit: (_req, data) => {
|
||||
const result = data as JsonRecord;
|
||||
const source = result.source as JsonRecord | undefined;
|
||||
const effective = result.effective as JsonRecord | undefined;
|
||||
|
||||
return {
|
||||
action: "platform_affiliate_dash_config_updated",
|
||||
targetType: "platform_config",
|
||||
targetId: "affiliate_dash",
|
||||
data: {
|
||||
enabled: effective?.enabled !== false,
|
||||
hasAppKey: Boolean(effective?.hasAppKey),
|
||||
hasAppSecret: Boolean(effective?.hasAppSecret),
|
||||
hasCallbackSecret: Boolean(effective?.hasCallbackSecret),
|
||||
skuMappingCount: Number(source?.skuMapping && typeof source.skuMapping === "object"
|
||||
? Object.keys(source.skuMapping).length
|
||||
: 0),
|
||||
},
|
||||
};
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/affiliate-dash/match",
|
||||
createJsonHandler(
|
||||
(req) => matchAdminAffiliateDashSku(req.body as JsonRecord),
|
||||
{
|
||||
successMessage: "ok",
|
||||
errorMessage: "匹配 affiliate-dash sku 失败",
|
||||
scope: "[admin/platform-config/affiliate-dash/match]",
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/affiliate-dash/products",
|
||||
createJsonHandler(
|
||||
(req) => listAdminAffiliateDashProducts(req.body as JsonRecord),
|
||||
{
|
||||
successMessage: "ok",
|
||||
errorMessage: "查询 affiliate-dash 商品失败",
|
||||
scope: "[admin/platform-config/affiliate-dash/products]",
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/affiliate-dash/wallet",
|
||||
createJsonHandler(() => getAdminAffiliateDashWallet(), {
|
||||
successMessage: "ok",
|
||||
errorMessage: "查询 affiliate-dash 钱包失败",
|
||||
scope: "[admin/platform-config/affiliate-dash/wallet]",
|
||||
})
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,80 @@
|
||||
import { getAffiliateDashConfig } from '../../platforms/affiliate-dash/config.js'
|
||||
import type { JsonObject } from '../../../types/json.js'
|
||||
import {
|
||||
getAffiliateDashWallet,
|
||||
} from '../../platforms/affiliate-dash/order-service.js'
|
||||
import { listAffiliateDashProducts } from '../../platforms/affiliate-dash/product-service.js'
|
||||
import {
|
||||
getAffiliateDashSourceConfig,
|
||||
hasAffiliateDashConfig,
|
||||
normalizeAffiliateDashSourceConfig,
|
||||
saveAffiliateDashSourceConfig,
|
||||
} from '../../platforms/affiliate-dash/source-config-service.js'
|
||||
|
||||
export function getAdminAffiliateDashConfig() {
|
||||
const source = getAdminEditableAffiliateDashConfig()
|
||||
const effective = getAffiliateDashConfig()
|
||||
|
||||
return {
|
||||
source,
|
||||
effective: mapEffectiveAffiliateDashConfig(effective),
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateAdminAffiliateDashConfig(payload: JsonObject = {}) {
|
||||
const saved = await saveAffiliateDashSourceConfig(payload)
|
||||
const effective = getAffiliateDashConfig()
|
||||
|
||||
return {
|
||||
source: saved,
|
||||
effective: mapEffectiveAffiliateDashConfig(effective),
|
||||
}
|
||||
}
|
||||
|
||||
/** 按 91 商品编码(productNo)测试 sku 映射命中。 */
|
||||
export function matchAdminAffiliateDashSku(payload: JsonObject = {}) {
|
||||
const productNo = String(payload.productNo || payload.product_no || '').trim()
|
||||
const source = getAdminEditableAffiliateDashConfig()
|
||||
const sku = productNo
|
||||
? String(source.skuMapping[productNo] || '').trim()
|
||||
: ''
|
||||
|
||||
return {
|
||||
productNo,
|
||||
sku,
|
||||
matched: Boolean(productNo && sku),
|
||||
}
|
||||
}
|
||||
|
||||
export async function listAdminAffiliateDashProducts(payload: JsonObject = {}) {
|
||||
return listAffiliateDashProducts({
|
||||
page: Number(payload.page || 1) || 1,
|
||||
size: Math.min(200, Math.max(1, Number(payload.size || 50) || 50)),
|
||||
})
|
||||
}
|
||||
|
||||
export async function getAdminAffiliateDashWallet() {
|
||||
return getAffiliateDashWallet()
|
||||
}
|
||||
|
||||
function getAdminEditableAffiliateDashConfig() {
|
||||
if (hasAffiliateDashConfig()) {
|
||||
return getAffiliateDashSourceConfig()
|
||||
}
|
||||
|
||||
return normalizeAffiliateDashSourceConfig(getAffiliateDashConfig())
|
||||
}
|
||||
|
||||
function mapEffectiveAffiliateDashConfig(config: ReturnType<typeof getAffiliateDashConfig>) {
|
||||
return {
|
||||
enabled: config.enabled !== false,
|
||||
baseUrl: config.baseUrl,
|
||||
timeoutMs: config.timeoutMs,
|
||||
notifyUrl: config.notifyUrl,
|
||||
timestampToleranceSeconds: config.timestampToleranceSeconds,
|
||||
hasAppKey: Boolean(config.appKey),
|
||||
hasAppSecret: Boolean(config.appSecret),
|
||||
hasCallbackSecret: Boolean(config.callbackSecret),
|
||||
skuMappingCount: Object.keys(config.skuMapping).length,
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,15 @@ const CORE_PROFILES: CoreProfile[] = [
|
||||
inventoryStrategy: 'external_platform',
|
||||
requirements: [],
|
||||
},
|
||||
{
|
||||
profileKey: 'affiliate_dash',
|
||||
name: 'affiliate-dash 履约',
|
||||
executorKey: 'affiliate_dash',
|
||||
requiresClaim: true,
|
||||
autoDispatch: false,
|
||||
inventoryStrategy: 'external_platform',
|
||||
requirements: [],
|
||||
},
|
||||
]
|
||||
|
||||
export async function ensureFulfillmentCatalogBootstrapped() {
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { JsonObject } from '../../../types/json.js'
|
||||
import {
|
||||
createAffiliateDashOrder,
|
||||
getAffiliateDashOrder,
|
||||
getAffiliateDashWallet,
|
||||
type AffiliateDashOrder,
|
||||
} from '../../platforms/affiliate-dash/order-service.js'
|
||||
import { getAffiliateDashConfig } from '../../platforms/affiliate-dash/config.js'
|
||||
@@ -60,6 +61,9 @@ export async function prepareAffiliateDashTask(task: TaskRow) {
|
||||
const data = buildAffiliateDashOrderData(task, taskContext)
|
||||
const buyerReference = String(task.platform_order_id || task.task_no || '').trim()
|
||||
|
||||
// 余额预检:钱包余额不足提前拦截(避免建单 400 后才降级);预检失败不阻塞下单
|
||||
await preflightAffiliateDashWallet(flow.sku)
|
||||
|
||||
const order = await createAffiliateDashOrder({
|
||||
clientOrderNo,
|
||||
sku: flow.sku,
|
||||
@@ -278,6 +282,31 @@ export function buildAffiliateDashClientOrderNo(task: TaskRow) {
|
||||
return String(task.task_no || `OS-AD-${task.id}`).trim()
|
||||
}
|
||||
|
||||
async function preflightAffiliateDashWallet(sku: string) {
|
||||
try {
|
||||
const wallet = await getAffiliateDashWallet()
|
||||
if (wallet.availableBalance <= 0) {
|
||||
throw createHttpError('affiliate-dash 钱包余额不足,请先充值', {
|
||||
statusCode: 409,
|
||||
errorCode: 'affiliate_dash_wallet_not_enough',
|
||||
context: {
|
||||
sku,
|
||||
availableBalance: wallet.availableBalance,
|
||||
currency: wallet.currency,
|
||||
},
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
if ((error as { errorCode?: string })?.errorCode === 'affiliate_dash_wallet_not_enough') {
|
||||
throw error
|
||||
}
|
||||
logIntegration('[affiliate-dash]', '余额预检失败(忽略,继续下单)', {
|
||||
sku,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
}, { level: 'warn' })
|
||||
}
|
||||
}
|
||||
|
||||
function buildAffiliateDashOrderData(task: TaskRow, context: Record<string, unknown>): JsonObject {
|
||||
const identity = getClaimIdentityFromContext(context)
|
||||
const data: JsonObject = {}
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { JsonObject } from '../../types/json.js'
|
||||
import type { OrderItemRow, OrderRow } from '../../types/repository/rows.js'
|
||||
import {
|
||||
FULFILLMENT_EXECUTOR_KEYS,
|
||||
isAffiliateDashExecutor,
|
||||
isKuaishouCloudExecutor,
|
||||
isKuaishouFeifeiExecutor,
|
||||
} from './executors/types.js'
|
||||
@@ -98,6 +99,7 @@ function buildFulfillmentTaskContext({
|
||||
const itemSnapshot = parseJsonObject(item.item_snapshot_json)
|
||||
const cloudtentaclesConfig = parseJsonObject(fulfillmentConfig.cloudtentacles)
|
||||
const kuaishouFeifeiConfig = parseJsonObject(fulfillmentConfig.kuaishouFeifei)
|
||||
const affiliateDashConfig = parseJsonObject(fulfillmentConfig.affiliateDash)
|
||||
const kuaishouConsumeConfig = parseJsonObject(fulfillmentConfig.kuaishouConsume)
|
||||
const kuaishouShopConfig = parseJsonObject(fulfillmentConfig.kuaishouShop)
|
||||
const cloudSourceKeys = normalizeStringArray(cloudtentaclesConfig.cloudSourceKeys)
|
||||
@@ -223,6 +225,33 @@ function buildFulfillmentTaskContext({
|
||||
lastSyncedAt: null,
|
||||
}
|
||||
: null,
|
||||
affiliateDash: isAffiliateDashExecutor(executorKey)
|
||||
? {
|
||||
flowType: 'affiliate_dash',
|
||||
sku: String(affiliateDashConfig.sku || '').trim(),
|
||||
productName: String(
|
||||
affiliateDashConfig.productName || item.sku_name || '',
|
||||
).trim(),
|
||||
orderNo: '',
|
||||
clientOrderNo: '',
|
||||
orderStatus: '',
|
||||
canShip: false,
|
||||
cannotShipReason: '',
|
||||
providerOrderNo: '',
|
||||
failureReason: '',
|
||||
amount: 0,
|
||||
currency: '',
|
||||
bindUuid: '',
|
||||
bindUrl: '',
|
||||
qrUrl: '',
|
||||
gameAccount: '',
|
||||
expectedGameAccount: '',
|
||||
bindMismatch: false,
|
||||
submitStatus: '',
|
||||
consumeStatus: 'pending',
|
||||
lastSyncedAt: null,
|
||||
}
|
||||
: null,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +269,10 @@ async function resolveDynamicFulfillmentProfile(
|
||||
return resolveDynamicKuaishouFeifeiProfile(item, getProfileByKey)
|
||||
}
|
||||
|
||||
if (selectedExecutorKey === FULFILLMENT_EXECUTOR_KEYS.AFFILIATE_DASH) {
|
||||
return resolveDynamicAffiliateDashProfile(item, getProfileByKey)
|
||||
}
|
||||
|
||||
if (selectedExecutorKey === FULFILLMENT_EXECUTOR_KEYS.MANUAL_DISPATCH) {
|
||||
return resolveDynamicManualDispatchProfile(item, getProfileByKey)
|
||||
}
|
||||
@@ -247,7 +280,8 @@ async function resolveDynamicFulfillmentProfile(
|
||||
|
||||
return (
|
||||
await resolveDynamicCloudtentaclesProfile(item, getProfileByKey) ||
|
||||
await resolveDynamicKuaishouFeifeiProfile(item, getProfileByKey)
|
||||
await resolveDynamicKuaishouFeifeiProfile(item, getProfileByKey) ||
|
||||
await resolveDynamicAffiliateDashProfile(item, getProfileByKey)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -357,6 +391,45 @@ async function resolveDynamicKuaishouFeifeiProfile(
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveDynamicAffiliateDashProfile(
|
||||
item: OrderItemRow,
|
||||
getProfileByKey: FulfillmentProfileResolver,
|
||||
): Promise<FulfillmentBindingLike | null> {
|
||||
const snapshot = parseJsonObject(item.item_snapshot_json)
|
||||
const affiliateDash = parseJsonObject(snapshot.affiliateDash)
|
||||
const sku = String(affiliateDash.sku || '').trim()
|
||||
if (!sku) {
|
||||
return null
|
||||
}
|
||||
|
||||
const profile = await getProfileByKey(FULFILLMENT_EXECUTOR_KEYS.AFFILIATE_DASH)
|
||||
if (!profile) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
...profile,
|
||||
profile_id: Number(profile.id || profile.profile_id || 0),
|
||||
profile_key: FULFILLMENT_EXECUTOR_KEYS.AFFILIATE_DASH,
|
||||
profile_name: String(profile.profile_name || profile.name || 'affiliate-dash 履约').trim(),
|
||||
executor_key: FULFILLMENT_EXECUTOR_KEYS.AFFILIATE_DASH,
|
||||
requires_claim: true,
|
||||
auto_dispatch: false,
|
||||
config_json: {
|
||||
flowType: 'affiliate_dash',
|
||||
configId: `affiliate_dash:${sku}`,
|
||||
affiliateDash: {
|
||||
sku,
|
||||
productName: String(
|
||||
affiliateDash.skuName || affiliateDash.productName || item.sku_name || '',
|
||||
).trim(),
|
||||
matchMode: String(affiliateDash.matchMode || 'affiliate_dash_sku_mapping').trim(),
|
||||
},
|
||||
notes: '91卡券商品自动匹配 affiliate-dash sku 映射',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveDynamicManualDispatchProfile(
|
||||
item: OrderItemRow,
|
||||
getProfileByKey: FulfillmentProfileResolver,
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
resolveFulfillmentRoute,
|
||||
type FulfillmentRouteResult,
|
||||
} from './routing-config-service.js'
|
||||
import { getAffiliateDashConfig } from '../platforms/affiliate-dash/config.js'
|
||||
import { FULFILLMENT_EXECUTOR_KEYS } from './executors/types.js'
|
||||
|
||||
export type FulfillmentItem = {
|
||||
@@ -36,6 +37,13 @@ type HasConfiguredOrderItemsInput = Omit<ResolveOrderItemForFulfillmentInput, 'i
|
||||
items?: FulfillmentItem[]
|
||||
}
|
||||
|
||||
export type AffiliateDashSkuMatch = {
|
||||
sku: string
|
||||
productNo: string
|
||||
skuName: string
|
||||
matchMode: 'affiliate_dash_sku_mapping'
|
||||
}
|
||||
|
||||
type FulfillmentItemCandidate = {
|
||||
externalItemId: string
|
||||
externalSkuCode: string
|
||||
@@ -44,6 +52,7 @@ type FulfillmentItemCandidate = {
|
||||
resolvedSkuCode: string
|
||||
cloudtentaclesNameMatch: CloudtentaclesNameMatchResult | null
|
||||
kuaishouFeifeiMatch: KuaishouFeifeiProductMatch | null
|
||||
affiliateDashMatch: AffiliateDashSkuMatch | null
|
||||
fulfillmentRoute: FulfillmentRouteResult | null
|
||||
isConfigured: boolean
|
||||
}
|
||||
@@ -76,17 +85,18 @@ export async function resolveOrderItemForFulfillment({
|
||||
externalSkuNameNormalized,
|
||||
cloudtentaclesNameMatch,
|
||||
kuaishouFeifeiMatch,
|
||||
affiliateDashMatch,
|
||||
fulfillmentRoute,
|
||||
} = candidate
|
||||
|
||||
const selectedExecutorKey = fulfillmentRoute?.selectedExecutorKey || ''
|
||||
const resolvedSkuCode = pickFirstNonEmpty([
|
||||
selectedExecutorKey === FULFILLMENT_EXECUTOR_KEYS.KUAISHOU_CLOUD
|
||||
? cloudtentaclesNameMatch?.cloudSkuName
|
||||
: '',
|
||||
selectedExecutorKey === FULFILLMENT_EXECUTOR_KEYS.KUAISHOU_FEIFEI
|
||||
? kuaishouFeifeiMatch?.productCode
|
||||
: '',
|
||||
selectedExecutorKey === FULFILLMENT_EXECUTOR_KEYS.AFFILIATE_DASH
|
||||
? affiliateDashMatch?.sku
|
||||
: '',
|
||||
externalSkuCode,
|
||||
externalItemId,
|
||||
])
|
||||
@@ -97,6 +107,9 @@ export async function resolveOrderItemForFulfillment({
|
||||
selectedExecutorKey === FULFILLMENT_EXECUTOR_KEYS.KUAISHOU_FEIFEI
|
||||
? kuaishouFeifeiMatch?.skuName
|
||||
: '',
|
||||
selectedExecutorKey === FULFILLMENT_EXECUTOR_KEYS.AFFILIATE_DASH
|
||||
? affiliateDashMatch?.sku
|
||||
: '',
|
||||
item.skuName,
|
||||
externalSkuName,
|
||||
resolvedSkuCode,
|
||||
@@ -113,7 +126,9 @@ export async function resolveOrderItemForFulfillment({
|
||||
? cloudtentaclesNameMatch?.matchMode || ''
|
||||
: selectedExecutorKey === FULFILLMENT_EXECUTOR_KEYS.KUAISHOU_FEIFEI
|
||||
? kuaishouFeifeiMatch?.matchMode || ''
|
||||
: '',
|
||||
: selectedExecutorKey === FULFILLMENT_EXECUTOR_KEYS.AFFILIATE_DASH
|
||||
? affiliateDashMatch?.matchMode || ''
|
||||
: '',
|
||||
fulfillmentRoute,
|
||||
cloudtentacles: cloudtentaclesNameMatch
|
||||
? {
|
||||
@@ -139,6 +154,14 @@ export async function resolveOrderItemForFulfillment({
|
||||
skuName: kuaishouFeifeiMatch.skuName,
|
||||
}
|
||||
: null,
|
||||
affiliateDash: affiliateDashMatch
|
||||
? {
|
||||
matchMode: affiliateDashMatch.matchMode,
|
||||
productNo: affiliateDashMatch.productNo,
|
||||
sku: affiliateDashMatch.sku,
|
||||
skuName: affiliateDashMatch.skuName,
|
||||
}
|
||||
: null,
|
||||
isConfigured: candidate.isConfigured,
|
||||
}
|
||||
|
||||
@@ -210,6 +233,7 @@ async function resolveConfiguredItemCandidate({
|
||||
if (isOpen91KuaishouOrder(provider, platform)) {
|
||||
const cloudtentaclesNameMatch = await resolveCloudtentaclesSkuByProductName(externalSkuName)
|
||||
const kuaishouFeifeiMatch = resolveKuaishouFeifeiProductByName(externalSkuName)
|
||||
const affiliateDashMatch = resolveAffiliateDashSkuByProductNo(item)
|
||||
const fulfillmentRoute = resolveFulfillmentRoute({
|
||||
productName: externalSkuName,
|
||||
candidates: [
|
||||
@@ -223,6 +247,11 @@ async function resolveConfiguredItemCandidate({
|
||||
available: Boolean(kuaishouFeifeiMatch),
|
||||
reason: kuaishouFeifeiMatch ? '' : '未命中 kuaishou-feifei 商品规则',
|
||||
},
|
||||
{
|
||||
executorKey: FULFILLMENT_EXECUTOR_KEYS.AFFILIATE_DASH,
|
||||
available: Boolean(affiliateDashMatch),
|
||||
reason: affiliateDashMatch ? '' : '未命中 affiliate-dash sku 映射',
|
||||
},
|
||||
],
|
||||
})
|
||||
const selectedExecutorKey = fulfillmentRoute.selectedExecutorKey
|
||||
@@ -233,6 +262,9 @@ async function resolveConfiguredItemCandidate({
|
||||
selectedExecutorKey === FULFILLMENT_EXECUTOR_KEYS.KUAISHOU_FEIFEI
|
||||
? kuaishouFeifeiMatch?.productCode
|
||||
: '',
|
||||
selectedExecutorKey === FULFILLMENT_EXECUTOR_KEYS.AFFILIATE_DASH
|
||||
? affiliateDashMatch?.sku
|
||||
: '',
|
||||
externalSkuCode,
|
||||
externalItemId,
|
||||
])
|
||||
@@ -245,6 +277,7 @@ async function resolveConfiguredItemCandidate({
|
||||
resolvedSkuCode,
|
||||
cloudtentaclesNameMatch,
|
||||
kuaishouFeifeiMatch,
|
||||
affiliateDashMatch,
|
||||
fulfillmentRoute,
|
||||
isConfigured: Boolean(selectedExecutorKey),
|
||||
}
|
||||
@@ -263,11 +296,39 @@ async function resolveConfiguredItemCandidate({
|
||||
resolvedSkuCode,
|
||||
cloudtentaclesNameMatch: null,
|
||||
kuaishouFeifeiMatch: null,
|
||||
affiliateDashMatch: null,
|
||||
fulfillmentRoute: null,
|
||||
isConfigured: false,
|
||||
}
|
||||
}
|
||||
|
||||
function resolveAffiliateDashSkuByProductNo(item: FulfillmentItem): AffiliateDashSkuMatch | null {
|
||||
const config = getAffiliateDashConfig()
|
||||
if (config.enabled === false || !config.baseUrl) {
|
||||
return null
|
||||
}
|
||||
|
||||
const snapshot = isPlainObject(item.snapshot) ? item.snapshot : {}
|
||||
const productNo = pickFirstNonEmpty([
|
||||
snapshot.productNo,
|
||||
item.externalSkuCode,
|
||||
item.externalItemId,
|
||||
])
|
||||
const sku = productNo
|
||||
? String(config.skuMapping[String(productNo)] || '').trim()
|
||||
: ''
|
||||
if (!productNo || !sku) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
sku,
|
||||
productNo: String(productNo),
|
||||
skuName: String(item.externalSkuName || '').trim(),
|
||||
matchMode: 'affiliate_dash_sku_mapping',
|
||||
}
|
||||
}
|
||||
|
||||
function isOpen91KuaishouOrder(provider: unknown, platform: unknown) {
|
||||
return String(provider || '').trim() === '91kaquan' &&
|
||||
String(platform || '').trim() === 'kuaishou'
|
||||
|
||||
@@ -67,12 +67,14 @@ const FULFILLMENT_ROUTING_CONFIG_FILE_PATH = path.join(
|
||||
const ROUTABLE_EXECUTOR_KEYS: string[] = [
|
||||
FULFILLMENT_EXECUTOR_KEYS.KUAISHOU_CLOUD,
|
||||
FULFILLMENT_EXECUTOR_KEYS.KUAISHOU_FEIFEI,
|
||||
FULFILLMENT_EXECUTOR_KEYS.AFFILIATE_DASH,
|
||||
FULFILLMENT_EXECUTOR_KEYS.MANUAL_DISPATCH,
|
||||
]
|
||||
|
||||
const DEFAULT_EXECUTOR_PRIORITY = [
|
||||
FULFILLMENT_EXECUTOR_KEYS.KUAISHOU_CLOUD,
|
||||
FULFILLMENT_EXECUTOR_KEYS.KUAISHOU_FEIFEI,
|
||||
FULFILLMENT_EXECUTOR_KEYS.AFFILIATE_DASH,
|
||||
]
|
||||
|
||||
export function getFulfillmentRoutingConfigFilePath() {
|
||||
|
||||
Reference in New Issue
Block a user