diff --git a/apps/backend/src/repositories/admin-user-repo.js b/apps/backend/src/repositories/admin-user-repo.ts similarity index 62% rename from apps/backend/src/repositories/admin-user-repo.js rename to apps/backend/src/repositories/admin-user-repo.ts index 763ad865..db686581 100644 --- a/apps/backend/src/repositories/admin-user-repo.js +++ b/apps/backend/src/repositories/admin-user-repo.ts @@ -1,5 +1,43 @@ import { query, withTransaction } from '../db/client.js' +type AdminUserRow = { + id: number + username: string + password_hash: string + role: string + status: string + created_at: string + updated_at: string + inventory_group_codes: string[] +} + +type AdminUserCreateInput = { + username: string + passwordHash: string + role: string + status: string + createdAt: string + updatedAt: string +} + +type AdminUserPatch = Partial> + +type AdminUserListInput = { + page?: number + pageSize?: number + username?: string + role?: string + status?: string +} + +type AdminUserListResult = { + items: AdminUserRow[] + total: number +} + const ADMIN_USER_SELECT = ` SELECT au.*, @@ -12,24 +50,24 @@ const ADMIN_USER_SELECT = ` ) bindings ON TRUE ` -export async function getAdminUserById(userId) { - const result = await query( +export async function getAdminUserById(userId: number | string): Promise { + const result = await query( `${ADMIN_USER_SELECT} WHERE au.id = $1 LIMIT 1`, [Number(userId)], ) return result.rows[0] || null } -export async function getAdminUserByUsername(username) { - const result = await query( +export async function getAdminUserByUsername(username: string): Promise { + const result = await query( `${ADMIN_USER_SELECT} WHERE au.username = $1 LIMIT 1`, [String(username || '').trim().toLowerCase()], ) return result.rows[0] || null } -export async function createAdminUser(input) { - const result = await query( +export async function createAdminUser(input: AdminUserCreateInput): Promise { + const result = await query<{ [column: string]: unknown, id: number }>( ` INSERT INTO admin_users ( username, @@ -54,14 +92,17 @@ export async function createAdminUser(input) { return getAdminUserById(result.rows[0]?.id || 0) } -export async function updateAdminUser(userId, patch) { +export async function updateAdminUser( + userId: number | string, + patch: AdminUserPatch, +): Promise { const current = await getAdminUserById(userId) if (!current) { return null } const next = { ...current, ...patch } - const result = await query( + const result = await query<{ [column: string]: unknown, id: number }>( ` UPDATE admin_users SET @@ -86,10 +127,16 @@ export async function updateAdminUser(userId, patch) { return getAdminUserById(result.rows[0]?.id || 0) } -export async function listAdminUsers({ page = 1, pageSize = 20, username = '', role = '', status = '' } = {}) { +export async function listAdminUsers({ + page = 1, + pageSize = 20, + username = '', + role = '', + status = '', +}: AdminUserListInput = {}): Promise { const offset = (page - 1) * pageSize - const filters = [] - const params = [] + const filters: string[] = [] + const params: unknown[] = [] if (username) { params.push(`%${username}%`) @@ -107,11 +154,14 @@ export async function listAdminUsers({ page = 1, pageSize = 20, username = '', r } const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '' - const totalResult = await query(`SELECT COUNT(*)::int AS total FROM admin_users ${whereClause}`, params) + const totalResult = await query<{ [column: string]: unknown, total: number }>( + `SELECT COUNT(*)::int AS total FROM admin_users ${whereClause}`, + params, + ) params.push(pageSize) params.push(offset) - const itemsResult = await query( + const itemsResult = await query( ` ${ADMIN_USER_SELECT} ${whereClause} @@ -127,14 +177,18 @@ export async function listAdminUsers({ page = 1, pageSize = 20, username = '', r } } -export async function countActiveAdminUsers() { - const result = await query( +export async function countActiveAdminUsers(): Promise { + const result = await query<{ [column: string]: unknown, total: number }>( `SELECT COUNT(*)::int AS total FROM admin_users WHERE role = 'admin' AND status = 'active'`, ) return Number(result.rows[0]?.total || 0) } -export async function replaceAdminUserInventoryGroupBindings(userId, inventoryGroupCodes = [], timestamp) { +export async function replaceAdminUserInventoryGroupBindings( + userId: number | string, + inventoryGroupCodes: unknown[] = [], + timestamp: string, +): Promise { const normalizedUserId = Number(userId) const normalizedCodes = Array.from(new Set((Array.isArray(inventoryGroupCodes) ? inventoryGroupCodes : []) .map((value) => String(value || '').trim()) diff --git a/apps/backend/src/repositories/fulfillment-profile-repo.js b/apps/backend/src/repositories/fulfillment-profile-repo.ts similarity index 58% rename from apps/backend/src/repositories/fulfillment-profile-repo.js rename to apps/backend/src/repositories/fulfillment-profile-repo.ts index 4cbdc9e2..f1027ace 100644 --- a/apps/backend/src/repositories/fulfillment-profile-repo.js +++ b/apps/backend/src/repositories/fulfillment-profile-repo.ts @@ -1,15 +1,106 @@ import { query, withTransaction } from '../db/client.js' -export async function getFulfillmentProfileByKey(profileKey) { - const result = await query( +type FulfillmentProfileRow = { + id: number + profile_key: string + name: string + executor_key: string + requires_claim: boolean + auto_dispatch: boolean + inventory_strategy: string + config_json: string | Record + created_at: string + updated_at: string +} + +type FulfillmentProfileRequirementRow = { + id: number + profile_id: number + role_key: string + roleKey?: string + credential_type: string + credentialType?: string + quantity_per_unit: number + is_required: boolean + config_json: string | Record + created_at: string + updated_at: string +} + +type SkuFulfillmentBindingRow = { + id: number + sku_code: string + provider: string + platform: string + shop_id: string + profile_id: number + enabled: boolean + priority: number + config_json: string | Record + created_at: string + updated_at: string + profile_key?: string + name?: string + profile_name?: string + executor_key?: string + requires_claim?: boolean + auto_dispatch?: boolean + inventory_strategy?: string + profile_config_json?: string | Record +} + +type FulfillmentProfileUpsertInput = { + profileKey: string + name: string + executorKey: string + requiresClaim?: boolean + autoDispatch?: boolean + inventoryStrategy?: string + configJson?: string | Record + createdAt: string + updatedAt: string +} + +type FulfillmentProfileRequirementInput = { + roleKey: string + credentialType: string + quantityPerUnit?: number | string + isRequired?: boolean + configJson?: string | Record +} + +type SkuFulfillmentBindingUpsertInput = { + skuCode: string + provider?: string + platform?: string + shopId?: string + profileId: number | string + enabled?: boolean + priority?: number | string + configJson?: string | Record + createdAt: string + updatedAt: string +} + +type FulfillmentBindingResolveInput = { + skuCode: string + provider?: string + platform?: string + shopId?: string +} + +export async function getFulfillmentProfileByKey(profileKey: string): Promise { + const result = await query( 'SELECT * FROM fulfillment_profiles WHERE profile_key = $1 LIMIT 1', [String(profileKey || '').trim()], ) return result.rows[0] || null } -export async function upsertFulfillmentProfile(input) { - const result = await query( +export async function upsertFulfillmentProfile( + input: FulfillmentProfileUpsertInput, +): Promise { + const result = await query( ` INSERT INTO fulfillment_profiles ( profile_key, @@ -49,7 +140,11 @@ export async function upsertFulfillmentProfile(input) { return result.rows[0] || null } -export async function replaceFulfillmentProfileRequirements(profileId, requirements = [], timestamp) { +export async function replaceFulfillmentProfileRequirements( + profileId: number | string, + requirements: FulfillmentProfileRequirementInput[] = [], + timestamp: string, +): Promise { await withTransaction(async (client) => { await client.query( 'DELETE FROM fulfillment_profile_requirements WHERE profile_id = $1', @@ -85,8 +180,10 @@ export async function replaceFulfillmentProfileRequirements(profileId, requireme }) } -export async function listFulfillmentProfileRequirements(profileId) { - const result = await query( +export async function listFulfillmentProfileRequirements( + profileId: number | string, +): Promise { + const result = await query( ` SELECT * FROM fulfillment_profile_requirements @@ -99,8 +196,10 @@ export async function listFulfillmentProfileRequirements(profileId) { return result.rows } -export async function upsertSkuFulfillmentBinding(input) { - const existing = await query( +export async function upsertSkuFulfillmentBinding( + input: SkuFulfillmentBindingUpsertInput, +): Promise { + const existing = await query( ` SELECT * FROM sku_fulfillment_bindings @@ -111,7 +210,7 @@ export async function upsertSkuFulfillmentBinding(input) { ) if (!existing.rows[0]) { - const inserted = await query( + const inserted = await query( ` INSERT INTO sku_fulfillment_bindings ( sku_code, @@ -144,7 +243,7 @@ export async function upsertSkuFulfillmentBinding(input) { return inserted.rows[0] || null } - const updated = await query( + const updated = await query( ` UPDATE sku_fulfillment_bindings SET @@ -169,8 +268,13 @@ export async function upsertSkuFulfillmentBinding(input) { return updated.rows[0] || null } -export async function resolveFulfillmentBinding({ skuCode, provider = '', platform = '', shopId = '' }) { - const result = await query( +export async function resolveFulfillmentBinding({ + skuCode, + provider = '', + platform = '', + shopId = '', +}: FulfillmentBindingResolveInput): Promise { + const result = await query( ` SELECT sfb.*, diff --git a/apps/backend/src/repositories/product-match-rule-repo.js b/apps/backend/src/repositories/product-match-rule-repo.ts similarity index 74% rename from apps/backend/src/repositories/product-match-rule-repo.js rename to apps/backend/src/repositories/product-match-rule-repo.ts index 1d88ab4b..9c77b611 100644 --- a/apps/backend/src/repositories/product-match-rule-repo.js +++ b/apps/backend/src/repositories/product-match-rule-repo.ts @@ -1,7 +1,53 @@ import { query } from '../db/client.js' -export async function upsertProductMatchRule(input) { - const result = await query( +type ProductMatchRuleRow = { + id: number + provider: string + platform: string + shop_id: string + external_item_id: string + external_sku_code: string + external_sku_name: string + external_sku_name_normalized: string + resolved_sku_code: string + enabled: boolean + priority: number + config_json: string | Record + created_at: string + updated_at: string + matched_by?: string + match_score?: number +} + +type ProductMatchRuleUpsertInput = { + provider?: string + platform?: string + shopId?: string + externalItemId?: string + externalSkuCode?: string + externalSkuName?: string + externalSkuNameNormalized?: string + resolvedSkuCode: string + enabled?: boolean + priority?: number | string + configJson?: string | Record + createdAt: string + updatedAt: string +} + +type ProductMatchRuleResolveInput = { + provider?: string + platform?: string + shopId?: string + externalItemId?: string + externalSkuCode?: string + externalSkuNameNormalized?: string +} + +export async function upsertProductMatchRule( + input: ProductMatchRuleUpsertInput, +): Promise { + const result = await query( ` INSERT INTO product_match_rules ( provider, @@ -64,8 +110,8 @@ export async function resolveProductMatchRule({ externalItemId = '', externalSkuCode = '', externalSkuNameNormalized = '', -}) { - const result = await query( +}: ProductMatchRuleResolveInput): Promise { + const result = await query( ` SELECT pmr.*, diff --git a/docs/backend-typescript-migration-plan.md b/docs/backend-typescript-migration-plan.md index 110c3081..66769d89 100644 --- a/docs/backend-typescript-migration-plan.md +++ b/docs/backend-typescript-migration-plan.md @@ -224,12 +224,16 @@ - `src/repositories/message-delivery-repo.ts` - `src/repositories/webhook-event-repo.ts` 13. `MessageDeliveryRow` 与列表查询结果已进入共享 repository row 类型 +14. 小型 repository 第三批已迁移到 `.ts`: + - `src/repositories/admin-user-repo.ts` + - `src/repositories/product-match-rule-repo.ts` + - `src/repositories/fulfillment-profile-repo.ts` ## 下一步建议 第一批继续推进时,建议按这个顺序: -1. 继续迁移剩余 repository:`admin-user-repo`、`inventory-repo`、`order-repo`、`task-repo` +1. 继续迁移剩余核心 repository:`inventory-repo`、`order-repo`、`task-repo` 2. 拆分并迁移 `runtime.js`,把 env 解析、默认配置加载、配置合并分开 3. 为 webhook、库存换码、自动发货补测试