From 678b7bdf0ce6818b390b1d1d1c103fd7c8cd52b5 Mon Sep 17 00:00:00 2001 From: yml Date: Thu, 21 May 2026 14:35:08 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E8=BF=81=E7=A7=BB=E5=B1=A5?= =?UTF-8?q?=E7=BA=A6=E4=BB=BB=E5=8A=A1=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...sk-service.js => delivery-task-service.ts} | 144 +++++++++++++++--- docs/backend-typescript-migration-plan.md | 11 +- 2 files changed, 128 insertions(+), 27 deletions(-) rename apps/backend/src/services/order/{delivery-task-service.js => delivery-task-service.ts} (73%) diff --git a/apps/backend/src/services/order/delivery-task-service.js b/apps/backend/src/services/order/delivery-task-service.ts similarity index 73% rename from apps/backend/src/services/order/delivery-task-service.js rename to apps/backend/src/services/order/delivery-task-service.ts index 42cd638a..2a469509 100644 --- a/apps/backend/src/services/order/delivery-task-service.js +++ b/apps/backend/src/services/order/delivery-task-service.ts @@ -8,12 +8,102 @@ import { createTaskClaimToken } from '../claim/claim-service.js' import { notifyTaskAutoManualReview } from '../notification/domain-notifications.js' import { nowIso } from '../../utils/time.js' import { randomId } from '../../utils/random.js' +import type { InventoryItemRow, OrderItemRow, OrderRow, TaskRow } from '../../types/repository-rows.js' -export async function syncDeliveryTasksForOrder(order, orderItems) { +type JsonObject = Record + +type ClaimTokenLike = { + token: string + expired_at: string + [key: string]: unknown +} + +type FulfillmentBindingLike = { + id: number + profile_id?: number + profile_key?: string + profile_name?: string + name?: string + executor_key?: string + requires_claim?: boolean + auto_dispatch?: boolean + config_json?: string | JsonObject + [key: string]: unknown +} + +type FulfillmentRequirementLike = { + role_key?: string + roleKey?: string + credential_type?: string + credentialType?: string + is_required?: boolean + [key: string]: unknown +} + +type DeliveryTaskRow = TaskRow & { + skuCode?: string + skuName?: string +} + +type DeliveryTaskDeps = { + createTask?: typeof createTask + listTasksByOrderId?: typeof listTasksByOrderId + updateTask?: typeof updateTask + listFulfillmentProfileRequirements?: ( + profileId: number | string, + ) => Promise + resolveFulfillmentBinding?: (input: { + skuCode: string + provider?: string + platform?: string + shopId?: string + }) => Promise + reserveInventoryForTask?: (input: { + skuCode: string + taskId: number | string + credentialType?: string + roleKey?: string + }) => Promise + createTaskClaimToken?: (taskId: number | string) => Promise + notifyTaskAutoManualReview?: (payload: { + task: unknown + reason: string + source: string + }) => Promise | unknown + nowIso?: () => string + randomId?: (prefix?: string) => string +} + +type RuntimeDeliveryTaskDeps = Required> + +type TaskContext = { + inventorySkuCode?: string + primaryRequirement?: { + roleKey?: string + credentialType?: string + } | null + [key: string]: unknown +} + +export async function syncDeliveryTasksForOrder( + order: OrderRow, + orderItems: OrderItemRow[], +): Promise> { return syncDeliveryTasksForOrderWithDeps(order, orderItems) } -export async function syncDeliveryTasksForOrderWithDeps(order, orderItems, deps = {}) { +export async function syncDeliveryTasksForOrderWithDeps( + order: OrderRow, + orderItems: OrderItemRow[], + deps: DeliveryTaskDeps = {}, +): Promise> { const { createTask: createDeliveryTask = createTask, listTasksByOrderId: listTasks = listTasksByOrderId, @@ -27,7 +117,7 @@ export async function syncDeliveryTasksForOrderWithDeps(order, orderItems, deps randomId: createRandomId = randomId, } = deps - const runtimeDeps = { + const runtimeDeps: RuntimeDeliveryTaskDeps = { updateTask: updateDeliveryTask, reserveInventoryForTask: reserveInventory, createTaskClaimToken: createClaimToken, @@ -50,7 +140,7 @@ export async function syncDeliveryTasksForOrderWithDeps(order, orderItems, deps }, runtimeDeps))) } - const tasks = [] + const tasks: DeliveryTaskRow[] = [] for (const item of orderItems) { const binding = await resolveBinding({ @@ -68,9 +158,9 @@ export async function syncDeliveryTasksForOrderWithDeps(order, orderItems, deps const primaryRequirement = requirements.find((requirement) => requirement.is_required !== false) || requirements[0] || null const quantity = Math.max(1, Number(item.quantity || 1)) const fulfillmentConfig = parseJsonObject(profile.config_json) - const kuaishouShopConfig = fulfillmentConfig.kuaishouShop && typeof fulfillmentConfig.kuaishouShop === 'object' - ? fulfillmentConfig.kuaishouShop - : {} + const cloudtentaclesConfig = parseJsonObject(fulfillmentConfig.cloudtentacles) + const kuaishouConsumeConfig = parseJsonObject(fulfillmentConfig.kuaishouConsume) + const kuaishouShopConfig = parseJsonObject(fulfillmentConfig.kuaishouShop) for (let index = 0; index < quantity; index += 1) { const createdAt = getNowIso() @@ -133,9 +223,9 @@ export async function syncDeliveryTasksForOrderWithDeps(order, orderItems, deps }, binding: { prepareStatus: 'pending', - cloudSourceKey: String(fulfillmentConfig.cloudtentacles?.cloudSourceKey || 'default').trim() || 'default', - skuId: Number(fulfillmentConfig.cloudtentacles?.skuId || 0) || 0, - skuName: String(fulfillmentConfig.cloudtentacles?.skuName || '').trim(), + cloudSourceKey: String(cloudtentaclesConfig.cloudSourceKey || 'default').trim() || 'default', + skuId: Number(cloudtentaclesConfig.skuId || 0) || 0, + skuName: String(cloudtentaclesConfig.skuName || '').trim(), vnKey: '1', vnId: 0, vnPhone: '', @@ -155,8 +245,8 @@ export async function syncDeliveryTasksForOrderWithDeps(order, orderItems, deps rawInfo: null, }, purchase: { - autoBuyEnabled: fulfillmentConfig.cloudtentacles?.autoBuyEnabled !== false, - minAssetReserve: Number(fulfillmentConfig.cloudtentacles?.minAssetReserve || 0) || 0, + autoBuyEnabled: cloudtentaclesConfig.autoBuyEnabled !== false, + minAssetReserve: Number(cloudtentaclesConfig.minAssetReserve || 0) || 0, usedKnapsack: false, purchaseTriggered: false, assetBefore: 0, @@ -174,13 +264,13 @@ export async function syncDeliveryTasksForOrderWithDeps(order, orderItems, deps status: 'pending', returnedAt: null, returnedBy: null, - autoReturnEnabled: fulfillmentConfig.cloudtentacles?.autoReturnNumberAfterDispatch === true, + autoReturnEnabled: cloudtentaclesConfig.autoReturnNumberAfterDispatch === true, }, consume: { status: 'pending', - shopId: String(fulfillmentConfig.kuaishouConsume?.shopId || kuaishouShopConfig.shopId || '').trim(), - shopName: String(fulfillmentConfig.kuaishouConsume?.shopName || kuaishouShopConfig.kshopName || order.shop_name || '').trim(), - autoConsumeEnabled: fulfillmentConfig.kuaishouConsume?.autoConsumeAfterDispatch === true, + shopId: String(kuaishouConsumeConfig.shopId || kuaishouShopConfig.shopId || '').trim(), + shopName: String(kuaishouConsumeConfig.shopName || kuaishouShopConfig.kshopName || order.shop_name || '').trim(), + autoConsumeEnabled: kuaishouConsumeConfig.autoConsumeAfterDispatch === true, consumedAt: null, errorMessage: '', }, @@ -207,7 +297,10 @@ export async function syncDeliveryTasksForOrderWithDeps(order, orderItems, deps return Promise.all(tasks.map((task) => preparePaidTask(task, runtimeDeps))) } -async function preparePaidTask(task, deps = {}) { +async function preparePaidTask( + task: DeliveryTaskRow, + deps: Partial = {}, +): Promise { const { updateTask: updateDeliveryTask = updateTask, reserveInventoryForTask: reserveInventory = reserveInventoryForTask, @@ -344,7 +437,7 @@ async function preparePaidTask(task, deps = {}) { }) } -function resolvePaidTaskStatus(profile) { +function resolvePaidTaskStatus(profile: FulfillmentBindingLike | null | undefined): string { if (Boolean(profile?.requires_claim)) { return 'paid' } @@ -360,7 +453,7 @@ function resolvePaidTaskStatus(profile) { return 'paid' } -function parseTaskContext(task) { +function parseTaskContext(task: { context_json?: unknown } | null | undefined): TaskContext { const value = task?.context_json if (!value) { @@ -368,33 +461,34 @@ function parseTaskContext(task) { } if (typeof value === 'object') { - return value + return value as TaskContext } try { - return JSON.parse(String(value || '{}')) + const parsed = JSON.parse(String(value || '{}')) + return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as TaskContext : {} } catch { return {} } } -function parseJsonObject(value) { +function parseJsonObject(value: unknown): JsonObject { if (!value) { return {} } if (typeof value === 'object' && !Array.isArray(value)) { - return value + return value as JsonObject } try { const parsed = JSON.parse(String(value || '{}')) - return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {} + return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as JsonObject : {} } catch { return {} } } -function isKuaishouCloudExecutor(value) { +function isKuaishouCloudExecutor(value: unknown): boolean { return String(value || '').trim() === 'kuaishou_ct_assisted' } diff --git a/docs/backend-typescript-migration-plan.md b/docs/backend-typescript-migration-plan.md index 9e411b32..967fba7a 100644 --- a/docs/backend-typescript-migration-plan.md +++ b/docs/backend-typescript-migration-plan.md @@ -297,14 +297,21 @@ - `npm run typecheck` - `npm run build` - `npm test` 共 128 个用例通过 +39. delivery task 服务迁移到 `.ts`: + - `src/services/order/delivery-task-service.ts` +40. 订单行、订单商品、履约 profile / requirement、依赖注入、任务上下文、JSON 配置读取已显式类型化 +41. Docker 内验证通过: + - delivery task / 订单 / webhook / open91 相关 4 个测试文件共 18 个用例通过 + - `npm run typecheck` + - `npm run build` + - `npm test` 共 128 个用例通过 ## 下一步建议 第一批继续推进时,建议按这个顺序: 1. 继续迁移服务层中最核心、最常改的订单 / 履约 / claim 模块 -2. 迁移 delivery task 服务到 `.ts` -3. 逐步移除服务层 `@ts-nocheck`,优先处理 webhook service、自动发货 +2. 逐步移除服务层 `@ts-nocheck`,优先处理 webhook service、自动发货 ## 执行原则