From c870414ad63415237bd53d2d96689b85019164e3 Mon Sep 17 00:00:00 2001 From: yml Date: Thu, 21 May 2026 14:27:45 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E8=BF=81=E7=A7=BB=E5=95=86?= =?UTF-8?q?=E5=93=81=E5=8C=B9=E9=85=8D=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ch-service.js => product-match-service.ts} | 115 ++++++++++-------- docs/backend-typescript-migration-plan.md | 9 ++ 2 files changed, 75 insertions(+), 49 deletions(-) rename apps/backend/src/services/order/{product-match-service.js => product-match-service.ts} (71%) diff --git a/apps/backend/src/services/order/product-match-service.js b/apps/backend/src/services/order/product-match-service.ts similarity index 71% rename from apps/backend/src/services/order/product-match-service.js rename to apps/backend/src/services/order/product-match-service.ts index 91d2ec9d..b4ab6a93 100644 --- a/apps/backend/src/services/order/product-match-service.js +++ b/apps/backend/src/services/order/product-match-service.ts @@ -1,37 +1,65 @@ import { resolveFulfillmentBinding } from '../../repositories/fulfillment-profile-repo.js' import { resolveProductMatchRule } from '../../repositories/product-match-rule-repo.js' -/** - * @typedef {{ - * itemId?: string - * externalItemId?: string - * skuCode?: string - * skuName?: string - * externalSkuCode?: string - * externalSkuName?: string - * quantity?: number - * spec?: Record - * snapshot?: Record - * [key: string]: unknown - * }} FulfillmentItem - */ +export type FulfillmentItem = { + itemId?: string + externalItemId?: string + skuCode?: string + skuName?: string + externalSkuCode?: string + externalSkuName?: string + quantity?: number + spec?: Record + snapshot?: Record + [key: string]: unknown +} + +type ResolveOrderItemForFulfillmentInput = { + provider?: string + platform?: string + shopId?: string + shopIdAliases?: string[] + item?: FulfillmentItem +} + +type HasConfiguredOrderItemsInput = Omit & { + items?: FulfillmentItem[] +} + +type FulfillmentItemCandidate = { + externalItemId: string + externalSkuCode: string + externalSkuName: string + externalSkuNameNormalized: string + matchedRule: Awaited> + resolvedSkuCode: string + binding: Awaited> + isConfigured: boolean +} + +export type ResolvedFulfillmentItem = FulfillmentItem & { + itemId: string + externalItemId: string + externalSkuCode: string + externalSkuName: string + skuCode: string + skuName: string + snapshot: Record + isConfigured: boolean +} + +type ShopIdCandidatesInput = { + shopId?: string + shopIdAliases?: string[] +} -/** - * @param {{ - * provider?: string - * platform?: string - * shopId?: string - * shopIdAliases?: string[] - * item?: FulfillmentItem - * }} params - */ export async function resolveOrderItemForFulfillment({ provider = '', platform = '', shopId = '', shopIdAliases = [], - item = /** @type {FulfillmentItem} */ ({}), -}) { + item = {}, +}: ResolveOrderItemForFulfillmentInput): Promise { const candidate = await resolveConfiguredItemCandidate({ provider, platform, @@ -87,22 +115,13 @@ export async function resolveOrderItemForFulfillment({ } } -/** - * @param {{ - * provider?: string - * platform?: string - * shopId?: string - * shopIdAliases?: string[] - * items?: FulfillmentItem[] - * }} params - */ export async function hasConfiguredOrderItems({ provider = '', platform = '', shopId = '', shopIdAliases = [], items = [], -}) { +}: HasConfiguredOrderItemsInput): Promise { const candidates = await Promise.all( (Array.isArray(items) ? items : []).map((item) => resolveConfiguredItemCandidate({ provider, @@ -116,7 +135,7 @@ export async function hasConfiguredOrderItems({ return candidates.some((item) => item.isConfigured) } -export function normalizeProductName(value) { +export function normalizeProductName(value: unknown): string { return String(value || '') .toLowerCase() .replace(/[【】\[\]()()]/g, ' ') @@ -126,7 +145,7 @@ export function normalizeProductName(value) { .trim() } -function pickFirstNonEmpty(values) { +function pickFirstNonEmpty(values: unknown[]): string { for (const value of values) { const normalized = String(value || '').trim() if (normalized) { @@ -142,8 +161,8 @@ async function resolveConfiguredItemCandidate({ platform = '', shopId = '', shopIdAliases = [], - item = /** @type {FulfillmentItem} */ ({}), -}) { + item = {}, +}: ResolveOrderItemForFulfillmentInput): Promise { const externalItemId = pickFirstNonEmpty([ item.externalItemId, item.itemId, @@ -213,7 +232,10 @@ async function resolveConfiguredItemCandidate({ } } -export function resolveShopIdCandidates({ shopId = '', shopIdAliases = [] } = {}) { +export function resolveShopIdCandidates({ + shopId = '', + shopIdAliases = [], +}: ShopIdCandidatesInput = {}): string[] { const candidates = [...new Set([ String(shopId || '').trim(), ...(Array.isArray(shopIdAliases) ? shopIdAliases : []).map((item) => String(item || '').trim()), @@ -222,7 +244,7 @@ export function resolveShopIdCandidates({ shopId = '', shopIdAliases = [] } = {} return candidates.length > 0 ? candidates : [''] } -function readConfigValue(rawValue, key) { +function readConfigValue(rawValue: unknown, key: string): string { if (!rawValue) { return '' } @@ -232,11 +254,10 @@ function readConfigValue(rawValue, key) { return '' } - const config = /** @type {Record} */ (parsed) - return String(config[key] || '').trim() + return String(parsed[key] || '').trim() } -function safeParseJson(rawValue) { +function safeParseJson(rawValue: unknown): unknown { try { return JSON.parse(String(rawValue || '{}')) } catch { @@ -244,10 +265,6 @@ function safeParseJson(rawValue) { } } -/** - * @param {unknown} value - * @returns {value is Record} - */ -function isPlainObject(value) { +function isPlainObject(value: unknown): value is Record { return Object.prototype.toString.call(value) === '[object Object]' } diff --git a/docs/backend-typescript-migration-plan.md b/docs/backend-typescript-migration-plan.md index b1dc663d..39839996 100644 --- a/docs/backend-typescript-migration-plan.md +++ b/docs/backend-typescript-migration-plan.md @@ -280,6 +280,14 @@ - `npm run typecheck` - `npm run build` - `npm test` 共 125 个用例通过 +34. 商品匹配服务迁移到 `.ts`: + - `src/services/order/product-match-service.ts` +35. 外部商品 item、匹配候选、解析后的履约商品、店铺候选与配置读取已显式类型化 +36. Docker 内验证通过: + - 商品匹配 / 订单 / webhook / bootstrap 相关 4 个测试文件共 13 个用例通过 + - `npm run typecheck` + - `npm run build` + - `npm test` 共 125 个用例通过 ## 下一步建议 @@ -287,6 +295,7 @@ 1. 继续迁移服务层中最核心、最常改的订单 / 履约 / claim 模块 2. 逐步移除服务层 `@ts-nocheck`,优先处理 webhook service、自动发货、delivery task +3. 为 delivery task 补直接单测后再迁移,避免只依赖订单链路间接覆盖 ## 执行原则