diff --git a/apps/backend/data/kuaishou-cloud-fulfillment.json b/apps/backend/data/kuaishou-cloud-fulfillment.json
index 9cf0d641..f04b2d49 100644
--- a/apps/backend/data/kuaishou-cloud-fulfillment.json
+++ b/apps/backend/data/kuaishou-cloud-fulfillment.json
@@ -8,6 +8,33 @@
"provider": "khhao",
"platform": "kuaishou",
"shopId": "4269276762",
+ "khhaoShopId": "10",
+ "internalSkuCode": "套装-浪漫天命",
+ "internalSkuName": "套装-浪漫天命",
+ "externalSkuCode": "952",
+ "externalItemId": "952",
+ "externalSkuName": "测试2",
+ "resolvedSkuName": "测试2",
+ "cloudSourceKey": "default",
+ "cloudSkuId": 28,
+ "cloudSkuName": "套装-浪漫天命",
+ "vnKey": "1",
+ "autoBuyEnabled": true,
+ "minAssetReserve": 0,
+ "autoReturnNumberAfterDispatch": true,
+ "autoConsumeAfterDispatch": true,
+ "kuaishouConsumeShopId": "4269276762",
+ "kuaishouConsumeShopName": "稚嫩游戏交易店",
+ "notes": "从 khhao 订单 2612400002308936 导入"
+ },
+ {
+ "id": "套装-浪漫天命",
+ "enabled": false,
+ "priority": 100,
+ "provider": "khhao",
+ "platform": "kuaishou",
+ "shopId": "4269276762",
+ "khhaoShopId": "10",
"internalSkuCode": "套装-浪漫天命",
"internalSkuName": "测试1",
"externalSkuCode": "830",
diff --git a/apps/backend/src/services/admin/platform-config/mappers.js b/apps/backend/src/services/admin/platform-config/mappers.js
index 3f99c806..ec2c879c 100644
--- a/apps/backend/src/services/admin/platform-config/mappers.js
+++ b/apps/backend/src/services/admin/platform-config/mappers.js
@@ -121,6 +121,7 @@ export function mapAdminKuaishouCloudFulfillmentItem(item = {}) {
provider: String(item.provider || 'khhao').trim() || 'khhao',
platform: String(item.platform || 'kuaishou').trim() || 'kuaishou',
shopId: String(item.shopId || '').trim(),
+ khhaoShopId: String(item.khhaoShopId || '').trim(),
internalSkuCode: String(item.internalSkuCode || '').trim(),
internalSkuName: String(item.internalSkuName || '').trim(),
externalSkuCode: String(item.externalSkuCode || '').trim(),
diff --git a/apps/backend/src/services/admin/platform-config/mappers.test.js b/apps/backend/src/services/admin/platform-config/mappers.test.js
index d34ae1b4..07c64cbc 100644
--- a/apps/backend/src/services/admin/platform-config/mappers.test.js
+++ b/apps/backend/src/services/admin/platform-config/mappers.test.js
@@ -172,6 +172,7 @@ test('mapAdminKuaishouCloudFulfillmentSource normalizes items and defaults', ()
provider: 'khhao',
platform: 'kuaishou',
shopId: '',
+ khhaoShopId: '',
internalSkuCode: '',
internalSkuName: '',
externalSkuCode: '',
diff --git a/apps/backend/src/services/bootstrap/fulfillment-bootstrap-service.js b/apps/backend/src/services/bootstrap/fulfillment-bootstrap-service.js
index 8a8e3bd2..ddb1b172 100644
--- a/apps/backend/src/services/bootstrap/fulfillment-bootstrap-service.js
+++ b/apps/backend/src/services/bootstrap/fulfillment-bootstrap-service.js
@@ -120,55 +120,60 @@ export async function syncConfiguredFulfillmentBindings(profileMap = {}) {
continue
}
- const matchShopId = resolveBindingMatchShopId(binding)
const bindingConfig = resolveBindingRuntimeConfig(binding)
-
- await upsertSkuFulfillmentBinding({
- skuCode: binding.skuCode,
- provider: binding.provider,
- platform: binding.platform,
- shopId: matchShopId,
- profileId: profile.id,
- enabled: binding.enabled,
- priority: binding.priority,
- configJson: JSON.stringify(bindingConfig),
- createdAt: timestamp,
- updatedAt: timestamp,
- })
-
+ const matchShopIds = resolveBindingMatchShopIds(binding)
const match = binding.match || {}
const externalSkuName = String(match.externalSkuName || '').trim()
const externalItemId = String(match.externalItemId || '').trim()
const externalSkuCode = String(match.externalSkuCode || '').trim()
- if (!externalSkuName && !externalItemId && !externalSkuCode) {
- continue
- }
+ for (const matchShopId of matchShopIds) {
+ await upsertSkuFulfillmentBinding({
+ skuCode: binding.skuCode,
+ provider: binding.provider,
+ platform: binding.platform,
+ shopId: matchShopId,
+ profileId: profile.id,
+ enabled: binding.enabled,
+ priority: binding.priority,
+ configJson: JSON.stringify(bindingConfig),
+ createdAt: timestamp,
+ updatedAt: timestamp,
+ })
- await upsertProductMatchRule({
- provider: binding.provider,
- platform: binding.platform,
- shopId: matchShopId,
- externalItemId,
- externalSkuCode,
- externalSkuName,
- externalSkuNameNormalized: normalizeProductName(externalSkuName),
- resolvedSkuCode: binding.skuCode,
- enabled: binding.enabled,
- priority: binding.priority,
- configJson: JSON.stringify(match.config || {}),
- createdAt: timestamp,
- updatedAt: timestamp,
- })
+ if (!externalSkuName && !externalItemId && !externalSkuCode) {
+ continue
+ }
+
+ await upsertProductMatchRule({
+ provider: binding.provider,
+ platform: binding.platform,
+ shopId: matchShopId,
+ externalItemId,
+ externalSkuCode,
+ externalSkuName,
+ externalSkuNameNormalized: normalizeProductName(externalSkuName),
+ resolvedSkuCode: binding.skuCode,
+ enabled: binding.enabled,
+ priority: binding.priority,
+ configJson: JSON.stringify(match.config || {}),
+ createdAt: timestamp,
+ updatedAt: timestamp,
+ })
+ }
}
}
-function resolveBindingMatchShopId(binding = {}) {
+export function resolveBindingMatchShopIds(binding = {}) {
if (String(binding.provider || '').trim() === 'khhao' && String(binding.platform || '').trim() === 'kuaishou') {
- return String(binding.khhaoShopId || binding.shopId || '').trim()
+ const candidates = [...new Set([
+ String(binding.khhaoShopId || '').trim(),
+ String(binding.shopId || '').trim(),
+ ].filter(Boolean))]
+ return candidates.length > 0 ? candidates : ['']
}
- return String(binding.shopId || '').trim()
+ return [String(binding.shopId || '').trim()]
}
function resolveBindingRuntimeConfig(binding = {}) {
diff --git a/apps/backend/src/services/bootstrap/fulfillment-bootstrap-service.test.js b/apps/backend/src/services/bootstrap/fulfillment-bootstrap-service.test.js
new file mode 100644
index 00000000..6616d81e
--- /dev/null
+++ b/apps/backend/src/services/bootstrap/fulfillment-bootstrap-service.test.js
@@ -0,0 +1,27 @@
+import test from 'node:test'
+import assert from 'node:assert/strict'
+
+import { resolveBindingMatchShopIds } from './fulfillment-bootstrap-service.js'
+
+test('resolveBindingMatchShopIds returns dual shop ids for khhao kuaishou bindings', () => {
+ assert.deepEqual(
+ resolveBindingMatchShopIds({
+ provider: 'khhao',
+ platform: 'kuaishou',
+ shopId: '4269276762',
+ khhaoShopId: '10',
+ }),
+ ['10', '4269276762'],
+ )
+})
+
+test('resolveBindingMatchShopIds preserves wildcard rules for empty shop id', () => {
+ assert.deepEqual(
+ resolveBindingMatchShopIds({
+ provider: 'agiso',
+ platform: 'xianyu',
+ shopId: '',
+ }),
+ [''],
+ )
+})
diff --git a/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.js b/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.js
index 6347aef9..52efd9c0 100644
--- a/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.js
+++ b/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.js
@@ -33,6 +33,7 @@ export function mapKuaishouCloudFulfillmentItemsToBindings(config = {}) {
provider: String(item.provider || 'khhao').trim() || 'khhao',
platform: String(item.platform || 'kuaishou').trim() || 'kuaishou',
shopId: String(item.shopId || '').trim(),
+ khhaoShopId: String(item.khhaoShopId || '').trim(),
skuCode: String(item.internalSkuCode || '').trim(),
skuName: String(item.internalSkuName || '').trim(),
profileKey: 'kuaishou_ct_assisted',
@@ -123,6 +124,7 @@ function normalizeKuaishouCloudFulfillmentItem(rawValue) {
provider: String(rawValue.provider || 'khhao').trim() || 'khhao',
platform: String(rawValue.platform || 'kuaishou').trim() || 'kuaishou',
shopId: String(rawValue.shopId || '').trim(),
+ khhaoShopId: String(rawValue.khhaoShopId || '').trim(),
internalSkuCode,
internalSkuName: String(rawValue.internalSkuName || '').trim() || internalSkuCode,
externalSkuCode,
diff --git a/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.test.js b/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.test.js
new file mode 100644
index 00000000..36054c2f
--- /dev/null
+++ b/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.test.js
@@ -0,0 +1,66 @@
+import test from 'node:test'
+import assert from 'node:assert/strict'
+
+import { mapKuaishouCloudFulfillmentItemsToBindings } from './kuaishou-cloud-fulfillment-config-service.js'
+
+test('mapKuaishouCloudFulfillmentItemsToBindings preserves khhao shop id', () => {
+ assert.deepEqual(
+ mapKuaishouCloudFulfillmentItemsToBindings({
+ items: [
+ {
+ enabled: true,
+ provider: 'khhao',
+ platform: 'kuaishou',
+ shopId: '4269276762',
+ khhaoShopId: '10',
+ internalSkuCode: 'sku-1',
+ internalSkuName: '内部商品',
+ externalSkuCode: '952',
+ externalItemId: '952',
+ externalSkuName: '测试2',
+ cloudSkuId: 28,
+ },
+ ],
+ }),
+ [
+ {
+ provider: 'khhao',
+ platform: 'kuaishou',
+ shopId: '4269276762',
+ khhaoShopId: '10',
+ skuCode: 'sku-1',
+ skuName: '内部商品',
+ profileKey: 'kuaishou_ct_assisted',
+ enabled: true,
+ priority: 100,
+ config: {
+ flowType: 'kuaishou_cloud_fulfillment',
+ configId: '',
+ cloudtentacles: {
+ cloudSourceKey: 'default',
+ skuId: 28,
+ skuName: '',
+ vnKey: '1',
+ autoBuyEnabled: true,
+ minAssetReserve: 0,
+ autoReturnNumberAfterDispatch: false,
+ },
+ kuaishouConsume: {
+ shopId: '',
+ shopName: '',
+ autoConsumeAfterDispatch: false,
+ },
+ notes: '',
+ },
+ match: {
+ externalSkuCode: '952',
+ externalItemId: '952',
+ externalSkuName: '测试2',
+ config: {
+ resolvedSkuName: '内部商品',
+ },
+ },
+ },
+ ],
+ )
+})
diff --git a/apps/backend/src/services/order/order-service.js b/apps/backend/src/services/order/order-service.js
index cebfe428..cc9c92d0 100644
--- a/apps/backend/src/services/order/order-service.js
+++ b/apps/backend/src/services/order/order-service.js
@@ -46,6 +46,7 @@ export async function upsertOrderFromSource(event, { sourceLabel = 'source' } =
provider: event.provider,
platform: event.platform,
shopId: event.shopId,
+ shopIdAliases: event.shopIdAliases,
item,
})),
)
diff --git a/apps/backend/src/services/order/product-match-service.js b/apps/backend/src/services/order/product-match-service.js
index c2579569..91d2ec9d 100644
--- a/apps/backend/src/services/order/product-match-service.js
+++ b/apps/backend/src/services/order/product-match-service.js
@@ -21,6 +21,7 @@ import { resolveProductMatchRule } from '../../repositories/product-match-rule-r
* provider?: string
* platform?: string
* shopId?: string
+ * shopIdAliases?: string[]
* item?: FulfillmentItem
* }} params
*/
@@ -28,12 +29,14 @@ export async function resolveOrderItemForFulfillment({
provider = '',
platform = '',
shopId = '',
+ shopIdAliases = [],
item = /** @type {FulfillmentItem} */ ({}),
}) {
const candidate = await resolveConfiguredItemCandidate({
provider,
platform,
shopId,
+ shopIdAliases,
item,
})
const {
@@ -89,6 +92,7 @@ export async function resolveOrderItemForFulfillment({
* provider?: string
* platform?: string
* shopId?: string
+ * shopIdAliases?: string[]
* items?: FulfillmentItem[]
* }} params
*/
@@ -96,6 +100,7 @@ export async function hasConfiguredOrderItems({
provider = '',
platform = '',
shopId = '',
+ shopIdAliases = [],
items = [],
}) {
const candidates = await Promise.all(
@@ -103,6 +108,7 @@ export async function hasConfiguredOrderItems({
provider,
platform,
shopId,
+ shopIdAliases,
item,
})),
)
@@ -135,6 +141,7 @@ async function resolveConfiguredItemCandidate({
provider = '',
platform = '',
shopId = '',
+ shopIdAliases = [],
item = /** @type {FulfillmentItem} */ ({}),
}) {
const externalItemId = pickFirstNonEmpty([
@@ -152,28 +159,47 @@ async function resolveConfiguredItemCandidate({
externalSkuCode,
])
const externalSkuNameNormalized = normalizeProductName(externalSkuName)
+ const shopIdCandidates = resolveShopIdCandidates({ shopId, shopIdAliases })
+ let matchedRule = null
+ let binding = null
+
+ for (const candidateShopId of shopIdCandidates) {
+ const candidateRule = await resolveProductMatchRule({
+ provider,
+ platform,
+ shopId: candidateShopId,
+ externalItemId,
+ externalSkuCode,
+ externalSkuNameNormalized,
+ })
+ if (candidateRule) {
+ matchedRule = candidateRule
+ }
+
+ const resolvedSkuCode = pickFirstNonEmpty([
+ candidateRule?.resolved_sku_code,
+ externalSkuCode,
+ externalItemId,
+ ])
+ binding = resolvedSkuCode
+ ? await resolveFulfillmentBinding({
+ skuCode: resolvedSkuCode,
+ provider,
+ platform,
+ shopId: candidateShopId,
+ })
+ : null
+
+ if (binding) {
+ break
+ }
+ }
- const matchedRule = await resolveProductMatchRule({
- provider,
- platform,
- shopId,
- externalItemId,
- externalSkuCode,
- externalSkuNameNormalized,
- })
const resolvedSkuCode = pickFirstNonEmpty([
matchedRule?.resolved_sku_code,
externalSkuCode,
externalItemId,
])
- const binding = resolvedSkuCode
- ? await resolveFulfillmentBinding({
- skuCode: resolvedSkuCode,
- provider,
- platform,
- shopId,
- })
- : null
return {
externalItemId,
@@ -187,6 +213,15 @@ async function resolveConfiguredItemCandidate({
}
}
+export function resolveShopIdCandidates({ shopId = '', shopIdAliases = [] } = {}) {
+ const candidates = [...new Set([
+ String(shopId || '').trim(),
+ ...(Array.isArray(shopIdAliases) ? shopIdAliases : []).map((item) => String(item || '').trim()),
+ ].filter(Boolean))]
+
+ return candidates.length > 0 ? candidates : ['']
+}
+
function readConfigValue(rawValue, key) {
if (!rawValue) {
return ''
diff --git a/apps/backend/src/services/order/product-match-service.test.js b/apps/backend/src/services/order/product-match-service.test.js
new file mode 100644
index 00000000..a98c0b7d
--- /dev/null
+++ b/apps/backend/src/services/order/product-match-service.test.js
@@ -0,0 +1,18 @@
+import test from 'node:test'
+import assert from 'node:assert/strict'
+
+import { resolveShopIdCandidates } from './product-match-service.js'
+
+test('resolveShopIdCandidates merges primary and alias shop ids', () => {
+ assert.deepEqual(
+ resolveShopIdCandidates({
+ shopId: '10',
+ shopIdAliases: ['4269276762', '10', ''],
+ }),
+ ['10', '4269276762'],
+ )
+})
+
+test('resolveShopIdCandidates preserves wildcard fallback when ids are empty', () => {
+ assert.deepEqual(resolveShopIdCandidates({}), [''])
+})
diff --git a/apps/backend/src/services/order/webhook-service.js b/apps/backend/src/services/order/webhook-service.js
index 637748e5..1bdc3713 100644
--- a/apps/backend/src/services/order/webhook-service.js
+++ b/apps/backend/src/services/order/webhook-service.js
@@ -141,6 +141,7 @@ async function executeAgisoTradeWebhook(parsed, webhookEventId, { requestId = ''
provider: parsed.provider,
platform: parsed.platform,
shopId: parsed.shopId,
+ shopIdAliases: parsed.shopIdAliases,
items: parsed.items,
})
diff --git a/apps/backend/src/types/admin-write-inputs.js b/apps/backend/src/types/admin-write-inputs.js
index 1ec0a77f..cd46ac56 100644
--- a/apps/backend/src/types/admin-write-inputs.js
+++ b/apps/backend/src/types/admin-write-inputs.js
@@ -75,6 +75,7 @@ export {}
* provider?: string
* platform?: string
* shopId?: string
+ * khhaoShopId?: string
* internalSkuCode?: string
* internalSkuName?: string
* externalSkuCode?: string
diff --git a/apps/frontend/src/types/admin.ts b/apps/frontend/src/types/admin.ts
index b781a2ce..76568ead 100644
--- a/apps/frontend/src/types/admin.ts
+++ b/apps/frontend/src/types/admin.ts
@@ -378,6 +378,7 @@ export interface AdminKuaishouCloudFulfillmentItem {
provider: string
platform: string
shopId: string
+ khhaoShopId: string
internalSkuCode: string
internalSkuName: string
externalSkuCode: string
diff --git a/apps/frontend/src/views/admin/AdminKuaishouCloudFulfillmentView.vue b/apps/frontend/src/views/admin/AdminKuaishouCloudFulfillmentView.vue
index 45d7e6e3..37cfca4d 100644
--- a/apps/frontend/src/views/admin/AdminKuaishouCloudFulfillmentView.vue
+++ b/apps/frontend/src/views/admin/AdminKuaishouCloudFulfillmentView.vue
@@ -99,6 +99,7 @@ function createEmptyItem(): EditableItem {
provider: 'khhao',
platform: 'kuaishou',
shopId: '',
+ khhaoShopId: '',
internalSkuCode: '',
internalSkuName: '',
externalSkuCode: '',
@@ -128,6 +129,7 @@ function mapEditableItem(item: AdminKuaishouCloudFulfillmentItem): EditableItem
provider: item.provider || 'khhao',
platform: item.platform || 'kuaishou',
shopId: item.shopId || '',
+ khhaoShopId: item.khhaoShopId || '',
internalSkuCode: item.internalSkuCode || '',
internalSkuName: item.internalSkuName || '',
externalSkuCode: item.externalSkuCode || '',
@@ -156,6 +158,7 @@ function mapSaveItem(item: EditableItem): AdminKuaishouCloudFulfillmentItem {
provider: item.provider.trim() || 'khhao',
platform: item.platform.trim() || 'kuaishou',
shopId: item.shopId.trim(),
+ khhaoShopId: item.khhaoShopId.trim(),
internalSkuCode: item.internalSkuCode.trim(),
internalSkuName: item.internalSkuName.trim(),
externalSkuCode: item.externalSkuCode.trim(),
@@ -193,6 +196,7 @@ function hasMeaningfulContent(item: EditableItem) {
|| item.cloudSkuName.trim()
|| item.vnKey.trim()
|| item.shopId.trim()
+ || item.khhaoShopId.trim()
|| item.notes.trim()
|| item.provider.trim() !== 'khhao'
|| item.platform.trim() !== 'kuaishou'
@@ -398,6 +402,7 @@ function createItemFromKhhaoOrder(item: AdminKhhaoOrderPreview): EditableItem {
provider: item.provider || 'khhao',
platform: item.platform || 'kuaishou',
shopId: item.kuaishouShopId || item.shopId || '',
+ khhaoShopId: item.khhaoShopId || item.shopId || '',
internalSkuCode: '',
internalSkuName: item.itemTitle || '',
externalSkuCode: item.skuCode || '',
@@ -425,7 +430,10 @@ function findExistingItemFromKhhaoOrder(order: AdminKhhaoOrderPreview) {
const orderTitle = String(order.itemTitle || '').trim()
return items.value.find((item) => {
- const sameShop = !orderShopId || item.shopId.trim() === orderShopId || item.kuaishouConsumeShopId.trim() === orderShopId
+ const sameShop = !orderShopId
+ || item.shopId.trim() === orderShopId
+ || item.khhaoShopId.trim() === String(order.khhaoShopId || '').trim()
+ || item.kuaishouConsumeShopId.trim() === orderShopId
const sameItemId = orderItemId && item.externalItemId.trim() === orderItemId
const sameSkuCode = orderSkuCode && item.externalSkuCode.trim() === orderSkuCode
const sameTitle = orderTitle && [item.externalSkuName, item.internalSkuName, item.resolvedSkuName].some((value) => value.trim() === orderTitle)
@@ -932,6 +940,10 @@ onMounted(loadConfigs)
店铺 ID
+