From d11879f682feed1dacb1b24b775eeabe0aefe40f Mon Sep 17 00:00:00 2001 From: yml Date: Mon, 4 May 2026 15:31:07 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8B=E6=B2=89=E5=90=8E=E5=8F=B0=E5=B1=A5?= =?UTF-8?q?=E7=BA=A6=E8=A7=84=E5=88=99=E6=A0=A1=E9=AA=8C=E7=BC=96=E6=8E=92?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../services/admin/platform-config/service.js | 49 ++----------- .../admin/platform-config/validation.js | 46 ++++++++++++ .../admin/platform-config/validation.test.js | 71 +++++++++++++++++++ 3 files changed, 123 insertions(+), 43 deletions(-) diff --git a/apps/backend/src/services/admin/platform-config/service.js b/apps/backend/src/services/admin/platform-config/service.js index 3cdd43b8..4c8b1db9 100644 --- a/apps/backend/src/services/admin/platform-config/service.js +++ b/apps/backend/src/services/admin/platform-config/service.js @@ -74,7 +74,6 @@ import { saveKuaishouCloudFulfillmentConfig, } from '../../order/kuaishou-cloud-fulfillment-config-service.js' import { getFulfillmentProfileByKey } from '../../../repositories/fulfillment-profile-repo.js' -import { createHttpError } from '../../../utils/http.js' import { syncConfiguredFulfillmentBindings } from '../../bootstrap/fulfillment-bootstrap-service.js' import { resolveDisplayShopName } from '../admin-read-shared-helpers.js' import { @@ -94,9 +93,7 @@ import { } from './cloudtentacles.js' import { listAdminObservedProducts } from './observed-products.js' import { - assertAdminFulfillmentBindingsInput, - buildAdminFulfillmentBindingUniqueKey, - normalizeAdminFulfillmentBindingItem, + validateAdminFulfillmentBindingConfigs, } from './validation.js' import { applyOptionalStringField, @@ -740,7 +737,11 @@ export async function updateAdminFulfillmentBindingConfigs( payload = /** @type {AdminFulfillmentBindingConfigSaveInput} */ ({}), ) { const bindingsInput = Array.isArray(payload.bindings) ? payload.bindings : [] - const normalizedBindings = await validateAdminFulfillmentBindingConfigs(bindingsInput) + const normalizedBindings = await validateAdminFulfillmentBindingConfigs(bindingsInput, { + kuaishouEticketSource: getKuaishouEticketSourceConfig(), + resolveKuaishouEticketShopConfig, + getFulfillmentProfileByKey, + }) const saved = saveOrderFulfillmentBindingConfigs(normalizedBindings) await syncConfiguredFulfillmentBindings() @@ -749,41 +750,3 @@ export async function updateAdminFulfillmentBindingConfigs( bindings: saved.map(mapAdminFulfillmentBindingConfigItem), } } - -async function validateAdminFulfillmentBindingConfigs(bindings = []) { - assertAdminFulfillmentBindingsInput(bindings) - - const seenKeys = new Set() - const kuaishouEticketSource = getKuaishouEticketSourceConfig() - const normalizedBindings = [] - - for (const [index, rawBinding] of bindings.entries()) { - const normalized = normalizeAdminFulfillmentBindingItem(rawBinding, { - index, - kuaishouEticketSource, - resolveKuaishouEticketShopConfig, - }) - - const profile = await getFulfillmentProfileByKey(normalized.profileKey) - if (!profile) { - throw createHttpError(`第 ${index + 1} 条规则使用了不存在的履约方式: ${normalized.profileKey}`, { - statusCode: 400, - errorCode: 'admin_fulfillment_bindings_invalid_profile_key', - }) - } - - const uniqueKey = buildAdminFulfillmentBindingUniqueKey(normalized) - - if (seenKeys.has(uniqueKey)) { - throw createHttpError(`第 ${index + 1} 条规则与其它规则重复,请调整匹配条件或内部履约 SKU`, { - statusCode: 409, - errorCode: 'admin_fulfillment_bindings_duplicate_rule', - }) - } - - seenKeys.add(uniqueKey) - normalizedBindings.push(normalized) - } - - return normalizedBindings -} diff --git a/apps/backend/src/services/admin/platform-config/validation.js b/apps/backend/src/services/admin/platform-config/validation.js index d1f4c21f..27365c54 100644 --- a/apps/backend/src/services/admin/platform-config/validation.js +++ b/apps/backend/src/services/admin/platform-config/validation.js @@ -135,3 +135,49 @@ export function buildAdminFulfillmentBindingUniqueKey(binding) { String(binding?.skuCode || '').trim(), ].join('::') } + +export async function validateAdminFulfillmentBindingConfigs( + bindings, + options = {}, +) { + assertAdminFulfillmentBindingsInput(bindings) + + const kuaishouEticketSource = /** @type {Record} */ (options.kuaishouEticketSource || {}) + const resolveKuaishouEticketShopConfig = /** @type {(shop: Record, source: Record) => Record | null} */ ( + options.resolveKuaishouEticketShopConfig || (() => null) + ) + const getFulfillmentProfileByKey = /** @type {(profileKey: string) => Promise} */ ( + options.getFulfillmentProfileByKey || (async () => null) + ) + const seenKeys = new Set() + const normalizedBindings = [] + + for (const [index, rawBinding] of bindings.entries()) { + const normalized = normalizeAdminFulfillmentBindingItem(rawBinding, { + index, + kuaishouEticketSource, + resolveKuaishouEticketShopConfig, + }) + + const profile = await getFulfillmentProfileByKey(normalized.profileKey) + if (!profile) { + throw createHttpError(`第 ${index + 1} 条规则使用了不存在的履约方式: ${normalized.profileKey}`, { + statusCode: 400, + errorCode: 'admin_fulfillment_bindings_invalid_profile_key', + }) + } + + const uniqueKey = buildAdminFulfillmentBindingUniqueKey(normalized) + if (seenKeys.has(uniqueKey)) { + throw createHttpError(`第 ${index + 1} 条规则与其它规则重复,请调整匹配条件或内部履约 SKU`, { + statusCode: 409, + errorCode: 'admin_fulfillment_bindings_duplicate_rule', + }) + } + + seenKeys.add(uniqueKey) + normalizedBindings.push(normalized) + } + + return normalizedBindings +} diff --git a/apps/backend/src/services/admin/platform-config/validation.test.js b/apps/backend/src/services/admin/platform-config/validation.test.js index 7f4eb892..2434d0ba 100644 --- a/apps/backend/src/services/admin/platform-config/validation.test.js +++ b/apps/backend/src/services/admin/platform-config/validation.test.js @@ -5,6 +5,7 @@ import { assertAdminFulfillmentBindingsInput, buildAdminFulfillmentBindingUniqueKey, normalizeAdminFulfillmentBindingItem, + validateAdminFulfillmentBindingConfigs, } from './validation.js' test('assertAdminFulfillmentBindingsInput rejects non-array payloads', () => { @@ -117,3 +118,73 @@ test('buildAdminFulfillmentBindingUniqueKey uses normalized external sku name an 'khhao::kuaishou::khhao-a::::::礼包 a::sku-1', ) }) + +test('validateAdminFulfillmentBindingConfigs rejects missing fulfillment profile', async () => { + await assert.rejects( + () => + validateAdminFulfillmentBindingConfigs( + [ + { + provider: 'agiso', + platform: 'xianyu', + shopId: 'shop-1', + skuCode: 'sku-1', + profileKey: 'missing-profile', + match: { + externalSkuCode: 'ext-1', + }, + }, + ], + { + kuaishouEticketSource: {}, + resolveKuaishouEticketShopConfig() { + return null + }, + async getFulfillmentProfileByKey() { + return null + }, + }, + ), + /第 1 条规则使用了不存在的履约方式: missing-profile/, + ) +}) + +test('validateAdminFulfillmentBindingConfigs rejects duplicate rules after normalization', async () => { + await assert.rejects( + () => + validateAdminFulfillmentBindingConfigs( + [ + { + provider: 'agiso', + platform: 'xianyu', + shopId: 'shop-1', + skuCode: 'sku-1', + profileKey: 'manual_review', + match: { + externalSkuCode: ' ext-1 ', + }, + }, + { + provider: 'agiso', + platform: 'xianyu', + shopId: ' shop-1 ', + skuCode: 'sku-1', + profileKey: 'manual_review', + match: { + externalSkuCode: 'ext-1', + }, + }, + ], + { + kuaishouEticketSource: {}, + resolveKuaishouEticketShopConfig() { + return null + }, + async getFulfillmentProfileByKey() { + return { key: 'manual_review' } + }, + }, + ), + /第 2 条规则与其它规则重复/, + ) +})