优化feifei商品名映射

This commit is contained in:
yml2213
2026-07-07 15:12:48 +08:00
parent 6af2fe2e23
commit 177f72dd2c
11 changed files with 235 additions and 144 deletions
@@ -6,6 +6,7 @@ import {
listAdminKuaishouFeifeiProducts,
matchAdminKuaishouFeifeiProduct,
queryAdminKuaishouFeifeiTestOrder,
syncAdminKuaishouFeifeiProductRules,
updateAdminKuaishouFeifeiConfig,
} from "../../../services/admin/platform-config/kuaishou-feifei-service.js";
import { createJsonHandler } from "../session.js";
@@ -74,6 +75,33 @@ router.post(
)
);
router.post(
"/kuaishou-feifei/sync-products",
createJsonHandler(
(req) => syncAdminKuaishouFeifeiProductRules(req.body as JsonRecord),
{
successMessage: "kuaishou-feifei 商品映射已同步",
errorMessage: "同步 kuaishou-feifei 商品映射失败",
scope: "[admin/platform-config/kuaishou-feifei/sync-products]",
audit: (_req, data) => {
const result = data as JsonRecord;
const sync = result.sync as JsonRecord | undefined;
return {
action: "platform_kuaishou_feifei_products_synced",
targetType: "platform_config",
targetId: "kuaishou_feifei",
data: {
productCount: Number(sync?.productCount || 0) || 0,
ruleCount: Number(sync?.ruleCount || 0) || 0,
status: String(sync?.status || "").trim(),
},
};
},
}
)
);
router.post(
"/kuaishou-feifei/test-order",
createJsonHandler(
@@ -3,6 +3,7 @@ import {
createKuaishouFeifeiOrder,
listKuaishouFeifeiProducts,
queryKuaishouFeifeiOrder,
type KuaishouFeifeiProductListResult,
} from '../../platforms/kuaishou-feifei/order-service.js'
import { resolveKuaishouFeifeiProductByName } from '../../platforms/kuaishou-feifei/product-rule-service.js'
import {
@@ -64,6 +65,52 @@ export async function listAdminKuaishouFeifeiProducts(payload: JsonObject = {})
})
}
export async function syncAdminKuaishouFeifeiProductRules(payload: JsonObject = {}) {
const source = getAdminEditableKuaishouFeifeiConfig()
const status = String(payload.status || 'on_sale').trim() || 'on_sale'
const supplyProductName = String(payload.supplyProductName || payload.supply_product_name || '').trim()
const products = await listAllKuaishouFeifeiProducts({
status,
supplyProductName,
})
const productRules = products
.map((product) => {
const productName = String(product.name || '').trim()
const productCode = String(product.productCode || '').trim()
if (!productName || !productCode) {
return null
}
return {
id: normalizeCloudtentaclesMatchName(productName) || productCode,
enabled: product.status !== 'off_sale' && product.supplyStatus !== 'off_sale',
productName,
normalizedProductName: normalizeCloudtentaclesMatchName(productName),
productCode,
skuName: productName,
notes: `feifei 商品目录同步:${product.channel || 'unknown'}`,
}
})
.filter((rule): rule is NonNullable<typeof rule> => Boolean(rule))
const saved = saveKuaishouFeifeiSourceConfig({
...source,
productRules,
})
const effective = getKuaishouFeifeiConfig()
return {
filePath: getKuaishouFeifeiConfigFilePath(),
source: saved,
effective: mapEffectiveKuaishouFeifeiConfig(effective),
sync: {
productCount: products.length,
ruleCount: saved.productRules.length,
status,
supplyProductName,
},
}
}
export async function createAdminKuaishouFeifeiTestOrder(payload: JsonObject = {}) {
const platformOrderNo = String(payload.platformOrderNo || payload.platform_order_no || '').trim()
const productCode = String(payload.productCode || payload.product_code || '').trim()
@@ -139,6 +186,34 @@ function mapEffectiveKuaishouFeifeiConfig(config: ReturnType<typeof getKuaishouF
}
}
async function listAllKuaishouFeifeiProducts(input: {
status: string
supplyProductName: string
}) {
const perPage = 100
const firstPage = await listKuaishouFeifeiProducts({
page: 1,
perPage,
status: input.status,
supplyProductName: input.supplyProductName,
})
const products = [...firstPage.items]
const total = Number(firstPage.total || products.length) || products.length
const pageCount = Math.max(1, Math.ceil(total / perPage))
for (let page = 2; page <= pageCount; page += 1) {
const result: KuaishouFeifeiProductListResult = await listKuaishouFeifeiProducts({
page,
perPage,
status: input.status,
supplyProductName: input.supplyProductName,
})
products.push(...result.items)
}
return products
}
function normalizeOptionalNumber(value: unknown) {
if (value == null || String(value).trim() === '') {
return undefined
@@ -545,9 +545,9 @@ async function resolveDynamicKuaishouFeifeiProfile(
kuaishouFeifei: {
productCode,
productName: String(feifei.skuName || feifei.productName || item.sku_name || '').trim(),
matchMode: String(feifei.matchMode || 'kuaishou_feifei_rule').trim(),
matchMode: String(feifei.matchMode || 'kuaishou_feifei_name').trim(),
},
notes: '91卡券商品名命中 kuaishou-feifei 商品规则',
notes: '91卡券商品名自动匹配 kuaishou-feifei 商品映射',
},
}
}
@@ -3,6 +3,8 @@ import { kuaishouFeifeiRequest } from './http-client.js'
type JsonObject = Record<string, any>
export type KuaishouFeifeiProductListResult = ReturnType<typeof mapKuaishouFeifeiProductList>
export async function listKuaishouFeifeiProducts(input: {
page?: number
perPage?: number
@@ -0,0 +1,22 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { resolveKuaishouFeifeiProductByName } from './product-rule-service.js'
test('resolveKuaishouFeifeiProductByName matches generated name-code mapping by normalized name', () => {
const match = resolveKuaishouFeifeiProductByName('【自动发货】套装-Alan Walker', {
rules: [
{
productName: '套装-Alan Walker',
normalizedProductName: '套装 alan walker',
productCode: '10000013',
skuName: '套装-Alan Walker',
},
],
})
assert.equal(match?.productCode, '10000013')
assert.equal(match?.matchMode, 'kuaishou_feifei_name')
assert.equal(match?.skuName, '套装-Alan Walker')
assert.equal(match?.normalizedProductName, '套装 alan walker')
})
@@ -3,7 +3,7 @@ import { getKuaishouFeifeiConfig } from './config.js'
import type { KuaishouFeifeiProductRule } from '../../../types/runtime-config.js'
export type KuaishouFeifeiProductMatch = {
matchMode: 'kuaishou_feifei_rule'
matchMode: 'kuaishou_feifei_name'
productName: string
normalizedProductName: string
productCode: string
@@ -37,7 +37,7 @@ export function resolveKuaishouFeifeiProductByName(
}
return {
matchMode: 'kuaishou_feifei_rule',
matchMode: 'kuaishou_feifei_name',
productName: String(matched.productName || productName || '').trim(),
normalizedProductName,
productCode,